22 Commits

Author SHA1 Message Date
YourWishes 9c71df5bfd mbed first pass 2026-04-19 16:22:00 -05:00
YourWishes 4205899f5a No idea why gamecube is crashing, disabling this for now 2026-04-18 21:57:57 -05:00
YourWishes 7dd3940770 Moved code to dolphin for network 2026-04-18 17:41:30 -05:00
YourWishes 00d94e3015 Slight wii improvements 2026-04-18 16:01:53 -05:00
YourWishes 7bacb3ee2b Testing on real wii hardware some more 2026-04-18 15:59:25 -05:00
YourWishes 8e49be5ac4 Testing some wii rendering bugs 2026-04-18 15:29:40 -05:00
YourWishes 3b94598d2c Fixed dolphin matricies the ugly way 2026-04-18 00:36:35 -05:00
YourWishes bddc9af3b6 "Improved" Dolphin matricies slightly 2026-04-18 00:32:50 -05:00
YourWishes 2451d73a7c Improved Wii aspect ratio significantly 2026-04-17 23:49:39 -05:00
YourWishes 1dd2efa182 Dolphin compiles, network untested 2026-04-17 22:53:49 -05:00
YourWishes acea610773 Disable curl on linux 2026-04-17 22:53:29 -05:00
YourWishes 8f2f1fd496 Added network info 2026-04-17 17:00:03 -05:00
YourWishes 39c775872a PSP networking matches linux now. 2026-04-17 16:32:45 -05:00
YourWishes bdb3cbd109 Fixed crash for cross/cancel logic 2026-04-17 16:02:45 -05:00
YourWishes ff84ce2b04 Added PSP Accept/Cance 2026-04-17 15:28:03 -05:00
YourWishes 225f405592 PSP Networking refactor 2026-04-17 14:57:10 -05:00
YourWishes 715ecffa18 Taking a break on net 2026-04-16 06:38:56 -05:00
YourWishes e51cdc8992 PSP net code first pass 2026-04-15 15:50:43 -05:00
YourWishes 133685ea37 Linux HTTP implementation 2026-04-15 15:11:44 -05:00
YourWishes 6aff98d555 Fix PSP build 2026-04-15 06:10:38 -05:00
YourWishes acdc524284 bit more accurate 2026-04-15 06:04:30 -05:00
YourWishes 1ee5ec7b43 Vita builds for the first time 2026-04-15 05:52:30 -05:00
100 changed files with 4612 additions and 85 deletions
+16
View File
@@ -53,6 +53,22 @@ jobs:
path: ./git-artifcats/Dusk path: ./git-artifcats/Dusk
if-no-files-found: error 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: build-knulli:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
+2 -2
View File
@@ -9,8 +9,8 @@ if PSP then
inputBind("down", INPUT_ACTION_DOWN) inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT) inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT) inputBind("right", INPUT_ACTION_RIGHT)
inputBind("circle", INPUT_ACTION_CANCEL) inputBind("accept", INPUT_ACTION_ACCEPT)
inputBind("cross", INPUT_ACTION_ACCEPT) inputBind("cancel", INPUT_ACTION_CANCEL)
inputBind("select", INPUT_ACTION_RAGEQUIT) inputBind("select", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP) inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN) inputBind("lstick_down", INPUT_ACTION_DOWN)
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* Wii/Dolphin overrides for mbedtls 4.x.
* Used as both MBEDTLS_USER_CONFIG_FILE and TF_PSA_CRYPTO_USER_CONFIG_FILE,
* so intentionally has no include guard (operations are idempotent).
*/
/* Disable the mbedtls TCP/socket layer — Wii has no Unix/Windows sockets.
* We provide our own socket implementation via networksocket.c. */
#undef MBEDTLS_NET_C
/* Disable built-in Unix/Windows entropy; use driver-provided entropy instead.
* We implement mbedtls_platform_get_entropy() in networktls.c. */
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
/* Disable the built-in ms_time; we implement mbedtls_ms_time() via OGC
* timer (gettime / ticks_to_millisecs) in networktls.c. */
#define MBEDTLS_PLATFORM_MS_TIME_ALT
+201
View File
@@ -0,0 +1,201 @@
# Findmbedtls.cmake
#
# Usage:
# find_package(mbedtls REQUIRED)
#
# Optional cache variables the parent project may set before calling:
# MBEDTLS_FETCHCONTENT_VERSION e.g. "v3.6.4" or "mbedtls-4.1.0"
# MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
# MBEDTLS_FETCHCONTENT_GIT_TAG
# MBEDTLS_FETCHCONTENT_BASE_DIR
# MBEDTLS_BUILD_SHARED ON/OFF
#
# Provided variables:
# mbedtls_FOUND
# MBEDTLS_FOUND
# MBEDTLS_INCLUDE_DIRS
# MBEDTLS_LIBRARIES
#
# Provided imported targets:
# MbedTLS::mbedtls
# MbedTLS::mbedx509
# MbedTLS::mbedcrypto
include_guard(GLOBAL)
include(FetchContent)
include(FindPackageHandleStandardArgs)
set(_MBEDTLS_DEFAULT_REPOSITORY "https://github.com/Mbed-TLS/mbedtls.git")
set(_MBEDTLS_DEFAULT_TAG "v4.1.0")
set(MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
"${_MBEDTLS_DEFAULT_REPOSITORY}"
CACHE STRING "Git repository for fetching Mbed TLS")
if(DEFINED MBEDTLS_FETCHCONTENT_VERSION AND NOT DEFINED MBEDTLS_FETCHCONTENT_GIT_TAG)
set(MBEDTLS_FETCHCONTENT_GIT_TAG
"${MBEDTLS_FETCHCONTENT_VERSION}"
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
endif()
set(MBEDTLS_FETCHCONTENT_GIT_TAG
"${MBEDTLS_FETCHCONTENT_GIT_TAG}"
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
if(NOT MBEDTLS_FETCHCONTENT_GIT_TAG)
set(MBEDTLS_FETCHCONTENT_GIT_TAG "${_MBEDTLS_DEFAULT_TAG}" CACHE STRING "" FORCE)
endif()
option(MBEDTLS_BUILD_SHARED "Build Mbed TLS shared libraries" OFF)
# 1) Prefer an installed package config if available.
find_package(MbedTLS CONFIG QUIET)
if(TARGET MbedTLS::mbedtls AND TARGET MbedTLS::mbedx509 AND TARGET MbedTLS::mbedcrypto)
set(mbedtls_FOUND TRUE)
set(MBEDTLS_FOUND TRUE)
set(MBEDTLS_LIBRARIES
MbedTLS::mbedtls
MbedTLS::mbedx509
MbedTLS::mbedcrypto)
set(MBEDTLS_INCLUDE_DIRS "")
return()
endif()
# 2) If upstream exported plain targets instead of namespaced ones, alias them.
if(TARGET mbedtls AND TARGET mbedx509 AND TARGET mbedcrypto)
if(NOT TARGET MbedTLS::mbedtls)
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedtls INTERFACE mbedtls)
endif()
if(NOT TARGET MbedTLS::mbedx509)
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedx509 INTERFACE mbedx509)
endif()
if(NOT TARGET MbedTLS::mbedcrypto)
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedcrypto INTERFACE mbedcrypto)
endif()
set(mbedtls_FOUND TRUE)
set(MBEDTLS_FOUND TRUE)
set(MBEDTLS_LIBRARIES
MbedTLS::mbedtls
MbedTLS::mbedx509
MbedTLS::mbedcrypto)
set(MBEDTLS_INCLUDE_DIRS "")
return()
endif()
# 3) Fetch and build Mbed TLS.
# Upstream options:
# - USE_STATIC_MBEDTLS_LIBRARY / USE_SHARED_MBEDTLS_LIBRARY
# - ENABLE_PROGRAMS / ENABLE_TESTING
# - MBEDTLS_AS_SUBPROJECT / DISABLE_PACKAGE_CONFIG_AND_INSTALL
# - MBEDTLS_TARGET_PREFIX
#
# These are supported by the upstream CMake build. :contentReference[oaicite:1]{index=1}
set(FETCHCONTENT_QUIET FALSE)
if(MBEDTLS_FETCHCONTENT_BASE_DIR)
set(FETCHCONTENT_BASE_DIR "${MBEDTLS_FETCHCONTENT_BASE_DIR}")
endif()
# Avoid polluting the parent build and skip extras we usually do not want.
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON CACHE BOOL "" FORCE)
set(MBEDTLS_AS_SUBPROJECT ON CACHE BOOL "" FORCE)
set(MBEDTLS_TARGET_PREFIX "" CACHE STRING "" FORCE)
if(MBEDTLS_BUILD_SHARED)
set(USE_SHARED_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
set(USE_STATIC_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
else()
set(USE_SHARED_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
set(USE_STATIC_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
endif()
FetchContent_Declare(
mbedtls_fc
GIT_REPOSITORY "${MBEDTLS_FETCHCONTENT_GIT_REPOSITORY}"
GIT_TAG "${MBEDTLS_FETCHCONTENT_GIT_TAG}"
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(mbedtls_fc)
# 4) Normalize targets across upstream versions.
#
# Mbed TLS 3.x:
# mbedtls, mbedx509, mbedcrypto
#
# Mbed TLS 4.x:
# mbedtls, mbedx509, tfpsacrypto
#
# Map everything to stable namespaced targets for the consumer. :contentReference[oaicite:2]{index=2}
set(_mbedtls_tls_target "")
set(_mbedtls_x509_target "")
set(_mbedtls_crypto_target "")
if(TARGET mbedtls)
set(_mbedtls_tls_target mbedtls)
elseif(TARGET MbedTLS::mbedtls)
set(_mbedtls_tls_target MbedTLS::mbedtls)
endif()
if(TARGET mbedx509)
set(_mbedtls_x509_target mbedx509)
elseif(TARGET MbedTLS::mbedx509)
set(_mbedtls_x509_target MbedTLS::mbedx509)
endif()
if(TARGET mbedcrypto)
set(_mbedtls_crypto_target mbedcrypto)
elseif(TARGET tfpsacrypto)
set(_mbedtls_crypto_target tfpsacrypto)
elseif(TARGET MbedTLS::mbedcrypto)
set(_mbedtls_crypto_target MbedTLS::mbedcrypto)
endif()
if(_mbedtls_tls_target AND NOT TARGET MbedTLS::mbedtls)
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedtls INTERFACE "${_mbedtls_tls_target}")
endif()
if(_mbedtls_x509_target AND NOT TARGET MbedTLS::mbedx509)
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedx509 INTERFACE "${_mbedtls_x509_target}")
endif()
if(_mbedtls_crypto_target AND NOT TARGET MbedTLS::mbedcrypto)
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
target_link_libraries(MbedTLS::mbedcrypto INTERFACE "${_mbedtls_crypto_target}")
endif()
find_package_handle_standard_args(
mbedtls
REQUIRED_VARS
_mbedtls_tls_target
_mbedtls_x509_target
_mbedtls_crypto_target
)
if(mbedtls_FOUND)
set(MBEDTLS_FOUND TRUE)
set(MBEDTLS_LIBRARIES
MbedTLS::mbedtls
MbedTLS::mbedx509
MbedTLS::mbedcrypto)
# Best-effort include directory discovery for legacy consumers.
get_target_property(_mbedtls_inc "${_mbedtls_tls_target}" INTERFACE_INCLUDE_DIRECTORIES)
if(_mbedtls_inc)
set(MBEDTLS_INCLUDE_DIRS "${_mbedtls_inc}")
else()
set(MBEDTLS_INCLUDE_DIRS "")
endif()
endif()
+14
View File
@@ -1,9 +1,23 @@
# mbedtls/tf-psa-crypto user config overrides for Wii/Dolphin.
# Both variables point to the same file; it is included after each library's
# default config header, so #undef/#define are applied on top of the defaults.
# Must be set before find_package(mbedtls) so FetchContent picks them up.
set(TF_PSA_CRYPTO_USER_CONFIG_FILE
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
CACHE FILEPATH "tf-psa-crypto user config for Wii/Dolphin" FORCE)
set(MBEDTLS_USER_CONFIG_FILE
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
CACHE FILEPATH "mbedtls user config for Wii/Dolphin" FORCE)
# Target definitions # Target definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_DOLPHIN DUSK_DOLPHIN
DUSK_INPUT_GAMEPAD DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640 DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480 DUSK_DISPLAY_HEIGHT=480
MBEDTLS_PSA_DRIVER_GET_ENTROPY
MBEDTLS_PLATFORM_MS_TIME_ALT
THREAD_PTHREAD=1
) )
# Custom compiler flags # Custom compiler flags
+6 -1
View File
@@ -2,4 +2,9 @@ include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_GAMECUBE DUSK_GAMECUBE
) )
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
# bba
)
+9 -1
View File
@@ -1,6 +1,7 @@
# Find link platform-specific libraries # Find link platform-specific libraries
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
# find_package(CURL REQUIRED)
# Setup endianess at compile time to optimize. # Setup endianess at compile time to optimize.
include(TestBigEndian) include(TestBigEndian)
@@ -16,12 +17,17 @@ else()
endif() endif()
# Link required libraries. # Link required libraries.
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
SDL2 SDL2
pthread pthread
OpenGL::GL OpenGL::GL
GL GL
m m
# CURL::libcurl
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${MBEDTLS_INCLUDE_DIR}
) )
# Define platform-specific macros. # Define platform-specific macros.
@@ -38,4 +44,6 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_POINTER DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6
THREAD_PTHREAD=1
) )
+6
View File
@@ -24,6 +24,11 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
pspvfpu pspvfpu
pspvram pspvram
psphprm psphprm
pspnet
pspnet_inet
pspnet_apctl
psphttp
pspssl
) )
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
@@ -39,6 +44,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_OPENGL_LEGACY DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480 DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272 DUSK_DISPLAY_HEIGHT=272
THREAD_PTHREAD=1
) )
# Postbuild, create .pbp file for PSP. # Postbuild, create .pbp file for PSP.
+110
View File
@@ -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
)
+1 -1
View File
@@ -1,6 +1,6 @@
FROM devkitpro/devkitppc FROM devkitpro/devkitppc
WORKDIR /workdir WORKDIR /workdir
RUN apt update && \ RUN apt update && \
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl && \ apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl python3-jsonschema python3-jinja2 python3-jsonschema && \
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip
VOLUME ["/workdir"] VOLUME ["/workdir"]
+13
View File
@@ -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"]
+3
View File
@@ -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"
+13
View File
@@ -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)
+6 -1
View File
@@ -16,7 +16,12 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(dusksdl2) add_subdirectory(dusksdl2)
add_subdirectory(duskgl) add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii") elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(duskvita)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube")
add_subdirectory(duskdolphin) add_subdirectory(duskdolphin)
endif() endif()
+13 -4
View File
@@ -46,6 +46,16 @@ if(NOT Lua_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua) target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif() endif()
if(NOT mbedtls_FOUND)
find_package(mbedtls REQUIRED)
if(mbedtls_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_LIBRARIES})
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_INCLUDE_DIRS})
else()
message(FATAL_ERROR "mbedtls not found. Please ensure mbedtls is correctly installed.")
endif()
endif()
# Includes # Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
@@ -72,10 +82,9 @@ add_subdirectory(locale)
add_subdirectory(physics) add_subdirectory(physics)
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(script) add_subdirectory(script)
add_subdirectory(system)
add_subdirectory(time) add_subdirectory(time)
add_subdirectory(ui) add_subdirectory(ui)
add_subdirectory(network)
add_subdirectory(util) add_subdirectory(util)
add_subdirectory(thread)
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
# add_subdirectory(thread)
# endif()
+1 -4
View File
@@ -64,7 +64,7 @@ errorret_t displayInit(void) {
glm_perspective( glm_perspective(
glm_rad(45.0f), glm_rad(45.0f),
(float_t)SCREEN.width / (float_t)SCREEN.height, SCREEN.aspect,
0.1f, 0.1f,
100.0f, 100.0f,
proj proj
@@ -101,9 +101,6 @@ errorret_t displayUpdate(void) {
errorChain(sceneRender()); errorChain(sceneRender());
// Render UI
// uiRender();
// Finish up // Finish up
screenUnbind(); screenUnbind();
screenRender(); screenRender();
@@ -40,6 +40,17 @@ uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer) {
return frameBufferPlatformGetHeight(framebuffer); return frameBufferPlatformGetHeight(framebuffer);
} }
float_t frameBufferGetAspect(const framebuffer_t *framebuffer) {
#ifdef frameBufferPlatformGetAspect
return frameBufferPlatformGetAspect(framebuffer);
#endif
uint32_t width = frameBufferGetWidth(framebuffer);
uint32_t height = frameBufferGetHeight(framebuffer);
if(height == 0) return 1.0f; // Avoid divide by zero, just return 1:1 aspect.
return (float_t)width / (float_t)height;
}
void frameBufferClear(const uint8_t flags, const color_t color) { void frameBufferClear(const uint8_t flags, const color_t color) {
frameBufferPlatformClear(flags, color); frameBufferPlatformClear(flags, color);
} }
@@ -58,6 +58,16 @@ uint32_t frameBufferGetWidth(const framebuffer_t *framebuffer);
*/ */
uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer); uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer);
/**
* Returns the aspect ratio of the framebuffer. This is ALMOST always just
* the width / height, however some platforms may choose to override this if
* they have stretched styled back buffers, e.g. 640x480 stretched.
*
* @param framebuffer The framebuffer to get the aspect ratio of.
* @return The aspect ratio of the framebuffer.
*/
float_t frameBufferGetAspect(const framebuffer_t *framebuffer);
/** /**
* Binds the framebuffer for rendering, or the backbuffer if the framebuffer * Binds the framebuffer for rendering, or the backbuffer if the framebuffer
* provided is NULL. * provided is NULL.
+4 -4
View File
@@ -52,7 +52,7 @@ errorret_t screenBind() {
// Screen mode backbuffer uses the full display size // Screen mode backbuffer uses the full display size
SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND);
SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND);
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
// No needd for a framebuffer. // No needd for a framebuffer.
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC #ifdef DUSK_DISPLAY_SIZE_DYNAMIC
@@ -100,8 +100,7 @@ errorret_t screenBind() {
int32_t fbWidth, fbHeight; int32_t fbWidth, fbHeight;
fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND); fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND);
fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND); fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND);
float_t currentAspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
float_t currentAspect = (float_t)fbWidth / (float_t)fbHeight;
if(currentAspect == SCREEN.aspectRatio.ratio) { if(currentAspect == SCREEN.aspectRatio.ratio) {
// No need to use framebuffer. // No need to use framebuffer.
SCREEN.width = fbWidth; SCREEN.width = fbWidth;
@@ -129,13 +128,14 @@ errorret_t screenBind() {
if(SCREEN.framebufferReady) { if(SCREEN.framebufferReady) {
// Is current framebuffer the correct size? // Is current framebuffer the correct size?
int32_t curFbWidth, curFbHeight; int32_t curFbWidth, curFbHeight;
float_t curFbAspect = frameBufferGetAspect(&SCREEN.framebuffer);
curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer); curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer); curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) { if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) {
// Correct size, nothing to do. // Correct size, nothing to do.
SCREEN.width = newFbWidth; SCREEN.width = newFbWidth;
SCREEN.height = newFbHeight; SCREEN.height = newFbHeight;
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; SCREEN.aspect = curFbAspect;
errorChain(frameBufferBind(&SCREEN.framebuffer)); errorChain(frameBufferBind(&SCREEN.framebuffer));
errorOk(); errorOk();
} }
+6 -3
View File
@@ -70,9 +70,7 @@ errorret_t textDraw(
const float_t x, const float_t x,
const float_t y, const float_t y,
const char_t *text, const char_t *text,
#if MESH_ENABLE_COLOR const color_t color,
const color_t color,
#endif
const tileset_t *tileset, const tileset_t *tileset,
texture_t *texture texture_t *texture
) { ) {
@@ -83,6 +81,11 @@ errorret_t textDraw(
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, texture)); errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, texture));
#if MESH_ENABLE_COLOR
#else
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, color));
#endif
// errorChain(spriteBatchPush( // errorChain(spriteBatchPush(
// // texture, // // texture,
// posX, posY, // posX, posY,
+1 -3
View File
@@ -66,9 +66,7 @@ errorret_t textDraw(
const float_t x, const float_t x,
const float_t y, const float_t y,
const char_t *text, const char_t *text,
#if MESH_ENABLE_COLOR const color_t color,
const color_t color,
#endif
const tileset_t *tileset, const tileset_t *tileset,
texture_t *texture texture_t *texture
); );
+138 -19
View File
@@ -20,14 +20,119 @@
#include "entity/component/physics/entityphysics.h" #include "entity/component/physics/entityphysics.h"
#include "game/game.h" #include "game/game.h"
#include "physics/physicsmanager.h" #include "physics/physicsmanager.h"
#include "network/network.h"
#include "network/networkinfo.h"
#include "system/system.h"
#include "network/httpclient.h"
#include "display/mesh/cube.h" #include "display/mesh/cube.h"
#include "display/mesh/plane.h" #include "display/mesh/plane.h"
engine_t ENGINE; engine_t ENGINE;
entityid_t phBoxEnt;
componentid_t phBoxPhys;
float_t onlineSwapTime = FLT_MAX;
void goOnline();
void goOffline();
void onGETComplete(httpclient_t *client, void *user) {
sceneLog("GET request complete!\n");
sceneLog("Response status: %u\n", client->statusCode);
for(size_t i = 0; i < client->responseHeaderCount; i++) {
sceneLog("Header: %s: %s\n", client->responseHeaders[i].name, client->responseHeaders[i].value);
}
}
void onGETError(httpclient_t *client, errorret_t err, void *user) {
errorCatch(errorPrint(err));
}
void onNetworkConnected(void *user) {
onlineSwapTime = TIME.time + 3.0f;
networkinfo_t info = networkGetInfo();
if(info.type == NETWORK_TYPE_IPV4) {
sceneLog(
"Connected to network with IPv4 address: " NETWORK_INFO_FORMAT_IPV4 "\n",
info.ipv4.ip[0], info.ipv4.ip[1], info.ipv4.ip[2], info.ipv4.ip[3]
);
#ifdef DUSK_NETWORK_IPV6
} else if(info.type == NETWORK_TYPE_IPV6) {
sceneLog(
"Connected to network with IPv6 address: " NETWORK_INFO_FORMAT_IPV6 "\n",
info.ipv6.ip[0], info.ipv6.ip[1], info.ipv6.ip[2], info.ipv6.ip[3],
info.ipv6.ip[4], info.ipv6.ip[5], info.ipv6.ip[6], info.ipv6.ip[7],
info.ipv6.ip[8], info.ipv6.ip[9], info.ipv6.ip[10], info.ipv6.ip[11],
info.ipv6.ip[12], info.ipv6.ip[13], info.ipv6.ip[14], info.ipv6.ip[15]
);
#endif
}
char_t *domain = "https://google.com";
sceneLog("Online, sending GET to %s...\n", domain);
httpclient_t client;
errorret_t ret;
ret = httpclientInit(&client);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
return;
}
httpclientRequest(
&client,
"GET",
domain,
false,
NULL,
NULL,
onGETComplete,
onGETError,
NULL
);
ret = httpclientDispose(&client);
if(ret.code != ERROR_OK) {
errorCatch(errorPrint(ret));
return;
}
// sceneLog("Network connected, I will disconnect at: %.2f1.\n", onlineSwapTime);
}
void onNetworkFailed(errorret_t error, void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Failed to connect to network, will try again at %.2f1.\n", onlineSwapTime);
}
void onNetworkDisconnected(errorret_t error, void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Network disconnected, will go online at %.2f1.\n", onlineSwapTime);
errorCatch(errorPrint(error));
}
void onNetworkDisconnectFinished(void *user) {
onlineSwapTime = TIME.time + 3.0f;
sceneLog("Finished disconnecting from network, will go online at %.2f1.\n", onlineSwapTime);
}
void goOnline() {
sceneLog("Going online...\n");
networkRequestConnection(
onNetworkConnected,
onNetworkFailed,
onNetworkDisconnected,
NULL
);
}
void goOffline() {
sceneLog("Going offline...\n");
networkRequestDisconnection(onNetworkDisconnectFinished, NULL);
}
/* Kept module-level only because engineUpdate needs them for the reset. */
static entityid_t phBoxEnt;
static componentid_t phBoxPhys;
errorret_t engineInit(const int32_t argc, const char_t **argv) { errorret_t engineInit(const int32_t argc, const char_t **argv) {
memoryZero(&ENGINE, sizeof(engine_t)); memoryZero(&ENGINE, sizeof(engine_t));
@@ -36,6 +141,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
ENGINE.argv = argv; ENGINE.argv = argv;
// Init systems. Order is important. // Init systems. Order is important.
errorChain(systemInit());
timeInit(); timeInit();
errorChain(inputInit()); errorChain(inputInit());
errorChain(assetInit()); errorChain(assetInit());
@@ -46,9 +152,13 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(sceneInit()); errorChain(sceneInit());
entityManagerInit(); entityManagerInit();
physicsManagerInit(); physicsManagerInit();
// errorChain(networkInit());
errorChain(gameInit()); errorChain(gameInit());
/* ---- Camera ---- */ sceneLog("Init done, going to queue online in 1 seconds...\n");
onlineSwapTime = TIME.time + 1.0f;
// Camera
entityid_t cam = entityManagerAdd(); entityid_t cam = entityManagerAdd();
componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION); componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION);
float_t distance = 6.0f; float_t distance = 6.0f;
@@ -59,13 +169,13 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
(vec3){ distance, distance, distance } (vec3){ distance, distance, distance }
); );
componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA); componentid_t camCam = entityAddComponent(cam, COMPONENT_TYPE_CAMERA);
entityCameraSetZFar(cam, camCam, distance * 6.0f); entityCameraSetZFar(cam, camCam, 100.0f);
/* ---- Static floor (visual + physics) ---- */ // Floor
entityid_t floorEnt = entityManagerAdd(); entityid_t floorEnt = entityManagerAdd();
componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION); componentid_t floorPos = entityAddComponent(floorEnt, COMPONENT_TYPE_POSITION);
componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH); componentid_t floorMesh = entityAddComponent(floorEnt, COMPONENT_TYPE_MESH);
componentid_t floorMat = entityAddComponent(floorEnt, COMPONENT_TYPE_MATERIAL); componentid_t floorMat = entityAddComponent(floorEnt, COMPONENT_TYPE_MATERIAL);
componentid_t floorPhys = entityAddComponent(floorEnt, COMPONENT_TYPE_PHYSICS); componentid_t floorPhys = entityAddComponent(floorEnt, COMPONENT_TYPE_PHYSICS);
entityPositionSetPosition(floorEnt, floorPos, (vec3){ -5.0f, 0.0f, -5.0f }); entityPositionSetPosition(floorEnt, floorPos, (vec3){ -5.0f, 0.0f, -5.0f });
@@ -74,27 +184,24 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
entityMaterialGetShaderMaterial(floorEnt, floorMat)->unlit.color = COLOR_GREEN; entityMaterialGetShaderMaterial(floorEnt, floorMat)->unlit.color = COLOR_GREEN;
entityphysics_t *floorPhysData = entityPhysicsGet(floorEnt, floorPhys); entityphysics_t *floorPhysData = entityPhysicsGet(floorEnt, floorPhys);
floorPhysData->type = PHYSICS_BODY_STATIC; floorPhysData->type = PHYSICS_BODY_STATIC;
floorPhysData->shape.type = PHYSICS_SHAPE_PLANE; floorPhysData->shape.type = PHYSICS_SHAPE_PLANE;
floorPhysData->shape.data.plane.normal[0] = 0.0f; floorPhysData->shape.data.plane.normal[0] = 0.0f;
floorPhysData->shape.data.plane.normal[1] = 1.0f; floorPhysData->shape.data.plane.normal[1] = 1.0f;
floorPhysData->shape.data.plane.normal[2] = 0.0f; floorPhysData->shape.data.plane.normal[2] = 0.0f;
floorPhysData->shape.data.plane.distance = 0.0f; floorPhysData->shape.data.plane.distance = 0.0f;
/* ---- Dynamic box ---- */ // Box
phBoxEnt = entityManagerAdd(); phBoxEnt = entityManagerAdd();
componentid_t boxPos = entityAddComponent(phBoxEnt, COMPONENT_TYPE_POSITION); componentid_t boxPos = entityAddComponent(phBoxEnt, COMPONENT_TYPE_POSITION);
componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH); componentid_t boxMesh = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MESH);
componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL); componentid_t boxMat = entityAddComponent(phBoxEnt, COMPONENT_TYPE_MATERIAL);
phBoxPhys = entityAddComponent(phBoxEnt, COMPONENT_TYPE_PHYSICS); phBoxPhys = entityAddComponent(phBoxEnt, COMPONENT_TYPE_PHYSICS);
entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE); entityMeshSetMesh(phBoxEnt, boxMesh, &CUBE_MESH_SIMPLE);
entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED; entityMaterialGetShaderMaterial(phBoxEnt, boxMat)->unlit.color = COLOR_RED;
/* Physics position lives in the POSITION component. CUBE_MESH_SIMPLE is
* centred at origin (-0.5..0.5), so entity position == physics centre. */
entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f }); entityPositionSetPosition(phBoxEnt, boxPos, (vec3){ 0.0f, 4.0f, 0.0f });
/* Run the init script. */ /* Run the init script. */
scriptcontext_t ctx; scriptcontext_t ctx;
errorChain(scriptContextInit(&ctx)); errorChain(scriptContextInit(&ctx));
@@ -105,6 +212,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
} }
errorret_t engineUpdate(void) { errorret_t engineUpdate(void) {
// errorChain(networkUpdate());
timeUpdate(); timeUpdate();
inputUpdate(); inputUpdate();
@@ -126,6 +235,15 @@ errorret_t engineUpdate(void) {
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false; if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
if(TIME.time >= onlineSwapTime) {
onlineSwapTime = FLT_MAX;
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
goOffline();
} else {
goOnline();
}
}
errorOk(); errorOk();
} }
@@ -134,6 +252,7 @@ void engineExit(void) {
} }
errorret_t engineDispose(void) { errorret_t engineDispose(void) {
// errorChain(networkDispose());
sceneDispose(); sceneDispose();
errorChain(gameDispose()); errorChain(gameDispose());
entityManagerDispose(); entityManagerDispose();
+2 -2
View File
@@ -8,8 +8,8 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
#define ENTITY_COUNT_MAX 64 #define ENTITY_COUNT_MAX 6
#define ENTITY_COMPONENT_COUNT_MAX 16 #define ENTITY_COMPONENT_COUNT_MAX 6
typedef uint8_t entityid_t; typedef uint8_t entityid_t;
typedef uint8_t componentid_t; typedef uint8_t componentid_t;
+3 -2
View File
@@ -8,6 +8,7 @@
#include "entitymanager.h" #include "entitymanager.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "scene/scene.h"
entitymanager_t ENTITY_MANAGER; entitymanager_t ENTITY_MANAGER;
@@ -18,8 +19,8 @@ void entityManagerInit(void) {
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
); );
printf( sceneLog(
"Entity Manager size is currently: %zu bytes (%.2f KB)\n", "Entity Manager size: %zu bytes (%.2f KB)\n",
sizeof(entitymanager_t), sizeof(entitymanager_t),
sizeof(entitymanager_t) / 1024.0f sizeof(entitymanager_t) / 1024.0f
); );
+13
View File
@@ -0,0 +1,13 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
network.c
networkinfo.c
networksocket.c
networktls.c
httpclient.c
)
+545
View File
@@ -0,0 +1,545 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "httpclient.h"
#include "util/memory.h"
#include "assert/assert.h"
/* ---- helpers ----------------------------------------------------------- */
static bool_t httpclientStrEqualCI(const char_t *a, const char_t *b) {
while(*a && *b) {
if(tolower((unsigned char)*a) != tolower((unsigned char)*b)) return false;
a++; b++;
}
return *a == '\0' && *b == '\0';
}
/**
* Reads exactly len bytes from the TLS stream, retrying on timeout.
* Returns an error on I/O failure or unexpected connection close.
*/
static errorret_t httpclientReadExact(
networktls_t *tls,
errorstate_t *es,
uint8_t *buf,
size_t len
) {
size_t total = 0;
while(total < len) {
size_t got = 0;
errorret_t err = networktlsRead(tls, buf + total, len - total, &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) {
errorThrowState(es, "Connection closed before all expected bytes arrived");
}
total += got;
}
errorOk();
}
/**
* Reads bytes one at a time until \r\n is found. Writes the line (without
* the terminator) into buf, null-terminates, and sets *outLen. Returns an
* error on I/O failure or if the line exceeds maxLen-1 characters.
*/
static errorret_t httpclientReadLine(
networktls_t *tls,
errorstate_t *es,
char_t *buf,
size_t maxLen,
size_t *outLen
) {
size_t len = 0;
uint8_t ch;
size_t got;
errorret_t err;
while(len < maxLen - 1) {
do {
got = 0;
err = networktlsRead(tls, &ch, 1, &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) {
errorThrowState(es, "Connection closed during header read");
}
} while(got == 0);
if(ch == '\r') {
/* consume the \n */
do {
got = 0;
err = networktlsRead(tls, &ch, 1, &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) break;
} while(got == 0);
break;
}
buf[len++] = (char_t)ch;
}
buf[len] = '\0';
*outLen = len;
errorOk();
}
/* ---- response parsing -------------------------------------------------- */
/**
* Parses status code from "HTTP/x.y NNN ..." — returns 0 on failure.
*/
static uint16_t httpclientParseStatus(const char_t *line) {
const char_t *p = strchr(line, ' ');
if(!p) return 0;
return (uint16_t)atoi(p + 1);
}
/**
* Parses "Name: Value" into the client's responseHeaders array. Trims
* leading whitespace from the value.
*/
static void httpclientParseHeader(
httpclient_t *client,
const char_t *line
) {
const char_t *colon;
const char_t *valStart;
size_t nameLen;
size_t valLen;
httpheader_t *h;
if(client->responseHeaderCount >= HTTPCLIENT_HEADER_MAX) return;
colon = strchr(line, ':');
if(!colon) return;
nameLen = (size_t)(colon - line);
if(nameLen == 0 || nameLen >= HTTPCLIENT_HEADER_NAME_MAX) return;
valStart = colon + 1;
while(*valStart == ' ' || *valStart == '\t') valStart++;
valLen = strlen(valStart);
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
h = &client->responseHeaders[client->responseHeaderCount++];
memoryCopy(h->name, line, nameLen);
h->name[nameLen] = '\0';
memoryCopy(h->value, valStart, valLen);
h->value[valLen] = '\0';
}
static const char_t *httpclientGetResponseHeader(
const httpclient_t *client,
const char_t *name
) {
size_t i;
for(i = 0; i < client->responseHeaderCount; i++) {
if(httpclientStrEqualCI(client->responseHeaders[i].name, name)) {
return client->responseHeaders[i].value;
}
}
return NULL;
}
/* ---- body delivery ----------------------------------------------------- */
static errorret_t httpclientReadBodyFixed(
httpclient_t *client,
size_t contentLength
) {
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
size_t remaining = contentLength;
size_t got;
errorret_t err;
while(remaining > 0) {
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
err = httpclientReadExact(&client->tls, &client->errorState, buf, want);
if(err.code != ERROR_OK) return err;
if(client->onData) client->onData(client, buf, want, client->user);
remaining -= want;
got = want;
(void)got;
}
errorOk();
}
static errorret_t httpclientReadBodyChunked(httpclient_t *client) {
char_t sizeLine[32];
size_t lineLen;
errorret_t err;
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
for(;;) {
err = httpclientReadLine(
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
);
if(err.code != ERROR_OK) return err;
/* strtol parses hex chunk size; stop on terminating "0" chunk */
size_t chunkSize = (size_t)strtol(sizeLine, NULL, 16);
if(chunkSize == 0) {
/* consume trailing CRLF after the zero chunk */
httpclientReadLine(
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
);
break;
}
size_t remaining = chunkSize;
while(remaining > 0) {
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
err = httpclientReadExact(
&client->tls, &client->errorState, buf, want
);
if(err.code != ERROR_OK) return err;
if(client->onData) client->onData(client, buf, want, client->user);
remaining -= want;
}
/* consume the CRLF after chunk data */
err = httpclientReadLine(
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
);
if(err.code != ERROR_OK) return err;
}
errorOk();
}
static errorret_t httpclientReadBodyUntilClose(httpclient_t *client) {
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
size_t got;
errorret_t err;
for(;;) {
err = networktlsRead(&client->tls, buf, sizeof(buf), &got);
if(err.code != ERROR_OK) return err;
if(got == NETWORKSOCKET_RECV_CLOSED) break;
if(got > 0 && client->onData) {
client->onData(client, buf, got, client->user);
}
}
errorOk();
}
/* ---- TLS onConnect — runs the full HTTP exchange ----------------------- */
static void httpclientTlsOnConnect(networktls_t *tls, void *user) {
httpclient_t *client = (httpclient_t *)user;
char_t reqBuf[HTTPCLIENT_REQ_BUF_SIZE];
char_t lineBuf[HTTPCLIENT_HEADER_NAME_MAX + HTTPCLIENT_HEADER_VALUE_MAX + 4];
int reqLen;
size_t lineLen;
size_t i;
errorret_t err;
/* ---- build request headers ----------------------------------------- */
reqLen = snprintf(
reqBuf, sizeof(reqBuf),
"%s %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"User-Agent: DuskEngine/1.0\r\n",
client->method,
client->url.path[0] != '\0' ? client->url.path : "/",
client->url.host
);
for(i = 0; i < client->requestHeaderCount; i++) {
int n = snprintf(
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
"%s: %s\r\n",
client->requestHeaders[i].name,
client->requestHeaders[i].value
);
if(n > 0) reqLen += n;
}
if(client->requestBodyLen > 0) {
int n = snprintf(
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
"Content-Length: %zu\r\n",
client->requestBodyLen
);
if(n > 0) reqLen += n;
}
/* blank line terminates headers */
if((size_t)reqLen + 2 < sizeof(reqBuf)) {
reqBuf[reqLen++] = '\r';
reqBuf[reqLen++] = '\n';
}
err = networktlsWrite(tls, (const uint8_t *)reqBuf, (size_t)reqLen);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
if(client->requestBody != NULL && client->requestBodyLen > 0) {
err = networktlsWrite(tls, client->requestBody, client->requestBodyLen);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
}
/* ---- read response status line ------------------------------------- */
err = httpclientReadLine(
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
client->statusCode = httpclientParseStatus(lineBuf);
/* ---- read response headers until blank line ------------------------ */
client->responseHeaderCount = 0;
for(;;) {
err = httpclientReadLine(
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
);
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
if(lineLen == 0) break; /* blank line = end of headers */
httpclientParseHeader(client, lineBuf);
}
if(client->onHeaders) {
client->onHeaders(
client,
client->statusCode,
client->responseHeaders,
client->responseHeaderCount,
client->user
);
}
/* ---- read response body -------------------------------------------- */
/* 1xx, 204 No Content and 304 Not Modified carry no body */
bool_t noBody = (
client->statusCode < 200 ||
client->statusCode == 204 ||
client->statusCode == 304
);
if(!noBody) {
const char_t *transferEncoding =
httpclientGetResponseHeader(client, "Transfer-Encoding");
const char_t *contentLengthStr =
httpclientGetResponseHeader(client, "Content-Length");
if(transferEncoding != NULL &&
strstr(transferEncoding, "chunked") != NULL) {
err = httpclientReadBodyChunked(client);
} else if(contentLengthStr != NULL) {
size_t contentLength = (size_t)atoi(contentLengthStr);
err = httpclientReadBodyFixed(client, contentLength);
} else {
err = httpclientReadBodyUntilClose(client);
}
if(err.code != ERROR_OK) {
if(client->onError) client->onError(client, err, client->user);
return;
}
}
if(client->onComplete) client->onComplete(client, client->user);
}
static void httpclientTlsOnError(
networktls_t *tls,
errorret_t err,
void *user
) {
httpclient_t *client = (httpclient_t *)user;
if(client->onError) client->onError(client, err, client->user);
}
static void httpclientTlsOnDisconnect(networktls_t *tls, void *user) {
/* nothing — httpclientTlsOnConnect drives the full lifecycle */
}
/* ---- public API -------------------------------------------------------- */
errorret_t httpclientInit(httpclient_t *client) {
memoryZero(client, sizeof(httpclient_t));
errorChain(networktlsInit(&client->tls));
errorOk();
}
errorret_t httpclientParseUrl(
httpurl_t *url,
errorstate_t *errorState,
const char_t *urlStr
) {
const char_t *schemeEnd;
const char_t *hostStart;
const char_t *portStart;
const char_t *pathStart;
size_t hostLen;
memoryZero(url, sizeof(httpurl_t));
schemeEnd = strstr(urlStr, "://");
if(!schemeEnd) {
errorThrowState(errorState, "URL missing scheme: %s", urlStr);
}
size_t schemeLen = (size_t)(schemeEnd - urlStr);
if(schemeLen >= HTTPCLIENT_SCHEME_MAX) {
errorThrowState(errorState, "URL scheme too long");
}
memoryCopy(url->scheme, urlStr, schemeLen);
url->scheme[schemeLen] = '\0';
/* default port by scheme */
if(httpclientStrEqualCI(url->scheme, "https")) {
url->port = 443;
} else if(httpclientStrEqualCI(url->scheme, "http")) {
url->port = 80;
} else {
errorThrowState(errorState, "Unsupported URL scheme: %s", url->scheme);
}
hostStart = schemeEnd + 3;
pathStart = strchr(hostStart, '/');
portStart = strchr(hostStart, ':');
/* port present and appears before the path */
if(portStart != NULL && (pathStart == NULL || portStart < pathStart)) {
hostLen = (size_t)(portStart - hostStart);
url->port = (uint16_t)atoi(portStart + 1);
} else {
hostLen = pathStart != NULL
? (size_t)(pathStart - hostStart)
: strlen(hostStart);
}
if(hostLen == 0 || hostLen >= HTTPCLIENT_HOST_MAX) {
errorThrowState(errorState, "URL host missing or too long");
}
memoryCopy(url->host, hostStart, hostLen);
url->host[hostLen] = '\0';
if(pathStart != NULL) {
size_t pathLen = strlen(pathStart);
if(pathLen >= HTTPCLIENT_PATH_MAX) pathLen = HTTPCLIENT_PATH_MAX - 1;
memoryCopy(url->path, pathStart, pathLen);
url->path[pathLen] = '\0';
} else {
url->path[0] = '/';
url->path[1] = '\0';
}
errorOk();
}
void httpclientSetHeader(
httpclient_t *client,
const char_t *name,
const char_t *value
) {
size_t nameLen;
size_t valLen;
size_t i;
httpheader_t *h;
assertNotNull(name, "header name must not be null");
assertNotNull(value, "header value must not be null");
/* replace existing header with same name */
for(i = 0; i < client->requestHeaderCount; i++) {
if(httpclientStrEqualCI(client->requestHeaders[i].name, name)) {
h = &client->requestHeaders[i];
valLen = strlen(value);
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
memoryCopy(h->value, value, valLen);
h->value[valLen] = '\0';
return;
}
}
assertTrue(
client->requestHeaderCount < HTTPCLIENT_HEADER_MAX,
"httpclientSetHeader: request header limit exceeded"
);
h = &client->requestHeaders[client->requestHeaderCount++];
nameLen = strlen(name);
if(nameLen >= HTTPCLIENT_HEADER_NAME_MAX) nameLen = HTTPCLIENT_HEADER_NAME_MAX - 1;
memoryCopy(h->name, name, nameLen);
h->name[nameLen] = '\0';
valLen = strlen(value);
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
memoryCopy(h->value, value, valLen);
h->value[valLen] = '\0';
}
void httpclientRequest(
httpclient_t *client,
const char_t *method,
const char_t *url,
bool_t verifyPeer,
httpclientheadercallback_t onHeaders,
httpclientdatacallback_t onData,
httpclientcompletecallback_t onComplete,
httpclienterrorcallback_t onError,
void *user
) {
size_t methodLen;
errorret_t err;
assertNotNull(method, "method must not be null");
assertNotNull(url, "url must not be null");
methodLen = strlen(method);
if(methodLen >= HTTPCLIENT_METHOD_MAX) methodLen = HTTPCLIENT_METHOD_MAX - 1;
memoryCopy(client->method, method, methodLen);
client->method[methodLen] = '\0';
err = httpclientParseUrl(&client->url, &client->errorState, url);
if(err.code != ERROR_OK) {
if(onError) onError(client, err, user);
return;
}
client->onHeaders = onHeaders;
client->onData = onData;
client->onComplete = onComplete;
client->onError = onError;
client->user = user;
client->statusCode = 0;
client->responseHeaderCount = 0;
networktlsConnect(
&client->tls,
client->url.host,
client->url.port,
verifyPeer,
httpclientTlsOnConnect,
httpclientTlsOnError,
httpclientTlsOnDisconnect,
client
);
}
void httpclientDisconnect(httpclient_t *client) {
networktlsDisconnect(&client->tls);
}
errorret_t httpclientDispose(httpclient_t *client) {
return networktlsDispose(&client->tls);
}
+170
View File
@@ -0,0 +1,170 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "network/networktls.h"
#include "util/endian.h"
#define HTTPCLIENT_METHOD_MAX 8
#define HTTPCLIENT_URL_MAX 1024
#define HTTPCLIENT_HOST_MAX 256
#define HTTPCLIENT_PATH_MAX 768
#define HTTPCLIENT_SCHEME_MAX 8
#define HTTPCLIENT_HEADER_NAME_MAX 64
#define HTTPCLIENT_HEADER_VALUE_MAX 512
#define HTTPCLIENT_HEADER_MAX 32
#define HTTPCLIENT_HEADER_BUF_SIZE 8192
#define HTTPCLIENT_BODY_BUF_SIZE 4096
#define HTTPCLIENT_REQ_BUF_SIZE 4096
/** Parsed components of a URL. */
typedef struct {
char_t scheme[HTTPCLIENT_SCHEME_MAX];
char_t host[HTTPCLIENT_HOST_MAX];
uint16_t port;
char_t path[HTTPCLIENT_PATH_MAX];
} httpurl_t;
/** A single HTTP header (name + value pair). */
typedef struct {
char_t name[HTTPCLIENT_HEADER_NAME_MAX];
char_t value[HTTPCLIENT_HEADER_VALUE_MAX];
} httpheader_t;
typedef struct httpclient_s httpclient_t;
/**
* Called once the response status line and all headers have been parsed.
* Fired from the socket thread before any body data arrives.
*/
typedef void (*httpclientheadercallback_t)(
httpclient_t *client,
uint16_t statusCode,
const httpheader_t *headers,
size_t headerCount,
void *user
);
/**
* Called for each chunk of response body data.
* May be called multiple times per request. Fired from the socket thread.
* For binary payloads use endianReadBE32 / endianReadLE32 from util/endian.h
* to read multi-byte values with correct byte order.
*/
typedef void (*httpclientdatacallback_t)(
httpclient_t *client,
const uint8_t *data,
size_t len,
void *user
);
/** Called once when the response body has been fully received. */
typedef void (*httpclientcompletecallback_t)(
httpclient_t *client,
void *user
);
/** Called on any transport or protocol error. */
typedef void (*httpclienterrorcallback_t)(
httpclient_t *client,
errorret_t err,
void *user
);
typedef struct httpclient_s {
networktls_t tls;
errorstate_t errorState;
/* --- request config (set before httpclientRequest) --- */
char_t method[HTTPCLIENT_METHOD_MAX];
httpurl_t url;
httpheader_t requestHeaders[HTTPCLIENT_HEADER_MAX];
size_t requestHeaderCount;
/**
* Optional request body. The pointer must remain valid until onComplete or
* onError fires. The HTTP layer does not copy the body.
*/
const uint8_t *requestBody;
size_t requestBodyLen;
/* --- response state (populated during request) --- */
uint16_t statusCode;
httpheader_t responseHeaders[HTTPCLIENT_HEADER_MAX];
size_t responseHeaderCount;
/* --- callbacks (all fired from the socket thread) --- */
httpclientheadercallback_t onHeaders;
httpclientdatacallback_t onData;
httpclientcompletecallback_t onComplete;
httpclienterrorcallback_t onError;
void *user;
} httpclient_t;
/**
* Initializes an HTTP client. Must be called before any other httpclient
* function.
*/
errorret_t httpclientInit(httpclient_t *client);
/**
* Parses a URL string into its components. Supports http:// and https://.
* Port defaults to 80 (HTTP) or 443 (HTTPS) when not specified.
*
* @return ERROR_NOT_OK if the URL is malformed.
*/
errorret_t httpclientParseUrl(
httpurl_t *url,
errorstate_t *errorState,
const char_t *urlStr
);
/**
* Adds or replaces a request header. Must be called before httpclientRequest.
* Asserts if HTTPCLIENT_HEADER_MAX is exceeded.
*/
void httpclientSetHeader(
httpclient_t *client,
const char_t *name,
const char_t *value
);
/**
* Starts an asynchronous HTTPS (or plain HTTP) request. All callbacks fire
* from the socket thread, not the main thread.
*
* @param method HTTP verb ("GET", "POST", etc.).
* @param url Full URL string — parsed internally.
* @param verifyPeer Pass true to verify the server TLS certificate. Pass
* false for self-signed / development servers.
* @param onHeaders Called when response headers arrive (may be NULL).
* @param onData Called for each body chunk (may be NULL).
* @param onComplete Called when the full response is received (may be NULL).
* @param onError Called on any error (may be NULL).
* @param user Passed to all callbacks.
*/
void httpclientRequest(
httpclient_t *client,
const char_t *method,
const char_t *url,
bool_t verifyPeer,
httpclientheadercallback_t onHeaders,
httpclientdatacallback_t onData,
httpclientcompletecallback_t onComplete,
httpclienterrorcallback_t onError,
void *user
);
/**
* Requests cancellation of an in-flight request. Non-blocking.
*/
void httpclientDisconnect(httpclient_t *client);
/**
* Blocks until the request thread exits, then frees all resources.
*/
errorret_t httpclientDispose(httpclient_t *client);
+116
View File
@@ -0,0 +1,116 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "log/log.h"
network_t NETWORK;
errorret_t networkInit() {
memoryZero(&NETWORK, sizeof(network_t));
NETWORK.errorState.code = ERROR_OK;
NETWORK.onDisconnect = NULL;
return networkPlatformInit();
}
errorret_t networkUpdate() {
errorChain(networkPlatformUpdate());
if(NETWORK.state == NETWORK_STATE_CONNECTED && !networkIsConnected()) {
NETWORK.state = NETWORK_STATE_DISCONNECTED;
if(NETWORK.onDisconnect) {
errorret_t ret;
if(NETWORK.errorState.code == ERROR_OK) {
ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"Network connection lost"
);
} else {
ret.code = NETWORK.errorState.code;
ret.state = &NETWORK.errorState;
}
NETWORK.onDisconnect(ret, NETWORK.disconnectUser);
}
}
errorOk();
}
bool_t networkIsConnected() {
return networkPlatformIsConnected();
}
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
) {
assertNotNull(onConnected, "onConnected callback must not be null");
assertNotNull(onFailed, "onFailed callback must not be null");
assertNotNull(onDisconnect, "onDisconnect callback must not be null");
NETWORK.state = NETWORK_STATE_CONNECTING;
NETWORK.onDisconnect = onDisconnect;
NETWORK.disconnectUser = user;
#ifndef networkPlatformRequestConnection
// This is a platform cannot be requested to go online, this would basically
// be for platforms like Linux or Windows where the OS is responsible for
// maintaining the network connection.
if(networkIsConnected()) {
NETWORK.state = NETWORK_STATE_CONNECTED;
onConnected(user);
} else {
errorret_t ret = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"No network connection available"
);
onFailed(ret, user);
}
#else
networkPlatformRequestConnection(onConnected, onFailed, onDisconnect, user);
#endif
}
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
) {
assertNotNull(onComplete, "onComplete callback must not be null");
NETWORK.state = NETWORK_STATE_DISCONNECTING;
#ifndef networkPlatformRequestDisconnection
NETWORK.state = NETWORK_STATE_DISCONNECTED;
onComplete(user);
#else
networkPlatformRequestDisconnection(onComplete, user);
#endif
}
void networkDisconnectedDuringDispose(void *u) {
logDebug("Network disconnected during dispose\n");
}
errorret_t networkDispose() {
if(NETWORK.state == NETWORK_STATE_CONNECTED) {
networkRequestDisconnection(networkDisconnectedDuringDispose, NULL);
}
errorChain(networkPlatformDispose());
errorOk();
}
+126
View File
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "network/networkplatform.h"
#ifndef networkPlatformInit
#error "networkPlatformInit must be defined"
#endif
#ifndef networkPlatformUpdate
#error "networkPlatformUpdate must be defined"
#endif
#ifndef networkPlatformDispose
#error "networkPlatformDispose must be defined"
#endif
#ifndef networkPlatformIsConnected
#error "networkPlatformIsConnected must be defined"
#endif
typedef enum {
NETWORK_STATE_DISCONNECTED,
NETWORK_STATE_CONNECTING,
NETWORK_STATE_CONNECTED,
NETWORK_STATE_DISCONNECTING,
} networkstate_t;
typedef struct {
networkplatform_t platform;
errorstate_t errorState;
networkstate_t state;
void (*onDisconnect)(errorret_t error, void *user);
void *disconnectUser;
} network_t;
extern network_t NETWORK;
/**
* Initializes the network system. This will NOT connect to the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkInit();
/**
* Updates the network manager, dispatching any completed async request
* callbacks on the main thread.
*
* @return An error code indicating success or failure.
*/
errorret_t networkUpdate();
/**
* Disposes of the network manager. This will NOT disconnect from the network.
*
* @return An error code indicating success or failure.
*/
errorret_t networkDispose();
/**
* Returns true if the system is connected to AN network, this doesn't mean that
* requests will succeed, doesn't mean there's internet, just that we could
* possibly make network requests. If this is false, this usually means
* something like;
* - A network cable is not connnected
* - No Wi-Fi Connection has been established
* - No IP Address has been assigned
*
* That kinda stuff.
*
* In future I will probably have REASONS for why it's not connected, for
* example;
* - On PSP you need to "request" network access
* - On GameCube, network settings need to be defined, including DHCP, etc.
* - On Windows, this may require additional permissions
*
* @return True if some network connection is detected.
*/
bool_t networkIsConnected();
/**
* See networkIsConnected for a bit more info, but this is for some
* platforms (mainly PSP) to request the system to start doing networking.
*
* You should only call this once and assume that it is "pending" until either
* onComplete or onFailed is invoked. If you call this twice it is undefined
* behavior.
*
* onDisconnect must be provided, and is called whenever the network is lost
* after a successful connection. This will NOT be called if disconnect is
* manually triggered, but WILL if an error occurs, or a network stack bug, etc.
*
* @param onConnected Callback to invoke when the network is connected.
* @param onFailed Callback to invoke if the network connection fails.
* @param onDisconnect Called after a successful connection, when disconnected.
* @param user User data to pass to the callbacks.
*/
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
);
/**
* Requests the system to disconnect from the network. This is basically just
* for PSP, but I guess it could be used on other platforms in future if they
* have some kind of "network connection mode" that needs to be exited.
*
* You should only call this once and assume that it is "pending" until
* onComplete is invoked. If you call this twice it is undefined behavior.
*
* If it fails, you still get onComplete called, but may fail if you try to
* reconnect later unfortunately.
*
* @param onComplete Callback to invoke when the network is disconnected.
* @param user User data to pass to the callback.
*/
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
);
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network.h"
#include "networkinfo.h"
#include "network/networkplatform.h"
#include "assert/assert.h"
#ifndef networkPlatformGetInfo
#error "networkPlatformGetInfo must be defined"
#endif
networkinfo_t networkGetInfo() {
assertTrue(
NETWORK.state == NETWORK_STATE_CONNECTED,
"networkGetInfo called when not connected"
);
return networkPlatformGetInfo();
}
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define NETWORK_INFO_IPV4_DNS_COUNT_MAX 2
#define NETWORK_INFO_IPV4_OCTET_COUNT 4
#define NETWORK_INFO_IPV6_OCTET_COUNT 16
#define NETWORK_INFO_IPV6_DNS_COUNT_MAX 2
#define NETWORK_INFO_FORMAT_IPV4 "%u.%u.%u.%u"
#define NETWORK_INFO_FORMAT_IPV6 \
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x"
typedef enum {
NETWORK_TYPE_IPV4,
#ifdef DUSK_NETWORK_IPV6
NETWORK_TYPE_IPV6,
#endif
} networktype_t;
typedef struct {
uint8_t ip[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t subnet[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t gateway[NETWORK_INFO_IPV4_OCTET_COUNT];
// uint8_t dns[NETWORK_INFO_IPV4_OCTET_COUNT][NETWORK_INFO_IPV4_DNS_COUNT_MAX];
} networkinfoipv4_t;
#ifdef DUSK_NETWORK_IPV6
typedef struct {
uint8_t ip[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t subnet[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t gateway[NETWORK_INFO_IPV6_OCTET_COUNT];
// uint8_t dns[NETWORK_INFO_IPV6_OCTET_COUNT][NETWORK_INFO_IPV6_DNS_COUNT_MAX];
} networkinfoipv6_t;
#endif
typedef struct {
networktype_t type;
union {
networkinfoipv4_t ipv4;
#ifdef DUSK_NETWORK_IPV6
networkinfoipv6_t ipv6;
#endif
};
} networkinfo_t;
/**
* Returns the network information for the currently active network connection.
*
* @return Network information for the currently active network connection.
*/
networkinfo_t networkGetInfo();
+165
View File
@@ -0,0 +1,165 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networksocket.h"
#include "util/memory.h"
#include "assert/assert.h"
static void networksocketThreadCallback(thread_t *thread) {
networksocket_t *socket = (networksocket_t *)thread->data;
errorret_t err;
bool_t hadError;
hadError = false;
err = networksocketPlatformConnect(socket);
if (err.code != ERROR_OK) {
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
if (socket->onError) socket->onError(socket, err, socket->user);
return;
}
socket->state = NETWORKSOCKET_STATE_CONNECTED;
if (socket->onConnect) socket->onConnect(socket, socket->user);
if (socket->onReceive != NULL) {
uint8_t recvBuf[NETWORKSOCKET_RECV_BUFFER_SIZE];
uint8_t localSendBuf[NETWORKSOCKET_SEND_BUFFER_SIZE];
size_t sendLen;
size_t recvLen;
while (!threadShouldStop(thread)) {
threadMutexLock(&socket->sendMutex);
sendLen = socket->sendLen;
if (sendLen > 0) {
memoryCopy(localSendBuf, socket->sendBuffer, sendLen);
socket->sendLen = 0;
}
threadMutexUnlock(&socket->sendMutex);
if (sendLen > 0) {
err = networksocketPlatformSend(socket, localSendBuf, sendLen);
if (err.code != ERROR_OK) {
hadError = true;
break;
}
}
recvLen = 0;
err = networksocketPlatformRecv(
socket, recvBuf, NETWORKSOCKET_RECV_BUFFER_SIZE, &recvLen
);
if (err.code != ERROR_OK) {
hadError = true;
break;
}
if (recvLen == NETWORKSOCKET_RECV_CLOSED) break;
if (recvLen > 0) {
socket->onReceive(socket, recvBuf, recvLen, socket->user);
}
}
}
socket->state = NETWORKSOCKET_STATE_DISCONNECTING;
networksocketPlatformDisconnect(socket);
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
if (hadError) {
if (socket->onError) socket->onError(socket, err, socket->user);
} else {
if (socket->onDisconnect) socket->onDisconnect(socket, socket->user);
}
}
errorret_t networksocketInit(networksocket_t *socket) {
memoryZero(socket, sizeof(networksocket_t));
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
threadInit(&socket->thread, networksocketThreadCallback);
socket->thread.data = socket;
threadMutexInit(&socket->sendMutex);
errorChain(networksocketPlatformInit(socket));
errorOk();
}
void networksocketConnect(
networksocket_t *socket,
const char_t *host,
uint16_t port,
networksocketcallback_t onConnect,
networksocketerrorcallback_t onError,
networksocketcallback_t onDisconnect,
networksocketrecvcallback_t onReceive,
void *user
) {
size_t hostLen;
assertNotNull(host, "host must not be null");
hostLen = strlen(host);
assertTrue(hostLen < NETWORKSOCKET_HOST_MAX, "host exceeds NETWORKSOCKET_HOST_MAX");
memoryCopy(socket->host, host, hostLen + 1);
socket->port = port;
socket->onConnect = onConnect;
socket->onError = onError;
socket->onDisconnect = onDisconnect;
socket->onReceive = onReceive;
socket->user = user;
socket->sendLen = 0;
socket->state = NETWORKSOCKET_STATE_CONNECTING;
threadStart(&socket->thread);
}
errorret_t networksocketSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
) {
assertNotNull(data, "data must not be null");
assertTrue(len > 0, "len must be greater than 0");
threadMutexLock(&socket->sendMutex);
assertTrue(
socket->sendLen + len <= NETWORKSOCKET_SEND_BUFFER_SIZE,
"networksocketSend would overflow send buffer"
);
memoryCopy(socket->sendBuffer + socket->sendLen, data, len);
socket->sendLen += len;
threadMutexUnlock(&socket->sendMutex);
errorOk();
}
errorret_t networksocketWrite(
networksocket_t *socket,
const uint8_t *data,
size_t len
) {
return networksocketPlatformSend(socket, data, len);
}
errorret_t networksocketRead(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
) {
return networksocketPlatformRecv(socket, buf, maxLen, outLen);
}
void networksocketDisconnect(networksocket_t *socket) {
threadStopRequest(&socket->thread);
}
errorret_t networksocketDispose(networksocket_t *socket) {
threadStop(&socket->thread);
errorChain(networksocketPlatformDispose(socket));
threadMutexDispose(&socket->sendMutex);
errorOk();
}
+170
View File
@@ -0,0 +1,170 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "thread/thread.h"
#include "network/networksocketplatform.h"
#ifndef networksocketPlatformInit
#error "networksocketPlatformInit must be defined"
#endif
#ifndef networksocketPlatformConnect
#error "networksocketPlatformConnect must be defined"
#endif
#ifndef networksocketPlatformSend
#error "networksocketPlatformSend must be defined"
#endif
#ifndef networksocketPlatformRecv
#error "networksocketPlatformRecv must be defined"
#endif
#ifndef networksocketPlatformDisconnect
#error "networksocketPlatformDisconnect must be defined"
#endif
#ifndef networksocketPlatformDispose
#error "networksocketPlatformDispose must be defined"
#endif
#define NETWORKSOCKET_HOST_MAX 256
#define NETWORKSOCKET_SEND_BUFFER_SIZE 4096
#define NETWORKSOCKET_RECV_BUFFER_SIZE 4096
/** Sentinel returned by networksocketPlatformRecv when the peer closed the connection. */
#define NETWORKSOCKET_RECV_CLOSED ((size_t)(-1))
typedef enum {
NETWORKSOCKET_STATE_DISCONNECTED,
NETWORKSOCKET_STATE_CONNECTING,
NETWORKSOCKET_STATE_CONNECTED,
NETWORKSOCKET_STATE_DISCONNECTING,
} networksocketstate_t;
typedef struct networksocket_s networksocket_t;
typedef void (*networksocketcallback_t)(
networksocket_t *socket, void *user
);
typedef void (*networksocketerrorcallback_t)(
networksocket_t *socket, errorret_t err, void *user
);
typedef void (*networksocketrecvcallback_t)(
networksocket_t *socket, const uint8_t *data, size_t len, void *user
);
typedef struct networksocket_s {
networksocketstate_t state;
errorstate_t errorState;
thread_t thread;
threadmutex_t sendMutex;
char_t host[NETWORKSOCKET_HOST_MAX];
uint16_t port;
networksocketplatform_t platform;
uint8_t sendBuffer[NETWORKSOCKET_SEND_BUFFER_SIZE];
size_t sendLen;
/**
* Called from the socket thread once TCP is established. If onReceive is
* NULL, the socket thread will exit after this returns and disconnect.
* If onReceive is set, a recv loop runs after this returns.
* Suitable entry point for TLS/HTTP layers that drive their own I/O.
*/
networksocketcallback_t onConnect;
/**
* When non-NULL, the socket thread runs a receive loop after connection
* and calls this for each incoming data chunk. When NULL, the thread only
* calls onConnect and then disconnects.
*/
networksocketrecvcallback_t onReceive;
networksocketerrorcallback_t onError;
networksocketcallback_t onDisconnect;
void *user;
} networksocket_t;
/**
* Initializes a socket structure. Must be called before any other socket
* function.
*/
errorret_t networksocketInit(networksocket_t *socket);
/**
* Starts an asynchronous connection in a dedicated thread. All callbacks are
* invoked from the socket thread, not the main thread.
*
* If onReceive is non-NULL, the thread runs a receive loop after connection
* and delivers incoming data via onReceive. If onReceive is NULL, the thread
* calls onConnect and exits (useful for TLS or request-driven I/O that
* manages its own reads via networksocketRead/networksocketWrite).
*
* @param socket Socket to connect.
* @param host Hostname or IP address to connect to.
* @param port Port number.
* @param onConnect Called once TCP is established (may be NULL).
* @param onError Called on connect or I/O error (may be NULL).
* @param onDisconnect Called on graceful disconnect (may be NULL).
* @param onReceive Called for each received chunk; NULL disables recv loop.
* @param user User data passed to all callbacks.
*/
void networksocketConnect(
networksocket_t *socket,
const char_t *host,
uint16_t port,
networksocketcallback_t onConnect,
networksocketerrorcallback_t onError,
networksocketcallback_t onDisconnect,
networksocketrecvcallback_t onReceive,
void *user
);
/**
* Queues data for sending. Thread-safe; may be called from any thread.
* The socket thread drains the queue on each recv-loop iteration.
* Asserts if the send buffer would overflow.
*/
errorret_t networksocketSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
);
/**
* Writes data directly to the socket without buffering. Must be called from
* the socket's own thread (e.g., from within onConnect or a TLS bio callback).
*/
errorret_t networksocketWrite(
networksocket_t *socket,
const uint8_t *data,
size_t len
);
/**
* Reads data directly from the socket. Must be called from the socket's own
* thread. Returns 0 in *outLen on timeout (no data yet); returns
* NETWORKSOCKET_RECV_CLOSED in *outLen when the peer has closed the connection.
*/
errorret_t networksocketRead(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
);
/**
* Requests the socket thread to stop. Non-blocking; onDisconnect fires once
* the thread has finished.
*/
void networksocketDisconnect(networksocket_t *socket);
/**
* Blocks until the socket thread has stopped, then releases platform
* resources. Safe to call on an already-disconnected socket.
*/
errorret_t networksocketDispose(networksocket_t *socket);
+288
View File
@@ -0,0 +1,288 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networktls.h"
#include "util/memory.h"
#include "assert/assert.h"
// Define missing mbedtls net error codes if not present
#ifndef MBEDTLS_ERR_NET_SEND_FAILED
#define MBEDTLS_ERR_NET_SEND_FAILED -0x004E
#endif
#ifndef MBEDTLS_ERR_NET_RECV_FAILED
#define MBEDTLS_ERR_NET_RECV_FAILED -0x004C
#endif
/* ---- mbedTLS bio callbacks -------------------------------------------- */
#ifdef DUSK_DOLPHIN
#include <ogc/lwp_watchdog.h>
#include <mbedtls/platform_time.h>
mbedtls_ms_time_t mbedtls_ms_time(void) {
return (mbedtls_ms_time_t)ticks_to_millisecs(gettime());
}
#endif
int mbedtls_platform_get_entropy(uint8_t *output, size_t len) {
for (size_t i = 0; i < len; i++) {
output[i] = rand() & 0xFF;
}
return 0;
}
static int networktlsBioSend(
void *ctx,
const unsigned char *buf,
size_t len
) {
networktls_t *tls = (networktls_t *)ctx;
errorret_t err = networksocketWrite(&tls->socket, (const uint8_t *)buf, len);
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_SEND_FAILED;
return (int)len;
}
static int networktlsBioRecv(void *ctx, unsigned char *buf, size_t len) {
networktls_t *tls = (networktls_t *)ctx;
size_t outLen = 0;
errorret_t err = networksocketRead(
&tls->socket, (uint8_t *)buf, len, &outLen
);
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_RECV_FAILED;
if(outLen == NETWORKSOCKET_RECV_CLOSED) return 0;
if(outLen == 0) return MBEDTLS_ERR_SSL_WANT_READ;
return (int)outLen;
}
/* ---- socket callbacks (run in socket thread) --------------------------- */
static void networktlsSocketOnConnect(networksocket_t *socket, void *user) {
networktls_t *tls = (networktls_t *)user;
errorret_t errret;
int ret;
if(psa_crypto_init() != PSA_SUCCESS) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"psa_crypto_init failed"
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
ret = mbedtls_ssl_config_defaults(
&tls->conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT
);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"mbedtls_ssl_config_defaults failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
mbedtls_ssl_conf_authmode(
&tls->conf,
tls->verifyPeer ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE
);
mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->cacert, NULL);
ret = mbedtls_ssl_setup(&tls->ssl, &tls->conf);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"mbedtls_ssl_setup failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
ret = mbedtls_ssl_set_hostname(&tls->ssl, tls->socket.host);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"mbedtls_ssl_set_hostname failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
mbedtls_ssl_set_bio(
&tls->ssl, tls,
networktlsBioSend, networktlsBioRecv, NULL
);
/* TLS handshake loop — WANT_READ/WANT_WRITE are non-fatal retry signals */
do {
ret = mbedtls_ssl_handshake(&tls->ssl);
} while(
ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
);
if(ret != 0) {
errret = errorThrowImpl(
&tls->errorState, ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"TLS handshake failed: -0x%04x", -ret
);
if(tls->onError) tls->onError(tls, errret, tls->user);
return;
}
/* Handshake succeeded — let the user drive I/O from this thread */
if(tls->onConnect) tls->onConnect(tls, tls->user);
mbedtls_ssl_close_notify(&tls->ssl);
}
static void networktlsSocketOnError(
networksocket_t *socket,
errorret_t err,
void *user
) {
networktls_t *tls = (networktls_t *)user;
if(tls->onError) tls->onError(tls, err, tls->user);
}
static void networktlsSocketOnDisconnect(
networksocket_t *socket,
void *user
) {
networktls_t *tls = (networktls_t *)user;
if(tls->onDisconnect) tls->onDisconnect(tls, tls->user);
}
/* ---- public API -------------------------------------------------------- */
errorret_t networktlsInit(networktls_t *tls) {
memoryZero(tls, sizeof(networktls_t));
errorChain(networksocketInit(&tls->socket));
mbedtls_ssl_init(&tls->ssl);
mbedtls_ssl_config_init(&tls->conf);
mbedtls_x509_crt_init(&tls->cacert);
errorOk();
}
errorret_t networktlsAddCACert(
networktls_t *tls,
const uint8_t *certPem,
size_t len
) {
int ret = mbedtls_x509_crt_parse(&tls->cacert, certPem, len);
if(ret != 0) {
errorThrowState(
&tls->errorState,
"Failed to parse CA certificate: -0x%04x", -ret
);
}
errorOk();
}
void networktlsConnect(
networktls_t *tls,
const char_t *host,
uint16_t port,
bool_t verifyPeer,
networktlscallback_t onConnect,
networktlserrorcallback_t onError,
networktlscallback_t onDisconnect,
void *user
) {
tls->verifyPeer = verifyPeer;
tls->onConnect = onConnect;
tls->onError = onError;
tls->onDisconnect = onDisconnect;
tls->user = user;
networksocketConnect(
&tls->socket,
host, port,
networktlsSocketOnConnect,
networktlsSocketOnError,
networktlsSocketOnDisconnect,
NULL, /* no recv loop — TLS drives all I/O from onConnect */
tls
);
}
errorret_t networktlsWrite(
networktls_t *tls,
const uint8_t *data,
size_t len
) {
size_t sent = 0;
int ret;
while(sent < len) {
ret = mbedtls_ssl_write(
&tls->ssl,
(const unsigned char *)(data + sent),
len - sent
);
if(ret == MBEDTLS_ERR_SSL_WANT_WRITE || ret == MBEDTLS_ERR_SSL_WANT_READ) {
continue;
}
if(ret < 0) {
errorThrowState(
&tls->errorState,
"TLS write failed: -0x%04x", -ret
);
}
sent += (size_t)ret;
}
errorOk();
}
errorret_t networktlsRead(
networktls_t *tls,
uint8_t *buf,
size_t maxLen,
size_t *outLen
) {
int ret;
do {
ret = mbedtls_ssl_read(&tls->ssl, (unsigned char *)buf, maxLen);
} while(
ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
);
if(ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
*outLen = NETWORKSOCKET_RECV_CLOSED;
errorOk();
}
if(ret < 0) {
errorThrowState(
&tls->errorState,
"TLS read failed: -0x%04x", -ret
);
}
*outLen = (size_t)ret;
errorOk();
}
void networktlsDisconnect(networktls_t *tls) {
networksocketDisconnect(&tls->socket);
}
errorret_t networktlsDispose(networktls_t *tls) {
errorChain(networksocketDispose(&tls->socket));
mbedtls_ssl_free(&tls->ssl);
mbedtls_ssl_config_free(&tls->conf);
mbedtls_x509_crt_free(&tls->cacert);
errorOk();
}
+108
View File
@@ -0,0 +1,108 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "network/networksocket.h"
#include <mbedtls/ssl.h>
#include <mbedtls/x509_crt.h>
#include <psa/crypto.h>
typedef struct networktls_s networktls_t;
typedef void (*networktlscallback_t)(networktls_t *tls, void *user);
typedef void (*networktlserrorcallback_t)(
networktls_t *tls, errorret_t err, void *user
);
typedef struct networktls_s {
networksocket_t socket;
errorstate_t errorState;
bool_t verifyPeer;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
mbedtls_x509_crt cacert;
networktlscallback_t onConnect;
networktlserrorcallback_t onError;
networktlscallback_t onDisconnect;
void *user;
} networktls_t;
/**
* Initializes the TLS context and the underlying socket.
*/
errorret_t networktlsInit(networktls_t *tls);
/**
* Adds a PEM-encoded CA certificate used to verify the server. Must be called
* before networktlsConnect when verifyPeer is true.
*/
errorret_t networktlsAddCACert(
networktls_t *tls,
const uint8_t *certPem,
size_t len
);
/**
* Starts an async TCP+TLS connection in the socket's own thread. All callbacks
* fire from that thread, not the main thread.
*
* When onConnect fires, the TLS handshake is complete and the connection is
* ready for networktlsWrite / networktlsRead. The connection is kept alive
* until onConnect returns, so all I/O should be performed synchronously within
* that callback.
*
* @param verifyPeer When true, the server certificate is verified against the
* CA certificates added via networktlsAddCACert. When false,
* certificate verification is skipped (useful for development
* or self-signed certificates).
*/
void networktlsConnect(
networktls_t *tls,
const char_t *host,
uint16_t port,
bool_t verifyPeer,
networktlscallback_t onConnect,
networktlserrorcallback_t onError,
networktlscallback_t onDisconnect,
void *user
);
/**
* Writes data through the TLS layer. Must be called from within the
* onConnect callback (i.e., from the socket thread).
*/
errorret_t networktlsWrite(
networktls_t *tls,
const uint8_t *data,
size_t len
);
/**
* Reads up to maxLen bytes from the TLS layer. Blocks until data arrives or
* the connection closes. Sets *outLen to bytes received, or
* NETWORKSOCKET_RECV_CLOSED when the peer has closed the connection.
* Must be called from within the onConnect callback.
*/
errorret_t networktlsRead(
networktls_t *tls,
uint8_t *buf,
size_t maxLen,
size_t *outLen
);
/**
* Requests disconnect. Non-blocking; onDisconnect fires once complete.
*/
void networktlsDisconnect(networktls_t *tls);
/**
* Blocks until the connection thread stops, then releases all resources.
*/
errorret_t networktlsDispose(networktls_t *tls);
+79
View File
@@ -12,12 +12,59 @@
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
#include "display/mesh/cube.h" #include "display/mesh/cube.h"
#include "display/spritebatch/spritebatch.h"
#include "display/text/text.h"
#include "display/screen/screen.h"
scene_t SCENE; scene_t SCENE;
char_t SCENE_LOG[SCENE_LOG_SIZE];
void sceneLog(const char *fmt, ...) {
char temp[512];
// 1. Format input like printf
va_list args;
va_start(args, fmt);
vsnprintf(temp, sizeof(temp), fmt, args);
va_end(args);
printf("%s", temp);
// 2. Split into lines
char *lines[64];
int line_count = 0;
char *ptr = temp;
while (*ptr && line_count < 64) {
lines[line_count++] = ptr;
char *nl = strchr(ptr, '\n');
if (!nl) break;
*nl = '\0';
ptr = nl + 1;
}
// 3. Prepend lines in reverse order (so final order is correct)
for (int i = 0; i < line_count; i++) {
char new_log[SCENE_LOG_SIZE];
snprintf(new_log, sizeof(new_log), "%s\n%s", lines[i], SCENE_LOG);
// Copy back safely
strncpy(SCENE_LOG, new_log, SCENE_LOG_SIZE - 1);
SCENE_LOG[SCENE_LOG_SIZE - 1] = '\0';
}
}
errorret_t sceneInit(void) { errorret_t sceneInit(void) {
memoryZero(&SCENE, sizeof(scene_t)); memoryZero(&SCENE, sizeof(scene_t));
memoryZero(SCENE_LOG, sizeof(SCENE_LOG));
sceneLog("Init\n");
errorOk(); errorOk();
} }
@@ -52,6 +99,7 @@ errorret_t sceneRender(void) {
mat4 view, proj, model; mat4 view, proj, model;
errorChain(shaderBind(&SHADER_UNLIT)); errorChain(shaderBind(&SHADER_UNLIT));
// For each camera.
for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) { for(entityid_t camIndex = 0; camIndex < camCount; camIndex++) {
entityid_t camEnt = camEnts[camIndex]; entityid_t camEnt = camEnts[camIndex];
componentid_t camComp = camComps[camIndex]; componentid_t camComp = camComps[camIndex];
@@ -110,6 +158,37 @@ errorret_t sceneRender(void) {
} }
} }
// Here is where UI will go
glm_ortho(
0.0f, SCREEN.width,
SCREEN.height, 0.0f,
0.1f, 100.0f,
proj
);
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
glm_mat4_identity(model);
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_0TEXTURE, &DEFAULT_FONT_TEXTURE));
// errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
errorChain(textDraw(
32, 32,
// "Hello World",
SCENE_LOG,
COLOR_WHITE,
&DEFAULT_FONT_TILESET,
&DEFAULT_FONT_TEXTURE
));
errorChain(spriteBatchFlush());
errorOk(); errorOk();
} }
+4
View File
@@ -14,6 +14,10 @@ typedef struct {
extern scene_t SCENE; extern scene_t SCENE;
#define SCENE_LOG_SIZE 1024
extern char_t SCENE_LOG[SCENE_LOG_SIZE];
void sceneLog(const char *fmt, ...);
/** /**
* Initialize the scene subsystem. * Initialize the scene subsystem.
* *
+1 -3
View File
@@ -52,9 +52,7 @@ int moduleTextDraw(lua_State *L) {
x, x,
y, y,
text, text,
#if MESH_ENABLE_COLOR color == NULL ? COLOR_WHITE : *color,
color == NULL ? COLOR_WHITE : *color,
#endif
&DEFAULT_FONT_TILESET, &DEFAULT_FONT_TILESET,
&DEFAULT_FONT_TEXTURE &DEFAULT_FONT_TEXTURE
); );
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
system.c
)
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "system.h"
#include "system/systemplatform.h"
#ifndef systemGetActiveDialogTypePlatform
#error "systemGetActiveDialogTypePlatform is not defined"
#endif
#ifndef systemInitPlatform
#error "systemInitPlatform is not defined"
#endif
errorret_t systemInit() {
return systemInitPlatform();
}
systemdialogtype_t systemGetActiveDialogType() {
return systemGetActiveDialogTypePlatform();
}
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef enum {
SYSTEM_DIALOG_TYPE_NONE,
SYSTEM_DIALOG_TYPE_RENDER_BLOCKING,
SYSTEM_DIALOG_TYPE_TICK_BLOCKING
} systemdialogtype_t;
/**
* Initializes the system module. This is called really early in the init
* process of the engine.
*
* @return Error code indicating success or failure.
*/
errorret_t systemInit(void);
/**
* Basically this is only used on a few system types, it is to ask the plaform,
* e.g. PSP "What dialog is currently open?" and then the engine will change the
* behavior of the main loop to accomodate.
*
* For example, PSP can show dialogs for things like, save files, wifi, system
* information, etc. When these are open, the engine really can't do much and
* the system needs to finish processing.
*
* For most systems this will go unused.
*
* @return Dialog type currently open.
*/
systemdialogtype_t systemGetActiveDialogType();
+77 -1
View File
@@ -94,7 +94,83 @@ float_t endianLittleToHostFloat(float_t value) {
memoryCopy(&temp, &value, sizeof(uint32_t)); memoryCopy(&temp, &value, sizeof(uint32_t));
temp = endianLittleToHost32(temp); temp = endianLittleToHost32(temp);
memoryCopy(&result, &temp, sizeof(uint32_t)); memoryCopy(&result, &temp, sizeof(uint32_t));
return result; return result;
} }
uint16_t endianBigToHost16(uint16_t value) {
#if defined(DUSK_PLATFORM_ENDIAN_BIG)
return value;
#elif defined(DUSK_PLATFORM_ENDIAN_LITTLE)
// Perform conversion below
#else
if(!isHostLittleEndian()) return value;
#endif
return (uint16_t)(((value & 0x00FFu) << 8) | ((value & 0xFF00u) >> 8));
}
uint32_t endianBigToHost32(uint32_t value) {
#if defined(DUSK_PLATFORM_ENDIAN_BIG)
return value;
#elif defined(DUSK_PLATFORM_ENDIAN_LITTLE)
// Perform conversion below
#else
if(!isHostLittleEndian()) return value;
#endif
return (
((value & 0x000000FFu) << 24) |
((value & 0x0000FF00u) << 8) |
((value & 0x00FF0000u) >> 8) |
((value & 0xFF000000u) >> 24)
);
}
uint64_t endianBigToHost64(uint64_t value) {
#if defined(DUSK_PLATFORM_ENDIAN_BIG)
return value;
#elif defined(DUSK_PLATFORM_ENDIAN_LITTLE)
// Perform conversion below
#else
if(!isHostLittleEndian()) return value;
#endif
return (
((value & 0x00000000000000FFull) << 56) |
((value & 0x000000000000FF00ull) << 40) |
((value & 0x0000000000FF0000ull) << 24) |
((value & 0x00000000FF000000ull) << 8) |
((value & 0x000000FF00000000ull) >> 8) |
((value & 0x0000FF0000000000ull) >> 24) |
((value & 0x00FF000000000000ull) >> 40) |
((value & 0xFF00000000000000ull) >> 56)
);
}
uint16_t endianReadBE16(const uint8_t *buf, size_t offset) {
return endianBigToHost16(
(uint16_t)((uint16_t)buf[offset] << 8 | (uint16_t)buf[offset + 1])
);
}
uint32_t endianReadBE32(const uint8_t *buf, size_t offset) {
return endianBigToHost32(
((uint32_t)buf[offset ] << 24) |
((uint32_t)buf[offset + 1] << 16) |
((uint32_t)buf[offset + 2] << 8) |
((uint32_t)buf[offset + 3] )
);
}
uint16_t endianReadLE16(const uint8_t *buf, size_t offset) {
return endianLittleToHost16(
(uint16_t)((uint16_t)buf[offset + 1] << 8 | (uint16_t)buf[offset])
);
}
uint32_t endianReadLE32(const uint8_t *buf, size_t offset) {
return endianLittleToHost32(
((uint32_t)buf[offset + 3] << 24) |
((uint32_t)buf[offset + 2] << 16) |
((uint32_t)buf[offset + 1] << 8) |
((uint32_t)buf[offset ] )
);
}
+56 -2
View File
@@ -46,8 +46,62 @@ uint64_t endianLittleToHost64(uint64_t value);
/** /**
* Converts a float from little-endian to host byte order. * Converts a float from little-endian to host byte order.
* *
* @param value The little-endian value to convert. * @param value The little-endian value to convert.
* @return The value in host byte order. * @return The value in host byte order.
*/ */
float_t endianLittleToHostFloat(float_t value); float_t endianLittleToHostFloat(float_t value);
/**
* Converts a 16-bit integer from big-endian (network byte order) to host
* byte order. Use this for binary data received over network protocols.
*
* @param value The big-endian value to convert.
* @return The value in host byte order.
*/
uint16_t endianBigToHost16(uint16_t value);
/**
* Converts a 32-bit integer from big-endian (network byte order) to host
* byte order.
*
* @param value The big-endian value to convert.
* @return The value in host byte order.
*/
uint32_t endianBigToHost32(uint32_t value);
/**
* Converts a 64-bit integer from big-endian (network byte order) to host
* byte order.
*
* @param value The big-endian value to convert.
* @return The value in host byte order.
*/
uint64_t endianBigToHost64(uint64_t value);
/**
* Reads a big-endian uint16 from a byte buffer at the given offset. Safe on
* all alignment and endianness combinations. Suitable for binary HTTP response
* body parsing where the format specifies network (big-endian) byte order.
*
* @param buf Source byte buffer.
* @param offset Byte offset within buf.
* @return Host-order uint16.
*/
uint16_t endianReadBE16(const uint8_t *buf, size_t offset);
/**
* Reads a big-endian uint32 from a byte buffer at the given offset.
*/
uint32_t endianReadBE32(const uint8_t *buf, size_t offset);
/**
* Reads a little-endian uint16 from a byte buffer at the given offset. Use
* for binary payloads that specify little-endian byte order.
*/
uint16_t endianReadLE16(const uint8_t *buf, size_t offset);
/**
* Reads a little-endian uint32 from a byte buffer at the given offset.
*/
uint32_t endianReadLE32(const uint8_t *buf, size_t offset);
+3 -1
View File
@@ -18,4 +18,6 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(network)
add_subdirectory(system)
+1 -1
View File
@@ -76,7 +76,7 @@ errorret_t displayInitDolphin(void) {
); );
// Setup cull modes // Setup cull modes
GX_SetCullMode(GX_CULL_BACK); GX_SetCullMode(GX_CULL_NONE);
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
GX_SetZMode(GX_TRUE, GX_ALWAYS, GX_FALSE); GX_SetZMode(GX_TRUE, GX_ALWAYS, GX_FALSE);
GX_SetDispCopyGamma(GX_GM_1_0); GX_SetDispCopyGamma(GX_GM_1_0);
@@ -8,6 +8,7 @@
#include "display/framebuffer/framebuffer.h" #include "display/framebuffer/framebuffer.h"
#include "display/display.h" #include "display/display.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "system/systemdolphin.h"
errorret_t frameBufferInitBackBufferDolphin(void) { errorret_t frameBufferInitBackBufferDolphin(void) {
errorOk(); errorOk();
@@ -15,16 +16,29 @@ errorret_t frameBufferInitBackBufferDolphin(void) {
uint32_t frameBufferGetWidthDolphin(const framebufferdolphin_t *framebuffer) { uint32_t frameBufferGetWidthDolphin(const framebufferdolphin_t *framebuffer) {
assertNotNull(framebuffer, "Cannot get width of NULL framebuffer."); assertNotNull(framebuffer, "Cannot get width of NULL framebuffer.");
return DISPLAY.screenMode->fbWidth; return DISPLAY.screenMode->fbWidth;
} }
uint32_t frameBufferGetHeightDolphin(const framebufferdolphin_t *framebuffer) { uint32_t frameBufferGetHeightDolphin(const framebufferdolphin_t *framebuffer) {
assertNotNull(framebuffer, "Cannot get height of NULL framebuffer."); assertNotNull(framebuffer, "Cannot get height of NULL framebuffer.");
return DISPLAY.screenMode->efbHeight; return DISPLAY.screenMode->efbHeight;
} }
float_t frameBufferGetAspectDolphin(const framebufferdolphin_t *framebuffer) {
assertNotNull(framebuffer, "Cannot get aspect of NULL framebuffer.");
switch(systemGetAspectRatioDolphin()) {
case CONF_ASPECT_16_9:
return 16.0f / 9.0f;
case CONF_ASPECT_4_3:
return 4.0f / 3.0f;
default:
return (
(float_t)DISPLAY.screenMode->fbWidth /
(float_t)DISPLAY.screenMode->efbHeight
);
}
}
errorret_t frameBufferBindDolphin(framebufferdolphin_t *framebuffer) { errorret_t frameBufferBindDolphin(framebufferdolphin_t *framebuffer) {
assertNotNull(framebuffer, "Cannot bind NULL framebuffer."); assertNotNull(framebuffer, "Cannot bind NULL framebuffer.");
assertTrue( assertTrue(
@@ -38,6 +38,15 @@ uint32_t frameBufferGetWidthDolphin(const framebufferdolphin_t *framebuffer);
*/ */
uint32_t frameBufferGetHeightDolphin(const framebufferdolphin_t *framebuffer); uint32_t frameBufferGetHeightDolphin(const framebufferdolphin_t *framebuffer);
/**
* Gets the aspect ratio of the framebuffer. (Dolphin implementation). Taking
* the Wii aspect setting into consideration.
*
* @param framebuffer The framebuffer to get the aspect ratio of.
* @return The aspect ratio of the framebuffer.
*/
float_t frameBufferGetAspectDolphin(const framebufferdolphin_t *framebuffer);
/** /**
* Binds the framebuffer for rendering. (Dolphin implementation). * Binds the framebuffer for rendering. (Dolphin implementation).
* *
@@ -13,5 +13,6 @@ typedef framebufferdolphin_t framebufferplatform_t;
#define frameBufferPlatformInitBackBuffer frameBufferInitBackBufferDolphin #define frameBufferPlatformInitBackBuffer frameBufferInitBackBufferDolphin
#define frameBufferPlatformGetWidth frameBufferGetWidthDolphin #define frameBufferPlatformGetWidth frameBufferGetWidthDolphin
#define frameBufferPlatformGetHeight frameBufferGetHeightDolphin #define frameBufferPlatformGetHeight frameBufferGetHeightDolphin
#define frameBufferPlatformGetAspect frameBufferGetAspectDolphin
#define frameBufferPlatformBind frameBufferBindDolphin #define frameBufferPlatformBind frameBufferBindDolphin
#define frameBufferPlatformClear frameBufferClearDolphin #define frameBufferPlatformClear frameBufferClearDolphin
@@ -52,6 +52,7 @@ errorret_t meshDrawDolphin(
assertTrue(offsetof(meshvertex_t, pos) == 8, "pos offset wrong"); assertTrue(offsetof(meshvertex_t, pos) == 8, "pos offset wrong");
#endif #endif
// Flush vertex data to GPU. This is required before drawing with GX.
DCFlushRange( DCFlushRange(
(void*)&mesh->vertices[vertexOffset], (void*)&mesh->vertices[vertexOffset],
sizeof(meshvertex_t) * vertexCount sizeof(meshvertex_t) * vertexCount
+13 -16
View File
@@ -43,10 +43,10 @@ errorret_t shaderBindDolphin(shaderdolphin_t *shader) {
GX_LoadProjectionMtx( GX_LoadProjectionMtx(
shader->matrixProjection, shader->dolphinProj,
shader->isProjectionPerspective ? GX_PERSPECTIVE : GX_ORTHOGRAPHIC shader->isProjectionPerspective ? GX_PERSPECTIVE : GX_ORTHOGRAPHIC
); );
GX_LoadPosMtxImm(shader->matrixModelView, GX_PNMTX0); GX_LoadPosMtxImm(shader->dolphinModelView, GX_PNMTX0);
errorOk(); errorOk();
} }
@@ -193,13 +193,13 @@ errorret_t shaderUpdateMVPDolphin() {
// Need to update projection? // Need to update projection?
if((SHADER_BOUND->dirtyMatrix & SHADER_DOLPHIN_DIRTY_PROJ) != 0) { if((SHADER_BOUND->dirtyMatrix & SHADER_DOLPHIN_DIRTY_PROJ) != 0) {
shaderMat4ToMtx44(SHADER_BOUND->proj, SHADER_BOUND->matrixProjection); shaderMat4ToMtx44(SHADER_BOUND->proj, SHADER_BOUND->dolphinProj);
// Fix projection Z mapping between GLM and GX. // Fix projection Z mapping between GLM and GX.
float A = SHADER_BOUND->matrixProjection[2][2]; float A = SHADER_BOUND->dolphinProj[2][2];
float B = SHADER_BOUND->matrixProjection[2][3]; float B = SHADER_BOUND->dolphinProj[2][3];
SHADER_BOUND->matrixProjection[2][2] = 0.5f * (A + 1.0f); SHADER_BOUND->dolphinProj[2][2] = 0.5f * (A + 1.0f);
SHADER_BOUND->matrixProjection[2][3] = 0.5f * B; SHADER_BOUND->dolphinProj[2][3] = 0.5f * B;
// Is this perspective or ortho originally? Dolphin cares for some reason. // Is this perspective or ortho originally? Dolphin cares for some reason.
const float_t epsilon = 0.0001f; const float_t epsilon = 0.0001f;
@@ -209,7 +209,7 @@ errorret_t shaderUpdateMVPDolphin() {
); );
GX_LoadProjectionMtx( GX_LoadProjectionMtx(
SHADER_BOUND->matrixProjection, SHADER_BOUND->dolphinProj,
SHADER_BOUND->isProjectionPerspective ? GX_PERSPECTIVE : GX_ORTHOGRAPHIC SHADER_BOUND->isProjectionPerspective ? GX_PERSPECTIVE : GX_ORTHOGRAPHIC
); );
} }
@@ -217,23 +217,18 @@ errorret_t shaderUpdateMVPDolphin() {
// Need to update view or model? // Need to update view or model?
bool_t mvDirt = false; bool_t mvDirt = false;
if((SHADER_BOUND->dirtyMatrix & SHADER_DOLPHIN_DIRTY_VIEW) != 0) { if((SHADER_BOUND->dirtyMatrix & SHADER_DOLPHIN_DIRTY_VIEW) != 0) {
shaderMat4ToMtx(SHADER_BOUND->view, SHADER_BOUND->matrixView);
mvDirt = true; mvDirt = true;
} }
if((SHADER_BOUND->dirtyMatrix & SHADER_DOLPHIN_DIRTY_MODEL) != 0) { if((SHADER_BOUND->dirtyMatrix & SHADER_DOLPHIN_DIRTY_MODEL) != 0) {
shaderMat4ToMtx(SHADER_BOUND->model, SHADER_BOUND->matrixModel);
mvDirt = true; mvDirt = true;
} }
// Set Model/View Matrix // Set Model/View Matrix
if(mvDirt) { if(mvDirt) {
guMtxConcat( glm_mat4_mul(SHADER_BOUND->view, SHADER_BOUND->model, SHADER_BOUND->modelView);
SHADER_BOUND->matrixView, shaderMat4ToMtx(SHADER_BOUND->modelView, SHADER_BOUND->dolphinModelView);
SHADER_BOUND->matrixModel, GX_LoadPosMtxImm(SHADER_BOUND->dolphinModelView, GX_PNMTX0);
SHADER_BOUND->matrixModelView
);
GX_LoadPosMtxImm(SHADER_BOUND->matrixModelView, GX_PNMTX0);
} }
SHADER_BOUND->dirtyMatrix = 0; SHADER_BOUND->dirtyMatrix = 0;
@@ -255,6 +250,8 @@ void shaderMat4ToMtx(const mat4 inGlmMatrix, Mtx outGXMatrix) {
assertNotNull(inGlmMatrix, "Input matrix cannot be null"); assertNotNull(inGlmMatrix, "Input matrix cannot be null");
assertNotNull(outGXMatrix, "Output matrix cannot be null"); assertNotNull(outGXMatrix, "Output matrix cannot be null");
guMtxIdentity(outGXMatrix);
for(int row = 0; row < 3; ++row) { for(int row = 0; row < 3; ++row) {
for(int col = 0; col < 4; ++col) { for(int col = 0; col < 4; ++col) {
outGXMatrix[row][col] = inGlmMatrix[col][row]; outGXMatrix[row][col] = inGlmMatrix[col][row];
@@ -24,13 +24,12 @@ typedef struct shaderdolphin_s {
mat4 view; mat4 view;
mat4 proj; mat4 proj;
mat4 model; mat4 model;
mat4 modelView;
Mtx dolphinProj;
Mtx dolphinModelView;
bool_t isProjectionPerspective; bool_t isProjectionPerspective;
Mtx44 matrixProjection;
Mtx matrixView;
Mtx matrixModel;
Mtx matrixModelView;
uint_fast8_t dirtyMatrix; uint_fast8_t dirtyMatrix;
} shaderdolphin_t; } shaderdolphin_t;
+9 -1
View File
@@ -8,4 +8,12 @@
#pragma once #pragma once
#include <ogcsys.h> #include <ogcsys.h>
#include <gccore.h> #include <gccore.h>
#include <malloc.h> #include <malloc.h>
#ifdef DUSK_GAMECUBE
#define CONF_ASPECT_4_3 0
#define CONF_ASPECT_16_9 1
#define CONF_GetAspectRatio() CONF_ASPECT_4_3
#define CONF_GetLanguage() SYS_GetLanguage()
#endif
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
networkdolphin.c
networksocketdolphin.c
)
+118
View File
@@ -0,0 +1,118 @@
// /**
// * Copyright (c) 2026 Dominic Masters
// *
// * This software is released under the MIT License.
// * https://opensource.org/licenses/MIT
// */
#include "network/network.h"
#include "util/memory.h"
#include "assert/assert.h"
errorret_t networkDolphinInit() {
// s32 ret = net_init();
// if(ret < 0) errorThrow("Failed to init network stack: %d", ret);
errorOk();
}
errorret_t networkDolphinUpdate() {
errorOk();
}
errorret_t networkDolphinDispose() {
// #ifdef DUSK_WII
// net_deinit();
// #endif
errorOk();
}
bool_t networkDolphinIsConnected() {
return NETWORK.state == NETWORK_STATE_CONNECTED;
}
void networkDolphinRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
) {
assertTrue(
NETWORK.state == NETWORK_STATE_CONNECTING,
"Network host should be in a connecting state."
);
NETWORK.platform.onConnected = onConnected;
NETWORK.platform.onFailed = onFailed;
NETWORK.platform.onConnectedUser = user;
memoryZero(NETWORK.platform.ip, sizeof(NETWORK.platform.ip));
memoryZero(NETWORK.platform.netmask, sizeof(NETWORK.platform.netmask));
memoryZero(NETWORK.platform.gateway, sizeof(NETWORK.platform.gateway));
// Negotiate DHCP using the Wi-Fi settings saved in Wii System Menu.
// This call blocks until the interface is configured or times out.
#ifdef DUSK_WII
s32 ret = if_config(
NETWORK.platform.ip,
NETWORK.platform.netmask,
NETWORK.platform.gateway,
true,
20
);
#else
s32 ret = -1;
#endif
if(ret >= 0) {
NETWORK.state = NETWORK_STATE_CONNECTED;
assertNotNull(
NETWORK.platform.onConnected,
"Network platform onConnected callback should be set."
);
NETWORK.platform.onConnected(NETWORK.platform.onConnectedUser);
} else {
NETWORK.state = NETWORK_STATE_DISCONNECTED;
assertNotNull(
NETWORK.platform.onFailed,
"Network platform onFailed callback should be set."
);
errorret_t error = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"Failed to connect to network"
);
NETWORK.platform.onFailed(error, NETWORK.platform.onConnectedUser);
}
}
void networkDolphinRequestDisconnection(
void (*onComplete)(void *user),
void *user
) {
assertTrue(
NETWORK.state == NETWORK_STATE_DISCONNECTING,
"Network host should be in a disconnecting state."
);
NETWORK.state = NETWORK_STATE_DISCONNECTED;
onComplete(user);
}
networkinfo_t networkDolphinGetInfo() {
networkinfo_t info;
memoryZero(&info, sizeof(networkinfo_t));
info.type = NETWORK_TYPE_IPV4;
int ret = sscanf(
NETWORK.platform.ip,
"%hhu.%hhu.%hhu.%hhu",
&info.ipv4.ip[0],
&info.ipv4.ip[1],
&info.ipv4.ip[2],
&info.ipv4.ip[3]
);
assertTrue(ret == 4, "Failed to parse IP address");
return info;
}
+86
View File
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "network/networkinfo.h"
#include <network.h>
#define NETWORK_DOLPHIN_IP_MAX 16
typedef struct {
char_t ip[NETWORK_DOLPHIN_IP_MAX];
char_t netmask[NETWORK_DOLPHIN_IP_MAX];
char_t gateway[NETWORK_DOLPHIN_IP_MAX];
void *onConnectedUser;
void (*onConnected)(void *user);
void (*onFailed)(errorret_t error, void *user);
} networkdolphin_t;
/**
* Initializes the Wii network stack via IOS. Does not connect; use
* networkDolphinRequestConnection for that.
*
* @return Error state (if any).
*/
errorret_t networkDolphinInit();
/**
* Called each frame. No-op on Wii since connection is synchronous.
*
* @return Error state (if any).
*/
errorret_t networkDolphinUpdate();
/**
* Disposes the Wii network stack.
*
* @return Error state (if any).
*/
errorret_t networkDolphinDispose();
/**
* Returns true if the Wii is connected to a network.
*
* @return True if connected.
*/
bool_t networkDolphinIsConnected();
/**
* Requests the Wii to connect to the network using the Wi-Fi settings saved
* in the Wii System Menu. Blocks until connected or failed.
*
* @param onConnected Callback on successful connection.
* @param onFailed Callback if connection fails.
* @param onDisconnect Callback when connection is later lost.
* @param user User data passed to all callbacks.
*/
void networkDolphinRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
);
/**
* Requests the Wii to disconnect from the network.
*
* @param onComplete Callback when disconnection is complete.
* @param user User data passed to the callback.
*/
void networkDolphinRequestDisconnection(
void (*onComplete)(void *user),
void *user
);
/**
* Returns the current network IP information.
*
* @return Network info for the active connection.
*/
networkinfo_t networkDolphinGetInfo();
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "networkdolphin.h"
#define networkPlatformInit networkDolphinInit
#define networkPlatformUpdate networkDolphinUpdate
#define networkPlatformDispose networkDolphinDispose
#define networkPlatformIsConnected networkDolphinIsConnected
#define networkPlatformRequestConnection networkDolphinRequestConnection
#define networkPlatformRequestDisconnection networkDolphinRequestDisconnection
#define networkPlatformGetInfo networkDolphinGetInfo
typedef networkdolphin_t networkplatform_t;
@@ -0,0 +1,162 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network/networksocket.h"
#include "assert/assert.h"
#include <network.h>
#include <netdb.h>
#include <string.h>
/** Poll timeout in microseconds — short enough to check the stop flag often. */
#define NETWORKSOCKET_DOLPHIN_RECV_TIMEOUT_US 100000
errorret_t networksocketDolphinInit(networksocket_t *socket) {
socket->platform.fd = -1;
errorOk();
}
errorret_t networksocketDolphinConnect(networksocket_t *nsocket) {
struct hostent *he;
struct sockaddr_in addr;
s32 fd;
s32 ret;
he = net_gethostbyname(nsocket->host);
if (!he) {
errorThrowState(
&nsocket->errorState,
"net_gethostbyname(%s) failed",
nsocket->host
);
}
fd = net_socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(fd == INVALID_SOCKET) {
errorThrowState(
&nsocket->errorState,
"net_socket failed with invalid socket"
);
} else if (fd < 0) {
errorThrowState(
&nsocket->errorState,
"net_socket failed: %d",
(int)fd
);
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(nsocket->port);
memcpy(&addr.sin_addr, he->h_addr_list[0], (size_t)he->h_length);
ret = net_connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
net_close(fd);
errorThrowState(
&nsocket->errorState,
"net_connect to %s:%u failed: %d",
nsocket->host,
(unsigned int)nsocket->port,
(int)ret
);
}
nsocket->platform.fd = fd;
errorOk();
}
errorret_t networksocketDolphinSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
) {
size_t total = 0;
s32 sent;
while (total < len) {
sent = net_send(
socket->platform.fd,
(const void *)(data + total),
len - total,
0
);
if (sent <= 0) {
errorThrowState(
&socket->errorState,
"net_send failed: %d",
(int)sent
);
}
total += (size_t)sent;
}
errorOk();
}
errorret_t networksocketDolphinRecv(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
) {
struct timeval tv;
fd_set readset;
s32 ready;
s32 received;
tv.tv_sec = 0;
tv.tv_usec = NETWORKSOCKET_DOLPHIN_RECV_TIMEOUT_US;
FD_ZERO(&readset);
FD_SET(socket->platform.fd, &readset);
ready = net_select(socket->platform.fd + 1, &readset, NULL, NULL, &tv);
if (ready < 0) {
errorThrowState(
&socket->errorState,
"net_select failed: %d",
(int)ready
);
}
if (ready == 0) {
*outLen = 0;
errorOk();
}
received = net_recv(socket->platform.fd, buf, maxLen, 0);
if (received < 0) {
errorThrowState(
&socket->errorState,
"net_recv failed: %d",
(int)received
);
}
if (received == 0) {
*outLen = NETWORKSOCKET_RECV_CLOSED;
errorOk();
}
*outLen = (size_t)received;
errorOk();
}
errorret_t networksocketDolphinDisconnect(networksocket_t *socket) {
if (socket->platform.fd >= 0) {
net_close(socket->platform.fd);
socket->platform.fd = -1;
}
errorOk();
}
errorret_t networksocketDolphinDispose(networksocket_t *socket) {
if (socket->platform.fd >= 0) {
net_close(socket->platform.fd);
socket->platform.fd = -1;
}
errorOk();
}
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "error/error.h"
#include <network.h>
typedef struct networksocket_s networksocket_t;
typedef struct {
s32 fd;
} networksocketdolphin_t;
/**
* Initializes platform state (sets fd to -1).
*/
errorret_t networksocketDolphinInit(networksocket_t *socket);
/**
* Resolves host/port via net_gethostbyname and establishes a TCP connection.
*/
errorret_t networksocketDolphinConnect(networksocket_t *socket);
/**
* Writes all bytes in data to the socket. Loops on partial sends.
*/
errorret_t networksocketDolphinSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
);
/**
* Polls for incoming data with a short timeout (via net_select) so the
* socket thread can periodically check its stop flag. Sets *outLen to bytes
* received, 0 on timeout, or NETWORKSOCKET_RECV_CLOSED when peer closed.
*/
errorret_t networksocketDolphinRecv(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
);
/**
* Closes the socket file descriptor.
*/
errorret_t networksocketDolphinDisconnect(networksocket_t *socket);
/**
* Closes the file descriptor if still open (idempotent).
*/
errorret_t networksocketDolphinDispose(networksocket_t *socket);
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "networksocketdolphin.h"
#define networksocketPlatformInit networksocketDolphinInit
#define networksocketPlatformConnect networksocketDolphinConnect
#define networksocketPlatformSend networksocketDolphinSend
#define networksocketPlatformRecv networksocketDolphinRecv
#define networksocketPlatformDisconnect networksocketDolphinDisconnect
#define networksocketPlatformDispose networksocketDolphinDispose
typedef networksocketdolphin_t networksocketplatform_t;
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
systemdolphin.c
)
+27
View File
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "systemdolphin.h"
#include "input/input.h"
#include "util/string.h"
#include "assert/assert.h"
errorret_t systemInitDolphin(void) {
errorOk();
}
systemdialogtype_t systemGetActiveDialogTypeDolphin(void) {
return SYSTEM_DIALOG_TYPE_NONE;
}
int32_t systemGetAspectRatioDolphin(void) {
return CONF_GetAspectRatio();
}
int32_t systemGetLanguageDolphin(void) {
return CONF_GetLanguage();
}
+51
View File
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/system.h"
/**
* Initializes the Dolphin system module.
*
* @return Error code indicating success or failure.
*/
errorret_t systemInitDolphin(void);
/**
* Returns which Dolphin system dialog is currently open (if any).
*
* @return Currently open system dialog type.
*/
systemdialogtype_t systemGetActiveDialogTypeDolphin(void);
/**
* Returns either CONF_ASPECT_4_3 or CONF_ASPECT_16_9 depending on the aspect
* ratio of the system. I do believe that Gamecube will only ever return 4:3.
*
* Refer to;
* https://github.com/devkitPro/libogc/blob/20d90e944b83c8991538e88b00b1e5f309428e85/gc/ogc/conf.h#L190
*
* @return Aspect ratio of the system.
*/
int32_t systemGetAspectRatioDolphin(void);
/**
* Returns the language the system is set to. This is used for things like
* locale management, to try to match the system language if possible.
*
* Refer to;
* https://github.com/devkitPro/libogc/blob/20d90e944b83c8991538e88b00b1e5f309428e85/gc/ogc/conf.h#L190
*
* On gamecube, refer to;
* https://libogc.devkitpro.org/system_8h.html
*
* @return System language.
*/
int32_t systemGetLanguageDolphin(void);
// There's actually a tonne more things Wii can return, this is it for now
// though.
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/systemdolphin.h"
#define systemInitPlatform systemInitDolphin
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeDolphin
+8 -3
View File
@@ -19,7 +19,12 @@
#define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0 #define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0
#define glClearDepth(depth) glClearDepthf(depth) #define glClearDepth(depth) glClearDepthf(depth)
#else #else
#define GL_GLEXT_PROTOTYPES // For some platforms (Vita) we do not include GL extensions.
#include <GL/gl.h> #ifndef GL_GLEXT_PROTOTYPES
#include <GL/glext.h> #define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#else
#include <GL/gl.h>
#endif
#endif #endif
+3 -1
View File
@@ -12,4 +12,6 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(network)
add_subdirectory(system)
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
networklinux.c
networksocketlinux.c
)
+120
View File
@@ -0,0 +1,120 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "networklinux.h"
#include "util/memory.h"
#include "util/string.h"
#include "assert/assert.h"
errorret_t networkLinuxInit() {
errorOk();
}
errorret_t networkLinuxUpdate() {
errorOk();
}
bool_t networkLinuxIsConnected() {
// Call the OS network stack to check for connectivity.
struct ifaddrs *ifaddr, *ifa;
if(getifaddrs(&ifaddr) == -1) {
return false;
}
// Check if any non loopback interfaces have running flag set.
bool_t connected = false;
for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (!ifa->ifa_name || !ifa->ifa_flags) continue;
// Skip loopback (localhost)
if(ifa->ifa_flags & IFF_LOOPBACK) continue;
// Is interface up and running?
if(!(ifa->ifa_flags & IFF_UP)) continue;
if(!(ifa->ifa_flags & IFF_RUNNING)) continue;
connected = true;
break;
}
// Free the linked list of interfaces
freeifaddrs(ifaddr);
return connected;
}
errorret_t networkLinuxDispose() {
errorOk();
}
networkinfo_t networkLinuxGetInfo() {
networkinfo_t info;
memset(&info, 0, sizeof(networkinfo_t));
bool_t found = false;
struct ifaddrs *ifaddr, *ifa;
if(getifaddrs(&ifaddr) == -1) {
assertUnreachable("getifaddrs failed");
}
// Find the first non-loopback interface with an IP address
for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (!ifa->ifa_name || !ifa->ifa_flags) continue;
// Skip loopback (localhost)
if(ifa->ifa_flags & IFF_LOOPBACK) continue;
// Is interface up and running?
if(!(ifa->ifa_flags & IFF_UP)) continue;
if(!(ifa->ifa_flags & IFF_RUNNING)) continue;
if(ifa->ifa_addr == NULL) continue;
// Check for IPv4 address
if(ifa->ifa_addr->sa_family == AF_INET) {
info.type = NETWORK_TYPE_IPV4;
struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr;
memoryCopy(
info.ipv4.ip,
&sa->sin_addr,
NETWORK_INFO_IPV4_OCTET_COUNT
);
found = true;
// sa = (struct sockaddr_in *)ifa->ifa_netmask;
// memoryCopy(
// info.ipv4.subnet,
// &sa->sin_addr,
// NETWORK_INFO_IPV4_OCTET_COUNT
// );
break;
}
// Check for IPv6 address
#ifdef DUSK_NETWORK_IPV6
if(ifa->ifa_addr->sa_family == AF_INET6) {
info.type = NETWORK_TYPE_IPV6;
struct sockaddr_in6 *sa = (struct sockaddr_in6 *)ifa->ifa_addr;
memoryCopy(
info.ipv6.ip,
&sa->sin6_addr,
NETWORK_INFO_IPV6_OCTET_COUNT
);
found = true;
break;
}
#endif
}
freeifaddrs(ifaddr);
assertTrue(found, "No active network interface found?");
return info;
}
+54
View File
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "network/networkinfo.h"
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
typedef struct {
void *nothing;
} networklinux_t;
/**
* Initializes the network manager. Must be called before any other network
* functions.
*
* @return Any error that occurs.
*/
errorret_t networkLinuxInit();
/**
* Updates the network manager, called once per frame to handle completed
* HTTP requests.
*
* @return Any error that occurs.
*/
errorret_t networkLinuxUpdate();
/**
* Returns true — Linux uses the OS network stack which is always available.
*
* @return true
*/
bool_t networkLinuxIsConnected();
/**
* Disposes the network manager.
*
* @return Any error that occurs.
*/
errorret_t networkLinuxDispose();
/**
* Gets the network information for the currently active network connection.
*
* @return Network information for the currently active network connection.
*/
networkinfo_t networkLinuxGetInfo();
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "networklinux.h"
#define networkPlatformInit networkLinuxInit
#define networkPlatformUpdate networkLinuxUpdate
#define networkPlatformDispose networkLinuxDispose
#define networkPlatformIsConnected networkLinuxIsConnected
#define networkPlatformGetInfo networkLinuxGetInfo
typedef networklinux_t networkplatform_t;
+142
View File
@@ -0,0 +1,142 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network/networksocket.h"
#include "assert/assert.h"
/** Recv timeout: short enough to check the thread stop flag regularly. */
#define NETWORKSOCKET_LINUX_RECV_TIMEOUT_US 100000
errorret_t networksocketLinuxInit(networksocket_t *socket) {
socket->platform.fd = -1;
errorOk();
}
errorret_t networksocketLinuxConnect(networksocket_t *nsocket) {
struct addrinfo hints;
struct addrinfo *result;
struct addrinfo *rp;
struct timeval tv;
char_t portStr[8];
int ret;
int fd;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf(portStr, sizeof(portStr), "%u", (unsigned int)nsocket->port);
ret = getaddrinfo(nsocket->host, portStr, &hints, &result);
if (ret != 0) {
errorThrowState(
&nsocket->errorState,
"getaddrinfo(%s): %s",
nsocket->host,
gai_strerror(ret)
);
}
fd = -1;
for (rp = result; rp != NULL; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) continue;
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break;
close(fd);
fd = -1;
}
freeaddrinfo(result);
if (fd == -1) {
errorThrowState(
&nsocket->errorState,
"Failed to connect to %s:%u",
nsocket->host,
(unsigned int)nsocket->port
);
}
tv.tv_sec = 0;
tv.tv_usec = NETWORKSOCKET_LINUX_RECV_TIMEOUT_US;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
nsocket->platform.fd = fd;
errorOk();
}
errorret_t networksocketLinuxSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
) {
ssize_t sent;
size_t total;
total = 0;
while (total < len) {
sent = send(socket->platform.fd, data + total, len - total, MSG_NOSIGNAL);
if (sent <= 0) {
errorThrowState(
&socket->errorState,
"send failed: %s",
strerror(errno)
);
}
total += (size_t)sent;
}
errorOk();
}
errorret_t networksocketLinuxRecv(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
) {
ssize_t received;
received = recv(socket->platform.fd, buf, maxLen, 0);
if (received < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
*outLen = 0;
errorOk();
}
errorThrowState(
&socket->errorState,
"recv failed: %s",
strerror(errno)
);
}
if (received == 0) {
*outLen = NETWORKSOCKET_RECV_CLOSED;
errorOk();
}
*outLen = (size_t)received;
errorOk();
}
errorret_t networksocketLinuxDisconnect(networksocket_t *socket) {
if (socket->platform.fd != -1) {
shutdown(socket->platform.fd, SHUT_RDWR);
close(socket->platform.fd);
socket->platform.fd = -1;
}
errorOk();
}
errorret_t networksocketLinuxDispose(networksocket_t *socket) {
if (socket->platform.fd != -1) {
close(socket->platform.fd);
socket->platform.fd = -1;
}
errorOk();
}
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "error/error.h"
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/time.h>
typedef struct networksocket_s networksocket_t;
typedef struct {
int fd;
} networksocketlinux_t;
/**
* Initializes platform state for the socket (sets fd to -1).
*/
errorret_t networksocketLinuxInit(networksocket_t *socket);
/**
* Resolves host/port and establishes a TCP connection. Sets SO_RCVTIMEO
* on the resulting file descriptor so recv calls return periodically even
* when no data arrives, allowing the thread to check its stop flag.
*/
errorret_t networksocketLinuxConnect(networksocket_t *socket);
/**
* Writes all bytes in data to the socket. Loops on partial sends.
*/
errorret_t networksocketLinuxSend(
networksocket_t *socket,
const uint8_t *data,
size_t len
);
/**
* Attempts to read up to maxLen bytes into buf. Sets *outLen to the number
* of bytes received. Returns 0 in *outLen on timeout (EAGAIN/EWOULDBLOCK).
* Returns NETWORKSOCKET_RECV_CLOSED in *outLen when the peer closed the
* connection.
*/
errorret_t networksocketLinuxRecv(
networksocket_t *socket,
uint8_t *buf,
size_t maxLen,
size_t *outLen
);
/**
* Shuts down and closes the socket file descriptor.
*/
errorret_t networksocketLinuxDisconnect(networksocket_t *socket);
/**
* Closes the file descriptor if still open (idempotent).
*/
errorret_t networksocketLinuxDispose(networksocket_t *socket);
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "networksocketlinux.h"
#define networksocketPlatformInit networksocketLinuxInit
#define networksocketPlatformConnect networksocketLinuxConnect
#define networksocketPlatformSend networksocketLinuxSend
#define networksocketPlatformRecv networksocketLinuxRecv
#define networksocketPlatformDisconnect networksocketLinuxDisconnect
#define networksocketPlatformDispose networksocketLinuxDispose
typedef networksocketlinux_t networksocketplatform_t;
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
systemlinux.c
)
+16
View File
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "systemlinux.h"
errorret_t systemInitLinux() {
errorOk();
}
systemdialogtype_t systemGetActiveDialogTypeLinux() {
return SYSTEM_DIALOG_TYPE_NONE;
}
+21
View File
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/system.h"
/**
* Initializes the Linux system module.
*/
errorret_t systemInitLinux(void);
/**
* Currently just returns SYSTEM_DIALOG_TYPE_NONE.
*
* @return Currently open system dialog type.
*/
systemdialogtype_t systemGetActiveDialogTypeLinux();
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/systemlinux.h"
#define systemInitPlatform systemInitLinux
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeLinux
+3 -1
View File
@@ -17,4 +17,6 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(network)
add_subdirectory(system)
+7
View File
@@ -7,6 +7,9 @@
#include "input/input.h" #include "input/input.h"
// #define INPUT_PSP_GAMEPAD_BUTTON_ACCEPT INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM
// #define INPUT_PSP_GAMEPAD_BUTTON_CANCEL INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM
inputbuttondata_t INPUT_BUTTON_DATA[] = { inputbuttondata_t INPUT_BUTTON_DATA[] = {
{ .name = "triangle", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_Y } }, { .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 = "cross", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_A } },
@@ -21,6 +24,10 @@ inputbuttondata_t INPUT_BUTTON_DATA[] = {
{ .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_LEFTSHOULDER } }, { .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 = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER } },
// Refer to systempsp.c for some extra info.
{ .name = "accept", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_A } },
{ .name = "cancel", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = SDL_CONTROLLER_BUTTON_B } },
{ .name = "lstick_down", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTY, .positive = true } } }, { .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_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_right", { .type = INPUT_BUTTON_TYPE_GAMEPAD_AXIS, .gpAxis = { .axis = SDL_CONTROLLER_AXIS_LEFTX, .positive = true } } },
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
networkpsp.c
)
+19
View File
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "networkpsp.h"
#define networkPlatformInit networkPSPInit
#define networkPlatformUpdate networkPSPUpdate
#define networkPlatformDispose networkPSPDispose
#define networkPlatformIsConnected networkPSPIsConnected
#define networkPlatformRequestConnection networkPSPRequestConnection
#define networkPlatformRequestDisconnection networkPSPRequestDisconnection
#define networkPlatformGetInfo networkPSPGetInfo
typedef networkpsp_t networkplatform_t;
+259
View File
@@ -0,0 +1,259 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "network/network.h"
#include "util/memory.h"
#include "util/string.h"
#include "assert/assert.h"
#include "display/displaysdl2.h"
errorret_t networkPSPInit() {
// Requests the PSP to load the network modules.
int ret;
ret = sceUtilityLoadNetModule(PSP_NET_MODULE_COMMON);
if(ret < 0) errorThrow("Failed to init NET COMMON: 0x%08X", ret);
ret = sceUtilityLoadNetModule(PSP_NET_MODULE_INET);
if(ret < 0) errorThrow("Failed to init NET INET: 0x%08X", ret);
// ret = sceUtilityLoadNetModule(PSP_NET_MODULE_PARSEURI);
// if(ret < 0) errorThrow("Failed to init NET PARSEURI: 0x%08X", ret);
// ret = sceUtilityLoadNetModule(PSP_NET_MODULE_PARSEHTTP);
// if(ret < 0) errorThrow("Failed to init NET PARSEHTTP: 0x%08X", ret);
// ret = sceUtilityLoadNetModule(PSP_NET_MODULE_SSL);
// if(ret < 0) errorThrow("Failed to init NET SSL: 0x%08X", ret);
// ret = sceUtilityLoadNetModule(PSP_NET_MODULE_HTTP);
// if(ret < 0) errorThrow("Failed to init NET HTTP: 0x%08X", ret);
// ret = sceSslInit(0x28000);
// if(ret < 0) errorThrow("sceSslInit failed: 0x%08X", ret);
// ret = sceHttpInit(0x25800);
// if(ret < 0) errorThrow("sceHttpInit failed: 0x%08X", ret);
// ret = sceHttpsInit(0, 0, 0, 0);
// if(ret < 0) errorThrow("sceHttpsInit failed: 0x%08X", ret);
// ret = sceHttpsLoadDefaultCert(0, 0);
// if(ret < 0) errorThrow("sceHttpsLoadDefaultCert failed: 0x%08X", ret);
// ret = sceHttpLoadSystemCookie();
// if(ret < 0) errorThrow("sceHttpLoadSystemCookie failed: 0x%08X", ret);
// All good.
errorOk();
}
errorret_t networkPSPUpdate() {
int ret;
if(NETWORK.state == NETWORK_STATE_CONNECTING) {
switch(sceUtilityNetconfGetStatus()) {
case PSP_UTILITY_DIALOG_INIT:
break;
case PSP_UTILITY_DIALOG_NONE:
NETWORK.state = NETWORK_STATE_DISCONNECTED;
errorThrow("PSP Netconf dialog disappeared without result");
break;
case PSP_UTILITY_DIALOG_VISIBLE:
// 1 is mandatory?
ret = sceUtilityNetconfUpdate(1);
if(ret != 0) {
errorThrow("sceUtilityNetconfUpdate failed: 0x%08X", ret);
}
break;
case PSP_UTILITY_DIALOG_QUIT:
ret = sceUtilityNetconfShutdownStart();
if(ret != 0) {
errorThrow("sceUtilityNetconfShutdownStart failed: 0x%08X", ret);
}
break;
case PSP_UTILITY_DIALOG_FINISHED:
// Did we connect?
int apState = 0;
sceNetApctlGetState(&apState);
if(apState == PSP_NET_APCTL_STATE_GOT_IP) {
NETWORK.state = NETWORK_STATE_CONNECTED;
assertNotNull(
NETWORK.platform.onConnected,
"Network platform onConnected callback should be set."
);
NETWORK.platform.onConnected(NETWORK.platform.onConnectedUser);
} else {
NETWORK.state = NETWORK_STATE_DISCONNECTED;
assertNotNull(
NETWORK.platform.onFailed,
"Network platform onFailed callback should be set."
);
// Kill the PSP network stack.
errorret_t err = networkPSPTerm();
if(err.code != ERROR_OK) {
errorCatch(errorPrint(err));
}
errorret_t error = errorThrowImpl(
&NETWORK.errorState,
ERROR_NOT_OK,
__FILE__, __func__, __LINE__,
"Failed to connect to network"
);
NETWORK.platform.onFailed(error, NETWORK.platform.onConnectedUser);
}
break;
default:
errorThrow("Unknown PSP Netconf dialog status: %d", sceUtilityNetconfGetStatus());
}
}
errorOk();
}
errorret_t networkPSPDispose() {
sceUtilityNetconfGetStatus();
errorCatch(errorPrint(networkPSPTerm()));
sceUtilityUnloadNetModule(PSP_NET_MODULE_HTTP);
sceUtilityUnloadNetModule(PSP_NET_MODULE_INET);
sceUtilityUnloadNetModule(PSP_NET_MODULE_COMMON);
errorOk();
}
bool_t networkPSPIsConnected() {
return NETWORK.state == NETWORK_STATE_CONNECTED;
}
void networkPSPRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
) {
assertTrue(
NETWORK.state == NETWORK_STATE_CONNECTING,
"Network host should be in a connecting state."
);
NETWORK.platform.onConnected = onConnected;
NETWORK.platform.onFailed = onFailed;
NETWORK.platform.onConnectedUser = user;
int ret;
// Init the PSP network stack.
ret = sceNetInit(0x20000, 0x20, 4096, 0x20, 4096);
assertTrue(ret >= 0, "Failed to init net: 0x%08X");
ret = sceNetInetInit();
assertTrue(ret >= 0, "Failed to init net inet: 0x%08X");
ret = sceNetResolverInit();
assertTrue(ret >= 0, "Failed to init net resolver: 0x%08X");
ret = sceNetApctlInit(0x1800, 0x30);
assertTrue(ret >= 0, "Failed to init net apctl: 0x%08X");
// This is all related to getting the PSP online, refer to;
// https://github.com/joel16/CMFileManager-PSP/blob/00dab16c64cd48bf6452fc274a3b898d77c39a8d/app/source/net.cpp#L97
// since I follow this implementation closely.
memoryZero(&NETWORK.platform.dialogData, sizeof(NETWORK.platform.dialogData));
memoryZero(&NETWORK.platform.dialogAdhoc, sizeof(NETWORK.platform.dialogAdhoc));
NETWORK.platform.dialogData.base.size = sizeof(pspUtilityNetconfData);
NETWORK.platform.dialogData.base.language = systemPSPGetLanguage();
NETWORK.platform.dialogData.base.buttonSwap = systemPSPGetCrossButtonSetting();
NETWORK.platform.dialogData.base.graphicsThread = 17;
NETWORK.platform.dialogData.base.accessThread = 19;
NETWORK.platform.dialogData.base.fontThread = 18;
NETWORK.platform.dialogData.base.soundThread = 16;
NETWORK.platform.dialogData.action = PSP_NETCONF_ACTION_CONNECTAP;
NETWORK.platform.dialogData.adhocparam = (
&NETWORK.platform.dialogAdhoc
);
ret = sceUtilityNetconfInitStart(&NETWORK.platform.dialogData);
assertTrue(ret >= 0, "Failed to init netconf");
}
void networkPSPRequestDisconnection(
void (*onComplete)(void *user),
void *user
) {
assertTrue(
NETWORK.state == NETWORK_STATE_DISCONNECTING,
"Network host should be in a disconnecting state."
);
errorret_t err = networkPSPTerm();
if(err.code != ERROR_OK) {
errorCatch(errorPrint(err));
}
NETWORK.state = NETWORK_STATE_DISCONNECTED;
onComplete(user);
}
errorret_t networkPSPTerm() {
int ret;
ret = sceNetApctlTerm();
if(ret < 0) {
errorThrow("Failed to terminate netctl: 0x%08X", ret);
}
ret = sceNetResolverTerm();
if(ret < 0) {
errorThrow("Failed to terminate net resolver: 0x%08X", ret);
}
ret = sceNetInetTerm();
if(ret < 0) {
errorThrow("Failed to terminate net inet: 0x%08X", ret);
}
ret = sceNetTerm();
if(ret < 0) {
errorThrow("Failed to terminate net: 0x%08X", ret);
}
errorOk();
}
networkinfo_t networkPSPGetInfo() {
networkinfo_t netInfo;
memoryZero(&netInfo, sizeof(networkinfo_t));
// Get the IP address of the current connection.
union SceNetApctlInfo info;
int ret = sceNetApctlGetInfo(PSP_NET_APCTL_INFO_IP, &info);
assertTrue(ret >= 0, "Failed to get IP address");
// Parse the IP address string into octets.
netInfo.type = NETWORK_TYPE_IPV4;
ret = sscanf(
info.ip,
"%hhu.%hhu.%hhu.%hhu",
&netInfo.ipv4.ip[0],
&netInfo.ipv4.ip[1],
&netInfo.ipv4.ip[2],
&netInfo.ipv4.ip[3]
);
assertTrue(ret == 4, "Failed to parse IP address");
return netInfo;
}
+110
View File
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "network/networkinfo.h"
#include "system/systempsp.h"
#include <psphttp.h>
#include <pspnet.h>
#include <pspnet_inet.h>
#include <pspnet_apctl.h>
#include <pspssl.h>
#include <pspnet_resolver.h>
#include <psphttp.h>
// #define NETWORK_HTTP_PENDING_MAX 4
// #define NETWORK_HTTP_URL_MAX 512
// #define NETWORK_HTTP_BODY_MAX 2048
// #define NETWORK_HTTP_RESPONSE_MAX 16384
// #define NETWORK_HTTP_HEADER_MAX 8
// #define NETWORK_HTTP_HEADER_KEY_MAX 64
// #define NETWORK_HTTP_HEADER_VAL_MAX 256
// #define NETWORK_ERROR_MESSAGE_MAX 256
// #define NETWORK_PSP_AGENT "DuskEngine/1.0"
typedef struct {
pspUtilityNetconfData dialogData;
struct pspUtilityNetconfAdhoc dialogAdhoc;
// Used during establishing connection
void *onConnectedUser;
void (*onConnected)(void *user);
void (*onFailed)(errorret_t error, void *user);
} networkpsp_t;
/**
* Initializes the PSP Network manager. This will NOT do network connecting,
* only prep it for being able to connect in future.
*
* @return Error state (if any).
*/
errorret_t networkPSPInit();
/**
* Called each frame for handling PSP requests, basically this is where all
* communication between the HTTP thread and the main thread happens.
*
* @return Error state (if any).
*/
errorret_t networkPSPUpdate();
/**
* Disposes the PSP Network manager, this will clean all resources and, if the
* network is connected, it will disconnect it safely.
*
* @return Error state (if any).
*/
errorret_t networkPSPDispose();
/**
* Checks if the PSP network is connected.
*
* @return True if the PSP is connected to a network, false otherwise.
*/
bool_t networkPSPIsConnected();
/**
* Requests the PSP to connect to a network (Shows the Wi-Fi connected).
*
* @param onConnected Callback connected successfully.
* @param onFailed Callback if the connection failed.
* @param onDisconnect Callback when connection is lost.
* @param user User data to pass to the callbacks.
*/
void networkPSPRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
);
/**
* Requests the PSP to disconnect from the network (Shows the Wi-Fi disconnecting).
*
* @param onComplete Callback when disconnection is complete.
* @param user User data to pass to the callback.
*/
void networkPSPRequestDisconnection(
void (*onComplete)(void *user),
void *user
);
/**
* Disposes the PSP sce net libraries, doesn't unload the modules and won't
* term the dialog if it's active.
*
* @return Error state (if any).
*/
errorret_t networkPSPTerm();
/**
* Gets the network information for the currently active network connection.
*
* @return Network information for the currently active network connection.
*/
networkinfo_t networkPSPGetInfo();
+9
View File
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
systempsp.c
)
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/systempsp.h"
#define systemInitPlatform systemInitPSP
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypePSP
+64
View File
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "systempsp.h"
#include "input/input.h"
#include "util/string.h"
#include "assert/assert.h"
errorret_t systemInitPSP() {
// Bind ACCEPT and CANCEL binds.
inputbuttondata_t *buttonCross, *buttonCircle, *buttonAccept, *buttonCancel;
inputbuttondata_t *i = INPUT_BUTTON_DATA;
while(i->name) {
if(stringCompare(i->name, "cross") == 0) {
buttonCross = i;
} else if(stringCompare(i->name, "circle") == 0) {
buttonCircle = i;
} else if(stringCompare(i->name, "accept") == 0) {
buttonAccept = i;
} else if(stringCompare(i->name, "cancel") == 0) {
buttonCancel = i;
}
i++;
}
assertNotNull(buttonCross, "Cross button not found!");
assertNotNull(buttonCircle, "Circle button not found!");
assertNotNull(buttonAccept, "Accept button not found!");
assertNotNull(buttonCancel, "Cancel button not found!");
if(systemPSPGetCrossButtonSetting() == PSP_UTILITY_ACCEPT_CROSS) {
buttonAccept->button.gpButton = buttonCross->button.gpButton;
buttonCancel->button.gpButton = buttonCircle->button.gpButton;
} else {
buttonAccept->button.gpButton = buttonCircle->button.gpButton;
buttonCancel->button.gpButton = buttonCross->button.gpButton;
}
errorOk();
}
systemdialogtype_t systemGetActiveDialogTypePSP() {
return SYSTEM_DIALOG_TYPE_NONE;
}
int systemPSPGetLanguage() {
int ret;
sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_LANGUAGE, &ret);
return ret;
}
int systemPSPGetCrossButtonSetting() {
int ret;
// See: https://pspdev.github.io/pspsdk/psputility__sysparam_8h.html#ab588fd5a14adc025f065e09325ffe729
sceUtilityGetSystemParamInt(PSP_SYSTEMPARAM_ID_INT_UNKNOWN, &ret);
return (
ret == 1 ? PSP_UTILITY_ACCEPT_CROSS : PSP_UTILITY_ACCEPT_CIRCLE
);
}
+40
View File
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "system/system.h"
#include <psputility.h>
/**
* Initializes the PSP system module.
*
* @return Error code indicating success or failure.
*/
errorret_t systemInitPSP(void);
/**
* Returns which PSP system dialog is currently open (if any).
*
* @return Currently open system dialog type.
*/
systemdialogtype_t systemGetActiveDialogTypePSP();
/**
* Returns the PSP_SYSTEMPARAM language for the current system.
*
* @return PSP_SYSTEMPARAM language value.
*/
int systemPSPGetLanguage();
/**
* Returns the user's setting for the PSP "Cross Button" configuration, which
* determines whether the "Cross" or "Circle" button is used for "Accept"
* actions in system dialogs.
*
* @return PSP_UTILITY_ACCEPT_CROSS or PSP_UTILITY_ACCEPT_CIRCLE.
*/
int systemPSPGetCrossButtonSetting();
+5
View File
@@ -73,6 +73,11 @@ float_t inputButtonGetValueSDL2(const inputbutton_t button) {
#ifdef DUSK_INPUT_GAMEPAD #ifdef DUSK_INPUT_GAMEPAD
case INPUT_BUTTON_TYPE_GAMEPAD: { case INPUT_BUTTON_TYPE_GAMEPAD: {
assertTrue(
button.gpButton < SDL_CONTROLLER_BUTTON_MAX,
"Gamepad button out of range"
);
if(SDL_GameControllerGetButton( if(SDL_GameControllerGetButton(
INPUT.platform.controller, button.gpButton INPUT.platform.controller, button.gpButton
)) { )) {
+20
View File
@@ -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)
+10
View File
@@ -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
)
+13
View File
@@ -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
+22
View File
@@ -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();
}
+30
View File
@@ -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);
+16
View File
@@ -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
+22
View File
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <vitasdk.h>
#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
#define GL_GLEXT_PROTOTYPES
typedef uint32_t GLbitfield;
#include "dusksdl2.h"
+10
View File
@@ -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
)
+39
View File
@@ -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;
}
+10
View File
@@ -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
)
+52
View File
@@ -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);
}
@@ -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
@@ -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");
}