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 <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..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 <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) + +# 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)