diff --git a/.circleci/config.yml b/.circleci/config.yml index b1d0acddff2..fa8de0033a8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,6 +58,20 @@ jobs: - run: cd build && cmake -DNIGHTLY_BUILD=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/mingwcc.cmake .. - run: cd build && cmake --build . -j $(nproc) - store_artifacts: {path: ./build/devilutionx.exe, destination: devilutionx_x86.exe} + switch: + docker: + - image: devkitpro/devkita64:latest + working_directory: ~/repo + steps: + - checkout + - run: echo deb http://deb.debian.org/debian stretch-backports main > /etc/apt/sources.list.d/debian-backports.list + - run: apt-get update && apt-get install -y -t stretch-backports cmake + - run: dkp-pacman -Syu --noconfirm + # Install cmake files (https://github.com/devkitPro/docker/issues/3) + - run: dkp-pacman -S --needed --noconfirm --quiet devkitpro-pkgbuild-helpers + - run: cd build && cmake .. -DBINARY_RELEASE=ON -DNONET=ON -DPREFILL_PLAYER_NAME=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/switch/devkita64-libnx.cmake + - run: cd build && cmake --build . -j $(nproc) + - store_artifacts: {path: ./build/devilutionx.nro, destination: devilutionx.nro} workflows: version: 2 @@ -67,3 +81,4 @@ workflows: - linux_x86 - windows_x86 - linux_x86_64_sdl1 + - switch diff --git a/.gitignore b/.gitignore index fc30b83d22f..fedca48b275 100644 --- a/.gitignore +++ b/.gitignore @@ -425,3 +425,6 @@ DerivedData/ # Don't accidently commit the diabdat.mpq *.mpq + +### Nintendo Switch ### +exefs/main diff --git a/3rdParty/StormLib/src/FileStream.cpp b/3rdParty/StormLib/src/FileStream.cpp index bafd76a8d70..08a5ac80fcf 100644 --- a/3rdParty/StormLib/src/FileStream.cpp +++ b/3rdParty/StormLib/src/FileStream.cpp @@ -91,7 +91,7 @@ static bool BaseFile_Create(TFileStream * pStream) } #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) { intptr_t handle; @@ -141,7 +141,7 @@ static bool BaseFile_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD } #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) { struct stat64 fileinfo; int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR; @@ -212,7 +212,7 @@ static bool BaseFile_Read( } #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) { ssize_t bytes_read; @@ -283,7 +283,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const } #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) { ssize_t bytes_written; @@ -350,7 +350,7 @@ static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize) } #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) { if(ftruncate64((intptr_t)pStream->Base.File.hFile, (off64_t)NewFileSize) == -1) { @@ -394,7 +394,7 @@ static bool BaseFile_Replace(TFileStream * pStream, TFileStream * pNewStream) return (bool)MoveFile(pNewStream->szFileName, pStream->szFileName); #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) // "rename" on Linux also works if the target file exists if(rename(pNewStream->szFileName, pStream->szFileName) == -1) { @@ -414,7 +414,7 @@ static void BaseFile_Close(TFileStream * pStream) CloseHandle(pStream->Base.File.hFile); #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) close((intptr_t)pStream->Base.File.hFile); #endif } @@ -493,7 +493,7 @@ static bool BaseMap_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD return false; #endif -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) struct stat64 fileinfo; intptr_t handle; bool bResult = false; @@ -506,7 +506,11 @@ static bool BaseMap_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD if(fstat64(handle, &fileinfo) != -1) { #if !defined(PLATFORM_AMIGA) +#if defined(PLATFORM_SWITCH) + pStream->Base.Map.pbFile = (LPBYTE)malloc((size_t)fileinfo.st_size); +#else pStream->Base.Map.pbFile = (LPBYTE)mmap(NULL, (size_t)fileinfo.st_size, PROT_READ, MAP_PRIVATE, handle, 0); +#endif if(pStream->Base.Map.pbFile != NULL) { // time_t is number of seconds since 1.1.1970, UTC. @@ -564,10 +568,13 @@ static void BaseMap_Close(TFileStream * pStream) UnmapViewOfFile(pStream->Base.Map.pbFile); #endif -#if (defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU)) && !defined(PLATFORM_AMIGA) +#if (defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU)) && !defined(PLATFORM_AMIGA) && !defined(PLATFORM_SWITCH) //Todo(Amiga): Fix a proper solution for this if(pStream->Base.Map.pbFile != NULL) munmap(pStream->Base.Map.pbFile, (size_t )pStream->Base.Map.FileSize); +#elif defined(PLATFORM_SWITCH) + if(pStream->Base.Map.pbFile != NULL) + free(pStream->Base.Map.pbFile); #endif pStream->Base.Map.pbFile = NULL; diff --git a/3rdParty/StormLib/src/StormPort.h b/3rdParty/StormLib/src/StormPort.h index 8658dc82b71..809d952555f 100644 --- a/3rdParty/StormLib/src/StormPort.h +++ b/3rdParty/StormLib/src/StormPort.h @@ -151,6 +151,29 @@ #endif +#if !defined(PLATFORM_DEFINED) && defined(__SWITCH__) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #ifndef __BIG_ENDIAN__ + #define PLATFORM_LITTLE_ENDIAN + #endif + + #define PLATFORM_SWITCH + #define PLATFORM_DEFINED + +#endif //----------------------------------------------------------------------------- // Assumption: we are not on Windows nor Macintosh, so this must be linux *grin* @@ -254,7 +277,7 @@ #endif // !PLATFORM_WINDOWS // 64-bit calls are supplied by "normal" calls on Mac -#if defined(PLATFORM_MAC) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) #define stat64 stat #define fstat64 fstat #define lseek64 lseek @@ -264,7 +287,7 @@ #endif // Platform-specific error codes for UNIX-based platforms -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) #define ERROR_SUCCESS 0 #define ERROR_FILE_NOT_FOUND ENOENT #define ERROR_ACCESS_DENIED EPERM diff --git a/CMake/switch/FindLIBNX.cmake b/CMake/switch/FindLIBNX.cmake new file mode 100644 index 00000000000..4ea27295562 --- /dev/null +++ b/CMake/switch/FindLIBNX.cmake @@ -0,0 +1,44 @@ +# https://github.com/switchpy/libnx-template/blob/7037982c77e1767410143103d5963d0ddc77fb64/cmake/FindLIBNX.cmake + +# Tries to find libnx +# Once done, this will define: +# > LIBNX_FOUND - The system has libnx +# > LIBNX_INCLUDE_DIRS - The libnx include directories +# > LIBNX_LIBRARIES - The libnx libraries required for using it +# +# It also adds an imported target named `switch::libnx`. + +include(utils) # <- devilutionX patch + +if (NOT SWITCH) + cmake_panic("This helper can only be used if you are using the Switch toolchain file.") +endif () + +set(LIBNX_PATHS $ENV{LIBNX} libnx ${LIBNX} ${DEVKITPRO}/libnx) + +find_path(LIBNX_INCLUDE_DIR switch.h + PATHS ${LIBNX_PATHS} + PATH_SUFFIXES include) + +find_library(LIBNX_LIBRARY NAMES libnx.a + PATHS ${LIBNX_PATHS} + PATH_SUFFIXES lib) + +set(LIBNX_INCLUDE_DIRS ${LIBNX_INCLUDE_DIR}) +set(LIBNX_LIBRARIES ${LIBNX_LIBRARY}) + +# Handle the QUIETLY and REQUIRED arguments and set LIBNX_FOUND to TRUE if all above variables are TRUE. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LIBNX DEFAULT_MSG + LIBNX_INCLUDE_DIR LIBNX_LIBRARY) + +mark_as_advanced(LIBNX_INCLUDE_DIR LIBNX_LIBRARY) +if (LIBNX_FOUND) + set(LIBNX ${LIBNX_INCLUDE_DIR}/..) + cmake_info("Setting LIBNX to ${LIBNX}") + + add_library(switch::libnx STATIC IMPORTED GLOBAL) + set_target_properties(switch::libnx PROPERTIES + IMPORTED_LOCATION ${LIBNX_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES ${LIBNX_INCLUDE_DIR}) +endif () diff --git a/CMake/switch/devkita64-libnx.cmake b/CMake/switch/devkita64-libnx.cmake new file mode 100644 index 00000000000..93da9b3bf85 --- /dev/null +++ b/CMake/switch/devkita64-libnx.cmake @@ -0,0 +1,46 @@ +if(NOT DEFINED ENV{DEVKITPRO}) + message(FATAL_ERROR "Please set the DEVKITPRO env var to ") +endif() + +# devkitPro paths are broken on Windows. We need to use this macro to fix those. +# from https://github.com/switchpy/libnx-template/blob/7037982c77e1767410143103d5963d0ddc77fb64/devkita64-libnx.cmake +macro(msys_to_cmake_path msys_path resulting_path) + if (WIN32) + string(REGEX REPLACE "^/([a-zA-Z])/" "\\1:/" ${resulting_path} ${msys_path}) + else () + set(${resulting_path} ${msys_path}) + endif () +endmacro() +msys_to_cmake_path($ENV{DEVKITPRO} DEVKITPRO) + +# Default devkitpro cmake +include(${DEVKITPRO}/switch.cmake) + +# Set root paths: +set(DEVKITA64 ${DEVKITPRO}/devkitA64) +set(LIBNX ${DEVKITPRO}/libnx) +set(PORTLIBS_PATH ${DEVKITPRO}/portlibs) +set(PORTLIBS ${PORTLIBS_PATH}/switch) +set(CMAKE_FIND_ROOT_PATH ${DEVKITA64} ${LIBNX} ${PORTLIBS}) + +# Set absolute tool paths: +set(TOOLCHAIN_PREFIX ${DEVKITA64}/bin/aarch64-none-elf-) +if(WIN32) + set(TOOLCHAIN_SUFFIX ".exe") +else() + set(TOOLCHAIN_SUFFIX "") +endif() +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc${TOOLCHAIN_SUFFIX}) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++${TOOLCHAIN_SUFFIX}) +set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}as${TOOLCHAIN_SUFFIX}) +set(PKG_CONFIG_EXECUTABLE ${TOOLCHAIN_PREFIX}pkg-config${TOOLCHAIN_SUFFIX}) +set(CMAKE_AR ${TOOLCHAIN_PREFIX}gcc-ar${TOOLCHAIN_SUFFIX} CACHE STRING "") +set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}gcc-ranlib${TOOLCHAIN_SUFFIX} CACHE STRING "") +set(CMAKE_LD "/${TOOLCHAIN_PREFIX}ld${TOOLCHAIN_SUFFIX}" CACHE INTERNAL "") +set(CMAKE_OBJCOPY "${TOOLCHAIN_PREFIX}objcopy${TOOLCHAIN_SUFFIX}" CACHE INTERNAL "") +set(CMAKE_SIZE_UTIL "${TOOLCHAIN_PREFIX}size${TOOLCHAIN_SUFFIX}" CACHE INTERNAL "") + +set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available") + +set(SWITCH ON) +add_definitions(-D__SWITCH__ -DSWITCH) diff --git a/CMake/switch/nx-utils.cmake b/CMake/switch/nx-utils.cmake new file mode 100644 index 00000000000..d82754a02e6 --- /dev/null +++ b/CMake/switch/nx-utils.cmake @@ -0,0 +1,186 @@ +# https://github.com/vbe0201/libnx-template/blob/5283aabad32789675542ef98dad9c91838a3e729/cmake/nx-utils.cmake + +if (NOT SWITCH) + cmake_panic("These utils can only be used if you are using the Switch toolchain file.") +endif () + +############# +## ELF2NRO ## +############# +if (NOT ELF2NRO) + find_program(ELF2NRO elf2nro ${DEVKITPRO}/tools/bin) + if (ELF2NRO) + cmake_info("elf2nro: ${ELF2NRO} - found") + else () + cmake_warning("elf2nro - not found") + endif () +endif () + +############# +## ELF2KIP ## +############# +if (NOT ELF2KIP) + find_program(ELF2KIP elf2kip ${DEVKITPRO}/tools/bin) + if (ELF2KIP) + cmake_info("elf2kip: ${ELF2KIP} - found") + else () + cmake_warning("elf2kip - not found") + endif () +endif () + +############# +## ELF2NSO ## +############# +if (NOT ELF2NSO) + find_program(ELF2NSO elf2nso ${DEVKITPRO}/tools/bin) + if (ELF2NSO) + cmake_info("elf2nso: ${ELF2NSO} - found") + else () + cmake_warning("elf2nso - not found") + endif () +endif () + +############# +## BIN2S ## +############# +if (NOT BIN2S) + find_program(BIN2S bin2s ${DEVKITPRO}/tools/bin) + if (BIN2S) + cmake_info("bin2s: ${BIN2S} - found") + else () + cmake_warning("bin2s - not found") + endif () +endif () + +############# +## RAW2C ## +############# +if (NOT RAW2C) + find_program(RAW2C raw2c ${DEVKITPRO}/tools/bin) + if (RAW2C) + cmake_info("raw2c: ${RAW2C} - found") + else () + cmake_warning("raw2c - not found") + endif () +endif () + +################## +## BUILD_PFS0 ## +################## +if (NOT BUILD_PFS0) + find_program(BUILD_PFS0 build_pfs0 ${DEVKITPRO}/tools/bin) + if (BUILD_PFS0) + cmake_info("build_pfs0: ${BUILD_PFS0} - found") + else () + cmake_warning("build_pfs0 - not found") + endif () +endif () + +################ +## NACPTOOL ## +################ +if (NOT NACPTOOL) + find_program(NACPTOOL nacptool ${DEVKITPRO}/tools/bin) + if (NACPTOOL) + cmake_info("nacptool: ${NACPTOOL} - found") + else () + cmake_warning("nacptool - not found") + endif () +endif () + +macro(acquire_homebrew_icon target) + # This basically imitates the behavior of the Makefiles + # from the switchbrew/switch-examples repository. + if (EXISTS ${target}.jpg) + set(APP_ICON ${target}.jpg) + elseif (EXISTS ${PROJECT_SOURCE_DIR}/assets/icon.jpg) + set(APP_ICON ${PROJECT_SOURCE_DIR}/assets/icon.jpg) + elseif (LIBNX) + set(APP_ICON ${LIBNX}/default_icon.jpg) + else () + cmake_panic("No icon found, please provide one!") + endif () +endmacro() + +function(add_nso_target target) + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target}.nso + COMMAND ${ELF2NSO} ${CMAKE_CURRENT_BINARY_DIR}/${target}.elf ${CMAKE_CURRENT_BINARY_DIR}/${target}.nso + DEPENDS ${target}.elf + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + VERBATIM) + + if (CMAKE_RUNTIME_OUTPUT_DIRECTORY) + add_custom_target(${target}.nso ALL SOURCES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.nso) + else () + add_custom_target(${target}.nso ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${target}.nso) + endif () +endfunction() + +function(add_nacp target) + set(__NACP_COMMAND ${NACPTOOL} --create ${APP_TITLE} ${APP_AUTHOR} ${APP_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/${target}) + + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target} + COMMAND ${__NACP_COMMAND} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + VERBATIM) +endfunction() + +function(add_nro_target target) + set(__NRO_COMMAND + ${ELF2NRO} $ ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp --icon=${APP_ICON}) + + if (NOT ${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp) + add_nacp(${target}.nacp) + endif () + + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro + COMMAND ${__NRO_COMMAND} + DEPENDS ${target}.elf ${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp + VERBATIM) + + if (CMAKE_RUNTIME_OUTPUT_DIRECTORY) + add_custom_target(${target}.nro ALL SOURCES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.nro) + else () + add_custom_target(${target}.nro ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro) + endif () +endfunction() + +function(build_switch_binaries target) + get_filename_component(target_we ${target} NAME_WE) + + if (NOT APP_TITLE) + if (${ARGC} GREATER 1) + set(APP_TITLE ${ARGV1}) + else () + set(APP_TITLE ${target_we}) + endif () + endif () + + if (NOT APP_AUTHOR) + if (${ARGC} GREATER 2) + set(APP_AUTHOR ${ARGV2}) + else () + set(APP_AUTHOR "Unspecified Author") + endif () + endif () + + if (NOT APP_ICON) + if (${ARGC} GREATER 4) + set(APP_ICON ${ARGV4}) + else () + acquire_homebrew_icon(${target_we}) + endif () + endif () + + if (NOT APP_VERSION) + if (${ARGC} GREATER 3) + set(APP_VERSION ${ARGV3}) + else () + set(APP_VERSION "1.0.0") + endif () + endif () + + # Build the binaries + add_nso_target(${target_we}) + add_nro_target(${target_we}) +endfunction() diff --git a/CMake/switch/utils.cmake b/CMake/switch/utils.cmake new file mode 100644 index 00000000000..3042595864a --- /dev/null +++ b/CMake/switch/utils.cmake @@ -0,0 +1,17 @@ +# https://github.com/switchpy/libnx-template/blob/7037982c77e1767410143103d5963d0ddc77fb64/cmake/utils.cmake + +function(cmake_info message) + if (cmake_VERBOSE) + message("Build-Info: ${message}") + endif () +endfunction() + +function(cmake_warning message) + if (cmake_VERBOSE) + message(WARNING "${message}") + endif () +endfunction() + +function(cmake_panic message) + message(FATAL_ERROR "${message}") +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e0a57fefed..3deceadac4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.10) # CMP0083 NEW include(CMake/out_of_tree.cmake) @@ -9,6 +9,7 @@ endif() option(ASAN "Enable address sanitizer" ON) option(UBSAN "Enable undefined behaviour sanitizer" ON) option(DEBUG "Enable debug mode in engine" ON) +option(PIE "Generate position-independent code" OFF) option(LTO "Enable link-time optimization (if supported by the toolchain)" OFF) option(SPAWN "Build the shareware version" OFF) option(DIST "Dynamically link only glibc and SDL2" OFF) @@ -62,6 +63,15 @@ endif() list(APPEND CMAKE_MODULE_PATH "${DevilutionX_SOURCE_DIR}/CMake") +if(SWITCH) + set(ASAN OFF) + set(UBSAN OFF) + set(PIE ON) + + list(APPEND CMAKE_MODULE_PATH "${DevilutionX_SOURCE_DIR}/CMake/switch") + find_package(LIBNX REQUIRED) +endif() + if(${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD OR ${CMAKE_SYSTEM_NAME} STREQUAL OpenBSD) set(ASAN OFF) set(UBSAN OFF) @@ -83,6 +93,10 @@ if(DIST OR DINGUX) set(sodium_USE_STATIC_LIBS ON) endif() +if(PIE) + set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) +endif() + set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -265,18 +279,28 @@ if(NOT NONET) SourceX/dvlnet/udp_p2p.cpp) endif() -add_executable(devilutionx MACOSX_BUNDLE ${devilutionx_SRCS}) +set(BIN_TARGET devilutionx) + +if(SWITCH) + list(APPEND devilutionx_SRCS + SourceX/platform/switch/network.cpp + SourceX/platform/switch/keyboard.cpp + SourceX/platform/switch/docking.cpp) + set(BIN_TARGET devilutionx.elf) +endif() + +add_executable(${BIN_TARGET} MACOSX_BUNDLE ${devilutionx_SRCS}) configure_file(SourceS/config.h.in config.h @ONLY) target_include_directories(devilution PUBLIC Source SourceS ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(devilutionx PRIVATE +target_include_directories(${BIN_TARGET} PRIVATE SourceX 3rdParty/asio/include 3rdParty/Radon/Radon/include 3rdParty/libsmacker) target_link_libraries(devilution PUBLIC Threads::Threads) -target_link_libraries(devilutionx PRIVATE +target_link_libraries(${BIN_TARGET} PRIVATE devilution PKWare StormLib @@ -284,7 +308,7 @@ target_link_libraries(devilutionx PRIVATE Radon) if(NOT NONET) - target_link_libraries(devilutionx PRIVATE sodium) + target_link_libraries(${BIN_TARGET} PRIVATE sodium) endif() target_compile_definitions(devilution PRIVATE DEVILUTION_ENGINE) @@ -292,7 +316,7 @@ target_compile_definitions(devilution PUBLIC "$<$:_DEBUG>" # Skip fades and other fluff "$<$:FASTER>") -target_compile_definitions(devilutionx PRIVATE ASIO_STANDALONE) +target_compile_definitions(${BIN_TARGET} PRIVATE ASIO_STANDALONE) # Defines without value foreach( @@ -364,7 +388,7 @@ foreach( endif() endforeach(def_name) -foreach(target devilution devilutionx) +foreach(target devilution ${BIN_TARGET}) if(USE_SDL1) target_link_libraries(${target} PRIVATE ${SDL_LIBRARY} ${SDL_TTF_LIBRARY} ${SDL_MIXER_LIBRARY}) @@ -386,15 +410,20 @@ foreach(target devilution devilutionx) target_link_libraries(${target} PUBLIC -fsanitize=undefined) endif() + if(SWITCH) + target_link_libraries(${target} PRIVATE switch::libnx + -lfreetype -lvorbisfile -lvorbis -logg -lmodplug -lmpg123 -lSDL2 -lopusfile -lopus -lEGL -lglapi -ldrm_nouveau -lpng -lbz2 -lz -lnx) + endif() + target_compile_definitions(${target} PRIVATE ${def_list}) endforeach(target) if(DIST AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") - target_link_libraries(devilutionx PUBLIC -static-libgcc -static-libstdc++) + target_link_libraries(${BIN_TARGET} PUBLIC -static-libgcc -static-libstdc++) endif() if(WIN32) - target_link_libraries(devilutionx PRIVATE wsock32 ws2_32 wininet) + target_link_libraries(${BIN_TARGET} PRIVATE wsock32 ws2_32 wininet) if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") target_compile_options(devilution PUBLIC $<$:-gstabs>) @@ -407,7 +436,7 @@ if(NOT WIN32 AND NOT APPLE) endif() if(HAIKU) - target_link_libraries(devilutionx PRIVATE network) + target_link_libraries(${BIN_TARGET} PRIVATE network) endif() if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") @@ -424,11 +453,11 @@ if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") target_compile_options(devilution PRIVATE -fpermissive -w) # Warnings for devilutionX - target_compile_options(devilutionx PRIVATE -Wall -Wextra -Wno-write-strings -Wno-unused-parameter -Wno-missing-field-initializers -Wno-format-security) + target_compile_options(${BIN_TARGET} PRIVATE -Wall -Wextra -Wno-write-strings -Wno-unused-parameter -Wno-missing-field-initializers -Wno-format-security) # For ARM and other default unsigned char platforms target_compile_options(devilution PRIVATE -fsigned-char) - target_compile_options(devilutionx PRIVATE -fsigned-char) + target_compile_options(${BIN_TARGET} PRIVATE -fsigned-char) endif() if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") @@ -437,9 +466,9 @@ endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # Style issues - target_compile_options(devilutionx PRIVATE -Wno-parentheses -Wno-logical-op-parentheses -Wno-bitwise-op-parentheses) + target_compile_options(${BIN_TARGET} PRIVATE -Wno-parentheses -Wno-logical-op-parentheses -Wno-bitwise-op-parentheses) # Silence warnings about __int64 alignment hack not always being applicable - target_compile_options(devilutionx PRIVATE -Wno-ignored-attributes) + target_compile_options(${BIN_TARGET} PRIVATE -Wno-ignored-attributes) # Silence appfat.cpp warnings set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-narrowing") endif() @@ -454,10 +483,10 @@ if(APPLE) set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}) set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) set(MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${PROJECT_VERSION}") - set_target_properties(devilutionx PROPERTIES MACOSX_BUNDLE_ICON_FILE "AppIcon") - set_target_properties(devilutionx PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Packaging/macOS/Info.plist") + set_target_properties(${BIN_TARGET} PROPERTIES MACOSX_BUNDLE_ICON_FILE "AppIcon") + set_target_properties(${BIN_TARGET} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Packaging/macOS/Info.plist") - install (TARGETS devilutionx DESTINATION ./) + install (TARGETS ${BIN_TARGET} DESTINATION ./) if(DIST) install(CODE " @@ -475,6 +504,15 @@ if(APPLE) include(CPack) endif() +if(SWITCH) + set(APP_TITLE "DevilutionX") + set(APP_AUTHOR "Devilution Team") + set(APP_ICON "${PROJECT_SOURCE_DIR}/Packaging/switch/icon.jpg") + set(APP_VERSION ${PROJECT_VERSION}) + include(nx-utils) + build_switch_binaries(${BIN_TARGET}) +endif() + if(DINGUX) - set_target_properties(devilutionx PROPERTIES OUTPUT_NAME "devilutionx.dge") + set_target_properties(${BIN_TARGET} PROPERTIES OUTPUT_NAME "devilutionx.dge") endif() diff --git a/Packaging/OpenDingux/build.sh b/Packaging/OpenDingux/build.sh index 6c35b520a09..668abc62055 100755 --- a/Packaging/OpenDingux/build.sh +++ b/Packaging/OpenDingux/build.sh @@ -133,7 +133,7 @@ build() { fi cmake .. ${defs[@]} \ -DCMAKE_TOOLCHAIN_FILE="$BUILDROOT/output/host/usr/share/buildroot/toolchainfile.cmake" - make -j $(nproc) + make -j $(getconf _NPROCESSORS_ONLN) cd - } diff --git a/Packaging/switch/build.sh b/Packaging/switch/build.sh new file mode 100755 index 00000000000..c60fd1f17e3 --- /dev/null +++ b/Packaging/switch/build.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DEVKITPRO="${DEVKITPRO:-/opt/devkitpro}" + +declare -r DIR="$(dirname "${BASH_SOURCE[0]}")" +cd "$DIR" + +main() { + prepare_devkitpro + set -x + install_deps + build +} + +build() { + mkdir -p ../../build + cd ../../build + rm -f CMakeCache.txt + DEVKITPRO="$DEVKITPRO" cmake .. \ + -DJOY_BUTTON_DPAD_LEFT=16 \ + -DJOY_BUTTON_DPAD_UP=17 \ + -DJOY_BUTTON_DPAD_RIGHT=18 \ + -DJOY_BUTTON_DPAD_DOWN=19 \ + -DBINARY_RELEASE=ON \ + -DNONET=ON \ + -DPREFILL_PLAYER_NAME=ON \ + -DCMAKE_TOOLCHAIN_FILE=../CMake/switch/devkita64-libnx.cmake + DEVKITPRO="$DEVKITPRO" make -j "$(getconf _NPROCESSORS_ONLN)" + cd - +} + +package() { + ./package.sh ../../build/devilutionx-opendingux-musl-sdl1.ipk +} + +install_deps() { + "$DEVKITPRO/pacman/bin/pacman" -S --needed --noconfirm --quiet \ + switch-freetype switch-mesa switch-glad switch-glm switch-sdl2 \ + switch-sdl2_ttf switch-sdl2_mixer switch-libvorbis switch-libmikmod switch-libsodium \ + libnx devkitA64 devkitA64 general-tools switch-tools devkitpro-pkgbuild-helpers +} + +prepare_devkitpro() { + if [[ -d $DEVKITPRO ]]; then + return; + fi + if which dpkg > /dev/null; then + install_devkitpro_debian + else + >&2 printf "Please set DEVKITPRO:\nhttps://devkitpro.org/wiki/Getting_Started\n" + exit 1 + fi +} + +install_devkitpro_debian() { + >&2 echo 'Installing devkitpro-pacman.deb from GitHub...' + local -r dpkg_path=/tmp/devkitpro-pacman.deb + set -x + \curl -L https://github.com/devkitPro/pacman/releases/download/devkitpro-pacman-1.0.1/devkitpro-pacman.deb -o "$dpkg_path" + sudo dpkg -i "$dpkg_path" + rm "$dpkg_path" + { set +x; } 2>/dev/null +} + +main diff --git a/Packaging/switch/icon.jpg b/Packaging/switch/icon.jpg new file mode 100644 index 00000000000..c869f3a3148 Binary files /dev/null and b/Packaging/switch/icon.jpg differ diff --git a/Packaging/switch/readme-switch.md b/Packaging/switch/readme-switch.md new file mode 100644 index 00000000000..aef262c067e --- /dev/null +++ b/Packaging/switch/readme-switch.md @@ -0,0 +1,41 @@ +# Nintendo Switch Port of DevilutionX (Diablo) + +### How To Play: +- Put `devilutionx.nro` in into `/switch/devilutionx` +- Copy `DIABDAT.MPQ` from the original Diablo game disc or GOG version into `/switch/devilutionx` +- Launch `devilutionx.nro` (do not use album to launch, see the note below) +- *Note:* Hold R on any installed game and launch it. Do not use album to launch. If you use album, the homebrew only has very little memory available, and the touch keyboard doesn't work. This is true for all homebrew, not just Diablo-NX. + +### Joycon Controls + +- Left analog : move hero +- Right analog : simulate mouse +- A : attack nearby enemies, talk to towns people and merchants, pickup & drop items in inventory, OK while in main menu +- X : pickup gold, potions & equipment from ground, open chests and doors that are nearby, use item when in inventory (useful to read books etc.) +- Y : cast spell, go to previous screen when talking to people and in shops, delete character while in main menu +- B : Select spell, cancel while in main menu +- R : inventory +- L : character +- ZR : drink mana potion +- ZL : drink health potion +- Minus + Y : quest log +- Right analog click : left mouse click +- Left analog click : automap +- Plus : game Menu, skip intro + +### Touch Controls + +- Single finger drag : move the mouse pointer (pointer jumps to finger) +- Single short tap : left mouse click +- Single short tap while holding a second finger down : right mouse click +- Dual finger drag : drag'n'drop (left mouse button is held down) +- Three finger drag : drag'n'drop (right mouse button is held down) + +### Credits + +- Initial Switch Port by [MVG](https://github.com/lantus) in 2019 +- Controller code by [Jacob Fliss](https://github.com/erfg12) +- Control improvements and bug fixes by [rsn8887](https://github.com/rsn8887) in 2019 +- [AJenbo](https://github.com/AJenbo) for upstreaming Switch code and many code fixes + +And a special thanks to all the support and people who work on Devilution to make it possible! <3 diff --git a/README.md b/README.md index 6c0bb333c54..ad6572ff755 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,21 @@ Make sure to install the `C++ CMake tools for Windows` component for Visual Stud 7. Use build/debug etc. commands inside Visual Studio Solution like with any normal Visual Studio project. +
Nintendo Switch +Run: +``` +Packaging/switch/build.sh +``` + +This will install the [Switch devkit](https://switchbrew.org/wiki/Setting_up_Development_Environment) and build a DevilutionX Switch package. If you already have the devkit installed, or are on a non-Debian system, pass the the devkit path to the script like this: + +``` +DEVKITPRO= Packaging/switch/build.sh +``` + +- .nro lives in release. Test with an emulator (RyuJinx) or real hardware. +
+
Haiku ### Installing dependencies on 32 bit Haiku diff --git a/Source/init.cpp b/Source/init.cpp index 93eb36e9ec0..e626658d65b 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -94,7 +94,11 @@ HANDLE init_test_access(char *mpq_path, char *mpq_name, char *reg_loc, int dwPri for (int i = 0; i < 2; i++) { snprintf(mpq_path, MAX_PATH, "%s%s", Buffer[i], mpq_name); +#ifndef __SWITCH__ if (SFileOpenArchive(mpq_path, dwPriority, MPQ_FLAG_READ_ONLY, &archive)) { +#else + if (SFileOpenArchive(mpq_path, dwPriority, 0, &archive)) { +#endif SFileSetBasePath(Buffer[i]); return archive; diff --git a/SourceX/DiabloUI/diabloui.cpp b/SourceX/DiabloUI/diabloui.cpp index f53649f416e..e6d72c3483c 100644 --- a/SourceX/DiabloUI/diabloui.cpp +++ b/SourceX/DiabloUI/diabloui.cpp @@ -16,6 +16,11 @@ #include "DiabloUI/button.h" #include "DiabloUI/dialogs.h" +#ifdef __SWITCH__ +// for virtual keyboard on Switch +#include "platform/switch/keyboard.h" +#endif + namespace dvl { int SelectedItemMin = 1; @@ -80,6 +85,9 @@ void UiInitList(int min, int max, void (*fnFocus)(int value), void (*fnSelect)(i SDL_StopTextInput(); // input is enabled by default for (int i = 0; i < itemCnt; i++) { if (items[i].type == UI_EDIT) { +#ifdef __SWITCH__ + switch_start_text_input(items[i - 1].art_text.text, items[i].edit.value, /*multiline=*/0); +#endif SDL_StartTextInput(); UiTextInput = items[i].edit.value; UiTextInputLen = items[i].edit.max_length; diff --git a/SourceX/controls/devices/joystick.cpp b/SourceX/controls/devices/joystick.cpp index 454b303f531..be6949d586e 100644 --- a/SourceX/controls/devices/joystick.cpp +++ b/SourceX/controls/devices/joystick.cpp @@ -4,13 +4,6 @@ #include "controls/controller_motion.h" #include "stubs.h" -#ifdef SWITCH -#define JOY_BUTTON_DPAD_LEFT 16 -#define JOY_BUTTON_DPAD_UP 17 -#define JOY_BUTTON_DPAD_RIGHT 18 -#define JOY_BUTTON_DPAD_DOWN 19 -#endif - namespace dvl { ControllerButton JoyButtonToControllerButton(const SDL_Event &event) diff --git a/SourceX/main.cpp b/SourceX/main.cpp index 10893eb8901..bed3cdbb3ac 100644 --- a/SourceX/main.cpp +++ b/SourceX/main.cpp @@ -1,5 +1,8 @@ #include #include +#ifdef __SWITCH__ +#include "platform/switch/network.h" +#endif #include "devilution.h" @@ -25,5 +28,10 @@ static std::string build_cmdline(int argc, char **argv) int main(int argc, char **argv) { auto cmdline = build_cmdline(argc, argv); + +#ifdef __SWITCH__ + switch_enable_network(); +#endif + return dvl::WinMain(NULL, NULL, (char *)cmdline.c_str(), 0); } diff --git a/SourceX/miniwin/misc_msg.cpp b/SourceX/miniwin/misc_msg.cpp index d391f9c5cb2..4721457730a 100644 --- a/SourceX/miniwin/misc_msg.cpp +++ b/SourceX/miniwin/misc_msg.cpp @@ -11,6 +11,11 @@ #include "controls/touch.h" #include "miniwin/ddraw.h" +#ifdef __SWITCH__ +#include "platform/switch/docking.h" +#include +#endif + /** @file * * * Windows message handling and keyboard event conversion for SDL. @@ -322,6 +327,10 @@ void BlurInventory() WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) { +#ifdef __SWITCH__ + HandleDocking(); +#endif + if (wMsgFilterMin != 0) UNIMPLEMENTED(); if (wMsgFilterMax != 0) diff --git a/SourceX/platform/switch/docking.cpp b/SourceX/platform/switch/docking.cpp new file mode 100644 index 00000000000..631c6574309 --- /dev/null +++ b/SourceX/platform/switch/docking.cpp @@ -0,0 +1,49 @@ +#include +#include +#include "miniwin/ddraw.h" +#include "platform/switch/docking.h" + +namespace dvl { + +static int currently_docked = -1; // keep track of docked or handheld mode + +/** + * @brief Do a manual window resize when docking/undocking the Switch + */ +void HandleDocking() +{ + int docked; + switch (appletGetOperationMode()) { + case AppletOperationMode_Handheld: + docked = 0; + break; + case AppletOperationMode_Docked: + docked = 1; + break; + default: + docked = 0; + } + + int display_width; + int display_height; + if ((currently_docked == -1) || (docked && !currently_docked) || (!docked && currently_docked)) { + // docked mode has changed, update window size + if (docked) { + display_width = 1920; + display_height = 1080; + currently_docked = 1; + } else { + display_width = 1280; + display_height = 720; + currently_docked = 0; + } + // remove leftover-garbage on screen + for (int i = 0; i < 3; i++) { + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + } + SDL_SetWindowSize(window, display_width, display_height); + } +} + +} // namespace dvl diff --git a/SourceX/platform/switch/docking.h b/SourceX/platform/switch/docking.h new file mode 100644 index 00000000000..5a81a1e242a --- /dev/null +++ b/SourceX/platform/switch/docking.h @@ -0,0 +1,7 @@ +#pragma once + +namespace dvl { + +void HandleDocking(); + +} // namespace dvl diff --git a/SourceX/platform/switch/keyboard.cpp b/SourceX/platform/switch/keyboard.cpp new file mode 100644 index 00000000000..e4e33e0ca55 --- /dev/null +++ b/SourceX/platform/switch/keyboard.cpp @@ -0,0 +1,79 @@ +#include +#include + +#include +#include +#include "platform/switch/keyboard.h" + +static void switch_keyboard_get(const char *guide_text, char *initial_text, int max_len, int multiline, char *buf) +{ + Result rc = 0; + + SwkbdConfig kbd; + + rc = swkbdCreate(&kbd, 0); + + if (R_SUCCEEDED(rc)) { + swkbdConfigMakePresetDefault(&kbd); + swkbdConfigSetGuideText(&kbd, guide_text); + swkbdConfigSetInitialText(&kbd, initial_text); + swkbdConfigSetStringLenMax(&kbd, max_len); + rc = swkbdShow(&kbd, buf, max_len); + swkbdClose(&kbd); + } +} + +static int get_utf8_character_bytes(const uint8_t *uc) +{ + if (uc[0] < 0x80) { + return 1; + } else if ((uc[0] & 0xe0) == 0xc0 && (uc[1] & 0xc0) == 0x80) { + return 2; + } else if ((uc[0] & 0xf0) == 0xe0 && (uc[1] & 0xc0) == 0x80 && (uc[2] & 0xc0) == 0x80) { + return 3; + } else if ((uc[0] & 0xf8) == 0xf0 && (uc[1] & 0xc0) == 0x80 && (uc[2] & 0xc0) == 0x80 && (uc[3] & 0xc0) == 0x80) { + return 4; + } else { + return 1; + } +} + +static void switch_create_and_push_sdlkey_event(uint32_t event_type, SDL_Scancode scan, SDL_Keycode key) +{ + SDL_Event event; + event.type = event_type; + event.key.keysym.scancode = scan; + event.key.keysym.sym = key; + event.key.keysym.mod = 0; + SDL_PushEvent(&event); +} + +void switch_start_text_input(const char *guide_text, char *initial_text, int multiline) +{ + char text[65] = {'\0'}; + switch_keyboard_get(guide_text, initial_text, 64, multiline, text); + for (int i = 0; i < 600; i++) { + switch_create_and_push_sdlkey_event(SDL_KEYDOWN, SDL_SCANCODE_BACKSPACE, SDLK_BACKSPACE); + switch_create_and_push_sdlkey_event(SDL_KEYUP, SDL_SCANCODE_BACKSPACE, SDLK_BACKSPACE); + } + for (int i = 0; i < 600; i++) { + switch_create_and_push_sdlkey_event(SDL_KEYDOWN, SDL_SCANCODE_DELETE, SDLK_DELETE); + switch_create_and_push_sdlkey_event(SDL_KEYUP, SDL_SCANCODE_DELETE, SDLK_DELETE); + } + if (text[0] == '\0') { + strncpy(text, initial_text, 63); + text[64] = {'\0'}; + } + const uint8_t *utf8_text = (uint8_t*) text; + for (int i = 0; i < 599 && utf8_text[i];) { + int bytes_in_char = get_utf8_character_bytes(&utf8_text[i]); + SDL_Event textinput_event; + textinput_event.type = SDL_TEXTINPUT; + for (int n = 0; n < bytes_in_char; n++) { + textinput_event.text.text[n] = text[i + n]; + } + textinput_event.text.text[bytes_in_char] = 0; + SDL_PushEvent(&textinput_event); + i += bytes_in_char; + } +} diff --git a/SourceX/platform/switch/keyboard.h b/SourceX/platform/switch/keyboard.h new file mode 100644 index 00000000000..5ab73791377 --- /dev/null +++ b/SourceX/platform/switch/keyboard.h @@ -0,0 +1,3 @@ +#pragma once + +void switch_start_text_input(const char *guide_text, char *initial_text, int multiline); diff --git a/SourceX/platform/switch/network.cpp b/SourceX/platform/switch/network.cpp new file mode 100644 index 00000000000..e9520014b01 --- /dev/null +++ b/SourceX/platform/switch/network.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include "platform/switch/network.h" + +static int nxlink_sock = -1; // for stdio on Switch + +void switch_enable_network() +{ + // enable network and stdio on Switch + socketInitializeDefault(); + // enable error messages via nxlink on Switch + nxlink_sock = nxlinkStdio(); + atexit(switch_disable_network); +} + +void switch_disable_network() +{ + // disable network and stdio on Switch + if (nxlink_sock != -1) + close(nxlink_sock); + socketExit(); +} diff --git a/SourceX/platform/switch/network.h b/SourceX/platform/switch/network.h new file mode 100644 index 00000000000..7b849eee86a --- /dev/null +++ b/SourceX/platform/switch/network.h @@ -0,0 +1,4 @@ +#pragma once + +void switch_enable_network(); +void switch_disable_network();