diff --git a/.github/workflows/build-cia.yml b/.github/workflows/build-cia.yml new file mode 100644 index 000000000..45141b04f --- /dev/null +++ b/.github/workflows/build-cia.yml @@ -0,0 +1,88 @@ +name: Build CIA + +on: + workflow_dispatch: {} + +jobs: + build-cia: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install devkitPro pacman (deb installer) + run: | + set -eux + # Use curl with retries and show output for debugging + # Try to download installer; if it fails (403) proceed to fallbacks + if ! curl -sSL -o install-devkitpro-pacman https://apt.devkitpro.org/install-devkitpro-pacman; then + echo "installer download failed; will try fallbacks" + else + chmod +x install-devkitpro-pacman + sudo bash ./install-devkitpro-pacman || true + fi + # Verify dkp-pacman exists + if ! command -v dkp-pacman >/dev/null 2>&1; then + echo "dkp-pacman not available after installer; proceeding to fallbacks" + ls -la || true + cat install-devkitpro-pacman || true + fi + + - name: Update pacman & install 3DS tools + run: | + # dkp-pacman should be available after the installer; try to update and install makerom/bannertool + if command -v dkp-pacman >/dev/null 2>&1; then + sudo dkp-pacman -Syu --noconfirm || true + sudo dkp-pacman -S --noconfirm makerom bannertool 3ds-dev || true + else + echo "dkp-pacman not available; using prebuilt tools from repo and manual setup" + mkdir -p tools-local/bin || true + export PATH="$PWD/tools-local/bin:$PATH" + # Use prebuilt tools already in repo + if [ -f tools-local/bin/makerom ] && [ -f tools-local/bin/bannertool ]; then + echo "Found prebuilt tools in repo" + chmod +x tools-local/bin/makerom tools-local/bin/bannertool + else + echo "Prebuilt tools not found in repo; attempting to download" + set +e + curl -sSL -o tools-local/bin/makerom "https://github.com/3DSGuy/Project_CTR/releases/download/makerom-v0.19.0/makerom" || true + if [ ! -f tools-local/bin/makerom ]; then + git clone https://github.com/carstene1ns/3ds-bannertool.git /tmp/bannertool-src + mkdir -p /tmp/bannertool-src/build + cd /tmp/bannertool-src/build + cmake .. || true + cmake --build . || true + if [ -f bannertool ]; then cp bannertool "$GITHUB_WORKSPACE/tools-local/bin/bannertool" || true; fi + cd "$GITHUB_WORKSPACE" + fi + set -e + fi + if [ -f tools-local/bin/makerom ]; then chmod +x tools-local/bin/makerom; fi + if [ -f tools-local/bin/bannertool ]; then chmod +x tools-local/bin/bannertool; fi + if ! command -v makerom >/dev/null 2>&1 || ! command -v bannertool >/dev/null 2>&1; then + echo "Tools still missing; aborting"; exit 1 + fi + fi + + - name: Setup build environment + run: | + # Install build-essential and make for local builds + sudo apt-get update || true + sudo apt-get install -y build-essential cmake gcc-arm-none-eabi || true + + - name: Configure and build CIA + run: | + export PATH="${GITHUB_WORKSPACE}/tools-local/bin:$PATH" + # Don't set DEVKITPRO — let cmake use local toolchain instead + mkdir -p build-3ds + cd build-3ds + cmake -DPLATFORM=3ds .. + cmake --build . --target butterscotch_cia -j$(nproc) + + - name: Upload CIA artifact + if: success() + uses: actions/upload-artifact@v4 + with: + name: butterscotch-cia + path: build-3ds/butterscotch.cia diff --git a/3ds/cmake/3DS.cmake b/3ds/cmake/3DS.cmake new file mode 100644 index 000000000..04b958255 --- /dev/null +++ b/3ds/cmake/3DS.cmake @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.13) + +if(NOT CMAKE_SYSTEM_NAME) + set(CMAKE_SYSTEM_NAME Nintendo3DS) +endif() + +if(NOT CMAKE_SYSTEM_PROCESSOR) + set(CMAKE_SYSTEM_PROCESSOR armv6k) +endif() + +# Import devkitARM toolchain +include(${CMAKE_CURRENT_LIST_DIR}/devkitARM.cmake) + +set(CTR_ROOT ${DEVKITPRO}/libctru) +set(DKP_INSTALL_PREFIX_INIT ${DEVKITPRO}/portlibs/3ds) + +__dkp_platform_prefix( + ${DEVKITPRO}/portlibs/3ds + ${CTR_ROOT} +) + +find_program(PKG_CONFIG_EXECUTABLE NAMES arm-none-eabi-pkg-config HINTS "${DEVKITPRO}/portlibs/3ds/bin") +if (NOT PKG_CONFIG_EXECUTABLE) + message(FATAL_ERROR "Could not find arm-none-eabi-pkg-config: try installing 3ds-pkg-config") +endif() + +find_program(CTR_SMDHTOOL_EXE NAMES smdhtool HINTS "${DEVKITPRO}/tools/bin") +find_program(CTR_3DSXTOOL_EXE NAMES 3dsxtool HINTS "${DEVKITPRO}/tools/bin") +find_program(CTR_PICASSO_EXE NAMES picasso HINTS "${DEVKITPRO}/tools/bin") +find_program(CTR_TEX3DS_EXE NAMES tex3ds HINTS "${DEVKITPRO}/tools/bin") + +find_file(CTR_DEFAULT_ICON NAMES default_icon.png HINTS "${CTR_ROOT}" NO_CMAKE_FIND_ROOT_PATH) diff --git a/3ds/cmake/Catnip/Information.cmake b/3ds/cmake/Catnip/Information.cmake new file mode 100644 index 000000000..81c4f4251 --- /dev/null +++ b/3ds/cmake/Catnip/Information.cmake @@ -0,0 +1,275 @@ + +set(CATNIP_RESERVED_IDENTIFIERS all build install clean) + +function(__catnip_check_identifier id) + string(REGEX REPLACE "[a-zA-Z0-9_]" "" invalidchars "${id}") + if(NOT "${invalidchars}" STREQUAL "") + message(FATAL_ERROR "Identifier contains an invalid character: ${id}") + endif() + + string(FIND "${id}" "__" badseq) + if(badseq GREATER_EQUAL 0) + message(FATAL_ERROR "Identifier contains a reserved sequence: ${id}") + endif() + + if("${id}" IN_LIST CATNIP_RESERVED_IDENTIFIERS) + message(FATAL_ERROR "Reserved identifier: ${id}") + endif() +endfunction() + +function(__catnip_check_identifier2 id) + string(REGEX REPLACE "[a-zA-Z0-9_-]" "" invalidchars "${id}") + if(NOT "${invalidchars}" STREQUAL "") + message(FATAL_ERROR "Identifier2 contains an invalid character: ${id}") + endif() +endfunction() + +function(__catnip_build_cache outvar) + set(_entlist "") + foreach(entry ${ARGN}) + if(NOT entry MATCHES "^([a-zA-Z0-9_]+)(:BOOL|:FILEPATH|:PATH|:STRING|:INTERNAL|:UNSET)?=") + message(FATAL_ERROR "Invalid cache entry: ${entry}") + endif() + + if(CMAKE_MATCH_2 STREQUAL ":UNSET") + set(_ent_${CMAKE_MATCH_1} "") + else() + set(_ent_${CMAKE_MATCH_1} "${entry}") + list(APPEND _entlist "${CMAKE_MATCH_1}") + endif() + endforeach() + + list(SORT _entlist) + set(_cache "") + foreach(var IN LISTS _entlist) + set(entry "${_ent_${var}}") + if(NOT entry STREQUAL "") + set(_ent_${var} "") + list(APPEND _cache "${entry}") + endif() + endforeach() + + set(${outvar} "${_cache}" PARENT_SCOPE) +endfunction() + +macro(__catnip_scope var) + get_property(${var} GLOBAL PROPERTY CATNIP_SCOPE) + if(NOT ${var}) + message(FATAL_ERROR "catnip: missing catnip_package() call") + endif() +endmacro() + +function(__catnip_find_root) + set(dir "") + set(nextdir "${CMAKE_SOURCE_DIR}") + while(NOT "${dir}" STREQUAL "${nextdir}") + set(dir "${nextdir}") + + if(EXISTS "${dir}/.catnip_root") + set(CATNIP_ROOT "${dir}" PARENT_SCOPE) + return() + endif() + + get_filename_component(nextdir "${dir}" DIRECTORY) + endwhile(NOT "${dir}" STREQUAL "${nextdir}") + + set(CATNIP_ROOT "${CMAKE_SOURCE_DIR}" PARENT_SCOPE) +endfunction() + +function(__catnip_find_toolset var toolset severity) + foreach(dir IN LISTS CMAKE_MODULE_PATH) + file(GLOB files "${dir}/*.cmake" LIST_DIRECTORIES false) + string(TOLOWER "${dir}/${toolset}.cmake" match1) + foreach(file IN LISTS files) + string(TOLOWER "${file}" match2) + if(match1 STREQUAL match2) + set(${var} "${file}" PARENT_SCOPE) + return() + endif() + endforeach() + endforeach() + + set(${var} "" PARENT_SCOPE) + message("${severity}" "Could not find a suitable '${toolset}' toolset") +endfunction() + +function(__catnip_add_subdirectory subdir) + get_filename_component(subdir "${subdir}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") + + include(${subdir}/Catnip.cmake OPTIONAL RESULT_VARIABLE ret) + if(ret) + return() + endif() + + if(EXISTS ${subdir}/CMakeLists.txt) + file(READ ${subdir}/CMakeLists.txt listtext) + if(listtext MATCHES "catnip_package") + include(${subdir}/CMakeLists.txt) + elseif("${subdir}" STREQUAL "${CATNIP_ROOT}") + # Handle pure CMake projects + catnip_package(main DIR "${subdir}" DEFAULT release) + catnip_add_preset(release BUILD_TYPE Release) + catnip_add_preset(debug BUILD_TYPE Debug) + set_property(GLOBAL PROPERTY CATNIP_CWD_PACKAGE main) + else() + message(FATAL_ERROR "${subdir} is a pure CMake project, please use catnip_package() instead") + endif() + return() + endif() + + message(FATAL_ERROR "Missing Catnip.cmake or CMakeLists.txt in ${subdir}") +endfunction() + +function(catnip_add_subdirectory subdir) + get_property(backupscope GLOBAL PROPERTY CATNIP_SCOPE) + set_property(GLOBAL PROPERTY CATNIP_SCOPE "") + __catnip_add_subdirectory("${subdir}") + set_property(GLOBAL PROPERTY CATNIP_SCOPE ${backupscope}) +endfunction() + +function(catnip_package pkgname) + __catnip_check_identifier("${pkgname}") + cmake_parse_arguments(PARSE_ARGV 1 ARG + "" + "DIR" + "DEFAULT" + ) + + if(NOT ARG_DIR) + set(ARG_DIR "${CMAKE_CURRENT_LIST_DIR}") + else() + get_filename_component(ARG_DIR "${ARG_DIR}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") + endif() + + if(NOT EXISTS "${ARG_DIR}/CMakeLists.txt") + message(FATAL_ERROR "Missing CMakeLists.txt in ${pkgname}") + endif() + + set(scope CATNIP_${pkgname}) + set_property(GLOBAL PROPERTY CATNIP_SCOPE ${scope}) + set_property(GLOBAL APPEND PROPERTY CATNIP_PACKAGES ${pkgname}) + set_property(GLOBAL PROPERTY ${scope}_SOURCE "${ARG_DIR}") + set_property(GLOBAL PROPERTY ${scope}_DEFAULT "${ARG_DEFAULT}") + + if("${ARG_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + set_property(GLOBAL PROPERTY CATNIP_CWD_PACKAGE ${pkgname}) + endif() +endfunction() + +function(catnip_add_preset presetname) + __catnip_scope(scope) + __catnip_check_identifier("${presetname}") + cmake_parse_arguments(PARSE_ARGV 1 ARG + "DEFAULT" + "TOOLSET;BUILD_TYPE;SYSTEM;PROCESSOR" + "CACHE;DEPENDS" + ) + + if(ARG_TOOLSET) + __catnip_check_identifier2("${ARG_TOOLSET}") + __catnip_find_toolset(ARG_TOOLSET "${ARG_TOOLSET}" WARNING) + if(NOT ARG_TOOLSET) + return() + endif() + list(APPEND ARG_CACHE "CMAKE_TOOLCHAIN_FILE=${ARG_TOOLSET}") + elseif(NOT CATNIP_HAS_DEFAULT_TOOLSET) + message(FATAL_ERROR "No default toolset specified: use -T to select one (e.g. -T 3DS)") + endif() + + if(ARG_BUILD_TYPE) + __catnip_check_identifier2("${ARG_BUILD_TYPE}") + list(APPEND ARG_CACHE "CMAKE_BUILD_TYPE=${ARG_BUILD_TYPE}") + endif() + + if(ARG_SYSTEM) + __catnip_check_identifier2("${ARG_SYSTEM}") + list(APPEND ARG_CACHE "CMAKE_SYSTEM_NAME=${ARG_SYSTEM}") + endif() + + if(ARG_PROCESSOR) + __catnip_check_identifier2("${ARG_PROCESSOR}") + list(APPEND ARG_CACHE "CMAKE_SYSTEM_PROCESSOR=${ARG_PROCESSOR}") + endif() + + if(NOT ARG_CACHE STREQUAL "") + __catnip_build_cache(ARG_CACHE ${CATNIP_CACHE} ${ARG_CACHE}) + else() + set(ARG_CACHE "${CATNIP_CACHE}") + endif() + + string(MD5 stamp "${ARG_CACHE}") + + set(cmakeargs "") + foreach(cacheentry IN LISTS ARG_CACHE) + list(APPEND cmakeargs "-D${cacheentry}") + endforeach() + + set_property(GLOBAL APPEND PROPERTY ${scope}_PRESETS ${presetname}) + if(ARG_DEFAULT) + set_property(GLOBAL APPEND PROPERTY ${scope}_DEFAULT ${presetname}) + endif() + + set(scope ${scope}__${presetname}) + set_property(GLOBAL PROPERTY ${scope}_STAMP "${stamp}") + set_property(GLOBAL PROPERTY ${scope}_CMAKE_ARGS "${cmakeargs}") + set_property(GLOBAL PROPERTY ${scope}_DEPENDS "${ARG_DEPENDS}") +endfunction() + +if(NOT DEFINED CATNIP_ROOT) + if(DEFINED ENV{CATNIP_ROOT}) + get_filename_component(CATNIP_ROOT "$ENV{CATNIP_ROOT}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") + else() + __catnip_find_root() + endif() +else() + get_filename_component(CATNIP_ROOT "${CATNIP_ROOT}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") +endif() + +if(NOT DEFINED CATNIP_BUILD_DIR) + if(DEFINED ENV{CATNIP_BUILD_DIR}) + get_filename_component(CATNIP_BUILD_DIR "$ENV{CATNIP_BUILD_DIR}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") + else() + set(CATNIP_BUILD_DIR ${CATNIP_ROOT}/build) + endif() +else() + get_filename_component(CATNIP_BUILD_DIR "${CATNIP_BUILD_DIR}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") +endif() + +if("${CATNIP_BUILD_DIR}" STREQUAL "${CATNIP_ROOT}") + message(FATAL_ERROR "Catnip build directory must be different than source root directory") +endif() + +if(DEFINED CATNIP_DEFAULT_TOOLSET) + __catnip_check_identifier2("${CATNIP_DEFAULT_TOOLSET}") + __catnip_find_toolset(CATNIP_DEFAULT_TOOLSET "${CATNIP_DEFAULT_TOOLSET}" FATAL_ERROR) + list(APPEND CATNIP_CACHE "CMAKE_TOOLCHAIN_FILE=${CATNIP_DEFAULT_TOOLSET}") +endif() + +if(EXISTS "${CATNIP_BUILD_DIR}/.catnip_cache") + file(READ "${CATNIP_BUILD_DIR}/.catnip_cache" CATNIP_OLD_CACHE) +endif() + +if(NOT "${CATNIP_CACHE}" STREQUAL "") + __catnip_build_cache(CATNIP_CACHE ${CATNIP_OLD_CACHE} ${CATNIP_CACHE}) + + message(STATUS "Updating Catnip cache") + file(MAKE_DIRECTORY "${CATNIP_BUILD_DIR}") + file(WRITE "${CATNIP_BUILD_DIR}/.catnip_cache" "${CATNIP_CACHE}") +else() + set(CATNIP_CACHE "${CATNIP_OLD_CACHE}") +endif() + +if(CATNIP_CACHE MATCHES "^CMAKE_TOOLCHAIN_FILE[:=]" OR CATNIP_CACHE MATCHES ";CMAKE_TOOLCHAIN_FILE[:=]") + set(CATNIP_HAS_DEFAULT_TOOLSET TRUE) +endif() + +if(CATNIP_VERBOSE) + message(STATUS "Catnip source root directory is ${CATNIP_ROOT}") + message(STATUS "Catnip build directory is ${CATNIP_BUILD_DIR}") + + if(NOT CATNIP_CACHE STREQUAL "") + message(STATUS "Catnip cache is ${CATNIP_CACHE}") + endif() +endif() + +catnip_add_subdirectory(${CATNIP_ROOT}) diff --git a/3ds/cmake/Catnip/Topology.cmake b/3ds/cmake/Catnip/Topology.cmake new file mode 100644 index 000000000..bd202b460 --- /dev/null +++ b/3ds/cmake/Catnip/Topology.cmake @@ -0,0 +1,164 @@ + +set(CATNIP_VERBS_WITH_DEPS config build install) + +function(__catnip_validate_deplist pkgname preset propname) + get_property(inlist GLOBAL PROPERTY CATNIP_${pkgname}__${preset}_${propname}) + set(outlist "") + + foreach(dep IN LISTS inlist) + string(FIND "${dep}" "." dotpos) + if(dotpos GREATER_EQUAL 0) + string(SUBSTRING "${dep}" 0 ${dotpos} deppkg) + math(EXPR dotpos "${dotpos}+1") + string(SUBSTRING "${dep}" ${dotpos} -1 deppreset) + else() + set(deppkg "${dep}") + set(deppreset "${preset}") + endif() + + if("${deppreset}" STREQUAL "all") + get_property(depall GLOBAL PROPERTY CATNIP_${deppkg}_PRESETS) + if("${depall}" STREQUAL "") + message(FATAL_ERROR "Preset ${pkgname}.${preset} has unsatisfied dependency: ${deppkg}.all") + endif() + + foreach(deppreset IN LISTS depall) + list(APPEND outlist "${deppkg}.${deppreset}") + endforeach() + else() + get_property(valid GLOBAL PROPERTY CATNIP_${deppkg}__${deppreset}_STAMP SET) + if(NOT valid) + message(FATAL_ERROR "Preset ${pkgname}.${preset} has unsatisfied dependency: ${deppkg}.${deppreset}") + endif() + + list(APPEND outlist "${deppkg}.${deppreset}") + endif() + endforeach() + + set_property(GLOBAL PROPERTY CATNIP_${pkgname}__${preset}_${propname} "${outlist}") +endfunction() + +function(__catnip_validate_packages) + get_property(pkglist GLOBAL PROPERTY CATNIP_PACKAGES) + if("${pkglist}" STREQUAL "") + message(FATAL_ERROR "No packages defined") + endif() + + foreach(pkgname IN LISTS pkglist) + set(scope CATNIP_${pkgname}) + get_property(presets GLOBAL PROPERTY ${scope}_PRESETS) + get_property(default GLOBAL PROPERTY ${scope}_DEFAULT) + + if("${presets}" STREQUAL "") + message(FATAL_ERROR "Package ${pkgname} has no presets defined") + endif() + + if("${default}" MATCHES "^[aA][lL][lL]$") + set(default "${presets}") + set_property(GLOBAL PROPERTY ${scope}_DEFAULT ${default}) + elseif("${default}" STREQUAL "") + list(GET presets 0 default) + set_property(GLOBAL PROPERTY ${scope}_DEFAULT ${default}) + else() + foreach(preset IN LISTS default) + __catnip_check_identifier(preset) + get_property(valid GLOBAL PROPERTY ${scope}__${preset}_STAMP SET) + if(NOT valid) + message(FATAL_ERROR "Package ${pkgname} has invalid default preset: ${preset}") + endif() + endforeach() + endif() + + foreach(preset IN LISTS presets) + __catnip_validate_deplist(${pkgname} ${preset} DEPENDS) + endforeach() + endforeach() +endfunction() + +function(__catnip_visit selector verb) + string(REPLACE "." "__" scope "${selector}") + set(scope CATNIP_${scope}) + + get_property(valid GLOBAL PROPERTY ${scope}_STAMP SET) + if(NOT valid) + message(FATAL_ERROR "Invalid preset: ${selector}") + endif() + + # Store verb now in order to allow overriding it even if + # this selector has already been visited before + set_property(GLOBAL PROPERTY ${scope}_VERB "${verb}") + + get_property(state GLOBAL PROPERTY ${scope}_VISIT) + if("${state}" STREQUAL "2") + return() + elseif("${state}" STREQUAL "1") + message(FATAL_ERROR "Dependency cycle detected: ${selector}") + endif() + + set_property(GLOBAL PROPERTY ${scope}_VISIT 1) + + if("${verb}" IN_LIST CATNIP_VERBS_WITH_DEPS) + if("${verb}" STREQUAL "install") + # install decays to build for dependencies + set(depverb "build") + else() + set(depverb "${verb}") + endif() + + get_property(deps GLOBAL PROPERTY ${scope}_DEPENDS) + foreach(dep IN LISTS deps) + __catnip_visit("${dep}" "${depverb}") + endforeach() + endif() + + set_property(GLOBAL PROPERTY ${scope}_VISIT 2) + set_property(GLOBAL APPEND PROPERTY CATNIP_SELECTORS "${selector}") +endfunction() + +function(__catnip_extract_verb) + if(NOT "${CATNIP_VERB}" STREQUAL "") + return() + endif() + + if(NOT "${CATNIP_ARGV}" STREQUAL "") + list(GET CATNIP_ARGV 0 verb) + else() + set(verb "") + endif() + + if("${verb}" IN_LIST CATNIP_VERBS OR "${verb}" MATCHES "^dump-.+") + list(REMOVE_AT CATNIP_ARGV 0) + else() + set(verb "build") + endif() + + set(CATNIP_VERB "${verb}" PARENT_SCOPE) + set(CATNIP_ARGV "${CATNIP_ARGV}" PARENT_SCOPE) +endfunction() + +function(__catnip_planner) + if(NOT CATNIP_PACKAGE_SELECTORS) + get_property(cwdpkg GLOBAL PROPERTY CATNIP_CWD_PACKAGE) + if("${cwdpkg}" STREQUAL "") + message(FATAL_ERROR "The current directory does not contain a Catnip package.") + endif() + + if("${CATNIP_ARGV}" STREQUAL "") + get_property(CATNIP_ARGV GLOBAL PROPERTY CATNIP_${cwdpkg}_DEFAULT) + elseif("all" IN_LIST CATNIP_ARGV) + get_property(CATNIP_ARGV GLOBAL PROPERTY CATNIP_${cwdpkg}_PRESETS) + endif() + + foreach(selector IN LISTS CATNIP_ARGV) + __catnip_visit("${cwdpkg}.${selector}" "${CATNIP_VERB}") + endforeach() + else() + message(FATAL_ERROR "Package selectors not implemented") + endif() + + get_property(out GLOBAL PROPERTY CATNIP_SELECTORS) + set(CATNIP_SELECTORS "${out}" PARENT_SCOPE) +endfunction() + +__catnip_validate_packages() +__catnip_extract_verb() diff --git a/3ds/cmake/Catnip/Utility.cmake b/3ds/cmake/Catnip/Utility.cmake new file mode 100644 index 000000000..94ac41210 --- /dev/null +++ b/3ds/cmake/Catnip/Utility.cmake @@ -0,0 +1,72 @@ + +if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "MSYS") + set(CATNIP_IS_MSYS TRUE) + + function(catnip_xlate_path outvar inpath) + if(NOT IS_ABSOLUTE "${inpath}") + # Pass through absolute paths + set(out "${inpath}") + elseif(IS_DIRECTORY "${inpath}") + # Real directories can be translated directly + string(MD5 hash "${inpath}") + get_property(out GLOBAL PROPERTY CATNIP_XLATE_${hash}) + if(NOT out) + execute_process( + COMMAND cygpath -ma "${inpath}" + OUTPUT_VARIABLE out + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + COMMAND_ERROR_IS_FATAL ANY + ) + set_property(GLOBAL PROPERTY CATNIP_XLATE_${hash} "${out}") + endif() + else() + # Other paths can be decomposed into parent dir + child + get_filename_component(indir "${inpath}" DIRECTORY) + get_filename_component(infil "${inpath}" NAME) + catnip_xlate_path(indir "${indir}") + set(out "${indir}/${infil}") + endif() + + set(${outvar} "${out}" PARENT_SCOPE) + endfunction() + + function(catnip_xlate_args outvar inlist) + set(out "") + foreach(arg IN LISTS inlist) + if("${arg}" MATCHES "^(-I|-iquote|-isystem)?(/.+)$") + catnip_xlate_path(xlatedarg "${CMAKE_MATCH_2}") + set(arg "${CMAKE_MATCH_1}${xlatedarg}") + endif() + list(APPEND out "${arg}") + endforeach() + + set(${outvar} "${out}" PARENT_SCOPE) + endfunction() +else() + macro(catnip_xlate_path outvar inpath) + set(${outvar} "${inpath}") + endmacro() + + macro(catnip_xlate_args outvar inlist) + set(${outvar} "${inlist}") + endmacro() +endif() + +function(catnip_str_to_json outvar str) + string(REPLACE "\\" "\\\\" str "${str}") + string(REPLACE "\"" "\\\"" str "${str}") + set(${outvar} "\"${str}\"" PARENT_SCOPE) +endfunction() + +function(catnip_list_to_json outvar inlist) + set(out "[]") + set(i "0") + foreach(item IN LISTS inlist) + catnip_str_to_json(item "${item}") + string(JSON out SET "${out}" "${i}" "${item}") + math(EXPR i "${i}+1") + endforeach() + + set(${outvar} "${out}" PARENT_SCOPE) +endfunction() diff --git a/3ds/cmake/Catnip/Verb-Build.cmake b/3ds/cmake/Catnip/Verb-Build.cmake new file mode 100644 index 000000000..8a8c09442 --- /dev/null +++ b/3ds/cmake/Catnip/Verb-Build.cmake @@ -0,0 +1,201 @@ + +function(__catnip_generator var) + get_property(generator GLOBAL PROPERTY CATNIP_GENERATOR) + if(NOT generator) + find_program(PROG_NINJA ninja) + if(PROG_NINJA) + set(generator "Ninja") + else() + set(generator "Unix Makefiles") + endif() + set_property(GLOBAL PROPERTY CATNIP_GENERATOR "${generator}") + endif() + + set(${var} "${generator}" PARENT_SCOPE) +endfunction() + +function(__catnip_fix_compiler_commands_json filename) + # This fixing only applies to msys + if (NOT CATNIP_IS_MSYS) + return() + endif() + + message(STATUS "Translating ${filename}") + file(READ "${filename}" injson) + + string(JSON nument LENGTH "${injson}") + if (nument EQUAL 0) + return() + endif() + + set(outjson "[]") + math(EXPR loopmax "${nument}-1") + foreach(i RANGE "${loopmax}") + string(JSON curent GET "${injson}" "${i}") + string(JSON curdir GET "${curent}" "directory") + string(JSON curcmd GET "${curent}" "command") + string(JSON curfil GET "${curent}" "file") + string(JSON curout GET "${curent}" "output") + + separate_arguments(curcmd UNIX_COMMAND "${curcmd}") + + catnip_xlate_path(curdir "${curdir}") + catnip_xlate_args(curcmd "${curcmd}") + catnip_xlate_path(curfil "${curfil}") + catnip_xlate_path(curout "${curout}") + + # XX: is there any way to do this properly? as in the inverse of separate_arguments + list(JOIN curcmd " " curcmd) + + catnip_str_to_json(curdir "${curdir}") + catnip_str_to_json(curcmd "${curcmd}") + catnip_str_to_json(curfil "${curfil}") + catnip_str_to_json(curout "${curout}") + + set(curent "{}") + string(JSON curent SET "${curent}" "directory" "${curdir}") + string(JSON curent SET "${curent}" "command" "${curcmd}") + string(JSON curent SET "${curent}" "file" "${curfil}") + string(JSON curent SET "${curent}" "output" "${curout}") + string(JSON outjson SET "${outjson}" "${i}" "${curent}") + endforeach() + + file(WRITE "${filename}" "${outjson}") +endfunction() + +function(__catnip_gen_clangd pkgname preset) + get_property(srcdir GLOBAL PROPERTY CATNIP_${pkgname}_SOURCE) + set(builddir "${CATNIP_BUILD_DIR}/${pkgname}.${preset}") + file(RELATIVE_PATH builddir_srcrel "${srcdir}" "${builddir}") + + set(clangd_cf "{}") + catnip_str_to_json(cf_dbdir "${builddir_srcrel}") + string(JSON clangd_cf SET "${clangd_cf}" "CompilationDatabase" "${cf_dbdir}") + catnip_list_to_json(cf_remove "${CATNIP_CLANGD_FLAGS_REMOVE}") + string(JSON clangd_cf SET "${clangd_cf}" "Remove" "${cf_remove}") + + set(clangd_config "{}") + string(JSON clangd_config SET "${clangd_config}" "CompileFlags" "${clangd_cf}") + + file(WRITE "${srcdir}/.clangd" "${clangd_config}") +endfunction() + +function(__catnip_build selector) + string(FIND "${selector}" "." dotpos) + string(SUBSTRING "${selector}" 0 ${dotpos} pkgname) + math(EXPR dotpos "${dotpos}+1") + string(SUBSTRING "${selector}" ${dotpos} -1 preset) + set(scope CATNIP_${pkgname}__${preset}) + + get_property(verb GLOBAL PROPERTY ${scope}_VERB) + get_property(srcdir GLOBAL PROPERTY CATNIP_${pkgname}_SOURCE) + get_property(stamp GLOBAL PROPERTY ${scope}_STAMP) + set(builddir "${CATNIP_BUILD_DIR}/${selector}") + set(stampfile "${builddir}/.catnip_stamp") + set(jsonfile "${builddir}/compile_commands.json") + + if("${verb}" STREQUAL "config" AND CATNIP_FORCE_FLAG) + file(REMOVE_RECURSE "${builddir}") + endif() + + if(EXISTS "${stampfile}") + file(READ "${stampfile}" existingstamp) + if(NOT "${existingstamp}" STREQUAL "${stamp}") + message(STATUS "Config for ${selector} changed, deleting stale build dir") + file(REMOVE_RECURSE "${builddir}") + endif() + endif() + + file(MAKE_DIRECTORY "${builddir}") + message(STATUS "Entering ${builddir}") + + file(TIMESTAMP "${jsonfile}" old_jsontime) + + if(NOT EXISTS "${stampfile}") + get_property(cmakeargs GLOBAL PROPERTY ${scope}_CMAKE_ARGS) + __catnip_generator(generator) + + if(NOT CATNIP_VERBOSE) + set(silence OUTPUT_QUIET) + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} -E env + "DKP_BUILD_TOOL_HOOK=dkp-catnip-utils" + "CATNIP_BUILD_DIR=${CATNIP_BUILD_DIR}" + "CATNIP_PRESET=${preset}" + ${CMAKE_COMMAND} ${cmakeargs} + -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE + -G "${generator}" + -S "${srcdir}" + -B "${builddir}" + RESULT_VARIABLE error + ${silence} + ) + + if(error) + file(REMOVE_RECURSE "${builddir}") + message(FATAL_ERROR "Failed to configure ${selector}") + endif() + + file(WRITE "${stampfile}" "${stamp}") + endif() + + if (NOT "${verb}" STREQUAL "config") + set(buildargs --build "${builddir}") + + if(NOT "${verb}" STREQUAL "build") + list(APPEND buildargs --target "${verb}") + endif() + + if(CATNIP_FORCE_FLAG) + list(APPEND buildargs --clean-first) + endif() + + if(CATNIP_VERBOSE AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.14) + list(APPEND buildargs --verbose) + endif() + + if(CATNIP_PARALLEL_JOBS) + list(APPEND buildargs -j${CATNIP_PARALLEL_JOBS}) + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} ${buildargs} + RESULT_VARIABLE error + ) + endif() + + file(TIMESTAMP "${jsonfile}" new_jsontime) + if(NOT "${old_jsontime}" STREQUAL "${new_jsontime}") + __catnip_fix_compiler_commands_json("${jsonfile}") + endif() + + if(CATNIP_GEN_CLANGD) + get_property(already_done GLOBAL PROPERTY CATNIP_${pkgname}_GEN_CLANGD SET) + if (NOT already_done) + message(STATUS "Generating .clangd for ${selector}") + set_property(GLOBAL PROPERTY CATNIP_${pkgname}_GEN_CLANGD TRUE) + __catnip_gen_clangd("${pkgname}" "${preset}") + endif() + endif() + + if(error) + message(FATAL_ERROR "Failed to build ${selector}") + endif() +endfunction() + +if(NOT DEFINED CATNIP_GEN_CLANGD AND DEFINED ENV{CATNIP_GEN_CLANGD}) + set(CATNIP_GEN_CLANGD "$ENV{CATNIP_GEN_CLANGD}") +endif() + +if("${CATNIP_VERB}" STREQUAL "install" AND DEFINED ENV{DESTDIR}) + # Fixup DESTDIR so that it matches the correct CWD + get_filename_component(DESTDIR "$ENV{DESTDIR}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") + set(ENV{DESTDIR} "${DESTDIR}") +endif() + +__catnip_planner() +foreach(sel IN LISTS CATNIP_SELECTORS) + __catnip_build("${sel}") +endforeach() diff --git a/3ds/cmake/Catnip/Verb-Clean.cmake b/3ds/cmake/Catnip/Verb-Clean.cmake new file mode 100644 index 000000000..7cc92eadf --- /dev/null +++ b/3ds/cmake/Catnip/Verb-Clean.cmake @@ -0,0 +1,34 @@ + +function(__catnip_clean selector) + string(FIND "${selector}" "." dotpos) + string(SUBSTRING "${selector}" 0 ${dotpos} pkgname) + math(EXPR dotpos "${dotpos}+1") + string(SUBSTRING "${selector}" ${dotpos} -1 preset) + set(scope CATNIP_${pkgname}__${preset}) + + get_property(verb GLOBAL PROPERTY ${scope}_VERB) + get_property(srcdir GLOBAL PROPERTY CATNIP_${pkgname}_SOURCE) + set(builddir "${CATNIP_BUILD_DIR}/${selector}") + set(stampfile "${builddir}/.catnip_stamp") + + if(NOT IS_DIRECTORY "${builddir}") + message(STATUS "${selector}: already clean") + return() + endif() + + if(CATNIP_FORCE_FLAG) + file(REMOVE "${stampfile}") + endif() + + message(STATUS "Cleaning ${selector}...") + if(EXISTS "${stampfile}") + execute_process(COMMAND ${CMAKE_COMMAND} --build "${builddir}" --target clean) + else() + file(REMOVE_RECURSE "${builddir}") + endif() +endfunction() + +__catnip_planner() +foreach(sel IN LISTS CATNIP_SELECTORS) + __catnip_clean("${sel}") +endforeach() diff --git a/3ds/cmake/Catnip/Verb-Dump.cmake b/3ds/cmake/Catnip/Verb-Dump.cmake new file mode 100644 index 000000000..27f462c60 --- /dev/null +++ b/3ds/cmake/Catnip/Verb-Dump.cmake @@ -0,0 +1,54 @@ +if(NOT "${CATNIP_VERB}" STREQUAL "dump-info") + message(FATAL_ERROR "--${CATNIP_VERB} not implemented") +endif() + +function(__catnip_dump_package outvar pkgname) + set(scope CATNIP_${pkgname}) + get_property(srcdir GLOBAL PROPERTY ${scope}_SOURCE) + get_property(prlist GLOBAL PROPERTY ${scope}_PRESETS) + get_property(default GLOBAL PROPERTY ${scope}_DEFAULT) + + set(out "{}") + catnip_str_to_json(p_name "${pkgname}") + string(JSON out SET "${out}" "name" "${p_name}") + catnip_xlate_path(p_srcdir "${srcdir}") + catnip_str_to_json(p_srcdir "${p_srcdir}") + string(JSON out SET "${out}" "source" "${p_srcdir}") + catnip_list_to_json(p_def "${default}") + string(JSON out SET "${out}" "default" "${p_def}") + + set(j "0") + set(p_presets "[]") + foreach(preset IN LISTS prlist) + set(scope CATNIP_${pkgname}__${preset}) + get_property(args GLOBAL PROPERTY ${scope}_CMAKE_ARGS) + get_property(deps GLOBAL PROPERTY ${scope}_DEPENDS) + + set(pr_dump "{}") + catnip_str_to_json(pr_name "${preset}") + string(JSON pr_dump SET "${pr_dump}" "name" "${pr_name}") + catnip_list_to_json(pr_args "${args}") + string(JSON pr_dump SET "${pr_dump}" "cmake_args" "${pr_args}") + catnip_list_to_json(pr_deps "${deps}") + string(JSON pr_dump SET "${pr_dump}" "depends" "${pr_deps}") + + string(JSON p_presets SET "${p_presets}" "${j}" "${pr_dump}") + math(EXPR j "${j}+1") + endforeach() + + string(JSON out SET "${out}" "presets" "${p_presets}") + + set(${outvar} "${out}" PARENT_SCOPE) +endfunction() + +set(outjson "[]") +set(i "0") +get_property(pkglist GLOBAL PROPERTY CATNIP_PACKAGES) +foreach(pkgname IN LISTS pkglist) + __catnip_dump_package(pkgdump "${pkgname}") + + string(JSON outjson SET "${outjson}" "${i}" "${pkgdump}") + math(EXPR i "${i}+1") +endforeach() + +message(STATUS "catnip dump begin --\n${outjson}") diff --git a/3ds/cmake/Platform/Generic-dkP.cmake b/3ds/cmake/Platform/Generic-dkP.cmake new file mode 100644 index 000000000..27cde3995 --- /dev/null +++ b/3ds/cmake/Platform/Generic-dkP.cmake @@ -0,0 +1,82 @@ +# ----------------------------------------------------------------------------- +# Generic configuration for devkitPro platforms + +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +# Set default build type +set(CMAKE_BUILD_TYPE_INIT "Release") + +# Use .elf extension for compiled binaries +set(CMAKE_EXECUTABLE_SUFFIX .elf) + +# Prevent standard build configurations from loading unwanted "default" flags +if(DKP_NO_BUILTIN_CMAKE_CONFIGS) + set(CMAKE_NOT_USING_CONFIG_FLAGS TRUE) +endif() + +# Disable shared library support +set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available") +set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE) + +# Helper macro for platform settings initialization +macro(__dkp_init_platform_settings platform) + foreach(lang IN ITEMS C CXX ASM) + set(CMAKE_${lang}_FLAGS_INIT "${${platform}_ARCH_SETTINGS} ${${platform}_COMMON_FLAGS}") + + if(NOT DKP_PLATFORM_BOOTSTRAP) + set(CMAKE_${lang}_STANDARD_LIBRARIES "${${platform}_STANDARD_LIBRARIES}" CACHE STRING "" FORCE) + set(CMAKE_${lang}_STANDARD_INCLUDE_DIRECTORIES "${${platform}_STANDARD_INCLUDE_DIRECTORIES}" CACHE STRING "") + endif() + endforeach() + + string(APPEND CMAKE_ASM_FLAGS_INIT " -x assembler-with-cpp") + + if(NOT DKP_PLATFORM_BOOTSTRAP) + set(CMAKE_EXE_LINKER_FLAGS_INIT "${${platform}_LINKER_FLAGS}") + else() + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + endif() + + if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND DEFINED DKP_INSTALL_PREFIX_INIT) + set(CMAKE_INSTALL_PREFIX "${DKP_INSTALL_PREFIX_INIT}" CACHE PATH + "Install path prefix, prepended onto install directories." FORCE) + endif() + + if(NOT CMAKE_LIBRARY_ARCHITECTURE) + string(REPLACE " " ";" _dkp_arch "${${platform}_ARCH_SETTINGS}") + if(CMAKE_POSITION_INDEPENDENT_CODE) + list(APPEND _dkp_arch "-fPIC") + endif() + + execute_process( + COMMAND "${CMAKE_C_COMPILER}" ${_dkp_arch} -print-multi-directory + OUTPUT_VARIABLE _dkp_multidir + RESULT_VARIABLE _dkp_retval + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(NOT _dkp_retval AND NOT _dkp_multidir STREQUAL ".") + set(CMAKE_LIBRARY_ARCHITECTURE "${_dkp_multidir}" CACHE INTERNAL "abi") + message(STATUS "Detected multilib folder: ${CMAKE_LIBRARY_ARCHITECTURE}") + endif() + endif() +endmacro() + +# ----------------------------------------------------------------------------- +# Helper utilities + +# Include common devkitPro bits and pieces +include(dkp-impl-helpers) +include(dkp-linker-utils) +include(dkp-custom-target) +include(dkp-embedded-binary) +include(dkp-asset-folder) + +# Build tool hook +if(DEFINED ENV{DKP_BUILD_TOOL_HOOK}) + set(DKP_BUILD_TOOL_HOOK "$ENV{DKP_BUILD_TOOL_HOOK}" CACHE INTERNAL "") +endif() +if(DEFINED DKP_BUILD_TOOL_HOOK) + include(${DKP_BUILD_TOOL_HOOK}) +endif() diff --git a/3ds/cmake/Platform/Nintendo3DS.cmake b/3ds/cmake/Platform/Nintendo3DS.cmake new file mode 100644 index 000000000..60f1249bb --- /dev/null +++ b/3ds/cmake/Platform/Nintendo3DS.cmake @@ -0,0 +1,229 @@ +# ----------------------------------------------------------------------------- +# Platform configuration + +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +# Inherit default devkitPro platform configuration +include(Platform/Generic-dkP) + +# Platform identification flags +set(NINTENDO_3DS TRUE) + +# Platform settings +set(CTR_ARCH_SETTINGS "-march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft") +set(CTR_COMMON_FLAGS "-mword-relocations -ffunction-sections -D__3DS__") +set(CTR_LINKER_FLAGS "-L${CTR_ROOT}/lib -L${DEVKITPRO}/portlibs/3ds/lib -specs=3dsx.specs") +set(CTR_STANDARD_LIBRARIES "-lctru -lm") +set(CTR_STANDARD_INCLUDE_DIRECTORIES "${CTR_ROOT}/include") + +__dkp_init_platform_settings(CTR) + +# ----------------------------------------------------------------------------- +# Platform-specific helper utilities + +function(ctr_generate_smdh) + cmake_parse_arguments(PARSE_ARGV 0 SMDH "" "OUTPUT;NAME;DESCRIPTION;AUTHOR;ICON" "") + if (NOT CTR_SMDHTOOL_EXE) + message(FATAL_ERROR "Could not find smdhtool: try installing 3ds-tools") + endif() + if (NOT DEFINED SMDH_OUTPUT) + if(DEFINED SMDH_UNPARSED_ARGUMENTS) + list(GET SMDH_UNPARSED_ARGUMENTS 0 SMDH_OUTPUT) + else() + message(FATAL_ERROR "ctr_generate_smdh: missing OUTPUT argument") + endif() + endif() + if (NOT DEFINED SMDH_NAME) + set(SMDH_NAME "${CMAKE_PROJECT_NAME}") + endif() + if (NOT DEFINED SMDH_DESCRIPTION) + set(SMDH_DESCRIPTION "Built with devkitARM & libctru") + endif() + if (NOT DEFINED SMDH_AUTHOR) + set(SMDH_AUTHOR "Unspecified Author") + endif() + if (NOT DEFINED SMDH_ICON) + if(NOT CTR_DEFAULT_ICON) + message(FATAL_ERROR "ctr_generate_smdh: could not find default icon, try installing libctru") + endif() + set(SMDH_ICON "${CTR_DEFAULT_ICON}") + else() + get_filename_component(SMDH_ICON "${SMDH_ICON}" ABSOLUTE) + endif() + + get_filename_component(SMDH_OUTPUT "${SMDH_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + add_custom_command( + OUTPUT "${SMDH_OUTPUT}" + COMMAND ${CTR_SMDHTOOL_EXE} --create "${SMDH_NAME}" "${SMDH_DESCRIPTION}" "${SMDH_AUTHOR}" "${SMDH_ICON}" "${SMDH_OUTPUT}" + DEPENDS "${SMDH_ICON}" + VERBATIM + ) +endfunction() + +function(ctr_create_3dsx target) + cmake_parse_arguments(PARSE_ARGV 1 CTR_3DSXTOOL "NOSMDH" "TARGET;OUTPUT;SMDH;ROMFS" "") + + if (NOT CTR_3DSXTOOL_EXE) + message(FATAL_ERROR "Could not find 3dsxtool: try installing 3ds-tools") + endif() + + if(DEFINED CTR_3DSXTOOL_TARGET) + set(intarget "${CTR_3DSXTOOL_TARGET}") + set(outtarget "${target}") + else() + set(intarget "${target}") + set(outtarget "${target}_3dsx") + endif() + + if(NOT TARGET "${intarget}") + message(FATAL_ERROR "ctr_create_3dsx: target '${intarget}' not defined") + endif() + + if(DEFINED CTR_3DSXTOOL_OUTPUT) + get_filename_component(CTR_3DSXTOOL_OUTPUT "${CTR_3DSXTOOL_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + elseif(DEFINED CTR_3DSXTOOL_TARGET) + set(CTR_3DSXTOOL_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${outtarget}.3dsx") + else() + __dkp_target_derive_name(CTR_3DSXTOOL_OUTPUT ${intarget} ".3dsx") + endif() + + set(CTR_3DSXTOOL_ARGS "$" "${CTR_3DSXTOOL_OUTPUT}") + set(CTR_3DSXTOOL_DEPS ${intarget}) + + if (DEFINED CTR_3DSXTOOL_SMDH AND CTR_3DSXTOOL_NOSMDH) + message(FATAL_ERROR "ctr_create_3dsx: cannot specify SMDH and NOSMDH at the same time") + endif() + + if (NOT DEFINED CTR_3DSXTOOL_SMDH AND NOT CTR_3DSXTOOL_NOSMDH) + set(CTR_3DSXTOOL_SMDH "${CMAKE_CURRENT_BINARY_DIR}/${outtarget}.default.smdh") + ctr_generate_smdh(OUTPUT "${CTR_3DSXTOOL_SMDH}") + endif() + + if (DEFINED CTR_3DSXTOOL_SMDH) + get_filename_component(CTR_3DSXTOOL_SMDH "${CTR_3DSXTOOL_SMDH}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + list(APPEND CTR_3DSXTOOL_ARGS "--smdh=${CTR_3DSXTOOL_SMDH}") + list(APPEND CTR_3DSXTOOL_DEPS "${CTR_3DSXTOOL_SMDH}") + endif() + + if (DEFINED CTR_3DSXTOOL_ROMFS) + if (TARGET "${CTR_3DSXTOOL_ROMFS}") + get_target_property(_folder "${CTR_3DSXTOOL_ROMFS}" DKP_ASSET_FOLDER) + if (NOT _folder) + message(FATAL_ERROR "ctr_create_3dsx: not a valid asset target") + endif() + list(APPEND CTR_3DSXTOOL_ARGS "--romfs=${_folder}") + list(APPEND CTR_3DSXTOOL_DEPS ${CTR_3DSXTOOL_ROMFS} $) + else() + get_filename_component(CTR_3DSXTOOL_ROMFS "${CTR_3DSXTOOL_ROMFS}" ABSOLUTE) + if (NOT IS_DIRECTORY "${CTR_3DSXTOOL_ROMFS}") + message(FATAL_ERROR "ctr_create_3dsx: cannot find romfs dir: ${CTR_3DSXTOOL_ROMFS}") + endif() + list(APPEND CTR_3DSXTOOL_ARGS "--romfs=${CTR_3DSXTOOL_ROMFS}") + endif() + endif() + + add_custom_command( + OUTPUT "${CTR_3DSXTOOL_OUTPUT}" + COMMAND "${CTR_3DSXTOOL_EXE}" ${CTR_3DSXTOOL_ARGS} + DEPENDS ${CTR_3DSXTOOL_DEPS} + COMMENT "Building 3DSX executable target ${outtarget}" + VERBATIM + ) + + add_custom_target(${outtarget} ALL DEPENDS "${CTR_3DSXTOOL_OUTPUT}") + dkp_set_target_file(${outtarget} "${CTR_3DSXTOOL_OUTPUT}") +endfunction() + +function(ctr_add_shader_library target) + cmake_parse_arguments(PARSE_ARGV 1 CTR_PICASSO "" "OUTPUT" "SOURCES") + + if (NOT CTR_PICASSO_EXE) + message(FATAL_ERROR "Could not find picasso: try installing picasso") + endif() + + if(DEFINED CTR_PICASSO_OUTPUT) + get_filename_component(CTR_PICASSO_OUTPUT "${CTR_PICASSO_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + else() + set(CTR_PICASSO_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${target}.shbin") + endif() + + if(NOT DEFINED CTR_PICASSO_SOURCES AND DEFINED CTR_PICASSO_UNPARSED_ARGUMENTS) + set(CTR_PICASSO_SOURCES "${CTR_PICASSO_UNPARSED_ARGUMENTS}") + else() + message(FATAL_ERROR "ctr_add_shader_library: must provide at least one source code file") + endif() + + add_custom_command( + OUTPUT "${CTR_PICASSO_OUTPUT}" + COMMAND "${CTR_PICASSO_EXE}" -o "${CTR_PICASSO_OUTPUT}" ${CTR_PICASSO_SOURCES} + DEPENDS ${CTR_PICASSO_SOURCES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Building shader library ${target}" + VERBATIM + ) + + add_custom_target(${target} DEPENDS "${CTR_PICASSO_OUTPUT}") + dkp_set_target_file(${target} "${CTR_PICASSO_OUTPUT}") +endfunction() + +function(ctr_add_graphics_target target kind) + cmake_parse_arguments(PARSE_ARGV 2 CTR_TEX3DS "" "OUTPUT" "INPUTS;OPTIONS") + + if (NOT CTR_TEX3DS_EXE) + message(FATAL_ERROR "Could not find tex3ds: try installing tex3ds") + endif() + + if(DEFINED CTR_TEX3DS_OUTPUT) + get_filename_component(CTR_TEX3DS_OUTPUT "${CTR_TEX3DS_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + else() + set(CTR_TEX3DS_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${target}.t3x") + endif() + + set(CTR_TEX3DS_ARGS -o "${CTR_TEX3DS_OUTPUT}") + set(CTR_TEX3DS_DEPS "") + + string(TOUPPER "${kind}" kind) + if(kind STREQUAL "IMAGE") + # Nothing on purpose + elseif(kind STREQUAL "CUBEMAP") + list(APPEND CTR_TEX3DS_ARGS "--cubemap") + elseif(kind STREQUAL "SKYBOX") + list(APPEND CTR_TEX3DS_ARGS "--skybox") + elseif(kind STREQUAL "ATLAS") + list(APPEND CTR_TEX3DS_ARGS "--atlas") + else() + message(FATAL_ERROR "ctr_add_graphics_target: invalid mode: ${kind}") + endif() + + list(LENGTH CTR_TEX3DS_INPUTS numinputs) + if(numinputs LESS 1) + message(FATAL_ERROR "ctr_add_graphics_target: must provide at least one input") + endif() + if(NOT kind STREQUAL "ATLAS" AND numinputs GREATER 1) + message(FATAL_ERROR "ctr_add_graphics_target: multiple inputs only supported with atlas mode") + endif() + + list(APPEND CTR_TEX3DS_ARGS ${CTR_TEX3DS_OPTIONS}) + foreach(input IN LISTS CTR_TEX3DS_INPUTS) + dkp_resolve_file(infile "${input}") + list(APPEND CTR_TEX3DS_ARGS "${infile}") + + if (TARGET "${input}") + list(APPEND CTR_TEX3DS_DEPS ${input} "${infile}") + else() + list(APPEND CTR_TEX3DS_DEPS "${infile}") + endif() + endforeach() + + add_custom_command( + OUTPUT "${CTR_TEX3DS_OUTPUT}" + COMMAND "${CTR_TEX3DS_EXE}" ${CTR_TEX3DS_ARGS} + DEPENDS ${CTR_TEX3DS_DEPS} + COMMENT "Converting graphics target ${target}" + VERBATIM + ) + + add_custom_target(${target} DEPENDS "${CTR_TEX3DS_OUTPUT}") + dkp_set_target_file(${target} "${CTR_TEX3DS_OUTPUT}") +endfunction() diff --git a/3ds/cmake/catnip-main.cmake b/3ds/cmake/catnip-main.cmake new file mode 100644 index 000000000..fdfa09f31 --- /dev/null +++ b/3ds/cmake/catnip-main.cmake @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.13) + +set(CATNIP TRUE) +set(CATNIP_VERBS config build install clean) + +set(CATNIP_CLANGD_FLAGS_REMOVE + -mword-relocations +) + +include(${CMAKE_CURRENT_LIST_DIR}/dkp-initialize-path.cmake) +include(Catnip/Utility) +include(Catnip/Information) +include(Catnip/Topology) + +if("${CATNIP_VERB}" STREQUAL "clean") + include(Catnip/Verb-Clean) +elseif("${CATNIP_VERB}" MATCHES "^dump-.+") + include(Catnip/Verb-Dump) +else() + include(Catnip/Verb-Build) +endif() diff --git a/3ds/cmake/devkitARM.cmake b/3ds/cmake/devkitARM.cmake new file mode 100644 index 000000000..be61b8959 --- /dev/null +++ b/3ds/cmake/devkitARM.cmake @@ -0,0 +1,6 @@ +include(${CMAKE_CURRENT_LIST_DIR}/dkp-initialize-path.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/dkp-toolchain-common.cmake) + +set (DKP_BIN2S_ALIGNMENT 4) + +__dkp_toolchain(devkitARM arm arm-none-eabi) diff --git a/3ds/cmake/dkp-arm-mode-wrapper b/3ds/cmake/dkp-arm-mode-wrapper new file mode 100755 index 000000000..27de338ea --- /dev/null +++ b/3ds/cmake/dkp-arm-mode-wrapper @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -e +shopt -s extglob + +args=() +argmode="" +filemode="" +while [[ $# -gt 0 ]]; do + case "$1" in + -marm|-mthumb) + argmode="$1" + ;; + -*) + args+=("$1") + ;; + *.@(32|arm|iwram|itcm).@(c|m|C|M|c++|cc|cpp|cxx|mm|CPP)) + args+=("$1") + filemode="-marm" + ;; + *.@(16|thumb).@(c|m|C|M|c++|cc|cpp|cxx|mm|CPP)) + args+=("$1") + filemode="-mthumb" + ;; + *) + args+=("$1") + ;; + esac + shift +done + +if [ ! -z "$filemode" ]; then + args+=("$filemode") +elif [ ! -z "$argmode" ]; then + args+=("$argmode") +fi + +exec "${args[@]}" diff --git a/3ds/cmake/dkp-asset-folder.cmake b/3ds/cmake/dkp-asset-folder.cmake new file mode 100644 index 000000000..f8e19c6e3 --- /dev/null +++ b/3ds/cmake/dkp-asset-folder.cmake @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +include(${CMAKE_CURRENT_LIST_DIR}/dkp-custom-target.cmake) + +function(dkp_add_asset_target target folder) + get_filename_component(folder "${folder}" ABSOLUTE) + add_custom_target(${target}) + set_target_properties(${target} PROPERTIES + DKP_ASSET_FOLDER "${folder}" + DKP_ASSET_FILES "" + ) +endfunction() + +function(dkp_install_assets target) + get_target_property(_dest ${target} DKP_ASSET_FOLDER) + if (NOT _dest) + message(FATAL_ERROR "dkp_install_assets: ${target} is not a valid asset target") + endif() + + cmake_parse_arguments(PARSE_ARGV 1 ASSET "" "DESTINATION" "TARGETS") + + if (DEFINED ASSET_DESTINATION) + set(_dest "${_dest}/${ASSET_DESTINATION}") + endif() + + if (NOT DEFINED ASSET_TARGETS) + message(FATAL_ERROR "dkp_install_assets: must specify at least one target to install") + endif() + + foreach (srctarget IN LISTS ASSET_TARGETS) + if (NOT TARGET ${srctarget}) + message(FATAL_ERROR "dkp_install_assets: target ${srctarget} not found") + endif() + + if (TARGET ${srctarget}_install_to_${target}) + message(WARNING "dkp_install_assets: target ${srctarget} already installed to ${target}, skipping") + continue() + endif() + + dkp_resolve_file(_srcfiles ${srctarget} MULTI) + set(_destfiles "") + set(_installcmds "") + foreach(_srcfile IN LISTS _srcfiles) + get_filename_component(_name "${_srcfile}" NAME) + set(_destfile "${_dest}/${_name}") + list(APPEND _destfiles "${_destfile}") + list(APPEND _installcmds + COMMAND ${CMAKE_COMMAND} -E copy "${_srcfile}" "${_destfile}" + ) + endforeach() + + add_custom_command( + OUTPUT ${_destfiles} + COMMAND ${CMAKE_COMMAND} -E make_directory "${_dest}" + ${_installcmds} + COMMENT "Installing ${srctarget} to ${target}" + DEPENDS ${srctarget} ${_srcfiles} + ) + + add_custom_target(${srctarget}_install_to_${target} + DEPENDS ${_destfiles} + ) + + add_dependencies(${target} ${srctarget}_install_to_${target}) + set_property(TARGET ${target} APPEND PROPERTY DKP_ASSET_FILES ${_destfiles}) + endforeach() +endfunction() + +function(dkp_track_assets target) + get_target_property(_dest ${target} DKP_ASSET_FOLDER) + if (NOT _dest) + message(FATAL_ERROR "dkp_track_assets: ${target} is not a valid asset target") + endif() + + cmake_parse_arguments(PARSE_ARGV 1 ASSET "" "FOLDER" "FILES") + + if (DEFINED ASSET_FOLDER) + set(_dest "${_dest}/${ASSET_FOLDER}") + endif() + + if (NOT DEFINED ASSET_FILES) + message(FATAL_ERROR "dkp_track_assets: must provide at least one input file") + endif() + + foreach (file IN LISTS ASSET_FILES) + set(file "${_dest}/${file}") + if (NOT EXISTS "${file}") + message(FATAL_ERROR "dkp_track_assets: file ${file} does not exist") + endif() + + set_property(TARGET ${target} APPEND PROPERTY DKP_ASSET_FILES "${file}") + endforeach() +endfunction() diff --git a/3ds/cmake/dkp-catnip-utils.cmake b/3ds/cmake/dkp-catnip-utils.cmake new file mode 100644 index 000000000..4734bb4af --- /dev/null +++ b/3ds/cmake/dkp-catnip-utils.cmake @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +if(NOT DEFINED CATNIP_PRESET) + if(NOT DEFINED ENV{CATNIP_PRESET}) + return() + endif() + + set(CATNIP_PRESET "$ENV{CATNIP_PRESET}" CACHE INTERNAL "") + set(CATNIP_BUILD_DIR "$ENV{CATNIP_BUILD_DIR}" CACHE INTERNAL "") +endif() + +function(catnip_import selector file) + string(FIND "${selector}" "." dotpos) + if("${dotpos}" LESS 0) + set(selector "${selector}.${CATNIP_PRESET}") + endif() + + include(${CATNIP_BUILD_DIR}/${selector}/${file}.cmake) +endfunction() diff --git a/3ds/cmake/dkp-custom-target.cmake b/3ds/cmake/dkp-custom-target.cmake new file mode 100644 index 000000000..c5b38eaa2 --- /dev/null +++ b/3ds/cmake/dkp-custom-target.cmake @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +function(dkp_set_target_file target) + if (NOT ${ARGC} GREATER 1) + message(FATAL_ERROR "dkp_set_target_file: must provide at least one input file") + endif() + + set_target_properties(${target} PROPERTIES DKP_FILE "${ARGN}") +endfunction() + +function(dkp_resolve_file outvar inname) + cmake_parse_arguments(PARSE_ARGV 2 ARG "MULTI" "" "") + + if (TARGET "${inname}") + get_target_property(_filename "${inname}" DKP_FILE) + if (NOT _filename) + message(FATAL_ERROR "dkp_resolve_file: target ${inname} does not have a registered file") + endif() + + list(LENGTH _filename listlen) + if(listlen GREATER 1 AND NOT ARG_MULTI) + message(FATAL_ERROR "dkp_resolve_file: target ${inname} has more than one registered file") + endif() + + set(${outvar} "${_filename}" PARENT_SCOPE) + else() + get_filename_component(innameabs "${inname}" ABSOLUTE) + if(NOT EXISTS "${innameabs}") + message(FATAL_ERROR "dkp_resolve_file: unable to resolve file: ${inname}") + endif() + set(${outvar} "${innameabs}" PARENT_SCOPE) + endif() +endfunction() diff --git a/3ds/cmake/dkp-embedded-binary.cmake b/3ds/cmake/dkp-embedded-binary.cmake new file mode 100644 index 000000000..6800bdcf8 --- /dev/null +++ b/3ds/cmake/dkp-embedded-binary.cmake @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +include(${CMAKE_CURRENT_LIST_DIR}/dkp-impl-helpers.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/dkp-custom-target.cmake) + +function(dkp_add_embedded_binary_library target) + if (NOT ${ARGC} GREATER 1) + message(FATAL_ERROR "dkp_add_embedded_binary_library: must provide at least one input file") + endif() + + if (NOT DKP_BIN2S) + message(FATAL_ERROR "Could not find bin2s: try installing general-tools") + endif() + + __dkp_asm_lang(lang dkp_add_embedded_binary_library) + set(genfolder "${CMAKE_CURRENT_BINARY_DIR}/.dkp-generated/${target}") + set(intermediates "") + foreach (inname IN LISTS ARGN) + dkp_resolve_file(infiles "${inname}" MULTI) + foreach(infile IN LISTS infiles) + get_filename_component(basename "${infile}" NAME) + string(REPLACE "." "_" basename "${basename}") + + if (TARGET "${inname}") + set(indeps ${inname} ${infile}) + else() + set(indeps ${infile}) + endif() + + add_custom_command( + OUTPUT "${genfolder}/${basename}.S" "${genfolder}/${basename}.h" + COMMAND ${CMAKE_COMMAND} -E make_directory "${genfolder}" + COMMAND ${DKP_BIN2S} -a ${DKP_BIN2S_ALIGNMENT} -H "${genfolder}/${basename}.h" "${infile}" > "${genfolder}/${basename}.S" + DEPENDS ${indeps} + COMMENT "Generating binary embedding source for ${inname}" + ) + + list(APPEND intermediates "${genfolder}/${basename}.S" "${genfolder}/${basename}.h") + set_source_files_properties("${genfolder}/${basename}.S" PROPERTIES LANGUAGE "${lang}") + endforeach() + endforeach() + + add_library(${target} OBJECT ${intermediates}) + target_include_directories(${target} INTERFACE "${genfolder}") +endfunction() + +function(dkp_target_use_embedded_binary_libraries target) + message(DEPRECATION "dkp_target_use_embedded_binary_libraries is deprecated, please use target_link_libraries(${target} PRIVATE ...) instead") + if (NOT ${ARGC} GREATER 1) + message(FATAL_ERROR "dkp_target_use_embedded_binary_libraries: must provide at least one input library") + endif() + + target_link_libraries(${target} PRIVATE ${ARGN}) +endfunction() diff --git a/3ds/cmake/dkp-gba-ds-common.cmake b/3ds/cmake/dkp-gba-ds-common.cmake new file mode 100644 index 000000000..8d9c2a85c --- /dev/null +++ b/3ds/cmake/dkp-gba-ds-common.cmake @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES DKP_NO_ARM_MODE_WRAPPER) + +if(NOT DKP_NO_ARM_MODE_WRAPPER AND NOT CTEST_USE_LAUNCHERS) + # Use this "internal" (but documented) global property instead of CMAKE__COMPILER_LAUNCHER + # in order to allow users to specify their own compiler wrapper tools. + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_CURRENT_LIST_DIR}/dkp-arm-mode-wrapper") +endif() + +find_program(GRIT_EXE NAMES grit HINTS "${DEVKITPRO}/tools/bin") +find_program(MMUTIL_EXE NAMES mmutil HINTS "${DEVKITPRO}/tools/bin") diff --git a/3ds/cmake/dkp-gba-ds-utils.cmake b/3ds/cmake/dkp-gba-ds-utils.cmake new file mode 100644 index 000000000..ab5bec271 --- /dev/null +++ b/3ds/cmake/dkp-gba-ds-utils.cmake @@ -0,0 +1,183 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +include(${CMAKE_CURRENT_LIST_DIR}/dkp-impl-helpers.cmake) + +macro(__grit_parse_options_common) + set(GRIT_ARGS -g) + set(GRIT_DEPS "") + set(GRIT_RENAME_CMDS "") + + if (NOT GRIT_EXE) + message(FATAL_ERROR "Could not find grit: try installing grit") + endif() + + if(GRIT_TEXTURE) + set(GRIT_BITMAP TRUE) + list(APPEND GRIT_ARGS -gb -gx) + elseif(GRIT_BITMAP) + list(APPEND GRIT_ARGS -gb) + else() + list(APPEND GRIT_ARGS -gt) + endif() + + if(NOT DEFINED GRIT_DEPTH) + if(GRIT_BITMAP) + set(GRIT_DEPTH 8) + else() + set(GRIT_DEPTH 4) + endif() + endif() + list(APPEND GRIT_ARGS -gB${GRIT_DEPTH}) + + if(NOT GRIT_BITMAP AND NOT GRIT_NO_MAP) + list(APPEND GRIT_ARGS -m) + else() + set(GRIT_NO_MAP TRUE) + list(APPEND GRIT_ARGS -m!) + endif() + + # TODO: handle metatile output + + if(NOT GRIT_DEPTH EQUAL 16 AND NOT GRIT_NO_PALETTE) + list(APPEND GRIT_ARGS -p) + else() + set(GRIT_NO_PALETTE TRUE) + list(APPEND GRIT_ARGS -p!) + endif() + + if(DEFINED GRIT_OPTIONS) + list(APPEND GRIT_ARGS ${GRIT_OPTIONS}) + endif() +endmacro() + +macro(__grit_output_name var fext) + if(DEFINED GRIT_${var}) + get_filename_component(GRIT_${var} "${GRIT_${var}}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + else() + set(GRIT_${var} "${CMAKE_CURRENT_BINARY_DIR}/${target}.${fext}") + endif() +endmacro() + +macro(__grit_rename_cmd from to) + list(APPEND GRIT_RENAME_CMDS + COMMAND ${CMAKE_COMMAND} -E rename "${from}" "${to}" + ) +endmacro() + +function(grit_add_grf_target target input) + cmake_parse_arguments(PARSE_ARGV 1 GRIT "BITMAP;TEXTURE;NO_MAP;NO_PALETTE" "OUTPUT;DEPTH" "OPTIONS") + __grit_parse_options_common() + + __grit_output_name(OUTPUT grf) + __grit_rename_cmd("~$${target}.grf" "${GRIT_OUTPUT}") + + get_filename_component(input "${input}" ABSOLUTE) + add_custom_command( + OUTPUT "${GRIT_OUTPUT}" + COMMAND ${GRIT_EXE} "${input}" ${GRIT_ARGS} -ftr -fh! -o "~$${target}.grf" + ${GRIT_RENAME_CMDS} + DEPENDS "${input}" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Converting graphics to GRF ${target}" + VERBATIM + ) + + add_custom_target(${target} DEPENDS "${GRIT_OUTPUT}") + dkp_set_target_file(${target} "${GRIT_OUTPUT}") +endfunction() + +macro(grit_add_nds_icon_target target input) + grit_add_grf_target(${target} "${input}" + NO_MAP + DEPTH 4 + OPTIONS -pe 16 -gT FF00FF + ) +endmacro() + +function(grit_add_binary_target target input) + cmake_parse_arguments(PARSE_ARGV 1 GRIT "BITMAP;TEXTURE;NO_MAP;NO_PALETTE" "OUTPUT_GFX;OUTPUT_MAP;OUTPUT_PAL;DEPTH" "OPTIONS") + __grit_parse_options_common() + + __grit_output_name(OUTPUT_GFX gfx) + __grit_rename_cmd("~$${target}.img.bin" "${GRIT_OUTPUT_GFX}") + set(GRIT_OUTPUTS "${GRIT_OUTPUT_GFX}") + + if(NOT GRIT_NO_MAP) + __grit_output_name(OUTPUT_MAP map) + __grit_rename_cmd("~$${target}.map.bin" "${GRIT_OUTPUT_MAP}") + list(APPEND GRIT_OUTPUTS "${GRIT_OUTPUT_MAP}") + endif() + + if(NOT GRIT_NO_PALETTE) + __grit_output_name(OUTPUT_PAL pal) + __grit_rename_cmd("~$${target}.pal.bin" "${GRIT_OUTPUT_PAL}") + list(APPEND GRIT_OUTPUTS "${GRIT_OUTPUT_PAL}") + endif() + + get_filename_component(input "${input}" ABSOLUTE) + add_custom_command( + OUTPUT ${GRIT_OUTPUTS} + COMMAND ${GRIT_EXE} "${input}" ${GRIT_ARGS} -ftb -fh! -o "~$${target}.bin" + ${GRIT_RENAME_CMDS} + DEPENDS "${input}" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Converting graphics to binary ${target}" + VERBATIM + ) + + add_custom_target(${target} DEPENDS ${GRIT_OUTPUTS}) + dkp_set_target_file(${target} ${GRIT_OUTPUTS}) +endfunction() + +function(mm_add_soundbank_target target) + cmake_parse_arguments(PARSE_ARGV 1 MMUTIL "" "OUTPUT;HEADER" "INPUTS;OPTIONS") + + set(MMUTIL_ARGS "") + set(MMUTIL_OUTPUTS "") + + if (NOT MMUTIL_EXE) + message(FATAL_ERROR "Could not find mmutil: try installing mmutil") + endif() + + if(NINTENDO_GBA) + # Nothing on purpose + elseif(NINTENDO_DS) + list(APPEND MMUTIL_ARGS -d) + else() + message(FATAL_ERROR "mm_add_soundbank_target: only supported on GBA and DS platforms") + endif() + + if(DEFINED MMUTIL_OUTPUT) + get_filename_component(MMUTIL_OUTPUT "${MMUTIL_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + else() + set(MMUTIL_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${target}.bin") + endif() + list(APPEND MMUTIL_OUTPUTS "${MMUTIL_OUTPUT}") + + if(DEFINED MMUTIL_HEADER) + get_filename_component(MMUTIL_HEADER "${MMUTIL_HEADER}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + list(APPEND MMUTIL_ARGS "-h${MMUTIL_HEADER}") + list(APPEND MMUTIL_OUTPUTS "${MMUTIL_HEADER}") + endif() + + if(NOT DEFINED MMUTIL_INPUTS) + if(DEFINED MMUTIL_UNPARSED_ARGUMENTS) + set(MMUTIL_INPUTS "${MMUTIL_UNPARSED_ARGUMENTS}") + else() + message(FATAL_ERROR "mm_add_soundbank_target: must provide at least one input file") + endif() + endif() + + add_custom_command( + OUTPUT ${MMUTIL_OUTPUTS} + COMMAND "${MMUTIL_EXE}" ${MMUTIL_ARGS} "-o${MMUTIL_OUTPUT}" ${MMUTIL_INPUTS} + DEPENDS ${MMUTIL_INPUTS} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Building maxmod soundbank ${target}" + VERBATIM + ) + + add_custom_target(${target} DEPENDS "${MMUTIL_OUTPUT}") + dkp_set_target_file(${target} "${MMUTIL_OUTPUT}") +endfunction() diff --git a/3ds/cmake/dkp-impl-helpers.cmake b/3ds/cmake/dkp-impl-helpers.cmake new file mode 100644 index 000000000..cbbd8ee38 --- /dev/null +++ b/3ds/cmake/dkp-impl-helpers.cmake @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +function(__dkp_target_derive_name outvar target suffix) + get_target_property(dir ${target} BINARY_DIR) + get_target_property(outname ${target} OUTPUT_NAME) + if(NOT outname) + set(outname "${target}") + endif() + + set(${outvar} "${dir}/${outname}${suffix}" PARENT_SCOPE) +endfunction() + +function(__dkp_asm_lang outvar func) + get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if("ASM" IN_LIST languages) + set(${outvar} ASM PARENT_SCOPE) + elseif("C" IN_LIST languages) + set(${outvar} C PARENT_SCOPE) + else() + message(FATAL_ERROR "${func}: the ASM language is currently disabled, enable it in project() or with enable_language()") + endif() +endfunction() diff --git a/3ds/cmake/dkp-initialize-path.cmake b/3ds/cmake/dkp-initialize-path.cmake new file mode 100644 index 000000000..de999d4f2 --- /dev/null +++ b/3ds/cmake/dkp-initialize-path.cmake @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.13) + +if(CMAKE_HOST_WIN32) + message(FATAL_ERROR "CMake must be installed and launched from msys2: pacman -S cmake") +endif() + +if(NOT DEFINED ENV{DEVKITPRO}) + set(DEVKITPRO "/opt/devkitpro") +else() + set(DEVKITPRO "$ENV{DEVKITPRO}") +endif() + +list(APPEND CMAKE_MODULE_PATH "${DEVKITPRO}/cmake") diff --git a/3ds/cmake/dkp-linker-utils.cmake b/3ds/cmake/dkp-linker-utils.cmake new file mode 100644 index 000000000..72517c144 --- /dev/null +++ b/3ds/cmake/dkp-linker-utils.cmake @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.13) +include_guard(GLOBAL) + +include(${CMAKE_CURRENT_LIST_DIR}/dkp-impl-helpers.cmake) + +function(dkp_target_generate_symbol_list target) + __dkp_target_derive_name(prefix ${target} "") + + target_link_options(${target} PRIVATE + -Wl,-Map,${prefix}.map + ) + + add_custom_command( + TARGET ${target} POST_BUILD + COMMAND ${DKP_NM} -CSn $ > ${prefix}.lst + BYPRODUCTS ${prefix}.lst ${prefix}.map + ) +endfunction() diff --git a/3ds/cmake/dkp-rule-overrides.cmake b/3ds/cmake/dkp-rule-overrides.cmake new file mode 100644 index 000000000..3cf5f850b --- /dev/null +++ b/3ds/cmake/dkp-rule-overrides.cmake @@ -0,0 +1,25 @@ + +foreach(lang IN ITEMS C CXX ASM) + # Set object file extension back to the default for Unix-like platforms + set(CMAKE_${lang}_OUTPUT_EXTENSION .o) + + if(NOT DKP_USE_DOUBLE_OBJECT_FILE_EXTENSIONS) + # Disable usage of double object file extensions (.cpp.o -> .o) + set(CMAKE_${lang}_OUTPUT_EXTENSION_REPLACE 1) + endif() + + if(NOT DKP_NO_BUILTIN_CMAKE_CONFIGS) + # Override CMake's built-in flags for GNU compilers with saner defaults: + # - Define DEBUG for Debug (no idea why CMake doesn't do this by default) + # - Use -Og for Debug (As per GCC docs: "It is a better choice than -O0 for producing debuggable + # code because some compiler passes that collect debug information are disabled at -O0.") + # - Use -Oz for MinSizeRel (Os no longer generates the "smallest possible size binaries") + # - Use -O2 instead of -O3 for Release (O3 enables aggressive loop unrolling which can be + # extremely detrimental to code size, especially for platforms such as GBA/DS) + # - Emit debug metadata on all configurations + set(CMAKE_${lang}_FLAGS_DEBUG_INIT " -g -Og -DDEBUG") + set(CMAKE_${lang}_FLAGS_MINSIZEREL_INIT " -g -Oz -DNDEBUG") + set(CMAKE_${lang}_FLAGS_RELEASE_INIT " -g -O2 -DNDEBUG") + set(CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT " -g -O2 -DNDEBUG") + endif() +endforeach() diff --git a/3ds/cmake/dkp-toolchain-common.cmake b/3ds/cmake/dkp-toolchain-common.cmake new file mode 100644 index 000000000..467da2c36 --- /dev/null +++ b/3ds/cmake/dkp-toolchain-common.cmake @@ -0,0 +1,69 @@ +cmake_minimum_required(VERSION 3.13) + +macro(__dkp_toolchain name arch triplet) + set(${name} TRUE) + + if(NOT CMAKE_SYSTEM_NAME) + # Set default platform if none was supplied. + set(CMAKE_SYSTEM_NAME Generic-dkP) + endif() + + if(NOT CMAKE_SYSTEM_VERSION) + # Usually, this setting is unused in "non-standard" platforms. + set(CMAKE_SYSTEM_VERSION 1) + endif() + + if(NOT CMAKE_SYSTEM_PROCESSOR) + # Set the correct processor name if no refinement was supplied. + set(CMAKE_SYSTEM_PROCESSOR ${arch}) + endif() + + if(NOT CMAKE_USER_MAKE_RULES_OVERRIDE) + # Load platform information overrides + set(CMAKE_USER_MAKE_RULES_OVERRIDE ${CMAKE_CURRENT_LIST_DIR}/dkp-rule-overrides.cmake) + endif() + + set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES DKP_PLATFORM_BOOTSTRAP) + + # [CMake 3.15+] Start find_package in config mode + set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) + + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.16) + set(DKP_PREFIX_VAR CMAKE_SYSTEM_PREFIX_PATH) + set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH OFF) + string(REPLACE ":" ";" CMAKE_SYSTEM_PROGRAM_PATH "$ENV{PATH}") + else() + set(DKP_PREFIX_VAR CMAKE_FIND_ROOT_PATH) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_SYSTEM_INCLUDE_PATH /include) + set(CMAKE_SYSTEM_LIBRARY_PATH /lib) + set(CMAKE_SYSTEM_PROGRAM_PATH /bin) + endif() + + set(${DKP_PREFIX_VAR} + ${DEVKITPRO}/${name} + ${DEVKITPRO}/${name}/${triplet} + ${DEVKITPRO}/tools + ) + + set(TOOL_HINT ${DEVKITPRO}/${name}/bin) + find_program(CMAKE_ASM_COMPILER ${triplet}-gcc HINTS ${TOOL_HINT}) + find_program(CMAKE_C_COMPILER ${triplet}-gcc HINTS ${TOOL_HINT}) + find_program(CMAKE_CXX_COMPILER ${triplet}-g++ HINTS ${TOOL_HINT}) + find_program(CMAKE_LINKER ${triplet}-ld HINTS ${TOOL_HINT}) + find_program(CMAKE_AR ${triplet}-gcc-ar HINTS ${TOOL_HINT}) + find_program(CMAKE_RANLIB ${triplet}-gcc-ranlib HINTS ${TOOL_HINT}) + find_program(CMAKE_STRIP ${triplet}-strip HINTS ${TOOL_HINT}) + find_program(DKP_OBJCOPY ${triplet}-objcopy HINTS ${TOOL_HINT}) + find_program(DKP_NM ${triplet}-nm HINTS ${TOOL_HINT}) + + set(TOOL_HINT ${DEVKITPRO}/tools/bin) + find_program(DKP_BIN2S NAMES bin2s HINTS ${TOOL_HINT}) +endmacro() + +macro(__dkp_platform_prefix) + list(INSERT ${DKP_PREFIX_VAR} 0 ${ARGN}) +endmacro() diff --git a/CMakeLists.txt b/CMakeLists.txt index 9266ee6b6..a18a8083e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,11 @@ cmake_minimum_required(VERSION 3.21) if(PLATFORM STREQUAL "3ds") if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) if(NOT DEFINED ENV{DEVKITPRO}) - message(FATAL_ERROR "Please define DEVKITPRO environment variable to point to your SDK path!") + # Use local toolchain file as fallback + set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/3ds/cmake/3DS.cmake" CACHE PATH "toolchain file") + else() + set(CMAKE_TOOLCHAIN_FILE "$ENV{DEVKITPRO}/cmake/3DS.cmake" CACHE PATH "toolchain file") endif() - - set(CMAKE_TOOLCHAIN_FILE "$ENV{DEVKITPRO}/cmake/3DS.cmake" CACHE PATH "toolchain file") endif() endif() @@ -117,6 +118,24 @@ endif() add_executable(butterscotch ${SOURCES} ${PLATFORM_SOURCES}) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src) +# Include bzip2 sources for platforms that may not have system libbz2 (e.g., 3DS cross builds) +if(PLATFORM STREQUAL "3ds") + set(BZIP2_SOURCES + vendor/bzip2/blocksort.c + vendor/bzip2/bzlib.c + vendor/bzip2/compress.c + vendor/bzip2/crctable.c + vendor/bzip2/decompress.c + vendor/bzip2/huffman.c + vendor/bzip2/randtable.c + ) + target_sources(butterscotch PRIVATE ${BZIP2_SOURCES}) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/vendor/bzip2) + if(NOT MSVC) + set_source_files_properties(${BZIP2_SOURCES} PROPERTIES COMPILE_FLAGS "-w") + endif() +endif() + # stb_ds target_include_directories(butterscotch PUBLIC vendor/stb/ds) @@ -476,7 +495,6 @@ elseif(PLATFORM STREQUAL "3ds") SDL citro3d ctru - bz2 m 3ds_shaders ) diff --git a/src/3ds/main.c b/src/3ds/main.c index 0a3eaea20..73ee7e98b 100644 --- a/src/3ds/main.c +++ b/src/3ds/main.c @@ -20,7 +20,7 @@ #include "render2d_shader_shbin.h" #include "launcher.h" -//u32 __ctru_heap_size = 35 * 1024 * 1024; +u32 __ctru_heap_size = 12 * 1024 * 1024; // keep main heap modest to avoid svc allocation failures u32 __ctru_linear_heap_size = 48 * 1024 * 1024; u32 __stacksize__ = 64 * 1024; diff --git a/src/3ds/sdl12_audio_system.c b/src/3ds/sdl12_audio_system.c index 1adcad40f..314fa1072 100644 --- a/src/3ds/sdl12_audio_system.c +++ b/src/3ds/sdl12_audio_system.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "stb_ds.h" @@ -872,6 +873,35 @@ static bool load_music_from_entry(SysMixer *sm, int id, AudioEntry *ent, const c if (!music) { fprintf(stderr, "[AUDIO] Mix_LoadMUS_RW failed for '%s': %s\n", snd && snd->name ? snd->name : "?", Mix_GetError()); + // Fallback: write to a temporary file in the same directory as the archive + const char *dir = strrchr(archive, '/'); + char tmpPath[1024]; + if (dir) { + int dirlen = (int)(dir - archive); + snprintf(tmpPath, sizeof(tmpPath), "%.*s/butterscotch_tmp_music_%d.ogg", dirlen, archive, id); + } else { + snprintf(tmpPath, sizeof(tmpPath), "butterscotch_tmp_music_%d.ogg", id); + } + FILE *tf = fopen(tmpPath, "wb"); + if (tf) { + if (fwrite(mb, 1, ent->dataSize, tf) == ent->dataSize) { + fclose(tf); + music = Mix_LoadMUS(tmpPath); + if (music) { + sm->music[id] = music; + sm->musicBuf[id] = mb; + sm->musicTmpPaths[id] = strdup(tmpPath); + evict_old_music(sm, id); + return true; + } else { + fprintf(stderr, "[AUDIO] Mix_LoadMUS fallback failed for '%s' at '%s': %s\n", + snd && snd->name ? snd->name : "?", tmpPath, Mix_GetError()); + } + } else { + fclose(tf); + } + unlink(tmpPath); + } free(mb); return false; } @@ -982,6 +1012,7 @@ static void sys_init(AudioSystem *sys, DataWin *dw, FileSystem *fs) { sm->chunks = calloc(cnt, sizeof(Mix_Chunk *)); sm->music = calloc(cnt, sizeof(Mix_Music *)); sm->musicBuf = calloc(cnt, sizeof(uint8_t *)); + sm->musicTmpPaths = calloc(cnt, sizeof(char *)); sm->sfxBuf = calloc(cnt, sizeof(void *)); sm->soundGains = calloc(cnt, sizeof(float)); sm->lastUsed = calloc(cnt, sizeof(uint32_t)); @@ -1032,6 +1063,18 @@ static void sys_destroy(AudioSystem *sys) { free(sm->chunks); free(sm->music); + { + uint32_t __cnt = sm->base.dataWin ? sm->base.dataWin->sond.count : 0; + if (sm->musicTmpPaths) { + for (uint32_t __i = 0; __i < __cnt; __i++) { + if (sm->musicTmpPaths[__i]) { + unlink(sm->musicTmpPaths[__i]); + free(sm->musicTmpPaths[__i]); + } + } + } + free(sm->musicTmpPaths); + } free(sm->musicBuf); free(sm->sfxBuf); free(sm->soundGains); diff --git a/src/3ds/sdl12_audio_system.h b/src/3ds/sdl12_audio_system.h index 3323885a5..d31d30d62 100644 --- a/src/3ds/sdl12_audio_system.h +++ b/src/3ds/sdl12_audio_system.h @@ -56,6 +56,7 @@ typedef struct { Mix_Chunk **chunks; Mix_Music **music; uint8_t **musicBuf; // raw ogg/mp3 bytes backing a Mix_Music + char **musicTmpPaths; // optional temp file paths when Mix_LoadMUS_RW fails void **sfxBuf; // linearAlloc'd PCM backing a Mix_QuickLoad_RAW chunk float *soundGains; float *pitches; diff --git a/tools-local/bin/bannertool b/tools-local/bin/bannertool new file mode 100755 index 000000000..356125696 Binary files /dev/null and b/tools-local/bin/bannertool differ diff --git a/tools-local/bin/makerom b/tools-local/bin/makerom new file mode 100755 index 000000000..b9335380f Binary files /dev/null and b/tools-local/bin/makerom differ