From b2001df3b484c60e431cd18003520d65a0909fa1 Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Tue, 7 Jul 2026 16:08:49 +0200 Subject: [PATCH 1/8] Add CMake build support alongside the autotools build Both build systems coexist and are fully independent: - cmake/ngdevkit-toolchain.cmake: m68k-neogeo-elf cross toolchain, applied automatically by the top-level CMakeLists.txt - cmake/NGDevKit.cmake: build rules mirroring build.mk/rom.mk/emu.mk (ELF -> byte-swapped P-ROMs, z80 sound drivers, asset conversions, cartridge zip + MAME/GnGeo hash files, emulator run targets) - cmake/SharedAssets.cmake: shared ngdevkit assets built once for all examples, replacing the configure-time rsync/.prebuild mechanism - one CMakeLists.txt per example, mirroring its Makefile - CI jobs building with CMake on Linux, macOS and Windows/MSYS2 Usage: cmake -B build && cmake --build build Run: cmake --build build --target 01-helloworld-gngeo --- .github/scripts/build-cmake.sh | 4 + .github/workflows/build-tests.yaml | 88 ++++++ .gitignore | 4 + 00-template/CMakeLists.txt | 44 +++ 01-helloworld/CMakeLists.txt | 25 ++ 02-sprite/CMakeLists.txt | 36 +++ 03-sprite-animation/CMakeLists.txt | 55 ++++ 04-palette/CMakeLists.txt | 44 +++ 05-scrolling/CMakeLists.txt | 66 +++++ 06-sound-adpcma/CMakeLists.txt | 69 +++++ 07-attract-and-game/CMakeLists.txt | 25 ++ 08-software-dips/CMakeLists.txt | 25 ++ 09-horizontal-sync/CMakeLists.txt | 45 +++ 10-credits-management/CMakeLists.txt | 25 ++ 11-backup-ram/CMakeLists.txt | 25 ++ 12-prom-1mb/CMakeLists.txt | 25 ++ 13-prom-full-2mb/CMakeLists.txt | 30 ++ 14-prom-bankswitch/CMakeLists.txt | 46 +++ 15-sound-adpcmb/CMakeLists.txt | 61 ++++ 16-sound-music/CMakeLists.txt | 111 ++++++++ 17-hello-c++/CMakeLists.txt | 26 ++ 18-memory-card/CMakeLists.txt | 30 ++ CMakeLists.txt | 208 ++++++++++++++ README.md | 32 +++ cmake/NGDevKit.cmake | 409 +++++++++++++++++++++++++++ cmake/SharedAssets.cmake | 177 ++++++++++++ cmake/concat.py | 49 ++++ cmake/ngdevkit-toolchain.cmake | 39 +++ 28 files changed, 1823 insertions(+) create mode 100755 .github/scripts/build-cmake.sh create mode 100644 00-template/CMakeLists.txt create mode 100644 01-helloworld/CMakeLists.txt create mode 100644 02-sprite/CMakeLists.txt create mode 100644 03-sprite-animation/CMakeLists.txt create mode 100644 04-palette/CMakeLists.txt create mode 100644 05-scrolling/CMakeLists.txt create mode 100644 06-sound-adpcma/CMakeLists.txt create mode 100644 07-attract-and-game/CMakeLists.txt create mode 100644 08-software-dips/CMakeLists.txt create mode 100644 09-horizontal-sync/CMakeLists.txt create mode 100644 10-credits-management/CMakeLists.txt create mode 100644 11-backup-ram/CMakeLists.txt create mode 100644 12-prom-1mb/CMakeLists.txt create mode 100644 13-prom-full-2mb/CMakeLists.txt create mode 100644 14-prom-bankswitch/CMakeLists.txt create mode 100644 15-sound-adpcmb/CMakeLists.txt create mode 100644 16-sound-music/CMakeLists.txt create mode 100644 17-hello-c++/CMakeLists.txt create mode 100644 18-memory-card/CMakeLists.txt create mode 100644 CMakeLists.txt create mode 100644 cmake/NGDevKit.cmake create mode 100644 cmake/SharedAssets.cmake create mode 100644 cmake/concat.py create mode 100644 cmake/ngdevkit-toolchain.cmake diff --git a/.github/scripts/build-cmake.sh b/.github/scripts/build-cmake.sh new file mode 100755 index 0000000..aaeb139 --- /dev/null +++ b/.github/scripts/build-cmake.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +cmake -B build-cmake -G Ninja +cmake --build build-cmake diff --git a/.github/workflows/build-tests.yaml b/.github/workflows/build-tests.yaml index 3b13f98..c314bca 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 @@ -92,3 +162,21 @@ jobs: - 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 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..e14ded6 --- /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 03_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..1073878 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,208 @@ +# 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-config --libs` yields `-specs ngdevkit`; fold the two-token form +# into -specs= so CMake does not mistake the argument for a library +set(_ldflags) +set(_pending_specs FALSE) +foreach(flag IN LISTS NGDEVKIT_LDFLAGS) + if(_pending_specs) + list(APPEND _ldflags "-specs=${flag}") + set(_pending_specs FALSE) + elseif(flag MATCHES "^--?specs$") + set(_pending_specs TRUE) + else() + list(APPEND _ldflags ${flag}) + endif() +endforeach() +set(NGDEVKIT_LDFLAGS ${_ldflags}) +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) +find_program(NGDK_Z80SDAS z80-neogeo-ihx-sdasz80 REQUIRED) +find_program(NGDK_Z80SDLD z80-neogeo-ihx-sdldz80 REQUIRED) +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. +if(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..c683a0d --- /dev/null +++ b/cmake/NGDevKit.cmake @@ -0,0 +1,409 @@ +# 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}) + # NGDEVKIT_LDFLAGS comes from `pkg-config --libs ngdevkit` and carries + # -L, -specs and -l flags whose relative order must be preserved + target_link_libraries(${target} PRIVATE ${NGDEVKIT_LDFLAGS}) + 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() + 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 <target> - program linked into PROM1 +# [PROM2_ELF <target>] - PROM2 (.text2) from a single ELF +# [PROM2_BANKS <targets...>] - PROM2 as concatenated ELF banks +# [PROM2SIZE <bytes>] +# [SROM <fix files...>] - defaults to the shared text tiles +# [CROM1 <c1 files...>] [CROM2 <c2 files...>] +# - default to the shared ngdevkit logo +# [SOUND_DRIVER <ihx file>] - defaults to the shared base driver +# [VROM <v1 file>] - defaults to an empty sample ROM +# [MEMCARD] - provide a memory card to emulators +# [DEPENDS <extra targets...>]) +# +# 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 (<name>-gngeo, <name>-gngeo-mvs, <name>-mame, <name>-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 <http://www.gnu.org/licenses/>. + +# 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 <http://www.gnu.org/licenses/>. + +"""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..c2c775f --- /dev/null +++ b/cmake/ngdevkit-toolchain.cmake @@ -0,0 +1,39 @@ +# 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 <http://www.gnu.org/licenses/>. + +# 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) + +# 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) From f371215471c76576a01f22aa1cff779e10e321e4 Mon Sep 17 00:00:00 2001 From: Florian Loitsch <florian@loitsch.com> Date: Tue, 7 Jul 2026 16:15:02 +0200 Subject: [PATCH 2/8] Fix ROM name of the 04-palette example in the CMake build The Makefile says 03_palette, which looks like a copy-paste slip; the Makefile fix is done separately to keep the build systems' changes independent. --- 04-palette/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/04-palette/CMakeLists.txt b/04-palette/CMakeLists.txt index e14ded6..d011a07 100644 --- a/04-palette/CMakeLists.txt +++ b/04-palette/CMakeLists.txt @@ -37,7 +37,7 @@ ngdevkit_elf(04-palette-rom ) ngdevkit_cartridge( - GAMEROM 03_palette + GAMEROM 04_palette TITLE "Palette test" ELF 04-palette-rom SROM ${NGDK_BASE_SROM_FIX} ${GRADIENT_FIX} From 4a50ca08a4857a4e4bc4151052c2b0ad62e1144e Mon Sep 17 00:00:00 2001 From: Florian Loitsch <florian@loitsch.com> Date: Tue, 7 Jul 2026 16:15:02 +0200 Subject: [PATCH 3/8] Link with pkg-config's decomposed ngdevkit flags Passing the raw `pkg-config --libs` output to target_link_libraries mis-handles the two-token `-specs ngdevkit` form: the bare file name would be rewritten into -lngdevkit. Instead of re-parsing the flags, use the decomposition pkg-config already provides: -L directories via target_link_directories, plain driver options via target_link_options (passed verbatim), and libraries via target_link_libraries. --- CMakeLists.txt | 15 --------------- cmake/NGDevKit.cmake | 10 +++++++--- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1073878..1e22ab3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,21 +37,6 @@ 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-config --libs` yields `-specs ngdevkit`; fold the two-token form -# into -specs=<file> so CMake does not mistake the argument for a library -set(_ldflags) -set(_pending_specs FALSE) -foreach(flag IN LISTS NGDEVKIT_LDFLAGS) - if(_pending_specs) - list(APPEND _ldflags "-specs=${flag}") - set(_pending_specs FALSE) - elseif(flag MATCHES "^--?specs$") - set(_pending_specs TRUE) - else() - list(APPEND _ldflags ${flag}) - endif() -endforeach() -set(NGDEVKIT_LDFLAGS ${_ldflags}) pkg_get_variable(NGDK_BINDIR ngdevkit bindir) pkg_get_variable(NGDK_SHAREDIR ngdevkit sharedir) pkg_get_variable(NGDK_LIBDIR ngdevkit libdir) diff --git a/cmake/NGDevKit.cmake b/cmake/NGDevKit.cmake index c683a0d..7f322cd 100644 --- a/cmake/NGDevKit.cmake +++ b/cmake/NGDevKit.cmake @@ -67,9 +67,13 @@ function(ngdevkit_elf target) LINKER_LANGUAGE C) target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/gen) target_compile_options(${target} PRIVATE ${NGDEVKIT_CFLAGS} ${ARG_CFLAGS}) - # NGDEVKIT_LDFLAGS comes from `pkg-config --libs ngdevkit` and carries - # -L, -specs and -l flags whose relative order must be preserved - target_link_libraries(${target} PRIVATE ${NGDEVKIT_LDFLAGS}) + # 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() From 49258e28e3beaf18cbe22e8d1499847aa94caf3a Mon Sep 17 00:00:00 2001 From: Florian Loitsch <florian@loitsch.com> Date: Tue, 7 Jul 2026 16:23:52 +0200 Subject: [PATCH 4/8] CI: trust the dciabrin/ngdevkit brew tap Recent Homebrew versions refuse to install formulae from untrusted third-party taps, which broke both macOS jobs (make and cmake alike). --- .github/workflows/build-tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-tests.yaml b/.github/workflows/build-tests.yaml index c314bca..54161f4 100644 --- a/.github/workflows/build-tests.yaml +++ b/.github/workflows/build-tests.yaml @@ -157,6 +157,7 @@ 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 @@ -173,6 +174,7 @@ jobs: - 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 From 74414f8e90063a10ad8d1edb51a6488ad7a3955f Mon Sep 17 00:00:00 2001 From: Florian Loitsch <florian@loitsch.com> Date: Tue, 7 Jul 2026 16:28:06 +0200 Subject: [PATCH 5/8] Fix first CI findings of the CMake build - resolve relative z80 sources against the example's source directory; the assembler runs in the binary directory, so user_commands.s was not found - skip the compiler sanity checks: they archive a test object with ar/ranlib, and the MSYS2 ngdevkit-toolchain ships a non-functional m68k-neogeo-elf-ar.exe (unnoticed by the Makefile build, which never invokes ar) --- cmake/NGDevKit.cmake | 5 +++++ cmake/ngdevkit-toolchain.cmake | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/cmake/NGDevKit.cmake b/cmake/NGDevKit.cmake index 7f322cd..6c23fb3 100644 --- a/cmake/NGDevKit.cmake +++ b/cmake/NGDevKit.cmake @@ -133,6 +133,11 @@ endfunction() 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) diff --git a/cmake/ngdevkit-toolchain.cmake b/cmake/ngdevkit-toolchain.cmake index c2c775f..4ba0ec3 100644 --- a/cmake/ngdevkit-toolchain.cmake +++ b/cmake/ngdevkit-toolchain.cmake @@ -31,6 +31,13 @@ set(CMAKE_CXX_COMPILER ${NGDK_M68K_GXX}) # so probe the compilers by building a static library instead. set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +# Skip the compiler sanity checks altogether: they archive the test +# object with ar/ranlib, and the MSYS2 ngdevkit-toolchain currently +# ships a non-functional m68k-neogeo-elf-ar.exe (the Makefile build +# never uses ar, so the package issue goes unnoticed there). +set(CMAKE_C_COMPILER_WORKS TRUE) +set(CMAKE_CXX_COMPILER_WORKS TRUE) + # All support tools (python tools, sdcc toolchain, emulators...) are host # programs found in PATH. set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) From 69fc4d60e6f7d6f9f2136cdc96074b9133adf04a Mon Sep 17 00:00:00 2001 From: Florian Loitsch <florian@loitsch.com> Date: Tue, 7 Jul 2026 16:37:59 +0200 Subject: [PATCH 6/8] Use the real per-target binutils/SDCC binaries on MSYS2 The MSYS2 ngdevkit-toolchain package installs the prefixed tools (m68k-neogeo-elf-{ar,ranlib}, z80-neogeo-ihx-*) as shell-script wrappers named .exe. Those only work when spawned through an MSYS shell, which is how the Makefile build runs them; native process creation (cmake/ninja) fails with ERROR_BAD_EXE_FORMAT. Prefer the real PE binaries from the per-target bin directories (<prefix>/m68k-neogeo-elf/bin, <prefix>/z80-neogeo-ihx/bin), falling back to the prefixed names on other platforms. With a working ar and ranlib the compiler sanity checks pass again, so the earlier CMAKE_<LANG>_COMPILER_WORKS workaround is reverted. --- CMakeLists.txt | 9 +++++++++ cmake/ngdevkit-toolchain.cmake | 16 ++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e22ab3..656bf3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,8 +45,17 @@ 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) diff --git a/cmake/ngdevkit-toolchain.cmake b/cmake/ngdevkit-toolchain.cmake index 4ba0ec3..15214b5 100644 --- a/cmake/ngdevkit-toolchain.cmake +++ b/cmake/ngdevkit-toolchain.cmake @@ -31,12 +31,16 @@ set(CMAKE_CXX_COMPILER ${NGDK_M68K_GXX}) # so probe the compilers by building a static library instead. set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) -# Skip the compiler sanity checks altogether: they archive the test -# object with ar/ranlib, and the MSYS2 ngdevkit-toolchain currently -# ships a non-functional m68k-neogeo-elf-ar.exe (the Makefile build -# never uses ar, so the package issue goes unnoticed there). -set(CMAKE_C_COMPILER_WORKS TRUE) -set(CMAKE_CXX_COMPILER_WORKS TRUE) +# 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. From b5da61c4c0dbe32d6be0ab6488ac74f1e86dc20a Mon Sep 17 00:00:00 2001 From: Florian Loitsch <florian@loitsch.com> Date: Tue, 7 Jul 2026 16:52:53 +0200 Subject: [PATCH 7/8] CI: shield the Windows CMake build from Defender scan races Freshly generated z80 sources failed to open ~30ms after nsstool wrote them (?ASxxxx-Error-<cannot open>), while checked-out sources assembled fine. Defender's real-time scan holds a transient exclusive handle on new files; ninja's parallelism reopens them within milliseconds, unlike the serial Makefile build. Exclude the workspace from scanning, and make the build script list the generated assets on failure for diagnosis. --- .github/scripts/build-cmake.sh | 6 +++++- .github/workflows/build-tests.yaml | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/scripts/build-cmake.sh b/.github/scripts/build-cmake.sh index aaeb139..55b1587 100755 --- a/.github/scripts/build-cmake.sh +++ b/.github/scripts/build-cmake.sh @@ -1,4 +1,8 @@ #!/bin/sh set -e cmake -B build-cmake -G Ninja -cmake --build build-cmake +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 54161f4..7cb4d7f 100644 --- a/.github/workflows/build-tests.yaml +++ b/.github/workflows/build-tests.yaml @@ -126,6 +126,13 @@ jobs: with: fetch-depth: 0 + # Defender's real-time scan briefly locks freshly written files; with + # ninja's parallelism, generated sources are reopened within + # milliseconds and the build fails with spurious "cannot open" errors + - name: Exclude workspace from Windows Defender + shell: powershell + run: Add-MpPreference -ExclusionPath "${{ github.workspace }}", "D:\a\_temp\msys64" + - name: Install MSYS2 uses: msys2/setup-msys2@v2 with: From 9b477c777994c116f1b7688c1c76a38f2e055f1f Mon Sep 17 00:00:00 2001 From: Florian Loitsch <florian@loitsch.com> Date: Tue, 7 Jul 2026 17:02:34 +0200 Subject: [PATCH 8/8] Launch python tools through the interpreter on Windows hosts The launcher decision tested WIN32, which describes the target system and is false under the Generic cross toolchain. On the MSYS2 runner the .py tools were thus 'executed' directly: cmd.exe resolves them via file association, which exits 0 without running anything, so every python-generated file (nss/instrument asm, srom.bmp, snd_commands.*) was silently missing. Test CMAKE_HOST_WIN32 instead. Also drop the speculative Defender exclusion from CI: the failure is fully explained by the launcher bug. --- .github/workflows/build-tests.yaml | 7 ------- CMakeLists.txt | 5 +++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-tests.yaml b/.github/workflows/build-tests.yaml index 7cb4d7f..54161f4 100644 --- a/.github/workflows/build-tests.yaml +++ b/.github/workflows/build-tests.yaml @@ -126,13 +126,6 @@ jobs: with: fetch-depth: 0 - # Defender's real-time scan briefly locks freshly written files; with - # ninja's parallelism, generated sources are reopened within - # milliseconds and the build fails with spurious "cannot open" errors - - name: Exclude workspace from Windows Defender - shell: powershell - run: Add-MpPreference -ExclusionPath "${{ github.workspace }}", "D:\a\_temp\msys64" - - name: Install MSYS2 uses: msys2/setup-msys2@v2 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 656bf3d..fb20b4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,8 +64,9 @@ foreach(tool IN ITEMS paltool tiletool adpcmtool vromtool furtool nsstool soundt 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. -if(WIN32) +# 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 "")