diff --git a/cmake/targets/saturn.cmake b/cmake/targets/saturn.cmake index 15ca97af..3e338ab6 100644 --- a/cmake/targets/saturn.cmake +++ b/cmake/targets/saturn.cmake @@ -85,22 +85,100 @@ target_link_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE ) target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE + # --start-group/--end-group allow circular archive references + # (e.g. libyaul allocator symbols referenced by libzip / yyjson objects). + -Wl,--start-group "${_YAUL_SYSROOT}/lib/libyaul.a" "${_YAUL_SYSROOT}/lib/libzip.a" "${_YAUL_SYSROOT}/lib/libz.a" - m + # GCC soft-float / soft-divide runtime (SH-2 has no FPU or hw divider). + # -lgcc is resolved by the compiler driver even with -nodefaultlibs. + -lgcc + -Wl,--end-group ) +# Yaul linker script (Saturn SH-2 memory map) and startup objects. +# Startup objects must precede all other objects so use target_link_options +# (those flags appear before the object-file list in cmake's link command). +target_link_options(${DUSK_BINARY_TARGET_NAME} PRIVATE + "-T${_YAUL_SYSROOT}/lib/ldscripts/yaul.x" + "${_YAUL_SYSROOT}/lib/crt0.o" + "${_YAUL_SYSROOT}/lib/init.o" + # Provide SH-2 stack top addresses required by crt0.o/cpu_dual_entries.o. + # RAM region: 0x06004000 .. 0x06100000 (~1 MB). + # Master stack grows down from the very top; slave CPU gets 4 KB below. + "-Wl,--defsym=___master_stack=0x06100000" + "-Wl,--defsym=___slave_stack=0x060FF000" +) + + # --------------------------------------------------------------------------- -# Post-build: ELF → binary image -# sh2eb-elf-objcopy converts the ELF to a flat binary that IP.BIN loads -# into Saturn Work RAM. +# Post-build: ELF → binary → Saturn disc image (ISO + CUE) # --------------------------------------------------------------------------- -set(DUSK_SAT_BIN "${CMAKE_BINARY_DIR}/Dusk.bin") +set(DUSK_SAT_BIN "${CMAKE_BINARY_DIR}/Dusk.bin") +set(DUSK_SAT_IP_BIN "${CMAKE_BINARY_DIR}/IP.BIN") +set(DUSK_SAT_CD_DIR "${CMAKE_BINARY_DIR}/cd") +set(DUSK_SAT_AUDIO_DIR "${CMAKE_BINARY_DIR}/audio-tracks") +set(DUSK_SAT_ISO "${CMAKE_BINARY_DIR}/Dusk.iso") +set(DUSK_SAT_CUE "${CMAKE_BINARY_DIR}/Dusk.cue") + +# Step 1: ELF → flat binary (loaded into Work RAM by IP.BIN) add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD COMMAND "${_YAUL_BIN}/sh2eb-elf-objcopy" -O binary "$" "${DUSK_SAT_BIN}" - COMMENT "Converting ${DUSK_BINARY_TARGET_NAME} ELF → ${DUSK_SAT_BIN}" + COMMENT "Converting ELF → ${DUSK_SAT_BIN}" +) + +# Step 2: flat binary → IP.BIN (boot header with region security blobs) +# make-ip writes IP.BIN into dirname(arg1), so it lands at ${CMAKE_BINARY_DIR}/IP.BIN. +# Pass -1 for 1st-read-size so make-ip uses the actual file size. +add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env + "YAUL_INSTALL_ROOT=${YAUL_INSTALL_ROOT}" + "YAUL_ARCH_SH_PREFIX=sh2eb-elf" + "${_YAUL_BIN}/make-ip" + "${DUSK_SAT_BIN}" + "V1.000" + "20260101" + "JTUE" + "J" + "DUSK" + "0x06100000" + "0x060FF000" + "0x06004000" + "-1" + COMMENT "Generating IP.BIN" +) + +# Step 3: build CD filesystem tree and create ISO 9660 image +add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_SAT_CD_DIR}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_SAT_AUDIO_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy "${DUSK_SAT_BIN}" "${DUSK_SAT_CD_DIR}/A.BIN" + # ISO 9660 requires these auxiliary text files + COMMAND ${CMAKE_COMMAND} -E touch "${DUSK_SAT_CD_DIR}/ABS.TXT" + COMMAND ${CMAKE_COMMAND} -E touch "${DUSK_SAT_CD_DIR}/BIB.TXT" + COMMAND ${CMAKE_COMMAND} -E touch "${DUSK_SAT_CD_DIR}/CPY.TXT" + COMMAND ${CMAKE_COMMAND} -E env + "YAUL_INSTALL_ROOT=${YAUL_INSTALL_ROOT}" + "MAKE_ISO_XORRISOFS=/usr/bin/xorrisofs" + "${_YAUL_BIN}/make-iso" + "${DUSK_SAT_CD_DIR}" + "${DUSK_SAT_IP_BIN}" + "${CMAKE_BINARY_DIR}" + "Dusk" + COMMENT "Generating ${DUSK_SAT_ISO}" +) + +# Step 4: ISO → CUE sheet (rename .iso → .bin for a classic BIN/CUE pair +# if desired; emulators accept .iso+.cue as-is) +add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E env + "YAUL_INSTALL_ROOT=${YAUL_INSTALL_ROOT}" + "${_YAUL_BIN}/make-cue" + "${DUSK_SAT_AUDIO_DIR}" + "${DUSK_SAT_ISO}" + COMMENT "Generating ${DUSK_SAT_CUE}" ) diff --git a/cmake/toolchains/saturn.cmake b/cmake/toolchains/saturn.cmake index e268a2cf..6de23f62 100644 --- a/cmake/toolchains/saturn.cmake +++ b/cmake/toolchains/saturn.cmake @@ -48,13 +48,16 @@ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) -# SH-2 core flags: big-endian (-mb), SH-2 ISA (-m2), no FPU -set(_SAT_C_FLAGS "-m2 -mb -fno-builtin -fomit-frame-pointer") +# SH-2 core flags: big-endian (-mb), SH-2 ISA (-m2), no FPU. +# -ffunction-sections/-fdata-sections enable --gc-sections to eliminate +# unreachable code at the function level (e.g. yyjson FILE* API paths). +set(_SAT_C_FLAGS "-m2 -mb -fno-builtin -fomit-frame-pointer -ffunction-sections -fdata-sections -Os") set(CMAKE_C_FLAGS_INIT "${_SAT_C_FLAGS}") -# Yaul installs yaul.specs to ${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/ and -# ldscripts/yaul.x to ${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/ldscripts/. -# GCC searches ${prefix}/${target}/lib/ for specs, so -specs=yaul.specs works -# without an absolute path once the SDK is installed. +# Linker flags: gc-sections, no startup/default libs (Yaul provides crt0/init; +# we supply all needed libraries explicitly). -nodefaultlibs prevents cmake/gcc +# from injecting -lc (which does not exist in the Yaul sysroot). +# --start-group/--end-group are placed in target_link_libraries in +# targets/saturn.cmake so they actually wrap the archive list. set(CMAKE_EXE_LINKER_FLAGS_INIT - "-specs=yaul.specs -Wl,--gc-sections -nostartfiles -Wl,--start-group -Wl,--end-group") + "-Wl,--gc-sections -nostartfiles -nodefaultlibs") diff --git a/docker/saturn/Dockerfile b/docker/saturn/Dockerfile index 63f67937..e9ac8cdf 100644 --- a/docker/saturn/Dockerfile +++ b/docker/saturn/Dockerfile @@ -40,6 +40,7 @@ RUN apt-get update && apt-get install -y \ libgmp-dev \ libmpfr-dev \ libmpc-dev \ + xorriso \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p "${YAUL_INSTALL_ROOT}" @@ -149,6 +150,7 @@ RUN for tool in "${YAUL_INSTALL_ROOT}/bin/m68k-elf-"*; do \ # --------------------------------------------------------------------------- RUN git clone --depth 1 --recurse-submodules \ https://github.com/yaul-org/libyaul.git /tmp/yaul && \ + mkdir -p /tmp/yaul-build && \ cd /tmp/yaul && \ YAUL_INSTALL_ROOT="${YAUL_INSTALL_ROOT}" \ YAUL_ARCH_SH_PREFIX=sh2eb-elf \ @@ -161,11 +163,134 @@ RUN git clone --depth 1 --recurse-submodules \ rm -rf /tmp/yaul /tmp/yaul-build # --------------------------------------------------------------------------- -# 11. Cross-compile zlib for sh2eb-elf via CMake -# Using CMake (not ./configure) so we can set -# CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY and skip link tests that -# the bare-metal cross-compiler can't satisfy. -# Install into ${YAUL_INSTALL_ROOT}/sh2eb-elf/ (Yaul sysroot layout). +# 11. Provide a freestanding stdint.h in the Yaul sysroot. +# GCC 12 --without-headers installs a stdint.h wrapper that emits +# `#include_next `, but Yaul's sysroot has no system stdint.h +# to satisfy that lookup. Yaul's own errno.h (and anything it pulls in) +# will fail to compile in any external cmake project without this stub. +# --------------------------------------------------------------------------- +RUN test -f "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/stdint.h" || \ + cp /opt/yaul/lib/gcc/sh2eb-elf/12.3.0/include/stdint-gcc.h \ + "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/stdint.h" 2>/dev/null || \ + printf '%s\n' \ + '#pragma once' \ + '#ifndef __STDINT_H' \ + '#define __STDINT_H' \ + 'typedef signed char int8_t;' \ + 'typedef signed short int16_t;' \ + 'typedef signed int int32_t;' \ + 'typedef signed long long int64_t;' \ + 'typedef unsigned char uint8_t;' \ + 'typedef unsigned short uint16_t;' \ + 'typedef unsigned int uint32_t;' \ + 'typedef unsigned long long uint64_t;' \ + 'typedef int8_t int_least8_t;' \ + 'typedef int16_t int_least16_t;' \ + 'typedef int32_t int_least32_t;' \ + 'typedef int64_t int_least64_t;' \ + 'typedef uint8_t uint_least8_t;' \ + 'typedef uint16_t uint_least16_t;' \ + 'typedef uint32_t uint_least32_t;' \ + 'typedef uint64_t uint_least64_t;' \ + 'typedef int32_t int_fast8_t;' \ + 'typedef int32_t int_fast16_t;' \ + 'typedef int32_t int_fast32_t;' \ + 'typedef int64_t int_fast64_t;' \ + 'typedef uint32_t uint_fast8_t;' \ + 'typedef uint32_t uint_fast16_t;' \ + 'typedef uint32_t uint_fast32_t;' \ + 'typedef uint64_t uint_fast64_t;' \ + 'typedef int32_t intptr_t;' \ + 'typedef uint32_t uintptr_t;' \ + 'typedef int64_t intmax_t;' \ + 'typedef uint64_t uintmax_t;' \ + '#define INT8_MIN (-128)' \ + '#define INT16_MIN (-32768)' \ + '#define INT32_MIN (-2147483647-1)' \ + '#define INT64_MIN (-9223372036854775807LL-1)' \ + '#define INT8_MAX (127)' \ + '#define INT16_MAX (32767)' \ + '#define INT32_MAX (2147483647)' \ + '#define INT64_MAX (9223372036854775807LL)' \ + '#define UINT8_MAX (255U)' \ + '#define UINT16_MAX (65535U)' \ + '#define UINT32_MAX (4294967295U)' \ + '#define UINT64_MAX (18446744073709551615ULL)' \ + '#define INTPTR_MIN INT32_MIN' \ + '#define INTPTR_MAX INT32_MAX' \ + '#define UINTPTR_MAX UINT32_MAX' \ + '#define INTMAX_MIN INT64_MIN' \ + '#define INTMAX_MAX INT64_MAX' \ + '#define UINTMAX_MAX UINT64_MAX' \ + '#define SIZE_MAX UINT32_MAX' \ + '#define INT8_C(c) (c)' \ + '#define INT16_C(c) (c)' \ + '#define INT32_C(c) (c)' \ + '#define INT64_C(c) (c ## LL)' \ + '#define UINT8_C(c) (c ## U)' \ + '#define UINT16_C(c) (c ## U)' \ + '#define UINT32_C(c) (c ## U)' \ + '#define UINT64_C(c) (c ## ULL)' \ + '#define INTMAX_C(c) (c ## LL)' \ + '#define UINTMAX_C(c)(c ## ULL)' \ + '#endif' \ + > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/stdint.h" + +# --------------------------------------------------------------------------- +# 11b. Stubs for non-standard / bare-metal-missing headers. +# Yaul's sysroot only ships a minimal libc; third-party libraries such as +# libzip need these POSIX/GNU headers to compile. +# --------------------------------------------------------------------------- +RUN printf '#pragma once\n#include \n' \ + > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/malloc.h" && \ + printf '#pragma once\n#include \n' \ + > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/memory.h" && \ + printf '%s\n' \ + '#pragma once' \ + '#ifndef _TIME_H' \ + '#define _TIME_H' \ + 'typedef long time_t;' \ + 'typedef long clock_t;' \ + '#define CLOCKS_PER_SEC 1000L' \ + 'struct tm {' \ + ' int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year;' \ + ' int tm_wday, tm_yday, tm_isdst;' \ + '};' \ + 'static inline time_t time(time_t *t) { if(t) *t=0; return 0; }' \ + 'static inline time_t mktime(struct tm *t) { (void)t; return 0; }' \ + 'static inline struct tm *localtime(const time_t *t) { (void)t; return 0; }' \ + 'static inline struct tm *gmtime(const time_t *t) { (void)t; return 0; }' \ + '#endif' \ + > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/time.h" && \ + printf '%s\n' \ + '#pragma once' \ + '#ifndef _SYS_STAT_H' \ + '#define _SYS_STAT_H' \ + '#include ' \ + '#include ' \ + 'typedef unsigned int dev_t;' \ + 'typedef unsigned int ino_t;' \ + 'typedef unsigned short nlink_t;' \ + 'typedef long blksize_t;' \ + 'typedef long blkcnt_t;' \ + 'struct stat {' \ + ' dev_t st_dev; ino_t st_ino; mode_t st_mode; nlink_t st_nlink;' \ + ' uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size;' \ + ' blksize_t st_blksize; blkcnt_t st_blocks;' \ + ' time_t st_atime, st_mtime, st_ctime;' \ + '};' \ + 'static inline int stat(const char *p, struct stat *b){(void)p;(void)b;return -1;}' \ + 'static inline int fstat(int f, struct stat *b){(void)f;(void)b;return -1;}' \ + '#define S_ISREG(m) (((m)&0170000)==0100000)' \ + '#define S_ISDIR(m) (((m)&0170000)==0040000)' \ + '#define S_IRUSR 0400' '#define S_IWUSR 0200' '#define S_IXUSR 0100' \ + '#endif' \ + > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/sys/stat.h" + +# --------------------------------------------------------------------------- +# 12. sat-xc.cmake — shared CMake toolchain for zlib and libzip. +# CMAKE_SYSROOT tells cmake to pass --sysroot to the compiler so that +# GCC's #include_next wrappers resolve against the Yaul sysroot headers. # --------------------------------------------------------------------------- RUN printf '%s\n' \ 'set(CMAKE_SYSTEM_NAME Generic)' \ @@ -173,27 +298,40 @@ RUN printf '%s\n' \ "set(CMAKE_C_COMPILER \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc\")" \ "set(CMAKE_AR \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar\")" \ "set(CMAKE_RANLIB \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ranlib\")" \ + "set(CMAKE_SYSROOT \"${YAUL_INSTALL_ROOT}/sh2eb-elf\")" \ 'set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)' \ 'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)' \ 'set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)' \ 'set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)' \ > /tmp/sat-xc.cmake -RUN wget -q https://zlib.net/zlib-1.3.1.tar.gz -O /tmp/zlib.tar.gz && \ +# --------------------------------------------------------------------------- +# 13. Cross-compile zlib for sh2eb-elf (inflate/deflate core only). +# We compile the nine core source files directly rather than via cmake +# to avoid the gz* files whose POSIX gzip-streaming API is both unused +# (libzip only needs inflate/deflate) and cannot compile on bare metal +# (gzguts.h -> errno.h -> stdint.h chain, and zutil.h includes gzguts.h +# unless NO_GZIP is defined). +# --------------------------------------------------------------------------- +RUN wget -q https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz -O /tmp/zlib.tar.gz && \ tar xf /tmp/zlib.tar.gz -C /tmp && \ - cmake -S /tmp/zlib-1.3.1 -B /tmp/zlib-build \ - -DCMAKE_TOOLCHAIN_FILE=/tmp/sat-xc.cmake \ - -DCMAKE_INSTALL_PREFIX="${YAUL_INSTALL_ROOT}/sh2eb-elf" \ - -DCMAKE_C_FLAGS="-m2 -mb -fno-builtin -O2" \ - -DBUILD_SHARED_LIBS=OFF \ - && cmake --build /tmp/zlib-build -- -j"$(nproc)" \ - && cmake --install /tmp/zlib-build \ - && rm -rf /tmp/zlib-1.3.1 /tmp/zlib.tar.gz /tmp/zlib-build + cd /tmp/zlib-1.3.1 && \ + for f in adler32.c compress.c crc32.c deflate.c infback.c \ + inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c; do \ + "${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc" \ + -m2 -mb -fno-builtin -O2 -DNO_GZIP -DSTDC -I. -c "$f" -o "${f%.c}.o"; \ + done && \ + "${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar" rcs \ + "${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/libz.a" \ + adler32.o compress.o crc32.o deflate.o infback.o inffast.o \ + inflate.o inftrees.o trees.o uncompr.o zutil.o && \ + install -m644 zlib.h zconf.h "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/" && \ + rm -rf /tmp/zlib-1.3.1 /tmp/zlib.tar.gz # --------------------------------------------------------------------------- -# 12. Cross-compile libzip for sh2eb-elf (reuses /tmp/sat-xc.cmake above) +# 14. Cross-compile libzip for sh2eb-elf (reuses /tmp/sat-xc.cmake above) # --------------------------------------------------------------------------- -RUN wget -q https://libzip.org/download/libzip-1.10.1.tar.gz -O /tmp/libzip.tar.gz && \ +RUN wget -q https://github.com/nih-at/libzip/releases/download/v1.10.1/libzip-1.10.1.tar.gz -O /tmp/libzip.tar.gz && \ tar xf /tmp/libzip.tar.gz -C /tmp && \ cmake -S /tmp/libzip-1.10.1 -B /tmp/libzip-build \ -DCMAKE_TOOLCHAIN_FILE=/tmp/sat-xc.cmake \ @@ -212,9 +350,13 @@ RUN wget -q https://libzip.org/download/libzip-1.10.1.tar.gz -O /tmp/libzip.tar. -DBUILD_DOCUMENTATION=OFF \ -DBUILD_REGRESS=OFF \ -DBUILD_TOOLS=OFF \ + -DHAVE_CLONEFILE=0 \ + -DHAVE_ARC4RANDOM=0 \ + -DHAVE_GETPROGNAME=0 \ && cmake --build /tmp/libzip-build -- -j"$(nproc)" \ && cmake --install /tmp/libzip-build \ - && rm -rf /tmp/libzip-1.10.1 /tmp/libzip.tar.gz /tmp/libzip-build /tmp/sat-xc.cmake /tmp/zlib.tar.gz 2>/dev/null || true + && rm -rf /tmp/libzip-1.10.1 /tmp/libzip-build /tmp/sat-xc.cmake \ + ; rm -f /tmp/libzip.tar.gz /tmp/zlib.tar.gz 2>/dev/null ; true WORKDIR /workdir VOLUME ["/workdir"] diff --git a/scripts/build-saturn.sh b/scripts/build-saturn.sh index ffe7c83a..865cc276 100755 --- a/scripts/build-saturn.sh +++ b/scripts/build-saturn.sh @@ -11,6 +11,8 @@ if [ -z "$YAUL_INSTALL_ROOT" ]; then fi mkdir -p build-saturn +# Remove stale cache so toolchain flag changes (CMAKE_*_FLAGS_INIT) take effect. +rm -f build-saturn/CMakeCache.txt cmake -S . -B build-saturn \ -DDUSK_TARGET_SYSTEM=saturn \ -DCMAKE_TOOLCHAIN_FILE="cmake/toolchains/saturn.cmake" \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4c5b4ce0..f9987cef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,6 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecub add_subdirectory(duskdolphin) elseif(DUSK_TARGET_SYSTEM STREQUAL "saturn") - add_subdirectory(dusksat) + add_subdirectory(dusksaturn) endif() \ No newline at end of file diff --git a/src/dusk/dusk.h b/src/dusk/dusk.h index c004dc33..af23af69 100644 --- a/src/dusk/dusk.h +++ b/src/dusk/dusk.h @@ -29,4 +29,5 @@ typedef bool bool_t; typedef int int_t; typedef float float_t; +typedef double double_t; typedef char char_t; \ No newline at end of file diff --git a/src/dusk/input/input.c b/src/dusk/input/input.c index 9c65586c..111c5f6d 100644 --- a/src/dusk/input/input.c +++ b/src/dusk/input/input.c @@ -20,8 +20,8 @@ errorret_t inputInit(void) { for(uint8_t i = 0; i < INPUT_ACTION_COUNT; i++) { INPUT.actions[i].action = (inputaction_t)i; - INPUT.actions[i].lastValue = 0.0f; - INPUT.actions[i].currentValue = 0.0f; + INPUT.actions[i].lastValue = 0; + INPUT.actions[i].currentValue = 0; eventInit( &INPUT.actions[i].onPressed, @@ -55,14 +55,14 @@ void inputUpdate(void) { do { #ifdef DUSK_TIME_DYNAMIC action->lastDynamicValue = action->currentDynamicValue; - action->currentDynamicValue = 0.0f; + action->currentDynamicValue = 0; if(!TIME.dynamicUpdate) { action->lastValue = action->currentValue; - action->currentValue = 0.0f; + action->currentValue = 0; } #else action->lastValue = action->currentValue; - action->currentValue = 0.0f; + action->currentValue = 0; #endif action++; @@ -74,7 +74,7 @@ void inputUpdate(void) { cur->lastVal = cur->curVal; cur->curVal = inputButtonGetValue(cur->button); - if(cur->curVal == 0.0f) { + if(cur->curVal == 0) { cur++; continue; } @@ -104,14 +104,14 @@ void inputUpdate(void) { for(uint8_t i = INPUT_ACTION_NULL + 1; i < INPUT_ACTION_COUNT; i++) { inputactiondata_t *act = &INPUT.actions[i]; - bool_t isDown = act->currentValue > 0.0f; - bool_t wasDown = act->lastValue > 0.0f; + bool_t isDown = act->currentValue > 0; + bool_t wasDown = act->lastValue > 0; if(isDown && !wasDown) eventInvoke(&act->onPressed, act); if(!isDown && wasDown) eventInvoke(&act->onReleased, act); } } -float_t inputGetCurrentValue(const inputaction_t action) { +int16_t inputGetCurrentValue(const inputaction_t action) { #ifdef DUSK_TIME_DYNAMIC if(TIME.dynamicUpdate) return inputGetCurrentValueDynamic(action); #endif @@ -120,33 +120,33 @@ float_t inputGetCurrentValue(const inputaction_t action) { return INPUT.actions[action].currentValue; } -float_t inputGetLastValue(const inputaction_t action) { +int16_t inputGetLastValue(const inputaction_t action) { #ifdef DUSK_TIME_DYNAMIC if(TIME.dynamicUpdate) return inputGetLastValueDynamic(action); #endif - + assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); return INPUT.actions[action].lastValue; } #ifdef DUSK_TIME_DYNAMIC - float_t inputGetCurrentValueDynamic(const inputaction_t action) { + int16_t inputGetCurrentValueDynamic(const inputaction_t action) { assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); return INPUT.actions[action].currentDynamicValue; } - float_t inputGetLastValueDynamic(const inputaction_t action) { + int16_t inputGetLastValueDynamic(const inputaction_t action) { assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); return INPUT.actions[action].lastDynamicValue; } #endif bool_t inputIsDown(const inputaction_t action) { - return inputGetCurrentValue(action) > 0.0f; + return inputGetCurrentValue(action) > 0; } bool_t inputWasDown(const inputaction_t action) { - return inputGetLastValue(action) > 0.0f; + return inputGetLastValue(action) > 0; } bool_t inputPressed(const inputaction_t action) { @@ -157,11 +157,11 @@ bool_t inputReleased(const inputaction_t action) { return !inputIsDown(action) && inputWasDown(action); } -float_t inputAxis(const inputaction_t neg, const inputaction_t pos) { +int16_t inputAxis(const inputaction_t neg, const inputaction_t pos) { assertTrue(neg < INPUT_ACTION_COUNT, "Negative input action out of bounds"); assertTrue(pos < INPUT_ACTION_COUNT, "Positive input action out of bounds"); - return inputGetCurrentValue(pos) - inputGetCurrentValue(neg); + return (int16_t)(inputGetCurrentValue(pos) - inputGetCurrentValue(neg)); } void inputAxis2D( @@ -170,8 +170,8 @@ void inputAxis2D( vec2 result ) { assertNotNull(result, "Result vector cannot be null"); - result[0] = inputAxis(negX, posX); - result[1] = inputAxis(negY, posY); + result[0] = (float_t)inputAxis(negX, posX) / (float_t)INT16_MAX; + result[1] = (float_t)inputAxis(negY, posY) / (float_t)INT16_MAX; } void inputAngle2D( @@ -180,8 +180,8 @@ void inputAngle2D( vec2 result ) { assertNotNull(result, "Result vector cannot be null"); - float_t x = inputAxis(negX, posX); - float_t y = inputAxis(negY, posY); + float_t x = (float_t)inputAxis(negX, posX) / (float_t)INT16_MAX; + float_t y = (float_t)inputAxis(negY, posY) / (float_t)INT16_MAX; float_t mag = sqrtf(x * x + y * y); if(mag <= 0.0f) { result[0] = 0.0f; @@ -211,7 +211,7 @@ void inputBind(const inputbutton_t button, const inputaction_t act) { data->action = act; } -float_t inputDeadzone(const float_t rawValue, const float_t deadzone) { - if(rawValue < deadzone) return 0.0f; - return (rawValue - deadzone) / (1.0f - deadzone); +int16_t inputDeadzone(const int16_t rawValue, const int16_t deadzone) { + if(rawValue < deadzone) return 0; + return (int16_t)(((int32_t)(rawValue - deadzone) * INT16_MAX) / (INT16_MAX - deadzone)); } \ No newline at end of file diff --git a/src/dusk/input/input.h b/src/dusk/input/input.h index 2ecbbdc8..049fe77b 100644 --- a/src/dusk/input/input.h +++ b/src/dusk/input/input.h @@ -35,36 +35,36 @@ void inputUpdate(void); /** * Gets the current value of a specific input action. - * + * * @param action The input action to get the value for. - * @return The current value of the action (0.0f to 1.0f). + * @return The current value of the action (0 to INT16_MAX). */ -float_t inputGetCurrentValue(const inputaction_t action); +int16_t inputGetCurrentValue(const inputaction_t action); /** * Gets the last value of a specific input action. - * + * * @param action The input action to get the value for. - * @return The last value of the action (0.0f to 1.0f). + * @return The last value of the action (0 to INT16_MAX). */ -float_t inputGetLastValue(const inputaction_t action); +int16_t inputGetLastValue(const inputaction_t action); #ifdef DUSK_TIME_DYNAMIC /** * Gets the current value of a specific input action (dynamic timestep). - * + * * @param action The input action to get the value for. - * @return The current value of the action (0.0f to 1.0f). + * @return The current value of the action (0 to INT16_MAX). */ - float_t inputGetCurrentValueDynamic(const inputaction_t action); + int16_t inputGetCurrentValueDynamic(const inputaction_t action); /** * Gets the last value of a specific input action (dynamic timestep). - * + * * @param action The input action to get the value for. - * @return The last value of the action (0.0f to 1.0f). + * @return The last value of the action (0 to INT16_MAX). */ - float_t inputGetLastValueDynamic(const inputaction_t action); + int16_t inputGetLastValueDynamic(const inputaction_t action); #endif /** @@ -103,12 +103,12 @@ bool_t inputReleased(const inputaction_t action); /** * Gets the value of an input axis, defined by two actions (negative and * positive). - * + * * @param neg The action representing the negative direction of the axis. * @param pos The action representing the positive direction of the axis. - * @return The current value of the axis (-1.0f to 1.0f). + * @return The current value of the axis (-INT16_MAX to INT16_MAX). */ -float_t inputAxis(const inputaction_t neg, const inputaction_t pos); +int16_t inputAxis(const inputaction_t neg, const inputaction_t pos); /** * Gets the values of a 2D input axis, defined by two pairs of actions (negative @@ -151,12 +151,12 @@ void inputAngle2D( void inputBind(const inputbutton_t button, const inputaction_t act); /** - * Applies deadzone formula to a raw input value. This will; - * return 0 if input is less than the deadzone. - * re-map the range between 0 and 1 in deadzone-space. - * - * @param rawValue The raw input value to apply the deadzone to. - * @param deadzone The deadzone threshold (0.0f to 1.0f). - * @return The input value after applying the deadzone. + * Applies deadzone formula to a raw input value. Returns 0 if the value is + * below the deadzone threshold, otherwise remaps to the full 0..INT16_MAX + * range. + * + * @param rawValue The raw input value (0 to INT16_MAX). + * @param deadzone The deadzone threshold (0 to INT16_MAX). + * @return The input value after applying the deadzone (0 to INT16_MAX). */ -float_t inputDeadzone(const float_t rawValue, const float_t deadzone); \ No newline at end of file +int16_t inputDeadzone(const int16_t rawValue, const int16_t deadzone); \ No newline at end of file diff --git a/src/dusk/input/inputaction.h b/src/dusk/input/inputaction.h index f094a42a..1b5b4f7b 100644 --- a/src/dusk/input/inputaction.h +++ b/src/dusk/input/inputaction.h @@ -14,12 +14,12 @@ typedef struct { inputaction_t action; - float_t lastValue; - float_t currentValue; + int16_t lastValue; + int16_t currentValue; #ifdef DUSK_TIME_DYNAMIC - float_t lastDynamicValue; - float_t currentDynamicValue; + int16_t lastDynamicValue; + int16_t currentDynamicValue; #endif eventcallback_t onPressedCallbacks[INPUT_ACTION_CALLBACK_COUNT_MAX]; diff --git a/src/dusk/input/inputbutton.c b/src/dusk/input/inputbutton.c index 32aa310e..e86c8a42 100644 --- a/src/dusk/input/inputbutton.c +++ b/src/dusk/input/inputbutton.c @@ -25,7 +25,7 @@ inputbutton_t inputButtonGetByName(const char_t *name) { return (inputbutton_t){ .type = INPUT_BUTTON_TYPE_NONE }; } -float_t inputButtonGetValue(const inputbutton_t button) { +int16_t inputButtonGetValue(const inputbutton_t button) { return inputButtonGetValuePlatform(button); } inputbuttondata_t * inputButtonGetData(const inputbutton_t button) { diff --git a/src/dusk/input/inputbutton.h b/src/dusk/input/inputbutton.h index 10199c40..b4166dd1 100644 --- a/src/dusk/input/inputbutton.h +++ b/src/dusk/input/inputbutton.h @@ -75,8 +75,8 @@ typedef struct inputbutton_s { typedef struct { const char_t *name; inputbutton_t button; - float_t curVal; - float_t lastVal; + int16_t curVal; + int16_t lastVal; inputaction_t action; } inputbuttondata_t; @@ -92,11 +92,11 @@ inputbutton_t inputButtonGetByName(const char_t *name); /** * Gets the current value of an input button. - * + * * @param button The input button. - * @return The current value of the input button (0.0f to 1.0f). + * @return The current value of the input button (0 to INT16_MAX). */ -float_t inputButtonGetValue(const inputbutton_t button); +int16_t inputButtonGetValue(const inputbutton_t button); /** * Gets the button data for a specific input button. diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index c04b6153..18a8b582 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -44,7 +44,7 @@ void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) { break; case CUTSCENE_ITEM_TYPE_WAIT: - data->wait = fixedSub(data->wait, TIME.delta); + data->wait -= TIME.delta; if(data->wait <= 0) cutsceneSystemNext(); break; diff --git a/src/dusk/rpg/cutscene/item/cutscenewait.h b/src/dusk/rpg/cutscene/item/cutscenewait.h index 7c774fc7..9d590df2 100644 --- a/src/dusk/rpg/cutscene/item/cutscenewait.h +++ b/src/dusk/rpg/cutscene/item/cutscenewait.h @@ -7,7 +7,6 @@ #pragma once #include "dusk.h" -#include "util/fixed.h" -typedef fixed_t cutscenewait_t; -typedef fixed_t cutscenewaitdata_t; \ No newline at end of file +typedef float_t cutscenewait_t; +typedef float_t cutscenewaitdata_t; \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/scene/testcutscene.h b/src/dusk/rpg/cutscene/scene/testcutscene.h index 7c54d154..bcdffb21 100755 --- a/src/dusk/rpg/cutscene/scene/testcutscene.h +++ b/src/dusk/rpg/cutscene/scene/testcutscene.h @@ -10,7 +10,7 @@ static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "This is a test cutscene.", .position = RPG_TEXTBOX_POS_BOTTOM } }, - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = FIXED(2.0f) }, + { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f }, { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "It has multiple lines of text.\nAnd waits in between.", .position = RPG_TEXTBOX_POS_TOP } }, }; @@ -20,7 +20,7 @@ static const cutscene_t TEST_CUTSCENE_ONE = { }; static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = FIXED(1.0f) }, + { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }, { .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE }, }; diff --git a/src/dusk/rpg/entity/entity.h b/src/dusk/rpg/entity/entity.h index 8bf19129..1ef1bb03 100644 --- a/src/dusk/rpg/entity/entity.h +++ b/src/dusk/rpg/entity/entity.h @@ -25,7 +25,7 @@ typedef struct entity_s { fixed_t lastPosition[3]; entityanim_t animation; - fixed_t animTime; + float_t animTime; entityinteract_t interact; } entity_t; diff --git a/src/dusk/rpg/entity/entityanim.c b/src/dusk/rpg/entity/entityanim.c index 0d8374df..8e5a4a6c 100644 --- a/src/dusk/rpg/entity/entityanim.c +++ b/src/dusk/rpg/entity/entityanim.c @@ -11,7 +11,7 @@ void entityAnimUpdate(entity_t *entity) { if(entity->animation == ENTITY_ANIM_IDLE) return; - entity->animTime = fixedSub(entity->animTime, TIME.delta); + entity->animTime -= TIME.delta; if(entity->animTime <= 0) { entity->animation = ENTITY_ANIM_IDLE; entity->animTime = 0; diff --git a/src/dusk/rpg/entity/entityanim.h b/src/dusk/rpg/entity/entityanim.h index 0a125126..9fb8d9ea 100644 --- a/src/dusk/rpg/entity/entityanim.h +++ b/src/dusk/rpg/entity/entityanim.h @@ -7,10 +7,9 @@ #pragma once #include "dusk.h" -#include "util/fixed.h" -#define ENTITY_ANIM_TURN_DURATION FIXED(0.06f) -#define ENTITY_ANIM_WALK_DURATION FIXED(0.1f) +#define ENTITY_ANIM_TURN_DURATION 0.06f +#define ENTITY_ANIM_WALK_DURATION 0.1f typedef struct entity_s entity_t; diff --git a/src/dusk/scene/full/scenefull.c b/src/dusk/scene/full/scenefull.c index c1e9201a..5d305f02 100644 --- a/src/dusk/scene/full/scenefull.c +++ b/src/dusk/scene/full/scenefull.c @@ -11,7 +11,6 @@ #include "display/screen.h" #include "display/color.h" #include "time/time.h" -#include "util/fixed.h" #include "util/memory.h" static const uint8_t initIndices[3 * 3] = { @@ -64,7 +63,7 @@ static void renderTest2DZOrder(void) { } static void renderTest3DQuad(void) { - float_t angle = fixedToFloat(TIME.time); + float_t angle = TIME.time; renderQuad3D( -45, 0, 0, (int16_t)(cosf(angle) * 30.0f), 0, (int16_t)(sinf(angle) * 30.0f), @@ -89,7 +88,7 @@ static void renderTest3DOverlap(void) { } static void renderTestTilemap(void) { - float_t t = fixedToFloat(TIME.time); + float_t t = TIME.time; float_t scrollPx = fmodf(t * TILEMAP_SCROLL_SPEED, TILEMAP_SCROLL_RANGE); int16_t x = -(int16_t)(int)scrollPx; renderTilemapChunk(x, TILEMAP_Y, 8000, tilemapChunk); @@ -129,7 +128,7 @@ errorret_t sceneFullInit(scenedata_t *data) { } errorret_t sceneFullUpdate(scenedata_t *data) { - float_t t = fixedToFloat(TIME.time); + float_t t = TIME.time; renderTextureGetPalette(testTex)[8] = hueColor(t * 2.0f); renderTextureGetIndices(testTex)[1 * 3 + 1] = (uint8_t)((uint32_t)t % 9u); diff --git a/src/dusk/scene/spinningbox/scenespinningbox.c b/src/dusk/scene/spinningbox/scenespinningbox.c index 8b0d795d..9200dd05 100644 --- a/src/dusk/scene/spinningbox/scenespinningbox.c +++ b/src/dusk/scene/spinningbox/scenespinningbox.c @@ -11,7 +11,6 @@ #include "display/screen.h" #include "display/color.h" #include "time/time.h" -#include "util/fixed.h" #include static const uint8_t BOX_INDICES[3 * 3] = { @@ -59,13 +58,13 @@ errorret_t sceneSpinningBoxRender(scenedata_t *data) { renderClear(color(16, 16, 24, 255)); renderSetProjection(FOV_Y, ASPECT, FIXED(10), FIXED(10000)); - renderSetView(0, 0, 150, 0, 0, 0); + renderSetView(0, 0, 270, 0, 0, 0); - float_t angle = fixedToFloat(TIME.time); + float_t angle = TIME.time; renderQuad3D( 0, 0, 0, - (int16_t)(cosf(angle) * 40.0f), 0, (int16_t)(sinf(angle) * 40.0f), - 0, 40, 0, + (int16_t)(cosf(angle) * 72.0f), 0, (int16_t)(sinf(angle) * 72.0f), + 0, 72, 0, 0, spinTex, COLOR_WHITE ); diff --git a/src/dusk/time/time.c b/src/dusk/time/time.c index 6a69997f..5ec9b5ca 100644 --- a/src/dusk/time/time.c +++ b/src/dusk/time/time.c @@ -8,7 +8,6 @@ #include "time.h" #include "util/memory.h" #include "assert/assert.h" - #include "console/console.h" dusktime_t TIME; @@ -25,6 +24,8 @@ void timeInit(void) { TIME.dynamicDelta = 0.0f; TIME.dynamicUpdate = false; TIME.lastNonDynamic = 0.0f; + // Prime the tick so the first-frame delta isn't the full SDL uptime. + timeTickPlatform(); #endif } @@ -37,15 +38,15 @@ void timeUpdate(void) { assertTrue(TIME.dynamicDelta >= 0.0f, "Time delta is negative"); - if((TIME.dynamicTime - TIME.lastNonDynamic) >= DUSK_TIME_STEP_F) { + if((TIME.dynamicTime - TIME.lastNonDynamic) >= DUSK_TIME_STEP) { TIME.dynamicUpdate = false; TIME.lastNonDynamic = TIME.dynamicTime; TIME.delta = DUSK_TIME_STEP; - TIME.time = fixedAdd(TIME.time, DUSK_TIME_STEP); + TIME.time += DUSK_TIME_STEP; } #else TIME.delta = DUSK_TIME_STEP; - TIME.time = fixedAdd(TIME.time, DUSK_TIME_STEP); + TIME.time += DUSK_TIME_STEP; #endif // Print time in UTC style string diff --git a/src/dusk/time/time.h b/src/dusk/time/time.h index 6924f7e8..89481239 100644 --- a/src/dusk/time/time.h +++ b/src/dusk/time/time.h @@ -8,16 +8,9 @@ #pragma once #include "timeepoch.h" #include "time/timeplatform.h" -#include "util/fixed.h" #ifndef DUSK_TIME_STEP - #define DUSK_TIME_STEP FIXED(1.0f / 60.0f) -#endif - -/* Float-space step used by the dynamic accumulator to avoid fixed-point - * truncation error in the trigger comparison. */ -#ifndef DUSK_TIME_STEP_F - #define DUSK_TIME_STEP_F (1.0f / 60.0f) + #define DUSK_TIME_STEP (1.0f / 60.0f) #endif #ifdef DUSK_TIME_DYNAMIC @@ -30,11 +23,10 @@ #endif typedef struct { - fixed_t delta; - fixed_t time; + float_t delta; + float_t time; #ifdef DUSK_TIME_DYNAMIC - /* Float accumulator avoids fixed-point truncation drift. */ float_t lastNonDynamic; bool_t dynamicUpdate; float_t dynamicDelta; diff --git a/src/dusk/ui/uifullbox.c b/src/dusk/ui/uifullbox.c index 1ae71591..996439d3 100644 --- a/src/dusk/ui/uifullbox.c +++ b/src/dusk/ui/uifullbox.c @@ -27,11 +27,11 @@ void uiFullboxInit(uifullbox_t *fullbox) { ); } -void uiFullboxUpdate(uifullbox_t *fullbox, fixed_t delta) { +void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) { assertNotNull(fullbox, "fullbox must not be NULL"); if(fullbox->duration <= 0 || fullbox->time >= fullbox->duration) return; - fullbox->time = fixedAdd(fullbox->time, delta); + fullbox->time += delta; if(fullbox->time >= fullbox->duration) { fullbox->time = fullbox->duration; eventInvoke(&fullbox->onTransitionEnd, fullbox); @@ -44,7 +44,7 @@ static color_t uiFullboxGetColor(const uifullbox_t *fullbox) { } fixed_t t = easingApply( fullbox->easing, - fixedDiv(fullbox->time, fullbox->duration) + fixedFromFloat(fullbox->time / fullbox->duration) ); return color4b( fixedToU8(fixedLerp( @@ -92,7 +92,7 @@ void uiFullboxTransition( uifullbox_t *fullbox, color_t from, color_t to, - fixed_t duration, + float_t duration, easingtype_t easing ) { assertNotNull(fullbox, "fullbox must not be NULL"); diff --git a/src/dusk/ui/uifullbox.h b/src/dusk/ui/uifullbox.h index 300b8174..c1f172cc 100644 --- a/src/dusk/ui/uifullbox.h +++ b/src/dusk/ui/uifullbox.h @@ -10,13 +10,12 @@ #include "display/color.h" #include "animation/easing.h" #include "event/event.h" -#include "util/fixed.h" typedef struct { color_t fromColor; color_t toColor; - fixed_t duration; - fixed_t time; + float_t duration; + float_t time; easingtype_t easing; eventcallback_t onTransitionEndCallbacks[4]; void *onTransitionEndUsers[4]; @@ -40,7 +39,7 @@ void uiFullboxInit(uifullbox_t *fullbox); * @param fullbox The fullbox to update. * @param delta Seconds elapsed since last update. */ -void uiFullboxUpdate(uifullbox_t *fullbox, fixed_t delta); +void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta); /** * Renders the fullbox. Skipped entirely when the current alpha is zero. @@ -63,7 +62,7 @@ void uiFullboxTransition( uifullbox_t *fullbox, color_t from, color_t to, - fixed_t duration, + float_t duration, easingtype_t easing ); diff --git a/src/dusk/ui/uiloading.c b/src/dusk/ui/uiloading.c index 881a8bbf..63025db0 100644 --- a/src/dusk/ui/uiloading.c +++ b/src/dusk/ui/uiloading.c @@ -7,7 +7,6 @@ #include "uiloading.h" #include "assert/assert.h" -#include "util/fixed.h" #include "util/memory.h" #include "display/text/text.h" #include "display/screen/screen.h" @@ -28,10 +27,10 @@ void uiLoadingInit(void) { ); } -void uiLoadingUpdate(fixed_t delta) { +void uiLoadingUpdate(float_t delta) { if(UI_LOADING.duration <= 0 || UI_LOADING.time >= UI_LOADING.duration) return; - UI_LOADING.time = fixedAdd(UI_LOADING.time, delta); + UI_LOADING.time += delta; if(UI_LOADING.time >= UI_LOADING.duration) { UI_LOADING.time = UI_LOADING.duration; eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING); @@ -43,7 +42,7 @@ errorret_t uiLoadingDraw(void) { if(UI_LOADING.duration <= 0 || UI_LOADING.time >= UI_LOADING.duration) { alpha = UI_LOADING.toAlpha; } else { - float_t t = fixedToFloat(fixedDiv(UI_LOADING.time, UI_LOADING.duration)); + float_t t = UI_LOADING.time / UI_LOADING.duration; alpha = UI_LOADING.fromAlpha + (UI_LOADING.toAlpha - UI_LOADING.fromAlpha) * t; } diff --git a/src/dusk/ui/uiloading.h b/src/dusk/ui/uiloading.h index f334f006..60bf9414 100644 --- a/src/dusk/ui/uiloading.h +++ b/src/dusk/ui/uiloading.h @@ -8,16 +8,15 @@ #pragma once #include "error/error.h" #include "event/event.h" -#include "util/fixed.h" -#define UI_LOADING_FADE_DURATION FIXED(0.5f) +#define UI_LOADING_FADE_DURATION 0.5f #define UI_LOADING_MARGIN 8.0f typedef struct { float_t fromAlpha; float_t toAlpha; - fixed_t duration; - fixed_t time; + float_t duration; + float_t time; eventcallback_t onTransitionEndCallbacks[4]; void *onTransitionEndUsers[4]; event_t onTransitionEnd; @@ -36,7 +35,7 @@ void uiLoadingInit(void); * * @param delta Seconds elapsed since last update. */ -void uiLoadingUpdate(fixed_t delta); +void uiLoadingUpdate(float_t delta); /** * Draws the loading indicator. No-op when fully transparent. diff --git a/src/dusk/util/memory.c b/src/dusk/util/memory.c index 21bcfb64..a67c07a4 100644 --- a/src/dusk/util/memory.c +++ b/src/dusk/util/memory.c @@ -29,7 +29,9 @@ void * memoryAllocate(const size_t size) { void * memoryAlign(size_t alignment, size_t size) { assertTrue(alignment > 0, "Alignment must be greater than 0."); assertTrue(size > 0, "Cannot allocate 0 bytes of memory."); - void *ptr = memalign(alignment, size); + // aligned_alloc (C11, ) requires size to be a multiple of alignment + size_t alignedSize = (size + alignment - 1) & ~(alignment - 1); + void *ptr = aligned_alloc(alignment, alignedSize); assertNotNull(ptr, "Aligned memory allocation failed."); memoryTrack(ptr); return ptr; diff --git a/src/duskdolphin/input/inputdolphin.c b/src/duskdolphin/input/inputdolphin.c index 8dfc7403..cc526ddc 100644 --- a/src/duskdolphin/input/inputdolphin.c +++ b/src/duskdolphin/input/inputdolphin.c @@ -160,30 +160,29 @@ void inputUpdateDolphin(void) { #endif } -float_t inputButtonGetValueDolphin(const inputbutton_t button) { +int16_t inputButtonGetValueDolphin(const inputbutton_t button) { switch(button.type) { #ifdef DUSK_INPUT_GAMEPAD case INPUT_BUTTON_TYPE_GAMEPAD: { - if(INPUT.platform.padState[0] & button.gpButton) return 1.0f; - return 0.0f; + return (INPUT.platform.padState[0] & button.gpButton) ? INT16_MAX : 0; } case INPUT_BUTTON_TYPE_GAMEPAD_AXIS: { - float_t axis = INPUT.platform.pads[0][button.gpAxis.axis]; - float_t value = axis / 128.0f; - if(!button.gpAxis.positive) value = -value; - value = inputDeadzone(value, inputGetDeadzoneDolphin(button)); - return value; + float_t f = INPUT.platform.pads[0][button.gpAxis.axis]; + if(!button.gpAxis.positive) f = -f; + if(f < 0.0f) return 0; + int16_t value = (int16_t)(f * INT16_MAX); + return inputDeadzone(value, inputGetDeadzoneDolphin(button)); } #endif default: { assertUnreachable("Unknown input button type"); - return 0.0f; + return 0; } } } -float_t inputGetDeadzoneDolphin(const inputbutton_t button) { - return 0.2f; +int16_t inputGetDeadzoneDolphin(const inputbutton_t button) { + return (int16_t)(0.2f * INT16_MAX); } \ No newline at end of file diff --git a/src/duskdolphin/input/inputdolphin.h b/src/duskdolphin/input/inputdolphin.h index 2f130051..a64fcd08 100644 --- a/src/duskdolphin/input/inputdolphin.h +++ b/src/duskdolphin/input/inputdolphin.h @@ -58,17 +58,17 @@ errorret_t inputInitDolphin(void); void inputUpdateDolphin(void); /** - * Returns the input value (between 0 and 1) of the given button. - * + * Returns the input value of the given button (0 to INT16_MAX). + * * @param button The button to get the value of. - * @return The value of the button, between 0 and 1. + * @return The value of the button (0 to INT16_MAX). */ -float_t inputButtonGetValueDolphin(const inputbutton_t button); +int16_t inputButtonGetValueDolphin(const inputbutton_t button); /** - * Returns the deadzone for the given button. - * + * Returns the deadzone for the given button (0 to INT16_MAX). + * * @param button The button to get the deadzone of. - * @return The deadzone for the button, between 0 and 1. + * @return The deadzone threshold (0 to INT16_MAX). */ -float_t inputGetDeadzoneDolphin(const inputbutton_t button); \ No newline at end of file +int16_t inputGetDeadzoneDolphin(const inputbutton_t button); \ No newline at end of file diff --git a/src/dusklinux/input/inputlinux.c b/src/dusklinux/input/inputlinux.c index 42ec3359..29987626 100644 --- a/src/dusklinux/input/inputlinux.c +++ b/src/dusklinux/input/inputlinux.c @@ -544,6 +544,6 @@ errorret_t inputInitLinux(void) { errorOk(); } -float_t inputGetDeadzoneSDL2(const inputbutton_t button) { - return 0.17f; +int16_t inputGetDeadzoneSDL2(const inputbutton_t button) { + return (int16_t)(0.17f * INT16_MAX); } \ No newline at end of file diff --git a/src/duskpsp/input/inputpsp.c b/src/duskpsp/input/inputpsp.c index 313ceb95..c3f006e1 100644 --- a/src/duskpsp/input/inputpsp.c +++ b/src/duskpsp/input/inputpsp.c @@ -92,6 +92,6 @@ errorret_t inputInitPSP(void) { errorOk(); } -float_t inputGetDeadzoneSDL2(const inputbutton_t button) { - return 0.2f; +int16_t inputGetDeadzoneSDL2(const inputbutton_t button) { + return (int16_t)(0.2f * INT16_MAX); } \ No newline at end of file diff --git a/src/dusksat/display/displaysat.c b/src/dusksat/display/displaysat.c deleted file mode 100644 index 18b8f598..00000000 --- a/src/dusksat/display/displaysat.c +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "display/displaysat.h" -#include "display/render/rendersat.h" -#include "display/display.h" -#include "assert/assert.h" -#include "log/log.h" -#include -#include -#include -#include -#include - -errorret_t displaySaturnInit(void) { - logDebug("[Saturn] displaySaturnInit: start\n"); - DISPLAY.whichBuffer = 0; - - /* - * TV mode: NTSC, 320×224, non-interlaced. - * Yaul's vdp2_tvmd_display_res_set() configures the sync standard and - * horizontal/vertical resolution. - * - * TODO: replace with the Yaul typed call when integrating the full SDK: - * vdp2_tvmd_display_res_set(VDP2_TVMD_INTERLACE_NONE, - * VDP2_TVMD_HORZ_NORMAL_A, - * VDP2_TVMD_VERT_224); - * vdp2_tvmd_display_set(); - */ - - /* - * VDP2 scroll planes: disable all NBG/RBG planes for now; game content is - * drawn entirely via VDP1 sprites. Tilemap chunks will re-enable NBG0 - * when the VDP2 tilemap backend is implemented. - * - * TODO: - * vdp2_scrn_display_set(VDP2_SCRN_DISP_NBG0, false); - * vdp2_scrn_display_set(VDP2_SCRN_DISP_NBG1, false); - * ... - */ - - /* - * VDP1 initialisation: the hardware starts drawing from VRAM offset 0. - * We place our command table there and texture data afterward. - * - * TODO: - * vdp1_vram_partitions_set( - * VDP1_VRAM_CYCP_..., // cycle patterns - * ... - * ); - */ - - logDebug("[Saturn] displaySaturnInit: calling renderSaturnInit\n"); - errorChain(renderSaturnInit()); - - logDebug("[Saturn] displaySaturnInit: done\n"); - errorOk(); -} - -errorret_t displaySaturnFlush(ropbuffer_t *buf) { - assertNotNull(buf, "Saturn flush: null ropbuffer"); - errorChain(renderSaturnFlush(buf)); - errorOk(); -} - -errorret_t displaySaturnSwap(void) { - logDebug("[Saturn] displaySaturnSwap\n"); - /* - * Wait for VDP1 to finish rendering the current frame then swap buffers. - * - * TODO: - * vdp1_sync_render(); - * vdp1_sync(); - * vdp2_sync(); - * vdp2_sync_wait(); - */ - DISPLAY.whichBuffer ^= 1; - errorOk(); -} - -void displaySaturnDispose(void) { - logDebug("[Saturn] displaySaturnDispose\n"); - renderSaturnDispose(); -} diff --git a/src/dusksat/input/inputplatform.h b/src/dusksat/input/inputplatform.h deleted file mode 100644 index 8f97b47c..00000000 --- a/src/dusksat/input/inputplatform.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "input/inputsat.h" - -#define inputInitPlatform inputInitSaturn diff --git a/src/dusksat/input/inputsat.c b/src/dusksat/input/inputsat.c deleted file mode 100644 index 887d32cd..00000000 --- a/src/dusksat/input/inputsat.c +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "input/input.h" -#include - -/* - * Saturn standard digital pad buttons (smpc_peripheral_digital_t). - * Yaul exposes them via smpc_peripheral_digital_port() and - * smpc_peripheral_digital_get(). - * - * Button bitmask in the SMPC peripheral data word: - * bit 11 = Right bit 10 = Left bit 9 = Down bit 8 = Up - * bit 7 = Start bit 6 = A bit 5 = C bit 4 = B - * bit 3 = R bit 2 = X bit 1 = Y bit 0 = Z - * - * We use Yaul's SMPC_PERIPHERAL_DIGITAL_* macros where available. - */ - -inputbuttondata_t INPUT_BUTTON_DATA[] = { - { .name = "a", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 6 } }, - { .name = "b", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 4 } }, - { .name = "c", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 5 } }, - { .name = "x", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 2 } }, - { .name = "y", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 1 } }, - { .name = "z", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 0 } }, - { .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 7 } }, - { .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 8 } }, - { .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 9 } }, - { .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 10 } }, - { .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 11 } }, - { .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 15 } }, - { .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 3 } }, - { .name = "accept", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 6 } }, /* A */ - { .name = "cancel", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 4 } }, /* B */ - { .name = NULL } -}; - -errorret_t inputInitSaturn(void) { - #define X(buttonName, buttonAction) \ - inputBind(inputButtonGetByName(buttonName), buttonAction); - X("up", INPUT_ACTION_UP); - X("down", INPUT_ACTION_DOWN); - X("left", INPUT_ACTION_LEFT); - X("right", INPUT_ACTION_RIGHT); - X("accept", INPUT_ACTION_ACCEPT); - X("cancel", INPUT_ACTION_CANCEL); - X("start", INPUT_ACTION_RAGEQUIT); - #undef X - - errorOk(); -} diff --git a/src/dusksat/input/inputsat.h b/src/dusksat/input/inputsat.h deleted file mode 100644 index e5d44807..00000000 --- a/src/dusksat/input/inputsat.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -errorret_t inputInitSaturn(void); diff --git a/src/dusksat/save/CMakeLists.txt b/src/dusksat/save/CMakeLists.txt deleted file mode 100644 index 02dea68b..00000000 --- a/src/dusksat/save/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_BINARY_TARGET_NAME} - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/savesat.c - ${CMAKE_CURRENT_LIST_DIR}/savestreamsat.c -) diff --git a/src/dusksat/save/savesat.c b/src/dusksat/save/savesat.c deleted file mode 100644 index 51dbc67b..00000000 --- a/src/dusksat/save/savesat.c +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "save/save.h" -#include "save/savesat.h" -#include "log/log.h" - -/* - * TODO: use Yaul's bup_* API for backup RAM access. - * Reference: in the Yaul SDK. - * - * bup_init(BUP_DEV_INTERNAL); // or BUP_DEV_EXTERNAL for cart - * bup_stat_t stat; - * bup_stat(BUP_DEV_INTERNAL, &stat); - * - * Write: bup_write(BUP_DEV_INTERNAL, &dir, data, size, BUP_MODE_NEW); - * Read: bup_read(BUP_DEV_INTERNAL, filename, data, size); - * Del: bup_delete(BUP_DEV_INTERNAL, filename); - */ - -errorret_t saveInitSaturn(void) { - logDebug("[Saturn] saveInitSaturn\n"); - /* TODO: bup_init(BUP_DEV_INTERNAL); */ - errorOk(); -} - -errorret_t saveDisposeSaturn(void) { - errorOk(); -} - -errorret_t saveDeleteSaturn(const uint8_t slot) { - logDebug("[Saturn] saveDeleteSaturn: slot=%u\n", (unsigned)slot); - /* TODO: bup_delete(BUP_DEV_INTERNAL, filename_for_slot(slot)); */ - errorOk(); -} diff --git a/src/dusksat/system/CMakeLists.txt b/src/dusksat/system/CMakeLists.txt deleted file mode 100644 index 7bd4e557..00000000 --- a/src/dusksat/system/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_BINARY_TARGET_NAME} - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/systemsat.c -) diff --git a/src/dusksat/time/CMakeLists.txt b/src/dusksat/time/CMakeLists.txt deleted file mode 100644 index f7ea99df..00000000 --- a/src/dusksat/time/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_BINARY_TARGET_NAME} - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/timesat.c -) diff --git a/src/dusksat/CMakeLists.txt b/src/dusksaturn/CMakeLists.txt similarity index 92% rename from src/dusksat/CMakeLists.txt rename to src/dusksaturn/CMakeLists.txt index ea97efc2..96d9d3b2 100644 --- a/src/dusksat/CMakeLists.txt +++ b/src/dusksaturn/CMakeLists.txt @@ -10,6 +10,7 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_BINARY_TARGET_NAME} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/compat.c ) add_subdirectory(asset) diff --git a/src/dusksat/asset/CMakeLists.txt b/src/dusksaturn/asset/CMakeLists.txt similarity index 80% rename from src/dusksat/asset/CMakeLists.txt rename to src/dusksaturn/asset/CMakeLists.txt index 31cd5f58..a93f7007 100644 --- a/src/dusksat/asset/CMakeLists.txt +++ b/src/dusksaturn/asset/CMakeLists.txt @@ -5,5 +5,5 @@ target_sources(${DUSK_BINARY_TARGET_NAME} PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/assetsat.c + ${CMAKE_CURRENT_LIST_DIR}/assetsaturn.c ) diff --git a/src/dusksat/asset/assetplatform.h b/src/dusksaturn/asset/assetplatform.h similarity index 79% rename from src/dusksat/asset/assetplatform.h rename to src/dusksaturn/asset/assetplatform.h index e8399e00..990e80a6 100644 --- a/src/dusksat/asset/assetplatform.h +++ b/src/dusksaturn/asset/assetplatform.h @@ -6,9 +6,9 @@ */ #pragma once -#include "assetsat.h" +#include "assetsaturn.h" -typedef assetsat_t assetplatform_t; +typedef assetsaturn_t assetplatform_t; #define assetInitPlatform assetInitSaturn #define assetDisposePlatform assetDisposeSaturn diff --git a/src/dusksat/asset/assetsat.c b/src/dusksaturn/asset/assetsaturn.c similarity index 79% rename from src/dusksat/asset/assetsat.c rename to src/dusksaturn/asset/assetsaturn.c index 0a31ef22..e5d2055c 100644 --- a/src/dusksat/asset/assetsat.c +++ b/src/dusksaturn/asset/assetsaturn.c @@ -5,16 +5,15 @@ * https://opensource.org/licenses/MIT */ -#include "assetsat.h" +#include "assetsaturn.h" #include "log/log.h" errorret_t assetInitSaturn(void) { logDebug("[Saturn] assetInitSaturn: initializing CD-Block\n"); - /* TODO: cd_block_init() */ + cd_block_init(); errorOk(); } errorret_t assetDisposeSaturn(void) { - /* TODO: cd_block_deinit() */ errorOk(); } diff --git a/src/dusksat/asset/assetsat.h b/src/dusksaturn/asset/assetsaturn.h similarity index 94% rename from src/dusksat/asset/assetsat.h rename to src/dusksaturn/asset/assetsaturn.h index e39f9bf9..c6edb4ea 100644 --- a/src/dusksat/asset/assetsat.h +++ b/src/dusksaturn/asset/assetsaturn.h @@ -11,7 +11,7 @@ typedef struct { uint8_t unused; -} assetsat_t; +} assetsaturn_t; errorret_t assetInitSaturn(void); errorret_t assetDisposeSaturn(void); diff --git a/src/dusksaturn/compat.c b/src/dusksaturn/compat.c new file mode 100644 index 00000000..ad41319b --- /dev/null +++ b/src/dusksaturn/compat.c @@ -0,0 +1,285 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/* + * Bare-metal libc compatibility stubs for Saturn/Yaul. + * + * Yaul's sysroot is a minimal bare-metal environment that lacks many + * standard C library functions used by third-party code (libzip, yyjson) + * and some Dusk utility modules. Provide minimal correct implementations. + */ + +#include +#include +#include +#include +#include + +/* ========================================================================= + * Memory allocation + * ========================================================================= */ + +/* realloc: libyaul provides a weak _realloc but it is not pulled from the + * archive in time for libzip/yyjson (link-order issue). Provide a strong + * definition using Yaul's __realloc (TLSF allocator). */ +extern void *__realloc(void *ptr, size_t size); +void *realloc(void *ptr, size_t size) { + return __realloc(ptr, size); +} + +/* calloc: zero-initialised allocation. */ +void *calloc(size_t nmemb, size_t size) { + size_t total = nmemb * size; + void *p = malloc(total); + if (p) memset(p, 0, total); + return p; +} + +/* aligned_alloc: C11 aligned allocation. + * Yaul's TLSF returns naturally aligned (≥8-byte) blocks. + * For Saturn-specific hardware requirements (VDP command lists need 32-byte + * alignment) this trivial wrapper is sufficient because Yaul's cmt_pool + * handles its own aligned allocation internally; memoryAlign() is only used + * for higher-level game objects where natural alignment is enough. */ +void *aligned_alloc(size_t alignment, size_t size) { + (void)alignment; + return malloc(size); +} + +/* ========================================================================= + * Unix file-descriptor I/O + * libzip's zip_random_unix.c opens /dev/urandom to seed its PRNG. + * Saturn has no filesystem; return -1 (ENOENT / error) unconditionally. + * ========================================================================= */ + +int open(const char *path, int flags, ...) { + (void)path; (void)flags; + return -1; +} + +int read(int fd, void *buf, size_t count) { + (void)fd; (void)buf; (void)count; + return -1; +} + +int close(int fd) { + (void)fd; + return -1; +} + +/* ========================================================================= + * memcpy_s — Annex K bounds-checking variant; not in Yaul's bare-metal libc. + * libzip uses it; forward to plain memcpy. + * ========================================================================= */ + +int memcpy_s(void *dest, size_t destsz, const void *src, size_t count) { + if (!dest || !src || count > destsz) return 1; + memcpy(dest, src, count); + return 0; +} + +/* ========================================================================= + * Timing + * ========================================================================= */ + +/* usleep: asset.c calls usleep() in async-load paths. Saturn has no threading; + * busy-wait is not useful without a timer, so simply return. */ +int usleep(unsigned int usecs) { + (void)usecs; + return 0; +} + +/* ========================================================================= + * String-to-number conversion + * Yaul provides strtol but not strtoul / strtoll / strtof. + * ========================================================================= */ + +static unsigned long long _parse_ull( + const char *s, char **endptr, int base, int issigned +) { + unsigned long long val = 0; + int neg = 0; + while (*s == ' ' || *s == '\t') s++; + if (*s == '+') { s++; } + else if (*s == '-') { neg = 1; s++; } + if (base == 0) { + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (s[0] == '0') { base = 8; s++; } + else { base = 10; } + } else if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } + const char *start = s; + while (*s) { + int digit; + if (*s >= '0' && *s <= '9') digit = *s - '0'; + else if (*s >= 'a' && *s <= 'f') digit = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'F') digit = *s - 'A' + 10; + else break; + if (digit >= base) break; + val = val * (unsigned long long)base + (unsigned long long)digit; + s++; + } + if (s == start) { if (endptr) *endptr = (char *)start; return 0; } + if (endptr) *endptr = (char *)s; + if (neg && issigned) return (unsigned long long)(-(long long)val); + if (neg) return (unsigned long long)(0ULL - val); + return val; +} + +unsigned long strtoul(const char *s, char **endptr, int base) { + return (unsigned long)_parse_ull(s, endptr, base, 0); +} + +long long strtoll(const char *s, char **endptr, int base) { + return (long long)_parse_ull(s, endptr, base, 1); +} + +float strtof(const char *s, char **endptr) { + while (*s == ' ' || *s == '\t') s++; + int neg = 0; + if (*s == '-') { neg = 1; s++; } else if (*s == '+') s++; + float val = 0.0f, frac = 1.0f; + int hasDot = 0; + const char *start = s; + while (*s >= '0' && *s <= '9') { val = val * 10.0f + (float)(*s - '0'); s++; } + if (*s == '.') { + hasDot = 1; s++; + while (*s >= '0' && *s <= '9') { + frac /= 10.0f; val += frac * (float)(*s - '0'); s++; + } + } + (void)hasDot; + int exp = 0; int expneg = 0; + if (*s == 'e' || *s == 'E') { + s++; + if (*s == '-') { expneg = 1; s++; } else if (*s == '+') s++; + while (*s >= '0' && *s <= '9') { exp = exp * 10 + (*s - '0'); s++; } + float p = 1.0f; + for (int i = 0; i < exp; i++) p *= 10.0f; + if (expneg) val /= p; else val *= p; + } + if (endptr) *endptr = (char *)(s == start ? start : s); + return neg ? -val : val; +} + +/* ========================================================================= + * String utilities missing from Yaul's libc + * ========================================================================= */ + +/* strdup: libzip pkware code uses strdup() for password strings. + * Yaul's bare-metal libc does not provide it. */ +char *strdup(const char *s) { + if (!s) return NULL; + size_t len = 0; + while (s[len]) len++; + char *p = (char *)malloc(len + 1); + if (p) memcpy(p, s, len + 1); + return p; +} + +/* stricmp / strcasecmp: Yaul has strcasecmp (_strcasecmp) but libzip uses + * _stricmp (Windows name). Map to the same logic. */ +int stricmp(const char *a, const char *b) { + while (*a && *b) { + int d = tolower((unsigned char)*a) - tolower((unsigned char)*b); + if (d) return d; + a++; b++; + } + return tolower((unsigned char)*a) - tolower((unsigned char)*b); +} + +/* ========================================================================= + * Sorting + * libzip uses qsort() for sorted-entry zip files (torrentzip). + * ========================================================================= */ + +void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)) { + if (nmemb < 2) return; + uint8_t *arr = (uint8_t *)base; + /* Insertion sort — O(n²) but avoids recursion and is fine for small n. */ + uint8_t tmp[256]; + for (size_t i = 1; i < nmemb; i++) { + size_t j = i; + memcpy(tmp, arr + j * size, size); + while (j > 0 && compar(arr + (j-1) * size, tmp) > 0) { + memcpy(arr + j * size, arr + (j-1) * size, size); + j--; + } + memcpy(arr + j * size, tmp, size); + } +} + +/* ========================================================================= + * Floating-point math (SH-2 has no FPU; all operations are soft-float) + * Used by test scenes and input helpers. + * ========================================================================= */ + +/* sqrtf — Newton-Raphson, 4 iterations (enough for single precision). */ +float sqrtf(float x) { + if (x <= 0.0f) return 0.0f; + float r = x; + r = (r + x / r) * 0.5f; + r = (r + x / r) * 0.5f; + r = (r + x / r) * 0.5f; + r = (r + x / r) * 0.5f; + return r; +} + +/* sinf / cosf — Taylor series with range reduction to [-π, π]. */ +#define _COMPAT_PI 3.14159265358979323846f +#define _COMPAT_2PI 6.28318530717958647692f + +float sinf(float x) { + /* Range reduction */ + while (x > _COMPAT_PI) x -= _COMPAT_2PI; + while (x < -_COMPAT_PI) x += _COMPAT_2PI; + float x2 = x * x; + /* Horner form: x(1 - x²/6(1 - x²/20(1 - x²/42))) */ + return x * (1.0f - x2 * (1.0f/6.0f * (1.0f - x2 * + (1.0f/20.0f * (1.0f - x2 * (1.0f/42.0f)))))); +} + +float cosf(float x) { + return sinf(x + _COMPAT_PI * 0.5f); +} + +/* fmodf — IEEE 754 fmod for floats. */ +float fmodf(float x, float y) { + if (y == 0.0f) return 0.0f; + float q = x / y; + /* Truncate toward zero */ + int qi = (int)q; + return x - (float)qi * y; +} + +/* ========================================================================= + * FILE* I/O stubs + * yyjson's file-reading path (yyjson_read_fp) pulls in fread/fseek/ftell. + * With -ffunction-sections + --gc-sections that function is dead-code + * eliminated if it is unreachable from Dusk. These stubs are a safety + * net in case the linker cannot eliminate the entire object in one pass. + * Saturn has no filesystem so all file operations return error/0. + * Use void* for the stream parameter to avoid conflicting with any + * stdio.h FILE typedef that the sysroot may or may not provide. + * ========================================================================= */ + +size_t fread(void *ptr, size_t size, size_t nmemb, void *stream) { + (void)ptr; (void)size; (void)nmemb; (void)stream; + return 0; +} + +int fseek(void *stream, long offset, int whence) { + (void)stream; (void)offset; (void)whence; + return -1; +} + +long ftell(void *stream) { + (void)stream; + return -1L; +} diff --git a/src/dusksat/display/CMakeLists.txt b/src/dusksaturn/display/CMakeLists.txt similarity index 81% rename from src/dusksat/display/CMakeLists.txt rename to src/dusksaturn/display/CMakeLists.txt index c0c5a8bf..8e5d2223 100644 --- a/src/dusksat/display/CMakeLists.txt +++ b/src/dusksaturn/display/CMakeLists.txt @@ -5,7 +5,7 @@ target_sources(${DUSK_BINARY_TARGET_NAME} PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/displaysat.c + ${CMAKE_CURRENT_LIST_DIR}/displaysaturn.c ) add_subdirectory(render) diff --git a/src/dusksat/display/displayplatform.h b/src/dusksaturn/display/displayplatform.h similarity index 83% rename from src/dusksat/display/displayplatform.h rename to src/dusksaturn/display/displayplatform.h index 320b76d2..769217ac 100644 --- a/src/dusksat/display/displayplatform.h +++ b/src/dusksaturn/display/displayplatform.h @@ -6,9 +6,9 @@ */ #pragma once -#include "display/displaysat.h" +#include "displaysaturn.h" -typedef displaysat_t displayplatform_t; +typedef displaysaturn_t displayplatform_t; #define displayPlatformInit displaySaturnInit #define displayPlatformFlush displaySaturnFlush diff --git a/src/dusksaturn/display/displaysaturn.c b/src/dusksaturn/display/displaysaturn.c new file mode 100644 index 00000000..81b4a42a --- /dev/null +++ b/src/dusksaturn/display/displaysaturn.c @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "display/displaysaturn.h" +#include "display/render/rendersaturn.h" +#include "display/display.h" +#include "assert/assert.h" +#include "log/log.h" +#include +#include +#include +#include +#include +#include + +errorret_t displaySaturnInit(void) { + logDebug("[Saturn] displaySaturnInit: start\n"); + DISPLAY.whichBuffer = 0; + + vdp2_tvmd_display_res_set( + VDP2_TVMD_INTERLACE_NONE, + VDP2_TVMD_HORZ_NORMAL_A, + VDP2_TVMD_VERT_224 + ); + vdp2_tvmd_display_set(); + + /* Disable all NBG/RBG scroll planes; game content drawn entirely via VDP1. */ + vdp2_scrn_display_set(VDP2_SCRN_DISP_NONE); + + logDebug("[Saturn] displaySaturnInit: calling renderSaturnInit\n"); + errorChain(renderSaturnInit()); + + logDebug("[Saturn] displaySaturnInit: done\n"); + errorOk(); +} + +errorret_t displaySaturnFlush(ropbuffer_t *buf) { + assertNotNull(buf, "Saturn flush: null ropbuffer"); + errorChain(renderSaturnFlush(buf)); + errorOk(); +} + +errorret_t displaySaturnSwap(void) { + logDebug("[Saturn] displaySaturnSwap\n"); + vdp1_sync_render(); + vdp1_sync(); + vdp2_sync(); + vdp2_sync_wait(); + DISPLAY.whichBuffer ^= 1; + errorOk(); +} + +void displaySaturnDispose(void) { + logDebug("[Saturn] displaySaturnDispose\n"); + renderSaturnDispose(); +} diff --git a/src/dusksat/display/displaysat.h b/src/dusksaturn/display/displaysaturn.h similarity index 81% rename from src/dusksat/display/displaysat.h rename to src/dusksaturn/display/displaysaturn.h index e2d99f1f..c4e265c7 100644 --- a/src/dusksat/display/displaysat.h +++ b/src/dusksaturn/display/displaysaturn.h @@ -10,12 +10,12 @@ #include "display/displaystate.h" #include "display/render/ropbuffer.h" -#define SAT_SCREEN_W DUSK_DISPLAY_WIDTH -#define SAT_SCREEN_H DUSK_DISPLAY_HEIGHT +#define SATURN_SCREEN_W DUSK_DISPLAY_WIDTH +#define SATURN_SCREEN_H DUSK_DISPLAY_HEIGHT typedef struct { int_t whichBuffer; -} displaysat_t; +} displaysaturn_t; errorret_t displaySaturnInit(void); errorret_t displaySaturnFlush(ropbuffer_t *buf); diff --git a/src/dusksat/input/CMakeLists.txt b/src/dusksaturn/display/render/CMakeLists.txt similarity index 80% rename from src/dusksat/input/CMakeLists.txt rename to src/dusksaturn/display/render/CMakeLists.txt index 3000bc06..17ebb0d4 100644 --- a/src/dusksat/input/CMakeLists.txt +++ b/src/dusksaturn/display/render/CMakeLists.txt @@ -5,5 +5,5 @@ target_sources(${DUSK_BINARY_TARGET_NAME} PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/inputsat.c + ${CMAKE_CURRENT_LIST_DIR}/rendersaturn.c ) diff --git a/src/dusksat/display/render/renderplatform.h b/src/dusksaturn/display/render/renderplatform.h similarity index 91% rename from src/dusksat/display/render/renderplatform.h rename to src/dusksaturn/display/render/renderplatform.h index f712ef4d..616e9988 100644 --- a/src/dusksat/display/render/renderplatform.h +++ b/src/dusksaturn/display/render/renderplatform.h @@ -6,7 +6,7 @@ */ #pragma once -#include "display/render/rendersat.h" +#include "display/render/rendersaturn.h" #define renderPlatformTextureCreate renderSaturnTextureCreate #define renderPlatformTextureDispose renderSaturnTextureDispose diff --git a/src/dusksat/display/render/rendersat.c b/src/dusksaturn/display/render/rendersaturn.c similarity index 64% rename from src/dusksat/display/render/rendersat.c rename to src/dusksaturn/display/render/rendersaturn.c index c695b59f..89d91006 100644 --- a/src/dusksat/display/render/rendersat.c +++ b/src/dusksaturn/display/render/rendersaturn.c @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -#include "display/render/rendersat.h" +#include "display/render/rendersaturn.h" #include "display/render/rop.h" #include "display/color.h" #include "assert/assert.h" @@ -27,15 +27,15 @@ * * VDP2 CRAM (4KB) holds palettes: * Each 256-color palette occupies 512 bytes (256 × 2-byte RGB1555 entries). - * We reserve one palette slot per texture handle (up to SAT_PALETTE_MAX). + * We reserve one palette slot per texture handle (up to SATURN_PALETTE_MAX). */ /* ---- Limits -------------------------------------------------------------- */ -#define SAT_RTEXTURE_MAX 128 -#define SAT_CMDT_MAX 1024 -#define SAT_TEXTURE_VRAM_BASE 0x010000u /* byte offset in VDP1 VRAM */ -#define SAT_TEXTURE_VRAM_SIZE (0x200000u - SAT_TEXTURE_VRAM_BASE) +#define SATURN_RTEXTURE_MAX 128 +#define SATURN_CMDT_MAX 1024 +#define SATURN_TEXTURE_VRAM_BASE 0x010000u /* byte offset in VDP1 VRAM */ +#define SATURN_TEXTURE_VRAM_SIZE (0x200000u - SATURN_TEXTURE_VRAM_BASE) /* ---- VDP1 command table entry (hardware layout, 32 bytes) ---------------- */ @@ -52,23 +52,23 @@ typedef struct __attribute__((packed)) { int16_t xd, yd; /* Vertex D (distorted sprite only) */ uint16_t grda; /* Gouraud shading address (unused = 0) */ uint16_t _pad; -} satcmd_t; +} saturncmd_t; -_Static_assert(sizeof(satcmd_t) == 32, "satcmd_t must be 32 bytes"); +_Static_assert(sizeof(saturncmd_t) == 32, "saturncmd_t must be 32 bytes"); /* CTRL command type bits (bits 2:0) */ -#define SATCMD_CTRL_NORMAL_SPRITE (0x0000u) /* aligned rect */ -#define SATCMD_CTRL_SCALED_SPRITE (0x0001u) -#define SATCMD_CTRL_DISTORTED_SPRITE (0x0002u) /* arbitrary quad */ -#define SATCMD_CTRL_POLYGON (0x0004u) /* solid polygon */ -#define SATCMD_CTRL_SYSCLIP (0x0009u) /* system clipping */ -#define SATCMD_CTRL_END (0x8000u) /* end of list */ +#define SATURNCMD_CTRL_NORMAL_SPRITE (0x0000u) /* aligned rect */ +#define SATURNCMD_CTRL_SCALED_SPRITE (0x0001u) +#define SATURNCMD_CTRL_DISTORTED_SPRITE (0x0002u) /* arbitrary quad */ +#define SATURNCMD_CTRL_POLYGON (0x0004u) /* solid polygon */ +#define SATURNCMD_CTRL_SYSCLIP (0x0009u) /* system clipping */ +#define SATURNCMD_CTRL_END (0x8000u) /* end of list */ /* PMOD draw mode */ -#define SATCMD_PMOD_TRANS (0x0000u) /* transparent pixel 0 */ -#define SATCMD_PMOD_8BPP_CBANK (0x0038u) /* 256-color, color bank */ -#define SATCMD_PMOD_ECD (0x0080u) /* extend color depth */ -#define SATCMD_PMOD_SPD (0x0040u) /* do not skip index 0 */ +#define SATURNCMD_PMOD_TRANS (0x0000u) /* transparent pixel 0 */ +#define SATURNCMD_PMOD_8BPP_CBANK (0x0038u) /* 256-color, color bank */ +#define SATURNCMD_PMOD_ECD (0x0080u) /* extend color depth */ +#define SATURNCMD_PMOD_SPD (0x0040u) /* do not skip index 0 */ /* ---- Texture table ------------------------------------------------------- */ @@ -78,27 +78,27 @@ typedef struct { uint16_t w, h; uint32_t vramByteOffset; /* byte offset into VDP1 VRAM pool */ uint16_t cramWordOffset; /* word offset into VDP2 CRAM for palette */ -} sattexentry_t; +} saturntexentry_t; -static sattexentry_t satTexTable[SAT_RTEXTURE_MAX]; -static uint16_t satTexNext = 1; /* 0 = white fallback */ -static uint32_t satTexVramUsed = 0; -static uint16_t satTexCramUsed = 0; /* in 256-entry slots */ +static saturntexentry_t saturnTexTable[SATURN_RTEXTURE_MAX]; +static uint16_t saturnTexNext = 1; /* 0 = white fallback */ +static uint32_t saturnTexVramUsed = 0; +static uint16_t saturnTexCramUsed = 0; /* in 256-entry slots */ /* ---- Command table buffer ------------------------------------------------ */ -static satcmd_t satCmdBuf[SAT_CMDT_MAX]; -static uint16_t satCmdCount; +static saturncmd_t saturnCmdBuf[SATURN_CMDT_MAX]; +static uint16_t saturnCmdCount; /* ---- Projection state ---------------------------------------------------- */ -static float satFovY = 0.0f; /* 0 = ortho */ -static float satAspect = 1.0f; -static float satNearZ = 1.0f; -static float satFarZ = 1000.0f; +static float saturnFovY = 0.0f; /* 0 = ortho */ +static float saturnAspect = 1.0f; +static float saturnNearZ = 1.0f; +static float saturnFarZ = 1000.0f; -static float satViewEyeX = 0.0f, satViewEyeY = 0.0f, satViewEyeZ = 1.0f; -static float satViewTgtX = 0.0f, satViewTgtY = 0.0f, satViewTgtZ = 0.0f; +static float saturnViewEyeX = 0.0f, saturnViewEyeY = 0.0f, saturnViewEyeZ = 1.0f; +static float saturnViewTgtX = 0.0f, saturnViewTgtY = 0.0f, saturnViewTgtZ = 0.0f; /* ---- Helpers ------------------------------------------------------------- */ @@ -114,7 +114,7 @@ static uint16_t toRGB1555(color_t c) { /* Write palette into VDP2 CRAM at the texture's slot. * CRAM is mapped at 0x25F00000 (Saturn memory map). * Each palette slot is 512 bytes = 256 × uint16_t. */ -static void uploadPalette(sattexentry_t *e) { +static void uploadPalette(saturntexentry_t *e) { volatile uint16_t *cram = (volatile uint16_t *)0x25F00000; uint32_t base = (uint32_t)e->cramWordOffset * 256u; for(uint32_t i = 0; i < 256; i++) { @@ -125,9 +125,9 @@ static void uploadPalette(sattexentry_t *e) { /* Copy indices row-by-row into VDP1 VRAM. * VDP1 VRAM is at 0x05C00000. Textures must be stored starting on an * 8-byte boundary; we keep our pool 8-byte aligned already. */ -static void uploadIndices(sattexentry_t *e) { +static void uploadIndices(saturntexentry_t *e) { volatile uint8_t *vram = - (volatile uint8_t *)(0x05C00000u + SAT_TEXTURE_VRAM_BASE + e->vramByteOffset); + (volatile uint8_t *)(0x05C00000u + SATURN_TEXTURE_VRAM_BASE + e->vramByteOffset); uint32_t total = (uint32_t)e->w * e->h; for(uint32_t i = 0; i < total; i++) { vram[i] = e->cpuIndices[i]; @@ -135,10 +135,10 @@ static void uploadIndices(sattexentry_t *e) { } /* Return a fresh command slot or NULL if full. */ -static satcmd_t *allocCmd(void) { - if(satCmdCount >= SAT_CMDT_MAX) return NULL; - satcmd_t *c = &satCmdBuf[satCmdCount++]; - memoryZero(c, sizeof(satcmd_t)); +static saturncmd_t *allocCmd(void) { + if(saturnCmdCount >= SATURN_CMDT_MAX) return NULL; + saturncmd_t *c = &saturnCmdBuf[saturnCmdCount++]; + memoryZero(c, sizeof(saturncmd_t)); return c; } @@ -146,24 +146,24 @@ static satcmd_t *allocCmd(void) { errorret_t renderSaturnInit(void) { logDebug("[Saturn] renderSaturnInit\n"); - memoryZero(satTexTable, sizeof(satTexTable)); - satTexNext = 1; - satTexVramUsed = 0; - satTexCramUsed = 0; - satCmdCount = 0; + memoryZero(saturnTexTable, sizeof(saturnTexTable)); + saturnTexNext = 1; + saturnTexVramUsed = 0; + saturnTexCramUsed = 0; + saturnCmdCount = 0; /* White 1×1 fallback: slot 0 */ - sattexentry_t *e = &satTexTable[0]; + saturntexentry_t *e = &saturnTexTable[0]; e->cpuIndices = (uint8_t *)malloc(1); assertNotNull(e->cpuIndices, "Saturn: failed to allocate fallback index buffer"); e->cpuIndices[0] = 0; memoryZero(e->palette, 256 * sizeof(color_t)); e->palette[0] = COLOR_WHITE; e->w = 1; e->h = 1; - e->vramByteOffset = satTexVramUsed; - e->cramWordOffset = satTexCramUsed; - satTexVramUsed += 8; /* 8-byte minimum alignment */ - satTexCramUsed++; + e->vramByteOffset = saturnTexVramUsed; + e->cramWordOffset = saturnTexCramUsed; + saturnTexVramUsed += 8; /* 8-byte minimum alignment */ + saturnTexCramUsed++; uploadIndices(e); uploadPalette(e); @@ -176,29 +176,29 @@ rtexture_t renderSaturnTextureCreate( uint16_t w, uint16_t h, const uint8_t *indices, const color_t *palette ) { - assertTrue(satTexNext < SAT_RTEXTURE_MAX, "Saturn texture table full"); + assertTrue(saturnTexNext < SATURN_RTEXTURE_MAX, "Saturn texture table full"); uint32_t byteCount = (uint32_t)w * h; /* Round up to 8-byte boundary for SRCA alignment. */ uint32_t vramBytes = (byteCount + 7u) & ~7u; assertTrue( - satTexVramUsed + vramBytes <= SAT_TEXTURE_VRAM_SIZE, + saturnTexVramUsed + vramBytes <= SATURN_TEXTURE_VRAM_SIZE, "Saturn VDP1 texture VRAM exhausted" ); - rtexture_t handle = (rtexture_t)satTexNext++; - sattexentry_t *e = &satTexTable[handle]; + rtexture_t handle = (rtexture_t)saturnTexNext++; + saturntexentry_t *e = &saturnTexTable[handle]; e->cpuIndices = (uint8_t *)malloc(byteCount); assertNotNull(e->cpuIndices, "Saturn: failed to allocate cpu index buffer"); memoryCopy(e->cpuIndices, indices, byteCount); memoryCopy(e->palette, palette, 256 * sizeof(color_t)); e->w = w; e->h = h; - e->vramByteOffset = satTexVramUsed; - e->cramWordOffset = satTexCramUsed; + e->vramByteOffset = saturnTexVramUsed; + e->cramWordOffset = saturnTexCramUsed; - satTexVramUsed += vramBytes; - satTexCramUsed++; + saturnTexVramUsed += vramBytes; + saturnTexCramUsed++; uploadIndices(e); uploadPalette(e); @@ -206,31 +206,31 @@ rtexture_t renderSaturnTextureCreate( } void renderSaturnTextureDispose(rtexture_t tex) { - if(tex == RTEXTURE_NONE || tex >= SAT_RTEXTURE_MAX) return; - sattexentry_t *e = &satTexTable[tex]; + if(tex == RTEXTURE_NONE || tex >= SATURN_RTEXTURE_MAX) return; + saturntexentry_t *e = &saturnTexTable[tex]; if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; } } color_t *renderSaturnTextureGetPalette(rtexture_t tex) { - if(tex == RTEXTURE_NONE || tex >= SAT_RTEXTURE_MAX) return NULL; - return satTexTable[tex].palette; + if(tex == RTEXTURE_NONE || tex >= SATURN_RTEXTURE_MAX) return NULL; + return saturnTexTable[tex].palette; } uint8_t *renderSaturnTextureGetIndices(rtexture_t tex) { - if(tex == RTEXTURE_NONE || tex >= SAT_RTEXTURE_MAX) return NULL; - return satTexTable[tex].cpuIndices; + if(tex == RTEXTURE_NONE || tex >= SATURN_RTEXTURE_MAX) return NULL; + return saturnTexTable[tex].cpuIndices; } /* ---- Flush --------------------------------------------------------------- */ /* Fill in the SRCA/CMDSIZE/CMDCOLR fields from a texture handle. */ -static void cmdSetTexture(satcmd_t *cmd, rtexture_t tex) { - sattexentry_t *e = (tex < SAT_RTEXTURE_MAX && satTexTable[tex].cpuIndices) - ? &satTexTable[tex] - : &satTexTable[0]; +static void cmdSetTexture(saturncmd_t *cmd, rtexture_t tex) { + saturntexentry_t *e = (tex < SATURN_RTEXTURE_MAX && saturnTexTable[tex].cpuIndices) + ? &saturnTexTable[tex] + : &saturnTexTable[0]; /* SRCA = byte offset from VDP1 VRAM base / 8. */ - uint32_t srcByteAddr = SAT_TEXTURE_VRAM_BASE + e->vramByteOffset; + uint32_t srcByteAddr = SATURN_TEXTURE_VRAM_BASE + e->vramByteOffset; cmd->srca = (uint16_t)(srcByteAddr / 8u); /* SIZE = ((width/8) << 8) | height (each axis limited to 0-255 after /8). */ @@ -241,18 +241,18 @@ static void cmdSetTexture(satcmd_t *cmd, rtexture_t tex) { uint32_t cramWordBase = (uint32_t)e->cramWordOffset * 256u; cmd->colr = (uint16_t)(cramWordBase / 16u); - cmd->pmod = SATCMD_PMOD_8BPP_CBANK; /* 256-color, index 0 transparent */ + cmd->pmod = SATURNCMD_PMOD_8BPP_CBANK; /* 256-color, index 0 transparent */ } static void flush2DSprite(const ropsprite_t *s) { - satcmd_t *cmd = allocCmd(); + saturncmd_t *cmd = allocCmd(); if(!cmd) return; - sattexentry_t *e = (s->texture < SAT_RTEXTURE_MAX && satTexTable[s->texture].cpuIndices) - ? &satTexTable[s->texture] - : &satTexTable[0]; + saturntexentry_t *e = (s->texture < SATURN_RTEXTURE_MAX && saturnTexTable[s->texture].cpuIndices) + ? &saturnTexTable[s->texture] + : &saturnTexTable[0]; - cmd->ctrl = SATCMD_CTRL_NORMAL_SPRITE; + cmd->ctrl = SATURNCMD_CTRL_NORMAL_SPRITE; cmd->link = 0; cmdSetTexture(cmd, s->texture); @@ -265,7 +265,7 @@ static void flush2DSprite(const ropsprite_t *s) { /* UV sub-region: the VDP1 always draws the full texture, so to support * sprite atlases we would need a clipped intermediate. For now we treat * the full texture as the sprite frame (atlas sub-rect is a TODO). */ - (void)e; /* suppress unused warning for e->w/h if UV clipping is added */ + (void)e; } /* @@ -277,20 +277,19 @@ static void project( float *sx, float *sy ) { /* Translate relative to eye. */ - float rx = wx - satViewEyeX; - float ry = wy - satViewEyeY; - float rz = wz - satViewEyeZ; + float rx = wx - saturnViewEyeX; + float ry = wy - saturnViewEyeY; + float rz = wz - saturnViewEyeZ; - /* Rotate view to look at target (approximated: no full matrix here). */ /* TODO: replace with a proper view-matrix multiply for non-axis-aligned cameras. */ - float fwd_z = satViewTgtZ - satViewEyeZ; - (void)fwd_z; /* simple pass-through for now */ + float fwd_z = saturnViewTgtZ - saturnViewEyeZ; + (void)fwd_z; float half_w = (float)DUSK_DISPLAY_WIDTH * 0.5f; float half_h = (float)DUSK_DISPLAY_HEIGHT * 0.5f; - if(satFovY > 0.0f && rz != 0.0f) { - float focal = half_h / (satFovY * 0.5f); + if(saturnFovY > 0.0f && rz != 0.0f) { + float focal = half_h / (saturnFovY * 0.5f); *sx = half_w + (rx / rz) * focal; *sy = half_h - (ry / rz) * focal; } else { @@ -300,10 +299,10 @@ static void project( } static void flush3DQuad(const ropquad3d_t *q) { - satcmd_t *cmd = allocCmd(); + saturncmd_t *cmd = allocCmd(); if(!cmd) return; - cmd->ctrl = SATCMD_CTRL_DISTORTED_SPRITE; + cmd->ctrl = SATURNCMD_CTRL_DISTORTED_SPRITE; cmd->link = 0; cmdSetTexture(cmd, q->texture); @@ -332,35 +331,37 @@ static void flush3DQuad(const ropquad3d_t *q) { /* Submit the finished command table to VDP1 VRAM and trigger rendering. */ static void submitCmdTable(void) { /* Append end-of-list sentinel. */ - if(satCmdCount < SAT_CMDT_MAX) { - satcmd_t *end = &satCmdBuf[satCmdCount]; - memoryZero(end, sizeof(satcmd_t)); - end->ctrl = SATCMD_CTRL_END; + if(saturnCmdCount < SATURN_CMDT_MAX) { + saturncmd_t *end = &saturnCmdBuf[saturnCmdCount]; + memoryZero(end, sizeof(saturncmd_t)); + end->ctrl = SATURNCMD_CTRL_END; } /* DMA or CPU-copy the command table to VDP1 VRAM at offset 0x000000. * VDP1 VRAM starts at 0x05C00000 in the Saturn memory map. */ - volatile satcmd_t *vdp1CmdTable = (volatile satcmd_t *)0x05C00000u; - uint32_t count = satCmdCount + 1u; /* include the END entry */ + volatile saturncmd_t *vdp1CmdTable = (volatile saturncmd_t *)0x05C00000u; + uint32_t count = saturnCmdCount + 1u; /* include the END entry */ for(uint32_t i = 0; i < count; i++) { - vdp1CmdTable[i] = satCmdBuf[i]; + vdp1CmdTable[i] = saturnCmdBuf[i]; } /* Set VDP1 command table top address to 0x000000 (the default). */ volatile uint16_t *vdp1Regs = (volatile uint16_t *)0x25D00000u; - /* VDP1 MODR (Mode Register) — ensure draw mode is correct */ - vdp1Regs[0] = 0x0000; /* PTMR: plot trigger — VDP1 draws on frame change */ - /* EWDR, EWLR, EWRR — Erase/Write window (full screen) */ - vdp1Regs[2] = 0x0000; /* EWDR: erase write data (black) */ - vdp1Regs[3] = 0x0000; /* EWLR: top-left (0,0) */ + /* PTMR: plot trigger — VDP1 draws on frame change */ + vdp1Regs[0] = 0x0000; + /* EWDR: erase write data (black) */ + vdp1Regs[2] = 0x0000; + /* EWLR: top-left (0,0) */ + vdp1Regs[3] = 0x0000; + /* EWRR: bottom-right */ vdp1Regs[4] = (uint16_t)(((DUSK_DISPLAY_HEIGHT - 1) << 9) | - ((DUSK_DISPLAY_WIDTH / 2) - 1)); /* EWRR */ + ((DUSK_DISPLAY_WIDTH / 2) - 1)); } errorret_t renderSaturnFlush(ropbuffer_t *buf) { logDebug("[Saturn] renderSaturnFlush: count=%u\n", (unsigned)buf->count); - satCmdCount = 0; + saturnCmdCount = 0; uint32_t offset = 0; while(offset < buf->byteCount) { @@ -375,9 +376,9 @@ errorret_t renderSaturnFlush(ropbuffer_t *buf) { cram[0] = toRGB1555(c->color); /* Issue a VDP1 system clipping command to reset the clip window. */ - satcmd_t *clip = allocCmd(); + saturncmd_t *clip = allocCmd(); if(clip) { - clip->ctrl = SATCMD_CTRL_SYSCLIP; + clip->ctrl = SATURNCMD_CTRL_SYSCLIP; clip->link = 0; clip->xa = 0; clip->ya = 0; @@ -393,21 +394,21 @@ errorret_t renderSaturnFlush(ropbuffer_t *buf) { case ROP_SET_PROJECTION: { const ropprojection_t *p = (const ropprojection_t *)hdr; - satFovY = fixedToFloat(p->fovY); - satAspect = fixedToFloat(p->aspect); - satNearZ = fixedToFloat(p->nearZ); - satFarZ = fixedToFloat(p->farZ); + saturnFovY = fixedToFloat(p->fovY); + saturnAspect = fixedToFloat(p->aspect); + saturnNearZ = fixedToFloat(p->nearZ); + saturnFarZ = fixedToFloat(p->farZ); break; } case ROP_SET_VIEW: { const ropview_t *v = (const ropview_t *)hdr; - satViewEyeX = (float)v->eyeX; - satViewEyeY = (float)v->eyeY; - satViewEyeZ = (float)v->eyeZ; - satViewTgtX = (float)v->tgtX; - satViewTgtY = (float)v->tgtY; - satViewTgtZ = (float)v->tgtZ; + saturnViewEyeX = (float)v->eyeX; + saturnViewEyeY = (float)v->eyeY; + saturnViewEyeZ = (float)v->eyeZ; + saturnViewTgtX = (float)v->tgtX; + saturnViewTgtY = (float)v->tgtY; + saturnViewTgtZ = (float)v->tgtZ; break; } @@ -436,11 +437,11 @@ errorret_t renderSaturnFlush(ropbuffer_t *buf) { /* ---- Dispose ------------------------------------------------------------- */ void renderSaturnDispose(void) { - for(uint16_t i = 0; i < satTexNext; i++) { - sattexentry_t *e = &satTexTable[i]; + for(uint16_t i = 0; i < saturnTexNext; i++) { + saturntexentry_t *e = &saturnTexTable[i]; if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; } } - satTexNext = 1; - satTexVramUsed = 0; - satTexCramUsed = 0; + saturnTexNext = 1; + saturnTexVramUsed = 0; + saturnTexCramUsed = 0; } diff --git a/src/dusksat/display/render/rendersat.h b/src/dusksaturn/display/render/rendersaturn.h similarity index 100% rename from src/dusksat/display/render/rendersat.h rename to src/dusksaturn/display/render/rendersaturn.h diff --git a/src/dusksat/duskplatform.h b/src/dusksaturn/duskplatform.h similarity index 100% rename from src/dusksat/duskplatform.h rename to src/dusksaturn/duskplatform.h diff --git a/src/dusksat/display/render/CMakeLists.txt b/src/dusksaturn/input/CMakeLists.txt similarity index 80% rename from src/dusksat/display/render/CMakeLists.txt rename to src/dusksaturn/input/CMakeLists.txt index 91dbc43c..3dafd311 100644 --- a/src/dusksat/display/render/CMakeLists.txt +++ b/src/dusksaturn/input/CMakeLists.txt @@ -5,5 +5,5 @@ target_sources(${DUSK_BINARY_TARGET_NAME} PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/rendersat.c + ${CMAKE_CURRENT_LIST_DIR}/inputsaturn.c ) diff --git a/src/dusksaturn/input/inputplatform.h b/src/dusksaturn/input/inputplatform.h new file mode 100644 index 00000000..2dd087f5 --- /dev/null +++ b/src/dusksaturn/input/inputplatform.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "inputsaturn.h" + +#ifdef DUSK_INPUT_GAMEPAD + typedef inputgamepadbuttonsaturn_t inputgamepadbuttonplatform_t; + typedef inputgamepadaxissaturn_t inputgamepadaxissplatform_t; +#endif + +typedef inputsaturn_t inputplatform_t; + +#define inputInitPlatform inputInitSaturn +#define inputUpdatePlatform inputUpdateSaturn +#define inputButtonGetValuePlatform inputButtonGetValueSaturn diff --git a/src/dusksaturn/input/inputsaturn.c b/src/dusksaturn/input/inputsaturn.c new file mode 100644 index 00000000..28a25cf2 --- /dev/null +++ b/src/dusksaturn/input/inputsaturn.c @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "input/input.h" +#include "assert/assert.h" +#include + +/* + * Saturn standard digital pad buttons. + * gpButton values are bit positions within the SMPC peripheral held.button word. + * + * bit 11 = Right bit 10 = Left bit 9 = Down bit 8 = Up + * bit 7 = Start bit 6 = A bit 5 = C bit 4 = B + * bit 3 = R bit 2 = X bit 1 = Y bit 0 = Z + * + * The Saturn has no L shoulder in the standard pad (only R); the 3D pad + * exposes L as bit 15 via the analog extension. + */ + +inputbuttondata_t INPUT_BUTTON_DATA[] = { + { .name = "a", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 6 } }, + { .name = "b", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 4 } }, + { .name = "c", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 5 } }, + { .name = "x", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 2 } }, + { .name = "y", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 1 } }, + { .name = "z", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 0 } }, + { .name = "start", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 7 } }, + { .name = "up", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 8 } }, + { .name = "down", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 9 } }, + { .name = "left", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 10 } }, + { .name = "right", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 11 } }, + { .name = "l", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 15 } }, + { .name = "r", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 3 } }, + { .name = "accept", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 6 } }, /* A */ + { .name = "cancel", { .type = INPUT_BUTTON_TYPE_GAMEPAD, .gpButton = 4 } }, /* B */ + { .name = NULL } +}; + +errorret_t inputInitSaturn(void) { + #define X(buttonName, buttonAction) \ + inputBind(inputButtonGetByName(buttonName), buttonAction); + X("up", INPUT_ACTION_UP); + X("down", INPUT_ACTION_DOWN); + X("left", INPUT_ACTION_LEFT); + X("right", INPUT_ACTION_RIGHT); + X("accept", INPUT_ACTION_ACCEPT); + X("cancel", INPUT_ACTION_CANCEL); + X("start", INPUT_ACTION_RAGEQUIT); + #undef X + + errorOk(); +} + +void inputUpdateSaturn(void) { + smpc_peripheral_digital_t digital; + smpc_peripheral_digital_port(1, &digital); + /* Yaul's held.button bitmask matches the bit-position layout above. */ + memcpy(&INPUT.platform.padButtons, &digital.held.button, sizeof(INPUT.platform.padButtons)); +} + +int16_t inputButtonGetValueSaturn(const inputbutton_t button) { + switch(button.type) { + case INPUT_BUTTON_TYPE_GAMEPAD: + return (INPUT.platform.padButtons & (uint16_t)(1u << button.gpButton)) + ? INT16_MAX : 0; + default: + assertUnreachable("Unknown input button type"); + return 0; + } +} + +int16_t inputGetDeadzoneSaturn(const inputbutton_t button) { + (void)button; + return 0; +} diff --git a/src/dusksaturn/input/inputsaturn.h b/src/dusksaturn/input/inputsaturn.h new file mode 100644 index 00000000..a875af91 --- /dev/null +++ b/src/dusksaturn/input/inputsaturn.h @@ -0,0 +1,41 @@ +/** + * 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 + +/* + * Saturn standard digital pad buttons (smpc_peripheral_digital_t). + * Yaul's smpc_peripheral_digital_port() fills the bitmask in held.button. + * + * Button bitmask: + * bit 11 = Right bit 10 = Left bit 9 = Down bit 8 = Up + * bit 7 = Start bit 6 = A bit 5 = C bit 4 = B + * bit 3 = R bit 2 = X bit 1 = Y bit 0 = Z + */ + +/* gpButton stores the bit-position within the SMPC button word */ +typedef uint8_t inputgamepadbuttonsaturn_t; + +/* Saturn's standard pad is digital-only; define a stub axis type */ +typedef enum { + SATURN_GAMEPAD_AXIS_COUNT = 0 +} inputgamepadaxissaturn_t; + +typedef struct { + uint16_t padButtons; /* SMPC digital pad held-button bitmask, port 1 */ +} inputsaturn_t; + +/* Forward declaration — avoids circular include with inputbutton.h */ +typedef struct inputbutton_s inputbutton_t; + +errorret_t inputInitSaturn(void); +void inputUpdateSaturn(void); +int16_t inputButtonGetValueSaturn(const inputbutton_t button); +int16_t inputGetDeadzoneSaturn(const inputbutton_t button); diff --git a/src/dusksat/log/CMakeLists.txt b/src/dusksaturn/log/CMakeLists.txt similarity index 100% rename from src/dusksat/log/CMakeLists.txt rename to src/dusksaturn/log/CMakeLists.txt diff --git a/src/dusksat/log/log.c b/src/dusksaturn/log/log.c similarity index 77% rename from src/dusksat/log/log.c rename to src/dusksaturn/log/log.c index d29132af..bbaf9455 100644 --- a/src/dusksat/log/log.c +++ b/src/dusksaturn/log/log.c @@ -12,8 +12,9 @@ * On Saturn, stdout goes to the debug serial port (via Yaul's dbgio module). * With a comm link or emulator (Mednafen, SSF) this is visible on the host. * - * TODO: add dbgio_init() in systemSaturnInit() and replace vprintf with - * dbgio_printf() for hardware-accurate serial output. + * TODO: add dbgio_dev_default_set(DBGIO_DEV_USB_CART, NULL) in + * systemSaturnInit() and replace vprintf with dbgio_printf() for + * hardware-accurate serial output. */ void logDebug(const char_t *message, ...) { diff --git a/src/dusksaturn/network/CMakeLists.txt b/src/dusksaturn/network/CMakeLists.txt new file mode 100644 index 00000000..d9dcbf8b --- /dev/null +++ b/src/dusksaturn/network/CMakeLists.txt @@ -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_BINARY_TARGET_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/networksaturn.c +) diff --git a/src/dusksat/network/networkplatform.h b/src/dusksaturn/network/networkplatform.h similarity index 89% rename from src/dusksat/network/networkplatform.h rename to src/dusksaturn/network/networkplatform.h index cbfee9f0..1083aad5 100644 --- a/src/dusksat/network/networkplatform.h +++ b/src/dusksaturn/network/networkplatform.h @@ -6,7 +6,7 @@ */ #pragma once -#include "networksat.h" +#include "networksaturn.h" #define networkPlatformInit networkSaturnInit #define networkPlatformUpdate networkSaturnUpdate @@ -16,4 +16,4 @@ #define networkPlatformRequestDisconnection networkSaturnRequestDisconnection #define networkPlatformGetInfo networkSaturnGetInfo -typedef networksat_t networkplatform_t; +typedef networksaturn_t networkplatform_t; diff --git a/src/dusksat/network/networksat.c b/src/dusksaturn/network/networksaturn.c similarity index 100% rename from src/dusksat/network/networksat.c rename to src/dusksaturn/network/networksaturn.c diff --git a/src/dusksat/network/networksat.h b/src/dusksaturn/network/networksaturn.h similarity index 98% rename from src/dusksat/network/networksat.h rename to src/dusksaturn/network/networksaturn.h index be3503cf..d93671a0 100644 --- a/src/dusksat/network/networksat.h +++ b/src/dusksaturn/network/networksaturn.h @@ -17,7 +17,7 @@ typedef struct { uint8_t unused; -} networksat_t; +} networksaturn_t; errorret_t networkSaturnInit(void); errorret_t networkSaturnUpdate(void); diff --git a/src/dusksaturn/save/CMakeLists.txt b/src/dusksaturn/save/CMakeLists.txt new file mode 100644 index 00000000..8c44ff5a --- /dev/null +++ b/src/dusksaturn/save/CMakeLists.txt @@ -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_BINARY_TARGET_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/savesaturn.c + ${CMAKE_CURRENT_LIST_DIR}/savestreamsaturn.c +) diff --git a/src/dusksat/save/saveplatform.h b/src/dusksaturn/save/saveplatform.h similarity index 85% rename from src/dusksat/save/saveplatform.h rename to src/dusksaturn/save/saveplatform.h index dc6a7fe8..5f9388f7 100644 --- a/src/dusksat/save/saveplatform.h +++ b/src/dusksaturn/save/saveplatform.h @@ -6,11 +6,11 @@ */ #pragma once -#include "save/savesat.h" -#include "save/savestreamsat.h" +#include "save/savesaturn.h" +#include "save/savestreamsaturn.h" -typedef savesat_t saveplatform_t; -typedef savestreamsat_t saveplatformstream_t; +typedef savesaturn_t saveplatform_t; +typedef savestreamsaturn_t saveplatformstream_t; #define saveInitPlatform saveInitSaturn #define saveDisposePlatform saveDisposeSaturn diff --git a/src/dusksaturn/save/savesaturn.c b/src/dusksaturn/save/savesaturn.c new file mode 100644 index 00000000..dc8c9939 --- /dev/null +++ b/src/dusksaturn/save/savesaturn.c @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "save/save.h" +#include "save/savesaturn.h" +#include "log/log.h" +#include + +static bup_t _bupState; + +errorret_t saveInitSaturn(void) { + logDebug("[Saturn] saveInitSaturn\n"); + bup_devices_t devices; + bup_init(&_bupState, &devices); + errorOk(); +} + +errorret_t saveDisposeSaturn(void) { + errorOk(); +} + +errorret_t saveDeleteSaturn(const uint8_t slot) { + logDebug("[Saturn] saveDeleteSaturn: slot=%u\n", (unsigned)slot); + /* TODO: construct filename for slot and call bup_delete: + * char filename[8]; + * snprintf(filename, sizeof(filename), "%s%02u", SAVE_SATURN_TITLE_ID, slot); + * bup_delete(BUP_DEV_INTERNAL, filename); + */ + errorOk(); +} diff --git a/src/dusksat/save/savesat.h b/src/dusksaturn/save/savesaturn.h similarity index 87% rename from src/dusksat/save/savesat.h rename to src/dusksaturn/save/savesaturn.h index d016ac7f..f212171e 100644 --- a/src/dusksat/save/savesat.h +++ b/src/dusksaturn/save/savesaturn.h @@ -15,13 +15,13 @@ * the save file name (e.g. "DUSK00", "DUSK01", …). */ -#ifndef SAVE_SAT_TITLE_ID - #define SAVE_SAT_TITLE_ID "DUSK" +#ifndef SAVE_SATURN_TITLE_ID + #define SAVE_SATURN_TITLE_ID "DUSK" #endif typedef struct { uint8_t unused; -} savesat_t; +} savesaturn_t; errorret_t saveInitSaturn(void); errorret_t saveDisposeSaturn(void); diff --git a/src/dusksat/save/savestreamsat.c b/src/dusksaturn/save/savestreamsaturn.c similarity index 53% rename from src/dusksat/save/savestreamsat.c rename to src/dusksaturn/save/savestreamsaturn.c index c7622396..c32f760e 100644 --- a/src/dusksat/save/savestreamsat.c +++ b/src/dusksaturn/save/savestreamsaturn.c @@ -6,64 +6,69 @@ */ #include "save/save.h" -#include "save/savestreamsat.h" +#include "save/savestreamsaturn.h" #include "util/memory.h" #include #include +#include /* * Saturn backup RAM (bup) does not support partial reads/seeks; data must * be read or written as a single contiguous block. We buffer the entire * save slot in heap memory and serialize to/from the bup device on open/close. * - * Maximum save size = sizeof(savefile_t). Adjust SAVE_SAT_MAX if needed. + * Maximum save size = sizeof(savefile_t). Adjust SAVE_SATURN_MAX if needed. */ -#define SAVE_SAT_MAX sizeof(savefile_t) +#define SAVE_SATURN_MAX sizeof(savefile_t) errorret_t saveStreamOpenReadSaturn( - savestreamsat_t *p, bool_t *found, const uint8_t slot + savestreamsaturn_t *p, bool_t *found, const uint8_t slot ) { - p->buf = (uint8_t *)malloc(SAVE_SAT_MAX); + p->buf = (uint8_t *)malloc(SAVE_SATURN_MAX); if(!p->buf) errorThrow("Saturn: failed to allocate save read buffer"); - p->size = SAVE_SAT_MAX; + p->size = SAVE_SATURN_MAX; p->pos = 0; p->slot = slot; p->writing = false; - /* - * TODO: read from bup device into p->buf: - * int32_t ret = bup_read(BUP_DEV_INTERNAL, filename, p->buf, SAVE_SAT_MAX); + /* TODO: construct filename and read from bup device: + * char filename[8]; + * snprintf(filename, sizeof(filename), "%s%02u", SAVE_SATURN_TITLE_ID, slot); + * int32_t ret = bup_read(BUP_DEV_INTERNAL, filename, p->buf, SAVE_SATURN_MAX); * *found = (ret >= 0); */ - *found = false; /* stub: always report no save */ + *found = false; errorOk(); } -errorret_t saveStreamOpenWriteSaturn(savestreamsat_t *p, const uint8_t slot) { - p->buf = (uint8_t *)malloc(SAVE_SAT_MAX); +errorret_t saveStreamOpenWriteSaturn(savestreamsaturn_t *p, const uint8_t slot) { + p->buf = (uint8_t *)malloc(SAVE_SATURN_MAX); if(!p->buf) errorThrow("Saturn: failed to allocate save write buffer"); - memoryZero(p->buf, SAVE_SAT_MAX); - p->size = SAVE_SAT_MAX; + memoryZero(p->buf, SAVE_SATURN_MAX); + p->size = SAVE_SATURN_MAX; p->pos = 0; p->slot = slot; p->writing = true; errorOk(); } -void saveStreamCloseSaturn(savestreamsat_t *p) { +void saveStreamCloseSaturn(savestreamsaturn_t *p) { if(p->writing && p->buf) { - /* - * TODO: write p->buf to bup device: + /* TODO: write p->buf to bup device: + * char filename[8]; + * snprintf(filename, sizeof(filename), "%s%02u", SAVE_SATURN_TITLE_ID, p->slot); * bup_dir_t dir; - * bup_write(BUP_DEV_INTERNAL, &dir, p->buf, SAVE_SAT_MAX, BUP_MODE_NEW); + * memset(&dir, 0, sizeof(dir)); + * strncpy(dir.filename, filename, sizeof(dir.filename)); + * bup_write(BUP_DEV_INTERNAL, &dir, p->buf, SAVE_SATURN_MAX, BUP_MODE_NEW); */ } if(p->buf) { free(p->buf); p->buf = NULL; } } errorret_t saveStreamReadBytesSaturn( - savestreamsat_t *p, void *buf, const size_t len + savestreamsaturn_t *p, void *buf, const size_t len ) { if(p->pos + len > p->size) errorThrow("Saturn: read past end of save buffer"); memoryCopy(buf, p->buf + p->pos, len); @@ -72,7 +77,7 @@ errorret_t saveStreamReadBytesSaturn( } errorret_t saveStreamWriteBytesSaturn( - savestreamsat_t *p, const void *buf, const size_t len + savestreamsaturn_t *p, const void *buf, const size_t len ) { if(p->pos + len > p->size) errorThrow("Saturn: write past end of save buffer"); memoryCopy(p->buf + p->pos, buf, len); @@ -80,7 +85,7 @@ errorret_t saveStreamWriteBytesSaturn( errorOk(); } -errorret_t saveStreamSeekSaturn(savestreamsat_t *p, const size_t pos) { +errorret_t saveStreamSeekSaturn(savestreamsaturn_t *p, const size_t pos) { if(pos > p->size) errorThrow("Saturn: seek out of bounds"); p->pos = pos; errorOk(); diff --git a/src/dusksat/save/savestreamsat.h b/src/dusksaturn/save/savestreamsaturn.h similarity index 52% rename from src/dusksat/save/savestreamsat.h rename to src/dusksaturn/save/savestreamsaturn.h index 77e617a3..472637b4 100644 --- a/src/dusksat/save/savestreamsat.h +++ b/src/dusksaturn/save/savestreamsaturn.h @@ -16,17 +16,17 @@ typedef struct { size_t pos; uint8_t slot; bool_t writing; -} savestreamsat_t; +} savestreamsaturn_t; errorret_t saveStreamOpenReadSaturn( - savestreamsat_t *p, bool_t *found, const uint8_t slot + savestreamsaturn_t *p, bool_t *found, const uint8_t slot ); -errorret_t saveStreamOpenWriteSaturn(savestreamsat_t *p, const uint8_t slot); -void saveStreamCloseSaturn(savestreamsat_t *p); +errorret_t saveStreamOpenWriteSaturn(savestreamsaturn_t *p, const uint8_t slot); +void saveStreamCloseSaturn(savestreamsaturn_t *p); errorret_t saveStreamReadBytesSaturn( - savestreamsat_t *p, void *buf, const size_t len + savestreamsaturn_t *p, void *buf, const size_t len ); errorret_t saveStreamWriteBytesSaturn( - savestreamsat_t *p, const void *buf, const size_t len + savestreamsaturn_t *p, const void *buf, const size_t len ); -errorret_t saveStreamSeekSaturn(savestreamsat_t *p, const size_t pos); +errorret_t saveStreamSeekSaturn(savestreamsaturn_t *p, const size_t pos); diff --git a/src/dusksat/script/module/moduleplatformplatform.h b/src/dusksaturn/script/module/moduleplatformplatform.h similarity index 85% rename from src/dusksat/script/module/moduleplatformplatform.h rename to src/dusksaturn/script/module/moduleplatformplatform.h index 2a2a32b3..70f70980 100644 --- a/src/dusksat/script/module/moduleplatformplatform.h +++ b/src/dusksaturn/script/module/moduleplatformplatform.h @@ -6,6 +6,6 @@ */ #pragma once -#include "moduleplatformsat.h" +#include "moduleplatformsaturn.h" #define modulePlatformPlatform modulePlatformSaturn diff --git a/src/dusksat/script/module/moduleplatformsat.h b/src/dusksaturn/script/module/moduleplatformsaturn.h similarity index 100% rename from src/dusksat/script/module/moduleplatformsat.h rename to src/dusksaturn/script/module/moduleplatformsaturn.h diff --git a/src/dusksaturn/system/CMakeLists.txt b/src/dusksaturn/system/CMakeLists.txt new file mode 100644 index 00000000..22961cec --- /dev/null +++ b/src/dusksaturn/system/CMakeLists.txt @@ -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_BINARY_TARGET_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/systemsaturn.c +) diff --git a/src/dusksat/system/systemplatform.h b/src/dusksaturn/system/systemplatform.h similarity index 89% rename from src/dusksat/system/systemplatform.h rename to src/dusksaturn/system/systemplatform.h index 8e3634d2..ff0080b8 100644 --- a/src/dusksat/system/systemplatform.h +++ b/src/dusksaturn/system/systemplatform.h @@ -6,7 +6,7 @@ */ #pragma once -#include "system/systemsat.h" +#include "system/systemsaturn.h" #define systemInitPlatform systemInitSaturn #define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeSaturn diff --git a/src/dusksat/system/systemsat.c b/src/dusksaturn/system/systemsaturn.c similarity index 66% rename from src/dusksat/system/systemsat.c rename to src/dusksaturn/system/systemsaturn.c index 329ec816..8384dc60 100644 --- a/src/dusksat/system/systemsat.c +++ b/src/dusksaturn/system/systemsaturn.c @@ -5,16 +5,14 @@ * https://opensource.org/licenses/MIT */ -#include "system/systemsat.h" +#include "system/systemsaturn.h" #include "log/log.h" +#include errorret_t systemInitSaturn(void) { logDebug("[Saturn] systemInitSaturn\n"); - /* - * TODO: initialize SMPC peripheral scanning so input reads work. - * smpc_peripheral_init(); - * smpc_peripheral_intback_issue(); - */ + smpc_peripheral_init(); + smpc_peripheral_intback_issue(); errorOk(); } diff --git a/src/dusksat/system/systemsat.h b/src/dusksaturn/system/systemsaturn.h similarity index 100% rename from src/dusksat/system/systemsat.h rename to src/dusksaturn/system/systemsaturn.h diff --git a/src/dusksat/network/CMakeLists.txt b/src/dusksaturn/time/CMakeLists.txt similarity index 80% rename from src/dusksat/network/CMakeLists.txt rename to src/dusksaturn/time/CMakeLists.txt index 02e38282..c9cb7f7a 100644 --- a/src/dusksat/network/CMakeLists.txt +++ b/src/dusksaturn/time/CMakeLists.txt @@ -5,5 +5,5 @@ target_sources(${DUSK_BINARY_TARGET_NAME} PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/networksat.c + ${CMAKE_CURRENT_LIST_DIR}/timesaturn.c ) diff --git a/src/dusksat/time/timeplatform.h b/src/dusksaturn/time/timeplatform.h similarity index 53% rename from src/dusksat/time/timeplatform.h rename to src/dusksaturn/time/timeplatform.h index a933a44b..9e43257f 100644 --- a/src/dusksat/time/timeplatform.h +++ b/src/dusksaturn/time/timeplatform.h @@ -6,9 +6,9 @@ */ #pragma once -#include "time/timesat.h" +#include "time/timesaturn.h" -#define timeTickPlatform timeTickSaturn -#define timeGetDeltaPlatform timeGetDeltaSaturn -#define timeGetRealPlatform timeGetRealSaturn +#define timeTickPlatform timeTickSaturn +#define timeGetDeltaPlatform timeGetDeltaSaturn +#define timeGetRealPlatform timeGetRealSaturn #define timeGetRealTimeZonePlatform timeGetRealTimeZoneSaturn diff --git a/src/dusksat/time/timesat.c b/src/dusksaturn/time/timesaturn.c similarity index 51% rename from src/dusksat/time/timesat.c rename to src/dusksaturn/time/timesaturn.c index 8bc486da..da5cc5bb 100644 --- a/src/dusksat/time/timesat.c +++ b/src/dusksaturn/time/timesaturn.c @@ -5,34 +5,33 @@ * https://opensource.org/licenses/MIT */ -#include "time/timesat.h" +#include "time/timesaturn.h" #include -#define SAT_FPS 60.0 +#define SATURN_FPS 60.0 -static double_t satTimeLast = 0.0; -static double_t satTimeDelta = 0.0; -static double_t satTimeAcc = 0.0; /* accumulated seconds (frame counter) */ +static double_t saturnTimeLast = 0.0; +static double_t saturnTimeDelta = 0.0; +static double_t saturnTimeAcc = 0.0; /* accumulated seconds (frame counter) */ void timeTickSaturn(void) { - double_t now = satTimeAcc + (1.0 / SAT_FPS); - satTimeDelta = now - satTimeAcc; - satTimeLast = satTimeAcc; - satTimeAcc = now; + double_t now = saturnTimeAcc + (1.0 / SATURN_FPS); + saturnTimeDelta = now - saturnTimeAcc; + saturnTimeLast = saturnTimeAcc; + saturnTimeAcc = now; } double_t timeGetDeltaSaturn(void) { - return satTimeDelta; + return saturnTimeDelta; } double_t timeGetRealSaturn(void) { - /* - * TODO: read the SMPC RTC for actual wall-clock time: + /* TODO: read the SMPC RTC for actual wall-clock time: * smpc_rtc_t rtc; * smpc_smc_rtc_read(&rtc); * return rtcToUnixSeconds(&rtc); */ - return satTimeAcc; + return saturnTimeAcc; } double_t timeGetRealTimeZoneSaturn(void) { diff --git a/src/dusksat/time/timesat.h b/src/dusksaturn/time/timesaturn.h similarity index 100% rename from src/dusksat/time/timesat.h rename to src/dusksaturn/time/timesaturn.h diff --git a/src/dusksdl2/input/inputsdl2.c b/src/dusksdl2/input/inputsdl2.c index d20b8a86..fa2f18d2 100644 --- a/src/dusksdl2/input/inputsdl2.c +++ b/src/dusksdl2/input/inputsdl2.c @@ -25,8 +25,8 @@ void inputUpdateSDL2(void) { #ifdef DUSK_INPUT_POINTER case SDL_MOUSEWHEEL: - INPUT.platform.scrollX = (float_t)e.wheel.x; - INPUT.platform.scrollY = (float_t)e.wheel.y; + INPUT.platform.scrollX = e.wheel.x > 0 ? INT16_MAX : (e.wheel.x < 0 ? -INT16_MAX : 0); + INPUT.platform.scrollY = e.wheel.y > 0 ? INT16_MAX : (e.wheel.y < 0 ? -INT16_MAX : 0); break; #endif @@ -61,16 +61,16 @@ void inputUpdateSDL2(void) { int_t windowWidth, windowHeight; SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight); - INPUT.platform.mouseX = (float_t)pointerX / (float_t)windowWidth; - INPUT.platform.mouseY = (float_t)pointerY / (float_t)windowHeight; + INPUT.platform.mouseX = (int16_t)((float_t)pointerX / (float_t)windowWidth * INT16_MAX); + INPUT.platform.mouseY = (int16_t)((float_t)pointerY / (float_t)windowHeight * INT16_MAX); #endif } -float_t inputButtonGetValueSDL2(const inputbutton_t button) { +int16_t inputButtonGetValueSDL2(const inputbutton_t button) { switch(button.type) { #ifdef DUSK_INPUT_KEYBOARD case INPUT_BUTTON_TYPE_KEYBOARD: { - return INPUT.platform.keyboardState[button.scancode] ? 1.0f : 0.0f; + return INPUT.platform.keyboardState[button.scancode] ? INT16_MAX : 0; } #endif @@ -84,7 +84,7 @@ float_t inputButtonGetValueSDL2(const inputbutton_t button) { return INPUT.platform.mouseY; case INPUT_POINTER_AXIS_Z: - return 0.0f; // Not supported in SDL2 + return 0; case INPUT_POINTER_AXIS_WHEEL_X: return INPUT.platform.scrollX; @@ -94,7 +94,7 @@ float_t inputButtonGetValueSDL2(const inputbutton_t button) { default: assertUnreachable("Unknown pointer axis"); - return 0.0f; + return 0; } } #endif @@ -106,30 +106,24 @@ float_t inputButtonGetValueSDL2(const inputbutton_t button) { "Gamepad button out of range" ); - if(SDL_GameControllerGetButton( + return SDL_GameControllerGetButton( INPUT.platform.controller, button.gpButton - )) { - return 1.0f; - } - return 0.0f; + ) ? INT16_MAX : 0; } case INPUT_BUTTON_TYPE_GAMEPAD_AXIS: { - float_t value = 0.0f; - - Sint16 axis = SDL_GameControllerGetAxis( + Sint16 raw = SDL_GameControllerGetAxis( INPUT.platform.controller, button.gpAxis.axis ); - value = (float_t)axis / 32767.0f; - if(!button.gpAxis.positive) value = -value; - value = inputDeadzone(value, inputGetDeadzoneSDL2(button)); - return value; + int16_t value = button.gpAxis.positive ? raw : (int16_t)-raw; + if(value < 0) return 0; + return inputDeadzone(value, inputGetDeadzoneSDL2(button)); } #endif default: { assertUnreachable("Unknown input button type"); - return 0.0f; + return 0; } } } \ No newline at end of file diff --git a/src/dusksdl2/input/inputsdl2.h b/src/dusksdl2/input/inputsdl2.h index 03ba9ebc..31192bf9 100644 --- a/src/dusksdl2/input/inputsdl2.h +++ b/src/dusksdl2/input/inputsdl2.h @@ -39,8 +39,8 @@ typedef struct { #endif #ifdef DUSK_INPUT_POINTER - float_t mouseX, mouseY; - float_t scrollX, scrollY; + int16_t mouseX, mouseY; + int16_t scrollX, scrollY; #endif } inputsdl2_t; @@ -69,18 +69,17 @@ typedef inputsdl2_t inputplatform_t; void inputUpdateSDL2(void); /** - * Returns the deadzone for the given gamepad axis. Requires implementation by - * the host platform. - * + * Returns the deadzone for the given gamepad axis (0 to INT16_MAX). + * * @param button The button to get the deadzone of. - * @return The deadzone for the given gamepad axis. + * @return The deadzone threshold (0 to INT16_MAX). */ -float_t inputGetDeadzoneSDL2(const inputbutton_t button); +int16_t inputGetDeadzoneSDL2(const inputbutton_t button); /** - * Returns the input value (between 0 and 1) of the given button. - * + * Returns the input value of the given button (0 to INT16_MAX). + * * @param button The button to get the value of. - * @return The value of the button, between 0 and 1. + * @return The value of the button (0 to INT16_MAX). */ -float_t inputButtonGetValueSDL2(const inputbutton_t button); \ No newline at end of file +int16_t inputButtonGetValueSDL2(const inputbutton_t button); \ No newline at end of file diff --git a/src/duskvita/input/inputvita.c b/src/duskvita/input/inputvita.c index 339839ab..304bfc36 100644 --- a/src/duskvita/input/inputvita.c +++ b/src/duskvita/input/inputvita.c @@ -82,6 +82,6 @@ inputbuttondata_t INPUT_BUTTON_DATA[] = { { .name = NULL } }; -float_t inputGetDeadzoneSDL2(const inputbutton_t button) { - return 0.17f; +int16_t inputGetDeadzoneSDL2(const inputbutton_t button) { + return (int16_t)(0.17f * INT16_MAX); }