Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c71df5bfd | |||
| 4205899f5a | |||
| 7dd3940770 | |||
| 00d94e3015 | |||
| 7bacb3ee2b | |||
| 8e49be5ac4 | |||
| 3b94598d2c | |||
| bddc9af3b6 | |||
| 2451d73a7c | |||
| 1dd2efa182 | |||
| acea610773 | |||
| 8f2f1fd496 | |||
| 39c775872a | |||
| bdb3cbd109 | |||
| ff84ce2b04 | |||
| 225f405592 | |||
| 715ecffa18 | |||
| e51cdc8992 | |||
| 133685ea37 | |||
| 6aff98d555 | |||
| acdc524284 | |||
| 1ee5ec7b43 |
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -3,3 +3,8 @@ 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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -22,6 +23,11 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
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
|
||||||
)
|
)
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,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"]
|
||||||
@@ -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"]
|
||||||
Executable
+3
@@ -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"
|
||||||
Executable
+13
@@ -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
@@ -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
@@ -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()
|
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
+129
-10
@@ -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,9 +169,9 @@ 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);
|
||||||
@@ -81,7 +191,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
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);
|
||||||
@@ -90,9 +200,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
|
|
||||||
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. */
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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
@@ -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 ] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,3 +51,57 @@ uint64_t endianLittleToHost64(uint64_t value);
|
|||||||
* @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);
|
||||||
@@ -19,3 +19,5 @@ 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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,3 +9,11 @@
|
|||||||
#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
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
// For some platforms (Vita) we do not include GL extensions.
|
||||||
|
#ifndef GL_GLEXT_PROTOTYPES
|
||||||
#define GL_GLEXT_PROTOTYPES
|
#define GL_GLEXT_PROTOTYPES
|
||||||
#include <GL/gl.h>
|
#include <GL/gl.h>
|
||||||
#include <GL/glext.h>
|
#include <GL/glext.h>
|
||||||
|
#else
|
||||||
|
#include <GL/gl.h>
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@@ -13,3 +13,5 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
add_subdirectory(asset)
|
add_subdirectory(asset)
|
||||||
add_subdirectory(log)
|
add_subdirectory(log)
|
||||||
add_subdirectory(input)
|
add_subdirectory(input)
|
||||||
|
add_subdirectory(network)
|
||||||
|
add_subdirectory(system)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
@@ -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
|
||||||
@@ -18,3 +18,5 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
|
|||||||
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,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 } } },
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
@@ -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
|
||||||
)) {
|
)) {
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user