diff --git a/.github/scripts/build-cmake.sh b/.github/scripts/build-cmake.sh
new file mode 100755
index 0000000..55b1587
--- /dev/null
+++ b/.github/scripts/build-cmake.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -e
+cmake -B build-cmake -G Ninja
+cmake --build build-cmake || {
+ echo "=== build failed, listing generated shared assets for diagnosis ==="
+ ls -la build-cmake/ngdevkit-assets || true
+ exit 1
+}
diff --git a/.github/workflows/build-tests.yaml b/.github/workflows/build-tests.yaml
index 3b13f98..54161f4 100644
--- a/.github/workflows/build-tests.yaml
+++ b/.github/workflows/build-tests.yaml
@@ -44,6 +44,43 @@ jobs:
- name: Build examples
run: ./.github/scripts/build.sh
+ linux-cmake:
+ name: "Linux CMake build"
+ runs-on: ubuntu-slim
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Launchpad PPA
+ id: launchpad_ppa
+ run: |
+ sudo add-apt-repository -y ppa:dciabrin/ngdevkit
+ sudo apt-get update -y
+ toolchain_version=$(sudo apt-cache policy ngdevkit-toolchain | awk '/Candidate:/ {print $2}')
+ arch=$(dpkg --print-architecture)
+ echo "toolchain_deb=ngdevkit-toolchain_${toolchain_version}_${arch}.deb" >> "$GITHUB_OUTPUT"
+
+ - name: Cache ngdevkit-toolchain package
+ uses: actions/cache@v5
+ with:
+ path: /tmp/${{ steps.launchpad_ppa.outputs.toolchain_deb }}
+ key: ${{ steps.launchpad_ppa.outputs.toolchain_deb }}
+
+ - name: Prepare cached dependency
+ run: |
+ if [ ! -f /tmp/${{ steps.launchpad_ppa.outputs.toolchain_deb }} ]; then
+ cd /tmp && sudo apt-get download ngdevkit-toolchain
+ fi
+ sudo cp /tmp/${{ steps.launchpad_ppa.outputs.toolchain_deb }} /var/cache/apt/archives
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get install -y ngdevkit ngdevkit-gngeo
+ sudo apt-get install -y pkg-config cmake ninja-build imagemagick sox libsox-fmt-mp3
+
+ - name: Build examples
+ run: ./.github/scripts/build-cmake.sh
+
win:
name: "Windows native MSYS2 build"
runs-on: windows-latest
@@ -77,6 +114,39 @@ jobs:
- name: Build examples
run: ./.github/scripts/build.sh
+ win-cmake:
+ name: "Windows native MSYS2 CMake build"
+ runs-on: windows-latest
+ defaults:
+ run:
+ shell: msys2 {0}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install MSYS2
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: UCRT64
+ update: true
+ install:
+ git zip pactoys
+ mingw-w64-ucrt-x86_64-pkg-config
+ mingw-w64-ucrt-x86_64-python
+ mingw-w64-ucrt-x86_64-python-pillow
+ mingw-w64-ucrt-x86_64-python-ruamel-yaml
+
+ - name: Install ngdevkit dependencies
+ run: |
+ echo -e "[ngdevkit]\nSigLevel = Optional TrustAll\nServer = https://dciabrin.net/msys2-ngdevkit/\$arch" >> /etc/pacman.conf
+ pacman -Sy
+ pacboy -S --noconfirm ngdevkit:u ngdevkit-gngeo:u toolchain:u zlib:u sox:u imagemagick:u cmake:u ninja:u
+
+ - name: Build examples
+ run: ./.github/scripts/build-cmake.sh
+
macos:
name: "macOS build"
runs-on: macos-15
@@ -87,8 +157,28 @@ jobs:
- name: Dependencies
run: |
brew tap dciabrin/ngdevkit
+ brew trust dciabrin/ngdevkit
brew install automake zip pkg-config
brew install ngdevkit ngdevkit-gngeo imagemagick sox glew make
- name: Build examples
run: ./.github/scripts/build.sh
+
+ macos-cmake:
+ name: "macOS CMake build"
+ runs-on: macos-15
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Dependencies
+ run: |
+ brew tap dciabrin/ngdevkit
+ brew trust dciabrin/ngdevkit
+ brew install cmake ninja pkg-config
+ brew install ngdevkit ngdevkit-gngeo imagemagick sox glew
+
+ - name: Build examples
+ run: |
+ export PATH=$HOMEBREW_PREFIX/opt/python3/bin:$PATH
+ ./.github/scripts/build-cmake.sh
diff --git a/.gitignore b/.gitignore
index 3b370a8..c5aa15f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,10 @@ assets/
assets/*.wav
.prebuild
+# cmake build directories and local presets
+build-cmake*/
+CMakeUserPresets.json
+
# generated ROM file
*.zip
diff --git a/00-template/CMakeLists.txt b/00-template/CMakeLists.txt
new file mode 100644
index 0000000..412ab46
--- /dev/null
+++ b/00-template/CMakeLists.txt
@@ -0,0 +1,44 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# This is the CMake counterpart of the template's Makefile. The helper
+# functions used below are defined in cmake/NGDevKit.cmake.
+
+# program ROM: your main program.
+# List your sources below; they are compiled into an ELF binary that
+# becomes the content of the program ROM (PROM1).
+# Default compile flags can be replaced per target with CFLAGS, and
+# linker flags added with LINK_OPTIONS.
+ngdevkit_elf(00-template-rom
+ SOURCES main.c
+)
+
+# The cartridge: all the ROM chips, the cartridge zip, the emulator
+# hash files, plus run targets (00-template-gngeo, 00-template-mame, ...).
+#
+# By default:
+# . the fixed tiles ROM (SROM) holds latin fonts for printing text
+# . the sprite ROM (CROM1/CROM2) holds the ngdevkit logo shown
+# during the attract mode
+# . the sound driver ROM (MROM) holds ngdevkit's base sound driver
+# . the sample ROM (VROM) is empty
+# Override them with the SROM, CROM1/CROM2, SOUND_DRIVER and VROM
+# arguments; see the other examples for how to convert your own assets.
+ngdevkit_cartridge(
+ GAMEROM ngdevkit
+ TITLE "ngdevkit template ROM"
+ ELF 00-template-rom
+)
diff --git a/01-helloworld/CMakeLists.txt b/01-helloworld/CMakeLists.txt
new file mode 100644
index 0000000..63cebe7
--- /dev/null
+++ b/01-helloworld/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+ngdevkit_elf(01-helloworld-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 01_helloworld
+ TITLE "Simple hello world"
+ ELF 01-helloworld-rom
+)
diff --git a/02-sprite/CMakeLists.txt b/02-sprite/CMakeLists.txt
new file mode 100644
index 0000000..f9d644c
--- /dev/null
+++ b/02-sprite/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# this example displays the ngdevkit logo as a movable sprite, so it
+# embeds a full (unclipped) conversion of the shared logo image
+set(logo ${CMAKE_CURRENT_BINARY_DIR}/gen/assets/logo.gif)
+add_custom_command(OUTPUT ${logo}
+ COMMAND ${CMAKE_COMMAND} -E copy ${NGDK_LOGO_GIF} ${logo}
+ DEPENDS ${NGDK_LOGO_GIF}
+ VERBATIM)
+ngdevkit_sprite_tiles(LOGO_C1 LOGO_C2 ${logo})
+
+ngdevkit_elf(02-sprite-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 02_sprite
+ TITLE "Setting up a sprite"
+ ELF 02-sprite-rom
+ CROM1 ${NGDK_BASE_CROM_C1} ${LOGO_C1}
+ CROM2 ${NGDK_BASE_CROM_C2} ${LOGO_C2}
+)
diff --git a/03-sprite-animation/CMakeLists.txt b/03-sprite-animation/CMakeLists.txt
new file mode 100644
index 0000000..68ecfff
--- /dev/null
+++ b/03-sprite-animation/CMakeLists.txt
@@ -0,0 +1,55 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+set(gfx ${CMAKE_CURRENT_SOURCE_DIR}/setup/prepare-hero/gfx)
+
+# the original files for this demo need to get their transparent color fixed
+foreach(anim IN ITEMS idle walk)
+ add_custom_command(OUTPUT ${gen}/${anim}.gif
+ COMMAND ${NGDK_SH} -c "cp '${gfx}/spr_m_traveler_${anim}_anim.gif' '${gen}/${anim}.gif' && printf '\\xff\\x00\\xff' | dd of='${gen}/${anim}.gif' bs=1 seek=13 count=3 conv=notrunc status=none"
+ DEPENDS ${gfx}/spr_m_traveler_${anim}_anim.gif
+ COMMENT "Preparing ${anim}.gif"
+ VERBATIM)
+endforeach()
+
+# we want all the sprites to share the same palette, so for simplicity
+# in this demo we merge all the frames into a single file with imagemagick
+add_custom_command(OUTPUT ${gen}/assets/all.gif
+ COMMAND ${NGDK_CONVERT} ${gen}/idle.gif ${gen}/walk.gif -coalesce -append
+ +repage "xc:#ff00ff[64x1024!]" -compose dstover -compose dstover
+ -fill "#00ffff" -opaque "#000100" -fill "#00ffff" -opaque "#000000"
+ -fill "#181818" -opaque "#00ffff" -fill "#000000" -opaque "#ff00ff"
+ -composite ${gen}/assets/all.gif
+ DEPENDS ${gen}/idle.gif ${gen}/walk.gif
+ COMMENT "Merging animation frames into all.gif"
+ VERBATIM)
+
+ngdevkit_sprite_tiles(ALL_C1 ALL_C2 ${gen}/assets/all.gif)
+ngdevkit_palette(ALL_PAL ${gen}/assets/all.gif)
+
+ngdevkit_elf(03-sprite-animation-rom
+ SOURCES main.c
+ GENERATED ${ALL_PAL}
+)
+
+ngdevkit_cartridge(
+ GAMEROM 03_sprite_anim
+ TITLE "Sprite animation"
+ ELF 03-sprite-animation-rom
+ CROM1 ${NGDK_BASE_CROM_C1} ${ALL_C1}
+ CROM2 ${NGDK_BASE_CROM_C2} ${ALL_C2}
+)
diff --git a/04-palette/CMakeLists.txt b/04-palette/CMakeLists.txt
new file mode 100644
index 0000000..d011a07
--- /dev/null
+++ b/04-palette/CMakeLists.txt
@@ -0,0 +1,44 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+
+# palette data for all the gradients displayed by the example
+add_custom_command(OUTPUT ${gen}/gradients.pal
+ COMMAND ${NGDK_SH} -c "'${NGDK_PYTHON}' '${CMAKE_CURRENT_SOURCE_DIR}/mk-gradients.py' > '${gen}/gradients.pal'"
+ DEPENDS mk-gradients.py
+ COMMENT "Generating gradients.pal"
+ VERBATIM)
+
+# a gradient image, converted to fixed tiles to visualize the palettes
+add_custom_command(OUTPUT ${gen}/assets/gradient.gif
+ COMMAND ${NGDK_CONVERT} -size 16x1 gradient:black-white -scale 800%
+ ${gen}/assets/gradient.gif
+ COMMENT "Generating gradient.gif"
+ VERBATIM)
+ngdevkit_fix_tiles(GRADIENT_FIX ${gen}/assets/gradient.gif)
+
+ngdevkit_elf(04-palette-rom
+ SOURCES main.c
+ GENERATED ${gen}/gradients.pal
+)
+
+ngdevkit_cartridge(
+ GAMEROM 04_palette
+ TITLE "Palette test"
+ ELF 04-palette-rom
+ SROM ${NGDK_BASE_SROM_FIX} ${GRADIENT_FIX}
+)
diff --git a/05-scrolling/CMakeLists.txt b/05-scrolling/CMakeLists.txt
new file mode 100644
index 0000000..b84641a
--- /dev/null
+++ b/05-scrolling/CMakeLists.txt
@@ -0,0 +1,66 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+set(gfx ${CMAKE_CURRENT_SOURCE_DIR}/setup/prepare-backgrounds/gfx)
+
+# the original art for this demo needs to get cropped to a specific size
+
+add_custom_command(OUTPUT ${gen}/assets/back.gif
+ COMMAND ${NGDK_CONVERT} ${gfx}/country-platform-back.png
+ -crop 384x144+0+16 +repage ${gen}/assets/back.gif
+ DEPENDS ${gfx}/country-platform-back.png
+ COMMENT "Preparing back.gif"
+ VERBATIM)
+
+add_custom_command(OUTPUT ${gen}/assets/forest.gif
+ COMMAND ${NGDK_CONVERT} ${gfx}/country-platform-forest.png
+ ${gfx}/country-platform-forest.png ${gfx}/country-platform-forest.png
+ +append -crop 480x144+0+80 +repage -background black -flatten
+ ${gen}/assets/forest.gif
+ DEPENDS ${gfx}/country-platform-forest.png
+ COMMENT "Preparing forest.gif"
+ VERBATIM)
+
+add_custom_command(OUTPUT ${gen}/assets/front.gif
+ COMMAND ${NGDK_CONVERT} ${gfx}/country-platform-tiles-example.png
+ ${gfx}/country-platform-tiles-example.png
+ +append -crop 384x144+0+80 +repage -background black -flatten
+ ${gen}/assets/front.gif
+ DEPENDS ${gfx}/country-platform-tiles-example.png
+ COMMENT "Preparing front.gif"
+ VERBATIM)
+
+set(pals)
+foreach(layer IN ITEMS back forest front)
+ string(TOUPPER ${layer} LAYER)
+ ngdevkit_sprite_tiles(${LAYER}_C1 ${LAYER}_C2 ${gen}/assets/${layer}.gif)
+ ngdevkit_palette(${LAYER}_PAL ${gen}/assets/${layer}.gif)
+ list(APPEND pals ${${LAYER}_PAL})
+endforeach()
+
+ngdevkit_elf(05-scrolling-rom
+ SOURCES main.c
+ GENERATED ${pals}
+)
+
+ngdevkit_cartridge(
+ GAMEROM 05_scrolling
+ TITLE "Parallax scrolling"
+ ELF 05-scrolling-rom
+ CROM1 ${NGDK_BASE_CROM_C1} ${BACK_C1} ${FOREST_C1} ${FRONT_C1}
+ CROM2 ${NGDK_BASE_CROM_C2} ${BACK_C2} ${FOREST_C2} ${FRONT_C2}
+)
diff --git a/06-sound-adpcma/CMakeLists.txt b/06-sound-adpcma/CMakeLists.txt
new file mode 100644
index 0000000..9776358
--- /dev/null
+++ b/06-sound-adpcma/CMakeLists.txt
@@ -0,0 +1,69 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+set(sfx ${CMAKE_CURRENT_SOURCE_DIR}/setup/prepare-sfx/sfx)
+
+# The original sound FX for this demo are mp3-encoded and need to get
+# cropped to a specific time window, hence these prepare steps. The
+# samples-map.yaml references the prepared files as assets/.wav.
+add_custom_command(OUTPUT ${gen}/assets/woosh.wav
+ COMMAND ${NGDK_SOX} -V1 ${sfx}/woosh.mp3 -c 1 -r 18500 ${gen}/assets/woosh.wav
+ silence 1 0.01 0.1% reverse silence 1 0.05 0.5% reverse
+ DEPENDS ${sfx}/woosh.mp3
+ COMMENT "Preparing woosh.wav" VERBATIM)
+add_custom_command(OUTPUT ${gen}/assets/lefthook.wav
+ COMMAND ${NGDK_SOX} -V1 ${sfx}/lefthook.mp3 -c 1 -r 18500 ${gen}/assets/lefthook.wav
+ silence 1 0.1 1% reverse silence 1 0.2 1% reverse
+ DEPENDS ${sfx}/lefthook.mp3
+ COMMENT "Preparing lefthook.wav" VERBATIM)
+add_custom_command(OUTPUT ${gen}/assets/lightbulbbreaking.wav
+ COMMAND ${NGDK_SOX} -V1 ${sfx}/lightbulbbreaking.mp3 -c 1 -r 18500
+ ${gen}/assets/lightbulbbreaking.wav
+ silence 1 0.1 1% reverse silence 1 0.1 1% reverse
+ DEPENDS ${sfx}/lightbulbbreaking.mp3
+ COMMENT "Preparing lightbulbbreaking.wav" VERBATIM)
+
+# sample ROM and the matching offsets for the sound driver
+ngdevkit_vrom_from_yaml(VROM
+ GAMEROM 06_sound_adpcma
+ YAML ${CMAKE_CURRENT_SOURCE_DIR}/assets/samples-map.yaml
+ SAMPLES_INC assets/samples.inc
+ STAGED ${gen}/assets/woosh.wav ${gen}/assets/lefthook.wav
+ ${gen}/assets/lightbulbbreaking.wav
+)
+
+# sound driver: nullsound + the example's sound commands
+ngdevkit_z80_rel(USER_COMMANDS_REL user_commands.s
+ DEPENDS ${gen}/assets/samples.inc
+)
+ngdevkit_sound_driver(SOUND_DRIVER
+ NAME demo_driver
+ INPUTS ${NGDK_EYE_CATCHER_LIB} ${USER_COMMANDS_REL}
+ DEPENDS ${NGDK_SHARED_ASSETS_TARGET}
+)
+
+ngdevkit_elf(06-sound-adpcma-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 06_sound_adpcma
+ TITLE "ADPCM-A sound FX"
+ ELF 06-sound-adpcma-rom
+ SOUND_DRIVER ${SOUND_DRIVER}
+ VROM ${VROM}
+)
diff --git a/07-attract-and-game/CMakeLists.txt b/07-attract-and-game/CMakeLists.txt
new file mode 100644
index 0000000..f501c5e
--- /dev/null
+++ b/07-attract-and-game/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+ngdevkit_elf(07-attract-and-game-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 07_attract_and_game
+ TITLE "Game state machine"
+ ELF 07-attract-and-game-rom
+)
diff --git a/08-software-dips/CMakeLists.txt b/08-software-dips/CMakeLists.txt
new file mode 100644
index 0000000..f91d099
--- /dev/null
+++ b/08-software-dips/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+ngdevkit_elf(08-software-dips-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 08_soft_dips
+ TITLE "MVS Software Dips"
+ ELF 08-software-dips-rom
+)
diff --git a/09-horizontal-sync/CMakeLists.txt b/09-horizontal-sync/CMakeLists.txt
new file mode 100644
index 0000000..f1e8eaf
--- /dev/null
+++ b/09-horizontal-sync/CMakeLists.txt
@@ -0,0 +1,45 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+
+# precomputed wobble offsets for the horizontal sync effect
+add_custom_command(OUTPUT ${gen}/precalc.inc
+ COMMAND ${NGDK_SH} -c "'${NGDK_PYTHON}' '${CMAKE_CURRENT_SOURCE_DIR}/gen-wobble.py' 26 70 64 > '${gen}/precalc.inc'"
+ DEPENDS gen-wobble.py
+ COMMENT "Generating precalc.inc"
+ VERBATIM)
+
+# the ngdevkit logo as a movable sprite
+set(logo ${CMAKE_CURRENT_BINARY_DIR}/gen/assets/logo.gif)
+add_custom_command(OUTPUT ${logo}
+ COMMAND ${CMAKE_COMMAND} -E copy ${NGDK_LOGO_GIF} ${logo}
+ DEPENDS ${NGDK_LOGO_GIF}
+ VERBATIM)
+ngdevkit_sprite_tiles(LOGO_C1 LOGO_C2 ${logo})
+
+ngdevkit_elf(09-horizontal-sync-rom
+ SOURCES main.c
+ GENERATED ${gen}/precalc.inc
+)
+
+ngdevkit_cartridge(
+ GAMEROM 09_hsync
+ TITLE "Horizontal sync"
+ ELF 09-horizontal-sync-rom
+ CROM1 ${NGDK_BASE_CROM_C1} ${LOGO_C1}
+ CROM2 ${NGDK_BASE_CROM_C2} ${LOGO_C2}
+)
diff --git a/10-credits-management/CMakeLists.txt b/10-credits-management/CMakeLists.txt
new file mode 100644
index 0000000..b6325ba
--- /dev/null
+++ b/10-credits-management/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+ngdevkit_elf(10-credits-management-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 10_credits
+ TITLE "Credits management"
+ ELF 10-credits-management-rom
+)
diff --git a/11-backup-ram/CMakeLists.txt b/11-backup-ram/CMakeLists.txt
new file mode 100644
index 0000000..21f43c5
--- /dev/null
+++ b/11-backup-ram/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+ngdevkit_elf(11-backup-ram-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 11_backup_ram
+ TITLE "Backup RAM"
+ ELF 11-backup-ram-rom
+)
diff --git a/12-prom-1mb/CMakeLists.txt b/12-prom-1mb/CMakeLists.txt
new file mode 100644
index 0000000..8805e12
--- /dev/null
+++ b/12-prom-1mb/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+ngdevkit_elf(12-prom-1mb-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 12_prom_1mb
+ TITLE "Big P-ROM 1MB"
+ ELF 12-prom-1mb-rom
+)
diff --git a/13-prom-full-2mb/CMakeLists.txt b/13-prom-full-2mb/CMakeLists.txt
new file mode 100644
index 0000000..a01610d
--- /dev/null
+++ b/13-prom-full-2mb/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# The program overflows the first 1MB P-ROM; the linker places the
+# overflowing code into section .text2, which is packed into a second
+# P-ROM built from the same ELF file.
+ngdevkit_elf(13-prom-full-2mb-rom
+ SOURCES main.c overflow.c
+ LINK_OPTIONS -Wl,--enable-non-contiguous-regions
+)
+
+ngdevkit_cartridge(
+ GAMEROM 13_prom_2mb
+ TITLE "P-ROM 2MB non-contiguous"
+ ELF 13-prom-full-2mb-rom
+ PROM2_ELF 13-prom-full-2mb-rom
+)
diff --git a/14-prom-bankswitch/CMakeLists.txt b/14-prom-bankswitch/CMakeLists.txt
new file mode 100644
index 0000000..be7dac6
--- /dev/null
+++ b/14-prom-bankswitch/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# Bankswitching program ROM is achieved by building several ELF files
+# - The first ELF file contains the common code that is not bank-switched,
+# it gets built and converted into a regular P-ROM
+# - For every bank, a dedicated ELF file is built that contains only
+# code that belongs to the bank. The ngdevkit linkscript achieves that
+# by automatically linking all files whose path or name contains '__bank'
+# into the bank-switch P-ROM address space
+# The second P-ROM is the concatenation of the code banks of all ELF files.
+
+set(flags -std=c99 -fomit-frame-pointer -g)
+
+ngdevkit_elf(14-prom-bankswitch-rom0
+ SOURCES main.c impl__bank0.c
+ CFLAGS ${flags}
+ LINK_OPTIONS -Wl,--enable-non-contiguous-regions
+)
+
+ngdevkit_elf(14-prom-bankswitch-rom1
+ SOURCES main.c impl__bank1.c
+ CFLAGS ${flags}
+ LINK_OPTIONS -Wl,--enable-non-contiguous-regions
+)
+
+ngdevkit_cartridge(
+ GAMEROM 14_prom_bankswitch
+ TITLE "P-ROM bank switching"
+ ELF 14-prom-bankswitch-rom0
+ PROM2_BANKS 14-prom-bankswitch-rom0 14-prom-bankswitch-rom1
+ PROM2SIZE 2097152
+)
diff --git a/15-sound-adpcmb/CMakeLists.txt b/15-sound-adpcmb/CMakeLists.txt
new file mode 100644
index 0000000..cb78481
--- /dev/null
+++ b/15-sound-adpcmb/CMakeLists.txt
@@ -0,0 +1,61 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+set(sfx ${CMAKE_CURRENT_SOURCE_DIR}/setup/prepare-sfx/sfx)
+
+# the original music loops for this demo are mp3-encoded and need to get
+# trimmed and resampled, hence these prepare steps
+add_custom_command(OUTPUT ${gen}/assets/beat.wav
+ COMMAND ${NGDK_SOX} -V1 ${sfx}/hip-hop-beat-n2-49252.mp3 -c 1 ${gen}/assets/beat.wav
+ reverse trim 0 2.290 reverse pad 0 0.41 speed 0.996555118
+ DEPENDS ${sfx}/hip-hop-beat-n2-49252.mp3
+ COMMENT "Preparing beat.wav" VERBATIM)
+add_custom_command(OUTPUT ${gen}/assets/dance.wav
+ COMMAND ${NGDK_SOX} -V1 ${sfx}/insect-dance-loop-93865.mp3 -c 1 ${gen}/assets/dance.wav
+ reverse trim 0 3.37 reverse speed 0.999802215
+ DEPENDS ${sfx}/insect-dance-loop-93865.mp3
+ COMMENT "Preparing dance.wav" VERBATIM)
+
+# sample ROM and the matching offsets for the sound driver
+ngdevkit_vrom_from_yaml(VROM
+ GAMEROM 15_adpcm_b
+ YAML ${CMAKE_CURRENT_SOURCE_DIR}/assets/samples-map.yaml
+ SAMPLES_INC assets/samples.inc
+ STAGED ${gen}/assets/beat.wav ${gen}/assets/dance.wav
+)
+
+# sound driver: nullsound + the example's sound commands
+ngdevkit_z80_rel(USER_COMMANDS_REL user_commands.s
+ DEPENDS ${gen}/assets/samples.inc
+)
+ngdevkit_sound_driver(SOUND_DRIVER
+ NAME demo_driver
+ INPUTS ${NGDK_EYE_CATCHER_LIB} ${USER_COMMANDS_REL}
+ DEPENDS ${NGDK_SHARED_ASSETS_TARGET}
+)
+
+ngdevkit_elf(15-sound-adpcmb-rom
+ SOURCES main.c
+)
+
+ngdevkit_cartridge(
+ GAMEROM 15_adpcm_b
+ TITLE "ADPCM-B sound FX API"
+ ELF 15-sound-adpcmb-rom
+ SOUND_DRIVER ${SOUND_DRIVER}
+ VROM ${VROM}
+)
diff --git a/16-sound-music/CMakeLists.txt b/16-sound-music/CMakeLists.txt
new file mode 100644
index 0000000..286d3d8
--- /dev/null
+++ b/16-sound-music/CMakeLists.txt
@@ -0,0 +1,111 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+set(assets ${CMAKE_CURRENT_SOURCE_DIR}/assets)
+set(yamls ${assets}/music-map.yaml ${assets}/sample-map.yaml)
+
+# the sound FX for this demo is mp3-encoded and needs to get trimmed;
+# the sample-map.yaml references it as assets/sfx/laser.wav
+add_custom_command(OUTPUT ${gen}/assets/sfx/laser.wav
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${gen}/assets/sfx
+ COMMAND ${NGDK_SOX} -V1 ${CMAKE_CURRENT_SOURCE_DIR}/setup/prepare-sfx/sfx/laser-gun-72558.mp3
+ -c 1 -r 18500 ${gen}/assets/sfx/laser.wav
+ silence 1 0.01 0.1% reverse silence 1 0.05 0.5% reverse trim 0 0.6
+ DEPENDS setup/prepare-sfx/sfx/laser-gun-72558.mp3
+ COMMENT "Preparing laser.wav" VERBATIM)
+
+# the music-map.yaml references the Furnace module as assets/musics/...,
+# stage it next to the generated sound FX
+add_custom_command(OUTPUT ${gen}/assets/musics/starship-battle.fur
+ COMMAND ${CMAKE_COMMAND} -E copy ${assets}/musics/starship-battle.fur
+ ${gen}/assets/musics/starship-battle.fur
+ DEPENDS ${assets}/musics/starship-battle.fur
+ VERBATIM)
+set(staged ${gen}/assets/sfx/laser.wav ${gen}/assets/musics/starship-battle.fur)
+
+# sample ROM and the matching offsets for the sound driver
+ngdevkit_vrom_from_yaml(VROM
+ GAMEROM 16_sound_music
+ YAML ${yamls}
+ SAMPLES_INC samples.inc
+ STAGED ${staged}
+)
+
+# generate the list of sound commands, both as z80 code for the sound
+# driver (snd_commands.inc) and as a C header for the m68k program
+add_custom_command(OUTPUT ${gen}/snd_commands.inc
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_SOUNDTOOL} -v -z -s ${yamls} -o ${gen}/snd_commands.inc
+ DEPENDS ${yamls} ${staged}
+ WORKING_DIRECTORY ${gen}
+ COMMENT "Generating snd_commands.inc" VERBATIM)
+add_custom_command(OUTPUT ${gen}/snd_commands.h
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_SOUNDTOOL} -c -s ${yamls} -o ${gen}/snd_commands.h
+ DEPENDS ${yamls} ${staged}
+ WORKING_DIRECTORY ${gen}
+ COMMENT "Generating snd_commands.h" VERBATIM)
+
+# the Furnace module is converted into two z80 asm files: the music
+# stream and the instruments
+add_custom_command(OUTPUT ${gen}/nss/nss-starship-battle.s
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_NSSTOOL} -z ${assets}/musics/starship-battle.fur
+ -n nss_starship_battle -o ${gen}/nss/nss-starship-battle.s
+ DEPENDS ${assets}/musics/starship-battle.fur
+ COMMENT "Generating nss-starship-battle.s" VERBATIM)
+add_custom_command(OUTPUT ${gen}/nss/instruments-starship-battle.s
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_FURTOOL} ${assets}/musics/starship-battle.fur
+ --instruments -n instruments_starship_battle -m ${gen}/samples.inc
+ -o ${gen}/nss/instruments-starship-battle.s
+ DEPENDS ${assets}/musics/starship-battle.fur ${gen}/samples.inc
+ COMMENT "Generating instruments-starship-battle.s" VERBATIM)
+
+# sound driver: nullsound + eye catcher music + the example's sound
+# commands and music data
+ngdevkit_z80_rel(USER_COMMANDS_REL user_commands.s
+ DEPENDS ${gen}/samples.inc ${gen}/snd_commands.inc
+)
+ngdevkit_z80_rel(NSS_REL ${gen}/nss/nss-starship-battle.s
+ OUTPUT nss/nss-starship-battle.rel
+)
+ngdevkit_z80_rel(INSTRUMENTS_REL ${gen}/nss/instruments-starship-battle.s
+ OUTPUT nss/instruments-starship-battle.rel
+)
+ngdevkit_sound_driver(SOUND_DRIVER
+ NAME demo_driver
+ INPUTS ${NGDK_EYE_CATCHER_LIB} ${USER_COMMANDS_REL} ${NSS_REL} ${INSTRUMENTS_REL}
+ DEPENDS ${NGDK_SHARED_ASSETS_TARGET}
+)
+
+# generate the list of available sound commands for the m68k program,
+# extracted from the sound driver's jump table listing
+set(awk [=[awk '/cmd_jmptable::/ {table=1; next} table==1 && /cmd_jmptable_padding/ {exit} table==1 && / C3 / {print "\""$8"\","} END {print "\"\""}']=])
+add_custom_command(OUTPUT ${gen}/snd_commands_info.h
+ COMMAND ${NGDK_SH} -c "cat '${gen}/user_commands.lst' | ${awk} | tr '_' ' ' > '${gen}/snd_commands_info.h'"
+ DEPENDS ${USER_COMMANDS_REL}
+ COMMENT "Generating snd_commands_info.h" VERBATIM)
+
+ngdevkit_elf(16-sound-music-rom
+ SOURCES main.c
+ GENERATED ${gen}/snd_commands.h ${gen}/snd_commands_info.h
+)
+
+ngdevkit_cartridge(
+ GAMEROM 16_sound_music
+ TITLE "Music playback API"
+ ELF 16-sound-music-rom
+ SOUND_DRIVER ${SOUND_DRIVER}
+ VROM ${VROM}
+)
diff --git a/17-hello-c++/CMakeLists.txt b/17-hello-c++/CMakeLists.txt
new file mode 100644
index 0000000..cd902d8
--- /dev/null
+++ b/17-hello-c++/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+ngdevkit_elf(17-hello-cxx-rom
+ SOURCES main.cc
+ CFLAGS -fomit-frame-pointer -O2 -g
+)
+
+ngdevkit_cartridge(
+ GAMEROM 17_hello_cxx
+ TITLE "Hello world in C++"
+ ELF 17-hello-cxx-rom
+)
diff --git a/18-memory-card/CMakeLists.txt b/18-memory-card/CMakeLists.txt
new file mode 100644
index 0000000..6ccd5a3
--- /dev/null
+++ b/18-memory-card/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# This example uses a memory card; MEMCARD below creates an empty card
+# image and passes it to the emulators that support it.
+ngdevkit_elf(18-memory-card-rom
+ SOURCES main.c
+ CFLAGS -fomit-frame-pointer -O2 -g
+ LINK_OPTIONS -Wl,--defsym,rom_NGH_ID=1338 -Wl,--defsym,rom_eye_catcher_mode=0
+)
+
+ngdevkit_cartridge(
+ GAMEROM 18_memory_card
+ TITLE "Accessing the memory card"
+ ELF 18-memory-card-rom
+ MEMCARD
+)
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..fb20b4a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,203 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# CMake build for the ngdevkit examples. This build system coexists with
+# the autotools/make one; both are fully independent, use whichever you
+# prefer:
+# cmake -B build
+# cmake --build build
+# Emulator run targets: cmake --build build --target 01-helloworld-gngeo
+# (also: -gngeo-mvs, -mame, -mame-mvs)
+
+cmake_minimum_required(VERSION 3.21)
+
+# The examples target the Neo-Geo m68k CPU; default to the bundled
+# toolchain file so a plain `cmake -B build` works out of the box.
+if(NOT CMAKE_TOOLCHAIN_FILE)
+ set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ngdevkit-toolchain.cmake)
+endif()
+
+project(ngdevkit-examples LANGUAGES C CXX)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
+
+# --- ngdevkit itself ---
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(NGDEVKIT REQUIRED "ngdevkit>=0.5")
+pkg_get_variable(NGDK_BINDIR ngdevkit bindir)
+pkg_get_variable(NGDK_SHAREDIR ngdevkit sharedir)
+pkg_get_variable(NGDK_LIBDIR ngdevkit libdir)
+pkg_get_variable(NGDK_Z80INCLUDEDIR ngdevkit z80includedir)
+pkg_get_variable(NGDK_Z80LIBDIR ngdevkit z80libdir)
+
+# --- toolchains and tools ---
+find_program(NGDK_M68KOBJCOPY m68k-neogeo-elf-objcopy REQUIRED)
+# The MSYS2 ngdevkit-toolchain installs the prefixed z80 tools as
+# shell-script wrappers, which only work when spawned through an MSYS
+# shell (as the Makefile build does); native processes like ninja cannot
+# execute them. Prefer the real binaries from the target's bin directory
+# and fall back to the prefixed names elsewhere.
+get_filename_component(_z80prefix ${NGDK_Z80LIBDIR} DIRECTORY)
+find_program(NGDK_Z80SDAS NAMES sdasz80 PATHS ${_z80prefix}/bin NO_DEFAULT_PATH)
+find_program(NGDK_Z80SDAS z80-neogeo-ihx-sdasz80 REQUIRED)
+find_program(NGDK_Z80SDLD NAMES sdldz80 PATHS ${_z80prefix}/bin NO_DEFAULT_PATH)
+find_program(NGDK_Z80SDLD z80-neogeo-ihx-sdldz80 REQUIRED)
+find_program(NGDK_Z80SDOBJCOPY NAMES sdobjcopy PATHS ${_z80prefix}/bin NO_DEFAULT_PATH)
+find_program(NGDK_Z80SDOBJCOPY z80-neogeo-ihx-sdobjcopy REQUIRED)
+
+find_program(NGDK_PYTHON NAMES python3 python REQUIRED)
+foreach(tool IN ITEMS paltool tiletool adpcmtool vromtool furtool nsstool soundtool romtool)
+ string(TOUPPER ${tool} TOOL)
+ find_program(NGDK_${TOOL} ${tool}.py HINTS ${NGDK_BINDIR} REQUIRED)
+endforeach()
+# On Windows the python tools cannot be executed via their shebang;
+# launch them through the python interpreter there. Note: WIN32
+# describes the target (Generic here), test the host system instead.
+if(CMAKE_HOST_WIN32)
+ set(NGDK_TOOL_LAUNCHER ${NGDK_PYTHON})
+else()
+ set(NGDK_TOOL_LAUNCHER "")
+endif()
+
+find_program(NGDK_CONVERT NAMES magick convert REQUIRED)
+find_program(NGDK_SOX sox REQUIRED)
+find_program(NGDK_DD dd REQUIRED)
+find_program(NGDK_SH sh REQUIRED)
+
+# --- BIOS ROMs used to run the examples (nullbios by default) ---
+set(AES_BIOS "" CACHE FILEPATH "Absolute path to the AES BIOS file to use")
+set(MVS_BIOS "" CACHE FILEPATH "Absolute path to the MVS BIOS file to use")
+if(AES_BIOS)
+ set(NGDK_AES_BIOS ${AES_BIOS})
+else()
+ set(NGDK_AES_BIOS ${NGDK_SHAREDIR}/aes.zip)
+endif()
+if(MVS_BIOS)
+ set(NGDK_MVS_BIOS ${MVS_BIOS})
+else()
+ set(NGDK_MVS_BIOS ${NGDK_SHAREDIR}/neogeo.zip)
+endif()
+
+# --- emulators ---
+find_program(NGDK_GNGEO ngdevkit-gngeo)
+find_program(NGDK_MAME mame)
+if(NOT NGDK_MAME)
+ set(NGDK_MAME mame)
+endif()
+
+# GnGeo dynamically loads the ngdevkit runtime library
+if(APPLE)
+ set(NGDK_EMU_ENV "DYLD_LIBRARY_PATH=${NGDK_LIBDIR}:$ENV{DYLD_LIBRARY_PATH}")
+else()
+ set(NGDK_EMU_ENV "LD_LIBRARY_PATH=${NGDK_LIBDIR}:$ENV{LD_LIBRARY_PATH}")
+endif()
+
+set(NGDK_GNGEO_DATA "")
+set(NGDK_GNGEO_SHADEROPTS "")
+if(NGDK_GNGEO)
+ # locate GnGeo's data file (holds its default skin and shaders)
+ execute_process(COMMAND ${NGDK_GNGEO} "" forcefail
+ OUTPUT_VARIABLE _gngeo_out ERROR_VARIABLE _gngeo_err TIMEOUT 10)
+ if("${_gngeo_out}\n${_gngeo_err}" MATCHES "DATAFILE = ([^\r\n]+)")
+ set(NGDK_GNGEO_DATA ${CMAKE_MATCH_1})
+ if(DEFINED ENV{MSYSTEM})
+ find_program(NGDK_CYGPATH cygpath)
+ if(NGDK_CYGPATH)
+ execute_process(COMMAND ${NGDK_CYGPATH} -u ${NGDK_GNGEO_DATA}
+ OUTPUT_VARIABLE NGDK_GNGEO_DATA OUTPUT_STRIP_TRAILING_WHITESPACE)
+ endif()
+ endif()
+ message(STATUS "Found GnGeo data file: ${NGDK_GNGEO_DATA}")
+ else()
+ message(WARNING "Could not detect GnGeo's data file; GnGeo targets disabled")
+ set(NGDK_GNGEO "")
+ endif()
+endif()
+if(NGDK_GNGEO)
+ # is GnGeo built with the GLSL shader blitter?
+ execute_process(COMMAND ${NGDK_GNGEO} -b help
+ OUTPUT_VARIABLE _gngeo_blitters ERROR_VARIABLE _ignored TIMEOUT 10)
+ if(_gngeo_blitters MATCHES "(^|\n)glsl")
+ set(NGDK_GNGEO_GLSL yes)
+ else()
+ set(NGDK_GNGEO_GLSL no)
+ endif()
+ message(STATUS "GnGeo GLSL blitter: ${NGDK_GNGEO_GLSL}")
+
+ # pick the CRT shader from the git submodule when available
+ set(NGDK_SHADER_PATH "")
+ set(NGDK_SHADER noop.glslp)
+ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/shaders/qcrt-flat.glslp)
+ set(NGDK_SHADER_PATH ${CMAKE_CURRENT_SOURCE_DIR}/shaders)
+ set(NGDK_SHADER qcrt-flat.glslp)
+ endif()
+ if(NGDK_GNGEO_GLSL STREQUAL yes)
+ set(NGDK_GNGEO_SHADEROPTS -b glsl)
+ if(NGDK_SHADER_PATH)
+ list(APPEND NGDK_GNGEO_SHADEROPTS --shaderpath=${NGDK_SHADER_PATH})
+ endif()
+ list(APPEND NGDK_GNGEO_SHADEROPTS --shader=${NGDK_SHADER})
+ endif()
+
+ # generate a default input config for GnGeo if none exists yet
+ if(DEFINED ENV{HOME})
+ set(_gngeo_cfg "$ENV{HOME}/.gngeo/ngdevkit-gngeorc")
+ if(NOT EXISTS ${_gngeo_cfg})
+ message(STATUS "Generating a default input config for GnGeo")
+ if(NGDK_GNGEO_GLSL STREQUAL yes)
+ set(_cfg "# default blitter\nblitter glsl\n")
+ if(NGDK_SHADER_PATH)
+ string(APPEND _cfg "shaderpath ${NGDK_SHADER_PATH}\n")
+ endif()
+ string(APPEND _cfg "shader ${NGDK_SHADER}\n")
+ else()
+ set(_cfg "# default blitter\nblitter soft\n")
+ endif()
+ string(APPEND _cfg "
+# default scale factor
+scale 3
+
+# default key mapping
+p1control A=K97,B=K115,C=K113,D=K119,START=K49,COIN=K51,UP=K82,DOWN=K81,LEFT=K80,RIGHT=K79,A=J0B0,B=J0B1,C=J0B2,D=J0B3,START=J0B9,COIN=J0B8,UP=J0a3,DOWN=J0a3,LEFT=J0A0,RIGHT=J0A0
+p2control A=K103,B=K104,C=K116,D=K117,START=K50,COIN=K52,UP=K105,DOWN=K107,LEFT=K106,RIGHT=K108,A=J1B0,B=J1B1,C=J1B2,D=J1B3,START=J1B9,COIN=J1B8,UP=J1a3,DOWN=J1a3,LEFT=J1A0,RIGHT=J1A0
+")
+ file(WRITE ${_gngeo_cfg} "${_cfg}")
+ endif()
+ endif()
+endif()
+
+include(NGDevKit)
+include(SharedAssets)
+
+add_subdirectory(00-template)
+add_subdirectory(01-helloworld)
+add_subdirectory(02-sprite)
+add_subdirectory(03-sprite-animation)
+add_subdirectory(04-palette)
+add_subdirectory(05-scrolling)
+add_subdirectory(06-sound-adpcma)
+add_subdirectory(07-attract-and-game)
+add_subdirectory(08-software-dips)
+add_subdirectory(09-horizontal-sync)
+add_subdirectory(10-credits-management)
+add_subdirectory(11-backup-ram)
+add_subdirectory(12-prom-1mb)
+add_subdirectory(13-prom-full-2mb)
+add_subdirectory(14-prom-bankswitch)
+add_subdirectory(15-sound-adpcmb)
+add_subdirectory(16-sound-music)
+add_subdirectory(17-hello-c++)
+add_subdirectory(18-memory-card)
diff --git a/README.md b/README.md
index 95871bb..ae95893 100644
--- a/README.md
+++ b/README.md
@@ -119,6 +119,38 @@ can be build with the following commands:
make
+### Compiling examples with CMake
+
+Alternatively, the examples can be built with CMake instead of
+autoconf and make. Both build systems coexist in this repository and
+are fully independent; use whichever you prefer. The prerequisites
+are the same as above, except that autoconf, automake, make and rsync
+are replaced by cmake (and optionally ninja):
+
+ # import the GLSL CRT shader for GnGeo
+ git submodule update --init --recursive
+ # configure and build all the examples
+ cmake -B build
+ cmake --build build
+
+If you want to use other BIOS instead of the default open source ones
+provided with ngdevkit, you can pass them at configure time:
+
+ cmake -B build -DAES_BIOS=path -DMVS_BIOS=path
+
+Each example can be run in GnGeo or MAME, in AES or MVS mode, via
+dedicated targets:
+
+ cmake --build build --target 01-helloworld-gngeo # AES mode
+ cmake --build build --target 01-helloworld-gngeo-mvs # MVS mode
+ cmake --build build --target 01-helloworld-mame
+ cmake --build build --target 01-helloworld-mame-mvs
+
+The build rules live in [`cmake/NGDevKit.cmake`](cmake/NGDevKit.cmake),
+and every example has a small `CMakeLists.txt` that mirrors its
+`Makefile`.
+
+
## Running the compiled examples
The makefiles in this repository allow you to run the ngdevkit
diff --git a/cmake/NGDevKit.cmake b/cmake/NGDevKit.cmake
new file mode 100644
index 0000000..6c23fb3
--- /dev/null
+++ b/cmake/NGDevKit.cmake
@@ -0,0 +1,418 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# Helper functions to build Neo-Geo cartridges with ngdevkit.
+# This is the CMake counterpart of the Makefile-based build rules found
+# in 00-template/{build.mk,rom.mk,emu.mk}. Both build systems coexist
+# and are fully independent from each other.
+#
+# Each example directory contains a CMakeLists.txt that composes these
+# helpers; generated files land in the example's binary directory:
+# gen/ compiled objects, converted assets, generated includes
+# rom/ the ROM chip files, the cartridge zip and emulator hash files
+
+# Default chip sizes, as in 00-template/rom.mk
+set(NGDK_PROMSIZE 1048576)
+set(NGDK_CROMSIZE 2097152)
+set(NGDK_SROMSIZE 131072)
+set(NGDK_MROMSIZE 131072)
+set(NGDK_VROMSIZE 524288)
+
+set(NGDK_CONCAT ${CMAKE_CURRENT_LIST_DIR}/concat.py)
+
+# Default compile flags for the m68k program, as in the template Makefile
+set(NGDK_DEFAULT_CFLAGS -std=c99 -fomit-frame-pointer -O2 -g)
+
+
+# ngdevkit_gen_dir()/ngdevkit_rom_dir(): per-example output directories
+function(_ngdk_dirs)
+ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/gen/assets)
+ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/rom)
+endfunction()
+
+
+# ngdevkit_elf( SOURCES
+# [CFLAGS ] - replaces the default C flags
+# [LINK_OPTIONS ]
+# [GENERATED ]) - generated headers/includes the
+# sources depend on
+#
+# Compiles an m68k ELF binary (the content of the program ROM). The
+# example's gen/ directory is on the include path, like -I$(BUILDDIR)
+# in the Makefile build.
+function(ngdevkit_elf target)
+ cmake_parse_arguments(ARG "" "" "SOURCES;CFLAGS;LINK_OPTIONS;GENERATED" ${ARGN})
+ _ngdk_dirs()
+ if(NOT ARG_CFLAGS)
+ set(ARG_CFLAGS ${NGDK_DEFAULT_CFLAGS})
+ endif()
+ add_executable(${target} ${ARG_SOURCES} ${ARG_GENERATED})
+ set_target_properties(${target} PROPERTIES
+ OUTPUT_NAME ${target}
+ SUFFIX .elf
+ # the Makefile build links C++ examples with gcc as well
+ LINKER_LANGUAGE C)
+ target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/gen)
+ target_compile_options(${target} PRIVATE ${NGDEVKIT_CFLAGS} ${ARG_CFLAGS})
+ # use pkg-config's own decomposition of `--libs ngdevkit` rather than
+ # re-parsing the flags: plain options like the two-token `-specs ngdevkit`
+ # go through target_link_options, which passes them to the driver verbatim
+ # (target_link_libraries would mistake `ngdevkit` for a library name)
+ target_link_directories(${target} PRIVATE ${NGDEVKIT_LIBRARY_DIRS})
+ target_link_options(${target} PRIVATE ${NGDEVKIT_LDFLAGS_OTHER})
+ target_link_libraries(${target} PRIVATE ${NGDEVKIT_LIBRARIES})
+ if(ARG_LINK_OPTIONS)
+ target_link_options(${target} PRIVATE ${ARG_LINK_OPTIONS})
+ endif()
+endfunction()
+
+
+# ngdevkit_fix_tiles( ): fixed tiles from an image
+function(ngdevkit_fix_tiles outvar image)
+ _ngdk_dirs()
+ get_filename_component(name ${image} NAME_WE)
+ set(out ${CMAKE_CURRENT_BINARY_DIR}/gen/assets/${name}.fix)
+ add_custom_command(OUTPUT ${out}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_TILETOOL} --fix -c ${image} -o ${out}
+ DEPENDS ${image}
+ COMMENT "Converting ${name} to fixed tiles"
+ VERBATIM)
+ set(${outvar} ${out} PARENT_SCOPE)
+endfunction()
+
+
+# ngdevkit_sprite_tiles( ): sprite tiles from an image
+function(ngdevkit_sprite_tiles c1var c2var image)
+ _ngdk_dirs()
+ get_filename_component(name ${image} NAME_WE)
+ set(c1 ${CMAKE_CURRENT_BINARY_DIR}/gen/assets/${name}.c1)
+ set(c2 ${CMAKE_CURRENT_BINARY_DIR}/gen/assets/${name}.c2)
+ add_custom_command(OUTPUT ${c1} ${c2}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_TILETOOL} --sprite -c ${image} -o ${c1} ${c2}
+ DEPENDS ${image}
+ COMMENT "Converting ${name} to sprite tiles"
+ VERBATIM)
+ set(${c1var} ${c1} PARENT_SCOPE)
+ set(${c2var} ${c2} PARENT_SCOPE)
+endfunction()
+
+
+# ngdevkit_palette( ): C palette data from an image,
+# included by the program via "assets/.pal"
+function(ngdevkit_palette outvar image)
+ _ngdk_dirs()
+ get_filename_component(name ${image} NAME_WE)
+ set(out ${CMAKE_CURRENT_BINARY_DIR}/gen/assets/${name}.pal)
+ add_custom_command(OUTPUT ${out}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_PALTOOL} ${image} -o ${out}
+ DEPENDS ${image}
+ COMMENT "Extracting palette of ${name}"
+ VERBATIM)
+ set(${outvar} ${out} PARENT_SCOPE)
+endfunction()
+
+
+# ngdevkit_z80_rel( [OUTPUT ]
+# [INCLUDE_DIRS ] [DEPENDS ])
+#
+# Assembles a Z80 source file for the sound driver. The nullsound include
+# directory and the example's gen/ directory are always on the include path.
+function(ngdevkit_z80_rel outvar source)
+ cmake_parse_arguments(ARG "" "OUTPUT" "INCLUDE_DIRS;DEPENDS" ${ARGN})
+ _ngdk_dirs()
+ # the assembler runs in the binary directory, resolve sources like
+ # user_commands.s against the example's source directory
+ if(NOT IS_ABSOLUTE ${source})
+ set(source ${CMAKE_CURRENT_SOURCE_DIR}/${source})
+ endif()
+ if(NOT ARG_OUTPUT)
+ get_filename_component(name ${source} NAME_WE)
+ set(ARG_OUTPUT ${name}.rel)
+ endif()
+ set(out ${CMAKE_CURRENT_BINARY_DIR}/gen/${ARG_OUTPUT})
+ set(incflags -I${NGDK_Z80INCLUDEDIR}/nullsound -I${CMAKE_CURRENT_BINARY_DIR}/gen)
+ foreach(dir IN LISTS ARG_INCLUDE_DIRS)
+ list(APPEND incflags -I${dir})
+ endforeach()
+ get_filename_component(outdir ${out} DIRECTORY)
+ file(MAKE_DIRECTORY ${outdir})
+ add_custom_command(OUTPUT ${out}
+ COMMAND ${NGDK_Z80SDAS} -g -l -p -u ${incflags} -o ${out} ${source}
+ DEPENDS ${source} ${ARG_DEPENDS}
+ COMMENT "Assembling (z80) ${ARG_OUTPUT}"
+ VERBATIM)
+ set(${outvar} ${out} PARENT_SCOPE)
+endfunction()
+
+
+# ngdevkit_sound_driver( NAME INPUTS
+# [DEPENDS ])
+#
+# Links a sound driver against nullsound. The inputs are passed to the
+# linker in the given order, after nullsound.lib.
+function(ngdevkit_sound_driver outvar)
+ cmake_parse_arguments(ARG "" "NAME" "INPUTS;DEPENDS" ${ARGN})
+ _ngdk_dirs()
+ set(out ${CMAKE_CURRENT_BINARY_DIR}/gen/${ARG_NAME}.ihx)
+ add_custom_command(OUTPUT ${out}
+ COMMAND ${NGDK_Z80SDLD} -b DATA=0xf800 -i ${out}
+ ${NGDK_Z80LIBDIR}/nullsound.lib ${ARG_INPUTS}
+ DEPENDS ${ARG_INPUTS} ${ARG_DEPENDS}
+ COMMENT "Linking (z80) sound driver ${ARG_NAME}"
+ VERBATIM)
+ set(${outvar} ${out} PARENT_SCOPE)
+endfunction()
+
+
+# ngdevkit_vrom_from_yaml( GAMEROM YAML
+# SAMPLES_INC
+# [STAGED ])
+#
+# Builds the ADPCM sample ROM from asset map YAML files (vromtool), and
+# generates the matching samples.inc for the sound driver. The YAML files
+# reference their samples with paths relative to the example directory
+# (e.g. file://assets/foo.wav); callers stage those (generated) assets
+# under gen/ and list them in STAGED.
+function(ngdevkit_vrom_from_yaml vromvar)
+ cmake_parse_arguments(ARG "" "GAMEROM;SAMPLES_INC" "YAML;STAGED" ${ARGN})
+ _ngdk_dirs()
+ set(gen ${CMAKE_CURRENT_BINARY_DIR}/gen)
+ set(samples_inc ${gen}/${ARG_SAMPLES_INC})
+ set(vrom ${CMAKE_CURRENT_BINARY_DIR}/rom/${ARG_GAMEROM}-v1.v1)
+ # the sample map: offsets of each sample in the VROM, for the z80 driver
+ add_custom_command(OUTPUT ${samples_inc}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_VROMTOOL} --asm -s ${NGDK_VROMSIZE}
+ ${ARG_YAML} -o ${gen}/vrom-scratch.bin -m ${samples_inc}
+ DEPENDS ${ARG_YAML} ${ARG_STAGED}
+ WORKING_DIRECTORY ${gen}
+ COMMENT "Generating ${ARG_SAMPLES_INC}"
+ VERBATIM)
+ # the VROM itself
+ add_custom_command(OUTPUT ${vrom}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_VROMTOOL} --roms -s ${NGDK_VROMSIZE}
+ ${ARG_YAML} -o ${CMAKE_CURRENT_BINARY_DIR}/rom/${ARG_GAMEROM}-vX.vX -n 1
+ DEPENDS ${ARG_YAML} ${ARG_STAGED} ${samples_inc}
+ WORKING_DIRECTORY ${gen}
+ COMMENT "Building sample ROM ${ARG_GAMEROM}-v1.v1"
+ VERBATIM)
+ set(${vromvar} ${vrom} PARENT_SCOPE)
+endfunction()
+
+
+# _ngdk_prom( ): common P-ROM recipe;
+# extracts a binary from the ELF and byte-swaps it in place
+function(_ngdk_prom out elf)
+ get_filename_component(outname ${out} NAME)
+ add_custom_command(OUTPUT ${out}
+ COMMAND ${NGDK_M68KOBJCOPY} -O binary ${ARGN} --gap-fill 0xff
+ $ ${out}
+ COMMAND ${NGDK_DD} if=${out} of=${out} conv=notrunc,swab status=none
+ DEPENDS ${elf}
+ COMMENT "Building program ROM ${outname}"
+ VERBATIM)
+endfunction()
+
+
+# ngdevkit_cartridge(
+# [NAME ] - defaults to the directory name
+# GAMEROM TITLE
+# ELF - program linked into PROM1
+# [PROM2_ELF ] - PROM2 (.text2) from a single ELF
+# [PROM2_BANKS ] - PROM2 as concatenated ELF banks
+# [PROM2SIZE ]
+# [SROM ] - defaults to the shared text tiles
+# [CROM1 ] [CROM2 ]
+# - default to the shared ngdevkit logo
+# [SOUND_DRIVER ] - defaults to the shared base driver
+# [VROM ] - defaults to an empty sample ROM
+# [MEMCARD] - provide a memory card to emulators
+# [DEPENDS ])
+#
+# Assembles all ROM chips into a cartridge zip plus the hash files used
+# by MAME and GnGeo, copies the BIOSes next to it, and creates emulator
+# run targets (-gngeo, -gngeo-mvs, -mame, -mame-mvs).
+function(ngdevkit_cartridge)
+ cmake_parse_arguments(ARG "MEMCARD" "NAME;GAMEROM;TITLE;ELF;PROM2_ELF;PROM2SIZE"
+ "PROM2_BANKS;SROM;CROM1;CROM2;SOUND_DRIVER;VROM;DEPENDS" ${ARGN})
+ _ngdk_dirs()
+ set(rom ${CMAKE_CURRENT_BINARY_DIR}/rom)
+ set(game ${ARG_GAMEROM})
+ if(NOT ARG_NAME)
+ get_filename_component(ARG_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+ endif()
+ if(NOT ARG_SROM)
+ set(ARG_SROM ${NGDK_BASE_SROM_FIX})
+ endif()
+ if(NOT ARG_CROM1)
+ set(ARG_CROM1 ${NGDK_BASE_CROM_C1})
+ set(ARG_CROM2 ${NGDK_BASE_CROM_C2})
+ endif()
+ if(NOT ARG_SOUND_DRIVER)
+ set(ARG_SOUND_DRIVER ${NGDK_BASE_SOUND_DRIVER})
+ endif()
+ # every consumed file may come from the shared assets directory scope,
+ # so all custom commands also carry a target-level dependency
+ set(deps ${NGDK_SHARED_ASSETS_TARGET} ${ARG_DEPENDS})
+
+ # program ROM(s)
+ set(prom1 ${rom}/${game}-p1.p1)
+ _ngdk_prom(${prom1} ${ARG_ELF} -S -R .text2 --pad-to ${NGDK_PROMSIZE})
+ set(proms ${prom1})
+ math(EXPR prom2_pad "2097152 + ${NGDK_PROMSIZE}")
+ if(ARG_PROM2_ELF)
+ set(prom2 ${rom}/${game}-p2.p2)
+ _ngdk_prom(${prom2} ${ARG_PROM2_ELF} -j .text2 --pad-to ${prom2_pad})
+ list(APPEND proms ${prom2})
+ elseif(ARG_PROM2_BANKS)
+ set(prom2 ${rom}/${game}-p2.p2)
+ set(banks)
+ set(banknum 0)
+ foreach(elf IN LISTS ARG_PROM2_BANKS)
+ set(bank ${prom2}_bank${banknum})
+ _ngdk_prom(${bank} ${elf} -j .text2 --pad-to ${prom2_pad})
+ list(APPEND banks ${bank})
+ math(EXPR banknum "${banknum} + 1")
+ endforeach()
+ add_custom_command(OUTPUT ${prom2}
+ COMMAND ${NGDK_PYTHON} ${NGDK_CONCAT} --size ${ARG_PROM2SIZE} ${prom2} ${banks}
+ DEPENDS ${banks}
+ COMMENT "Building banked program ROM ${game}-p2.p2"
+ VERBATIM)
+ list(APPEND proms ${prom2})
+ endif()
+
+ # fixed tiles ROM
+ set(srom ${rom}/${game}-s1.s1)
+ add_custom_command(OUTPUT ${srom}
+ COMMAND ${NGDK_PYTHON} ${NGDK_CONCAT} --size ${NGDK_SROMSIZE} ${srom} ${ARG_SROM}
+ DEPENDS ${ARG_SROM} ${deps}
+ COMMENT "Building fixed tiles ROM ${game}-s1.s1"
+ VERBATIM)
+
+ # sprite tiles ROMs
+ set(crom1 ${rom}/${game}-c1.c1)
+ set(crom2 ${rom}/${game}-c2.c2)
+ add_custom_command(OUTPUT ${crom1}
+ COMMAND ${NGDK_PYTHON} ${NGDK_CONCAT} --size ${NGDK_CROMSIZE} ${crom1} ${ARG_CROM1}
+ DEPENDS ${ARG_CROM1} ${deps}
+ COMMENT "Building sprite ROM ${game}-c1.c1"
+ VERBATIM)
+ add_custom_command(OUTPUT ${crom2}
+ COMMAND ${NGDK_PYTHON} ${NGDK_CONCAT} --size ${NGDK_CROMSIZE} ${crom2} ${ARG_CROM2}
+ DEPENDS ${ARG_CROM2} ${deps}
+ COMMENT "Building sprite ROM ${game}-c2.c2"
+ VERBATIM)
+
+ # sound driver ROM
+ set(mrom ${rom}/${game}-m1.m1)
+ add_custom_command(OUTPUT ${mrom}
+ COMMAND ${NGDK_Z80SDOBJCOPY} -I ihex -O binary ${ARG_SOUND_DRIVER} ${mrom}
+ --pad-to ${NGDK_MROMSIZE}
+ DEPENDS ${ARG_SOUND_DRIVER} ${deps}
+ COMMENT "Building sound driver ROM ${game}-m1.m1"
+ VERBATIM)
+
+ # ADPCM samples ROM
+ if(ARG_VROM)
+ set(vrom ${ARG_VROM})
+ else()
+ set(vrom ${rom}/${game}-v1.v1)
+ add_custom_command(OUTPUT ${vrom}
+ COMMAND ${NGDK_PYTHON} ${NGDK_CONCAT} --size ${NGDK_VROMSIZE} ${vrom}
+ COMMENT "Building empty sample ROM ${game}-v1.v1"
+ VERBATIM)
+ endif()
+
+ set(chips ${proms} ${srom} ${crom1} ${crom2} ${mrom} ${vrom})
+ set(chipargs -p ${proms} -c ${crom1} ${crom2} -v ${vrom} -s ${srom} -m ${mrom})
+
+ # cartridge zip and emulator hash files
+ set(cart ${rom}/${game}.zip)
+ add_custom_command(OUTPUT ${cart}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_ROMTOOL} -b cartridge -f zip ${chipargs}
+ -n ${game} -x "zip.comment=NGDEVKIT example ROM - https://github.com/dciabrin/ngdevkit"
+ -o ${cart}
+ DEPENDS ${chips} ${deps}
+ COMMENT "Building cartridge ${game}.zip"
+ VERBATIM)
+ set(mame_hash ${rom}/neogeo.xml)
+ add_custom_command(OUTPUT ${mame_hash}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_ROMTOOL} -b hash -f mame ${chipargs}
+ -n ${game} -l ${ARG_TITLE} -o ${mame_hash}
+ DEPENDS ${chips} ${cart} ${deps}
+ COMMENT "Building MAME hash file for ${game}"
+ VERBATIM)
+ set(arts ${cart} ${mame_hash})
+ if(NGDK_GNGEO_DATA)
+ set(gngeo_hash ${rom}/gngeo_data.zip)
+ add_custom_command(OUTPUT ${gngeo_hash}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_ROMTOOL} -b hash -f gngeo ${chipargs}
+ -n ${game} -l ${ARG_TITLE} -x gngeo.data=${NGDK_GNGEO_DATA} -o ${gngeo_hash}
+ DEPENDS ${chips} ${cart} ${deps}
+ COMMENT "Building GnGeo hash file for ${game}"
+ VERBATIM)
+ list(APPEND arts ${gngeo_hash})
+ endif()
+
+ # BIOS ROMs next to the cartridge (nullbios by default)
+ foreach(bios IN LISTS NGDK_AES_BIOS NGDK_MVS_BIOS)
+ get_filename_component(biosname ${bios} NAME)
+ add_custom_command(OUTPUT ${rom}/${biosname}
+ COMMAND ${CMAKE_COMMAND} -E copy ${bios} ${rom}/${biosname}
+ DEPENDS ${bios}
+ COMMENT "Installing BIOS ${biosname}"
+ VERBATIM)
+ list(APPEND arts ${rom}/${biosname})
+ endforeach()
+
+ # memory card
+ set(memcard)
+ set(mame_memcard_flags)
+ if(ARG_MEMCARD)
+ set(memcard ${CMAKE_CURRENT_BINARY_DIR}/card.mc)
+ add_custom_command(OUTPUT ${memcard}
+ COMMAND ${NGDK_PYTHON} ${NGDK_CONCAT} --size 2048 ${memcard}
+ COMMENT "Creating empty memory card"
+ VERBATIM)
+ set(mame_memcard_flags -memc ${memcard})
+ list(APPEND arts ${memcard})
+ endif()
+
+ add_custom_target(${ARG_NAME} ALL DEPENDS ${arts})
+
+ # emulator run targets
+ if(NGDK_GNGEO)
+ set(gngeo_cmd ${CMAKE_COMMAND} -E env ${NGDK_EMU_ENV} ${NGDK_GNGEO}
+ ${NGDK_GNGEO_SHADEROPTS} --scale 3 --no-resize
+ -i ${rom} -d ${rom}/gngeo_data.zip ${game})
+ add_custom_target(${ARG_NAME}-gngeo
+ COMMAND ${gngeo_cmd} --system home
+ DEPENDS ${ARG_NAME} USES_TERMINAL VERBATIM)
+ add_custom_target(${ARG_NAME}-gngeo-mvs
+ COMMAND ${gngeo_cmd} --system arcade
+ DEPENDS ${ARG_NAME} USES_TERMINAL VERBATIM)
+ endif()
+ math(EXPR mame_w "3 * 320")
+ math(EXPR mame_h "3 * 224")
+ set(mame_cmd ${NGDK_MAME} -w -resolution ${mame_w}x${mame_h} -noautosave
+ -skip_gameinfo -hash ${rom} -rp ${rom})
+ add_custom_target(${ARG_NAME}-mame
+ COMMAND ${mame_cmd} aes ${mame_memcard_flags} -cart ${game}
+ DEPENDS ${ARG_NAME} USES_TERMINAL VERBATIM)
+ add_custom_target(${ARG_NAME}-mame-mvs
+ COMMAND ${mame_cmd} neogeo ${mame_memcard_flags} -cart1 ${game}
+ DEPENDS ${ARG_NAME} USES_TERMINAL VERBATIM)
+endfunction()
diff --git a/cmake/SharedAssets.cmake b/cmake/SharedAssets.cmake
new file mode 100644
index 0000000..75230a1
--- /dev/null
+++ b/cmake/SharedAssets.cmake
@@ -0,0 +1,177 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# Builds the ngdevkit assets shared by all examples (text tiles, the
+# ngdevkit logo sprites, the base sound driver and the eye-catcher
+# music library). This is the CMake counterpart of
+# 00-template/setup/ngdevkit-assets/Makefile; the Makefile build runs
+# that once per configure and rsyncs the results into every example,
+# whereas here every example simply depends on the files built in
+# ${NGDK_SHARED_DIR}.
+
+set(NGDK_SHARED_DIR ${CMAKE_BINARY_DIR}/ngdevkit-assets)
+set(_src ${CMAKE_SOURCE_DIR}/00-template/setup/ngdevkit-assets)
+file(MAKE_DIRECTORY ${NGDK_SHARED_DIR})
+
+# --- fixed tiles ROM data: small and tall latin fonts from unscii ---
+
+foreach(size IN ITEMS 8 16)
+ if(size EQUAL 8)
+ set(name smalltext)
+ set(crop 256x24+2+1)
+ set(tile 8x8)
+ set(extent 1024x8)
+ else()
+ set(name talltext)
+ set(crop 256x48+2+1)
+ set(tile 8x16)
+ set(extent 1024x16)
+ endif()
+ add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/${name}.gif
+ COMMAND ${NGDK_CONVERT} ${_src}/gfx/unscii${size}.png -crop ${crop} +repage
+ -crop ${tile} +append +repage -background black -gravity east
+ -extent ${extent} -fuzz 30% -fill white -opaque white -colors 2
+ ${NGDK_SHARED_DIR}/${name}.gif
+ DEPENDS ${_src}/gfx/unscii${size}.png
+ COMMENT "Generating ${name}.gif"
+ VERBATIM)
+ add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/${name}-shadow.gif
+ COMMAND ${NGDK_CONVERT} -size ${extent} xc:black
+ "(" ${NGDK_SHARED_DIR}/${name}.gif -transparent black -fuzz 30% -fill "#ffffff" -opaque white ")"
+ -geometry +1+1 -composite
+ "(" ${NGDK_SHARED_DIR}/${name}.gif -transparent black -fuzz 30% -fill "#ff0000" -opaque white -geometry +0+0 ")"
+ -composite -colors 16 ${NGDK_SHARED_DIR}/${name}-shadow.gif
+ DEPENDS ${NGDK_SHARED_DIR}/${name}.gif
+ COMMENT "Generating ${name}-shadow.gif"
+ VERBATIM)
+endforeach()
+
+foreach(variant IN ITEMS "" "-shadow")
+ if(variant STREQUAL "")
+ set(small smalltext.gif)
+ set(tall talltext.gif)
+ set(fix base-srom-text.fix)
+ else()
+ set(small smalltext-shadow.gif)
+ set(tall talltext-shadow.gif)
+ set(fix base-srom-text-shadow.fix)
+ endif()
+ add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/srom${variant}.bmp
+ COMMAND ${NGDK_PYTHON} ${_src}/build-srom.py
+ -s ${NGDK_SHARED_DIR}/${small} -t ${NGDK_SHARED_DIR}/${tall}
+ -b ${NGDK_SHARED_DIR}/talltext.gif -o ${NGDK_SHARED_DIR}/srom${variant}.bmp
+ DEPENDS ${_src}/build-srom.py ${NGDK_SHARED_DIR}/${small}
+ ${NGDK_SHARED_DIR}/${tall} ${NGDK_SHARED_DIR}/talltext.gif
+ COMMENT "Generating srom${variant}.bmp"
+ VERBATIM)
+ add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/${fix}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_TILETOOL} --fix
+ -c ${NGDK_SHARED_DIR}/srom${variant}.bmp -o ${NGDK_SHARED_DIR}/${fix}
+ DEPENDS ${NGDK_SHARED_DIR}/srom${variant}.bmp
+ COMMENT "Generating ${fix}"
+ VERBATIM)
+endforeach()
+set(NGDK_BASE_SROM_FIX ${NGDK_SHARED_DIR}/base-srom-text-shadow.fix)
+
+# --- sprite ROM data: the ngdevkit logo used during attract mode ---
+# tile 14 of the converted logo is dropped, and the result is padded,
+# exactly like the dd-based recipe in the Makefile build
+set(NGDK_BASE_CROM_C1 ${NGDK_SHARED_DIR}/base-crom-logo.c1)
+set(NGDK_BASE_CROM_C2 ${NGDK_SHARED_DIR}/base-crom-logo.c2)
+add_custom_command(OUTPUT ${NGDK_BASE_CROM_C1} ${NGDK_BASE_CROM_C2}
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_TILETOOL} --sprite -c ${_src}/gfx/logo.gif
+ -o tmp.c1 tmp.c2
+ COMMAND ${NGDK_SH} -c "(dd bs=64 count=14 if=tmp.c1; dd bs=64 skip=15 if=tmp.c1; dd bs=64 count=197 if=/dev/zero) 2>/dev/null | cat > base-crom-logo.c1"
+ COMMAND ${NGDK_SH} -c "(dd bs=64 count=14 if=tmp.c2; dd bs=64 skip=15 if=tmp.c2; dd bs=64 count=197 if=/dev/zero) 2>/dev/null | cat > base-crom-logo.c2"
+ DEPENDS ${_src}/gfx/logo.gif
+ WORKING_DIRECTORY ${NGDK_SHARED_DIR}
+ COMMENT "Generating base-crom-logo.c1/.c2"
+ VERBATIM)
+
+# --- eye-catcher music library and base sound driver ---
+
+add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/samples.inc
+ COMMAND ${CMAKE_COMMAND} -E touch ${NGDK_SHARED_DIR}/samples.inc
+ COMMENT "Generating empty samples.inc"
+ VERBATIM)
+
+add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/nss-ngdevkit.s
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_NSSTOOL} -z -n nss_ngdevkit
+ ${_src}/music/ngdevkit.fur -o ${NGDK_SHARED_DIR}/nss-ngdevkit.s
+ DEPENDS ${_src}/music/ngdevkit.fur
+ COMMENT "Generating nss-ngdevkit.s"
+ VERBATIM)
+
+add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/instruments-ngdevkit.s
+ COMMAND ${NGDK_TOOL_LAUNCHER} ${NGDK_FURTOOL} ${_src}/music/ngdevkit.fur
+ --instruments -n instruments_ngdevkit -o ${NGDK_SHARED_DIR}/instruments-ngdevkit.s
+ DEPENDS ${_src}/music/ngdevkit.fur ${NGDK_SHARED_DIR}/samples.inc
+ COMMENT "Generating instruments-ngdevkit.s"
+ VERBATIM)
+
+set(_rels)
+foreach(src IN ITEMS ${_src}/eye-catcher.s
+ ${NGDK_SHARED_DIR}/instruments-ngdevkit.s
+ ${NGDK_SHARED_DIR}/nss-ngdevkit.s)
+ get_filename_component(relname ${src} NAME_WE)
+ set(rel ${NGDK_SHARED_DIR}/${relname}.rel)
+ add_custom_command(OUTPUT ${rel}
+ COMMAND ${NGDK_Z80SDAS} -g -l -p -u -I${NGDK_Z80INCLUDEDIR}/nullsound
+ -I${NGDK_SHARED_DIR} -I${_src} -o ${rel} ${src}
+ DEPENDS ${src} ${NGDK_SHARED_DIR}/samples.inc
+ COMMENT "Assembling (z80) ${relname}.rel"
+ VERBATIM)
+ list(APPEND _rels ${rel})
+endforeach()
+
+# the lib below can be reused by an example's sound driver to embed
+# ngdevkit's attract music
+set(NGDK_EYE_CATCHER_LIB ${NGDK_SHARED_DIR}/ngdevkit-eye-catcher.lib)
+add_custom_command(OUTPUT ${NGDK_EYE_CATCHER_LIB}
+ COMMAND ${NGDK_PYTHON} ${NGDK_CONCAT} ${NGDK_EYE_CATCHER_LIB} ${_rels}
+ DEPENDS ${_rels}
+ COMMENT "Generating ngdevkit-eye-catcher.lib"
+ VERBATIM)
+
+set(NGDK_BASE_SOUND_DRIVER ${NGDK_SHARED_DIR}/base-sound-driver.ihx)
+add_custom_command(OUTPUT ${NGDK_SHARED_DIR}/base-sound-driver.rel
+ COMMAND ${NGDK_Z80SDAS} -g -l -p -u -I${NGDK_Z80INCLUDEDIR}/nullsound
+ -I${NGDK_SHARED_DIR} -I${_src} -o ${NGDK_SHARED_DIR}/base-sound-driver.rel
+ ${_src}/base-sound-driver.s
+ DEPENDS ${_src}/base-sound-driver.s
+ COMMENT "Assembling (z80) base-sound-driver.rel"
+ VERBATIM)
+add_custom_command(OUTPUT ${NGDK_BASE_SOUND_DRIVER}
+ COMMAND ${NGDK_Z80SDLD} -b DATA=0xf800 -m -w -i ${NGDK_BASE_SOUND_DRIVER}
+ ${NGDK_Z80LIBDIR}/nullsound.lib ${NGDK_EYE_CATCHER_LIB}
+ ${NGDK_SHARED_DIR}/base-sound-driver.rel
+ DEPENDS ${NGDK_EYE_CATCHER_LIB} ${NGDK_SHARED_DIR}/base-sound-driver.rel
+ COMMENT "Linking (z80) base-sound-driver.ihx"
+ VERBATIM)
+
+# target-level anchor: custom command outputs are only visible in this
+# directory scope, examples depend on this target to bridge that
+set(NGDK_SHARED_ASSETS_TARGET ngdevkit-shared-assets)
+add_custom_target(${NGDK_SHARED_ASSETS_TARGET}
+ DEPENDS ${NGDK_BASE_SROM_FIX} ${NGDK_SHARED_DIR}/base-srom-text.fix
+ ${NGDK_BASE_CROM_C1} ${NGDK_BASE_CROM_C2}
+ ${NGDK_BASE_SOUND_DRIVER} ${NGDK_EYE_CATCHER_LIB})
+
+# the shared ngdevkit logo, reused as sprite input by some examples
+set(NGDK_LOGO_GIF ${_src}/gfx/logo.gif)
+
+unset(_src)
+unset(_rels)
diff --git a/cmake/concat.py b/cmake/concat.py
new file mode 100644
index 0000000..fc763fb
--- /dev/null
+++ b/cmake/concat.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+"""Concatenate input files into an output file, optionally resized.
+
+Portable replacement for `cat inputs > output && truncate -s SIZE output`
+used by the Makefile-based build (macOS ships no `truncate`). With no
+inputs and a size, produces a zero-filled file of that size.
+"""
+
+import argparse
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--size", type=int, default=None,
+ help="truncate or zero-extend the output to SIZE bytes")
+ parser.add_argument("output")
+ parser.add_argument("inputs", nargs="*")
+ args = parser.parse_args()
+
+ with open(args.output, "wb") as out:
+ for name in args.inputs:
+ with open(name, "rb") as f:
+ while True:
+ chunk = f.read(1 << 20)
+ if not chunk:
+ break
+ out.write(chunk)
+ if args.size is not None:
+ out.truncate(args.size)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/cmake/ngdevkit-toolchain.cmake b/cmake/ngdevkit-toolchain.cmake
new file mode 100644
index 0000000..15214b5
--- /dev/null
+++ b/cmake/ngdevkit-toolchain.cmake
@@ -0,0 +1,50 @@
+# Copyright (c) 2026 Florian Loitsch
+# This file is part of ngdevkit
+#
+# ngdevkit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# ngdevkit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with ngdevkit. If not, see .
+
+# Cross-compilation toolchain for the Neo-Geo main CPU (Motorola 68000).
+# The top-level CMakeLists.txt uses this file automatically; it can also
+# be passed explicitly with -DCMAKE_TOOLCHAIN_FILE=.
+
+set(CMAKE_SYSTEM_NAME Generic)
+set(CMAKE_SYSTEM_PROCESSOR m68k)
+
+find_program(NGDK_M68K_GCC m68k-neogeo-elf-gcc REQUIRED)
+find_program(NGDK_M68K_GXX m68k-neogeo-elf-g++ REQUIRED)
+
+set(CMAKE_C_COMPILER ${NGDK_M68K_GCC})
+set(CMAKE_CXX_COMPILER ${NGDK_M68K_GXX})
+
+# A plain test executable cannot link without the ngdevkit specs/libraries,
+# so probe the compilers by building a static library instead.
+set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
+
+# The MSYS2 ngdevkit-toolchain installs bin/m68k-neogeo-elf-{ar,ranlib}.exe
+# as shell-script wrappers, which only work when spawned through an MSYS
+# shell (as the Makefile build does); native processes like ninja cannot
+# execute them. Prefer the real binaries from the target's bin directory.
+get_filename_component(_ngdk_bindir ${NGDK_M68K_GCC} DIRECTORY)
+get_filename_component(_ngdk_prefix ${_ngdk_bindir} DIRECTORY)
+find_program(CMAKE_AR NAMES ar PATHS ${_ngdk_prefix}/m68k-neogeo-elf/bin NO_DEFAULT_PATH)
+find_program(CMAKE_AR m68k-neogeo-elf-ar REQUIRED)
+find_program(CMAKE_RANLIB NAMES ranlib PATHS ${_ngdk_prefix}/m68k-neogeo-elf/bin NO_DEFAULT_PATH)
+find_program(CMAKE_RANLIB m68k-neogeo-elf-ranlib REQUIRED)
+
+# All support tools (python tools, sdcc toolchain, emulators...) are host
+# programs found in PATH.
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)