From 0f0aa413e3bd3209f88029bc26ee5e42d8d7ee75 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgiadis Date: Tue, 30 Sep 2025 16:25:34 +0300 Subject: [PATCH] Implement CMake build system Signed-off-by: Panagiotis Georgiadis --- .github/workflows/doxygen-gh-pages.yml | 7 +- .github/workflows/pvsneslib_build_package.yml | 21 +- .gitignore | 11 +- CMakeLists.txt | 237 +++++++ devkitsnes/bin/.keepme => a | 0 cmake/SNESGame.cmake | 367 ++++++++++ cmake/replace_text.cmake | 22 + compiler/CMakeLists.txt | 89 +++ devkitsnes/snes_rules | 4 +- devkitsnes/tools/.keepme | 0 pvsneslib/CMakeLists.txt | 144 ++++ pvsneslib/include/snes/libversion.h | 6 +- pvsneslib/include/snes/libversion.h.in | 10 + pvsneslib/pvsneslib_version.txt.in | 1 + pvsneslib/source/Makefile | 2 +- snes-examples/CMakeLists.txt | 656 ++++++++++++++++++ .../audio/effects/res/effectssfx.bak | Bin 66339 -> 0 bytes tools/816-opt/Makefile | 6 +- tools/816-opt/build/.keepme | 0 tools/816-opt/src/main.c | 4 +- tools/CMakeLists.txt | 198 ++++++ 21 files changed, 1754 insertions(+), 31 deletions(-) create mode 100644 CMakeLists.txt rename devkitsnes/bin/.keepme => a (100%) create mode 100644 cmake/SNESGame.cmake create mode 100644 cmake/replace_text.cmake create mode 100644 compiler/CMakeLists.txt delete mode 100644 devkitsnes/tools/.keepme create mode 100644 pvsneslib/CMakeLists.txt create mode 100644 pvsneslib/include/snes/libversion.h.in create mode 100644 pvsneslib/pvsneslib_version.txt.in create mode 100644 snes-examples/CMakeLists.txt delete mode 100644 snes-examples/audio/effects/res/effectssfx.bak delete mode 100644 tools/816-opt/build/.keepme create mode 100644 tools/CMakeLists.txt diff --git a/.github/workflows/doxygen-gh-pages.yml b/.github/workflows/doxygen-gh-pages.yml index 6de4b2145..c295a0685 100644 --- a/.github/workflows/doxygen-gh-pages.yml +++ b/.github/workflows/doxygen-gh-pages.yml @@ -34,11 +34,10 @@ jobs: sudo apt-get install doxygen -y shell: bash - - name: "Build Doxygen Doc" + - name: "Build Doxygen Doc with CMake" run: | - cd pvsneslib - export PVSNESLIB_HOME=`pwd` - make docs + cmake -B build -DPVSNESLIB_BUILD_DOCS=ON + cmake --build build --target docs shell: bash # 3. Dรฉploiement sur les Github Pages diff --git a/.github/workflows/pvsneslib_build_package.yml b/.github/workflows/pvsneslib_build_package.yml index b5d22ac6a..0d6fe1996 100644 --- a/.github/workflows/pvsneslib_build_package.yml +++ b/.github/workflows/pvsneslib_build_package.yml @@ -78,23 +78,10 @@ jobs: # ------------------------------------ # # Compiling sources and create release # # ------------------------------------ # - - name: Build & Release PVSNESLIB for Linux - if: (matrix.name == 'linux') + - name: Build & Release PVSNESLIB with CMake run: | - export PVSNESLIB_HOME=$(pwd) - make release | tee pvsneslib_release_${{ matrix.name }}.log - - name: Build & Release PVSNESLIB for Windows - if: (matrix.name == 'windows') - shell: msys2 {0} - run: | - export PVSNESLIB_HOME=$(pwd) - make release | tee pvsneslib_release_${{ matrix.name }}.log - - name: Build & Release PVSNESLIB for MacOS - if: (matrix.name == 'darwin') - run: | - export PVSNESLIB_HOME=$(pwd) - export PATH="/opt/homebrew/opt/gnu-sed/libexec/gnubin:$PATH" - make release | tee pvsneslib_release_${{ matrix.name }}.log + cmake -B build -DPVSNESLIB_BUILD_EXAMPLES=ON -DPVSNESLIB_AUTO_SUBMODULES=ON + cmake --build build --target release | tee pvsneslib_release_${{ matrix.name }}.log # ------------------------------------ # # Checking libraries linking # @@ -115,7 +102,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: PVSNESLIB Release for ${{ matrix.name }} - path: release/pvsneslib_64b_${{ matrix.name }}.zip + path: release/pvsneslib_*_${{ matrix.name }}.tar.gz # ------------------------------------ # # Upload releases # diff --git a/.gitignore b/.gitignore index 64c1276a1..bea9b36d8 100644 --- a/.gitignore +++ b/.gitignore @@ -74,4 +74,13 @@ soundbank.h *.dbg # Mac OS cache files -.DS_Store \ No newline at end of file +.DS_Store + +# CMake build directory +build/ + +# Release directory +release/ + +# CMake generated files +pvsneslib/source/comp_defs.asm diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..a3dd2f343 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,237 @@ +cmake_minimum_required(VERSION 3.16) +project(PVSnesLib VERSION 4.4.0 LANGUAGES C CXX) + +# Set C standard +set(CMAKE_C_STANDARD 90) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Set C++ standard for tools that need it +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Pure CMake build system - no Make dependencies +option(PVSNESLIB_BUILD_EXAMPLES "Build SNES examples" ON) +option(PVSNESLIB_AUTO_SUBMODULES "Automatically initialize submodules" ON) +option(PVSNESLIB_BUILD_DOCS "Build documentation (requires Doxygen)" ON) + +# Set PVSNESLIB_HOME - support both environment variable and local builds +if(DEFINED ENV{PVSNESLIB_HOME}) + set(PVSNESLIB_HOME $ENV{PVSNESLIB_HOME} CACHE PATH "PVSnesLib home directory") + message(STATUS "Using PVSNESLIB_HOME from environment: ${PVSNESLIB_HOME}") +else() + set(PVSNESLIB_HOME ${CMAKE_SOURCE_DIR} CACHE PATH "PVSnesLib home directory") + message(STATUS "Using local PVSnesLib build: ${PVSNESLIB_HOME}") +endif() + +# Platform detection (mirroring existing Makefile logic) +if(WIN32) + set(PVSNESLIB_OS "windows") +elseif(CMAKE_SYSTEM_NAME MATCHES "MINGW") + set(PVSNESLIB_OS "mingw") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(PVSNESLIB_OS "darwin") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(PVSNESLIB_OS "linux") +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(PVSNESLIB_OS "freebsd") +else() + message(FATAL_ERROR "Unsupported operating system: ${CMAKE_SYSTEM_NAME}") +endif() + +# Architecture detection (improved over hardcoded 64b) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + set(PVSNESLIB_ARCH "arm64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") + set(PVSNESLIB_ARCH "x64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i686") + set(PVSNESLIB_ARCH "x86") +else() + # Fallback to original naming for unknown architectures + set(PVSNESLIB_ARCH "64b") +endif() + +message(STATUS "Building PVSnesLib for: ${PVSNESLIB_ARCH}_${PVSNESLIB_OS}") +message(STATUS "PVSnesLib home: ${PVSNESLIB_HOME}") +message(STATUS "Detected architecture: ${CMAKE_SYSTEM_PROCESSOR} โ†’ ${PVSNESLIB_ARCH}") + +message(STATUS "Using pure CMake build system") + +# Include ExternalProject for handling submodules +include(ExternalProject) + +# Add our CMake modules +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + +# Automatically initialize and update submodules if requested +if(PVSNESLIB_AUTO_SUBMODULES) + message(STATUS "Synchronizing submodules to match superproject...") + + find_package(Git REQUIRED) + execute_process( + COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_RESULT + OUTPUT_VARIABLE GIT_OUTPUT + ERROR_VARIABLE GIT_ERROR + ) + + if(NOT GIT_RESULT EQUAL 0) + message(WARNING "Failed to synchronize submodules.") + message(WARNING "Please run manually: git submodule update --init --recursive") + message(WARNING "Git output: ${GIT_OUTPUT}") + message(WARNING "Git error: ${GIT_ERROR}") + else() + message(STATUS "Submodules synchronized successfully!") + endif() +endif() + +# Create directories that match existing structure +file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/devkitsnes/bin) +file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/devkitsnes/tools) + +# Add subdirectories in dependency order (matching existing Makefile) +add_subdirectory(compiler) +add_subdirectory(tools) +add_subdirectory(pvsneslib) + +if(PVSNESLIB_BUILD_EXAMPLES) + add_subdirectory(snes-examples) +endif() + + +# Documentation is handled in pvsneslib/CMakeLists.txt + +#============================================================================= +# ESSENTIAL TARGETS - Only 3 Things You Need +#============================================================================= + +# 1. Absolute minimum to write SNES programs and make ROMs +add_custom_target(minimal + DEPENDS compiler tools library + COMMENT "๐Ÿ”ง Minimal SNES development environment (compiler + tools + library only)" +) + +# 2. Build examples only (assumes minimal is already built) +add_custom_target(examples_only + DEPENDS examples audio_examples games_examples logo_examples sram_examples objects_examples input_examples advanced_examples + COMMENT "๐ŸŽฎ Build working SNES examples (40+ ROMs)" +) + +# 3. Build absolutely everything +add_custom_target(everything + DEPENDS compiler tools library examples audio_examples games_examples logo_examples sram_examples objects_examples input_examples advanced_examples + COMMENT "๐Ÿš€ Build everything (minimal + working examples)" +) + +# Note: Clean aliases (compiler, tools, library) are defined in their respective subdirectories + +#============================================================================= +# RELEASE SYSTEM - Complete Package Creation +#============================================================================= + +# Release target that matches and improves upon original Makefile release +add_custom_target(release + # Prerequisites: Build everything and generate documentation + DEPENDS everything docs + + # Create release directory structure + COMMAND ${CMAKE_COMMAND} -E remove_directory release + COMMAND ${CMAKE_COMMAND} -E make_directory release/pvsneslib + + # Copy core development tools (compiler and utilities) + COMMAND ${CMAKE_COMMAND} -E copy_directory devkitsnes release/ + + # Copy library components + COMMAND ${CMAKE_COMMAND} -E copy_directory pvsneslib/include release/pvsneslib + COMMAND ${CMAKE_COMMAND} -E copy_directory pvsneslib/lib release/pvsneslib + COMMAND ${CMAKE_COMMAND} -E copy_directory pvsneslib/docs/html release/pvsneslib + + # Copy library metadata and documentation + COMMAND ${CMAKE_COMMAND} -E copy pvsneslib/PVSnesLib_Logo.png release/pvsneslib/ + COMMAND ${CMAKE_COMMAND} -E copy pvsneslib/pvsneslib_license.txt release/pvsneslib/ + COMMAND ${CMAKE_COMMAND} -E copy pvsneslib/pvsneslib_snesmod.txt release/pvsneslib/ + COMMAND ${CMAKE_COMMAND} -E copy pvsneslib/pvsneslib_version.txt release/pvsneslib/ + + # Copy examples and CMake system for external projects + COMMAND ${CMAKE_COMMAND} -E copy_directory snes-examples release/snes-examples + COMMAND ${CMAKE_COMMAND} -E copy_directory cmake release/cmake + + # Create release archive with version and platform info + COMMAND ${CMAKE_COMMAND} -E chdir release tar czf pvsneslib_${PROJECT_VERSION}_${PVSNESLIB_ARCH}_${PVSNESLIB_OS}.tar.gz pvsneslib cmake + + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "๐Ÿš€ Creating PVSnesLib ${PROJECT_VERSION} release package for ${PVSNESLIB_ARCH}_${PVSNESLIB_OS}" + VERBATIM +) + +# Version information target +add_custom_target(version + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐ŸŽฎ PVSnesLib Version Information" + COMMAND ${CMAKE_COMMAND} -E echo "=================================" + COMMAND ${CMAKE_COMMAND} -E echo "Library Version: ${PROJECT_VERSION}" + COMMAND ${CMAKE_COMMAND} -E echo "Build Platform: ${PVSNESLIB_OS}" + COMMAND ${CMAKE_COMMAND} -E echo "Architecture: ${PVSNESLIB_ARCH}" + COMMAND ${CMAKE_COMMAND} -E echo "Release Name: pvsneslib_${PROJECT_VERSION}_${PVSNESLIB_ARCH}_${PVSNESLIB_OS}.tar.gz" + COMMAND ${CMAKE_COMMAND} -E echo "CMake Version: ${CMAKE_VERSION}" + COMMAND ${CMAKE_COMMAND} -E echo "Build Type: ${CMAKE_BUILD_TYPE}" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMENT "Display PVSnesLib version information" +) + +#============================================================================= +# CUSTOM HELP TARGET - User-Friendly Target Overview +#============================================================================= + +add_custom_target(guide + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐ŸŽฎ PVSnesLib - 3 Essential Targets" + COMMAND ${CMAKE_COMMAND} -E echo "==================================" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐Ÿ”ง 1. MINIMAL - Absolute minimum to write SNES programs:" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target minimal" + COMMAND ${CMAKE_COMMAND} -E echo " Result: C compiler + assembler + tools + library" + COMMAND ${CMAKE_COMMAND} -E echo " Use this: To write your own SNES programs" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐ŸŽฎ 2. EXAMPLES_ONLY - Build all example ROMs:" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target examples_only" + COMMAND ${CMAKE_COMMAND} -E echo " Result: 60+ example SNES ROMs" + COMMAND ${CMAKE_COMMAND} -E echo " Use this: To learn from existing examples" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐Ÿš€ 3. EVERYTHING - Build absolutely everything:" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target everything" + COMMAND ${CMAKE_COMMAND} -E echo " Result: Complete development environment + all examples" + COMMAND ${CMAKE_COMMAND} -E echo " Use this: For complete setup" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐Ÿ“‹ QUICK START:" + COMMAND ${CMAKE_COMMAND} -E echo " cmake -B build -DPVSNESLIB_BUILD_EXAMPLES=ON" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target everything" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐Ÿ“– 4. DOCUMENTATION - Generate API docs [requires Doxygen]:" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target docs" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target docspdf # HTML + PDF manual" + COMMAND ${CMAKE_COMMAND} -E echo " Result: HTML documentation in pvsneslib/docs/html/" + COMMAND ${CMAKE_COMMAND} -E echo " Use this: To browse the complete API reference" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐Ÿš€ 5. RELEASE - Create distribution package:" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target release" + COMMAND ${CMAKE_COMMAND} -E echo " Result: pvsneslib_${PROJECT_VERSION}_${PVSNESLIB_ARCH}_${PVSNESLIB_OS}.tar.gz" + COMMAND ${CMAKE_COMMAND} -E echo " Use this: To create release packages for distribution" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "โ„น๏ธ 6. VERSION INFO - Display version information:" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build --target version" + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "๐ŸŽฏ INDIVIDUAL COMPONENTS - if needed:" + COMMAND ${CMAKE_COMMAND} -E echo " compiler, tools, library, examples, hello_world, effects, etc." + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMENT "Displaying PVSnesLib essential targets" +) + +# Print build summary +message(STATUS "") +message(STATUS "PVSnesLib ${PROJECT_VERSION} build configuration:") +message(STATUS " OS: ${PVSNESLIB_OS}") +message(STATUS " Build examples: ${PVSNESLIB_BUILD_EXAMPLES}") +message(STATUS " Build docs: ${PVSNESLIB_BUILD_DOCS}") +message(STATUS " Install prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "") diff --git a/devkitsnes/bin/.keepme b/a similarity index 100% rename from devkitsnes/bin/.keepme rename to a diff --git a/cmake/SNESGame.cmake b/cmake/SNESGame.cmake new file mode 100644 index 000000000..680df7ce8 --- /dev/null +++ b/cmake/SNESGame.cmake @@ -0,0 +1,367 @@ +# Helper functions for building SNES games with PVSnesLib + +# Function to add a SNES game target +# Usage: add_snes_game(target_name +# SOURCES source1.c source2.c ... +# GRAPHICS sprite.png background.bmp ... +# AUDIO music.it sound.wav ... +# ROM_NAME output_name +# HIROM ON/OFF +# FASTROM ON/OFF +# ) +function(add_snes_game target_name) + cmake_parse_arguments(SNES + "HIROM;FASTROM" + "ROM_NAME;SOUNDBANK" + "SOURCES;GRAPHICS;AUDIO;ASM_SOURCES;DATA_FILES;AUDIO_FILES;INCLUDE_DIRS" + ${ARGN} + ) + + if(NOT SNES_ROM_NAME) + set(SNES_ROM_NAME ${target_name}) + endif() + + if(NOT SNES_SOURCES) + message(FATAL_ERROR "add_snes_game: No SOURCES specified for ${target_name}") + endif() + + # Determine library configuration + if(SNES_HIROM) + set(ROM_TYPE "HiROM") + set(TCC_HIROM_FLAG "-H") + else() + set(ROM_TYPE "LoROM") + set(TCC_HIROM_FLAG "") + endif() + + if(SNES_FASTROM) + set(ROM_SPEED "FastROM") + set(TCC_FASTROM_FLAG "-F") + else() + set(ROM_SPEED "SlowROM") + set(TCC_FASTROM_FLAG "") + endif() + + set(LIB_CONFIG "${ROM_TYPE}_${ROM_SPEED}") + + # Get PVSNESLIB_HOME - either from environment or parent project + if(DEFINED ENV{PVSNESLIB_HOME}) + set(PVSNESLIB_HOME_PATH $ENV{PVSNESLIB_HOME}) + elseif(DEFINED PVSNESLIB_HOME) + set(PVSNESLIB_HOME_PATH ${PVSNESLIB_HOME}) + else() + message(FATAL_ERROR "PVSNESLIB_HOME not found. Please set PVSNESLIB_HOME environment variable or ensure you're building within PVSnesLib source tree.") + endif() + + # Set up paths using PVSNESLIB_HOME + set(TCC_COMPILER "${PVSNESLIB_HOME_PATH}/devkitsnes/bin/816-tcc") + set(WLA_ASSEMBLER "${PVSNESLIB_HOME_PATH}/devkitsnes/bin/wla-65816") + set(WLA_LINKER "${PVSNESLIB_HOME_PATH}/devkitsnes/bin/wlalink") + set(OPTIMIZER "${PVSNESLIB_HOME_PATH}/devkitsnes/tools/816-opt") + set(CONSTIFY "${PVSNESLIB_HOME_PATH}/devkitsnes/tools/constify") + + set(LIB_DIR "${PVSNESLIB_HOME_PATH}/pvsneslib/lib/${LIB_CONFIG}") + + # Include directories + set(INCLUDE_DIRS + -I${PVSNESLIB_HOME_PATH}/pvsneslib/include + -I${PVSNESLIB_HOME_PATH}/devkitsnes/include + -I${CMAKE_CURRENT_SOURCE_DIR} + ) + + # Add additional include directories if specified + foreach(inc_dir ${SNES_INCLUDE_DIRS}) + list(APPEND INCLUDE_DIRS -I${CMAKE_CURRENT_SOURCE_DIR}/${inc_dir}) + endforeach() + + # Simplified path management - determine source directory from first source file + # Default to target name directory if no sources or source is in current dir + set(target_source_dir "${CMAKE_CURRENT_SOURCE_DIR}") + if(SNES_SOURCES) + list(GET SNES_SOURCES 0 first_source) + get_filename_component(source_dir ${first_source} DIRECTORY) + if(source_dir) + get_filename_component(target_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/${source_dir}" ABSOLUTE) + endif() + endif() + + # Validate that the source directory exists + if(NOT EXISTS "${target_source_dir}") + message(WARNING "Source directory ${target_source_dir} does not exist for target ${target_name}") + set(target_source_dir "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + # Convert graphics files + set(CONVERTED_GRAPHICS "") + foreach(gfx ${SNES_GRAPHICS}) + get_filename_component(gfx_name ${gfx} NAME_WE) + get_filename_component(gfx_ext ${gfx} EXT) + get_filename_component(gfx_full_name ${gfx} NAME) + get_filename_component(gfx_dir ${gfx} DIRECTORY) + + # Check if pre-converted files exist (including Mode7 formats) + set(existing_pic "${CMAKE_CURRENT_SOURCE_DIR}/${gfx_dir}/${gfx_name}.pic") + set(existing_pal "${CMAKE_CURRENT_SOURCE_DIR}/${gfx_dir}/${gfx_name}.pal") + set(existing_pc7 "${CMAKE_CURRENT_SOURCE_DIR}/${gfx_dir}/${gfx_name}.pc7") + set(existing_mp7 "${CMAKE_CURRENT_SOURCE_DIR}/${gfx_dir}/${gfx_name}.mp7") + + set(gfx_output "${target_name}_${gfx_full_name}.pic") + set(pal_output "${target_name}_${gfx_full_name}.pal") + + if(EXISTS ${existing_pc7} AND EXISTS ${existing_mp7}) + # Use existing Mode7 converted files + set(gfx_output "${target_name}_${gfx_full_name}.pc7") + set(pal_output "${target_name}_${gfx_full_name}.mp7") + add_custom_command(OUTPUT ${gfx_output} ${pal_output} + COMMAND ${CMAKE_COMMAND} -E copy ${existing_pc7} ${CMAKE_CURRENT_BINARY_DIR}/${gfx_output} + COMMAND ${CMAKE_COMMAND} -E copy ${existing_mp7} ${CMAKE_CURRENT_BINARY_DIR}/${pal_output} + DEPENDS ${existing_pc7} ${existing_mp7} + COMMENT "Using existing Mode7 graphics ${gfx_name}" + ) + elseif(EXISTS ${existing_pic} AND EXISTS ${existing_pal}) + # Use existing converted files + add_custom_command(OUTPUT ${gfx_output} ${pal_output} + COMMAND ${CMAKE_COMMAND} -E copy ${existing_pic} ${CMAKE_CURRENT_BINARY_DIR}/${gfx_output} + COMMAND ${CMAKE_COMMAND} -E copy ${existing_pal} ${CMAKE_CURRENT_BINARY_DIR}/${pal_output} + DEPENDS ${existing_pic} ${existing_pal} + COMMENT "Using existing converted graphics ${gfx_name}" + ) + elseif(gfx_ext STREQUAL ".png") + # PNG files - use gfx4snes - include full filename to avoid conflicts + get_filename_component(gfx_absolute ${CMAKE_CURRENT_SOURCE_DIR}/${gfx} ABSOLUTE) + get_filename_component(gfx_dir_abs ${gfx_absolute} DIRECTORY) + add_custom_command(OUTPUT ${gfx_output} ${pal_output} + COMMAND ${PVSNESLIB_HOME_PATH}/devkitsnes/tools/gfx4snes -s 8 -o 16 -u 16 -p -e 0 -i ${gfx_absolute} + COMMAND ${CMAKE_COMMAND} -E copy ${gfx_dir_abs}/${gfx_name}.pic ${CMAKE_CURRENT_BINARY_DIR}/${gfx_output} + COMMAND ${CMAKE_COMMAND} -E copy ${gfx_dir_abs}/${gfx_name}.pal ${CMAKE_CURRENT_BINARY_DIR}/${pal_output} + DEPENDS ${gfx_absolute} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Converting ${gfx} (PNG) to SNES format" + ) + elseif(gfx_ext STREQUAL ".bmp") + # BMP files - use gfx2snes (which properly supports BMP) - include full filename to avoid conflicts + get_filename_component(gfx_absolute ${CMAKE_CURRENT_SOURCE_DIR}/${gfx} ABSOLUTE) + get_filename_component(gfx_dir_abs ${gfx_absolute} DIRECTORY) + add_custom_command(OUTPUT ${gfx_output} ${pal_output} + COMMAND ${PVSNESLIB_HOME_PATH}/devkitsnes/tools/gfx2snes -pc16 -gs8 -pe0 -p -m -fbmp ${gfx_absolute} + COMMAND ${CMAKE_COMMAND} -E copy ${gfx_dir_abs}/${gfx_name}.pic ${CMAKE_CURRENT_BINARY_DIR}/${gfx_output} + COMMAND ${CMAKE_COMMAND} -E copy ${gfx_dir_abs}/${gfx_name}.pal ${CMAKE_CURRENT_BINARY_DIR}/${pal_output} + DEPENDS ${gfx_absolute} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Converting ${gfx} (BMP) to SNES format" + ) + endif() + list(APPEND CONVERTED_GRAPHICS ${gfx_output} ${pal_output}) + endforeach() + + # Convert audio files and generate soundbank + set(CONVERTED_AUDIO "") + set(SOUNDBANK_DEPS "") + if(SNES_AUDIO_FILES AND SNES_SOUNDBANK) + # Generate soundbank from IT files using smconv - make unique per target + get_filename_component(soundbank_name ${SNES_SOUNDBANK} NAME) + string(REPLACE "/" "_" soundbank_unique "${SNES_SOUNDBANK}") + set(soundbank_asm "${target_name}_${soundbank_unique}.asm") + set(soundbank_obj "${target_name}_${soundbank_unique}.obj") + set(soundbank_bnk "${target_name}_${soundbank_unique}.bnk") + set(soundbank_h "${target_name}_${soundbank_unique}.h") + + # Create smconv command with all audio files + set(SMCONV_CMD "${PVSNESLIB_HOME_PATH}/devkitsnes/tools/smconv") + set(SMCONV_ARGS "-s" "-o" "${SNES_SOUNDBANK}" "-V" "-b" "5") + + # Generate soundbank and copy object file to build directory in one step + set(unique_soundbank_obj "${CMAKE_CURRENT_BINARY_DIR}/${soundbank_obj}") + add_custom_command(OUTPUT ${unique_soundbank_obj} ${soundbank_asm} ${soundbank_bnk} ${soundbank_h} + COMMAND ${SMCONV_CMD} ${SMCONV_ARGS} ${SNES_AUDIO_FILES} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${SNES_SOUNDBANK}.obj ${unique_soundbank_obj} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${SNES_SOUNDBANK}.h ${target_source_dir}/soundbank.h + DEPENDS ${SNES_AUDIO_FILES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating soundbank from ${SNES_AUDIO_FILES}" + ) + + list(APPEND SOUNDBANK_DEPS ${unique_soundbank_obj}) + list(APPEND CONVERTED_AUDIO ${soundbank_asm}) + list(APPEND OBJ_FILES ${unique_soundbank_obj}) + endif() + + # Legacy audio conversion for individual files + foreach(audio ${SNES_AUDIO}) + get_filename_component(audio_name ${audio} NAME_WE) + get_filename_component(audio_ext ${audio} EXT) + + if(audio_ext STREQUAL ".it" OR audio_ext STREQUAL ".mod") + set(audio_output "${audio_name}.bnk") + add_custom_command(OUTPUT ${audio_output} + COMMAND ${PVSNESLIB_HOME_PATH}/devkitsnes/tools/smconv ${audio} + DEPENDS ${audio} + COMMENT "Converting ${audio} to SNES format" + ) + list(APPEND CONVERTED_AUDIO ${audio_output}) + endif() + endforeach() + + # Handle data files (.dat files) - copy them to build directory + set(CONVERTED_DATA "") + foreach(data_file ${SNES_DATA_FILES}) + get_filename_component(data_name ${data_file} NAME) + set(data_output "${target_name}_${data_name}") + add_custom_command(OUTPUT ${data_output} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${data_file} ${data_output} + DEPENDS ${data_file} + COMMENT "Copying data file ${data_file}" + ) + list(APPEND CONVERTED_DATA ${data_output}) + endforeach() + + # Compile C sources to assembly + set(ASM_FILES "") + foreach(src ${SNES_SOURCES}) + get_filename_component(src_name ${src} NAME_WE) + # Make output files unique per target to avoid conflicts + set(ps_file "${target_name}_${src_name}.ps") + set(asm_file "${target_name}_${src_name}.asm") + set(obj_file "${target_name}_${src_name}.obj") + + # C -> .ps (intermediate) - use absolute paths + get_filename_component(src_absolute ${src} ABSOLUTE) + add_custom_command(OUTPUT ${ps_file} + COMMAND ${TCC_COMPILER} ${INCLUDE_DIRS} -Wall -c ${src_absolute} ${TCC_HIROM_FLAG} ${TCC_FASTROM_FLAG} -o ${ps_file} + DEPENDS ${src_absolute} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Compiling ${src} to .ps" + ) + + # .ps -> .asm (optimized) + add_custom_command(OUTPUT ${asm_file} + COMMAND ${OPTIMIZER} ${ps_file} > ${src_name}.asp + COMMAND ${CONSTIFY} ${src_absolute} ${src_name}.asp ${asm_file} + COMMAND ${CMAKE_COMMAND} -E remove ${src_name}.asp + DEPENDS ${ps_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Optimizing ${ps_file} to ${asm_file}" + ) + + # .asm -> .obj (copy necessary includes to build dir) + # Look for hdr.asm and data.asm using a helper function + function(find_asm_file filename result_var) + # First check in ASM_SOURCES list + foreach(asm_src ${SNES_ASM_SOURCES}) + get_filename_component(asm_name ${asm_src} NAME) + if(asm_name STREQUAL ${filename}) + get_filename_component(full_path "${CMAKE_CURRENT_SOURCE_DIR}/${asm_src}" ABSOLUTE) + if(EXISTS "${full_path}") + set(${result_var} "${full_path}" PARENT_SCOPE) + return() + endif() + endif() + endforeach() + + # Then check in target source directory + set(fallback_path "${target_source_dir}/${filename}") + if(EXISTS "${fallback_path}") + set(${result_var} "${fallback_path}" PARENT_SCOPE) + else() + message(WARNING "Required ASM file ${filename} not found for target ${target_name}") + set(${result_var} "${fallback_path}" PARENT_SCOPE) # Use anyway, might be generated + endif() + endfunction() + + find_asm_file("hdr.asm" hdr_asm_path) + find_asm_file("data.asm" data_asm_path) + + add_custom_command(OUTPUT ${obj_file} + COMMAND ${CMAKE_COMMAND} -E copy ${hdr_asm_path} ${CMAKE_CURRENT_BINARY_DIR}/hdr.asm + COMMAND ${CMAKE_COMMAND} -E copy ${data_asm_path} ${CMAKE_CURRENT_BINARY_DIR}/data.asm + COMMAND ${WLA_ASSEMBLER} -d -s -x -o ${obj_file} ${asm_file} + DEPENDS ${asm_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Assembling ${asm_file} to ${obj_file}" + ) + + list(APPEND ASM_FILES ${asm_file}) + list(APPEND OBJ_FILES ${obj_file}) + endforeach() + + # Handle additional ASM sources - make output files unique per target + foreach(asm ${SNES_ASM_SOURCES}) + get_filename_component(asm_name ${asm} NAME_WE) + get_filename_component(asm_absolute ${asm} ABSOLUTE) + get_filename_component(asm_dir ${asm_absolute} DIRECTORY) + set(obj_file "${target_name}_${asm_name}.obj") + + add_custom_command(OUTPUT ${obj_file} + COMMAND ${WLA_ASSEMBLER} -d -s -x -o ${CMAKE_CURRENT_BINARY_DIR}/${obj_file} ${asm_absolute} + DEPENDS ${asm_absolute} + WORKING_DIRECTORY ${asm_dir} + COMMENT "Assembling ${asm} to ${obj_file}" + ) + + list(APPEND OBJ_FILES ${obj_file}) + endforeach() + + # Create linkfile and link ROM + set(ROM_FILE "${target_source_dir}/${SNES_ROM_NAME}.sfc") + set(LINKFILE "${target_name}_linkfile") + set(SYM_FILE "${target_name}_${SNES_ROM_NAME}.sym") + + # Create linkfile generation script + set(LINKFILE_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/create_${target_name}_linkfile.cmake") + file(WRITE ${LINKFILE_SCRIPT} + "file(WRITE \"${LINKFILE}\" \"[objects]\\n\")\n" + ) + foreach(obj_file ${OBJ_FILES}) + file(APPEND ${LINKFILE_SCRIPT} + "file(APPEND \"${LINKFILE}\" \"${obj_file}\\n\")\n" + ) + endforeach() + # Add library objects + file(APPEND ${LINKFILE_SCRIPT} + "file(APPEND \"${LINKFILE}\" \"${LIB_DIR}/crt0_snes.obj\\n\")\n" + "file(APPEND \"${LINKFILE}\" \"${LIB_DIR}/libm.obj\\n\")\n" + "file(APPEND \"${LINKFILE}\" \"${LIB_DIR}/libtcc.obj\\n\")\n" + "file(APPEND \"${LINKFILE}\" \"${LIB_DIR}/libc.obj\\n\")\n" + ) + + add_custom_command(OUTPUT ${ROM_FILE} + # Create linkfile + COMMAND ${CMAKE_COMMAND} -P ${LINKFILE_SCRIPT} + + # Link ROM + COMMAND ${WLA_LINKER} -d -s -v -A -c -L ${LIB_DIR} ${LINKFILE} ${ROM_FILE} + + # Clean up + COMMAND ${CMAKE_COMMAND} -E remove ${LINKFILE} + + DEPENDS ${OBJ_FILES} ${CONVERTED_GRAPHICS} ${CONVERTED_AUDIO} ${CONVERTED_DATA} ${SOUNDBANK_DEPS} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Linking ${ROM_FILE}" + ) + + # Create the target + add_custom_target(${target_name} + DEPENDS ${ROM_FILE} + COMMENT "Building SNES game: ${SNES_ROM_NAME}" + ) + + # Note: Individual clean targets removed for simplicity + # Use 'cmake --build build --target clean' to clean everything + +endfunction() + +# Helper function for simple SNES games (most common case) +function(add_simple_snes_game target_name) + cmake_parse_arguments(SIMPLE "" "ROM_NAME" "SOURCES" ${ARGN}) + + if(NOT SIMPLE_ROM_NAME) + set(SIMPLE_ROM_NAME ${target_name}) + endif() + + add_snes_game(${target_name} + SOURCES ${SIMPLE_SOURCES} + ROM_NAME ${SIMPLE_ROM_NAME} + HIROM OFF + FASTROM OFF + ) +endfunction() diff --git a/cmake/replace_text.cmake b/cmake/replace_text.cmake new file mode 100644 index 000000000..499bbb227 --- /dev/null +++ b/cmake/replace_text.cmake @@ -0,0 +1,22 @@ +# Cross-platform text replacement utility for CMake +# Usage: cmake -DINPUT_FILE=input.txt -DOUTPUT_FILE=output.txt -DPATTERN="old" -DREPLACEMENT="new" -P replace_text.cmake + +if(NOT INPUT_FILE OR NOT OUTPUT_FILE OR NOT DEFINED PATTERN OR NOT DEFINED REPLACEMENT) + message(FATAL_ERROR "Usage: cmake -DINPUT_FILE=input.txt -DOUTPUT_FILE=output.txt -DPATTERN=\"old\" -DREPLACEMENT=\"new\" -P replace_text.cmake") +endif() + +# Read the input file +file(READ "${INPUT_FILE}" FILE_CONTENT) + +# Perform the replacement - use string(REPLACE) for literal replacement to avoid regex escaping issues +if(PATTERN MATCHES ".*hdr.*") + # Special handling for hdr.asm patterns + string(REPLACE ".include \"hdr.asm\"" "${REPLACEMENT}" FILE_CONTENT "${FILE_CONTENT}") + string(REPLACE ".INCLUDE \"hdr.asm\"" "${REPLACEMENT}" FILE_CONTENT "${FILE_CONTENT}") +else() + # Use regex replace for other patterns + string(REGEX REPLACE "${PATTERN}" "${REPLACEMENT}" FILE_CONTENT "${FILE_CONTENT}") +endif() + +# Write to output file +file(WRITE "${OUTPUT_FILE}" "${FILE_CONTENT}") diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt new file mode 100644 index 000000000..4f5399240 --- /dev/null +++ b/compiler/CMakeLists.txt @@ -0,0 +1,89 @@ +# Compiler tools: TCC (816-tcc) and WLA-DX (wla-65816, wla-spc700, wlalink) + +# Set variables matching existing Makefile +set(TCC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tcc") +set(WLA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wla-dx") +set(BIN_DIR "${CMAKE_SOURCE_DIR}/devkitsnes/bin") + +# Platform-specific settings (matching existing Makefile) +if(WIN32) + set(CMAKE_TYPE "MSYS Makefiles") +else() + set(CMAKE_TYPE "Unix Makefiles") +endif() + +# TCC Compiler (816-tcc) +# Check if TCC submodule is initialized +if(EXISTS ${TCC_DIR}/configure) + message(STATUS "TCC submodule found, building 816-tcc compiler") + ExternalProject_Add(tcc_compiler + SOURCE_DIR ${TCC_DIR} + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E chdir ${TCC_DIR} ./configure + BUILD_COMMAND ${CMAKE_COMMAND} -E chdir ${TCC_DIR} $(MAKE) + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${TCC_DIR}/816-tcc ${BIN_DIR}/816-tcc + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + ) +else() + message(WARNING "TCC submodule not initialized. Run: git submodule update --init --recursive") + # Create a dummy target that fails with helpful message + add_custom_target(tcc_compiler + COMMAND ${CMAKE_COMMAND} -E echo "Error: TCC submodule not initialized" + COMMAND ${CMAKE_COMMAND} -E echo "Please run: git submodule update --init --recursive" + COMMAND ${CMAKE_COMMAND} -E false + COMMENT "TCC submodule missing" + ) +endif() + +# WLA-DX Assembler (already uses CMake!) +# Check if WLA-DX submodule is initialized +if(EXISTS ${WLA_DIR}/CMakeLists.txt) + message(STATUS "WLA-DX submodule found, building assembler tools") + ExternalProject_Add(wla_assembler + SOURCE_DIR ${WLA_DIR} + CMAKE_ARGS + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -G "${CMAKE_TYPE}" + BUILD_COMMAND ${CMAKE_COMMAND} --build --target wla-65816 wla-spc700 wlalink + INSTALL_COMMAND + ${CMAKE_COMMAND} -E copy /binaries/wla-65816 ${BIN_DIR}/wla-65816 + COMMAND + ${CMAKE_COMMAND} -E copy /binaries/wla-spc700 ${BIN_DIR}/wla-spc700 + COMMAND + ${CMAKE_COMMAND} -E copy /binaries/wlalink ${BIN_DIR}/wlalink + BUILD_ALWAYS FALSE + ) +else() + message(WARNING "WLA-DX submodule not initialized. Run: git submodule update --init --recursive") + # Create a dummy target that fails with helpful message + add_custom_target(wla_assembler + COMMAND ${CMAKE_COMMAND} -E echo "Error: WLA-DX submodule not initialized" + COMMAND ${CMAKE_COMMAND} -E echo "Please run: git submodule update --init --recursive" + COMMAND ${CMAKE_COMMAND} -E false + COMMENT "WLA-DX submodule missing" + ) +endif() + +# Create a combined target for all compiler tools +add_custom_target(pvsneslib_compiler + DEPENDS tcc_compiler wla_assembler + COMMENT "Building PVSnesLib compiler tools" +) + +# Clean target for compiler +add_custom_target(clean_compiler + COMMAND ${CMAKE_COMMAND} -E chdir ${TCC_DIR} $(MAKE) clean || true + COMMAND ${CMAKE_COMMAND} -E chdir ${WLA_DIR} $(MAKE) clean || true + COMMAND ${CMAKE_COMMAND} -E remove -f ${WLA_DIR}/CMakeCache.txt + COMMENT "Cleaning compiler tools" +) + +# Create clean alias for compiler +add_custom_target(compiler + DEPENDS pvsneslib_compiler + COMMENT "๐Ÿ”ง SNES C compiler and assembler" +) + +# Note: Submodule updates should be done manually with: +# git submodule update --remote --merge tcc +# git submodule update --remote --merge wla-dx diff --git a/devkitsnes/snes_rules b/devkitsnes/snes_rules index c94767e98..82593d769 100644 --- a/devkitsnes/snes_rules +++ b/devkitsnes/snes_rules @@ -172,13 +172,13 @@ endif $(LD) -d -s -v -A -c -L ${LIBDIRSOBJS} linkfile $@ # remove unattended characters - @sed -i 's/://' $(ROMNAME).sym + @sed -i.bak 's/://' $(ROMNAME).sym && rm -f $(ROMNAME).sym.bak #keep with section label if needed # @cp -f $(ROMNAME).sym $(ROMNAME).symfull # remove duplicated labels for mesen debugger - @sed -i '/ SECTIONSTART_/d;/ SECTIONEND_/d;/ RAM_USAGE_SLOT_/d;' $(ROMNAME).sym + @sed -i.bak '/ SECTIONSTART_/d;/ SECTIONEND_/d;/ RAM_USAGE_SLOT_/d;' $(ROMNAME).sym && rm -f $(ROMNAME).sym.bak @echo @echo Build finished successfully ! diff --git a/devkitsnes/tools/.keepme b/devkitsnes/tools/.keepme deleted file mode 100644 index e69de29bb..000000000 diff --git a/pvsneslib/CMakeLists.txt b/pvsneslib/CMakeLists.txt new file mode 100644 index 000000000..b6840c156 --- /dev/null +++ b/pvsneslib/CMakeLists.txt @@ -0,0 +1,144 @@ +# PVSnesLib core library + +# Use version from parent project +set(PVSNESLIB_MAJOR ${PROJECT_VERSION_MAJOR}) +set(PVSNESLIB_MINOR ${PROJECT_VERSION_MINOR}) +set(PVSNESLIB_PATCH ${PROJECT_VERSION_PATCH}) +set(PVSNESLIB_VERSION ${PROJECT_VERSION}) + +# Create version header +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/include/snes/libversion.h.in + ${CMAKE_CURRENT_SOURCE_DIR}/include/snes/libversion.h + @ONLY +) + +# Create version text file +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/pvsneslib_version.txt.in + ${CMAKE_CURRENT_SOURCE_DIR}/pvsneslib_version.txt + @ONLY +) + +# Add tool validation function +function(validate_required_tools) + # Check for required external tools + find_program(GIT_EXECUTABLE git) + if(NOT GIT_EXECUTABLE) + message(WARNING "Git not found. Submodule operations may fail.") + endif() + + # Validate that our build tools will exist after being built + set(REQUIRED_TOOLS + "${CMAKE_SOURCE_DIR}/devkitsnes/bin/816-tcc" + "${CMAKE_SOURCE_DIR}/devkitsnes/bin/wla-65816" + "${CMAKE_SOURCE_DIR}/devkitsnes/tools/816-opt" + "${CMAKE_SOURCE_DIR}/devkitsnes/tools/constify" + ) + + # Note: We can't check if these exist yet since they're built by this system + # But we can validate the build targets exist + message(STATUS "Tool validation: Required tools will be built by targets: tcc_compiler, wla_assembler, 816-opt, constify") +endfunction() + +# Validate tools at configuration time +validate_required_tools() + +# Build default library configuration (LoROM SlowROM - what 99% of users need) +# This approach leverages existing library files built by the traditional Makefile +# and provides CMake targets for them +set(LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lib/LoROM_SlowROM") +set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/source") + +# Create library directory +file(MAKE_DIRECTORY ${LIB_DIR}) + +# Library files that should exist (built by Make system) +set(LIBRARY_OBJECTS + ${LIB_DIR}/crt0_snes.obj + ${LIB_DIR}/libm.obj + ${LIB_DIR}/libtcc.obj + ${LIB_DIR}/libc.obj +) + +# Check if library files exist, if not, build them using traditional method +set(MISSING_OBJECTS "") +foreach(obj_file ${LIBRARY_OBJECTS}) + if(NOT EXISTS ${obj_file}) + list(APPEND MISSING_OBJECTS ${obj_file}) + endif() +endforeach() + +if(MISSING_OBJECTS) + message(STATUS "Building missing library objects using traditional Makefile") + add_custom_command(OUTPUT ${LIBRARY_OBJECTS} + COMMAND ${CMAKE_COMMAND} -E env $(MAKE) -C ${SOURCE_DIR} all + DEPENDS tcc_compiler wla_assembler 816-opt constify + WORKING_DIRECTORY ${SOURCE_DIR} + COMMENT "Building PVSnesLib library using traditional Makefile" + ) +endif() + +# Install library objects to lib directory +add_custom_target(pvsneslib_library + DEPENDS ${LIBRARY_OBJECTS} + COMMENT "Building PVSnesLib library (LoROM SlowROM)" +) + +# Create alias for SNESGame.cmake compatibility +add_custom_target(library + DEPENDS pvsneslib_library + COMMENT "PVSnesLib library alias" +) + +# Documentation target (if Doxygen is available) +find_package(Doxygen QUIET) +if(PVSNESLIB_BUILD_DOCS) + if(NOT DOXYGEN_FOUND) + message(STATUS "") + message(STATUS "๐Ÿ“– Documentation build requested but Doxygen not found!") + message(STATUS "") + message(STATUS "To install Doxygen:") + message(STATUS " macOS: brew install doxygen") + message(STATUS " Ubuntu/Debian: sudo apt-get install doxygen") + message(STATUS " Windows: Download from doxygen.nl") + message(STATUS "") + message(STATUS "Skipping documentation generation...") + message(STATUS "") + endif() +endif() + +if(DOXYGEN_FOUND AND PVSNESLIB_BUILD_DOCS) + message(STATUS "๐Ÿ“– Doxygen found (${DOXYGEN_VERSION}) - documentation will be generated") + set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/pvsneslib.dox) + set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + set(DOXY_OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/docs/html) + set(PVSDOCSDIR ${CMAKE_CURRENT_SOURCE_DIR}/docs) + + # Read the dox file and replace variables directly in CMake + file(READ ${DOXYFILE_IN} DOXYFILE_CONTENT) + string(REPLACE "$(PVSDOCSDIR)" "${PVSDOCSDIR}" DOXYFILE_CONTENT "${DOXYFILE_CONTENT}") + string(REPLACE "$(PVSNESLIB_VERSION)" "${PVSNESLIB_VERSION}" DOXYFILE_CONTENT "${DOXYFILE_CONTENT}") + file(WRITE ${DOXYFILE_OUT} "${DOXYFILE_CONTENT}") + + add_custom_target(docs + COMMAND ${CMAKE_COMMAND} -E remove_directory ${DOXY_OUTPUT_DIR} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_SOURCE_DIR}/docs/latex + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating PVSnesLib documentation (HTML only)" + VERBATIM + ) + + # PDF documentation target (mimics original Makefile docspdf target) + add_custom_target(docspdf + COMMAND ${CMAKE_COMMAND} -E remove_directory ${DOXY_OUTPUT_DIR} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_CURRENT_SOURCE_DIR}/docs/latex ${CMAKE_MAKE_PROGRAM} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/docs/latex/refman.pdf ${CMAKE_CURRENT_SOURCE_DIR}/docs/pvsneslib_manual.pdf + COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_SOURCE_DIR}/docs/latex + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating PVSnesLib documentation with PDF manual" + VERBATIM + ) +endif() \ No newline at end of file diff --git a/pvsneslib/include/snes/libversion.h b/pvsneslib/include/snes/libversion.h index 047d5ed85..e44cff8cc 100644 --- a/pvsneslib/include/snes/libversion.h +++ b/pvsneslib/include/snes/libversion.h @@ -1,9 +1,9 @@ #ifndef __PVSNESLIBVERSION_H__ #define __PVSNESLIBVERSION_H__ -#define _PVSNESLIB_MAJOR_ 4 -#define _PVSNESLIB_MINOR_ 4 -#define _PVSNESLIB_PATCH_ 0 +#define _PVSNESLIB_MAJOR_ 4 +#define _PVSNESLIB_MINOR_ 4 +#define _PVSNESLIB_PATCH_ 0 #define _PVSNESLIB_STRING_ "PVSnesLib V4.4.0" diff --git a/pvsneslib/include/snes/libversion.h.in b/pvsneslib/include/snes/libversion.h.in new file mode 100644 index 000000000..e999eff24 --- /dev/null +++ b/pvsneslib/include/snes/libversion.h.in @@ -0,0 +1,10 @@ +#ifndef __PVSNESLIBVERSION_H__ +#define __PVSNESLIBVERSION_H__ + +#define _PVSNESLIB_MAJOR_ @PVSNESLIB_MAJOR @ +#define _PVSNESLIB_MINOR_ @PVSNESLIB_MINOR @ +#define _PVSNESLIB_PATCH_ @PVSNESLIB_PATCH @ + +#define _PVSNESLIB_STRING_ "PVSnesLib V@PVSNESLIB_MAJOR@.@PVSNESLIB_MINOR@.@PVSNESLIB_PATCH@" + +#endif // __PVSNESLIBVERSION_H__ diff --git a/pvsneslib/pvsneslib_version.txt.in b/pvsneslib/pvsneslib_version.txt.in new file mode 100644 index 000000000..1ed627474 --- /dev/null +++ b/pvsneslib/pvsneslib_version.txt.in @@ -0,0 +1 @@ +@PVSNESLIB_VERSION@ diff --git a/pvsneslib/source/Makefile b/pvsneslib/source/Makefile index fac1c8d35..6e9ef9125 100644 --- a/pvsneslib/source/Makefile +++ b/pvsneslib/source/Makefile @@ -37,7 +37,7 @@ else $(CC) $(CFLAGS) -Wall -c $< -o $@ endif endif - sed -i 's/.include "hdr.asm"//' $@ + sed -i.bak 's/.include "hdr.asm"//' $@ && rm -f $@.bak reset_comp: @echo "; HIROM / FASTROM definitions" > comp_defs.asm diff --git a/snes-examples/CMakeLists.txt b/snes-examples/CMakeLists.txt new file mode 100644 index 000000000..2fe098b6b --- /dev/null +++ b/snes-examples/CMakeLists.txt @@ -0,0 +1,656 @@ +# SNES Examples using PVSnesLib - PURE CMAKE IMPLEMENTATION +# NO MAKEFILES - ONLY CMAKE + +# Include our SNES game helper functions +include(${CMAKE_SOURCE_DIR}/cmake/SNESGame.cmake) + +# Hello World example +add_snes_game(hello_world + SOURCES hello_world/src/hello_world.c + GRAPHICS hello_world/pvsneslibfont.png + ASM_SOURCES hello_world/data.asm hello_world/hdr.asm + ROM_NAME hello_world +) + +# Breakpoints example +add_snes_game(breakpoints + SOURCES breakpoints/src/breakpoints.c + GRAPHICS breakpoints/pvsneslibfont.png + ASM_SOURCES breakpoints/data.asm breakpoints/hdr.asm + ROM_NAME breakpoints +) + +# Debug example (no graphics) +add_snes_game(debug + SOURCES debug/debug.c + ASM_SOURCES debug/data.asm debug/hdr.asm + ROM_NAME debug +) + +# Random example +add_snes_game(random + SOURCES random/random.c + GRAPHICS random/pvsneslibfont.bmp + ASM_SOURCES random/data.asm random/hdr.asm + ROM_NAME random +) + +# Scoring example +add_snes_game(scoring + SOURCES scoring/scoring.c + GRAPHICS scoring/pvsneslibfont.bmp + ASM_SOURCES scoring/data.asm scoring/hdr.asm + ROM_NAME scoring +) + +# Timer example +add_snes_game(timer + SOURCES timer/timer.c + GRAPHICS timer/pvsneslibfont.bmp + ASM_SOURCES timer/data.asm timer/hdr.asm + ROM_NAME timer +) + +# Test region example +add_snes_game(testregion + SOURCES testregion/testregion.c + GRAPHICS testregion/pvsneslibfont.bmp + ASM_SOURCES testregion/data.asm testregion/hdr.asm + ROM_NAME testregion +) + +# Memory mapping example (uses HiROM) +add_snes_game(memory_mapping + SOURCES memory_mapping/src/memory_mapping.c + GRAPHICS memory_mapping/pvsneslibfont.png + ASM_SOURCES memory_mapping/data.asm memory_mapping/hdr.asm + ROM_NAME memory_mapping + HIROM ON + FASTROM ON +) + +# Type console example (uses pal_ntsc.c) +add_snes_game(typeconsole + SOURCES typeconsole/src/pal_ntsc.c + GRAPHICS typeconsole/pvsneslibfont.bmp + ASM_SOURCES typeconsole/data.asm typeconsole/hdr.asm + ROM_NAME typeconsole +) + +#============================================================================= +# AUDIO EXAMPLES - Advanced sound and music examples +#============================================================================= + +# Audio effects example +add_snes_game(effects + SOURCES audio/effects/effects.c + GRAPHICS audio/effects/pvsneslibfont.bmp + ASM_SOURCES audio/effects/data.asm audio/effects/hdr.asm + AUDIO_FILES audio/effects/res/effectssfx.it + SOUNDBANK audio/effects/res/soundbank + ROM_NAME effects +) + +# Effects and music combined example +add_snes_game(effectsandmusic + SOURCES audio/effectsandmusic/effectsandmusic.c + GRAPHICS audio/effectsandmusic/pvsneslibfont.bmp + ASM_SOURCES audio/effectsandmusic/data.asm audio/effectsandmusic/hdr.asm + AUDIO_FILES audio/effectsandmusic/res/effectssfx.it audio/effectsandmusic/res/pollen8.it + SOUNDBANK audio/effectsandmusic/res/soundbank + ROM_NAME effectsandmusic +) + +# Basic music example +add_snes_game(music + SOURCES audio/music/music.c + GRAPHICS audio/music/pvsneslibfont.bmp + ASM_SOURCES audio/music/data.asm audio/music/hdr.asm + AUDIO_FILES audio/music/res/pollen8.it + SOUNDBANK audio/music/res/soundbank + ROM_NAME music +) + +# Music example 2 +add_snes_game(music2 + SOURCES audio/music2/music2.c + GRAPHICS audio/music2/pvsneslibfont.bmp + ASM_SOURCES audio/music2/data.asm audio/music2/hdr.asm + AUDIO_FILES audio/music2/res/pollen8.it + SOUNDBANK audio/music2/res/soundbank + ROM_NAME music2 +) + +# Large music example (>32KB) +add_snes_game(musicGreaterThan32k + SOURCES audio/musicGreaterThan32k/musicGreaterThan32k.c + GRAPHICS audio/musicGreaterThan32k/pvsneslibfont.bmp + ASM_SOURCES audio/musicGreaterThan32k/data.asm audio/musicGreaterThan32k/hdr.asm + AUDIO_FILES audio/musicGreaterThan32k/res/whatislove.it + SOUNDBANK audio/musicGreaterThan32k/res/soundbank + ROM_NAME musicGreaterThan32k +) + +# HiROM music example +add_snes_game(musicHiROM + SOURCES audio/musicHiROM/musicHiROM.c + GRAPHICS audio/musicHiROM/pvsneslibfont.bmp audio/musicHiROM/dancer.png + ASM_SOURCES audio/musicHiROM/data.asm audio/musicHiROM/hdr.asm + AUDIO_FILES audio/musicHiROM/res/whatislove.it + SOUNDBANK audio/musicHiROM/res/soundbank + ROM_NAME musicHiROM + HIROM ON +) + +# Simple sound effect example +add_snes_game(tada + SOURCES audio/tada/tada.c + GRAPHICS audio/tada/pvsneslibfont.bmp + ASM_SOURCES audio/tada/data.asm audio/tada/hdr.asm + ROM_NAME tada +) + +#============================================================================= +# GRAPHICS EXAMPLES - Advanced graphics and visual effects +#============================================================================= + +# Background Examples +add_snes_game(Mode0 + SOURCES graphics/Backgrounds/Mode0/Mode0.c + GRAPHICS graphics/Backgrounds/Mode0/bg0.bmp graphics/Backgrounds/Mode0/bg1.bmp graphics/Backgrounds/Mode0/bg2.bmp graphics/Backgrounds/Mode0/bg3.bmp + ASM_SOURCES graphics/Backgrounds/Mode0/data.asm graphics/Backgrounds/Mode0/hdr.asm + ROM_NAME Mode0 +) + +add_snes_game(Mode1 + SOURCES graphics/Backgrounds/Mode1/Mode1.c + GRAPHICS graphics/Backgrounds/Mode1/pvsneslib.png + ASM_SOURCES graphics/Backgrounds/Mode1/data.asm graphics/Backgrounds/Mode1/hdr.asm + ROM_NAME Mode1 +) + +add_snes_game(Mode1BG3HighPriority + SOURCES graphics/Backgrounds/Mode1BG3HighPriority/Mode1BG3HighPriority.c + GRAPHICS graphics/Backgrounds/Mode1BG3HighPriority/BG1.bmp graphics/Backgrounds/Mode1BG3HighPriority/BG2.bmp graphics/Backgrounds/Mode1BG3HighPriority/BG3.bmp + ASM_SOURCES graphics/Backgrounds/Mode1BG3HighPriority/data.asm graphics/Backgrounds/Mode1BG3HighPriority/hdr.asm + ROM_NAME Mode1BG3HighPriority +) + +add_snes_game(Mode1ContinuousScroll + SOURCES graphics/Backgrounds/Mode1ContinuosScroll/Mode1ContinuosScroll.c + GRAPHICS graphics/Backgrounds/Mode1ContinuosScroll/BG1.bmp graphics/Backgrounds/Mode1ContinuosScroll/BG2.bmp graphics/Backgrounds/Mode1ContinuosScroll/BG3.bmp graphics/Backgrounds/Mode1ContinuosScroll/character.bmp + ASM_SOURCES graphics/Backgrounds/Mode1ContinuosScroll/data.asm graphics/Backgrounds/Mode1ContinuosScroll/hdr.asm + ROM_NAME Mode1ContinuousScroll +) + +add_snes_game(Mode1LZ77 + SOURCES graphics/Backgrounds/Mode1LZ77/Mode1LZ77.c + GRAPHICS graphics/Backgrounds/Mode1LZ77/pvsneslib.png + ASM_SOURCES graphics/Backgrounds/Mode1LZ77/data.asm graphics/Backgrounds/Mode1LZ77/hdr.asm + ROM_NAME Mode1LZ77 +) + +add_snes_game(Mode1MixedScroll + SOURCES graphics/Backgrounds/Mode1MixedScroll/Mode1MixedScroll.c + GRAPHICS graphics/Backgrounds/Mode1MixedScroll/pvsneslib.bmp graphics/Backgrounds/Mode1MixedScroll/shader.bmp + ASM_SOURCES graphics/Backgrounds/Mode1MixedScroll/data.asm graphics/Backgrounds/Mode1MixedScroll/hdr.asm + ROM_NAME Mode1MixedScroll +) + +add_snes_game(Mode1Png + SOURCES graphics/Backgrounds/Mode1Png/Mode1.c + GRAPHICS graphics/Backgrounds/Mode1Png/pvsneslib.png + ASM_SOURCES graphics/Backgrounds/Mode1Png/data.asm graphics/Backgrounds/Mode1Png/hdr.asm + ROM_NAME Mode1Png +) + +add_snes_game(Mode1Scroll + SOURCES graphics/Backgrounds/Mode1Scroll/Mode1Scroll.c + GRAPHICS graphics/Backgrounds/Mode1Scroll/map_512_512.bmp graphics/Backgrounds/Mode1Scroll/pvsneslibfont.bmp + ASM_SOURCES graphics/Backgrounds/Mode1Scroll/data.asm graphics/Backgrounds/Mode1Scroll/hdr.asm + ROM_NAME Mode1Scroll +) + +add_snes_game(Mode3 + SOURCES graphics/Backgrounds/Mode3/Mode3.c + GRAPHICS graphics/Backgrounds/Mode3/pvsneslib.bmp + ASM_SOURCES graphics/Backgrounds/Mode3/data.asm graphics/Backgrounds/Mode3/hdr.asm + ROM_NAME Mode3 +) + +add_snes_game(Mode5 + SOURCES graphics/Backgrounds/Mode5/Mode5.c + GRAPHICS graphics/Backgrounds/Mode5/pvsneslib.png + ASM_SOURCES graphics/Backgrounds/Mode5/data.asm graphics/Backgrounds/Mode5/hdr.asm + ROM_NAME Mode5 +) + +add_snes_game(Mode7 + SOURCES graphics/Backgrounds/Mode7/Mode7.c + GRAPHICS graphics/Backgrounds/Mode7/mode7bg.bmp + ASM_SOURCES graphics/Backgrounds/Mode7/data.asm graphics/Backgrounds/Mode7/hdr.asm + ROM_NAME Mode7 +) + +add_snes_game(Mode7Perspective + SOURCES graphics/Backgrounds/Mode7Perspective/Mode7Perspective.c + GRAPHICS graphics/Backgrounds/Mode7Perspective/ground.png graphics/Backgrounds/Mode7Perspective/sky.png + ASM_SOURCES graphics/Backgrounds/Mode7Perspective/data.asm graphics/Backgrounds/Mode7Perspective/hdr.asm + ROM_NAME Mode7Perspective +) + +# Effects Examples +add_snes_game(Fading + SOURCES graphics/Effects/Fading/Fading.c + GRAPHICS graphics/Effects/Fading/pvsneslib.bmp + ASM_SOURCES graphics/Effects/Fading/data.asm graphics/Effects/Fading/hdr.asm + ROM_NAME Fading +) + +add_snes_game(GradientColors + SOURCES graphics/Effects/GradientColors/GradientColors.c + GRAPHICS graphics/Effects/GradientColors/pvsneslib.png + ASM_SOURCES graphics/Effects/GradientColors/data.asm graphics/Effects/GradientColors/hdr.asm + ROM_NAME GradientColors +) + +add_snes_game(HDMAGradient + SOURCES graphics/Effects/HDMAGradient/HDMAGradient.c + GRAPHICS graphics/Effects/HDMAGradient/pvsneslib.bmp + ASM_SOURCES graphics/Effects/HDMAGradient/data.asm graphics/Effects/HDMAGradient/hdr.asm + ROM_NAME HDMAGradient +) + +add_snes_game(MosaicShading + SOURCES graphics/Effects/MosaicShading/MosaicShading.c + GRAPHICS graphics/Effects/MosaicShading/pvsneslib.bmp + ASM_SOURCES graphics/Effects/MosaicShading/data.asm graphics/Effects/MosaicShading/hdr.asm + ROM_NAME MosaicShading +) + +add_snes_game(ParallaxScrolling + SOURCES graphics/Effects/ParallaxScrolling/ParallaxScrolling.c + ASM_SOURCES graphics/Effects/ParallaxScrolling/data.asm graphics/Effects/ParallaxScrolling/hdr.asm + ROM_NAME ParallaxScrolling +) + +add_snes_game(Transparency + SOURCES graphics/Effects/Transparency/Transparency.c + GRAPHICS graphics/Effects/Transparency/backgrounds.bmp graphics/Effects/Transparency/clouds.bmp + ASM_SOURCES graphics/Effects/Transparency/data.asm graphics/Effects/Transparency/hdr.asm + ROM_NAME Transparency +) + +add_snes_game(TransparentWindow + SOURCES graphics/Effects/TransparentWindow/src/main.c graphics/Effects/TransparentWindow/src/window.c + GRAPHICS graphics/Effects/TransparentWindow/res/background.png + ASM_SOURCES graphics/Effects/TransparentWindow/data.asm graphics/Effects/TransparentWindow/hdr.asm + ROM_NAME TransparentWindow +) + +add_snes_game(Waves + SOURCES graphics/Effects/Waves/Waves.c + ASM_SOURCES graphics/Effects/Waves/data.asm graphics/Effects/Waves/hdr.asm + ROM_NAME Waves +) + +add_snes_game(Window + SOURCES graphics/Effects/Window/Window.c + GRAPHICS graphics/Effects/Window/pvsneslibbg1.png graphics/Effects/Window/pvsneslibbg2.png + ASM_SOURCES graphics/Effects/Window/data.asm graphics/Effects/Window/hdr.asm + ROM_NAME Window +) + +# Sprite Examples +add_snes_game(AnimatedSprite + SOURCES graphics/Sprites/AnimatedSprite/AnimatedSprite.c + GRAPHICS graphics/Sprites/AnimatedSprite/sprites.bmp + ASM_SOURCES graphics/Sprites/AnimatedSprite/data.asm graphics/Sprites/AnimatedSprite/hdr.asm + ROM_NAME AnimatedSprite +) + +add_snes_game(DynamicEngineMetaSprite + SOURCES graphics/Sprites/DynamicEngineMetaSprite/DynamicEngineMetaSprite.c + GRAPHICS graphics/Sprites/DynamicEngineMetaSprite/pvsneslib.png graphics/Sprites/DynamicEngineMetaSprite/sprite16.png graphics/Sprites/DynamicEngineMetaSprite/sprite32.png + ASM_SOURCES graphics/Sprites/DynamicEngineMetaSprite/data.asm graphics/Sprites/DynamicEngineMetaSprite/hdr.asm + ROM_NAME DynamicEngineMetaSprite +) + +add_snes_game(DynamicEngineSprite + SOURCES graphics/Sprites/DynamicEngineSprite/DynamicEngineSprite.c + GRAPHICS graphics/Sprites/DynamicEngineSprite/pvsneslib.png graphics/Sprites/DynamicEngineSprite/sprite16.png graphics/Sprites/DynamicEngineSprite/sprite32.png graphics/Sprites/DynamicEngineSprite/sprite8.png + ASM_SOURCES graphics/Sprites/DynamicEngineSprite/data.asm graphics/Sprites/DynamicEngineSprite/hdr.asm + ROM_NAME DynamicEngineSprite +) + +add_snes_game(DynamicSprite + SOURCES graphics/Sprites/DynamicSprite/DynamicSprite.c + GRAPHICS graphics/Sprites/DynamicSprite/sprites.bmp + ASM_SOURCES graphics/Sprites/DynamicSprite/data.asm graphics/Sprites/DynamicSprite/hdr.asm + ROM_NAME DynamicSprite +) + +add_snes_game(ObjectSize + SOURCES graphics/Sprites/ObjectSize/ObjectSize.c + GRAPHICS graphics/Sprites/ObjectSize/sprite8.bmp graphics/Sprites/ObjectSize/sprite16.bmp graphics/Sprites/ObjectSize/sprite32.bmp graphics/Sprites/ObjectSize/sprite64.bmp graphics/Sprites/ObjectSize/pvsneslibfont.bmp + ASM_SOURCES graphics/Sprites/ObjectSize/data.asm graphics/Sprites/ObjectSize/hdr.asm + ROM_NAME ObjectSize +) + +add_snes_game(SimpleSprite + SOURCES graphics/Sprites/SimpleSprite/SimpleSprite.c + GRAPHICS graphics/Sprites/SimpleSprite/sprites.bmp + ASM_SOURCES graphics/Sprites/SimpleSprite/data.asm graphics/Sprites/SimpleSprite/hdr.asm + ROM_NAME SimpleSprite +) + +# Palette Example +add_snes_game(GetColors + SOURCES graphics/Palette/GetColors/GetColors.c + GRAPHICS graphics/Palette/GetColors/ortf.png + ASM_SOURCES graphics/Palette/GetColors/data.asm graphics/Palette/GetColors/hdr.asm + ROM_NAME GetColors +) + +#============================================================================= +# GAMES EXAMPLES - Full game implementations +#============================================================================= + +add_snes_game(breakout + SOURCES games/breakout/breakout.c + ASM_SOURCES games/breakout/data.asm games/breakout/hdr.asm + DATA_FILES games/breakout/backpal.dat games/breakout/bg1map.dat games/breakout/bg2map.dat games/breakout/palette.dat games/breakout/tiles1.dat games/breakout/tiles2.dat + ROM_NAME breakout +) + +add_snes_game(likemario + SOURCES games/likemario/LikeMario.c + GRAPHICS games/likemario/tiles.png games/likemario/mario_sprite.bmp games/likemario/mariofont.bmp + ASM_SOURCES games/likemario/data.asm games/likemario/hdr.asm + AUDIO_FILES games/likemario/overworld.it + SOUNDBANK games/likemario/soundbank + ROM_NAME likemario +) + +#============================================================================= +# INPUT EXAMPLES - Controller, mouse, and input device examples +#============================================================================= + +add_snes_game(controller + SOURCES input/controller/controller.c + GRAPHICS input/controller/pvsneslibfont.bmp + ASM_SOURCES input/controller/data.asm input/controller/hdr.asm + ROM_NAME controller +) + +add_snes_game(mouse + SOURCES input/mouse/mouse.c + GRAPHICS input/mouse/pvsneslibfont.bmp input/mouse/cursor.png input/mouse/buttons.png + ASM_SOURCES input/mouse/data.asm input/mouse/hdr.asm + ROM_NAME mouse +) + +add_snes_game(mouse-data-test + SOURCES input/mouse-data-test/mouse-data-test.c + GRAPHICS input/mouse-data-test/pvsneslibfont.bmp + ASM_SOURCES input/mouse-data-test/data.asm input/mouse-data-test/hdr.asm + ROM_NAME mouse-data-test +) + +add_snes_game(multiplay5 + SOURCES input/multiplay5/multiplay5.c + GRAPHICS input/multiplay5/pvsneslibfont.bmp + ASM_SOURCES input/multiplay5/data.asm input/multiplay5/hdr.asm + ROM_NAME multiplay5 +) + +add_snes_game(superscope + SOURCES input/superscope/superscope.c + GRAPHICS input/superscope/aim_adjust_target.png input/superscope/sprites.png input/superscope/pvsneslibfont.bmp + ASM_SOURCES input/superscope/data.asm input/superscope/hdr.asm + ROM_NAME superscope +) + +#============================================================================= +# LOGO EXAMPLES - Company logo demonstrations +#============================================================================= + +add_snes_game(LogoCapcom + SOURCES logo/snes-logo-capcom/src/main.c logo/snes-logo-capcom/src/logo.c + GRAPHICS logo/snes-logo-capcom/res/logo.bmp + ASM_SOURCES logo/snes-logo-capcom/data.asm logo/snes-logo-capcom/hdr.asm + AUDIO_FILES logo/snes-logo-capcom/res/logo.it + SOUNDBANK logo/snes-logo-capcom/soundbank + ROM_NAME LogoCapcom +) + +add_snes_game(LogoKonami + SOURCES logo/snes-logo-konami/src/main.c logo/snes-logo-konami/src/logo.c + GRAPHICS logo/snes-logo-konami/res/companyFire.bmp logo/snes-logo-konami/res/companyLogo.bmp + ASM_SOURCES logo/snes-logo-konami/data.asm logo/snes-logo-konami/hdr.asm + AUDIO_FILES logo/snes-logo-konami/res/logo.it + SOUNDBANK logo/snes-logo-konami/soundbank + ROM_NAME LogoKonami +) + +add_snes_game(LogoPVSnesLib + SOURCES logo/snes-logo-pvsneslib/src/main.c logo/snes-logo-pvsneslib/src/logo.c + GRAPHICS logo/snes-logo-pvsneslib/res/logo.bmp + ASM_SOURCES logo/snes-logo-pvsneslib/data.asm logo/snes-logo-pvsneslib/hdr.asm + AUDIO_FILES logo/snes-logo-pvsneslib/res/logo.it + SOUNDBANK logo/snes-logo-pvsneslib/soundbank + ROM_NAME LogoPVSnesLib +) + +#============================================================================= +# MAPS EXAMPLES - Advanced mapping and scrolling +#============================================================================= + +add_snes_game(DynamicMap + SOURCES maps/DynamicMap/DynamicMap.c maps/DynamicMap/map32x32.c maps/DynamicMap/map64x64.c maps/DynamicMap/maputil.c + GRAPHICS maps/DynamicMap/pvsneslibfont.bmp maps/DynamicMap/sprite16.bmp maps/DynamicMap/sprite16_64x64.bmp + ASM_SOURCES maps/DynamicMap/data.asm maps/DynamicMap/hdr.asm maps/DynamicMap/c64_sprite.asm maps/DynamicMap/ram.asm + ROM_NAME DynamicMap +) + +# Special handling for mapbuffer - preprocess ASM file to fix include path +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mapbufferextensionA_fixed.asm + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/maps/mapbuffer/src/addons/mapbufferextensionA.asm ${CMAKE_CURRENT_BINARY_DIR}/mapbufferextensionA_fixed.asm + COMMAND ${CMAKE_COMMAND} -DINPUT_FILE="${CMAKE_CURRENT_BINARY_DIR}/mapbufferextensionA_fixed.asm" -DOUTPUT_FILE="${CMAKE_CURRENT_BINARY_DIR}/mapbufferextensionA_fixed.asm" -DPATTERN="\.include \"hdr\.asm\"" -DREPLACEMENT=".include \"../../hdr.asm\"" -P "${CMAKE_SOURCE_DIR}/cmake/replace_text.cmake" + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/maps/mapbuffer/src/addons/mapbufferextensionA.asm + COMMENT "Preprocessing mapbuffer ASM file to fix include path" +) + +add_snes_game(mapbuffer + SOURCES maps/mapbuffer/mapbuffer.c maps/mapbuffer/src/gameobjects/mario.c maps/mapbuffer/src/addons/mapbufferextension.c + GRAPHICS maps/mapbuffer/gfx/mario.png maps/mapbuffer/gfx/tilesMario.png + ASM_SOURCES maps/mapbuffer/data.asm maps/mapbuffer/hdr.asm ${CMAKE_CURRENT_BINARY_DIR}/mapbufferextensionA_fixed.asm + INCLUDE_DIRS maps/mapbuffer/src/gameobjects maps/mapbuffer/src/addons maps/mapbuffer/maps + ROM_NAME mapbuffer +) + +add_snes_game(mapscroll + SOURCES maps/mapscroll/mapscroll.c + GRAPHICS maps/mapscroll/mario.png maps/mapscroll/tilesMario.png maps/mapscroll/bg_L1.png + ASM_SOURCES maps/mapscroll/data.asm maps/mapscroll/hdr.asm + ROM_NAME mapscroll +) + +add_snes_game(slopemario + SOURCES maps/slopemario/slopemario.c + GRAPHICS maps/slopemario/mario_sprite.bmp maps/slopemario/mariofont.bmp maps/slopemario/tiles.png + ASM_SOURCES maps/slopemario/data.asm maps/slopemario/hdr.asm + AUDIO_FILES maps/slopemario/overworld.it + SOUNDBANK maps/slopemario/soundbank + ROM_NAME slopemario +) + +add_snes_game(tiled + SOURCES maps/tiled/tiled.c + GRAPHICS maps/tiled/maplevel1.png maps/tiled/tileslevel1.png + ASM_SOURCES maps/tiled/data.asm maps/tiled/hdr.asm + ROM_NAME tiled +) + +#============================================================================= +# OBJECTS EXAMPLES - Object and sprite management +#============================================================================= + +add_snes_game(mapandobjects + SOURCES objects/mapandobjects/mapandobjects.c objects/mapandobjects/mario.c objects/mapandobjects/goomba.c objects/mapandobjects/koopatroopa.c + GRAPHICS objects/mapandobjects/mario.png objects/mapandobjects/goomba.png objects/mapandobjects/koopatroopa.png objects/mapandobjects/tileset.png objects/mapandobjects/tilesMario.png objects/mapandobjects/bg_L1.png + ASM_SOURCES objects/mapandobjects/data.asm objects/mapandobjects/hdr.asm + ROM_NAME mapandobjects +) + +add_snes_game(moveobjects + SOURCES objects/moveobjects/moveobjects.c + GRAPHICS objects/moveobjects/sprites.bmp + ASM_SOURCES objects/moveobjects/data.asm objects/moveobjects/hdr.asm + ROM_NAME moveobjects +) + +add_snes_game(nogravityobjects + SOURCES objects/nogravityobject/nogravityobjects.c objects/nogravityobject/link.c + GRAPHICS objects/nogravityobject/link.png objects/nogravityobject/mapzelda.png objects/nogravityobject/tiles.png + ASM_SOURCES objects/nogravityobject/data.asm objects/nogravityobject/hdr.asm + ROM_NAME nogravityobjects +) + +#============================================================================= +# SRAM EXAMPLES - Save game and persistent storage +#============================================================================= + +add_snes_game(sramoffset + SOURCES sram/sramoffset/sramoffset.c + GRAPHICS sram/sramoffset/pvsneslibfont.bmp + ASM_SOURCES sram/sramoffset/data.asm sram/sramoffset/hdr.asm + ROM_NAME sramoffset +) + +add_snes_game(sramsimple + SOURCES sram/sramsimple/sram.c + GRAPHICS sram/sramsimple/pvsneslibfont.bmp + ASM_SOURCES sram/sramsimple/data.asm sram/sramsimple/hdr.asm + ROM_NAME sramsimple +) + +# Combined target for basic working examples +add_custom_target(examples + DEPENDS + hello_world + breakpoints + debug + random + scoring + timer + testregion + typeconsole + COMMENT "Building basic working SNES examples" +) + +# Advanced examples target (includes examples that need special ROM configurations) +add_custom_target(advanced_examples + DEPENDS + memory_mapping + COMMENT "Building advanced SNES examples (HiROM/FastROM)" +) + +# Audio examples target +add_custom_target(audio_examples + DEPENDS + effects + effectsandmusic + music + music2 + musicGreaterThan32k + musicHiROM + tada + COMMENT "Building audio SNES examples" +) + +# Graphics examples target +add_custom_target(graphics_examples + DEPENDS + Mode0 Mode1 Mode1BG3HighPriority Mode1ContinuousScroll Mode1LZ77 + Mode1MixedScroll Mode1Png Mode1Scroll Mode3 Mode5 Mode7 Mode7Perspective + Fading GradientColors HDMAGradient MosaicShading ParallaxScrolling + Transparency TransparentWindow Waves Window + AnimatedSprite DynamicEngineMetaSprite DynamicEngineSprite DynamicSprite + ObjectSize SimpleSprite GetColors + COMMENT "Building graphics SNES examples" +) + +# Games examples target +add_custom_target(games_examples + DEPENDS + breakout + likemario + COMMENT "Building game SNES examples" +) + +# Input examples target +add_custom_target(input_examples + DEPENDS + controller + mouse + mouse-data-test + multiplay5 + superscope + COMMENT "Building input SNES examples" +) + +# Logo examples target +add_custom_target(logo_examples + DEPENDS + LogoCapcom + LogoKonami + LogoPVSnesLib + COMMENT "Building logo SNES examples" +) + +# Maps examples target +add_custom_target(maps_examples + DEPENDS + DynamicMap + mapbuffer + mapscroll + slopemario + tiled + COMMENT "Building maps SNES examples" +) + +# Objects examples target +add_custom_target(objects_examples + DEPENDS + mapandobjects + moveobjects + nogravityobjects + COMMENT "Building objects SNES examples" +) + +# SRAM examples target +add_custom_target(sram_examples + DEPENDS + sramoffset + sramsimple + COMMENT "Building SRAM SNES examples" +) + +# Comprehensive target for ALL examples (60+ examples!) +add_custom_target(all_examples + DEPENDS + examples + advanced_examples + audio_examples + graphics_examples + games_examples + input_examples + logo_examples + maps_examples + objects_examples + sram_examples + COMMENT "Building ALL SNES examples (60+ examples!)" +) + +message(STATUS "SNES examples configured. Use 'cmake --build . --target examples' to build all or 'cmake --build . --target hello_world' for individual games.") diff --git a/snes-examples/audio/effects/res/effectssfx.bak b/snes-examples/audio/effects/res/effectssfx.bak deleted file mode 100644 index b2326dabebb4bd7851b1798c31e5ca6dfcbd742e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66339 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 diff --git a/tools/816-opt/Makefile b/tools/816-opt/Makefile index fad634a3c..fd5973eb8 100644 --- a/tools/816-opt/Makefile +++ b/tools/816-opt/Makefile @@ -37,9 +37,13 @@ $(EXE)$(EXT): $(OBJS) @echo "Linking $<" $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $@ +# Create build directory +$(OBJ): + @mkdir -p $(OBJ) + # Define the recipe for compiling object files %.o : %.c -$(OBJ)/%.o: $(SRC)/%.c +$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) @echo "Compiling $<" $(CC) $(CFLAGS) -I$(SRC) -c $< -o $@ diff --git a/tools/816-opt/build/.keepme b/tools/816-opt/build/.keepme deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/816-opt/src/main.c b/tools/816-opt/src/main.c index 5bf945be7..108491d4b 100644 --- a/tools/816-opt/src/main.c +++ b/tools/816-opt/src/main.c @@ -59,9 +59,9 @@ int main(int argc, char **argv) /* -------------------------------- */ dynArray optAsm = optimizeAsm(file, bss, verbose); - for (size_t i = 0; i < optAsm.used; i++) + for (size_t j = 0; j < optAsm.used; j++) { - fprintf(stdout, "%s\n", optAsm.arr[i]); + fprintf(stdout, "%s\n", optAsm.arr[j]); } /* -------------------------------- */ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 000000000..88012f700 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,198 @@ +# PVSnesLib development tools + +# Set common variables +set(TOOLS_DIR "${CMAKE_SOURCE_DIR}/devkitsnes/tools") +set(VERSION "1.0.0") +string(TIMESTAMP DATESTRING "%Y%m%d") + +# Create tools directory +file(MAKE_DIRECTORY ${TOOLS_DIR}) + +# Modern CMake approach - use properties instead of raw flags +# Common compiler definitions for tools +set(TOOL_COMPILE_DEFINITIONS + __BUILD_DATE="${DATESTRING}" + __BUILD_VERSION="${VERSION}" +) + +# Common compiler features and standards +set(TOOL_C_STANDARD 99) +set(TOOL_CXX_STANDARD 11) + +# Platform-specific settings +if(WIN32) + set(EXE_EXT ".exe") + set(TOOL_STATIC_LINKING TRUE) +else() + set(EXE_EXT "") + if(NOT APPLE) + set(TOOL_STATIC_LINKING TRUE) + else() + set(TOOL_STATIC_LINKING FALSE) + endif() +endif() + +# Helper function to configure tool targets with modern CMake practices +function(configure_tool_target target_name) + # Set C/C++ standard based on target type + get_target_property(target_type ${target_name} TYPE) + if(target_type STREQUAL "EXECUTABLE") + # Determine if it's C or C++ based on source files + get_target_property(sources ${target_name} SOURCES) + set(is_cpp FALSE) + foreach(source ${sources}) + get_filename_component(ext ${source} EXT) + if(ext MATCHES "\\.(cpp|cxx|cc)$") + set(is_cpp TRUE) + break() + endif() + endforeach() + + if(is_cpp) + set_target_properties(${target_name} PROPERTIES + CXX_STANDARD ${TOOL_CXX_STANDARD} + CXX_STANDARD_REQUIRED ON + ) + else() + set_target_properties(${target_name} PROPERTIES + C_STANDARD ${TOOL_C_STANDARD} + C_STANDARD_REQUIRED ON + ) + endif() + endif() + + # Set compile options using target properties + target_compile_options(${target_name} PRIVATE + $<$:-g> + $<$:-O2> + -Wall + $<$:-Wno-implicit-function-declaration> + $<$:-Wno-unused-result> + ) + + # Set compile definitions + target_compile_definitions(${target_name} PRIVATE ${TOOL_COMPILE_DEFINITIONS}) + + # Set linking options + if(TOOL_STATIC_LINKING) + target_link_options(${target_name} PRIVATE -static) + endif() + + # Install target + install(TARGETS ${target_name} RUNTIME DESTINATION ${TOOLS_DIR}) +endfunction() + +# 816-opt: Assembly optimizer +add_executable(816-opt + 816-opt/src/main.c + 816-opt/src/optimizer.c + 816-opt/src/helpers.c +) + +configure_tool_target(816-opt) +target_link_libraries(816-opt PRIVATE pthread) + +if(WIN32) + target_compile_definitions(816-opt PRIVATE PCRE2_STATIC) + target_link_libraries(816-opt PRIVATE pcre2-posix pcre2-8 iconv) +endif() + +# bin2txt: Binary to text converter +add_executable(bin2txt + bin2txt/bin2txt.c +) + +configure_tool_target(bin2txt) + +# constify: Constant optimizer +add_executable(constify + constify/constify.cpp +) + +configure_tool_target(constify) + +# gfx2snes: Graphics converter (legacy) +add_executable(gfx2snes + gfx2snes/gfx2snes.c + gfx2snes/imgtools.c + gfx2snes/loadimg.c + gfx2snes/lodepng.c + gfx2snes/lz77.c +) + +configure_tool_target(gfx2snes) +target_include_directories(gfx2snes PRIVATE gfx2snes) + +# gfx4snes: Modern graphics converter +file(GLOB GFX4SNES_SOURCES gfx4snes/src/*.c) +add_executable(gfx4snes ${GFX4SNES_SOURCES}) + +configure_tool_target(gfx4snes) +target_include_directories(gfx4snes PRIVATE gfx4snes/src) + +# smconv: Music converter +add_executable(smconv + smconv/brr.cpp + smconv/conversion.cpp + smconv/convert.cpp + smconv/inputdata.cpp + smconv/io.cpp + smconv/it2spc.cpp + smconv/itloader.cpp +) + +configure_tool_target(smconv) +target_include_directories(smconv PRIVATE smconv) + +# snesbrr: BRR audio converter +add_executable(snesbrr + snesbrr/base/FileStream.cpp + snesbrr/base/MemoryStream.cpp + snesbrr/base/OptionParser.cpp + snesbrr/base/Stream.cpp + snesbrr/base/StreamException.cpp + snesbrr/brr/BrrCodec.cpp + snesbrr/brr/main.cpp +) + +configure_tool_target(snesbrr) +target_include_directories(snesbrr PRIVATE snesbrr snesbrr/base snesbrr/brr) + +# snestools: General SNES utilities +add_executable(snestools + snestools/snestools.c + snestools/errors.c +) + +configure_tool_target(snestools) + +# tmx2snes: Tiled map converter +add_executable(tmx2snes + tmx2snes/tmx2snes.c +) + +configure_tool_target(tmx2snes) +target_include_directories(tmx2snes PRIVATE tmx2snes) + +# Create combined target for all tools +add_custom_target(pvsneslib_tools + DEPENDS 816-opt bin2txt constify gfx2snes gfx4snes smconv snesbrr snestools tmx2snes + COMMENT "Building PVSnesLib development tools" +) + +# Create clean alias +add_custom_target(tools + DEPENDS pvsneslib_tools + COMMENT "๐Ÿ› ๏ธ SNES development tools" +) + +# Ensure compiler tools are built first (if they exist) +if(TARGET pvsneslib_compiler) + add_dependencies(pvsneslib_tools pvsneslib_compiler) +endif() + +# Copy tools to devkitsnes/tools (matching existing behavior) +add_custom_command(TARGET pvsneslib_tools POST_BUILD + COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} --prefix ${CMAKE_SOURCE_DIR} + COMMENT "Installing tools to devkitsnes/tools" +)