diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4f03623..50b8cea2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,22 @@ jobs: path: ./git-artifcats/Dusk if-no-files-found: error + build-vita: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Docker + uses: docker/setup-docker-action@v5 + - name: Build Vita + run: ./scripts/build-vita-docker.sh + - name: Upload Vita binary + uses: actions/upload-artifact@v6 + with: + name: dusk-vita + path: build-vita/Dusk.vpk + if-no-files-found: error + build-knulli: runs-on: ubuntu-latest steps: diff --git a/cmake/targets/vita.cmake b/cmake/targets/vita.cmake new file mode 100644 index 00000000..53f1d017 --- /dev/null +++ b/cmake/targets/vita.cmake @@ -0,0 +1,110 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +if(NOT DEFINED ENV{VITASDK}) + message(FATAL_ERROR "VITASDK environment variable is not set.") +endif() + +include("$ENV{VITASDK}/share/vita.cmake" REQUIRED) + +set(VITA_APP_NAME "Dusk") +set(VITA_TITLEID "DUSK00001") +set(VITA_VERSION "01.00") + +find_package(SDL2 REQUIRED) + +# Custom flags for cglm +set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE) +set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE) +find_package(cglm REQUIRED) + +# Compile lua +include(FetchContent) +FetchContent_Declare( + liblua + URL https://www.lua.org/ftp/lua-5.5.0.tar.gz +) +FetchContent_MakeAvailable(liblua) +set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src") +set(LUA_C_FILES + lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c + ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c + loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c + lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c +) +list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/") +add_library(liblua STATIC ${LUA_C_FILES}) +target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}") +target_compile_definitions(liblua PRIVATE LUA_USE_C89) +add_library(lua::lua ALIAS liblua) +set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE) + +# Link libraries +target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + ${SDL2_LIBRARIES} + liblua + cglm + SDL2 + SDL2main + zip + bz2 + z + zstd + crypto + lzma + m + pthread + stdc++ + vitaGL + mathneon + vitashark + kubridge_stub + SceAppMgr_stub + SceAudio_stub + SceCtrl_stub + SceCommonDialog_stub + SceDisplay_stub + SceKernelDmacMgr_stub + SceGxm_stub + SceShaccCg_stub + SceSysmodule_stub + ScePower_stub + SceTouch_stub + SceVshBridge_stub + SceIofilemgr_stub + SceShaccCgExt + libtaihen_stub.a + + + # SceKernel_stub + SceAppUtil_stub + SceHid_stub + SceRtc_stub +) + +target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE + ${SDL2_INCLUDE_DIRS} +) + +target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + DUSK_SDL2 + DUSK_OPENGL + DUSK_VITA + DUSK_INPUT_GAMEPAD + DUSK_PLATFORM_ENDIAN_LITTLE + DUSK_OPENGL_LEGACY + DUSK_DISPLAY_WIDTH=960 + DUSK_DISPLAY_HEIGHT=544 +) + +# Post-build: create SELF from the ELF binary (UNSAFE = homebrew, no signing) +vita_create_self(${DUSK_BINARY_TARGET_NAME}.self ${DUSK_BINARY_TARGET_NAME} UNSAFE) + +# Post-build: package SELF + assets into a .vpk installable on the Vita +vita_create_vpk(${DUSK_BINARY_TARGET_NAME}.vpk ${VITA_TITLEID} ${DUSK_BINARY_TARGET_NAME}.self + VERSION ${VITA_VERSION} + NAME ${VITA_APP_NAME} + FILE ${DUSK_ASSETS_ZIP} dusk.dsk +) diff --git a/docker/vita/Dockerfile b/docker/vita/Dockerfile new file mode 100644 index 00000000..cb0e6a93 --- /dev/null +++ b/docker/vita/Dockerfile @@ -0,0 +1,13 @@ +FROM vitasdk/vitasdk:latest +WORKDIR /workdir + +# Install vitaGL and its dependencies (vitashark, SceShaccCg) via vdpm +RUN which vdpm + +# Install Python (needed for Dusk code generation tools) +RUN apk add --no-cache \ + python3 \ + py3-pip \ + py3-dotenv + +VOLUME ["/workdir"] diff --git a/scripts/build-vita-docker.sh b/scripts/build-vita-docker.sh new file mode 100755 index 00000000..e7719668 --- /dev/null +++ b/scripts/build-vita-docker.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker build -t dusk-vita -f docker/vita/Dockerfile . +docker run --rm -v $(pwd):/workdir dusk-vita /bin/bash -c "./scripts/build-vita.sh" diff --git a/scripts/build-vita.sh b/scripts/build-vita.sh new file mode 100755 index 00000000..d6a3813d --- /dev/null +++ b/scripts/build-vita.sh @@ -0,0 +1,13 @@ +#!/bin/bash +if [ -z "$VITASDK" ]; then + echo "VITASDK environment variable is not set. Please set it to the path of your VitaSDK installation." + exit 1 +fi + +mkdir -p build-vita +cd build-vita +cmake \ + -DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake \ + -DDUSK_TARGET_SYSTEM=vita \ + .. +make -j$(nproc) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f29a2216..5f036e67 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,11 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "psp") add_subdirectory(dusksdl2) add_subdirectory(duskgl) +elseif(DUSK_TARGET_SYSTEM STREQUAL "vita") + add_subdirectory(duskvita) + add_subdirectory(dusksdl2) + add_subdirectory(duskgl) + elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii") add_subdirectory(duskdolphin) diff --git a/src/duskgl/duskgl.h b/src/duskgl/duskgl.h index 707a68ca..08f3f700 100644 --- a/src/duskgl/duskgl.h +++ b/src/duskgl/duskgl.h @@ -19,7 +19,12 @@ #define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0 #define glClearDepth(depth) glClearDepthf(depth) #else - #define GL_GLEXT_PROTOTYPES - #include - #include + // For some platforms (Vita) we do not include GL extensions. + #ifndef GL_GLEXT_PROTOTYPES + #include + #define GL_GLEXT_PROTOTYPES + #include + #else + #include + #endif #endif \ No newline at end of file diff --git a/src/duskvita/CMakeLists.txt b/src/duskvita/CMakeLists.txt new file mode 100644 index 00000000..ab5edee9 --- /dev/null +++ b/src/duskvita/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Includes +target_include_directories(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +# Sources +target_sources(${DUSK_BINARY_TARGET_NAME} + PUBLIC +) + +# Subdirs +add_subdirectory(asset) +add_subdirectory(input) +add_subdirectory(log) diff --git a/src/duskvita/asset/CMakeLists.txt b/src/duskvita/asset/CMakeLists.txt new file mode 100644 index 00000000..d3531331 --- /dev/null +++ b/src/duskvita/asset/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + assetvita.c +) diff --git a/src/duskvita/asset/assetplatform.h b/src/duskvita/asset/assetplatform.h new file mode 100644 index 00000000..725e5122 --- /dev/null +++ b/src/duskvita/asset/assetplatform.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "assetvita.h" + +typedef assetvita_t assetplatform_t; +#define assetInitPlatform assetInitVita +#define assetDisposePlatform assetDisposeVita diff --git a/src/duskvita/asset/assetvita.c b/src/duskvita/asset/assetvita.c new file mode 100644 index 00000000..667b2445 --- /dev/null +++ b/src/duskvita/asset/assetvita.c @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "asset/asset.h" +#include "assert/assert.h" + +errorret_t assetInitVita(void) { + int32_t error; + ASSET.zip = zip_open(ASSET_VITA_DSK_PATH, ZIP_RDONLY, &error); + if(ASSET.zip == NULL) { + errorThrow("Failed to open asset file: " ASSET_VITA_DSK_PATH); + } + errorOk(); +} + +errorret_t assetDisposeVita(void) { + errorOk(); +} diff --git a/src/duskvita/asset/assetvita.h b/src/duskvita/asset/assetvita.h new file mode 100644 index 00000000..82d2bbbf --- /dev/null +++ b/src/duskvita/asset/assetvita.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +// dusk.dsk is packaged at the root of the VPK and accessible via app0:/ +#define ASSET_VITA_DSK_PATH "app0:/" ASSET_FILE_NAME + +typedef struct { + uint8_t _unused; +} assetvita_t; + +/** + * Initializes the Vita asset system, opening dusk.dsk from the VPK mount. + * + * @returns An errorret_t indicating success or failure. + */ +errorret_t assetInitVita(void); + +/** + * Disposes the Vita asset system. + * + * @returns An errorret_t indicating success or failure. + */ +errorret_t assetDisposeVita(void); diff --git a/src/duskvita/display/displayplatform.h b/src/duskvita/display/displayplatform.h new file mode 100644 index 00000000..94ecc21f --- /dev/null +++ b/src/duskvita/display/displayplatform.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/displaysdl2.h" + +typedef displaysdl2_t displayplatform_t; + +#define displayPlatformInit displaySDL2Init +#define displayPlatformUpdate displaySDL2Update +#define displayPlatformSwap displaySDL2Swap +#define displayPlatformDispose displaySDL2Dispose diff --git a/src/duskvita/duskplatform.h b/src/duskvita/duskplatform.h new file mode 100644 index 00000000..865f3ee2 --- /dev/null +++ b/src/duskvita/duskplatform.h @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include + +#define GL_COLOR_INDEX8_EXT 0x80E5 + +#define GL_NO_ERROR 0 + +#define glDrawArrays(type, first, count) ((void)0) +#define glColorTableEXT(target, internalformat, count, format, type, pixels) ((void)0) +#define glDepthFunc(func) ((void)0) +#define glBlendFunc(sfactor, dfactor) ((void)0) +#define glGetError() GL_NO_ERROR + +typedef int GLbitfield; + +#define GL_GLEXT_PROTOTYPES + +#include "dusksdl2.h" \ No newline at end of file diff --git a/src/duskvita/input/CMakeLists.txt b/src/duskvita/input/CMakeLists.txt new file mode 100644 index 00000000..8c00669a --- /dev/null +++ b/src/duskvita/input/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + inputvita.c +) diff --git a/src/duskvita/input/inputvita.c b/src/duskvita/input/inputvita.c new file mode 100644 index 00000000..87ea3610 --- /dev/null +++ b/src/duskvita/input/inputvita.c @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "input/input.h" + +inputbuttondata_t INPUT_BUTTON_DATA[] = { + { .name = "triangle", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_Y } }, + { .name = "cross", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_A } }, + { .name = "circle", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_B } }, + { .name = "square", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_X } }, + { .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_START } }, + { .name = "select", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_BACK } }, + { .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_UP } }, + { .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_DOWN } }, + { .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_LEFT } }, + { .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_DPAD_RIGHT } }, + { .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_LEFTSHOULDER } }, + { .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER } }, + + { .name = "lstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTY, .positive = true } } }, + { .name = "lstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTY, .positive = false } } }, + { .name = "lstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTX, .positive = true } } }, + { .name = "lstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTX, .positive = false } } }, + + { .name = "rstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_RIGHTY, .positive = true } } }, + { .name = "rstick_up", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_RIGHTY, .positive = false } } }, + { .name = "rstick_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_RIGHTX, .positive = true } } }, + { .name = "rstick_left", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_RIGHTX, .positive = false } } }, + + { .name = NULL } +}; + +float_t inputGetDeadzoneSDL2(const inputbutton_t button) { + return 0.17f; +} diff --git a/src/duskvita/log/CMakeLists.txt b/src/duskvita/log/CMakeLists.txt new file mode 100644 index 00000000..9c72f882 --- /dev/null +++ b/src/duskvita/log/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + log.c +) diff --git a/src/duskvita/log/log.c b/src/duskvita/log/log.c new file mode 100644 index 00000000..90d888d0 --- /dev/null +++ b/src/duskvita/log/log.c @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "log/log.h" + +void logDebug(const char_t *message, ...) { + va_list args; + va_start(args, message); + + // print to stdout + va_list copy; + va_copy(copy, args); + vprintf(message, copy); + va_end(copy); + + // print to file + FILE *file = fopen("ux0:data/Dusk/debug.log", "a"); + if(file) { + va_copy(copy, args); + vfprintf(file, message, copy); + va_end(copy); + fclose(file); + } + + va_end(args); +} + +void logError(const char_t *message, ...) { + va_list args; + va_start(args, message); + + // print to stderr + va_list copy; + va_copy(copy, args); + vfprintf(stderr, message, copy); + va_end(copy); + + // print to file + FILE *file = fopen("ux0:data/Dusk/error.log", "a"); + if(file) { + va_copy(copy, args); + vfprintf(file, message, copy); + va_end(copy); + fclose(file); + } + + va_end(args); +} diff --git a/src/duskvita/script/module/moduleplatformplatform.h b/src/duskvita/script/module/moduleplatformplatform.h new file mode 100644 index 00000000..b4c40956 --- /dev/null +++ b/src/duskvita/script/module/moduleplatformplatform.h @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "moduleplatformvita.h" + +#define modulePlatformPlatform modulePlatformVita diff --git a/src/duskvita/script/module/moduleplatformvita.h b/src/duskvita/script/module/moduleplatformvita.h new file mode 100644 index 00000000..3f4f4643 --- /dev/null +++ b/src/duskvita/script/module/moduleplatformvita.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +void modulePlatformVita(scriptcontext_t *ctx) { + scriptContextExec(ctx, "VITA = true\n"); +}