diff --git a/.clang-format b/.clang-format
index 1428d3c71e..8ffd4ebe96 100644
--- a/.clang-format
+++ b/.clang-format
@@ -2,7 +2,7 @@
Language: Cpp
Standard: C++03
AccessModifierOffset: -4
-AlignAfterOpenBracket: Align
+AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignOperands: true
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 37679e3f56..7ccde425c9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,7 +8,7 @@ on:
pull_request:
env:
- # SCCACHE_GHA_ENABLED: "true"
+ SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
@@ -22,7 +22,7 @@ jobs:
matrix:
include:
- name: GCC x86_64
- runner: [self-hosted, Linux]
+ runner: ubuntu-latest
preset: gcc
artifact_arch: x86_64
# - name: GCC aarch64
@@ -41,7 +41,6 @@ jobs:
submodules: recursive
- name: Install dependencies
- if: 'false' # disabled for self-hosted
run: |
sudo apt-get update
sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \
@@ -51,8 +50,7 @@ jobs:
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
- name: Setup sccache
- if: 'false' # disabled for self-hosted
- uses: mozilla-actions/sccache-action@v0.0.9
+ uses: mozilla-actions/sccache-action@v0.0.10
- name: Print sccache stats
run: sccache --show-stats
@@ -67,7 +65,6 @@ jobs:
run: ci/build-appimage.sh
- name: Upload artifacts
- if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
@@ -77,7 +74,7 @@ jobs:
build-apple:
name: Build Apple (${{matrix.name}})
- runs-on: [self-hosted, macOS]
+ runs-on: macos-latest
strategy:
fail-fast: false
matrix:
@@ -86,14 +83,14 @@ jobs:
platform: macos
preset: x-macos-ci-arm64
artifact_name: macos-appleclang-arm64
- # - name: AppleClang macOS x86_64
- # platform: macos
- # preset: x-macos-ci-x86_64
- # artifact_name: macos-appleclang-x86_64
- # - name: AppleClang iOS arm64
- # platform: ios
- # preset: x-ios-ci
- # artifact_name: ios-appleclang-arm64
+ - name: AppleClang macOS x86_64
+ platform: macos
+ preset: x-macos-ci-x86_64
+ artifact_name: macos-appleclang-x86_64
+ - name: AppleClang iOS arm64
+ platform: ios
+ preset: x-ios-ci
+ artifact_name: ios-appleclang-arm64
# - name: AppleClang tvOS arm64
# platform: tvos
# preset: x-tvos-ci
@@ -106,7 +103,6 @@ jobs:
submodules: recursive
- name: Install dependencies
- if: 'false'
run: brew install cmake ninja
- name: Install Rust iOS target
@@ -128,7 +124,7 @@ jobs:
rustup target add x86_64-apple-darwin
- name: Setup sccache
- uses: mozilla-actions/sccache-action@v0.0.9
+ uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
@@ -137,7 +133,6 @@ jobs:
run: cmake --build --preset ${{matrix.preset}}
- name: Upload artifacts
- if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
@@ -145,6 +140,73 @@ jobs:
build/install/Dusk.app
build/install/debug.tar.*
+ build-android:
+ name: Build Android (${{matrix.name}})
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: Clang arm64-v8a
+ preset: x-android-ci-arm64
+ abi: arm64-v8a
+ artifact_arch: arm64
+ rust_target: aarch64-linux-android
+
+ env:
+ ANDROID_NDK_VERSION: "29.0.14206865"
+
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install ninja-build
+
+ - name: Setup Java
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: 17
+
+ - name: Setup Android SDK
+ uses: android-actions/setup-android@v4
+
+ - name: Install Android SDK packages
+ run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}"
+
+ - name: Install Rust Android target
+ run: |
+ rustup toolchain install stable
+ rustup target add ${{matrix.rust_target}}
+
+ - name: Setup sccache
+ uses: mozilla-actions/sccache-action@v0.0.10
+
+ - name: Configure CMake
+ run: cmake --preset ${{matrix.preset}}
+
+ - name: Build native library
+ run: cmake --build --preset ${{matrix.preset}} --target dusk
+
+ - name: Stage stripped JNI library
+ run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh
+
+ - name: Build APK
+ working-directory: platforms/android
+ run: ./gradlew :app:assembleRelease --rerun-tasks
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v7
+ with:
+ name: dusk-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}}
+ path: platforms/android/app/build/outputs/apk/release/app-${{matrix.abi}}-release-unsigned.apk
+
build-windows:
name: Build Windows (${{matrix.name}})
runs-on: ${{matrix.runner}}
@@ -154,7 +216,7 @@ jobs:
matrix:
include:
- name: MSVC x86_64
- runner: [self-hosted, Windows]
+ runner: windows-latest
preset: msvc
msvc_arch: amd64
vcpkg_arch: x64
@@ -191,7 +253,6 @@ jobs:
uses: mozilla-actions/sccache-action@v0.0.9
- name: Install dependencies
- if: 'false' # disabled for self-hosted
run: |
choco install ninja
vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static
@@ -203,7 +264,6 @@ jobs:
run: cmake --build --preset x-windows-ci-${{matrix.preset}}
- name: Upload artifacts
- if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v7
with:
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9004ab346d..f620de430b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,13 +48,15 @@ else ()
message(STATUS "Unable to find git, commit information will not be available")
endif ()
-if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9]+).*)?$")
+if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)([-+].*)?$")
set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
- if (CMAKE_MATCH_5)
- set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${CMAKE_MATCH_5}")
- else ()
- set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.0")
+ set(DUSK_VERSION_TWEAK "0")
+ if (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-([0-9]+)(-dirty)?$")
+ set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
+ elseif (DUSK_WC_DESCRIBE MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+-[0-9A-Za-z.-]+-([0-9]+)(-dirty)?$")
+ set(DUSK_VERSION_TWEAK "${CMAKE_MATCH_1}")
endif ()
+ set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${DUSK_VERSION_TWEAK}")
else ()
set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION")
set(DUSK_VERSION_STRING "0.0.0.0")
@@ -69,7 +71,7 @@ message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING})
if (APPLE)
- enable_language(OBJC)
+ enable_language(OBJC OBJCXX)
endif ()
if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS)
# ios.toolchain.cmake hack for SDL
@@ -100,26 +102,35 @@ if (CMAKE_SYSTEM_NAME STREQUAL Linux)
endif ()
set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE)
set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE)
+set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE)
add_subdirectory(extern/aurora EXCLUDE_FROM_ALL)
+target_compile_definitions(aurora_mtx PRIVATE MTX_USE_PS=1)
add_subdirectory(libs/freeverb)
option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)")
option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.")
option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON)
+option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
if(ANDROID)
set(DUSK_MOVIE_SUPPORT OFF)
- set(NOD_COMPRESS_BZIP2 OFF CACHE BOOL "" FORCE)
- set(NOD_COMPRESS_LZMA OFF CACHE BOOL "" FORCE)
- set(NOD_COMPRESS_ZLIB OFF CACHE BOOL "" FORCE)
- set(NOD_COMPRESS_ZSTD OFF CACHE BOOL "" FORCE)
endif ()
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
+# Edit & Continue
+if (MSVC)
+ if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
+ endif ()
+ if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
+ add_link_options("/INCREMENTAL")
+ endif ()
+endif ()
+
if (DUSK_MOVIE_SUPPORT)
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
if (libjpeg-turbo_FOUND)
@@ -149,6 +160,7 @@ if (DUSK_MOVIE_SUPPORT)
CMAKE_C_COMPILER_LAUNCHER
CMAKE_MAKE_PROGRAM
CMAKE_MSVC_RUNTIME_LIBRARY
+ CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
CMAKE_OSX_ARCHITECTURES
DEPLOYMENT_TARGET
ENABLE_ARC
@@ -309,22 +321,16 @@ set(DUSK_COMPANY_NAME "Twilit Realm")
set(DUSK_FILE_DESCRIPTION "Dusk")
set(DUSK_PRODUCT_NAME "Dusk")
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
-set(DUSK_GAME_NAME "GZ2E")
-set(DUSK_GAME_VERSION "01")
-set(DUSK_TP_VERSION ${DUSK_GAME_NAME}${DUSK_GAME_VERSION})
-
-message(STATUS "dusk: Game Version: ${DUSK_TP_VERSION}")
source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES})
-source_group("dusk" FILES ${DUSK_FILES})
+source_group("dusk" FILES ${DUSK_FILES} ${DUSK_HTTP_BACKEND_FILES})
-set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0
- DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}")
+set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1)
set(GAME_INCLUDE_DIRS
include
src
- assets/${DUSK_TP_VERSION}
+ assets/GZ2E01 # TODO: make this dynamic if needed?
libs/JSystem/include
libs
extern/aurora/include/dolphin
@@ -339,8 +345,10 @@ target_include_directories(dusk_game_headers INTERFACE ${GAME_INCLUDE_DIRS})
target_compile_definitions(dusk_game_headers INTERFACE TARGET_PC=1)
target_link_libraries(dusk_game_headers INTERFACE TracyClient)
+find_package(Threads REQUIRED)
set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd
- aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt funchook-static)
+ aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt funchook-static
+ Threads::Threads)
list(APPEND GAME_LIBS libzstd_static)
@@ -349,6 +357,45 @@ if (DUSK_ENABLE_SENTRY_NATIVE)
list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_SENTRY_NATIVE=1 SENTRY_BUILD_STATIC=1)
endif ()
+if (WIN32)
+ list(APPEND GAME_LIBS Ws2_32)
+endif ()
+
+set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/no_backend.cpp)
+if (DUSK_ENABLE_UPDATE_CHECKER)
+ list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_UPDATE_CHECKER=1)
+ if (WIN32)
+ set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/winhttp.cpp)
+ list(APPEND GAME_LIBS winhttp)
+ list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_WINHTTP=1)
+ message(STATUS "dusk: Enabled update checker (WinHTTP)")
+ elseif (ANDROID)
+ set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/android.cpp)
+ list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_ANDROID=1)
+ message(STATUS "dusk: Enabled update checker (Android)")
+ elseif (APPLE)
+ find_library(FOUNDATION_FRAMEWORK Foundation REQUIRED)
+ set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/url_session.mm)
+ set_source_files_properties(src/dusk/http/url_session.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
+ list(APPEND GAME_LIBS ${FOUNDATION_FRAMEWORK})
+ list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_URLSESSION=1)
+ message(STATUS "dusk: Enabled update checker (NSURLSession)")
+ elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
+ find_package(CURL QUIET OPTIONAL_COMPONENTS HTTPS SSL)
+ if (CURL_FOUND AND CURL_HTTPS_FOUND AND CURL_SSL_FOUND)
+ set(DUSK_HTTP_BACKEND_SOURCE src/dusk/http/curl.cpp)
+ list(APPEND GAME_LIBS CURL::libcurl)
+ list(APPEND GAME_COMPILE_DEFS DUSK_HTTP_BACKEND_LIBCURL=1)
+ message(STATUS "dusk: Enabled update checker (libcurl)")
+ else ()
+ message(STATUS "dusk: Disabled update checker (libcurl + HTTPS/SSL not found)")
+ endif ()
+ else ()
+ message(STATUS "dusk: Disabled update checker (unsupported platform)")
+ endif ()
+endif ()
+list(APPEND DUSK_FILES ${DUSK_HTTP_BACKEND_SOURCE})
+
if (DUSK_MOVIE_SUPPORT)
if (TARGET libjpeg-turbo::turbojpeg-static)
list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static)
@@ -358,56 +405,13 @@ if (DUSK_MOVIE_SUPPORT)
list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1)
endif ()
-option(DUSK_ENABLE_DISCORD_RPC "Enable Discord Rich Presence support" ON)
-if (DUSK_ENABLE_DISCORD_RPC AND NOT ANDROID AND NOT IOS AND NOT TVOS)
-
- FetchContent_Populate(discord_rpc
- URL https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.tar.gz
- URL_HASH SHA256=e13427019027acd187352dacba6c65953af66fdf3c35fcf38fc40b454a9d7855
- DOWNLOAD_EXTRACT_TIMESTAMP TRUE
- )
- # RapidJSON is a git submodule absent from the discord-rpc tarball; fetch separately.
- FetchContent_Populate(rapidjson
- URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz
- URL_HASH SHA256=bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e
- DOWNLOAD_EXTRACT_TIMESTAMP TRUE
- )
-
- if (NOT TARGET discord-rpc)
- set(_drpc ${discord_rpc_SOURCE_DIR}/src)
- set(_drpc_src
- ${_drpc}/discord_rpc.cpp
- ${_drpc}/rpc_connection.cpp
- ${_drpc}/serialization.cpp
- )
- if (WIN32)
- list(APPEND _drpc_src ${_drpc}/connection_win.cpp ${_drpc}/discord_register_win.cpp)
- elseif (APPLE)
- list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_osx.m)
- else ()
- list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_linux.cpp)
- endif ()
- add_library(discord-rpc STATIC ${_drpc_src})
- target_include_directories(discord-rpc PUBLIC
- ${discord_rpc_SOURCE_DIR}/include
- ${rapidjson_SOURCE_DIR}/include
- )
- if (UNIX)
- target_link_libraries(discord-rpc PUBLIC pthread)
- endif ()
- endif ()
- list(APPEND GAME_LIBS discord-rpc)
- list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD_RPC=1)
+set(DUSK_ENABLE_DISCORD_DEFAULT ON)
+if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD)
+ set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC})
endif ()
-
-# Edit & Continue
-if (MSVC)
- if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
- set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
- endif ()
- if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
- add_link_options("/INCREMENTAL")
- endif ()
+option(DUSK_ENABLE_DISCORD "Enable Discord Rich Presence support" ${DUSK_ENABLE_DISCORD_DEFAULT})
+if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
+ list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
endif ()
if(ANDROID)
diff --git a/CMakePresets.json b/CMakePresets.json
index 92799249a9..629bbe9c18 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -352,6 +352,31 @@
"ANDROID_ABI": "x86_64"
}
},
+ {
+ "name": "x-android-ci",
+ "hidden": true,
+ "inherits": [
+ "android-base",
+ "ci"
+ ],
+ "cacheVariables": {
+ "DUSK_ENABLE_SENTRY_NATIVE": {
+ "type": "BOOL",
+ "value": false
+ }
+ }
+ },
+ {
+ "name": "x-android-ci-arm64",
+ "binaryDir": "${sourceDir}/build/android-arm64",
+ "inherits": [
+ "x-android-ci"
+ ],
+ "cacheVariables": {
+ "ANDROID_ABI": "arm64-v8a",
+ "Rust_CARGO_TARGET": "aarch64-linux-android"
+ }
+ },
{
"name": "x-linux-ci",
"hidden": true,
@@ -412,6 +437,7 @@
"x-macos-ci"
],
"cacheVariables": {
+ "AURORA_DAWN_PROVIDER": "vendor",
"CMAKE_OSX_ARCHITECTURES": "x86_64",
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
}
@@ -555,6 +581,15 @@
"dusk"
]
},
+ {
+ "name": "x-android-ci-arm64",
+ "configurePreset": "x-android-ci-arm64",
+ "description": "(Internal) Android CI arm64-v8a",
+ "displayName": "(Internal) Android CI arm64-v8a",
+ "targets": [
+ "dusk"
+ ]
+ },
{
"name": "windows-msvc-debug",
"configurePreset": "windows-msvc-debug",
diff --git a/README.md b/README.md
index c2fe53576d..652eae2538 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,69 @@
-
+
+
-- ### **[Official Website](https://twilitrealm.dev)**
-- ### **[Discord](https://discord.gg/QACynxeyna)**
+
+ Official Website
+ •
+ Discord
+
+
+
+# Overview
+
+Dusk is a reverse-engineered reimplementation of Twilight Princess.
+
+It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience.
# Setup
-**⚠️Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.**
-### 1. Verify your ROM dump
-First make sure your dump of the game is clean and supported by Dusk. You can do this by checking the sha1 hash of your dump against this list of supported versions.
+> [!IMPORTANT]
+> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game.
-| Version | sha1 hash |
-|--------------| ---------------------------------------- |
-| GameCube USA | 75edd3ddff41f125d1b4ce1a40378f1b565519e7 |
-| GameCube PAL | 2601822a488eeb86fb89db16ca8f29c2c953e1ca |
+> [!IMPORTANT]
+> At a minimum, Dusk requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelyhood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them.
+
+### 1. Verify your dump
+
+First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
+
+| Version | SHA-1 hash |
+|--------------| ------------------------------------------ |
+| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
+| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` |
+
+*Support for other versions of the game is planned in the future.
### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases)
### 3. Setup the game
-- Extract the zip folder
+**Windows / macOS / Linux**
+- Extract the .zip file
- Launch Dusk
-- Select Options, then set the ISO Path to your supported game dump
-- Press Start Game to play!
+- Press **Select Disc Image** and provide the path to your supported game dump
+- Press **Play**!
-
+**iOS**
+- Follow the [iOS setup guide](docs/ios-install-altstore.md)
+
+**Android**
+- Install the Dusk apk
+- Launch Dusk
+- Press **Select Disc Image** and provide the path to your supported game dump
+- Press **Play**!
# Building
+
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md).
-# Credits
-- Taka
-- encounter
-- Antidote
-- caseif
-- CraftyBoss
-- crowell
-- dooplecks
-- gymnast86
-- Irastris
-- kipcode66
-- Lars
-- LunarSoap
-- Maddie
-- MelonSpeedruns
-- Pheenoh
-- PJB
-- Roeming
-- YunataSavior
+Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. Please also see the [code conventions](docs/code-conventions.md).
-Special thanks to the TP Decomp team, the GC/Wii Decomp community, the Aurora developers, and the TP speedrunning community.
+# Credits
+
+Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors).
+
+
+
diff --git a/assets/aurora-powered.png b/assets/aurora-powered.png
new file mode 100644
index 0000000000..35ff017776
Binary files /dev/null and b/assets/aurora-powered.png differ
diff --git a/assets/dusk_options.png b/assets/dusk_options.png
deleted file mode 100644
index 4ed4ad7563..0000000000
Binary files a/assets/dusk_options.png and /dev/null differ
diff --git a/assets/objdiff.png b/assets/objdiff.png
deleted file mode 100644
index 94835fa1b3..0000000000
Binary files a/assets/objdiff.png and /dev/null differ
diff --git a/assets/org-icon.svg b/assets/org-icon.svg
new file mode 100644
index 0000000000..b1e82ead45
--- /dev/null
+++ b/assets/org-icon.svg
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ci/build-appimage.sh b/ci/build-appimage.sh
index 078d6cabf0..cef4ddfaf0 100755
--- a/ci/build-appimage.sh
+++ b/ci/build-appimage.sh
@@ -1,18 +1,26 @@
#!/bin/bash -ex
-shopt -s extglob
+
+if [[ -n "${GITHUB_WORKSPACE:-}" ]]; then
+ cd "$GITHUB_WORKSPACE"
+fi
+
+build_dir="$PWD/build"
+linuxdeploy="$build_dir/linuxdeploy-$(uname -m).AppImage"
# Get linuxdeploy
-cd "$RUNNER_WORKSPACE"
-curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage
-chmod +x linuxdeploy-$(uname -m).AppImage
+mkdir -p "$build_dir"
+curl -fL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" -o "$linuxdeploy"
+chmod +x "$linuxdeploy"
# Build AppImage
-cd "$GITHUB_WORKSPACE"
mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}
-cp -r build/install/!(*.*) build/appdir/usr/bin
+for install_path in build/install/*; do
+ [[ "$(basename "$install_path")" == *.* ]] && continue
+ cp -r "$install_path" build/appdir/usr/bin
+done
cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor
cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications
cd build/install
-VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \
- -l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage
+VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
+ -l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$build_dir/appdir" --output appimage
diff --git a/cmake-variants.yaml b/cmake-variants.yaml
index 62d2642311..37edcede95 100644
--- a/cmake-variants.yaml
+++ b/cmake-variants.yaml
@@ -13,13 +13,3 @@ buildType:
short: RelWithDebInfo
long: Optimized, with debug symbols
buildType: RelWithDebInfo
-
-tp_version:
- default: GZ2E01
- description: TP Version
- choices:
- GZ2E01:
- short: GZ2E01
- long: GZ2E01
- settings:
- DUSK_TP_VERSION: GZ2E01
diff --git a/docs/code-conventions.md b/docs/code-conventions.md
new file mode 100644
index 0000000000..9c2659ac02
--- /dev/null
+++ b/docs/code-conventions.md
@@ -0,0 +1,13 @@
+# Code conventions for Dusk
+
+## Upstream when appropriate
+
+Bug fixes, documentation improvements, code cleanup, etc that also apply to the [original decompilation project](https://github.com/zeldaret/tp) should preferably be PR'd there.
+
+## Properly indicate Dusk-modified code
+
+When modifying original game code (i.e. in decomp) for Dusk's purposes, please clearly delineate such code as being Dusk-specific. Generally, this can be done by using `#if TARGET_PC` and keeping the original code in place. Use `#if AVOID_UB` for Undefined Behavior fixes to the original codebase.
+
+## Miscellaneous things
+
+* The original codebase makes heavy use of global `operator new` and similar overloads to allocate into a strict tree of heaps. This would cause many linkage headaches for us, so effectively all uses of `new` or `delete` in the original game code have been replaced with `JKR_NEW`, `JKR_DELETE`, or similar macros. See `JKRHeap.h` for the full list.
diff --git a/docs/ios-install-altstore.md b/docs/ios-install-altstore.md
new file mode 100644
index 0000000000..fdfc7689d6
--- /dev/null
+++ b/docs/ios-install-altstore.md
@@ -0,0 +1,46 @@
+# Installing Dusk on iOS via AltStore
+
+## Prerequisites
+
+- Mac with Homebrew installed
+- iPhone connected via USB
+- Dusk IPA file (download the latest `Dusk-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusk/releases))
+- Game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
+
+## 1. Install AltServer
+
+```sh
+brew install altserver
+open -a AltServer
+```
+
+AltServer will appear in your menu bar.
+
+## 2. Enable Developer Mode (iOS 16+)
+
+- On your iPhone, go to **Settings > Privacy & Security > Developer Mode**
+- Toggle it on and restart when prompted
+
+## 3. Install AltStore on Your iPhone
+
+- Click AltServer in the menu bar
+- Click **Install AltStore > [Your iPhone]**
+- Enter your Apple ID credentials when prompted
+- On your iPhone, go to **Settings > General > VPN & Device Management**
+- Tap your Apple ID under "Developer App" and tap **Trust**
+
+## 4. Copy Files to Your iPhone
+
+Transfer the IPA and game disc to your iPhone so they're accessible in the Files app. A few ways to do this:
+
+- **AirDrop** - Right-click the files on your Mac and choose Share > AirDrop
+- **iCloud Drive** - Place files in iCloud Drive on your Mac and they'll sync to Files on your iPhone
+- **USB transfer** - Connect your iPhone and drag files via Finder's sidebar
+- **Cloud storage** - Upload to Google Drive, Dropbox, etc. and download on your iPhone
+
+## 5. Install via AltStore
+
+- Open **AltStore** on your iPhone
+- Go to the **My Apps** tab
+- Tap the **+** button (top left)
+- Open the **Files** app and select the `.ipa` file
\ No newline at end of file
diff --git a/extern/aurora b/extern/aurora
index be1395c134..aff6edf9de 160000
--- a/extern/aurora
+++ b/extern/aurora
@@ -1 +1 @@
-Subproject commit be1395c1343a5587cceea25754daa5cacce34f76
+Subproject commit aff6edf9de39b3c211ccb252e76331abaa9e5e01
diff --git a/files.cmake b/files.cmake
index 5d6850cfca..b141291e7b 100644
--- a/files.cmake
+++ b/files.cmake
@@ -1,7 +1,7 @@
set(DOLZEL_FILES
src/m_Do/m_Do_main.cpp
- src/m_Do/m_Do_printf.cpp
+ #src/m_Do/m_Do_printf.cpp
src/m_Do/m_Do_audio.cpp
src/m_Do/m_Do_controller_pad.cpp
#src/m_Do/m_Re_controller_pad.cpp
@@ -1429,11 +1429,15 @@ set(DUSK_FILES
src/dusk/globals.cpp
src/dusk/gyro.cpp
src/dusk/gamepad_color.cpp
+ src/dusk/autosave.cpp
+ src/dusk/http/http.hpp
src/dusk/io.cpp
src/dusk/layout.cpp
src/dusk/logging.cpp
src/dusk/settings.cpp
src/dusk/stubs.cpp
+ src/dusk/update_check.cpp
+ src/dusk/update_check.hpp
#src/dusk/m_Do_ext_dusk.cpp
src/dusk/imgui/ImGuiConfig.hpp
src/dusk/imgui/ImGuiConsole.hpp
@@ -1446,28 +1450,84 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiBloomWindow.hpp
src/dusk/imgui/ImGuiMenuTools.cpp
src/dusk/imgui/ImGuiMenuTools.hpp
- src/dusk/imgui/ImGuiPreLaunchWindow.cpp
- src/dusk/imgui/ImGuiPreLaunchWindow.hpp
- src/dusk/imgui/ImGuiFirstRunPreset.hpp
- src/dusk/imgui/ImGuiFirstRunPreset.cpp
+ src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp
src/dusk/imgui/ImGuiHeapOverlay.cpp
- src/dusk/imgui/ImGuiDebugPad.cpp
src/dusk/imgui/ImGuiControllerOverlay.cpp
src/dusk/imgui/ImGuiStubLog.cpp
src/dusk/imgui/ImGuiMapLoader.cpp
src/dusk/imgui/ImGuiSaveEditor.cpp
src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp
+ src/dusk/ui/achievements.cpp
+ src/dusk/ui/achievements.hpp
+ src/dusk/ui/bool_button.cpp
+ src/dusk/ui/bool_button.hpp
+ src/dusk/ui/button.cpp
+ src/dusk/ui/button.hpp
+ src/dusk/ui/component.cpp
+ src/dusk/ui/component.hpp
+ src/dusk/ui/controller_config.cpp
+ src/dusk/ui/controller_config.hpp
+ src/dusk/ui/document.cpp
+ src/dusk/ui/document.hpp
+ src/dusk/ui/editor.cpp
+ src/dusk/ui/editor.hpp
+ src/dusk/ui/event.cpp
+ src/dusk/ui/event.hpp
+ src/dusk/ui/graphics_tuner.cpp
+ src/dusk/ui/graphics_tuner.hpp
+ src/dusk/ui/input.cpp
+ src/dusk/ui/input.hpp
+ src/dusk/ui/modal.cpp
+ src/dusk/ui/modal.hpp
+ src/dusk/ui/nav_types.hpp
+ src/dusk/ui/number_button.cpp
+ src/dusk/ui/number_button.hpp
+ src/dusk/ui/overlay.cpp
+ src/dusk/ui/overlay.hpp
+ src/dusk/ui/pane.cpp
+ src/dusk/ui/pane.hpp
+ src/dusk/ui/menu_bar.cpp
+ src/dusk/ui/menu_bar.hpp
+ src/dusk/ui/prelaunch.cpp
+ src/dusk/ui/prelaunch.hpp
+ src/dusk/ui/preset.cpp
+ src/dusk/ui/preset.hpp
+ src/dusk/ui/select_button.cpp
+ src/dusk/ui/select_button.hpp
+ src/dusk/ui/settings.cpp
+ src/dusk/ui/settings.hpp
+ src/dusk/ui/string_button.cpp
+ src/dusk/ui/string_button.hpp
+ src/dusk/ui/tab_bar.cpp
+ src/dusk/ui/tab_bar.hpp
+ src/dusk/ui/ui.cpp
+ src/dusk/ui/ui.hpp
+ src/dusk/ui/window.cpp
+ src/dusk/ui/window.hpp
+ src/dusk/achievements.cpp
src/dusk/iso_validate.cpp
+ src/dusk/livesplit.cpp
src/dusk/offset_ptr.cpp
src/dusk/OSContext.cpp
+ src/dusk/OSReport.cpp
src/dusk/OSThread.cpp
src/dusk/OSMutex.cpp
src/dusk/hook_system.cpp
src/dusk/mod_loader.cpp
src/dusk/imgui/ImGuiMenuMods.cpp
src/dusk/gx_helper.cpp
+ src/dusk/discord.cpp
+ src/dusk/discord.hpp
src/dusk/discord_presence.cpp
+ src/dusk/version.cpp
+)
+
+set(DUSK_HTTP_BACKEND_FILES
+ src/dusk/http/no_backend.cpp
+ src/dusk/http/curl.cpp
+ src/dusk/http/winhttp.cpp
+ src/dusk/http/url_session.mm
)
diff --git a/flake.nix b/flake.nix
index cd703bda64..c8e38a849b 100644
--- a/flake.nix
+++ b/flake.nix
@@ -5,9 +5,73 @@
outputs = { self, nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
+
+ # Dependencies that are not packaged in nixpkgs:
+ aurora-src = pkgs.fetchFromGitHub {
+ owner = "encounter";
+ repo = "aurora";
+ rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
+ hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
+ };
+ dawn-src = pkgs.fetchzip {
+ url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
+ hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
+ stripRoot = false;
+ };
+ nod-src = pkgs.fetchzip {
+ url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
+ hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
+ stripRoot = false;
+ };
+ # The version of imgui on nixpkgs does not map cleanly.
+ imgui-src = pkgs.fetchFromGitHub {
+ owner = "ocornut";
+ repo = "imgui";
+ rev = "v1.91.9b-docking";
+ hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
+ };
+ sqlite-src = pkgs.fetchzip {
+ url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
+ hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
+ };
+ rmlui-src = pkgs.fetchzip {
+ url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
+ hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
+ };
+ # Dusk Actual
dusk = pkgs.stdenv.mkDerivation {
name = "dusk";
src = ./.;
+ postUnpack = ''
+ mkdir -p $sourceRoot/extern/aurora
+ cp -r ${aurora-src}/. $sourceRoot/extern/aurora/
+ chmod -R u+w $sourceRoot/extern/aurora
+ sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
+ '';
+ # Remove last line to re-enable tests
+ cmakeFlags = [
+ "-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
+ "-DFETCHCONTENT_SOURCE_DIR_CXXOPTS=${pkgs.cxxopts.src}"
+ "-DFETCHCONTENT_SOURCE_DIR_JSON=${pkgs.nlohmann_json.src}"
+ "-DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${dawn-src}"
+ "-DFETCHCONTENT_SOURCE_DIR_XXHASH=${pkgs.xxHash.src}"
+ "-DFETCHCONTENT_SOURCE_DIR_FMT=${pkgs.fmt.src}"
+ "-DFETCHCONTENT_SOURCE_DIR_TRACY=${pkgs.tracy.src}"
+ "-DAURORA_SDL3_PROVIDER=system"
+ "-DFETCHCONTENT_SOURCE_DIR_NOD_PREBUILT=${nod-src}"
+ "-DAURORA_NOD_PROVIDER=package"
+ "-DFETCHCONTENT_SOURCE_DIR_FREETYPE=${pkgs.freetype.src}"
+ "-DFETCHCONTENT_SOURCE_DIR_ZSTD=${pkgs.zstd.src}"
+ "-DFETCHCONTENT_SOURCE_DIR_SQLITE3=${sqlite-src}"
+ "-DFETCHCONTENT_SOURCE_DIR_IMGUI=${imgui-src}"
+ "-DFETCHCONTENT_SOURCE_DIR_RMLUI=${rmlui-src}"
+ "-DCMAKE_CROSSCOMPILING=ON" # Tests are not working as I didn't want to work through getting google's test suite working as well. This is the only guard I could find to disable it.
+ ];
+ installPhase = ''
+ mkdir -p $out/bin
+ cp dusk $out/bin/dusk
+ cp -r ./res $out/bin/res
+ '';
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
@@ -25,6 +89,13 @@
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
+ pkgs.cxxopts
+ pkgs.abseil-cpp
+ pkgs.sdl3
+ pkgs.fmt
+ pkgs.tracy
+ pkgs.freetype
+ pkgs.zstd
];
};
in {
diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h
index 9980ab776f..107afc1ed3 100644
--- a/include/d/actor/d_a_alink.h
+++ b/include/d/actor/d_a_alink.h
@@ -4552,6 +4552,18 @@ public:
void handleWolfHowl();
void handleQuickTransform();
bool checkGyroAimContext();
+
+ void onIronBallChainInterpCallback();
+
+ static const int IRON_BALL_CHAIN_COUNT = 102;
+ cXyz mIBChainInterpPrevPos[IRON_BALL_CHAIN_COUNT];
+ cXyz mIBChainInterpCurrPos[IRON_BALL_CHAIN_COUNT];
+ csXyz mIBChainInterpPrevAngle[IRON_BALL_CHAIN_COUNT];
+ csXyz mIBChainInterpCurrAngle[IRON_BALL_CHAIN_COUNT];
+ cXyz mIBChainInterpPrevHandRoot;
+ cXyz mIBChainInterpCurrHandRoot;
+ bool mIBChainInterpPrevValid;
+ bool mIBChainInterpCurrValid;
#endif
}; // Size: 0x385C
diff --git a/include/d/actor/d_a_b_gnd.h b/include/d/actor/d_a_b_gnd.h
index b9c8781ec6..0827102eec 100644
--- a/include/d/actor/d_a_b_gnd.h
+++ b/include/d/actor/d_a_b_gnd.h
@@ -188,6 +188,15 @@ public:
/* 0x273C */ f32 mKankyoBlend;
/* 0x2740 */ u8 field_0x2740;
/* 0x2744 */ dMsgFlow_c mMsgFlow;
+#if TARGET_PC
+ cXyz mReinsInterpPrev[2][16];
+ cXyz mReinsInterpCurr[2][16];
+ cXyz mReinsTexInterpPrev[2];
+ cXyz mReinsTexInterpCurr[2];
+ bool mReinsInterpPrevValid;
+ bool mReinsInterpCurrValid;
+ s8 mDemoCamSyncTicks;
+#endif
};
STATIC_ASSERT(sizeof(b_gnd_class) == 0x2790);
diff --git a/include/d/actor/d_a_e_db.h b/include/d/actor/d_a_e_db.h
index a95722a6d1..4f10715443 100644
--- a/include/d/actor/d_a_e_db.h
+++ b/include/d/actor/d_a_e_db.h
@@ -80,6 +80,12 @@ public:
/* 0x125C */ u32 field_0x125c;
/* 0x1260 */ u8 field_0x1260[0x126C - 0x1260];
/* 0x126C */ u8 HIOInit;
+#if TARGET_PC
+ cXyz mStalkLineInterpPrev[12];
+ cXyz mStalkLineInterpCurr[12];
+ bool mStalkLineInterpPrevValid;
+ bool mStalkLineInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(e_db_class) == 0x1270);
diff --git a/include/d/actor/d_a_e_hb.h b/include/d/actor/d_a_e_hb.h
index a7e0241007..3069bcd325 100644
--- a/include/d/actor/d_a_e_hb.h
+++ b/include/d/actor/d_a_e_hb.h
@@ -73,6 +73,12 @@ public:
/* 0x124C */ f32 field_0x124c;
/* 0x1250 */ u8 field_0x1250[0x1264 - 0x1250];
/* 0x1264 */ u8 HIOInit;
+#if TARGET_PC
+ cXyz mStalkLineInterpPrev[12];
+ cXyz mStalkLineInterpCurr[12];
+ bool mStalkLineInterpPrevValid;
+ bool mStalkLineInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(e_hb_class) == 0x1268);
diff --git a/include/d/actor/d_a_e_mb.h b/include/d/actor/d_a_e_mb.h
index f36c744879..527cf30eac 100644
--- a/include/d/actor/d_a_e_mb.h
+++ b/include/d/actor/d_a_e_mb.h
@@ -44,6 +44,12 @@ public:
/* 0x88C */ u8 field_0x88C[0x8C8 - 0x88C];
/* 0x8C8 */ s8 field_0x8c8;
/* 0x8C9 */ u8 mInitHIO;
+#if TARGET_PC
+ cXyz mRopeInterpPrev[16];
+ cXyz mRopeInterpCurr[16];
+ bool mRopeInterpPrevValid;
+ bool mRopeInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(e_mb_class) == 0x8cc);
diff --git a/include/d/actor/d_a_e_s1.h b/include/d/actor/d_a_e_s1.h
index a631ba47ac..5cbae84696 100644
--- a/include/d/actor/d_a_e_s1.h
+++ b/include/d/actor/d_a_e_s1.h
@@ -81,6 +81,15 @@ public:
/* 0x306D */ u8 field_0x306D[0x307C - 0x306D];
/* 0x307C */ u32 mBodyEffEmtrID;
/* 0x3080 */ u8 mInitHIO;
+
+#if TARGET_PC
+ static const int HAIR_STRAND_COUNT = 22;
+ static const int HAIR_SEGMENT_COUNT = 16;
+ cXyz mHairInterpPrev[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
+ cXyz mHairInterpCurr[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
+ bool mHairInterpPrevValid;
+ bool mHairInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(e_s1_class) == 0x3084);
diff --git a/include/d/actor/d_a_e_yd.h b/include/d/actor/d_a_e_yd.h
index 188e435ba5..44d6036600 100644
--- a/include/d/actor/d_a_e_yd.h
+++ b/include/d/actor/d_a_e_yd.h
@@ -74,6 +74,12 @@ public:
/* 0x1250 */ f32 field_0x1250;
/* 0x1254 */ u8 field_0x1254[0x1268 - 0x1254];
/* 0x1268 */ u8 field_0x1268;
+#if TARGET_PC
+ cXyz mLineMatInterpPrev[12];
+ cXyz mLineMatInterpCurr[12];
+ bool mLineMatInterpPrevValid;
+ bool mLineMatInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(e_yd_class) == 0x126c);
diff --git a/include/d/actor/d_a_e_yg.h b/include/d/actor/d_a_e_yg.h
index 69a85430a8..6be19933cb 100644
--- a/include/d/actor/d_a_e_yg.h
+++ b/include/d/actor/d_a_e_yg.h
@@ -63,6 +63,15 @@ public:
/* 0x0BB4 */ yg_ke_s mYgKes[13];
/* 0x1880 */ mDoExt_3DlineMat0_c mLineMat;
/* 0x189C */ u8 mIsFirstSpawn;
+
+#if TARGET_PC
+ static const int TENTACLE_STRAND_COUNT = 13;
+ static const int TENTACLE_SEGMENT_COUNT = 10;
+ cXyz mTentacleInterpPrev[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
+ cXyz mTentacleInterpCurr[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
+ bool mTentacleInterpPrevValid;
+ bool mTentacleInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(e_yg_class) == 0x18a0);
diff --git a/include/d/actor/d_a_e_yh.h b/include/d/actor/d_a_e_yh.h
index 519e5e2779..63e8ac1882 100644
--- a/include/d/actor/d_a_e_yh.h
+++ b/include/d/actor/d_a_e_yh.h
@@ -77,6 +77,12 @@ public:
/* 0x1260 */ u32 field_0x1260;
/* 0x1260 */ u8 field_0x1264[0x1270 - 0x1264];
/* 0x1270 */ bool mIsHIOOwner;
+#if TARGET_PC
+ cXyz mLineInterpPrev[12];
+ cXyz mLineInterpCurr[12];
+ bool mLineInterpPrevValid;
+ bool mLineInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(e_yh_class) == 0x1274);
diff --git a/include/d/actor/d_a_mirror.h b/include/d/actor/d_a_mirror.h
index 06c5603899..e2f9a51e30 100644
--- a/include/d/actor/d_a_mirror.h
+++ b/include/d/actor/d_a_mirror.h
@@ -27,6 +27,7 @@ public:
/* 0x17C */ cXyz mViewScale;
#if TARGET_PC
bool mbReset = false;
+ bool mbHadEntry = false;
#endif
};
diff --git a/include/d/actor/d_a_obj_fchain.h b/include/d/actor/d_a_obj_fchain.h
index 1bd7b9a810..7c4ae74faf 100644
--- a/include/d/actor/d_a_obj_fchain.h
+++ b/include/d/actor/d_a_obj_fchain.h
@@ -31,6 +31,10 @@ public:
csXyz* getAngle() { return field_0x8a4; }
J3DModelData* getModelData() { return mModelData; }
+#if TARGET_PC
+ void onInterpCallback();
+#endif
+
private:
/* 0x568 */ request_of_phase_process_class mPhase;
/* 0x570 */ J3DModelData* mModelData;
@@ -42,6 +46,14 @@ private:
/* 0x694 */ cXyz field_0x694[22];
/* 0x79C */ cXyz field_0x79c[22];
/* 0x8A4 */ csXyz field_0x8a4[22];
+
+#if TARGET_PC
+ static const int CHAIN_COUNT = 22;
+ cXyz mChainInterpPrev[CHAIN_COUNT];
+ cXyz mChainInterpCurr[CHAIN_COUNT];
+ bool mChainInterpPrevValid;
+ bool mChainInterpCurrValid;
+#endif
};
STATIC_ASSERT(sizeof(daObjFchain_c) == 0x928);
diff --git a/include/d/actor/d_a_obj_klift00.h b/include/d/actor/d_a_obj_klift00.h
index 865fab8c47..ab5403d51b 100644
--- a/include/d/actor/d_a_obj_klift00.h
+++ b/include/d/actor/d_a_obj_klift00.h
@@ -25,6 +25,10 @@ public:
int Draw();
int Delete();
+#if TARGET_PC
+ void onInterpCallback();
+#endif
+
enum Param_e {
LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7)
};
@@ -50,6 +54,13 @@ private:
/* 0x1020 */ dCcD_Cyl mCylinderCollider;
/* 0x115C */ s32 mStopSwingingFrames;
+#if TARGET_PC
+ cXyz mChainInterpPrev[64];
+ cXyz mChainInterpCurr[64];
+ bool mChainInterpPrevValid;
+ bool mChainInterpCurrValid;
+#endif
+
// Number of chain models
u32 getArg0() {
return fopAcM_GetParamBit(this, 0, 6);
diff --git a/include/d/actor/d_a_obj_lv8Lift.h b/include/d/actor/d_a_obj_lv8Lift.h
index c5a8ae9f27..c327f4cf3c 100644
--- a/include/d/actor/d_a_obj_lv8Lift.h
+++ b/include/d/actor/d_a_obj_lv8Lift.h
@@ -58,6 +58,9 @@ public:
void setNextPoint();
int Draw();
int Delete();
+#if TARGET_PC
+ friend void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork);
+#endif
u8 getPthID() { return fopAcM_GetParamBit(this, 0, 8); }
u8 getMoveSpeed() { return fopAcM_GetParamBit(this, 8, 4); }
diff --git a/include/d/d_cam_param.h b/include/d/d_cam_param.h
index d6ff7b46d7..867c66e38d 100644
--- a/include/d/d_cam_param.h
+++ b/include/d/d_cam_param.h
@@ -143,6 +143,12 @@ public:
/* 0x20 */ JORFile mFile;
#endif
+#if TARGET_PC
+ /* 0x24 */ u8 mManualMode;
+ /* 0x25 */ f32 freeXAngle;
+ /* 0x29 */ f32 freeYAngle;
+#endif
+
u32 Id(s32 i_style) { return mCamStyleData[i_style].field_0x0; }
int Algorythmn(s32 i_style) { return mCamStyleData[i_style].field_0x4; }
int Algorythmn() { return mCurrentStyle->field_0x4; }
diff --git a/include/d/d_camera.h b/include/d/d_camera.h
index 85e930c395..c481262a46 100644
--- a/include/d/d_camera.h
+++ b/include/d/d_camera.h
@@ -118,6 +118,18 @@ class camera_class;
class dCamera_c;
typedef bool (dCamera_c::*engine_fn)(s32);
+#if TARGET_PC
+struct DebugFlyCam {
+ bool initialized;
+ f32 pitch;
+ f32 yaw;
+ cXyz savedCenter;
+ cXyz savedEye;
+ f32 savedFovy;
+ cSAngle savedBank;
+};
+#endif
+
class dCamera_c {
public:
class dCamInfo_c {
@@ -273,6 +285,8 @@ public:
/* 0xA4 */ f32 field_0xa4;
/* 0xA8 */ int field_0xa8;
/* 0xAC */ f32 field_0xac;
+ f32 xAngle;
+ f32 yAngle;
};
struct LockOnData {
@@ -1024,6 +1038,11 @@ public:
bool colosseumCamera(s32);
bool test1Camera(s32);
bool test2Camera(s32);
+ #if TARGET_PC
+ bool freeCamera();
+ bool executeDebugFlyCam();
+ void deactivateDebugFlyCam();
+ #endif
bool towerCamera(s32);
bool hookshotCamera(s32);
bool railCamera(s32);
@@ -1371,6 +1390,10 @@ public:
/* 0x970 */ dCamSetup_c mCamSetup;
/* 0xAEC */ dCamParam_c mCamParam;
/* 0xB0C */ u8 field_0xb0c;
+
+#if TARGET_PC
+ DebugFlyCam mDebugFlyCam;
+#endif
}; // Size: 0xB10
dCamera_c* dCam_getBody();
diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h
index fcd4e77ba2..e67406f3a3 100644
--- a/include/d/d_com_inf_game.h
+++ b/include/d/d_com_inf_game.h
@@ -1845,6 +1845,12 @@ inline void dComIfGs_addDeathCount() {
g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().addDeathCount();
}
+#if TARGET_PC
+inline u16 dComIfGs_getDeathCount() {
+ return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getDeathCount();
+}
+#endif
+
inline char* dComIfGs_getPlayerName() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getPlayerName();
}
diff --git a/include/d/d_menu_dmap.h b/include/d/d_menu_dmap.h
index e50c533654..81baea1446 100644
--- a/include/d/d_menu_dmap.h
+++ b/include/d/d_menu_dmap.h
@@ -91,6 +91,10 @@ public:
void calcCursor();
void drawCursor();
+ #if TARGET_PC
+ void dMapBgWide();
+ #endif
+
void setDPDFloorSelCurPos(s8 i_pos) { field_0xdd6 = i_pos; }
f32 getMapWidth() { return mMapWidth; }
diff --git a/include/d/d_menu_fmap2D.h b/include/d/d_menu_fmap2D.h
index 9b237f2771..7df78bb6d0 100644
--- a/include/d/d_menu_fmap2D.h
+++ b/include/d/d_menu_fmap2D.h
@@ -81,6 +81,10 @@ public:
void calcDrawPriority();
void setArrowPosAxis(f32, f32);
+ #if TARGET_PC
+ void fMapBackWide();
+ #endif
+
virtual void draw();
virtual ~dMenu_Fmap2DBack_c();
@@ -165,6 +169,12 @@ public:
void mapBlink() {}
+ #if PLATFORM_WII || TARGET_PC
+ f32 getMirrorPosX(f32 param_0, f32 param_1) {
+ return (field_0x11dc * 2.0f - (param_0 + param_1)) - param_1;
+ }
+ #endif
+
// Unknown name
struct RegionTexData {
/* 0x00 */ float mMinX;
@@ -330,6 +340,10 @@ public:
void setHIO(bool);
bool isWarpAccept();
+ #if TARGET_PC
+ void fMapTopWide();
+ #endif
+
virtual void draw();
virtual ~dMenu_Fmap2DTop_c();
diff --git a/include/d/d_menu_map_common.h b/include/d/d_menu_map_common.h
index 989aee7d8b..de50d775ca 100644
--- a/include/d/d_menu_map_common.h
+++ b/include/d/d_menu_map_common.h
@@ -66,6 +66,16 @@ public:
_c90 = param_2;
}
+#if PLATFORM_WII || TARGET_PC
+ f32 getMirrorCenterPosX(f32 param_0, f32 param_1) {
+ if (_c90) {
+ return (mCenterPosX * 2.0f - (param_0 + param_1)) - param_1;
+ }
+
+ return param_0;
+ }
+#endif
+
struct Stage_c {
// Incomplete class
diff --git a/include/d/d_meter_button.h b/include/d/d_meter_button.h
index 3028abeab5..0f05d32440 100644
--- a/include/d/d_meter_button.h
+++ b/include/d/d_meter_button.h
@@ -343,6 +343,11 @@ public:
/* 0x624 */ f32 mMidonaPosX;
/* 0x628 */ f32 mMidonaPosY;
/* 0x62C */ f32 mMidonaScale;
+
+#ifdef TARGET_PC
+ bool mWasListen[2];
+ bool mWasRepeat[2];
+#endif
};
#endif /* D_METER_D_METER_BUTTON_H */
diff --git a/include/d/d_msg_object.h b/include/d/d_msg_object.h
index b55ea73904..d2ac4ecb2a 100644
--- a/include/d/d_msg_object.h
+++ b/include/d/d_msg_object.h
@@ -67,6 +67,9 @@ public:
bool isStaffMessage();
bool isSaveMessage();
bool isTalkMessage();
+#if TARGET_PC
+ bool isShopItemMessage();
+#endif
const char* getSmellName();
const char* getPortalName();
const char* getBombName();
diff --git a/include/d/d_name.h b/include/d/d_name.h
index e685871738..3ac6592457 100644
--- a/include/d/d_name.h
+++ b/include/d/d_name.h
@@ -89,7 +89,7 @@ public:
void MojiSelectAnm3();
int mojiChange(u8);
void selectMojiSet();
- #if REGION_JPN
+ #if TARGET_PC || REGION_JPN
int checkDakuon(int, u8);
int setDakuon(int, u8);
#endif
diff --git a/include/d/d_s_logo.h b/include/d/d_s_logo.h
index 56d10eef82..ab714a17ae 100644
--- a/include/d/d_s_logo.h
+++ b/include/d/d_s_logo.h
@@ -79,7 +79,7 @@ public:
bool isProgressiveMode();
void setRenderMode();
- #if VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD
u8 getPalLanguage();
#endif
@@ -149,7 +149,7 @@ public:
/* 0x200 */ dDlst_2D_c* mNvLogo;
/* 0x204 */ dDlst_2D_c* mMocImg;
#endif
-#if VERSION == VERSION_GCN_PAL
+#if TARGET_PC || VERSION == VERSION_GCN_PAL
/* 0x1FC */ mDoDvdThd_mountArchive_c* mpPalLogoResCommand;
#endif
/* 0x1FC */ request_of_phase_process_class* m_preLoad_dylPhase;
diff --git a/include/d/d_save.h b/include/d/d_save.h
index 0e9a4b37cd..e2719e0a98 100644
--- a/include/d/d_save.h
+++ b/include/d/d_save.h
@@ -486,6 +486,9 @@ public:
mDeathCount++;
}
}
+#if TARGET_PC
+ u16 getDeathCount() const { return mDeathCount; }
+#endif
char* getPlayerName() const { return const_cast(mPlayerName); }
void setPlayerName(const char* i_name) {
#if AVOID_UB
diff --git a/include/dusk/achievements.h b/include/dusk/achievements.h
new file mode 100644
index 0000000000..5c2758b6b7
--- /dev/null
+++ b/include/dusk/achievements.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "nlohmann/json.hpp"
+
+namespace dusk {
+
+enum class AchievementCategory : uint8_t {
+ Challenge,
+ Collection,
+ Minigame,
+ Misc,
+ Glitched
+};
+
+struct Achievement {
+ const char* key;
+ const char* name;
+ const char* description;
+ AchievementCategory category;
+ bool isCounter;
+ int32_t goal;
+ int32_t progress;
+ bool unlocked;
+};
+
+// Responsible for updating a.progress.
+// Use extra for any per-achievement state that must survive across frames or sessions, extra is saved
+using AchievementCheckFn = std::function;
+
+class AchievementSystem {
+public:
+ static AchievementSystem& get();
+
+ void load();
+ void save();
+ void tick();
+ void clearAll();
+ void clearOne(const char* key);
+
+ // Signals are visible to all achievement checks within the same tick, then cleared.
+ void signal(const char* key);
+ bool hasSignal(const char* key) const;
+
+ std::vector getAchievements() const;
+
+private:
+ struct Entry {
+ Achievement achievement;
+ AchievementCheckFn check;
+ nlohmann::json extra;
+ };
+
+ AchievementSystem();
+ static std::vector makeEntries();
+ void processEntry(Entry& e);
+
+ std::vector m_entries;
+ std::unordered_set m_signals;
+ bool m_loaded = false;
+ bool m_dirty = false;
+};
+
+} // namespace dusk
diff --git a/include/dusk/autosave.h b/include/dusk/autosave.h
new file mode 100644
index 0000000000..248924fcb8
--- /dev/null
+++ b/include/dusk/autosave.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#ifndef AUTOSAVE_H
+#define AUTOSAVE_H
+
+#include
+#include
+
+void noAutoSave();
+void triggerAutoSave();
+void updateAutoSave();
+void enterAutoSave();
+void autoSaving();
+void waitingForWrite();
+void endAutoSave();
+
+#endif
\ No newline at end of file
diff --git a/include/dusk/discord_presence.hpp b/include/dusk/discord_presence.hpp
index 899b2ad5f9..ebecc2d07b 100644
--- a/include/dusk/discord_presence.hpp
+++ b/include/dusk/discord_presence.hpp
@@ -1,18 +1,14 @@
#pragma once
-#ifdef DUSK_DISCORD_RPC
+#ifdef DUSK_DISCORD
-namespace dusk {
-namespace discord {
+namespace dusk::discord {
-void Initialize();
+void initialize();
+void run_callbacks();
+void update_presence();
+void shutdown();
-void RunCallbacks();
+} // namespace dusk::discord
-void UpdatePresence();
-
-void Shutdown();
-}
-}
-
-#endif // DUSK_DISCORD_RPC
+#endif // DUSK_DISCORD
diff --git a/include/dusk/dvd_asset.hpp b/include/dusk/dvd_asset.hpp
index 014c683d44..8594d4631c 100644
--- a/include/dusk/dvd_asset.hpp
+++ b/include/dusk/dvd_asset.hpp
@@ -1,22 +1,31 @@
#pragma once
#include "dolphin/types.h"
+#include "version.hpp"
namespace dusk {
+struct OffsetVersion {
+ version::GameVersion mGameVersion;
+ u32 mOffset;
+
+ constexpr OffsetVersion(const version::GameVersion gameVersion, const u32 offset)
+ : mGameVersion(gameVersion), mOffset(offset) {}
+};
+
/**
* Load bytes from the main DOL by GameCube virtual address
*/
-bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size);
+bool LoadDolAsset(void* dst, std::initializer_list virtualAddress, s32 size);
/**
* Load bytes from a REL file in the ISO filesystem, dst must be 32-byte aligned
*/
-bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size);
+bool LoadRelAsset(void* dst, const char* dvdPath, std::initializer_list offset, s32 size);
/**
* Load bytes from a REL inside RELS.arc
*/
-bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size);
+bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, std::initializer_list offset, s32 size);
} // namespace dusk
diff --git a/include/dusk/gyro.h b/include/dusk/gyro.h
index 279aeae16e..a206100739 100644
--- a/include/dusk/gyro.h
+++ b/include/dusk/gyro.h
@@ -12,6 +12,7 @@ void rollgoalTableOffset(s16& out_ax, s16& out_az);
extern bool s_sensor_keep_alive;
bool get_sensor_keep_alive();
void set_sensor_keep_alive(bool value);
+bool rollgoal_gyro_enabled();
} // namespace dusk::gyro
#endif
diff --git a/include/dusk/io.hpp b/include/dusk/io.hpp
index fb71a77d68..2efc4a8d3b 100644
--- a/include/dusk/io.hpp
+++ b/include/dusk/io.hpp
@@ -1,6 +1,7 @@
#ifndef DUSK_IO_HPP
#define DUSK_IO_HPP
+#include
#include
// I can't believe it's 2026 and neither SDL (no error codes) nor
@@ -15,7 +16,7 @@ namespace dusk::io {
* Methods on this class throw appropriate C++ exceptions when an error occurs.
*/
class FileStream {
- void* file;
+ FILE* file;
public:
FileStream() noexcept;
@@ -23,7 +24,7 @@ public:
/**
* \brief Take ownership of a FILE* handle.
*/
- explicit FileStream(void* file);
+ explicit FileStream(FILE* file);
FileStream(const FileStream& other) = delete;
FileStream(FileStream&& other) noexcept;
@@ -34,6 +35,11 @@ public:
*/
static FileStream OpenRead(const char* utf8Path);
+ /**
+ * \brief Open a file for reading at the given path.
+ */
+ static FileStream OpenRead(const std::filesystem::path& path);
+
/**
* \brief Create a file for writing.
*
@@ -41,16 +47,33 @@ public:
*/
static FileStream Create(const char* utf8Path);
+ /**
+ * \brief Create a file for writing.
+ *
+ * If there is an existing file, its contents are demolished.
+ */
+ static FileStream Create(const std::filesystem::path& path);
+
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static std::vector ReadAllBytes(const char* utf8Path);
+ /**
+ * \brief Read the byte contents of a file directly into a vector.
+ */
+ static std::vector ReadAllBytes(const std::filesystem::path& path);
+
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static void WriteAllText(const char* utf8Path, std::string_view text);
+ /**
+ * \brief Read the byte contents of a file directly into a vector.
+ */
+ static void WriteAllText(const std::filesystem::path& path, std::string_view text);
+
/**
* \brief Read the remaining contents of the file directly into a vector.
*/
@@ -59,17 +82,24 @@ public:
/**
* Get direct access to the underlying FILE* handle.
*/
- [[nodiscard]] void* GetFileHandle() const noexcept {
- return file;
- }
+ [[nodiscard]] void* GetFileHandle() const noexcept { return file; }
/**
* Write data to the file.
*/
void Write(const char* data, size_t dataLen);
+
+ FILE* ToInner();
};
+/**
+ * Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows.
+ */
+inline std::string fs_path_to_string(const std::filesystem::path& path) {
+ const auto u8str = path.u8string();
+ return {reinterpret_cast(u8str.c_str())};
}
+} // namespace dusk::io
#endif // DUSK_IO_HPP
diff --git a/include/dusk/livesplit.h b/include/dusk/livesplit.h
new file mode 100644
index 0000000000..b283a29af4
--- /dev/null
+++ b/include/dusk/livesplit.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+
+namespace dusk::speedrun {
+void onGameFrame();
+uint64_t getFrameCount();
+void start();
+void reset();
+void connectLiveSplit(const char* host = "127.0.0.1", int port = 16834);
+void disconnectLiveSplit();
+bool consumeConnectedEvent();
+bool consumeDisconnectedEvent();
+void updateLiveSplit();
+void shutdown();
+}
diff --git a/include/dusk/main.h b/include/dusk/main.h
index 065f507d36..c5c4fb27d1 100644
--- a/include/dusk/main.h
+++ b/include/dusk/main.h
@@ -1,14 +1,36 @@
#ifndef DUSK_MAIN_H
#define DUSK_MAIN_H
+#if defined(__APPLE__)
+#include
+#endif
+
#include
+#if defined(_WIN32) || \
+ (defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST) || \
+ (defined(__linux__) && !defined(__ANDROID__))
+#define DUSK_CAN_OPEN_DATA_FOLDER 1
+#else
+#define DUSK_CAN_OPEN_DATA_FOLDER 0
+#endif
+
namespace dusk {
extern bool IsRunning;
extern bool IsShuttingDown;
extern bool IsGameLaunched;
- extern bool IsFocusPaused;
+ extern bool RestartRequested;
extern std::filesystem::path ConfigPath;
+
+#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
+ (defined(TARGET_OS_TV) && TARGET_OS_TV)
+ inline constexpr bool SupportsProcessRestart = false;
+#else
+ inline constexpr bool SupportsProcessRestart = true;
+#endif
+
+ void RequestRestart() noexcept;
+ bool OpenDataFolder();
}
#endif // DUSK_MAIN_H
diff --git a/include/dusk/map_loader_definitions.h b/include/dusk/map_loader_definitions.h
index a0f7150d2d..0ffda772ba 100644
--- a/include/dusk/map_loader_definitions.h
+++ b/include/dusk/map_loader_definitions.h
@@ -1,5 +1,7 @@
#pragma once
+#include
+
struct RoomEntry {
u8 roomNo;
std::vector roomPoints = {};
diff --git a/include/dusk/math.h b/include/dusk/math.h
index fe27b3046b..04f1087670 100644
--- a/include/dusk/math.h
+++ b/include/dusk/math.h
@@ -2,9 +2,6 @@
#define _SRC_DUSK_MATH_H_
#include
-#include
-#include
-#include
#ifndef M_PI
#define M_PI 3.14159265358979323846f
@@ -19,139 +16,6 @@ inline float i_cosf(float x) { return cos(x); }
inline float i_tanf(float x) { return tan(x); }
inline float i_acosf(float x) { return acos(x); }
-
-// frsqrte matching courtesy of Geotale, with reference to https://achurch.org/cpu-tests/ppc750cl.s
-
-struct BaseAndDec32 {
- uint32_t base;
- int32_t dec;
-};
-
-struct BaseAndDec64 {
- uint64_t base;
- int64_t dec;
-};
-
-union c32 {
- constexpr c32(const float p) {
- f = p;
- }
-
- constexpr c32(const uint32_t p) {
- u = p;
- }
-
- uint32_t u;
- float f;
-};
-
-union c64 {
- constexpr c64(const double p) {
- f = p;
- }
-
- constexpr c64(const uint64_t p) {
- u = p;
- }
-
- uint64_t u;
- double f;
-};
-
-static constexpr uint64_t EXPONENT_SHIFT_F64 = 52;
-static constexpr uint64_t MANTISSA_MASK_F64 = 0x000fffffffffffffULL;
-static constexpr uint64_t EXPONENT_MASK_F64 = 0x7ff0000000000000ULL;
-static constexpr uint64_t SIGN_MASK_F64 = 0x8000000000000000ULL;
-
-static constexpr std::array RSQRTE_TABLE = {{
- {0x69fa000000000ULL, -0x15a0000000LL},
- {0x5f2e000000000ULL, -0x13cc000000LL},
- {0x554a000000000ULL, -0x1234000000LL},
- {0x4c30000000000ULL, -0x10d4000000LL},
- {0x43c8000000000ULL, -0x0f9c000000LL},
- {0x3bfc000000000ULL, -0x0e88000000LL},
- {0x34b8000000000ULL, -0x0d94000000LL},
- {0x2df0000000000ULL, -0x0cb8000000LL},
- {0x2794000000000ULL, -0x0bf0000000LL},
- {0x219c000000000ULL, -0x0b40000000LL},
- {0x1bfc000000000ULL, -0x0aa0000000LL},
- {0x16ae000000000ULL, -0x0a0c000000LL},
- {0x11a8000000000ULL, -0x0984000000LL},
- {0x0ce6000000000ULL, -0x090c000000LL},
- {0x0862000000000ULL, -0x0898000000LL},
- {0x0416000000000ULL, -0x082c000000LL},
- {0xffe8000000000ULL, -0x1e90000000LL},
- {0xf0a4000000000ULL, -0x1c00000000LL},
- {0xe2a8000000000ULL, -0x19c0000000LL},
- {0xd5c8000000000ULL, -0x17c8000000LL},
- {0xc9e4000000000ULL, -0x1610000000LL},
- {0xbedc000000000ULL, -0x1490000000LL},
- {0xb498000000000ULL, -0x1330000000LL},
- {0xab00000000000ULL, -0x11f8000000LL},
- {0xa204000000000ULL, -0x10e8000000LL},
- {0x9994000000000ULL, -0x0fe8000000LL},
- {0x91a0000000000ULL, -0x0f08000000LL},
- {0x8a1c000000000ULL, -0x0e38000000LL},
- {0x8304000000000ULL, -0x0d78000000LL},
- {0x7c48000000000ULL, -0x0cc8000000LL},
- {0x75e4000000000ULL, -0x0c28000000LL},
- {0x6fd0000000000ULL, -0x0b98000000LL},
-}};
-
-[[nodiscard]] static inline double frsqrte(const double val) {
- c64 bits(val);
-
- uint64_t mantissa = bits.u & MANTISSA_MASK_F64;
- int64_t exponent = bits.u & EXPONENT_MASK_F64;
- bool sign = (bits.u & SIGN_MASK_F64) != 0;
-
- // Handle 0 case
- if (mantissa == 0 && exponent == 0) {
- return std::copysign(std::numeric_limits::infinity(), bits.f);
- }
-
- // Handle NaN-like
- if (exponent == EXPONENT_MASK_F64) {
- if (mantissa == 0) {
- return sign ? std::numeric_limits::quiet_NaN() : 0.0;
- }
-
- return val;
- }
-
- // Handle negative inputs
- if (sign) {
- return std::numeric_limits::quiet_NaN();
- }
-
- if (exponent == 0) {
- // Shift so one bit goes to where the exponent would be,
- // then clear that bit to mimic a not-subnormal number!
- // Aka, if there are 12 leading zeroes, shift left once
- uint32_t shift = std::countl_zero(mantissa) - static_cast(63 - EXPONENT_SHIFT_F64);
-
- mantissa <<= shift;
- mantissa &= MANTISSA_MASK_F64;
- // The shift is subtracted by 1 because denormals by default
- // are offset by 1 (exponent 0 doesn't have implied 1 bit)
- exponent -= static_cast(shift - 1) << EXPONENT_SHIFT_F64;
- }
-
- // In reality this doesn't get the full exponent -- Only the least significant bit
- // Only that's needed because square roots of higher exponent bits simply multiply the
- // result by 2!!
- uint32_t key = static_cast((static_cast(exponent) | mantissa) >> 37);
- uint64_t new_exp =
- (static_cast((0xbfcLL << EXPONENT_SHIFT_F64) - exponent) >> 1) & EXPONENT_MASK_F64;
-
- // Remove the bits relating to anything higher than the LSB of the exponent
- const auto &entry = RSQRTE_TABLE[0x1f & (key >> 11)];
-
- // The result is given by an estimate then an adjustment based on the original
- // key that was computed
- uint64_t new_mantissa = static_cast(entry.base + entry.dec * static_cast(key & 0x7ff));
-
- return c64(new_exp | new_mantissa).f;
-}
+#include
#endif // _SRC_DUSK_MATH_H_
diff --git a/include/dusk/settings.h b/include/dusk/settings.h
index 039e51af7d..f74a4bf61f 100644
--- a/include/dusk/settings.h
+++ b/include/dusk/settings.h
@@ -13,12 +13,49 @@ enum class BloomMode : int {
Dusk = 2,
};
+enum class GameLanguage : u8 {
+ English = OS_LANGUAGE_ENGLISH,
+ German = OS_LANGUAGE_GERMAN,
+ French = OS_LANGUAGE_FRENCH,
+ Spanish = OS_LANGUAGE_SPANISH,
+ Italian = OS_LANGUAGE_ITALIAN,
+};
+
+enum class DiscVerificationState : u8 {
+ Unknown = 0,
+ Success,
+ HashMismatch,
+};
+
+enum class GyroMode : u8 {
+ Sensor = 0,
+ Mouse = 1,
+};
+
namespace config {
template <>
struct ConfigEnumRange {
static constexpr auto min = BloomMode::Off;
static constexpr auto max = BloomMode::Dusk;
};
+
+template <>
+struct ConfigEnumRange {
+ static constexpr auto min = GameLanguage::English;
+ static constexpr auto max = GameLanguage::Italian;
+};
+
+template <>
+struct ConfigEnumRange {
+ static constexpr auto min = DiscVerificationState::Unknown;
+ static constexpr auto max = DiscVerificationState::HashMismatch;
+};
+
+template <>
+struct ConfigEnumRange {
+ static constexpr auto min = GyroMode::Sensor;
+ static constexpr auto max = GyroMode::Mouse;
+};
}
// Persistent user settings
@@ -31,6 +68,8 @@ struct UserSettings {
ConfigVar enableFullscreen;
ConfigVar enableVsync;
ConfigVar lockAspectRatio;
+ ConfigVar enableFpsOverlay;
+ ConfigVar fpsOverlayCorner;
} video;
struct {
@@ -41,15 +80,18 @@ struct UserSettings {
ConfigVar soundEffectsVolume;
ConfigVar fanfareVolume;
ConfigVar enableReverb;
+ ConfigVar enableHrtf;
+ ConfigVar menuSounds;
} audio;
// Game settings
struct {
+ ConfigVar language;
+
// QoL
ConfigVar enableQuickTransform;
ConfigVar hideTvSettingsScreen;
- ConfigVar skipWarningScreen;
ConfigVar biggerWallets;
ConfigVar noReturnRupees;
ConfigVar disableRupeeCutscenes;
@@ -60,17 +102,19 @@ struct UserSettings {
ConfigVar fastClimbing;
ConfigVar noMissClimbing;
ConfigVar fastTears;
+ ConfigVar no2ndFishForCat;
ConfigVar instantSaves;
ConfigVar instantText;
ConfigVar sunsSong;
+ ConfigVar autoSave;
// Preferences
ConfigVar enableMirrorMode;
- ConfigVar invertCameraXAxis;
- ConfigVar disableMainHUD;
+ ConfigVar minimalHUD;
ConfigVar pauseOnFocusLost;
ConfigVar enableLinkDollRotation;
-
+ ConfigVar enableAchievementToasts;
+ ConfigVar enableControllerToasts;
// Graphics
ConfigVar bloomMode;
@@ -80,12 +124,14 @@ struct UserSettings {
ConfigVar internalResolutionScale;
ConfigVar shadowResolutionMultiplier;
ConfigVar enableDepthOfField;
+ ConfigVar enableMapBackground;
// Audio
ConfigVar noLowHpSound;
ConfigVar midnasLamentNonStop;
// Input
+ ConfigVar gyroMode;
ConfigVar enableGyroAim;
ConfigVar enableGyroRollgoal;
ConfigVar gyroSensitivityX;
@@ -95,6 +141,13 @@ struct UserSettings {
ConfigVar gyroDeadband;
ConfigVar gyroInvertPitch;
ConfigVar gyroInvertYaw;
+ ConfigVar freeCamera;
+ ConfigVar invertCameraXAxis;
+ ConfigVar invertCameraYAxis;
+ ConfigVar freeCameraSensitivity;
+ ConfigVar debugFlyCam;
+ ConfigVar debugFlyCamLockEvents;
+ ConfigVar allowBackgroundInput;
// Cheats
ConfigVar infiniteHearts;
@@ -117,17 +170,24 @@ struct UserSettings {
// Controls
ConfigVar enableTurboKeybind;
+
+ // Tools
+ ConfigVar speedrunMode;
+ ConfigVar liveSplitEnabled;
+ ConfigVar recordingMode;
} game;
struct {
ConfigVar isoPath;
+ ConfigVar isoVerification;
ConfigVar graphicsBackend;
ConfigVar skipPreLaunchUI;
ConfigVar showPipelineCompilation;
ConfigVar wasPresetChosen;
ConfigVar enableCrashReporting;
- ConfigVar duskMenuOpen;
+ ConfigVar checkForUpdates;
ConfigVar cardFileType;
+ ConfigVar enableAdvancedSettings;
} backend;
};
diff --git a/include/dusk/version.hpp b/include/dusk/version.hpp
new file mode 100644
index 0000000000..fe393b049b
--- /dev/null
+++ b/include/dusk/version.hpp
@@ -0,0 +1,67 @@
+#ifndef DUSK_VERSION_HPP
+#define DUSK_VERSION_HPP
+
+/**
+ * Functionality for switching game behavior based on the loaded game version (e.g. PAL/JPN, GC/Wii)
+ */
+namespace dusk::version {
+ enum class GameVersion : u8 {
+ GcnUsa = VERSION_GCN_USA,
+ GcnPal = VERSION_GCN_PAL,
+ GcnJpn = VERSION_GCN_JPN,
+ WiiUsaRev0 = VERSION_WII_USA_R0,
+ WiiUsa = VERSION_WII_USA_R2,
+ WiiPal = VERSION_WII_PAL,
+ WiiJpn = VERSION_WII_JPN,
+ WiiKor = VERSION_WII_KOR,
+ };
+
+ bool isGcn();
+ bool isWii();
+ bool isPalOrAtLeastWiiR2();
+
+ bool isRegionPal();
+ bool isRegionJpn();
+ bool isRegionUsa();
+
+ GameVersion getGameVersion();
+
+ const DVDDiskID& getDiskID();
+
+ void init();
+
+ template
+ struct VersionOption {
+ GameVersion mVersion;
+ T mValue;
+
+ constexpr VersionOption(GameVersion version, T value) : mVersion(version), mValue(value) {}
+ };
+
+ template
+ const T& versionSelect(const std::initializer_list> options) {
+ const auto version = getGameVersion();
+ for (const auto& opt : options) {
+ if (opt.mVersion == version) {
+ return opt.mValue;
+ }
+ }
+
+ // Unable to find value.
+ abort();
+ }
+
+ template
+ const T& versionSelect(const std::initializer_list> options, const T& defaultValue) {
+ const auto version = getGameVersion();
+ for (const auto& opt : options) {
+ if (opt.mVersion == version) {
+ return opt.mValue;
+ }
+ }
+
+ return defaultValue;
+ }
+} // namespace dusk::version
+
+#endif // DUSK_VERSION_HPP
diff --git a/include/global.h b/include/global.h
index 8cc1b55743..11415b0737 100644
--- a/include/global.h
+++ b/include/global.h
@@ -243,12 +243,16 @@ using std::isnan;
// Some basic macros that are more convenient than putting down #if blocks for one-line changes.
#if TARGET_PC
#define IF_DUSK(statement) statement
+#define IF_DUSK_BLOCK(cond) if (cond) {
+#define IF_DUSK_BLOCK_END }
#define IF_DUSK_ARG(expr) , expr
#define IF_NOT_DUSK(statement)
#define DUSK_IF_ELSE(dusk, orig) dusk
#else
#define IF_DUSK(statement)
#define IF_DUSK_ARG(expr)
+#define IF_DUSK_BLOCK(cond)
+#define IF_DUSK_BLOCK_END
#define IF_NOT_DUSK(statement) statement
#define DUSK_IF_ELSE(dusk, orig) orig
#endif
diff --git a/include/m_Do/m_Do_audio.h b/include/m_Do/m_Do_audio.h
index 1d58e8d8f2..4bc2f20c94 100644
--- a/include/m_Do/m_Do_audio.h
+++ b/include/m_Do/m_Do_audio.h
@@ -5,6 +5,7 @@
#include "Z2AudioLib/Z2EnvSeMgr.h"
#include "Z2AudioLib/Z2LinkMgr.h"
#include "dusk/audio.h"
+#include "dusk/settings.h"
class mDoAud_zelAudio_c : public Z2AudioMgr {
public:
@@ -132,6 +133,18 @@ inline void mDoAud_seStart(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_re
-1.0f, -1.0f, 0);
}
+#if TARGET_PC
+inline void mDoAud_seStartMenu(u32 i_sfxID) {
+ if (!mDoAud_zelAudio_c::isInitFlag()) {
+ return;
+ }
+ if (!dusk::getSettings().audio.menuSounds.getValue()) {
+ return;
+ }
+ mDoAud_seStart(i_sfxID, nullptr, 0, 0);
+}
+#endif
+
inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) {
DUSK_AUDIO_SKIP()
Z2AudioMgr::getInterface()->seStartLevel(i_sfxID, i_sePos, param_2, i_reverb, 1.0f, 1.0f,
diff --git a/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h b/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h
index b565ad78f8..05663fd84e 100644
--- a/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h
+++ b/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h
@@ -1,9 +1,9 @@
#ifndef J3DSTRUCT_H
#define J3DSTRUCT_H
+#include
#include
#include
-#include
#include "global.h"
#include "JSystem/JMath/JMath.h"
@@ -11,7 +11,7 @@
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DLightInfo {
bool operator==(J3DLightInfo& other) const;
@@ -28,7 +28,7 @@ struct J3DLightInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DTextureSRTInfo {
// NOTE: Big endian when loaded from file!
@@ -79,7 +79,7 @@ enum J3DTexMtxMode {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DTexMtxInfo {
bool operator==(J3DTexMtxInfo& other) const;
@@ -97,7 +97,7 @@ struct J3DTexMtxInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DIndTexMtxInfo {
J3DIndTexMtxInfo& operator=(J3DIndTexMtxInfo const&);
@@ -107,7 +107,7 @@ struct J3DIndTexMtxInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DFogInfo {
bool operator==(J3DFogInfo&) const;
@@ -126,7 +126,7 @@ struct J3DFogInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DNBTScaleInfo {
bool operator==(const J3DNBTScaleInfo& other) const;
@@ -153,7 +153,7 @@ struct J3DIndTexOrderInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DTevSwapModeInfo {
/* 0x0 */ u8 mRasSel;
@@ -164,7 +164,7 @@ struct J3DTevSwapModeInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DTevSwapModeTableInfo {
/* 0x0 */ u8 field_0x0;
@@ -175,7 +175,7 @@ struct J3DTevSwapModeTableInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DTevStageInfo {
/* 0x0 */ u8 field_0x0;
@@ -202,7 +202,7 @@ struct J3DTevStageInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DIndTevStageInfo {
/* 0x0 */ u8 mIndStage;
@@ -219,7 +219,7 @@ struct J3DIndTevStageInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DTexCoordInfo {
/* 0x0 */ u8 mTexGenType;
@@ -265,7 +265,7 @@ struct J3DBlendInfo {
/**
* @ingroup jsystem-j3d
- *
+ *
*/
struct J3DTevOrderInfo {
void operator=(const J3DTevOrderInfo& other) {
diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h
index 87c85f316f..6f6d29ca98 100644
--- a/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h
+++ b/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h
@@ -59,6 +59,9 @@ public:
bool isActive() const { return mSeqList.getNumLinks() != 0; }
int getNumActiveSeqs() const { return mSeqList.getNumLinks(); }
void pause(bool paused) { mActivity.field_0x0.flags.flag2 = paused; }
+ #if TARGET_PC
+ JSUList* getSeqList() { return &mSeqList; }
+ #endif
private:
/* 0x08 */ JAIAudience* mAudience;
diff --git a/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h b/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h
index d348859ebe..795a5a2628 100644
--- a/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h
+++ b/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h
@@ -207,4 +207,11 @@ void JPARegistAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistPrmAlpha(JPAEmitterWorkData*, JPABaseParticle*);
void JPARegistPrmAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*);
+#if TARGET_PC
+void JPAInterpBillboard(JPAEmitterWorkData*, JPABaseParticle*);
+void JPAInterpRotBillboard(JPAEmitterWorkData*, JPABaseParticle*);
+void JPAInterpDirection(JPAEmitterWorkData*, JPABaseParticle*);
+void JPAInterpRotDirection(JPAEmitterWorkData*, JPABaseParticle*);
+#endif
+
#endif /* JPABASESHAPE_H */
diff --git a/libs/JSystem/include/JSystem/JParticle/JPAParticle.h b/libs/JSystem/include/JSystem/JParticle/JPAParticle.h
index 842f293286..c864a5cd4a 100644
--- a/libs/JSystem/include/JSystem/JParticle/JPAParticle.h
+++ b/libs/JSystem/include/JSystem/JParticle/JPAParticle.h
@@ -24,6 +24,9 @@ public:
void init_c(JPAEmitterWorkData*, JPABaseParticle*);
bool calc_p(JPAEmitterWorkData*);
bool calc_c(JPAEmitterWorkData*);
+#if TARGET_PC
+ void interp(JPAEmitterWorkData*, void const* drawFunc);
+#endif
bool canCreateChild(JPAEmitterWorkData*);
f32 getWidth(JPABaseEmitter const*) const;
f32 getHeight(JPABaseEmitter const*) const;
diff --git a/libs/JSystem/include/JSystem/JUtility/JUTPalette.h b/libs/JSystem/include/JSystem/JUtility/JUTPalette.h
index 017d511d50..5d29005a92 100644
--- a/libs/JSystem/include/JSystem/JUtility/JUTPalette.h
+++ b/libs/JSystem/include/JSystem/JUtility/JUTPalette.h
@@ -40,6 +40,9 @@ public:
JUTTransparency getTransparency() const { return JUTTransparency(mTransparency); }
u16 getNumColors() const { return mNumColors; }
ResTLUT* getColorTable() const { return mColorTable; }
+#if TARGET_PC
+ void dataUploaded();
+#endif
private:
/* 0x00 */ GXTlutObj mTlutObj;
diff --git a/libs/JSystem/include/JSystem/JUtility/JUTTexture.h b/libs/JSystem/include/JSystem/JUtility/JUTTexture.h
index e0f1e15ff0..6a7a8f9a0e 100644
--- a/libs/JSystem/include/JSystem/JUtility/JUTTexture.h
+++ b/libs/JSystem/include/JSystem/JUtility/JUTTexture.h
@@ -75,6 +75,7 @@ public:
s32 getTransparency() const { return mTexInfo->alphaEnabled; }
s32 getWidth() const { return mTexInfo->width; }
s32 getHeight() const { return mTexInfo->height; }
+ JUTPalette* getPalette() const { return mPalette; }
void setCaptureFlag(bool flag) { mFlags &= 2 | flag; }
bool getCaptureFlag() const { return mFlags & 1; }
bool getEmbPaletteDelFlag() const { return mFlags & 2; }
@@ -82,7 +83,7 @@ public:
int getTlutName() const { return mTlutName; }
bool operator==(const JUTTexture& other) {
return mTexInfo == other.mTexInfo
- && field_0x2c == other.field_0x2c
+ && mPalette == other.mPalette
&& mWrapS == other.mWrapS
&& mWrapT == other.mWrapT
&& mMinFilter == other.mMinFilter
@@ -100,7 +101,7 @@ private:
/* 0x20 */ const ResTIMG* mTexInfo;
/* 0x24 */ void* mTexData;
/* 0x28 */ JUTPalette* mEmbPalette;
- /* 0x2C */ JUTPalette* field_0x2c;
+ /* 0x2C */ JUTPalette* mPalette;
/* 0x30 */ u8 mWrapS;
/* 0x31 */ u8 mWrapT;
/* 0x32 */ u8 mMinFilter;
diff --git a/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp b/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp
index 32f0e4eed9..c3c72310a0 100644
--- a/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp
+++ b/libs/JSystem/src/J3DGraphLoader/J3DModelLoader.cpp
@@ -556,8 +556,8 @@ void J3DModelLoader::readVertexData(const J3DVertexBlock& block, J3DVertexData&
if (attr == GX_VA_POS) {
// can be a little off due to 0x20 alignment, account for that
- u32 expect = ((data.mVtxNum * vertStride) + 0x1F) & ~0x1F;
- JUT_ASSERT(1234, expect == addrDiff);
+ // u32 expect = ((data.mVtxNum * vertStride) + 0x1F) & ~0x1F;
+ // JUT_ASSERT(1234, expect == addrDiff);
} else if (attr == GX_VA_NRM) {
data.mNrmNum = num;
} else if (attr == GX_VA_CLR0) {
diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp
index 61fe80a098..758167cc5c 100644
--- a/libs/JSystem/src/JAudio2/JASChannel.cpp
+++ b/libs/JSystem/src/JAudio2/JASChannel.cpp
@@ -1,6 +1,9 @@
#include "JSystem/JSystem.h" // IWYU pragma: keep
#include "JSystem/JAudio2/JASChannel.h"
+#if TARGET_PC
+#include "dusk/audio/DuskDsp.hpp"
+#endif
#include "JSystem/JAudio2/JASAiCtrl.h"
#include "JSystem/JAudio2/JASCalc.h"
#include "JSystem/JAudio2/JASDriverIF.h"
@@ -170,7 +173,12 @@ void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVo
f32 pan = 0.5f;
f32 dolby = 0.0f;
- switch (JASDriver::getOutputMode()) {
+#if TARGET_PC
+ u32 effectiveOutputMode = dusk::audio::EnableHrtf ? JAS_OUTPUT_SURROUND : JASDriver::getOutputMode();
+#else
+ u32 effectiveOutputMode = JASDriver::getOutputMode();
+#endif
+ switch (effectiveOutputMode) {
case JAS_OUTPUT_MONO:
break;
case JAS_OUTPUT_STEREO:
diff --git a/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp b/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp
index 45568cc29a..e3722e3726 100644
--- a/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp
+++ b/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp
@@ -302,7 +302,6 @@ void JASKernel::setupRootHeap(JKRSolidHeap* heap, u32 size) {
JKRHEAP_NAME(sSystemHeap, "JASKernel::sSystemHeap");
JUT_ASSERT(787, sSystemHeap);
sCommandHeap = JKR_NEW_ARGS (heap, 0) JASMemChunkPool<1024, JASThreadingModel::ObjectLevelLockable>;
- JKRHEAP_NAME(sSystemHeap, "JASKernel::sCommandHeap");
JUT_ASSERT(790, sCommandHeap);
JASDram = heap;
}
diff --git a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp
index e1f45de1ae..cc94097712 100644
--- a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp
+++ b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp
@@ -442,6 +442,7 @@ static JAUSectionHeap* JAUNewSectionHeap(JKRSolidHeap* heap, bool param_1) {
JAUSectionHeap* JAUNewSectionHeap(bool param_0) {
s32 freeSize = JASDram->getFreeSize();
JKRSolidHeap* sectionHeap = JKRCreateSolidHeap(freeSize, JASDram, true);
+ JKRHEAP_NAME(sectionHeap, "sectionHeap");
JUT_ASSERT(821, sectionHeap);
return JAUNewSectionHeap(sectionHeap, param_0);
}
diff --git a/libs/JSystem/src/JKernel/JKRExpHeap.cpp b/libs/JSystem/src/JKernel/JKRExpHeap.cpp
index 4a6712cb65..dc88090a47 100644
--- a/libs/JSystem/src/JKernel/JKRExpHeap.cpp
+++ b/libs/JSystem/src/JKernel/JKRExpHeap.cpp
@@ -222,16 +222,11 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) {
OSReport_Error("Free block list as follows:\n");
OSReport_Error("Start | End | Size \n");
- int i = 0;
for (const CMemBlock* block = mHeadFreeList; block; block = block->mNext) {
if (block->mMagic) {
// Allocated, ignore.
continue;
}
- if (i++ > 10) {
- OSReport_Error("\n");
- break;
- }
auto blockStart = (uintptr_t)block - (uintptr_t)mStart;
auto blockEnd = (uintptr_t)block + block->size - (uintptr_t)mStart;
@@ -239,6 +234,14 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) {
OSReport_Error("%08X | %08X | %08X\n", (u32) blockStart, (u32) blockEnd, (u32) blockSize);
}
+ OSReport_Error("Child heaps as follows:\n");
+ OSReport_Error("Start | End | Name \n");
+
+ const JSUTree& tree = getHeapTree();
+ for (JSUTreeIterator iter(tree.getFirstChild()); iter != tree.getEndChild(); ++iter) {
+ OSReport_Error("%08X | %08X | %s\n", iter->getStartAddr(), iter->getEndAddr(), iter->getName());
+ }
+
CRASH("Aborting due to allocation failure!");
}
#else
diff --git a/libs/JSystem/src/JParticle/JPABaseShape.cpp b/libs/JSystem/src/JParticle/JPABaseShape.cpp
index 178606a687..ff346984cd 100644
--- a/libs/JSystem/src/JParticle/JPABaseShape.cpp
+++ b/libs/JSystem/src/JParticle/JPABaseShape.cpp
@@ -9,6 +9,9 @@
#include
#include
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
#include "tracy/Tracy.hpp"
void JPASetPointSize(JPAEmitterWorkData* work) {
@@ -418,50 +421,95 @@ static projectionFunc p_prj[3] = {
loadPrjAnm,
};
-void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
- if (param_1->checkStatus(JPAPtclStts_Invisible)) {
+#if TARGET_PC
+void JPAInterpBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ Mtx ptclPosMtx;
+ MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z);
+ dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl);
+}
+
+void JPAInterpRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ Mtx ptclPosMtx;
+ f32 sinRot = JMASSin(ptcl->mRotateAngle);
+ f32 cosRot = JMASCos(ptcl->mRotateAngle);
+ MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z);
+ ptclPosMtx[0][0] = cosRot;
+ ptclPosMtx[0][1] = -sinRot;
+ ptclPosMtx[1][0] = sinRot;
+ ptclPosMtx[1][1] = cosRot;
+ dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl);
+}
+#endif
+
+void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
- JGeometry::TVec3 local_48;
- MTXMultVec(work->mPosCamMtx, ¶m_1->mPosition, &local_48);
- Mtx local_38;
- local_38[0][0] = work->mGlobalPtclScl.x * param_1->mParticleScaleX;
- local_38[0][3] = local_48.x;
- local_38[1][1] = work->mGlobalPtclScl.y * param_1->mParticleScaleY;
- local_38[1][3] = local_48.y;
- local_38[2][2] = 1.0f;
- local_38[2][3] = local_48.z;
- local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[1][2] = local_38[2][0] = local_38[2][1] = 0.0f;
- GXLoadPosMtxImm(local_38, 0);
- p_prj[work->mPrjType](work, local_38);
+ JGeometry::TVec3 pos;
+#if TARGET_PC
+ Mtx ptclPosMtx;
+ if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) {
+ pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]);
+ MTXMultVec(work->mPosCamMtx, &pos, &pos);
+ } else
+#endif
+ {
+ MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos);
+ }
+ Mtx posMtx;
+ posMtx[0][0] = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
+ posMtx[0][3] = pos.x;
+ posMtx[1][1] = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
+ posMtx[1][3] = pos.y;
+ posMtx[2][2] = 1.0f;
+ posMtx[2][3] = pos.z;
+ posMtx[0][1] = posMtx[0][2] = posMtx[1][0] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f;
+ GXLoadPosMtxImm(posMtx, GX_PNMTX0);
+ p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
}
-void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
- if (param_1->checkStatus(JPAPtclStts_Invisible)) {
+void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
- JGeometry::TVec3 local_48;
- MTXMultVec(work->mPosCamMtx, ¶m_1->mPosition, &local_48);
- f32 sinRot = JMASSin(param_1->mRotateAngle);
- f32 cosRot = JMASCos(param_1->mRotateAngle);
- f32 particleX = work->mGlobalPtclScl.x * param_1->mParticleScaleX;
- f32 particleY = work->mGlobalPtclScl.y * param_1->mParticleScaleY;
+ if (work->mpRes->getUsrIdx() == 0x89d7) {
+ int a = 0;
+ }
- Mtx local_38;
- local_38[0][0] = cosRot * particleX;
- local_38[0][1] = -sinRot * particleY;
- local_38[0][3] = local_48.x;
- local_38[1][0] = sinRot * particleX;
- local_38[1][1] = cosRot * particleY;
- local_38[1][3] = local_48.y;
- local_38[2][2] = 1.0f;
- local_38[2][3] = local_48.z;
- local_38[0][2] = local_38[1][2] = local_38[2][0] = local_38[2][1] = 0.0f;
- GXLoadPosMtxImm(local_38, 0);
- p_prj[work->mPrjType](work, local_38);
+ JGeometry::TVec3 pos;
+ f32 sinRot, cosRot;
+#if TARGET_PC
+ Mtx ptclPosMtx;
+ MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z);
+ if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) {
+ pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]);
+ sinRot = ptclPosMtx[1][0];
+ cosRot = ptclPosMtx[0][0];
+ MTXMultVec(work->mPosCamMtx, &pos, &pos);
+ } else
+#endif
+ {
+ MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos);
+ sinRot = JMASSin(ptcl->mRotateAngle);
+ cosRot = JMASCos(ptcl->mRotateAngle);
+ }
+ f32 particleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
+ f32 particleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
+ Mtx posMtx;
+ posMtx[0][0] = cosRot * particleX;
+ posMtx[0][1] = -sinRot * particleY;
+ posMtx[0][3] = pos.x;
+ posMtx[1][0] = sinRot * particleX;
+ posMtx[1][1] = cosRot * particleY;
+ posMtx[1][3] = pos.y;
+ posMtx[2][2] = 1.0f;
+ posMtx[2][3] = pos.z;
+ posMtx[0][2] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f;
+ GXLoadPosMtxImm(posMtx, GX_PNMTX0);
+ p_prj[work->mPrjType](work, posMtx);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
}
@@ -484,7 +532,7 @@ void JPADrawYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
local_38[2][2] = work->mYBBCamMtx[2][2];
local_38[2][3] = local_48.z;
local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[2][0] = 0.0f;
- GXLoadPosMtxImm(local_38, 0);
+ GXLoadPosMtxImm(local_38, GX_PNMTX0);
p_prj[work->mPrjType](work, local_38);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
}
@@ -517,7 +565,7 @@ void JPADrawRotYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) {
local_38[2][1] = local_94 * fVar1;
local_38[2][2] = local_90;
local_38[2][3] = local_48.z;
- GXLoadPosMtxImm(local_38, 0);
+ GXLoadPosMtxImm(local_38, GX_PNMTX0);
p_prj[work->mPrjType](work, local_38);
GXCallDisplayList(jpa_dl, sizeof(jpa_dl));
}
@@ -681,103 +729,197 @@ static u8* p_dl[2] = {
jpa_dl_x,
};
-void JPADrawDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
- if (param_1->checkStatus(JPAPtclStts_Invisible)) {
+#if TARGET_PC
+void JPAInterpDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ JGeometry::TVec3 axisY;
+ JGeometry::TVec3 axisZ;
+ p_direction[work->mDirType](work, ptcl, &axisY);
+
+ if (axisY.isZero()) {
return;
}
- ZoneScoped;
+ axisY.normalize();
+ axisZ.cross(ptcl->mBaseAxis, axisY);
- JGeometry::TVec3 local_6c;
- JGeometry::TVec3 local_78;
- p_direction[param_0->mDirType](param_0, param_1, &local_6c);
-
- if (local_6c.isZero()) {
+ if (axisZ.isZero()) {
return;
}
- local_6c.normalize();
- local_78.cross(param_1->mBaseAxis, local_6c);
-
- if (local_78.isZero()) {
- return;
- }
-
- local_78.normalize();
- param_1->mBaseAxis.cross(local_6c, local_78);
- param_1->mBaseAxis.normalize();
- Mtx local_60;
- f32 fVar1 = param_0->mGlobalPtclScl.x * param_1->mParticleScaleX;
- f32 fVar2 = param_0->mGlobalPtclScl.y * param_1->mParticleScaleY;
- local_60[0][0] = param_1->mBaseAxis.x;
- local_60[0][1] = local_6c.x;
- local_60[0][2] = local_78.x;
- local_60[0][3] = param_1->mPosition.x;
- local_60[1][0] = param_1->mBaseAxis.y;
- local_60[1][1] = local_6c.y;
- local_60[1][2] = local_78.y;
- local_60[1][3] = param_1->mPosition.y;
- local_60[2][0] = param_1->mBaseAxis.z;
- local_60[2][1] = local_6c.z;
- local_60[2][2] = local_78.z;
- local_60[2][3] = param_1->mPosition.z;
- p_plane[param_0->mPlaneType](local_60, fVar1, fVar2);
- MTXConcat(param_0->mPosCamMtx, local_60, local_60);
- GXLoadPosMtxImm(local_60, 0);
- p_prj[param_0->mPrjType](param_0, local_60);
- GXCallDisplayList(p_dl[param_0->mDLType], sizeof(jpa_dl));
+ axisZ.normalize();
+ ptcl->mBaseAxis.cross(axisY, axisZ);
+ ptcl->mBaseAxis.normalize();
+ Mtx posMtx;
+ f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
+ f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
+ posMtx[0][0] = ptcl->mBaseAxis.x;
+ posMtx[0][1] = axisY.x;
+ posMtx[0][2] = axisZ.x;
+ posMtx[0][3] = ptcl->mPosition.x;
+ posMtx[1][0] = ptcl->mBaseAxis.y;
+ posMtx[1][1] = axisY.y;
+ posMtx[1][2] = axisZ.y;
+ posMtx[1][3] = ptcl->mPosition.y;
+ posMtx[2][0] = ptcl->mBaseAxis.z;
+ posMtx[2][1] = axisY.z;
+ posMtx[2][2] = axisZ.z;
+ posMtx[2][3] = ptcl->mPosition.z;
+ p_plane[work->mPlaneType](posMtx, scaleX, scaleY);
+ dusk::frame_interp::record_final_mtx(posMtx, ptcl);
}
-void JPADrawRotDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
- if (param_1->checkStatus(JPAPtclStts_Invisible)) {
+void JPAInterpRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ f32 sinRot = JMASSin(ptcl->mRotateAngle);
+ f32 cosRot = JMASCos(ptcl->mRotateAngle);
+ JGeometry::TVec3 axisY;
+ JGeometry::TVec3 axisZ;
+ p_direction[work->mDirType](work, ptcl, &axisY);
+
+ if (axisY.isZero()) {
+ return;
+ }
+
+ axisY.normalize();
+ axisZ.cross(ptcl->mBaseAxis, axisY);
+
+ if (axisZ.isZero()) {
+ return;
+ }
+
+ axisZ.normalize();
+ ptcl->mBaseAxis.cross(axisY, axisZ);
+ ptcl->mBaseAxis.normalize();
+ f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
+ f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
+ Mtx mtx1;
+ Mtx mtx2;
+ p_rot[work->mRotType](sinRot, cosRot, mtx1);
+ p_plane[work->mPlaneType](mtx1, scaleX, scaleY);
+ mtx2[0][0] = ptcl->mBaseAxis.x;
+ mtx2[0][1] = axisY.x;
+ mtx2[0][2] = axisZ.x;
+ mtx2[0][3] = ptcl->mPosition.x;
+ mtx2[1][0] = ptcl->mBaseAxis.y;
+ mtx2[1][1] = axisY.y;
+ mtx2[1][2] = axisZ.y;
+ mtx2[1][3] = ptcl->mPosition.y;
+ mtx2[2][0] = ptcl->mBaseAxis.z;
+ mtx2[2][1] = axisY.z;
+ mtx2[2][2] = axisZ.z;
+ mtx2[2][3] = ptcl->mPosition.z;
+ MTXConcat(mtx2, mtx1, mtx1);
+ dusk::frame_interp::record_final_mtx(mtx1, ptcl);
+}
+#endif
+
+void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
ZoneScoped;
- f32 sinRot = JMASSin(param_1->mRotateAngle);
- f32 cosRot = JMASCos(param_1->mRotateAngle);
- JGeometry::TVec3 local_6c;
- JGeometry::TVec3 local_78;
- p_direction[param_0->mDirType](param_0, param_1, &local_6c);
+ Mtx posMtx;
+#if TARGET_PC
+ if (!dusk::frame_interp::lookup_replacement(ptcl, posMtx))
+#endif
+ {
+ JGeometry::TVec3 axisY;
+ JGeometry::TVec3 axisZ;
+ p_direction[work->mDirType](work, ptcl, &axisY);
- if (local_6c.isZero()) {
+ if (axisY.isZero()) {
+ return;
+ }
+
+ axisY.normalize();
+ axisZ.cross(ptcl->mBaseAxis, axisY);
+
+ if (axisZ.isZero()) {
+ return;
+ }
+
+ axisZ.normalize();
+ ptcl->mBaseAxis.cross(axisY, axisZ);
+ ptcl->mBaseAxis.normalize();
+ f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
+ f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
+ posMtx[0][0] = ptcl->mBaseAxis.x;
+ posMtx[0][1] = axisY.x;
+ posMtx[0][2] = axisZ.x;
+ posMtx[0][3] = ptcl->mPosition.x;
+ posMtx[1][0] = ptcl->mBaseAxis.y;
+ posMtx[1][1] = axisY.y;
+ posMtx[1][2] = axisZ.y;
+ posMtx[1][3] = ptcl->mPosition.y;
+ posMtx[2][0] = ptcl->mBaseAxis.z;
+ posMtx[2][1] = axisY.z;
+ posMtx[2][2] = axisZ.z;
+ posMtx[2][3] = ptcl->mPosition.z;
+ p_plane[work->mPlaneType](posMtx, scaleX, scaleY);
+ }
+
+ MTXConcat(work->mPosCamMtx, posMtx, posMtx);
+ GXLoadPosMtxImm(posMtx, GX_PNMTX0);
+ p_prj[work->mPrjType](work, posMtx);
+ GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl));
+}
+
+void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) {
+ if (ptcl->checkStatus(JPAPtclStts_Invisible)) {
return;
}
- local_6c.normalize();
- local_78.cross(param_1->mBaseAxis, local_6c);
+ ZoneScoped;
- if (local_78.isZero()) {
- return;
+ Mtx mtx1;
+ Mtx mtx2;
+#if TARGET_PC
+ if (!dusk::frame_interp::lookup_replacement(ptcl, mtx1))
+#endif
+ {
+ f32 sinRot = JMASSin(ptcl->mRotateAngle);
+ f32 cosRot = JMASCos(ptcl->mRotateAngle);
+ JGeometry::TVec3 axisY;
+ JGeometry::TVec3 axisZ;
+ p_direction[work->mDirType](work, ptcl, &axisY);
+
+ if (axisY.isZero()) {
+ return;
+ }
+
+ axisY.normalize();
+ axisZ.cross(ptcl->mBaseAxis, axisY);
+
+ if (axisZ.isZero()) {
+ return;
+ }
+
+ axisZ.normalize();
+ ptcl->mBaseAxis.cross(axisY, axisZ);
+ ptcl->mBaseAxis.normalize();
+ f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX;
+ f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY;
+ p_rot[work->mRotType](sinRot, cosRot, mtx1);
+ p_plane[work->mPlaneType](mtx1, scaleX, scaleY);
+ mtx2[0][0] = ptcl->mBaseAxis.x;
+ mtx2[0][1] = axisY.x;
+ mtx2[0][2] = axisZ.x;
+ mtx2[0][3] = ptcl->mPosition.x;
+ mtx2[1][0] = ptcl->mBaseAxis.y;
+ mtx2[1][1] = axisY.y;
+ mtx2[1][2] = axisZ.y;
+ mtx2[1][3] = ptcl->mPosition.y;
+ mtx2[2][0] = ptcl->mBaseAxis.z;
+ mtx2[2][1] = axisY.z;
+ mtx2[2][2] = axisZ.z;
+ mtx2[2][3] = ptcl->mPosition.z;
+ MTXConcat(mtx2, mtx1, mtx1);
}
-
- local_78.normalize();
- param_1->mBaseAxis.cross(local_6c, local_78);
- param_1->mBaseAxis.normalize();
- f32 particleX = param_0->mGlobalPtclScl.x * param_1->mParticleScaleX;
- f32 particleY = param_0->mGlobalPtclScl.y * param_1->mParticleScaleY;
- Mtx auStack_80;
- Mtx local_60;
- p_rot[param_0->mRotType](sinRot, cosRot, auStack_80);
- p_plane[param_0->mPlaneType](auStack_80, particleX, particleY);
- local_60[0][0] = param_1->mBaseAxis.x;
- local_60[0][1] = local_6c.x;
- local_60[0][2] = local_78.x;
- local_60[0][3] = param_1->mPosition.x;
- local_60[1][0] = param_1->mBaseAxis.y;
- local_60[1][1] = local_6c.y;
- local_60[1][2] = local_78.y;
- local_60[1][3] = param_1->mPosition.y;
- local_60[2][0] = param_1->mBaseAxis.z;
- local_60[2][1] = local_6c.z;
- local_60[2][2] = local_78.z;
- local_60[2][3] = param_1->mPosition.z;
- MTXConcat(local_60, auStack_80, auStack_80);
- MTXConcat(param_0->mPosCamMtx, auStack_80, local_60);
- GXLoadPosMtxImm(local_60, 0);
- p_prj[param_0->mPrjType](param_0, local_60);
- GXCallDisplayList(p_dl[param_0->mDLType], sizeof(jpa_dl));
+ MTXConcat(work->mPosCamMtx, mtx1, mtx2);
+ GXLoadPosMtxImm(mtx2, GX_PNMTX0);
+ p_prj[work->mPrjType](work, mtx2);
+ GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl));
}
void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) {
diff --git a/libs/JSystem/src/JParticle/JPAParticle.cpp b/libs/JSystem/src/JParticle/JPAParticle.cpp
index a9294e0b4e..d3490ab294 100644
--- a/libs/JSystem/src/JParticle/JPAParticle.cpp
+++ b/libs/JSystem/src/JParticle/JPAParticle.cpp
@@ -204,6 +204,28 @@ void JPABaseParticle::init_c(JPAEmitterWorkData* work, JPABaseParticle* parent)
mTexAnmIdx = 0;
}
+#if TARGET_PC
+void JPABaseParticle::interp(JPAEmitterWorkData* work, void const* drawFunc) {
+ static bool enable = false;
+ if (!enable)
+ return;
+
+ // don't interpolate the first frame
+ if (mAge == 0)
+ return;
+
+ if (drawFunc == JPADrawBillboard) {
+ JPAInterpBillboard(work, this);
+ } else if (drawFunc == JPADrawRotBillboard) {
+ JPAInterpRotBillboard(work, this);
+ } else if (drawFunc == JPADrawDirection) {
+ JPAInterpDirection(work, this);
+ } else if (drawFunc == JPADrawRotDirection) {
+ JPAInterpRotDirection(work, this);
+ }
+}
+#endif
+
bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) {
if (++mAge >= mLifeTime) {
return true;
@@ -247,6 +269,17 @@ bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) {
mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y,
mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z);
+#if TARGET_PC
+ JPABaseShape* pBsp = work->mpRes->getBsp();
+ work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX();
+ work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY();
+ work->mDirType = pBsp->getDirType();
+ work->mRotType = pBsp->getRotType();
+ work->mDLType = pBsp->getType() == 4 || pBsp->getType() == 8;
+ work->mPlaneType = work->mDLType ? 2 : pBsp->getBasePlaneType();
+ interp(work, (void const*)work->mpRes->mpDrawParticleFuncList[0]);
+#endif
+
return false;
}
@@ -289,6 +322,23 @@ bool JPABaseParticle::calc_c(JPAEmitterWorkData* work) {
mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y,
mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z);
+#if TARGET_PC
+ JPABaseShape* pBsp = work->mpRes->getBsp();
+ JPAChildShape* pCsp = work->mpRes->getCsp();
+ if (pCsp->isScaleInherited()) {
+ work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX();
+ work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY();
+ } else {
+ work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pCsp->getScaleX();
+ work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pCsp->getScaleY();
+ }
+ work->mDirType = pCsp->getDirType();
+ work->mRotType = pCsp->getRotType();
+ work->mDLType = pCsp->getType() == 4 || pCsp->getType() == 8;
+ work->mPlaneType = work->mDLType ? 2 : pCsp->getBasePlaneType();
+ interp(work, (void const*)work->mpRes->mpDrawParticleChildFuncList[0]);
+#endif
+
return false;
}
diff --git a/libs/JSystem/src/JUtility/JUTFader.cpp b/libs/JSystem/src/JUtility/JUTFader.cpp
index 599ae22720..e7d33454b7 100644
--- a/libs/JSystem/src/JUtility/JUTFader.cpp
+++ b/libs/JSystem/src/JUtility/JUTFader.cpp
@@ -8,6 +8,10 @@
#include "JSystem/JUtility/JUTFader.h"
#include "JSystem/J2DGraph/J2DOrthoGraph.h"
+#ifdef TARGET_PC
+#include
+#endif
+
JUTFader::JUTFader(int x, int y, int width, int height, JUtility::TColor pColor)
: mColor(pColor), mBox(x, y, x + width, y + height) {
mStatus = None;
@@ -63,14 +67,24 @@ void JUTFader::advance() {
void JUTFader::control() {
advance();
-#ifndef TARGET_PC
- // FRAME INTERP NOTE: Draw is called by JFWDisplay when interpolation is active
draw();
-#endif
}
void JUTFader::draw() {
if (mColor.a != 0) {
+#ifdef TARGET_PC
+ if (dusk::frame_interp::is_enabled() && mDuration != 0) {
+ const auto step = dusk::frame_interp::get_interpolation_step();
+ const auto progress = static_cast(mTimer) / static_cast(mDuration);
+ const auto timer = mTimer - 1 + step + progress;
+ auto alpha = timer / mDuration;
+ if (mStatus == FadeIn) {
+ alpha = 1.0f - alpha;
+ }
+ alpha = std::clamp(alpha, 0.0f, 1.0f);
+ mColor.a = static_cast(alpha * 255.0f);
+ }
+#endif
J2DOrthoGraph orthograph;
orthograph.setColor(mColor);
orthograph.fillBox(mBox);
diff --git a/libs/JSystem/src/JUtility/JUTPalette.cpp b/libs/JSystem/src/JUtility/JUTPalette.cpp
index d98bf98c2e..9c8a51432c 100644
--- a/libs/JSystem/src/JUtility/JUTPalette.cpp
+++ b/libs/JSystem/src/JUtility/JUTPalette.cpp
@@ -38,3 +38,9 @@ bool JUTPalette::load() {
return check;
}
+
+#if TARGET_PC
+void JUTPalette::dataUploaded() {
+ GXInitTlutObjData(&mTlutObj, (void*)mColorTable);
+}
+#endif
diff --git a/libs/JSystem/src/JUtility/JUTTexture.cpp b/libs/JSystem/src/JUtility/JUTTexture.cpp
index 060c581e80..1d799c2820 100644
--- a/libs/JSystem/src/JUtility/JUTTexture.cpp
+++ b/libs/JSystem/src/JUtility/JUTTexture.cpp
@@ -27,7 +27,7 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, u8 param_1) {
mTexData = (void*)((intptr_t)mTexInfo + 0x20);
}
- field_0x2c = NULL;
+ mPalette = NULL;
mTlutName = 0;
mWrapS = mTexInfo->wrapS;
mWrapT = mTexInfo->wrapT;
@@ -95,7 +95,7 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, JUTPalette* param_1, GXTlut p
}
mEmbPalette = param_1;
setEmbPaletteDelFlag(false);
- field_0x2c = NULL;
+ mPalette = NULL;
if (param_1 != NULL) {
mTlutName = param_2;
if (param_2 != param_1->getTlutName()) {
@@ -120,11 +120,11 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, JUTPalette* param_1, GXTlut p
void JUTTexture::attachPalette(JUTPalette* param_0) {
if (mTexInfo->indexTexture) {
if (param_0 == NULL && mEmbPalette != NULL) {
- field_0x2c = mEmbPalette;
+ mPalette = mEmbPalette;
} else {
- field_0x2c = param_0;
+ mPalette = param_0;
}
- initTexObj(field_0x2c->getTlutName());
+ initTexObj(mPalette->getTlutName());
}
}
@@ -133,9 +133,9 @@ void JUTTexture::init() {
initTexObj();
} else {
if (mEmbPalette != NULL) {
- field_0x2c = mEmbPalette;
+ mPalette = mEmbPalette;
- initTexObj(field_0x2c->getTlutName());
+ initTexObj(mPalette->getTlutName());
} else {
OS_REPORT("This texture is CI-Format, but EmbPalette is NULL.\n");
}
@@ -179,8 +179,8 @@ void JUTTexture::initTexObj(GXTlut param_0) {
}
void JUTTexture::load(GXTexMapID param_0) {
- if (field_0x2c) {
- field_0x2c->load();
+ if (mPalette) {
+ mPalette->load();
}
GXLoadTexObj(&mTexObj, param_0);
}
diff --git a/platforms/android/README.md b/platforms/android/README.md
index c7ecd684c1..f51b37db73 100644
--- a/platforms/android/README.md
+++ b/platforms/android/README.md
@@ -66,12 +66,11 @@ Output APK:
You can pass command-line args through the activity intent:
```bash
-adb shell am start -n com.twilitrealm.dusk/.DuskActivity \
- --es dusk_args "'/sdcard/Download/The Legend of Zelda: Twilight Princess (USA).iso'"
+adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \
+ --es dusk_args "--backend vulkan"
```
Supported extras:
- `dusk_args`: single shell-like argument string
- `dusk_argv`: string-array argv
-- `dusk_disc`: compatibility shortcut (single ISO path)
diff --git a/platforms/android/app/build.gradle b/platforms/android/app/build.gradle
index 9375d06910..b7775df8e0 100644
--- a/platforms/android/app/build.gradle
+++ b/platforms/android/app/build.gradle
@@ -2,12 +2,22 @@ plugins {
id 'com.android.application'
}
+def duskRepoDir = rootProject.projectDir.parentFile.parentFile
+def duskGeneratedAssetsDir = layout.buildDirectory.dir('generated/assets/dusk').get().asFile
+def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
+ from(new File(duskRepoDir, 'res')) {
+ into 'res'
+ exclude '**/.DS_Store'
+ }
+ into duskGeneratedAssetsDir
+}
+
android {
- namespace 'com.twilitrealm.dusk'
+ namespace 'dev.twilitrealm.dusk'
compileSdk 36
defaultConfig {
- applicationId 'com.twilitrealm.dusk'
+ applicationId 'dev.twilitrealm.dusk'
minSdk 26
targetSdk 36
versionCode 1
@@ -27,7 +37,7 @@ android {
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
- assets.srcDirs = ['../../assets']
+ assets.srcDirs = [duskGeneratedAssetsDir]
}
}
@@ -48,3 +58,10 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
+
+tasks.configureEach { task ->
+ if ((task.name.startsWith('merge') && task.name.endsWith('Assets')) ||
+ task.name.toLowerCase().contains('lint')) {
+ task.dependsOn(syncDuskAssets)
+ }
+}
diff --git a/platforms/android/app/proguard-rules.pro b/platforms/android/app/proguard-rules.pro
index 952161ca8b..72b7ca16fa 100644
--- a/platforms/android/app/proguard-rules.pro
+++ b/platforms/android/app/proguard-rules.pro
@@ -1,2 +1,4 @@
# Keep SDL activity and related JNI bridge methods.
-keep class org.libsdl.app.** { *; }
+-keep class dev.twilitrealm.dusk.DuskHttpClient { *; }
+-keep class dev.twilitrealm.dusk.DuskHttpClient$Response { *; }
diff --git a/platforms/android/app/src/main/AndroidManifest.xml b/platforms/android/app/src/main/AndroidManifest.xml
index e8c2bb29a8..f5a0365856 100644
--- a/platforms/android/app/src/main/AndroidManifest.xml
+++ b/platforms/android/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
+
+
+
+
+
+
out = new ArrayList<>();
StringBuilder current = new StringBuilder();
@@ -61,18 +71,46 @@ public class DuskActivity extends SDLActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ hideSystemBars();
+ }
+ @Override
+ protected void onResume() {
+ super.onResume();
+ hideSystemBars();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus) {
+ hideSystemBars();
+ }
+ }
+
+ private void hideSystemBars() {
+ Window window = getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
- }else {
- View decorView = getWindow().getDecorView();
- // Hide the status bar.
- int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
+ window.setDecorFitsSystemWindows(false);
+ WindowInsetsController ctrl = window.getDecorView().getWindowInsetsController();
+ if (ctrl != null) {
+ ctrl.setSystemBarsBehavior(
+ WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+ ctrl.hide(WindowInsets.Type.systemBars());
+ }
+ } else {
+ View decorView = window.getDecorView();
+ int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
- // Remember that you should never show the action bar if the
- // status bar is hidden, so hide that too if necessary.
ActionBar actionBar = getActionBar();
- actionBar.hide();
+ if (actionBar != null) {
+ actionBar.hide();
+ }
}
}
@@ -100,12 +138,96 @@ public class DuskActivity extends SDLActivity {
return splitArgs(trimmed);
}
}
-
- String discPath = intent.getStringExtra("dusk_disc");
- if (discPath != null && !discPath.isEmpty()) {
- return new String[] { discPath };
- }
}
return new String[0];
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_OK) {
+ persistUriPermissions(data);
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ private void persistUriPermissions(Intent data) {
+ if (data == null) {
+ return;
+ }
+
+ int permissionFlags =
+ data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ if (permissionFlags == 0) {
+ return;
+ }
+
+ Uri uri = data.getData();
+ if (uri != null) {
+ persistUriPermission(uri, permissionFlags);
+ }
+
+ ClipData clipData = data.getClipData();
+ if (clipData == null) {
+ return;
+ }
+ for (int i = 0; i < clipData.getItemCount(); ++i) {
+ Uri itemUri = clipData.getItemAt(i).getUri();
+ if (itemUri != null) {
+ persistUriPermission(itemUri, permissionFlags);
+ }
+ }
+ }
+
+ private void persistUriPermission(Uri uri, int permissionFlags) {
+ if ((permissionFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ persistUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, "read");
+ }
+ if ((permissionFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ persistUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, "write");
+ }
+ }
+
+ private void persistUriPermission(Uri uri, int permissionFlag, String permissionName) {
+ try {
+ getContentResolver().takePersistableUriPermission(uri, permissionFlag);
+ } catch (SecurityException | IllegalArgumentException e) {
+ Log.w(TAG, "Unable to persist " + permissionName + " URI permission for " + uri, e);
+ }
+ }
+
+ public String getDisplayNameForUri(String uriString) {
+ if (uriString == null || uriString.isEmpty()) {
+ return "";
+ }
+
+ Uri uri = Uri.parse(uriString);
+ if ("content".equals(uri.getScheme())) {
+ try (Cursor cursor = getContentResolver().query(
+ uri, new String[] { OpenableColumns.DISPLAY_NAME }, null, null, null))
+ {
+ if (cursor != null && cursor.moveToFirst()) {
+ int displayNameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ if (displayNameColumn >= 0) {
+ String displayName = cursor.getString(displayNameColumn);
+ if (displayName != null && !displayName.isEmpty()) {
+ return displayName;
+ }
+ }
+ }
+ } catch (SecurityException | IllegalArgumentException e) {
+ Log.w(TAG, "Unable to query display name for " + uri, e);
+ }
+ } else if ("file".equals(uri.getScheme())) {
+ String path = uri.getPath();
+ if (path != null && !path.isEmpty()) {
+ String name = new File(path).getName();
+ if (!name.isEmpty()) {
+ return name;
+ }
+ }
+ }
+
+ String lastSegment = uri.getLastPathSegment();
+ return lastSegment != null ? lastSegment : "";
+ }
}
diff --git a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskDocumentsProvider.java b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskDocumentsProvider.java
new file mode 100644
index 0000000000..79fa8894d0
--- /dev/null
+++ b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskDocumentsProvider.java
@@ -0,0 +1,413 @@
+package dev.twilitrealm.dusk;
+
+import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
+import android.webkit.MimeTypeMap;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class DuskDocumentsProvider extends DocumentsProvider {
+ public static final String AUTHORITY = "dev.twilitrealm.dusk.documents";
+
+ private static final String ROOT_ID = "dusk";
+ private static final String ROOT_DOCUMENT_ID = "root";
+ private static final String DIRECTORY_MIME_TYPE = Document.MIME_TYPE_DIR;
+
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+ Root.COLUMN_ROOT_ID,
+ Root.COLUMN_FLAGS,
+ Root.COLUMN_TITLE,
+ Root.COLUMN_DOCUMENT_ID,
+ Root.COLUMN_ICON,
+ Root.COLUMN_AVAILABLE_BYTES,
+ Root.COLUMN_SUMMARY
+ };
+
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_FLAGS,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_LAST_MODIFIED,
+ Document.COLUMN_SIZE
+ };
+
+ @Override
+ public boolean onCreate() {
+ ensureUserDirectories();
+ return true;
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
+ final File root = getRootDirectory();
+ final MatrixCursor.RowBuilder row = result.newRow();
+
+ row.add(Root.COLUMN_ROOT_ID, ROOT_ID);
+ row.add(Root.COLUMN_FLAGS,
+ Root.FLAG_LOCAL_ONLY |
+ Root.FLAG_SUPPORTS_CREATE |
+ Root.FLAG_SUPPORTS_IS_CHILD);
+ row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
+ row.add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID);
+ row.add(Root.COLUMN_ICON, R.mipmap.icon);
+ row.add(Root.COLUMN_AVAILABLE_BYTES, root.getFreeSpace());
+ row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.documents_provider_summary));
+
+ return result;
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+ includeDocument(result, documentId, getFileForDocumentId(documentId));
+ return result;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
+ throws FileNotFoundException
+ {
+ return queryChildDocumentsInternal(parentDocumentId, projection);
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentDocumentId, String[] projection, Bundle queryArgs)
+ throws FileNotFoundException
+ {
+ return queryChildDocumentsInternal(parentDocumentId, projection);
+ }
+
+ private Cursor queryChildDocumentsInternal(String parentDocumentId, String[] projection)
+ throws FileNotFoundException
+ {
+ final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+ final File parent = getFileForDocumentId(parentDocumentId);
+ final File[] files = parent.listFiles();
+ result.setNotificationUri(getContext().getContentResolver(), getChildDocumentsUri(parentDocumentId));
+
+ if (files == null) {
+ return result;
+ }
+
+ for (File file : files) {
+ includeDocument(result, getDocumentIdForFile(file), file);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ try {
+ final File parent = getFileForDocumentId(parentDocumentId);
+ final File child = getFileForDocumentId(documentId);
+ return isInside(parent, child);
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String createDocument(String parentDocumentId, String mimeType, String displayName)
+ throws FileNotFoundException
+ {
+ final File parent = getFileForDocumentId(parentDocumentId);
+ if (!parent.isDirectory()) {
+ throw new FileNotFoundException("Parent is not a directory: " + parentDocumentId);
+ }
+
+ final String safeDisplayName = sanitizeDisplayName(displayName);
+ final File file = buildUniqueFile(parent, safeDisplayName);
+ final boolean created;
+ if (DIRECTORY_MIME_TYPE.equals(mimeType)) {
+ created = file.mkdir();
+ } else {
+ try {
+ created = file.createNewFile();
+ } catch (IOException e) {
+ throw asFileNotFound("Unable to create document", e);
+ }
+ }
+
+ if (!created) {
+ throw new FileNotFoundException("Unable to create document: " + displayName);
+ }
+
+ notifyChildrenChanged(parentDocumentId);
+ return getDocumentIdForFile(file);
+ }
+
+ @Override
+ public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
+ final File file = getFileForDocumentId(documentId);
+ if (ROOT_DOCUMENT_ID.equals(documentId)) {
+ throw new FileNotFoundException("Cannot rename root document");
+ }
+
+ final File target = buildUniqueFile(file.getParentFile(), sanitizeDisplayName(displayName));
+ final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
+ if (!file.renameTo(target)) {
+ throw new FileNotFoundException("Unable to rename document: " + documentId);
+ }
+ notifyDocumentChanged(documentId);
+ notifyDocumentChanged(getDocumentIdForFile(target));
+ notifyChildrenChanged(parentDocumentId);
+ return getDocumentIdForFile(target);
+ }
+
+ @Override
+ public void deleteDocument(String documentId) throws FileNotFoundException {
+ if (ROOT_DOCUMENT_ID.equals(documentId)) {
+ throw new FileNotFoundException("Cannot delete root document");
+ }
+
+ final File file = getFileForDocumentId(documentId);
+ final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
+ deleteRecursively(file);
+ notifyDocumentChanged(documentId);
+ notifyChildrenChanged(parentDocumentId);
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal)
+ throws FileNotFoundException
+ {
+ return ParcelFileDescriptor.open(getFileForDocumentId(documentId), modeToParcelMode(mode));
+ }
+
+ @Override
+ public AssetFileDescriptor openDocumentThumbnail(String documentId, android.graphics.Point sizeHint,
+ CancellationSignal signal) throws FileNotFoundException
+ {
+ throw new FileNotFoundException("Thumbnails are not supported");
+ }
+
+ private void includeDocument(MatrixCursor result, String documentId, File file) throws FileNotFoundException {
+ final MatrixCursor.RowBuilder row = result.newRow();
+ final boolean isDirectory = file.isDirectory();
+ final String displayName = ROOT_DOCUMENT_ID.equals(documentId)
+ ? getContext().getString(R.string.documents_provider_root_name)
+ : file.getName();
+
+ int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
+ if (isDirectory) {
+ flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
+ } else if (file.canWrite()) {
+ flags |= Document.FLAG_SUPPORTS_WRITE;
+ }
+ if (ROOT_DOCUMENT_ID.equals(documentId)) {
+ flags &= ~(Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME);
+ }
+
+ row.add(Document.COLUMN_DOCUMENT_ID, documentId);
+ row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+ row.add(Document.COLUMN_FLAGS, flags);
+ row.add(Document.COLUMN_MIME_TYPE, isDirectory ? DIRECTORY_MIME_TYPE : getMimeType(file));
+ row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
+ row.add(Document.COLUMN_SIZE, isDirectory ? null : file.length());
+ }
+
+ private File getRootDirectory() throws FileNotFoundException {
+ final File root = getContext().getFilesDir();
+ if (root == null) {
+ throw new FileNotFoundException("Dusk files directory is unavailable");
+ }
+ return root;
+ }
+
+ private File getFileForDocumentId(String documentId) throws FileNotFoundException {
+ final File root = getRootDirectory();
+ if (ROOT_DOCUMENT_ID.equals(documentId)) {
+ return root;
+ }
+ if (!documentId.startsWith(ROOT_DOCUMENT_ID + "/")) {
+ throw new FileNotFoundException("Invalid document id: " + documentId);
+ }
+
+ final String relativePath = documentId.substring(ROOT_DOCUMENT_ID.length() + 1);
+ final File file = new File(root, relativePath);
+ if (!isInside(root, file)) {
+ throw new FileNotFoundException("Document escapes Dusk files directory: " + documentId);
+ }
+ if (!file.exists()) {
+ throw new FileNotFoundException("Document does not exist: " + documentId);
+ }
+ return file;
+ }
+
+ private String getDocumentIdForFile(File file) throws FileNotFoundException {
+ final File root = getRootDirectory();
+ if (sameFile(root, file)) {
+ return ROOT_DOCUMENT_ID;
+ }
+ if (!isInside(root, file)) {
+ throw new FileNotFoundException("File escapes Dusk files directory: " + file);
+ }
+
+ final String rootPath = canonicalPath(root);
+ final String filePath = canonicalPath(file);
+ return ROOT_DOCUMENT_ID + "/" + filePath.substring(rootPath.length() + 1);
+ }
+
+ private void ensureUserDirectories() {
+ final File root = getContext().getFilesDir();
+ if (root == null) {
+ return;
+ }
+ new File(root, "texture_replacements").mkdirs();
+ new File(root, "USA/Card A").mkdirs();
+ new File(root, "EUR/Card A").mkdirs();
+ }
+
+ private static String[] resolveRootProjection(String[] projection) {
+ return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
+ }
+
+ private static String[] resolveDocumentProjection(String[] projection) {
+ return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
+ }
+
+ private static String sanitizeDisplayName(String displayName) throws FileNotFoundException {
+ if (displayName == null) {
+ throw new FileNotFoundException("Document name is empty");
+ }
+
+ final String sanitized = displayName.trim();
+ if (sanitized.isEmpty() || ".".equals(sanitized) || "..".equals(sanitized) ||
+ sanitized.contains("/") || sanitized.contains("\\"))
+ {
+ throw new FileNotFoundException("Invalid document name: " + displayName);
+ }
+ return sanitized;
+ }
+
+ private static File buildUniqueFile(File parent, String displayName) {
+ File file = new File(parent, displayName);
+ if (!file.exists()) {
+ return file;
+ }
+
+ final int dot = displayName.lastIndexOf('.');
+ final String baseName = dot > 0 ? displayName.substring(0, dot) : displayName;
+ final String extension = dot > 0 ? displayName.substring(dot) : "";
+ for (int i = 1; i < 100; ++i) {
+ file = new File(parent, baseName + " (" + i + ")" + extension);
+ if (!file.exists()) {
+ return file;
+ }
+ }
+ return new File(parent, baseName + " (" + System.currentTimeMillis() + ")" + extension);
+ }
+
+ private static int modeToParcelMode(String mode) {
+ if ("r".equals(mode)) {
+ return ParcelFileDescriptor.MODE_READ_ONLY;
+ }
+ if ("w".equals(mode) || "wt".equals(mode)) {
+ return ParcelFileDescriptor.MODE_WRITE_ONLY |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE;
+ }
+ if ("wa".equals(mode)) {
+ return ParcelFileDescriptor.MODE_WRITE_ONLY |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_APPEND;
+ }
+ if ("rw".equals(mode)) {
+ return ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE;
+ }
+ if ("rwt".equals(mode)) {
+ return ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE;
+ }
+ return ParcelFileDescriptor.MODE_READ_ONLY;
+ }
+
+ private static String getMimeType(File file) {
+ final int dot = file.getName().lastIndexOf('.');
+ if (dot >= 0) {
+ final String extension = file.getName().substring(dot + 1).toLowerCase();
+ final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mimeType != null) {
+ return mimeType;
+ }
+ }
+ return "application/octet-stream";
+ }
+
+ private Uri getChildDocumentsUri(String parentDocumentId) {
+ return DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId);
+ }
+
+ private void notifyChildrenChanged(String parentDocumentId) {
+ final ContentResolver resolver = getContext().getContentResolver();
+ resolver.notifyChange(getChildDocumentsUri(parentDocumentId), null, false);
+ }
+
+ private void notifyDocumentChanged(String documentId) {
+ final ContentResolver resolver = getContext().getContentResolver();
+ resolver.notifyChange(DocumentsContract.buildDocumentUri(AUTHORITY, documentId), null, false);
+ }
+
+ private static void deleteRecursively(File file) throws FileNotFoundException {
+ if (file.isDirectory()) {
+ final File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ deleteRecursively(child);
+ }
+ }
+ }
+ if (!file.delete()) {
+ throw new FileNotFoundException("Unable to delete document: " + file);
+ }
+ }
+
+ private static boolean isInside(File parent, File child) {
+ try {
+ final String parentPath = canonicalPath(parent);
+ final String childPath = canonicalPath(child);
+ return childPath.equals(parentPath) || childPath.startsWith(parentPath + File.separator);
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+ }
+
+ private static boolean sameFile(File a, File b) {
+ try {
+ return canonicalPath(a).equals(canonicalPath(b));
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+ }
+
+ private static String canonicalPath(File file) throws FileNotFoundException {
+ try {
+ return file.getCanonicalPath();
+ } catch (IOException e) {
+ throw asFileNotFound("Unable to resolve path", e);
+ }
+ }
+
+ private static FileNotFoundException asFileNotFound(String message, IOException cause) {
+ final FileNotFoundException exception = new FileNotFoundException(message + ": " + cause.getMessage());
+ exception.initCause(cause);
+ return exception;
+ }
+}
diff --git a/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskHttpClient.java b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskHttpClient.java
new file mode 100644
index 0000000000..be160d6a2d
--- /dev/null
+++ b/platforms/android/app/src/main/java/com/twilitrealm/dusk/DuskHttpClient.java
@@ -0,0 +1,237 @@
+package dev.twilitrealm.dusk;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HttpsURLConnection;
+
+public final class DuskHttpClient {
+ public static final int ERROR_NONE = 0;
+ public static final int ERROR_INVALID_URL = 1;
+ public static final int ERROR_UNSUPPORTED_SCHEME = 2;
+ public static final int ERROR_TIMEOUT = 3;
+ public static final int ERROR_TOO_LARGE = 4;
+ public static final int ERROR_NETWORK = 5;
+
+ private static final int MAX_REDIRECTS = 5;
+
+ public static final class Response {
+ public int error;
+ public String message;
+ public int statusCode;
+ public String[] headerNames;
+ public String[] headerValues;
+ public byte[] body;
+
+ Response(int error, String message, int statusCode, String[] headerNames,
+ String[] headerValues, byte[] body) {
+ this.error = error;
+ this.message = message;
+ this.statusCode = statusCode;
+ this.headerNames = headerNames != null ? headerNames : new String[0];
+ this.headerValues = headerValues != null ? headerValues : new String[0];
+ this.body = body != null ? body : new byte[0];
+ }
+ }
+
+ private DuskHttpClient() {
+ }
+
+ public static Response get(String url, String[] headerNames, String[] headerValues,
+ int timeoutMs, long maxBodyBytes) {
+ if (url == null || url.isEmpty()) {
+ return fail(ERROR_INVALID_URL, "URL is empty");
+ }
+
+ try {
+ URL currentUrl = new URL(url);
+ if (!isHttps(currentUrl)) {
+ return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported");
+ }
+
+ for (int redirect = 0; redirect <= MAX_REDIRECTS; ++redirect) {
+ HttpsURLConnection connection =
+ (HttpsURLConnection) currentUrl.openConnection();
+ try {
+ connection.setRequestMethod("GET");
+ connection.setConnectTimeout(timeoutMs);
+ connection.setReadTimeout(timeoutMs);
+ connection.setUseCaches(false);
+ connection.setInstanceFollowRedirects(false);
+ applyHeaders(connection, headerNames, headerValues);
+
+ int statusCode = connection.getResponseCode();
+ if (isRedirect(statusCode)) {
+ String location = connection.getHeaderField("Location");
+ if (location == null || location.isEmpty()) {
+ return fail(ERROR_NETWORK, "Redirect response did not include Location",
+ statusCode, connection, new byte[0]);
+ }
+
+ URL nextUrl = new URL(currentUrl, location);
+ if (!isHttps(nextUrl)) {
+ return fail(ERROR_UNSUPPORTED_SCHEME,
+ "Only https:// redirects are supported", statusCode,
+ connection, new byte[0]);
+ }
+ currentUrl = nextUrl;
+ continue;
+ }
+
+ byte[] body = readBody(connection, statusCode, maxBodyBytes);
+ return success(statusCode, connection, body);
+ } catch (ResponseTooLargeException e) {
+ return fail(ERROR_TOO_LARGE, "Response body exceeded the configured limit",
+ safeStatusCode(connection), connection, e.partialBody);
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ return fail(ERROR_NETWORK, "Too many redirects");
+ } catch (MalformedURLException e) {
+ return fail(ERROR_INVALID_URL, "Failed to parse URL");
+ } catch (SocketTimeoutException e) {
+ return fail(ERROR_TIMEOUT, "Request timed out");
+ } catch (IOException e) {
+ String message = e.getMessage();
+ return fail(ERROR_NETWORK, message != null ? message : e.toString());
+ } catch (ClassCastException e) {
+ return fail(ERROR_UNSUPPORTED_SCHEME, "Only https:// URLs are supported");
+ }
+ }
+
+ private static void applyHeaders(HttpsURLConnection connection, String[] names,
+ String[] values) {
+ if (names == null || values == null) {
+ return;
+ }
+
+ int count = Math.min(names.length, values.length);
+ for (int i = 0; i < count; ++i) {
+ if (names[i] != null && values[i] != null) {
+ connection.setRequestProperty(names[i], values[i]);
+ }
+ }
+ }
+
+ private static boolean isHttps(URL url) {
+ return "https".equalsIgnoreCase(url.getProtocol());
+ }
+
+ private static boolean isRedirect(int statusCode) {
+ return statusCode == HttpURLConnection.HTTP_MOVED_PERM ||
+ statusCode == HttpURLConnection.HTTP_MOVED_TEMP ||
+ statusCode == HttpURLConnection.HTTP_SEE_OTHER ||
+ statusCode == 307 ||
+ statusCode == 308;
+ }
+
+ private static byte[] readBody(HttpsURLConnection connection, int statusCode,
+ long maxBodyBytes) throws IOException,
+ ResponseTooLargeException {
+ InputStream stream = statusCode >= HttpURLConnection.HTTP_BAD_REQUEST ?
+ connection.getErrorStream() : connection.getInputStream();
+ if (stream == null) {
+ return new byte[0];
+ }
+
+ try (InputStream bodyStream = stream;
+ ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[8192];
+ long total = 0;
+ while (true) {
+ int read = bodyStream.read(buffer);
+ if (read < 0) {
+ return out.toByteArray();
+ }
+ if (read == 0) {
+ continue;
+ }
+ if (read > maxBodyBytes || total > maxBodyBytes - read) {
+ throw new ResponseTooLargeException(out.toByteArray());
+ }
+ out.write(buffer, 0, read);
+ total += read;
+ }
+ }
+ }
+
+ private static int safeStatusCode(HttpsURLConnection connection) {
+ try {
+ return connection.getResponseCode();
+ } catch (IOException e) {
+ return 0;
+ }
+ }
+
+ private static Response success(int statusCode, HttpsURLConnection connection, byte[] body) {
+ HeaderLists headers = readHeaders(connection);
+ return new Response(ERROR_NONE, "", statusCode, headers.names, headers.values, body);
+ }
+
+ private static Response fail(int error, String message) {
+ return new Response(error, message, 0, null, null, null);
+ }
+
+ private static Response fail(int error, String message, int statusCode,
+ HttpsURLConnection connection, byte[] body) {
+ HeaderLists headers = readHeaders(connection);
+ return new Response(error, message, statusCode, headers.names, headers.values, body);
+ }
+
+ private static HeaderLists readHeaders(HttpsURLConnection connection) {
+ List names = new ArrayList<>();
+ List values = new ArrayList<>();
+
+ Map> headerFields = connection.getHeaderFields();
+ if (headerFields == null) {
+ return new HeaderLists(new String[0], new String[0]);
+ }
+
+ for (Map.Entry> entry : headerFields.entrySet()) {
+ String name = entry.getKey();
+ if (name == null) {
+ continue;
+ }
+ List entryValues = entry.getValue();
+ if (entryValues == null || entryValues.isEmpty()) {
+ names.add(name);
+ values.add("");
+ continue;
+ }
+ for (String value : entryValues) {
+ names.add(name);
+ values.add(value != null ? value : "");
+ }
+ }
+
+ return new HeaderLists(names.toArray(new String[0]), values.toArray(new String[0]));
+ }
+
+ private static final class HeaderLists {
+ final String[] names;
+ final String[] values;
+
+ HeaderLists(String[] names, String[] values) {
+ this.names = names;
+ this.values = values;
+ }
+ }
+
+ private static final class ResponseTooLargeException extends Exception {
+ final byte[] partialBody;
+
+ ResponseTooLargeException(byte[] partialBody) {
+ this.partialBody = partialBody;
+ }
+ }
+}
diff --git a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
index b91a8211b1..1fb2bfb4a7 100644
--- a/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
+++ b/platforms/android/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
@@ -256,6 +256,7 @@ public class HIDDeviceManager {
0x24c6, // PowerA
0x2c22, // Qanba
0x2dc8, // 8BitDo
+ 0x37d7, // Flydigi
0x9886, // ASTRO Gaming
};
diff --git a/platforms/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/platforms/android/app/src/main/java/org/libsdl/app/SDLActivity.java
index 9548c529c0..42f5a911f7 100644
--- a/platforms/android/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/platforms/android/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 3;
private static final int SDL_MINOR_VERSION = 4;
- private static final int SDL_MICRO_VERSION = 2;
+ private static final int SDL_MICRO_VERSION = 4;
/*
// Display InputType.SOURCE/CLASS of events and devices
//
@@ -2032,7 +2032,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
try {
ParcelFileDescriptor pfd = mSingleton.getContentResolver().openFileDescriptor(Uri.parse(uri), mode);
return pfd != null ? pfd.detachFd() : -1;
- } catch (FileNotFoundException e) {
+ } catch (FileNotFoundException | SecurityException e) {
e.printStackTrace();
return -1;
}
@@ -2227,4 +2227,3 @@ class SDLClipboardHandler implements
SDLActivity.onNativeClipboardChanged();
}
}
-
diff --git a/platforms/android/app/src/main/java/org/libsdl/app/SDLInputConnection.java b/platforms/android/app/src/main/java/org/libsdl/app/SDLInputConnection.java
index 027b8fd3e5..fdc2994c15 100644
--- a/platforms/android/app/src/main/java/org/libsdl/app/SDLInputConnection.java
+++ b/platforms/android/app/src/main/java/org/libsdl/app/SDLInputConnection.java
@@ -65,17 +65,15 @@ class SDLInputConnection extends BaseInputConnection
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
- if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {
- // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
- // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
- if (beforeLength > 0 && afterLength == 0) {
- // backspace(s)
- while (beforeLength-- > 0) {
- nativeGenerateScancodeForUnichar('\b');
- }
- return true;
- }
- }
+ // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
+ // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
+ if (beforeLength > 0 && afterLength == 0) {
+ // backspace(s)
+ while (beforeLength-- > 0) {
+ nativeGenerateScancodeForUnichar('\b');
+ }
+ return true;
+ }
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
return false;
diff --git a/platforms/android/app/src/main/java/org/libsdl/app/SDLSurface.java b/platforms/android/app/src/main/java/org/libsdl/app/SDLSurface.java
index 1579b73345..5ed335ac39 100644
--- a/platforms/android/app/src/main/java/org/libsdl/app/SDLSurface.java
+++ b/platforms/android/app/src/main/java/org/libsdl/app/SDLSurface.java
@@ -35,6 +35,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
+ private static native void auroraNativeSetSurfaceReady(boolean ready);
+
// Sensors
protected SensorManager mSensorManager;
protected Display mDisplay;
@@ -96,6 +98,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v("SDL", "surfaceCreated()");
+ auroraNativeSetSurfaceReady(false);
SDLActivity.onNativeSurfaceCreated();
}
@@ -103,6 +106,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
+ auroraNativeSetSurfaceReady(false);
// Transition to pause, if needed
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
@@ -192,6 +196,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
/* Surface is ready */
mIsSurfaceReady = true;
+ auroraNativeSetSurfaceReady(true);
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
SDLActivity.handleNativeState();
diff --git a/platforms/android/app/src/main/res/values/strings.xml b/platforms/android/app/src/main/res/values/strings.xml
index 79d8812b82..752dae76bf 100644
--- a/platforms/android/app/src/main/res/values/strings.xml
+++ b/platforms/android/app/src/main/res/values/strings.xml
@@ -1,4 +1,6 @@
Dusk
+ Dusk Data
+ Saves, texture packs, settings, and logs
diff --git a/platforms/android/gradlew b/platforms/android/gradlew
old mode 100644
new mode 100755
diff --git a/platforms/android/scripts/stage-jni-libs.sh b/platforms/android/scripts/stage-jni-libs.sh
old mode 100644
new mode 100755
index ae25522e4d..42b08c13e6
--- a/platforms/android/scripts/stage-jni-libs.sh
+++ b/platforms/android/scripts/stage-jni-libs.sh
@@ -14,9 +14,22 @@ if [[ -z "$ANDROID_NDK_VER" ]] && [[ -d "$ANDROID_HOME_DIR/ndk" ]]; then
fi
if [[ -n "$ANDROID_NDK_VER" ]]; then
- TOOLCHAIN_BIN="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt/linux-x86_64/bin"
- if [[ -x "$TOOLCHAIN_BIN/llvm-strip" ]]; then
- STRIP_TOOL="$TOOLCHAIN_BIN/llvm-strip"
+ case "$(uname -s)" in
+ Darwin) HOST_TAG="darwin-x86_64" ;;
+ Linux) HOST_TAG="linux-x86_64" ;;
+ *) HOST_TAG="" ;;
+ esac
+
+ PREBUILT_DIR="$ANDROID_HOME_DIR/ndk/$ANDROID_NDK_VER/toolchains/llvm/prebuilt"
+ if [[ -n "$HOST_TAG" && -x "$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip" ]]; then
+ STRIP_TOOL="$PREBUILT_DIR/$HOST_TAG/bin/llvm-strip"
+ else
+ for candidate in "$PREBUILT_DIR"/*/bin/llvm-strip; do
+ if [[ -x "$candidate" ]]; then
+ STRIP_TOOL="$candidate"
+ break
+ fi
+ done
fi
fi
@@ -25,29 +38,35 @@ copy_lib() {
local src="$2"
local dst_dir="$APP_DIR/$abi"
local dst="$dst_dir/libmain.so"
+ local tmp="$dst_dir/.libmain.so.$$"
+ if [[ ! -f "$src" ]]; then
+ echo "Missing native library for $abi: $src" >&2
+ exit 1
+ fi
+
mkdir -p "$dst_dir"
- cp -f "$src" "$dst"
+ cp -f "$src" "$tmp"
if [[ "$ANDROID_STAGE_STRIP" != "0" ]] && [[ -n "$STRIP_TOOL" ]]; then
- "$STRIP_TOOL" --strip-debug "$dst"
- echo "Staged and stripped $src -> $dst"
+ "$STRIP_TOOL" --strip-unneeded "$tmp"
+ mv -f "$tmp" "$dst"
+ echo "Stripped and staged $src -> $dst"
else
+ mv -f "$tmp" "$dst"
echo "Staged $src -> $dst (strip disabled or strip tool unavailable)"
fi
}
-declare -A ABI_TO_LIB=(
- ["arm64-v8a"]="$ROOT_DIR/build/android-arm64/libmain.so"
- ["x86_64"]="$ROOT_DIR/build/android-x86_64/libmain.so"
-)
-
# Drop any previously staged ABI directories to avoid stale APK contents.
rm -rf "$APP_DIR/x86" "$APP_DIR/arm64-v8a" "$APP_DIR/x86_64"
for abi in $ANDROID_STAGE_ABIS; do
- src="${ABI_TO_LIB[$abi]:-}"
- if [[ -z "$src" ]]; then
- echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
- exit 1
- fi
+ case "$abi" in
+ arm64-v8a) src="$ROOT_DIR/build/android-arm64/libmain.so" ;;
+ x86_64) src="$ROOT_DIR/build/android-x86_64/libmain.so" ;;
+ *)
+ echo "Unsupported ABI '$abi'. Supported ABIs: arm64-v8a x86_64" >&2
+ exit 1
+ ;;
+ esac
copy_lib "$abi" "$src"
done
diff --git a/platforms/freedesktop/1024x1024/apps/dusk.png b/platforms/freedesktop/1024x1024/apps/dusk.png
index 862cbed0d8..33eceb7c37 100644
Binary files a/platforms/freedesktop/1024x1024/apps/dusk.png and b/platforms/freedesktop/1024x1024/apps/dusk.png differ
diff --git a/platforms/freedesktop/128x128/apps/dusk.png b/platforms/freedesktop/128x128/apps/dusk.png
index aaf38689d2..7d73dff804 100644
Binary files a/platforms/freedesktop/128x128/apps/dusk.png and b/platforms/freedesktop/128x128/apps/dusk.png differ
diff --git a/platforms/freedesktop/16x16/apps/dusk.png b/platforms/freedesktop/16x16/apps/dusk.png
index 21b653c63d..3680f3bf6a 100644
Binary files a/platforms/freedesktop/16x16/apps/dusk.png and b/platforms/freedesktop/16x16/apps/dusk.png differ
diff --git a/platforms/freedesktop/256x256/apps/dusk.png b/platforms/freedesktop/256x256/apps/dusk.png
index 1f4677878a..4fa9995913 100644
Binary files a/platforms/freedesktop/256x256/apps/dusk.png and b/platforms/freedesktop/256x256/apps/dusk.png differ
diff --git a/platforms/freedesktop/32x32/apps/dusk.png b/platforms/freedesktop/32x32/apps/dusk.png
index b879a01602..3d966b7cbf 100644
Binary files a/platforms/freedesktop/32x32/apps/dusk.png and b/platforms/freedesktop/32x32/apps/dusk.png differ
diff --git a/platforms/freedesktop/48x48/apps/dusk.png b/platforms/freedesktop/48x48/apps/dusk.png
index 4f3744ab89..e12bca0df9 100644
Binary files a/platforms/freedesktop/48x48/apps/dusk.png and b/platforms/freedesktop/48x48/apps/dusk.png differ
diff --git a/platforms/freedesktop/512x512/apps/dusk.png b/platforms/freedesktop/512x512/apps/dusk.png
index 6334f80e81..53afadf4a5 100644
Binary files a/platforms/freedesktop/512x512/apps/dusk.png and b/platforms/freedesktop/512x512/apps/dusk.png differ
diff --git a/platforms/freedesktop/64x64/apps/dusk.png b/platforms/freedesktop/64x64/apps/dusk.png
index 067a83da90..c0dd5502a5 100644
Binary files a/platforms/freedesktop/64x64/apps/dusk.png and b/platforms/freedesktop/64x64/apps/dusk.png differ
diff --git a/platforms/freedesktop/dusk.desktop b/platforms/freedesktop/dusk.desktop
index 2e19d7ea26..b052eac6ef 100644
--- a/platforms/freedesktop/dusk.desktop
+++ b/platforms/freedesktop/dusk.desktop
@@ -6,4 +6,4 @@ Exec=dusk
Icon=dusk
Terminal=false
Type=Application
-Categories=Graphics;3DGraphics;Game
+Categories=Game;
diff --git a/platforms/ios/Info.plist.in b/platforms/ios/Info.plist.in
index 881d5aff35..395b29b7d7 100644
--- a/platforms/ios/Info.plist.in
+++ b/platforms/ios/Info.plist.in
@@ -79,5 +79,11 @@
CADisableMinimumFrameDurationOnPhone
+ UIFileSharingEnabled
+
+ LSSupportsOpeningDocumentsInPlace
+
+ LSSupportsGameMode
+
diff --git a/platforms/macos/Info.plist.in b/platforms/macos/Info.plist.in
index b7bc7b706c..aee7393ee8 100644
--- a/platforms/macos/Info.plist.in
+++ b/platforms/macos/Info.plist.in
@@ -28,5 +28,7 @@
${MACOSX_BUNDLE_SHORT_VERSION_STRING}
NSHighResolutionCapable
+ LSSupportsGameMode
+
diff --git a/platforms/tvos/Info.plist.in b/platforms/tvos/Info.plist.in
index 841d120cc2..49ed85edd7 100644
--- a/platforms/tvos/Info.plist.in
+++ b/platforms/tvos/Info.plist.in
@@ -45,5 +45,7 @@
LaunchScreen
UIUserInterfaceStyle
Automatic
+ LSSupportsGameMode
+
diff --git a/res/AlegreyaSC-Bold.ttf b/res/AlegreyaSC-Bold.ttf
new file mode 100644
index 0000000000..dc73beb916
Binary files /dev/null and b/res/AlegreyaSC-Bold.ttf differ
diff --git a/res/AlegreyaSC-Regular.ttf b/res/AlegreyaSC-Regular.ttf
new file mode 100644
index 0000000000..31cae6c8ad
Binary files /dev/null and b/res/AlegreyaSC-Regular.ttf differ
diff --git a/res/FiraSans-Bold.ttf b/res/FiraSans-Bold.ttf
new file mode 100644
index 0000000000..e3593fb0f3
Binary files /dev/null and b/res/FiraSans-Bold.ttf differ
diff --git a/res/FiraSans-Regular.ttf b/res/FiraSans-Regular.ttf
new file mode 100644
index 0000000000..6f80647494
Binary files /dev/null and b/res/FiraSans-Regular.ttf differ
diff --git a/res/FiraSansCondensed-Bold.ttf b/res/FiraSansCondensed-Bold.ttf
new file mode 100644
index 0000000000..ec7e841549
Binary files /dev/null and b/res/FiraSansCondensed-Bold.ttf differ
diff --git a/res/FiraSansCondensed-Regular.ttf b/res/FiraSansCondensed-Regular.ttf
new file mode 100644
index 0000000000..6e1a192127
Binary files /dev/null and b/res/FiraSansCondensed-Regular.ttf differ
diff --git a/res/MaterialSymbolsRounded-Regular.ttf b/res/MaterialSymbolsRounded-Regular.ttf
new file mode 100644
index 0000000000..782fc0a406
Binary files /dev/null and b/res/MaterialSymbolsRounded-Regular.ttf differ
diff --git a/res/logo-mascot.png b/res/logo-mascot.png
new file mode 100644
index 0000000000..9f9a5d1ace
Binary files /dev/null and b/res/logo-mascot.png differ
diff --git a/res/logo-mascot.webp b/res/logo-mascot.webp
deleted file mode 100644
index c22f3bc90f..0000000000
Binary files a/res/logo-mascot.webp and /dev/null differ
diff --git a/res/org-icon-center.png b/res/org-icon-center.png
new file mode 100644
index 0000000000..b3ec7e1f6b
Binary files /dev/null and b/res/org-icon-center.png differ
diff --git a/res/org-icon-inner.png b/res/org-icon-inner.png
new file mode 100644
index 0000000000..4b3c864c74
Binary files /dev/null and b/res/org-icon-inner.png differ
diff --git a/res/org-icon-outer.png b/res/org-icon-outer.png
new file mode 100644
index 0000000000..6cbcf084d2
Binary files /dev/null and b/res/org-icon-outer.png differ
diff --git a/res/org-icon.png b/res/org-icon.png
index b7a0614b9b..288dc8768f 100644
Binary files a/res/org-icon.png and b/res/org-icon.png differ
diff --git a/res/prelaunch-bg.png b/res/prelaunch-bg.png
new file mode 100644
index 0000000000..045dcbe655
Binary files /dev/null and b/res/prelaunch-bg.png differ
diff --git a/res/rml/overlay.rcss b/res/rml/overlay.rcss
new file mode 100644
index 0000000000..3ce4d51068
--- /dev/null
+++ b/res/rml/overlay.rcss
@@ -0,0 +1,291 @@
+*, *:before, *:after {
+ box-sizing: border-box;
+}
+
+body {
+ overflow: visible;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ font-family: "Fira Sans";
+ font-weight: normal;
+ font-size: 20dp;
+ color: #E0DBC8;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: stretch;
+ z-index: 1;
+ pointer-events: none;
+}
+
+fps,
+toast {
+ position: absolute;
+ border: 1dp #92875B;
+ background-color: rgba(21, 22, 16, 80%);
+}
+
+toast {
+ top: 40dp;
+ right: 40dp;
+ display: flex;
+ flex-flow: column;
+ border-radius: 14dp;
+ overflow: hidden;
+ backdrop-filter: blur(5dp);
+ box-shadow: 0 0 15dp 3dp;
+ filter: opacity(0);
+ transform: scale(0.9);
+ transform-origin: center;
+ transition: filter transform 0.2s cubic-in-out;
+ padding: 18dp 24dp;
+ gap: 8dp;
+}
+
+toast[open] {
+ filter: opacity(1);
+ transform: scale(1);
+}
+
+/*toast:hover {
+ cursor: pointer;
+ background-color: rgba(61, 59, 36, 80%);
+}
+
+toast:active {
+ background-color: rgba(45, 43, 26, 80%);
+}*/
+
+toast heading {
+ display: flex;
+ gap: 18dp;
+ align-items: center;
+ font-family: "Fira Sans Condensed";
+ font-size: 18dp;
+ font-weight: bold;
+ text-transform: uppercase;
+ color: #92875B;
+}
+
+toast heading > span {
+ flex: 1 0 auto;
+}
+
+toast heading > row {
+ flex: 1 0 auto;
+ display: flex;
+ align-items: center;
+ gap: 4dp;
+}
+
+toast message {
+ display: flex;
+ flex-flow: column;
+ gap: 8dp;
+}
+
+toast message row {
+ display: flex;
+}
+
+toast message row.muted {
+ opacity: 0.5;
+}
+
+toast progress {
+ height: 4dp;
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+}
+
+toast progress fill {
+ background-color: rgba(194, 164, 45, 80%);
+}
+
+toast.achievement {
+ border: 1dp #C2A42D;
+}
+
+toast.achievement heading {
+ color: #C2A42D;
+}
+
+toast.controller-warning {
+ top: auto;
+ right: auto;
+ bottom: 40dp;
+ left: 50%;
+ width: 440dp;
+ max-width: 90%;
+ transform: translateX(-50%) scale(0.9);
+}
+
+toast.controller-warning[open] {
+ transform: translateX(-50%) scale(1);
+}
+
+toast.controller-warning heading {
+ color: #C2A42D;
+}
+
+toast.menu-notification {
+ top: 40dp;
+ right: auto;
+ bottom: auto;
+ left: 50%;
+ max-width: 90%;
+ transform: translateX(-50%) scale(0.9);
+}
+
+toast.menu-notification[open] {
+ transform: translateX(-50%) scale(1);
+}
+
+toast.menu-notification message {
+ align-items: center;
+ text-align: center;
+}
+
+toast.menu-notification message row {
+ align-items: center;
+ gap: 6dp;
+}
+
+icon {
+ font-family: "Material Symbols Rounded";
+ font-weight: normal;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+icon.arrow-forward {
+ width: 1.2em;
+ height: 1.2em;
+ font-size: 1.2em;
+ decorator: text("" center center);
+}
+
+icon.trophy {
+ width: 1.2em;
+ height: 1.2em;
+ font-size: 1.2em;
+ decorator: text("" center center);
+}
+
+icon.controller {
+ width: 1.2em;
+ height: 1.2em;
+ font-size: 1.2em;
+ decorator: text("" center center);
+}
+
+icon.warning {
+ width: 1.2em;
+ height: 1.2em;
+ font-size: 1.2em;
+ decorator: text("" center center);
+}
+
+fps {
+ display: none;
+ z-index: 99;
+ font-size: 18dp;
+ font-weight: bold;
+ padding: 9dp 12dp;
+ border-radius: 7dp;
+ pointer-events: none;
+ white-space: nowrap;
+}
+
+fps[open] {
+ display: block;
+}
+
+fps[corner=tl] {
+ top: 12dp;
+ left: 12dp;
+}
+
+fps[corner=tr] {
+ top: 12dp;
+ right: 12dp;
+}
+
+fps[corner=bl] {
+ bottom: 12dp;
+ left: 12dp;
+}
+
+fps[corner=br] {
+ bottom: 12dp;
+ right: 12dp;
+}
+
+logo {
+ position: absolute;
+ width: 100dp;
+ height: 100dp;
+ bottom: 40dp;
+ left: 40dp;
+ opacity: 0;
+ transition: opacity 0.5s linear-in-out;
+}
+
+logo[open] {
+ opacity: 0.65;
+}
+
+logo img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ filter: drop-shadow(#0008 0 0 14dp);
+ transform-origin: center;
+}
+
+logo img.inner {
+ animation: 24s linear-in-out infinite logo-inner-spin;
+}
+
+logo img.outer {
+ animation: 8s linear-in-out infinite logo-outer-spin;
+}
+
+@keyframes logo-inner-spin {
+ from {
+ transform: rotate(360deg);
+ }
+ to {
+ transform: rotate(0deg);
+ }
+}
+
+@keyframes logo-outer-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (max-height: 640dp) {
+ toast {
+ top: 20dp;
+ right: 20dp;
+ }
+
+ toast.controller-warning {
+ bottom: 20dp;
+ }
+
+ toast.menu-notification {
+ top: 20dp;
+ }
+}
diff --git a/res/rml/popup.rcss b/res/rml/popup.rcss
new file mode 100644
index 0000000000..effc80344d
--- /dev/null
+++ b/res/rml/popup.rcss
@@ -0,0 +1,45 @@
+*, *:before, *:after {
+ box-sizing: border-box;
+}
+
+body {
+ overflow: visible;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+ font-size: 18dp;
+ color: #E0DBC8;
+}
+
+button {
+ cursor: pointer;
+ focus: auto;
+}
+
+popup {
+ width: 100%;
+ display: flex;
+ align-items: stretch;
+ height: 64dp;
+ background-color: rgba(21, 22, 16, 80%);
+ border-bottom: 2dp #92875B;
+ backdrop-filter: blur(5dp);
+ transform: translateY(-64dp);
+ transition: transform 0.2s cubic-in-out;
+}
+
+popup[open] {
+ transform: translateY(0);
+}
+
+popup tab-bar {
+ flex: 1 1 0;
+}
+
+popup tab-bar tab {
+ opacity: 0.35;
+ color: #E0DBC8;
+}
diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss
new file mode 100644
index 0000000000..707d8b8234
--- /dev/null
+++ b/res/rml/prelaunch.rcss
@@ -0,0 +1,497 @@
+*, *:before, *:after {
+ box-sizing: border-box;
+}
+
+body {
+ width: 100%;
+ height: 100%;
+ font-family: "Fira Sans";
+ font-weight: normal;
+ font-size: 20dp;
+ color: #FFFFFF;
+ filter: opacity(0);
+ transition: filter 1s 0.2s linear-in-out;
+ z-index: -1;
+}
+
+.gradient {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ /* The color gradient from the Figma bands really badly. A fully black gradient does as well, but not as badly. */
+ decorator: horizontal-gradient(#000000FF #00000000);
+}
+
+body.mirrored .gradient {
+ decorator: horizontal-gradient(#00000000 #000000FF);
+}
+
+.background {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ decorator: image(../prelaunch-bg.png cover left center);
+ opacity: 0;
+ transition: opacity 1s linear-in-out;
+}
+
+body[open] {
+ filter: opacity(1);
+}
+
+body[open] .background {
+ opacity: 1;
+}
+
+body.disc-ready .background {
+ opacity: 0;
+}
+
+content {
+ display: block;
+ width: 100%;
+ height: 100%;
+ filter: opacity(0);
+ transition: filter 0.2s linear-in-out;
+}
+
+content[open] {
+ filter: opacity(1);
+}
+
+menu {
+ position: absolute;
+ left: 96dp;
+ right: auto;
+ top: 50%;
+ transform: translateY(-50%);
+ /* Scale based on a reference screen width, 428/1216 */
+ width: 35.230264vw;
+ min-width: 428dp;
+ max-width: 856dp;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 48dp;
+}
+
+body.mirrored menu {
+ left: auto;
+ right: 96dp;
+}
+
+hero {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ gap: 8dp;
+}
+
+body.mirrored hero {
+ align-items: flex-end;
+}
+
+hero img {
+ width: 100%;
+}
+
+.eyebrow {
+ font-family: "Alegreya SC";
+ font-size: 32dp;
+}
+
+@media (min-width: 1216dp) {
+ .eyebrow {
+ /* Same logic as .menu, 32/1216 */
+ font-size: 2.631579vw;
+ }
+}
+
+.eyebrow span {
+ font-weight: bold;
+}
+
+#menu-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12dp;
+ align-items: flex-start;
+}
+
+#menu-list button {
+ width: 428dp;
+ height: 54dp;
+ padding: 8dp 16dp;
+ border-radius: 8dp;
+ text-align: left;
+ text-transform: uppercase;
+ font-family: "Fira Sans Condensed";
+ font-size: 32dp;
+ font-weight: normal;
+ cursor: pointer;
+ /* Define a fully transparent gradient as the default state, otherwise a white flash occurs */
+ decorator: horizontal-gradient(#00000000 #00000000);
+}
+
+#menu-list button:disabled {
+ opacity: 0.75;
+ cursor: default;
+ decorator: horizontal-gradient(#00000000 #00000000);
+}
+
+#menu-list button.anim-done {
+ transition: decorator color opacity 0.1s linear-in-out;
+}
+
+#menu-list button:hover,
+#menu-list button:focus-visible {
+ color: black;
+ decorator: horizontal-gradient(#FEE685FF #FEE68500);
+}
+
+body.mirrored #menu-list {
+ align-items: flex-end;
+}
+
+body.mirrored #menu-list button {
+ text-align: right;
+}
+
+body.mirrored #menu-list button:hover,
+body.mirrored #menu-list button:focus-visible {
+ decorator: horizontal-gradient(#FEE68500 #FEE685FF);
+}
+
+disc-info {
+ position: absolute;
+ left: 96dp;
+ right: auto;
+ bottom: 72dp;
+ display: flex;
+ flex-direction: column;
+ gap: 12dp;
+ font-size: 24dp;
+ font-effect: glow(0dp 4dp 0dp 4dp black);
+ text-align: left;
+}
+
+body.mirrored disc-info {
+ left: auto;
+ right: 96dp;
+ text-align: right;
+}
+
+version-info {
+ position: absolute;
+ right: 96dp;
+ left: auto;
+ bottom: 72dp;
+ display: flex;
+ flex-direction: column;
+ gap: 12dp;
+ text-align: right;
+ font-size: 24dp;
+ font-effect: glow(0dp 4dp 0dp 4dp black);
+ text-align: right;
+}
+
+body.mirrored version-info {
+ right: auto;
+ left: 96dp;
+ text-align: left;
+}
+
+#disc-status {
+ display: flex;
+ align-items: center;
+ gap: 8dp;
+}
+
+#disc-status[status=good] {
+ color: #D8F999;
+}
+
+#disc-status[status=bad] {
+ color: #FFC9C9;
+}
+
+#disc-status[status=verifying] {
+ color: #FFFFFF;
+}
+
+#disc-status[status=mismatch] {
+ color: #FFD6A7;
+}
+
+#disc-status[status=unknown] {
+ color: rgba(224, 219, 200, 65%);
+}
+
+#disc-status[status=pending] {
+ color: #FEE685;
+}
+
+#disc-status icon {
+ display: none;
+ width: 24dp;
+ height: 24dp;
+ font-family: "Material Symbols Rounded";
+ font-weight: normal;
+ font-size: 24dp;
+}
+
+#disc-status[status] icon {
+ display: block;
+}
+
+#disc-status[status=good] icon {
+ decorator: text("" center center);
+}
+
+#disc-status[status=bad] icon {
+ decorator: text("" center center);
+}
+
+#disc-status[status=verifying] icon {
+ decorator: text("" center center);
+}
+
+#disc-status[status=mismatch] icon {
+ decorator: text("" center center);
+}
+
+#disc-status[status=unknown] icon {
+ decorator: text("" center center);
+}
+
+#disc-status[status=pending] icon {
+ decorator: text("" center center);
+}
+
+#disc-version {
+ font-size: 20dp;
+}
+
+.update {
+ display: none;
+ color: #A6A09B;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8dp;
+ font-size: 20dp;
+}
+
+.update[state=checking],
+.update[state=failed] {
+ display: block;
+}
+
+.update[state=available] {
+ display: flex;
+}
+
+#update-download {
+ display: none;
+ margin: 0dp;
+ padding: 0dp;
+ border-width: 0dp;
+ background-color: transparent;
+ color: #D8F999;
+ cursor: pointer;
+ text-transform: uppercase;
+ font-weight: bold;
+ decorator: horizontal-gradient(#00000000 #00000000);
+}
+
+.update[state=available] #update-download {
+ display: flex;
+ align-items: center;
+ gap: 2dp;
+}
+
+#update-download icon {
+ display: block;
+ width: 18dp;
+ height: 18dp;
+ font-family: "Material Symbols Rounded";
+ font-weight: normal;
+ decorator: text("" center center);
+}
+
+.detail {
+ color: #A6A09B;
+}
+
+body.mirrored .update {
+ justify-content: flex-start;
+}
+
+/* Startup animation */
+.intro-item {
+ opacity: 0;
+ transform: translateY(10dp);
+ transition: opacity transform 0.3s 0.1s cubic-in-out;
+}
+
+body.animate-in .intro-item {
+ opacity: 1;
+ transform: translateY(0dp);
+}
+
+.delay-0 {
+ transition: opacity transform 0.3s 0.1s cubic-in-out;
+}
+
+.delay-1 {
+ transition: opacity transform 0.3s 0.2s cubic-in-out;
+}
+
+.delay-2 {
+ transition: opacity transform 0.3s 0.3s cubic-in-out;
+}
+
+.delay-3 {
+ transition: opacity transform 0.3s 0.4s cubic-in-out;
+}
+
+.delay-4 {
+ transition: opacity transform 0.3s 0.5s cubic-in-out;
+}
+
+.delay-5 {
+ transition: opacity transform 0.3s 0.6s cubic-in-out;
+}
+
+/* Mobile layout */
+@media (max-height: 640dp) {
+ .gradient {
+ decorator: horizontal-gradient(#00000000 #000000FF);
+ }
+
+ body.mirrored .gradient {
+ decorator: horizontal-gradient(#000000FF #00000000);
+ }
+
+ menu {
+ left: 32dp;
+ right: 32dp;
+ width: auto;
+ min-width: 0;
+ max-width: none;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16dp;
+ }
+
+ body.mirrored menu {
+ left: 32dp;
+ right: 32dp;
+ flex-direction: row-reverse;
+ }
+
+ hero {
+ flex: 1 1 0;
+ min-width: 0;
+ max-width: 48%;
+ margin-left: 32dp;
+ }
+
+ body.mirrored hero {
+ align-items: flex-end;
+ }
+
+ hero img {
+ width: 100%;
+ }
+
+ #menu-list {
+ flex: 1 1 0;
+ min-width: 0;
+ max-width: 52%;
+ align-items: flex-end;
+ }
+
+ #menu-list button {
+ width: 100%;
+ max-width: 100%;
+ text-align: right;
+ }
+
+ #menu-list button:hover,
+ #menu-list button:focus-visible {
+ decorator: horizontal-gradient(#FEE68500 #FEE685FF);
+ }
+
+ body.mirrored #menu-list {
+ align-items: flex-start;
+ }
+
+ body.mirrored #menu-list button {
+ text-align: left;
+ }
+
+ body.mirrored #menu-list button:hover,
+ body.mirrored #menu-list button:focus-visible {
+ decorator: horizontal-gradient(#FEE685FF #FEE68500);
+ }
+
+ .eyebrow {
+ display: none;
+ }
+
+ disc-info {
+ right: 32dp;
+ left: auto;
+ bottom: 32dp;
+ top: auto;
+ text-align: right;
+ font-size: 16dp;
+ }
+
+ #disc-status {
+ justify-content: flex-end;
+ }
+
+ #disc-status icon {
+ font-size: 20dp;
+ }
+
+ #disc-version {
+ font-size: 16dp;
+ }
+
+ version-info {
+ right: 32dp;
+ left: auto;
+ bottom: auto;
+ top: 32dp;
+ text-align: right;
+ font-size: 16dp;
+ }
+
+ .update {
+ font-size: 16dp;
+ }
+
+ body.mirrored disc-info {
+ right: auto;
+ left: 32dp;
+ bottom: 32dp;
+ top: auto;
+ text-align: left;
+ }
+
+ body.mirrored version-info {
+ right: auto;
+ left: 32dp;
+ bottom: auto;
+ top: 32dp;
+ text-align: left;
+ }
+
+ body.mirrored #disc-status {
+ justify-content: flex-start;
+ }
+}
diff --git a/res/rml/tabbing.rcss b/res/rml/tabbing.rcss
new file mode 100644
index 0000000000..194ca89f05
--- /dev/null
+++ b/res/rml/tabbing.rcss
@@ -0,0 +1,81 @@
+tab-bar {
+ display: flex;
+ position: relative;
+ min-width: 0;
+ overflow: auto hidden;
+ clip: always;
+ text-transform: uppercase;
+}
+
+tab-bar scrollbarhorizontal,
+tab-bar scrollbarhorizontal sliderarrowdec,
+tab-bar scrollbarhorizontal sliderarrowinc,
+tab-bar scrollbarhorizontal slidertrack,
+tab-bar scrollbarhorizontal sliderbar {
+ width: 0;
+ height: 0;
+}
+
+tab-bar tab {
+ flex: 0 0 auto;
+ padding: 0 24dp;
+ line-height: 64dp;
+ white-space: nowrap;
+ decorator: vertical-gradient(#c2a42d00 #c2a42d00);
+ transition: decorator 0.1s linear-in-out, opacity 0.1s linear-in-out;
+ cursor: pointer;
+}
+
+tab-bar tab:selected {
+ opacity: 1;
+ border-bottom: 4dp #C2A42D;
+ font-effect: glow(0dp 4dp 0dp 4dp black);
+}
+
+tab-bar tab:focus-visible,
+tab-bar tab:hover {
+ opacity: 1;
+ font-effect: glow(0dp 4dp 0dp 4dp black);
+ decorator: vertical-gradient(#c2a42d00 #c2a42d26);
+}
+
+tab-bar tab:active {
+ decorator: vertical-gradient(#c2a42d10 #c2a42d40);
+}
+
+tab-bar[closable] tab-end-spacer {
+ display: block;
+ flex: 0 0 64dp;
+ width: 64dp;
+ pointer-events: none;
+}
+
+tab-bar[closable] close {
+ display: block;
+ position: fixed;
+ top: 8dp;
+ right: 8dp;
+ z-index: 1;
+ width: 48dp;
+ height: 48dp;
+ font-family: "Material Symbols Rounded";
+ font-weight: normal;
+ font-size: 24dp;
+ color: rgba(224, 219, 200, 70%);
+ backdrop-filter: blur(2dp);
+ border-radius: 6dp;
+ decorator: text("" center center);
+ transition: color background-color 0.12s linear-in-out;
+ cursor: pointer;
+}
+
+tab-bar[closable] close:hover,
+tab-bar[closable] close:focus-visible {
+ color: #fff;
+ background-color: rgba(194, 164, 45, 24%);
+}
+
+tab-bar[closable] close:active {
+ color: #fff;
+ background-color: rgba(194, 164, 45, 40%);
+}
diff --git a/res/rml/tuner.rcss b/res/rml/tuner.rcss
new file mode 100644
index 0000000000..86dd2043f6
--- /dev/null
+++ b/res/rml/tuner.rcss
@@ -0,0 +1,147 @@
+*, *:before, *:after {
+ box-sizing: border-box;
+}
+
+body {
+ overflow: visible;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ font-family: "Fira Sans Condensed";
+ font-size: 24dp;
+ color: #FFFFFF;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: stretch;
+}
+
+.tuner-root {
+ width: 100%;
+ min-height: 45%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: stretch;
+ decorator: vertical-gradient(#00000000 #151610F2);
+ filter: opacity(0);
+ transition: filter 0.2s linear-in-out;
+}
+
+.tuner-root[open] {
+ filter: opacity(1);
+}
+
+.tuner {
+ width: 100%;
+ max-width: 1216dp;
+ margin-left: auto;
+ margin-right: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 24dp;
+ padding: 48dp 64dp;
+}
+
+@media (max-height: 800dp) {
+ .tuner-root {
+ min-height: 38%;
+ }
+
+ .tuner {
+ gap: 16dp;
+ padding: 32dp 48dp;
+ }
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 24dp;
+}
+
+.carousel-container {
+ flex: 1 1 auto;
+ display: flex;
+ justify-content: flex-end;
+ min-width: 0;
+}
+
+.description {
+ font-size: 18dp;
+ line-height: 22dp;
+ color: rgba(255, 255, 255, 50%);
+}
+
+.divider {
+ margin: 1dp 0;
+ border-top: 1dp rgba(217, 217, 217, 50%);
+}
+
+.footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 24dp;
+}
+
+footer-button {
+ display: block;
+ width: 100%;
+ max-width: 220dp;
+ border: 0;
+ padding: 0;
+ background-color: transparent;
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+ font-size: 20dp;
+ line-height: 24dp;
+ text-transform: uppercase;
+ color: #FFFFFF;
+ opacity: 1;
+ cursor: pointer;
+}
+
+footer-button.return {
+ text-align: left;
+}
+
+footer-button.reset {
+ text-align: right;
+}
+
+.stepped-carousel {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16dp;
+ width: auto;
+ min-width: 246dp;
+ padding: 0;
+ background-color: transparent;
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+}
+
+.stepped-carousel-value {
+ line-height: 29dp;
+ min-width: 166dp;
+ text-align: center;
+ white-space: nowrap;
+ opacity: 0.9;
+}
+
+.stepped-carousel-arrow {
+ width: 24dp;
+ height: 24dp;
+ min-width: 24dp;
+ padding: 0;
+ border: 0;
+ background-color: transparent;
+ opacity: 1;
+ cursor: pointer;
+ font-family: "Material Symbols Rounded";
+ font-weight: normal;
+}
diff --git a/res/rml/window.rcss b/res/rml/window.rcss
new file mode 100644
index 0000000000..45473f312f
--- /dev/null
+++ b/res/rml/window.rcss
@@ -0,0 +1,506 @@
+*, *:before, *:after {
+ box-sizing: border-box;
+}
+
+body {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ padding: 64dp;
+ font-family: "Fira Sans";
+ font-weight: normal;
+ font-style: normal;
+ font-size: 15dp;
+ color: #E0DBC8;
+}
+
+window {
+ display: flex;
+ flex-flow: column;
+ position: relative;
+ height: 100%;
+ width: 100%;
+ max-width: 1088dp;
+ max-height: 768dp;
+ margin: auto;
+ border-radius: 14dp;
+ overflow: hidden;
+ border: 2dp #92875B;
+ backdrop-filter: blur(5dp);
+ box-shadow: 0 0 25dp 5dp;
+ background-color: rgba(21, 22, 16, 90%);
+ filter: opacity(0);
+ transform: scale(0.9);
+ transform-origin: center;
+ transition: filter transform 0.2s cubic-in-out;
+}
+
+window.small {
+ height: auto;
+ width: auto;
+}
+
+window.modal {
+ max-width: 640dp;
+}
+
+window[open] {
+ filter: opacity(1);
+ transform: scale(1);
+}
+
+@media (max-height: 640dp) {
+ body {
+ padding: 16dp;
+ }
+ window {
+ box-shadow: none;
+ }
+}
+
+window tab-bar {
+ flex: 0 0 64dp;
+ height: 64dp;
+ background-color: rgba(217, 217, 217, 10%);
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+ font-size: 18dp;
+ border-bottom: 2dp #92875B;
+}
+
+window tab-bar tab {
+ opacity: 0.25;
+}
+
+window content {
+ display: flex;
+ flex: 1 1 auto;
+ min-width: 0;
+ min-height: 0;
+ overflow: hidden;
+}
+
+window content pane {
+ display: flex;
+ flex-flow: column;
+ flex: 1 1 0;
+ min-width: 0;
+ min-height: 0;
+ padding: 24dp;
+ padding-bottom: 0dp;
+ gap: 8dp;
+ overflow: hidden auto;
+ font-size: 20dp;
+}
+
+window content pane:not(:last-of-type) {
+ border-right: 1dp #92875B;
+}
+
+window content pane > * {
+ flex: 0 0 auto;
+}
+
+window content pane:last-of-type > div {
+ line-height: 1.625;
+}
+
+window content pane > spacer {
+ display: block;
+ /* Completes the 24dp bottom inset after the pane's 8dp gap. */
+ flex: 0 0 16dp;
+ height: 16dp;
+ pointer-events: none;
+}
+
+scrollbarvertical {
+ width: 8dp;
+ margin: 4dp 4dp 4dp 0;
+}
+
+scrollbarvertical sliderarrowdec,
+scrollbarvertical sliderarrowinc {
+ width: 0;
+ height: 0;
+}
+
+scrollbarvertical slidertrack {
+ width: 8dp;
+}
+
+scrollbarvertical sliderbar {
+ width: 8dp;
+ min-height: 24dp;
+ background-color: rgba(224, 219, 200, 45%);
+ border-radius: 2dp;
+ transition: background-color 0.2s cubic-in-out;
+}
+
+scrollbarvertical sliderbar:hover,
+scrollbarvertical sliderbar:active {
+ background-color: rgba(194, 164, 45, 80%);
+}
+
+scrollbarhorizontal {
+ height: 0;
+}
+
+scrollbarhorizontal sliderarrowdec,
+scrollbarhorizontal sliderarrowinc {
+ width: 0;
+ height: 0;
+}
+
+scrollbarhorizontal slidertrack,
+scrollbarhorizontal sliderbar {
+ width: 0;
+ height: 0;
+}
+
+.section-heading {
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+ text-transform: uppercase;
+ font-size: 22dp;
+ opacity: 0.25;
+}
+
+.section-heading:not(:first-of-type) {
+ padding-top: 12dp;
+}
+
+button {
+ text-align: center;
+ background-color: rgba(17, 16, 10, 20%);
+ opacity: 0.9;
+ padding: 8dp 16dp;
+ border-radius: 14dp;
+ box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp;
+ font-size: 20dp;
+ transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
+ cursor: pointer;
+ focus: auto;
+}
+
+button:not(:disabled):hover,
+button:not(:disabled):focus-visible {
+ background-color: rgba(204, 184, 119, 20%);
+ box-shadow: #C2A42D 0 0 0 2dp;
+}
+
+button:not(:disabled):selected {
+ opacity: 1;
+ background-color: rgba(204, 184, 119, 40%);
+}
+
+button:not(:disabled):active {
+ opacity: 1;
+ background-color: rgba(204, 184, 119, 40%);
+ box-shadow: #C2A42D 0 0 0 2dp;
+}
+
+button.modal-btn {
+ flex: 1 1 0;
+ text-align: center;
+}
+
+select-button {
+ display: flex;
+ align-items: center;
+ gap: 8dp;
+ background-color: rgba(17, 16, 10, 20%);
+ opacity: 0.9;
+ padding: 8dp 16dp;
+ border-radius: 14dp;
+ box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp;
+ transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
+ cursor: pointer;
+ focus: auto;
+}
+
+select-button:not(:disabled):hover,
+select-button:not(:disabled):focus-visible {
+ background-color: rgba(204, 184, 119, 20%);
+ box-shadow: #C2A42D 0 0 0 2dp;
+}
+
+select-button:not(:disabled):selected {
+ opacity: 1;
+ background-color: rgba(204, 184, 119, 40%);
+}
+
+select-button:not(:disabled):active {
+ opacity: 1;
+ background-color: rgba(204, 184, 119, 40%);
+ box-shadow: #C2A42D 0 0 0 2dp;
+}
+
+select-button:disabled {
+ opacity: 0.35;
+ cursor: default;
+}
+
+select-button key {
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+ font-size: 18dp;
+ text-transform: uppercase;
+ flex: 0 1 auto;
+}
+
+select-button value {
+ flex: 1 1 auto;
+ text-align: right;
+ font-size: 20dp;
+}
+
+select-button value.modified {
+ font-weight: bold;
+}
+
+select-button input {
+ text-align: right;
+ font-size: 20dp;
+}
+
+icon {
+ width: 1em;
+ height: 1em;
+ font-family: "Material Symbols Rounded";
+ font-weight: normal;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+icon.warning {
+ decorator: text("" center center);
+}
+
+icon.error {
+ decorator: text("" center center);
+}
+
+icon.verifying {
+ decorator: text("" center center);
+}
+
+icon.celebration {
+ decorator: text("" center center);
+}
+
+icon.question-mark {
+ decorator: text("" center center);
+}
+
+.achievement-total {
+ position: absolute;
+ top: 0;
+ right: 64dp;
+ height: 64dp;
+ line-height: 64dp;
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+ font-size: 16dp;
+ color: rgba(224, 219, 200, 55%);
+ pointer-events: none;
+}
+
+.achievement-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 10dp;
+ padding: 12dp 0;
+ border-bottom: 1dp rgba(146, 135, 91, 30%);
+}
+
+.achievement-info {
+ display: block;
+ flex: 1 1 0;
+ min-width: 0;
+}
+
+.achievement-header {
+ display: flex;
+ align-items: center;
+}
+
+.achievement-name {
+ flex: 1;
+ font-weight: bold;
+}
+
+.achievement-name.unlocked {
+ color: #ffa826;
+}
+
+.achievement-badge {
+ font-size: 14dp;
+ opacity: 0.7;
+}
+
+.achievement-badge.unlocked {
+ color: #44cc55;
+ opacity: 1;
+}
+
+.achievement-badge.locked {
+ color: #cc4444;
+ opacity: 1;
+}
+
+.achievement-desc {
+ display: block;
+ color: rgba(224, 219, 200, 55%);
+ font-size: 16dp;
+ margin: 4dp 0 0 0;
+}
+
+.achievement-progress {
+ display: block;
+ font-size: 13dp;
+ color: rgba(224, 219, 200, 45%);
+}
+
+progress {
+ display: block;
+ width: 100%;
+ height: 6dp;
+ border-radius: 3dp;
+ background-color: rgba(255, 255, 255, 10%);
+ margin: 6dp 0 2dp 0;
+}
+
+progress.progress-done fill {
+ background-color: #44aa22;
+ border-radius: 3dp;
+}
+
+progress.progress-ongoing fill {
+ background-color: #2255bb;
+ border-radius: 3dp;
+}
+
+button.achievement-clear {
+ flex: 0 0 auto;
+ align-self: center;
+ font-size: 14dp;
+ padding: 2dp 8dp;
+ opacity: 0.45;
+}
+
+.preset-grid {
+ display: flex;
+ flex-direction: row;
+ gap: 20dp;
+ flex: 0 1 auto;
+ align-items: flex-start;
+ width: 100%;
+}
+
+.preset-col {
+ display: flex;
+ flex-flow: column;
+ gap: 12dp;
+ flex: 1 1 0;
+}
+
+.preset-desc {
+ display: block;
+ font-size: 16dp;
+ text-align: center;
+}
+
+.modal-dialog {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ padding: 24dp;
+ gap: 20dp;
+ flex: 0 1 auto;
+ width: 100%;
+ text-align: left;
+}
+
+window.modal.danger {
+ border: 2dp #852221;
+}
+
+.modal-header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ flex: 0 0 auto;
+ gap: 16dp;
+}
+
+.modal-header icon {
+ font-size: 24dp;
+ color: #92875B;
+}
+
+.modal-title {
+ display: block;
+ font-family: "Fira Sans Condensed";
+ font-weight: bold;
+ text-transform: uppercase;
+ font-size: 18dp;
+ color: #92875B;
+ flex: 1 1 auto;
+}
+
+window.modal.danger .modal-title,
+window.modal.danger .modal-header icon {
+ color: #B3261E;
+}
+
+.modal-body {
+ display: block;
+ width: 100%;
+ flex: 0 1 auto;
+ min-width: 0;
+ font-size: 20dp;
+ color: #FFFFFF;
+ font-weight: normal;
+}
+
+.modal-body span.tip {
+ font-size: 14dp;
+ color: #92875B;
+}
+
+.verification-progress {
+ display: flex;
+ flex-direction: column;
+ gap: 10dp;
+ width: 100%;
+}
+
+.verification-file {
+ display: block;
+ font-size: 17dp;
+ color: #FFFFFF;
+}
+
+progress.verification-progress-bar {
+ height: 8dp;
+ margin: 2dp 0 0 0;
+}
+
+.verification-detail {
+ display: block;
+ font-size: 14dp;
+ color: rgba(224, 219, 200, 65%);
+}
+
+.modal-actions {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: stretch;
+ gap: 12dp;
+ width: 100%;
+ flex: 0 0 auto;
+ padding-top: 4dp;
+}
diff --git a/src/SSystem/SComponent/c_lib.cpp b/src/SSystem/SComponent/c_lib.cpp
index 6441d6ceb8..d73dfb8627 100644
--- a/src/SSystem/SComponent/c_lib.cpp
+++ b/src/SSystem/SComponent/c_lib.cpp
@@ -468,9 +468,20 @@ s16 cLib_targetAngleX(cXyz const* lhs, cXyz const* rhs) {
void cLib_offsetPos(cXyz* pdest, cXyz const* psrc, s16 angle, cXyz const* vec) {
f32 cos = cM_scos(angle);
f32 sin = cM_ssin(angle);
+ // MWCC loads vec members into registers before writing to pdest; other compilers may not,
+ // which corrupts results when pdest and vec alias the same memory.
+#if !__MWERKS__
+ f32 vx = vec->x;
+ f32 vy = vec->y;
+ f32 vz = vec->z;
+ pdest->x = psrc->x + (vx * cos + vz * sin);
+ pdest->y = psrc->y + vy;
+ pdest->z = psrc->z + (vz * cos - vx * sin);
+#else
pdest->x = psrc->x + (vec->x * cos + vec->z * sin);
pdest->y = psrc->y + vec->y;
pdest->z = psrc->z + (vec->z * cos - vec->x * sin);
+#endif
}
/**
diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp
index 5bb91eef31..dda1a4b2d0 100644
--- a/src/Z2AudioLib/Z2Audience.cpp
+++ b/src/Z2AudioLib/Z2Audience.cpp
@@ -1,5 +1,10 @@
#include "Z2AudioLib/Z2Audience.h"
#include "Z2AudioLib/Z2SoundInfo.h"
+#if TARGET_PC
+#include "dusk/audio/DuskDsp.hpp"
+#include "dusk/settings.h"
+#include
+#endif
#include "Z2AudioLib/Z2Calc.h"
#include "Z2AudioLib/Z2Param.h"
#include "JSystem/JAudio2/JAISound.h"
@@ -701,6 +706,11 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) {
f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) {
Vec local_54 = param_0;
local_54.y = 0.0f;
+#if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ local_54.x = -local_54.x;
+ }
+#endif
f32 dVar6 = VECMag(&local_54);
if (dVar6 < 0.1f) {
@@ -734,9 +744,22 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) {
f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) {
f32 fVar1 = param_0.z + mAudioCamera[camID].getDolbyCenterZ();
+#if TARGET_PC
+ if (dusk::audio::EnableHrtf) {
+ // Normalize the direction so result is purely front/back orientation,
+ // independent of how far away the sound is
+ f32 lenSq = param_0.x * param_0.x + param_0.y * param_0.y + param_0.z * param_0.z;
+ if (lenSq < 0.0001f) {
+ return 0.5f;
+ }
+ f32 zNorm = param_0.z / sqrtf(lenSq);
+ f32 t = (zNorm + 1.0f) * 0.5f;
+ return 0.5f - 0.5f * cosf(t * static_cast(M_PI));
+ }
+#endif
if (fVar1 > mSetting.field_0x48) {
return 1.0f;
- }
+ }
if (fVar1 < mSetting.field_0x44) {
return 0.0f;
diff --git a/src/Z2AudioLib/Z2WolfHowlMgr.cpp b/src/Z2AudioLib/Z2WolfHowlMgr.cpp
index 4866af7a5d..f12d453fbe 100644
--- a/src/Z2AudioLib/Z2WolfHowlMgr.cpp
+++ b/src/Z2AudioLib/Z2WolfHowlMgr.cpp
@@ -369,7 +369,7 @@ void Z2WolfHowlMgr::setCorrectData(s8 curveID, Z2WolfHowlData* data) {
#if TARGET_PC
case Z2WOLFHOWL_TIMESONG:
cPitchUp = 1.3348f;
- cPitchCenter = 1.0f;
+ cPitchCenter = 0.8909f;
cPitchDown = 0.7937f;
break;
#endif
diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp
index e6943baf96..0204278ddc 100644
--- a/src/d/actor/d_a_alink.cpp
+++ b/src/d/actor/d_a_alink.cpp
@@ -51,6 +51,7 @@
#include "d/actor/d_a_ni.h"
#include "d/d_s_play.h"
+#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
#include "res/Object/Alink.h"
#include
@@ -4962,13 +4963,16 @@ int daAlink_c::create() {
setArcName(checkWolf());
setOriginalHeap(&mpArcHeap, 0xA2800);
+ JKRHEAP_NAME(mpArcHeap, "Alink ArcHeap");
if (dComIfG_resLoad(&mPhaseReq, mArcName, mpArcHeap) != cPhs_COMPLEATE_e) {
return cPhs_INIT_e;
}
setShieldArcName();
setOriginalHeap(&mpShieldArcHeap, 0x7000);
- if (dComIfG_resLoad(&mShieldPhaseReq, mShieldArcName, mpShieldArcHeap) != cPhs_COMPLEATE_e) {
+ JKRHEAP_NAME(mpShieldArcHeap, "Alink ShieldArcHeap");
+ if (dComIfG_resLoad(&mShieldPhaseReq, mShieldArcName, mpShieldArcHeap) != cPhs_COMPLEATE_e)
+ {
return cPhs_INIT_e;
}
@@ -5877,6 +5881,11 @@ void daAlink_c::setItemMatrix(int param_0) {
modelCalc(mSheathModel);
int var_r26;
+
+ #if AVOID_UB
+ var_r26 = 0;
+ #endif
+
if (!checkNoResetFlg3(FLG3_UNK_4000000)) {
if (mEquipItem == 0x103 || param_0 != 0) {
mSwordModel->setBaseTRMtx(mpLinkModel->getAnmMtx(mLeftItemJntNo));
@@ -14779,6 +14788,10 @@ void daAlink_c::deleteEquipItem(BOOL i_isPlaySound, BOOL i_isDeleteKantera) {
mIronBallChainPos = NULL;
mIronBallChainAngle = NULL;
field_0x3848 = NULL;
+#if TARGET_PC
+ mIBChainInterpPrevValid = false;
+ mIBChainInterpCurrValid = false;
+#endif
field_0x0774 = NULL;
field_0x0778 = NULL;
mpHookshotLinChk = NULL;
@@ -18936,11 +18949,20 @@ void daAlink_c::setDrawHand() {
mpLinkHandModel->setBaseTRMtx(mpLinkModel->getBaseTRMtx());
mpLinkHandModel->calc();
+#if TARGET_PC
+ // FRAME INTERP NOTE: Always set these, otherwise the hands occasionally zip to origin.
+ // Doing it regardless of interpolation being active seems harmless.
+ mpLinkHandModel->setAnmMtx(1, mpLinkModel->getAnmMtx(9));
+ mpLinkHandModel->setAnmMtx(2, mpLinkModel->getAnmMtx(0xE));
+#endif
+
if (var_r30 == 0xFE || var_r30 == 0xFB) {
field_0x06d0 = field_0x06d8;
} else {
field_0x06d0 = mpLinkHandModel->getModelData()->getMaterialNodePointer(var_r30)->getShape();
+#if !TARGET_PC
mpLinkHandModel->setAnmMtx(1, mpLinkModel->getAnmMtx(9));
+#endif
}
if (var_r30 == 0xFB) {
@@ -18959,7 +18981,9 @@ void daAlink_c::setDrawHand() {
field_0x06d4 = field_0x06dc;
} else {
field_0x06d4 = mpLinkHandModel->getModelData()->getMaterialNodePointer(var_r29)->getShape();
+#if !TARGET_PC
mpLinkHandModel->setAnmMtx(2, mpLinkModel->getAnmMtx(0xE));
+#endif
}
if (var_r29 == 0xFB) {
@@ -19698,6 +19722,27 @@ int daAlink_c::draw() {
)
{
dComIfGd_getOpaListDark()->entryImm(mpHookChain, 0);
+
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation &&
+ mEquipItem == dItemNo_IRONBALL_e &&
+ mIronBallChainPos != NULL && mIronBallChainAngle != NULL)
+ {
+ if (mIBChainInterpCurrValid) {
+ memcpy(mIBChainInterpPrevPos, mIBChainInterpCurrPos, IRON_BALL_CHAIN_COUNT * sizeof(cXyz));
+ memcpy(mIBChainInterpPrevAngle, mIBChainInterpCurrAngle, IRON_BALL_CHAIN_COUNT * sizeof(csXyz));
+ mIBChainInterpPrevHandRoot = mIBChainInterpCurrHandRoot;
+ mIBChainInterpPrevValid = true;
+ }
+
+ memcpy(mIBChainInterpCurrPos, mIronBallChainPos, IRON_BALL_CHAIN_COUNT * sizeof(cXyz));
+ memcpy(mIBChainInterpCurrAngle, mIronBallChainAngle, IRON_BALL_CHAIN_COUNT * sizeof(csXyz));
+ mIBChainInterpCurrHandRoot = mHookshotTopPos;
+ mIBChainInterpCurrValid = true;
+
+ dusk::frame_interp::add_interpolation_callback(&ironBallChainInterpCallback, this);
+ }
+#endif
}
}
diff --git a/src/d/actor/d_a_alink_damage.inc b/src/d/actor/d_a_alink_damage.inc
index ac5175e314..bccdbf7381 100644
--- a/src/d/actor/d_a_alink_damage.inc
+++ b/src/d/actor/d_a_alink_damage.inc
@@ -8,6 +8,10 @@
#include "d/actor/d_a_horse.h"
#include "d/actor/d_a_crod.h"
#include "d/d_msg_object.h"
+#ifdef TARGET_PC
+#include "d/actor/d_a_obj_carry.h"
+#include "dusk/achievements.h"
+#endif
#if DEBUG
#include "d/d_s_menu.h"
@@ -201,6 +205,9 @@ int daAlink_c::setDamagePoint(int i_dmgAmount, BOOL i_checkZoraMag, BOOL i_setDm
dComIfGp_setItemLifeCount(-i_dmgAmount, 0);
}
+#if TARGET_PC
+ dusk::AchievementSystem::get().signal("player_damaged");
+#endif
onResetFlg1(RFLG1_DAMAGE_IMPACT);
mSwordUpTimer = 0;
@@ -677,6 +684,15 @@ BOOL daAlink_c::checkDamageAction() {
}
setDamagePoint(dmg, at_mtrl == dCcD_MTRL_FIRE || at_mtrl == dCcD_MTRL_ICE, TRUE, 0);
+
+#ifdef TARGET_PC
+ if (tghit_ac_name == fpcNm_Obj_Carry_e) {
+ auto* carry = static_cast(tghit_ac);
+ if (carry->prm_chk_type_ironball() && carry->checkCannon()) {
+ dusk::AchievementSystem::get().signal("iron_ball_hit_player");
+ }
+ }
+#endif
if (armor_no_dmg && at_mtrl != dCcD_MTRL_ELECTRIC && at_mtrl != dCcD_MTRL_ICE) {
setGuardSe(var_r29);
diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc
index aa77b24129..5ac30a3cb9 100644
--- a/src/d/actor/d_a_alink_demo.inc
+++ b/src/d/actor/d_a_alink_demo.inc
@@ -23,6 +23,7 @@
#include "d/actor/d_a_npc_tkc.h"
#include
+#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
BOOL daAlink_c::checkEventRun() const {
@@ -4005,6 +4006,15 @@ int daAlink_c::procGanonFinishInit() {
field_0x37c8 = current.pos;
onEndResetFlg1(ERFLG1_SHIELD_BACKBONE);
+
+#if TARGET_PC
+ if (dusk::getSettings().game.speedrunMode) {
+ if (dusk::m_speedrunInfo.m_isRunStarted) {
+ dusk::m_speedrunInfo.stopRun();
+ }
+ }
+#endif
+
return 1;
}
diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp
index d53476aa91..8c1d415ae0 100644
--- a/src/d/actor/d_a_alink_dusk.cpp
+++ b/src/d/actor/d_a_alink_dusk.cpp
@@ -44,16 +44,14 @@ void daAlink_c::handleWolfHowl() {
bool canHowl = false;
if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) {
- if (!checkForestOldCentury()) {
- if (checkMidnaRide()) {
- if ((checkWolf() &&
- (checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
- (!checkWolf() &&
- (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
- (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
- {
- canHowl = true;
- }
+ if (checkMidnaRide()) {
+ if ((checkWolf() &&
+ (checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
+ (!checkWolf() &&
+ (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
+ (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
+ {
+ canHowl = true;
}
}
}
@@ -124,16 +122,14 @@ void daAlink_c::handleQuickTransform() {
bool canTransform = false;
if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) {
- if (!checkForestOldCentury()) {
- if (checkMidnaRide()) {
- if ((checkWolf() &&
- (checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
- (!checkWolf() &&
- (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
- (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
- {
- canTransform = true;
- }
+ if (checkMidnaRide()) {
+ if ((checkWolf() &&
+ (checkModeFlg(MODE_UNK_1000) || dComIfGp_checkPlayerStatus0(0, 0x10))) ||
+ (!checkWolf() &&
+ (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) &&
+ (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10))))
+ {
+ canTransform = true;
}
}
}
diff --git a/src/d/actor/d_a_alink_hook.inc b/src/d/actor/d_a_alink_hook.inc
index d152190161..36fba0c6d8 100644
--- a/src/d/actor/d_a_alink_hook.inc
+++ b/src/d/actor/d_a_alink_hook.inc
@@ -8,6 +8,8 @@
#include "d/actor/d_a_obj_swhang.h"
#include "d/actor/d_a_obj_chandelier.h"
#include "JSystem/J3DGraphBase/J3DMaterial.h"
+#include "dusk/frame_interpolation.h"
+#include "dusk/settings.h"
enum {
HS_MODE_NONE_e,
@@ -17,6 +19,10 @@ enum {
HS_MODE_RETURN_e = 6,
};
+#if TARGET_PC
+static const int HS_CHAIN_MAX_LINKS = 600;
+#endif
+
void daAlink_c::hsChainShape_c::draw() {
static const int dummy = 0;
@@ -161,7 +167,11 @@ void daAlink_c::hsChainShape_c::draw() {
}
(void)0;
- while (maxDistanceF > var_f30) {
+#if TARGET_PC
+ int chainLinks = 0;
+#endif
+
+ while (maxDistanceF > var_f30 IF_DUSK(&&chainLinks < HS_CHAIN_MAX_LINKS)) {
temp_f27 = var_f28 * cM_fsin(sp34 * var_f30);
s16 spC = cM_atan2s(temp_f27 - var_f26, 5.0f);
sp64.x = sp6C.x + spC;
@@ -183,6 +193,10 @@ void daAlink_c::hsChainShape_c::draw() {
var_f26 = temp_f27;
var_f30 += fabsf(cM_scos(spC)) * 5.0f;
+
+#if TARGET_PC
+ chainLinks++;
+#endif
}
}
@@ -198,7 +212,11 @@ void daAlink_c::hsChainShape_c::draw() {
sp98 = subChainTopPos;
sp6C.set(maxDistance.atan2sY_XZ(), maxDistance.atan2sX_Z(), 0);
- while (maxDistanceF > var_f30) {
+#if TARGET_PC
+ int subChainLinks = 0;
+#endif
+
+ while (maxDistanceF > var_f30 IF_DUSK(&&subChainLinks < HS_CHAIN_MAX_LINKS)) {
mDoMtx_stack_c::copy(j3dSys.getViewMtx());
mDoMtx_stack_c::transM(sp98);
mDoMtx_stack_c::ZXYrotM(sp6C);
@@ -211,11 +229,39 @@ void daAlink_c::hsChainShape_c::draw() {
sp98 += maxDistance * 5.0f;
ANGLE_ADD_2(sp6C.z, 0x3000);
var_f30 += 5.0f;
+#if TARGET_PC
+ subChainLinks++;
+#endif
}
}
}
}
+#if TARGET_PC
+static void ironBallChainInterpCallback(bool isSimFrame, void* pUserWork) {
+ static_cast(pUserWork)->onIronBallChainInterpCallback();
+}
+
+void daAlink_c::onIronBallChainInterpCallback() {
+ if (!mIBChainInterpPrevValid || !mIBChainInterpCurrValid) {
+ return;
+ }
+ if (mIronBallChainPos == NULL || mIronBallChainAngle == NULL) {
+ return;
+ }
+
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+
+ for (int i = 0; i < IRON_BALL_CHAIN_COUNT; i++) {
+ mIronBallChainPos[i] = mIBChainInterpPrevPos[i] + (mIBChainInterpCurrPos[i] - mIBChainInterpPrevPos[i]) * alpha;
+ mIronBallChainAngle[i].x = mIBChainInterpPrevAngle[i].x + (s16)((s16)(mIBChainInterpCurrAngle[i].x - mIBChainInterpPrevAngle[i].x) * alpha);
+ mIronBallChainAngle[i].y = mIBChainInterpPrevAngle[i].y + (s16)((s16)(mIBChainInterpCurrAngle[i].y - mIBChainInterpPrevAngle[i].y) * alpha);
+ mIronBallChainAngle[i].z = mIBChainInterpPrevAngle[i].z + (s16)((s16)(mIBChainInterpCurrAngle[i].z - mIBChainInterpPrevAngle[i].z) * alpha);
+ }
+ mHookshotTopPos = mIBChainInterpPrevHandRoot + (mIBChainInterpCurrHandRoot - mIBChainInterpPrevHandRoot) * alpha;
+}
+#endif
+
void daAlink_c::hookshotAtHitCallBack(dCcD_GObjInf* i_atObjInf, fopAc_ac_c* i_tgActor,
dCcD_GObjInf* i_tgObjInf) {
if (i_tgActor != NULL && fopAcM_IsActor(i_tgActor) && !i_tgObjInf->ChkTgHookshotThrough()) {
diff --git a/src/d/actor/d_a_alink_horse.inc b/src/d/actor/d_a_alink_horse.inc
index ac7ce73e50..f0c585f1b8 100644
--- a/src/d/actor/d_a_alink_horse.inc
+++ b/src/d/actor/d_a_alink_horse.inc
@@ -2721,7 +2721,7 @@ int daAlink_c::procHorseRun() {
}
if (mProcVar2.field_0x300c == 0) {
- set3DStatus(BUTTON_STATUS_HOLD_ON, 4);
+ set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 1 :) 4);
}
} else {
if (mProcVar3.field_0x300e != 0) {
@@ -2731,7 +2731,7 @@ int daAlink_c::procHorseRun() {
}
if (mProcVar2.field_0x300c == 0) {
- set3DStatus(BUTTON_STATUS_HOLD_ON, 1);
+ set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 4 :) 1);
}
}
diff --git a/src/d/actor/d_a_alink_swindow.inc b/src/d/actor/d_a_alink_swindow.inc
index 81c7a1ca18..9016f14205 100644
--- a/src/d/actor/d_a_alink_swindow.inc
+++ b/src/d/actor/d_a_alink_swindow.inc
@@ -41,12 +41,11 @@ void daAlink_c::setOriginalHeap(JKRExpHeap** i_ppheap, u32 i_size) {
u32 var_r28 = 0x10;
u32 size = ROUND(i_size, 16);
#if TARGET_PC
- size *= 2;
+ size *= 20; // Increase Link's heap size to prevent mods from crashing with higher-quality models.
#endif
JKRHeap* parent = mDoExt_getGameHeap();
JKRExpHeap* heap = JKRExpHeap::create(size + (var_r29 + var_r28), parent, true);
- JKRHEAP_NAME(heap, "Alink original");
*i_ppheap = heap;
}
}
diff --git a/src/d/actor/d_a_arrow.cpp b/src/d/actor/d_a_arrow.cpp
index 769c3273e9..c18d19e149 100644
--- a/src/d/actor/d_a_arrow.cpp
+++ b/src/d/actor/d_a_arrow.cpp
@@ -17,6 +17,9 @@
#include "d/actor/d_a_e_pz.h"
#include "d/actor/d_a_horse.h"
#include "d/actor/d_a_hozelda.h"
+#if TARGET_PC
+#include "dusk/achievements.h"
+#endif
int daArrow_c::createHeap() {
J3DModelData* model_data;
@@ -92,7 +95,12 @@ void daArrow_c::atHitCallBack(dCcD_GObjInf* i_atObjInf, fopAc_ac_c* i_tgActor, d
if (dist_to_hitpos < field_0x998) {
field_0x998 = dist_to_hitpos;
mHitAcID = fopAcM_GetID(i_tgActor);
-
+#if TARGET_PC
+ if (fopAcM_GetGroup(i_tgActor) == fopAc_ENEMY_e &&
+ current.pos.abs(mStartPos) > 10000.0f) {
+ dusk::AchievementSystem::get().signal("arrow_hit_100m");
+ }
+#endif
if (mArrowType == 1) {
field_0x9a8 = *hit_pos_p;
} else if (i_tgObjInf->ChkTgShield()) {
diff --git a/src/d/actor/d_a_b_gnd.cpp b/src/d/actor/d_a_b_gnd.cpp
index 2272c35376..f5b03be1b1 100644
--- a/src/d/actor/d_a_b_gnd.cpp
+++ b/src/d/actor/d_a_b_gnd.cpp
@@ -17,6 +17,12 @@
#include "Z2AudioLib/Z2Instances.h"
+#include "dusk/frame_interpolation.h"
+#include "dusk/settings.h"
+#if TARGET_PC
+#include "dusk/achievements.h"
+#endif
+
class daB_GND_HIO_c : public JORReflexible {
public:
daB_GND_HIO_c();
@@ -279,6 +285,30 @@ static int h_nodeCallBack(J3DJoint* i_joint, int param_2) {
return 1;
}
+#if TARGET_PC
+static void b_gnd_rein_interp_callback(bool isSimFrame, void* pUserWork) {
+ b_gnd_class* i_this = (b_gnd_class*)pUserWork;
+ if (!i_this->mReinsInterpPrevValid || !i_this->mReinsInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ for (int r = 0; r < 2; r++) {
+ cXyz* dst = i_this->mHorseReins[r].getPos(0);
+ for (int i = 0; i < 16; i++) {
+ const cXyz& p0 = i_this->mReinsInterpPrev[r][i];
+ const cXyz& p1 = i_this->mReinsInterpCurr[r][i];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+ }
+ cXyz* dst = i_this->field_0x21e8.getPos(0);
+ for (int i = 0; i < 2; i++) {
+ const cXyz& p0 = i_this->mReinsTexInterpPrev[i];
+ const cXyz& p1 = i_this->mReinsTexInterpCurr[i];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+}
+#endif
+
static int daB_GND_Draw(b_gnd_class* i_this) {
fopAc_ac_c* a_this = (fopAc_ac_c*)i_this;
@@ -366,6 +396,21 @@ static int daB_GND_Draw(b_gnd_class* i_this) {
i_this->field_0x21e8.update(2, l_color, &a_this->tevStr);
dComIfGd_set3DlineMat(&i_this->field_0x21e8);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mReinsInterpCurrValid) {
+ memcpy(i_this->mReinsInterpPrev, i_this->mReinsInterpCurr, sizeof(i_this->mReinsInterpCurr));
+ memcpy(i_this->mReinsTexInterpPrev, i_this->mReinsTexInterpCurr, sizeof(i_this->mReinsTexInterpCurr));
+ i_this->mReinsInterpPrevValid = true;
+ }
+ for (int r = 0; r < 2; r++) {
+ memcpy(i_this->mReinsInterpCurr[r], i_this->mHorseReins[r].getPos(0), 16 * sizeof(cXyz));
+ }
+ memcpy(i_this->mReinsTexInterpCurr, i_this->field_0x21e8.getPos(0), 2 * sizeof(cXyz));
+ i_this->mReinsInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&b_gnd_rein_interp_callback, i_this);
+ }
+#endif
}
return 1;
@@ -1189,10 +1234,16 @@ static void b_gnd_h_end(b_gnd_class* i_this) {
if (i_this->mDemoCamMode < 32) {
i_this->mDemoCamMode = 32;
+#if TARGET_PC
+ i_this->mDemoCamSyncTicks = 2;
+#endif
} else {
i_this->mDemoCamMode = 34;
i_this->mDemoCamTimer = 0;
i_this->mMoveMode = 2;
+#if TARGET_PC
+ i_this->mDemoCamSyncTicks = 2;
+#endif
}
}
break;
@@ -1241,6 +1292,9 @@ static void b_gnd_g_wait(b_gnd_class* i_this) {
if (i_this->mMoveMode < 5 && i_this->mPlayerDistXZ < 600.0f) {
i_this->mMoveMode = 5;
i_this->field_0xc44[0] = 10;
+#if TARGET_PC
+ dusk::AchievementSystem::get().signal("ganondorf_fishing_rod");
+#endif
}
} else if (i_this->mMoveMode == 5) {
i_this->mMoveMode = 6;
@@ -2887,6 +2941,9 @@ static void demo_camera(b_gnd_class* i_this) {
cXyz spF0;
s8 sp8 = false;
+#if TARGET_PC
+ const s16 entry_demo_cam_mode = i_this->mDemoCamMode;
+#endif
switch (i_this->mDemoCamMode) {
case 0:
break;
@@ -3725,6 +3782,15 @@ static void demo_camera(b_gnd_class* i_this) {
i_this->mDemoCamTimer = 10000;
}
}
+#if TARGET_PC
+ if (entry_demo_cam_mode != i_this->mDemoCamMode) {
+ i_this->mDemoCamSyncTicks = 2;
+ }
+ if (i_this->mDemoCamSyncTicks > 0) {
+ dusk::frame_interp::request_presentation_sync();
+ i_this->mDemoCamSyncTicks--;
+ }
+#endif
}
static void anm_se_set(b_gnd_class* i_this) {
diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp
index 6b5b5a2bd2..8627505371 100644
--- a/src/d/actor/d_a_balloon_2D.cpp
+++ b/src/d/actor/d_a_balloon_2D.cpp
@@ -6,6 +6,7 @@
#include "d/dolzel_rel.h" // IWYU pragma: keep
#include "d/actor/d_a_balloon_2D.h"
+#include "dusk/frame_interpolation.h"
#include "JSystem/J2DGraph/J2DGrafContext.h"
#include "JSystem/J2DGraph/J2DScreen.h"
#include "JSystem/J2DGraph/J2DTextBox.h"
@@ -438,7 +439,12 @@ void daBalloon2D_c::setComboAlpha() {
void daBalloon2D_c::drawAddScore() {
for (s32 i = 19; i >= 0; i--) {
if (field_0x5f8[i].field_0xe != 0) {
- field_0x5f8[i].field_0xe--;
+#ifdef TARGET_PC
+ if (dusk::frame_interp::get_ui_tick_pending())
+#endif
+ {
+ field_0x5f8[i].field_0xe--;
+ }
s32 score3;
s32 score2;
s32 score = field_0x5f8[i].field_0xc;
@@ -446,8 +452,13 @@ void daBalloon2D_c::drawAddScore() {
u8 local_88 = 0xff;
f32 dVar11 = 30.0f;
f32 dVar9 = 30.0f;
- field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f;
- field_0x5f8[i].field_0x0.y -= 1.0f;
+#ifdef TARGET_PC
+ if (dusk::frame_interp::get_ui_tick_pending())
+#endif
+ {
+ field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f;
+ field_0x5f8[i].field_0x0.y -= 1.0f;
+ }
if (field_0x5f8[i].field_0xe < 10) {
f32 fVar5 = field_0x5f8[i].field_0xe / 10.0f;
local_88 = fVar5 * 255.0f;
diff --git a/src/d/actor/d_a_door_boss.cpp b/src/d/actor/d_a_door_boss.cpp
index 476a9c8ad6..bf042b3323 100644
--- a/src/d/actor/d_a_door_boss.cpp
+++ b/src/d/actor/d_a_door_boss.cpp
@@ -254,7 +254,11 @@ BOOL daBdoor_c::checkArea() {
if (fabsf(vec.z) > 100.0f) {
return false;
}
+#ifdef TARGET_PC
+ return (s16)((s32)fabs(current.angle.y - 0x7fff - player->current.angle.y) & 0xffff) <= 0x4000 ? 1 : 0;
+#else
return (s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000 ? 1 : 0;
+#endif
}
BOOL daBdoor_c::checkFront() {
diff --git a/src/d/actor/d_a_door_bossL1.cpp b/src/d/actor/d_a_door_bossL1.cpp
index a756cc6fd2..649ebbd5f3 100644
--- a/src/d/actor/d_a_door_bossL1.cpp
+++ b/src/d/actor/d_a_door_bossL1.cpp
@@ -825,7 +825,11 @@ int daBdoorL1_c::checkArea() {
if (fabsf(local_48.z) > 100.0f) {
return 0;
}
+#ifdef TARGET_PC
+ if ((s16)((s32)fabs(current.angle.y - 0x7fff - player->current.angle.y) & 0xffff) <= 0x4000) {
+#else
if ((s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000) {
+#endif
return 1;
} else {
return 0;
diff --git a/src/d/actor/d_a_door_bossL5.cpp b/src/d/actor/d_a_door_bossL5.cpp
index d71fddad1f..34dac15fa9 100644
--- a/src/d/actor/d_a_door_bossL5.cpp
+++ b/src/d/actor/d_a_door_bossL5.cpp
@@ -348,7 +348,11 @@ int daBdoorL5_c::checkArea() {
if (fabsf(local_48.z) > 100.0f) {
return 0;
}
+#ifdef TARGET_PC
+ if ((s16)((s32)fabs(current.angle.y - 0x7fff - player->current.angle.y) & 0xffff) <= 0x4000) {
+#else
if ((s16)fabs((f64)(current.angle.y - 0x7fff - player->current.angle.y)) <= 0x4000) {
+#endif
return 1;
} else {
return 0;
diff --git a/src/d/actor/d_a_door_mbossL1.cpp b/src/d/actor/d_a_door_mbossL1.cpp
index 1f37fbcc7b..5dfd3bb96c 100644
--- a/src/d/actor/d_a_door_mbossL1.cpp
+++ b/src/d/actor/d_a_door_mbossL1.cpp
@@ -1317,8 +1317,12 @@ int daMBdoorL1_c::checkArea() {
if (fabsf(local_48.z) > 110.0f) {
return 0;
}
-
+
+#ifdef TARGET_PC
+ if ((s16)((s32)fabs(angle - 0x7fff - player->current.angle.y) & 0xffff) > 0x4000) {
+#else
if ((s16)fabs((f64)(angle - 0x7fff - player->current.angle.y)) > 0x4000) {
+#endif
return 0;
} else {
return 1;
diff --git a/src/d/actor/d_a_door_shutter.cpp b/src/d/actor/d_a_door_shutter.cpp
index a481762132..ebd1c9a185 100644
--- a/src/d/actor/d_a_door_shutter.cpp
+++ b/src/d/actor/d_a_door_shutter.cpp
@@ -17,6 +17,11 @@
#include
#include
+#if TARGET_PC
+#include
+#include
+#endif
+
char* daDoor20_c::getStopBmdName() {
switch (door_param2_c::getKind(this)) {
case 3:
@@ -196,6 +201,7 @@ void daDoor20_c::setEventPrm() {
} else {
roomNo = FRoomNo;
}
+
if (dComIfGp_roomControl_checkStatusFlag(roomNo, 1)) {
if (door_param2_c::getKind(this) == 9) {
if (daPy_py_c::checkNowWolf()) {
@@ -564,6 +570,11 @@ int daDoor20_c::openEnd(int param_1) {
openEnd_1();
break;
}
+
+ #if TARGET_PC
+ triggerAutoSave();
+ #endif
+
return 1;
}
diff --git a/src/d/actor/d_a_e_db.cpp b/src/d/actor/d_a_e_db.cpp
index cbd21176b9..4d60fbd6f6 100644
--- a/src/d/actor/d_a_e_db.cpp
+++ b/src/d/actor/d_a_e_db.cpp
@@ -10,6 +10,10 @@
#include "f_op/f_op_kankyo_mng.h"
#include "f_op/f_op_actor_enemy.h"
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
class daE_DB_HIO_c : public JORReflexible {
public:
daE_DB_HIO_c();
@@ -66,6 +70,22 @@ static BOOL leaf_anm_init(e_db_class* i_this, int i_anm, f32 i_morf, u8 i_mode,
return FALSE;
}
+#if TARGET_PC
+static void daE_DB_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_db_class* i_this = (e_db_class*)pUserWork;
+ if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ cXyz* dst = i_this->stalkLine.getPos(0);
+ for (int i = 0; i < 12; i++) {
+ const cXyz& p0 = i_this->mStalkLineInterpPrev[i];
+ const cXyz& p1 = i_this->mStalkLineInterpCurr[i];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+}
+#endif
+
static int daE_DB_Draw(e_db_class* i_this) {
fopAc_ac_c* actor = &i_this->enemy;
@@ -95,6 +115,17 @@ static int daE_DB_Draw(e_db_class* i_this) {
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
i_this->stalkLine.update(12, l_color, &actor->tevStr);
dComIfGd_set3DlineMat(&i_this->stalkLine);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mStalkLineInterpCurrValid) {
+ memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr));
+ i_this->mStalkLineInterpPrevValid = true;
+ }
+ memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz));
+ i_this->mStalkLineInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&daE_DB_interp_callback, i_this);
+ }
+#endif
for (int i = 1; i < 11; i++) {
if (i_this->thornModel[i] != NULL) {
diff --git a/src/d/actor/d_a_e_hb.cpp b/src/d/actor/d_a_e_hb.cpp
index d3177ba0f6..8ed9058c6f 100644
--- a/src/d/actor/d_a_e_hb.cpp
+++ b/src/d/actor/d_a_e_hb.cpp
@@ -9,6 +9,10 @@
#include "d/actor/d_a_e_hb_leaf.h"
#include "f_op/f_op_actor_enemy.h"
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
enum daE_HB_ACTION {
ACTION_STAY,
ACTION_APPEAR,
@@ -64,6 +68,22 @@ static BOOL leaf_anm_init(e_hb_class* i_this, int i_anm, f32 i_morf, u8 i_mode,
return FALSE;
}
+#if TARGET_PC
+static void daE_HB_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_hb_class* i_this = (e_hb_class*)pUserWork;
+ if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ cXyz* dst = i_this->stalkLine.getPos(0);
+ for (int i = 0; i < 12; i++) {
+ const cXyz& p0 = i_this->mStalkLineInterpPrev[i];
+ const cXyz& p1 = i_this->mStalkLineInterpCurr[i];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+}
+#endif
+
static int daE_HB_Draw(e_hb_class* i_this) {
fopAc_ac_c* actor = &i_this->enemy;
@@ -82,6 +102,17 @@ static int daE_HB_Draw(e_hb_class* i_this) {
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
i_this->stalkLine.update(12, l_color, &actor->tevStr);
dComIfGd_set3DlineMat(&i_this->stalkLine);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mStalkLineInterpCurrValid) {
+ memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr));
+ i_this->mStalkLineInterpPrevValid = true;
+ }
+ memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz));
+ i_this->mStalkLineInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&daE_HB_interp_callback, i_this);
+ }
+#endif
for (int i = 1; i < 11; i++) {
if (i_this->thornModel[i] != NULL) {
diff --git a/src/d/actor/d_a_e_mb.cpp b/src/d/actor/d_a_e_mb.cpp
index 939b253475..aa77a99612 100644
--- a/src/d/actor/d_a_e_mb.cpp
+++ b/src/d/actor/d_a_e_mb.cpp
@@ -12,6 +12,8 @@
#include "d/d_bomb.h"
#include "c/c_damagereaction.h"
#include "Z2AudioLib/Z2Instances.h"
+#include "dusk/frame_interpolation.h"
+#include "dusk/settings.h"
#define ACTION_STANDBY 0
#define ACTION_WALK1 1
@@ -63,6 +65,22 @@ static void anm_init(e_mb_class* i_this, int i_anmID, f32 i_morf, u8 i_attr, f32
i_this->mAnm = i_anmID;
}
+#if TARGET_PC
+static void e_mb_rope_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_mb_class* i_this = (e_mb_class*)pUserWork;
+ if (!i_this->mRopeInterpPrevValid || !i_this->mRopeInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ cXyz* dst = i_this->mRopeMat.getPos(0);
+ for (int i = 0; i < 16; i++) {
+ const cXyz& p0 = i_this->mRopeInterpPrev[i];
+ const cXyz& p1 = i_this->mRopeInterpCurr[i];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+}
+#endif
+
static int daE_MB_Draw(e_mb_class* i_this) {
fopAc_ac_c* a_this = (fopAc_ac_c*)i_this;
@@ -86,6 +104,17 @@ static int daE_MB_Draw(e_mb_class* i_this) {
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
i_this->mRopeMat.update(16, l_color, &a_this->tevStr);
dComIfGd_set3DlineMat(&i_this->mRopeMat);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mRopeInterpCurrValid) {
+ memcpy(i_this->mRopeInterpPrev, i_this->mRopeInterpCurr, sizeof(i_this->mRopeInterpCurr));
+ i_this->mRopeInterpPrevValid = true;
+ }
+ memcpy(i_this->mRopeInterpCurr, i_this->mRopeMat.getPos(0), 16 * sizeof(cXyz));
+ i_this->mRopeInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&e_mb_rope_interp_callback, i_this);
+ }
+#endif
return 1;
}
diff --git a/src/d/actor/d_a_e_oct_bg.cpp b/src/d/actor/d_a_e_oct_bg.cpp
index 14a421a3b6..94fa69596b 100644
--- a/src/d/actor/d_a_e_oct_bg.cpp
+++ b/src/d/actor/d_a_e_oct_bg.cpp
@@ -517,6 +517,12 @@ void daE_OctBg_c::core_fish_attack() {
field_0xbaf = cM_rndFX(80.0f) + 100.0f;
}
}
+ #if AVOID_UB
+ else {
+ in_f31 = cM_rndF(400.0f) + 80.0f;
+ field_0xbaf = cM_rndFX(80.0f) + 100.0f;
+ }
+ #endif
} else if (current.pos.abs(cStack_5c) < 400.0f) {
in_f31 = cM_rndF(50.0f) + 20.0f;
field_0xbaf = cM_rndFX(20.0f) + 40.0f;
diff --git a/src/d/actor/d_a_e_s1.cpp b/src/d/actor/d_a_e_s1.cpp
index 48cd7d6800..a5a05fce20 100644
--- a/src/d/actor/d_a_e_s1.cpp
+++ b/src/d/actor/d_a_e_s1.cpp
@@ -14,6 +14,8 @@
#include "d/d_s_play.h"
#include "f_op/f_op_actor_enemy.h"
#include "f_op/f_op_camera_mng.h"
+#include "dusk/frame_interpolation.h"
+#include "dusk/settings.h"
#include
class daE_S1_HIO_c {
@@ -99,6 +101,25 @@ static void anm_init(e_s1_class* i_this, int i_resNo, f32 i_morf, u8 i_attr, f32
i_this->mAnm = i_resNo;
}
+#if TARGET_PC
+static void daE_S1_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_s1_class* i_this = (e_s1_class*)pUserWork;
+ if (!i_this->mHairInterpPrevValid || !i_this->mHairInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) {
+ cXyz* dst = i_this->mLineMat.getPos(s);
+ for (int i = 0; i < e_s1_class::HAIR_SEGMENT_COUNT; i++) {
+ int idx = s * e_s1_class::HAIR_SEGMENT_COUNT + i;
+ const cXyz& p0 = i_this->mHairInterpPrev[idx];
+ const cXyz& p1 = i_this->mHairInterpCurr[idx];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+ }
+}
+#endif
+
static int daE_S1_Draw(e_s1_class* i_this) {
if (i_this->field_0x306c != 0) {
return 1;
@@ -132,6 +153,22 @@ static int daE_S1_Draw(e_s1_class* i_this) {
i_this->mLineMat.update(16, line_color, &i_this->tevStr);
dComIfGd_set3DlineMatDark(&i_this->mLineMat);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mHairInterpCurrValid) {
+ memcpy(i_this->mHairInterpPrev, i_this->mHairInterpCurr, sizeof(i_this->mHairInterpCurr));
+ i_this->mHairInterpPrevValid = true;
+ }
+ for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) {
+ cXyz* src = i_this->mLineMat.getPos(s);
+ memcpy(&i_this->mHairInterpCurr[s * e_s1_class::HAIR_SEGMENT_COUNT], src,
+ e_s1_class::HAIR_SEGMENT_COUNT * sizeof(cXyz));
+ }
+ i_this->mHairInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&daE_S1_interp_callback, i_this);
+ }
+#endif
+
dComIfGd_setList();
return 1;
}
@@ -2149,6 +2186,11 @@ static int daE_S1_Create(fopAc_ac_c* i_this) {
return cPhs_ERROR_e;
}
+#if TARGET_PC
+ a_this->mHairInterpPrevValid = false;
+ a_this->mHairInterpCurrValid = false;
+#endif
+
OS_REPORT("//////////////E_S1 SET 2 !!\n");
if (path_no != 0xFF) {
diff --git a/src/d/actor/d_a_e_sm2.cpp b/src/d/actor/d_a_e_sm2.cpp
index fdbc338670..f55c4411cb 100644
--- a/src/d/actor/d_a_e_sm2.cpp
+++ b/src/d/actor/d_a_e_sm2.cpp
@@ -14,6 +14,10 @@
#include "f_op/f_op_camera_mng.h"
#include
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
class daE_SM2_HIO_c : public fOpAcm_HIO_entry_c {
public:
daE_SM2_HIO_c();
@@ -76,8 +80,62 @@ static int nodeCallBack(J3DJoint* i_joint, int param_1) {
return 1;
}
+#if TARGET_PC
+static void daE_SM2_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_sm2_class* i_this = static_cast(pUserWork);
+ if (i_this == NULL) {
+ return;
+ }
+
+ fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy;
+ g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr);
+
+ if (!i_this->isPiece) {
+ if (i_this->modelMorf == NULL) {
+ return;
+ }
+ J3DModel* model = i_this->modelMorf->getModel();
+ if (model == NULL) {
+ return;
+ }
+ g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr);
+
+ J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0);
+ material->getTevKColor(1)->r = i_this->color_R;
+ material->getTevKColor(1)->g = i_this->color_G;
+ material->getTevKColor(1)->b = i_this->color_B;
+ material->getTevKColor(1)->a = 217.0f * i_this->color_alpha;
+
+ if (i_this->pbtk != NULL) {
+ i_this->pbtk->entry(model->getModelData());
+ }
+ } else {
+ if (i_this->pieceModelMorf == NULL) {
+ return;
+ }
+ J3DModel* model = i_this->pieceModelMorf->getModel();
+ if (model == NULL) {
+ return;
+ }
+
+ J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0);
+ material->getTevKColor(1)->r = i_this->color_R;
+ material->getTevKColor(1)->g = i_this->color_G;
+ material->getTevKColor(1)->b = i_this->color_B;
+ material->getTevKColor(1)->a = 217.0f * i_this->color_alpha;
+
+ g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr);
+ }
+}
+#endif
+
static int daE_SM2_Draw(e_sm2_class* i_this) {
fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy;
+
+#if TARGET_PC
+ dusk::frame_interp::add_interpolation_callback(&daE_SM2_interp_callback, i_this);
+#endif
+
g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr);
J3DModel* model;
diff --git a/src/d/actor/d_a_e_th.cpp b/src/d/actor/d_a_e_th.cpp
index f963350da4..056e9f565e 100644
--- a/src/d/actor/d_a_e_th.cpp
+++ b/src/d/actor/d_a_e_th.cpp
@@ -12,6 +12,9 @@
#include "c/c_damagereaction.h"
#include "f_op/f_op_actor_enemy.h"
#include "f_op/f_op_camera_mng.h"
+#if TARGET_PC
+#include "dusk/achievements.h"
+#endif
class daE_TH_HIO_c : public JORReflexible {
public:
@@ -282,6 +285,11 @@ static void e_th_spin_B(e_th_class* i_this) {
i_this->current.pos += spC;
f32 speed_target;
+
+ #if AVOID_UB
+ speed_target = 0;
+ #endif
+
f32 anm_frame = i_this->mpModelMorf->getFrame();
switch (i_this->mMode) {
@@ -537,6 +545,7 @@ static void damage_check(e_th_class* i_this) {
if (i_this->field_0x6a4 == 0 && i_this->mAction != ACTION_SPIN) {
daPy_py_c* player = (daPy_py_c*)dComIfGp_getPlayer(0);
OS_REPORT("E_th HP1 %d\n", i_this->health);
+ s16 prevHealth = i_this->health;
cc_at_check(i_this, &i_this->mAtInfo);
OS_REPORT("E_th HP2 %d\n", i_this->health);
@@ -549,6 +558,11 @@ static void damage_check(e_th_class* i_this) {
dComIfGs_onOneZoneSwitch(3, -1);
if (i_this->health <= 0) {
+#if TARGET_PC
+ if (prevHealth == i_this->field_0x560) {
+ dusk::AchievementSystem::get().signal("dark_hammer_one_hit");
+ }
+#endif
i_this->mAction = ACTION_END;
i_this->mMode = 0;
i_this->field_0x68a |= 4;
diff --git a/src/d/actor/d_a_e_wb.cpp b/src/d/actor/d_a_e_wb.cpp
index 75031c42bb..47004737cb 100644
--- a/src/d/actor/d_a_e_wb.cpp
+++ b/src/d/actor/d_a_e_wb.cpp
@@ -5852,6 +5852,8 @@ static int daE_WB_Create(fopAc_ac_c* actor) {
daE_WB_Execute(i_this);
c_start = 0;
+ // Note: this flag makes king bulblin 1 instant die when set, as it only requires 2 laps
+ // for insta-kill to trigger.
if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[88])) {
i_this->lap_num = 1;
}
diff --git a/src/d/actor/d_a_e_yd.cpp b/src/d/actor/d_a_e_yd.cpp
index bdcd044eed..18809e05ea 100644
--- a/src/d/actor/d_a_e_yd.cpp
+++ b/src/d/actor/d_a_e_yd.cpp
@@ -12,6 +12,10 @@
#include "d/d_cc_uty.h"
#include "f_op/f_op_actor_enemy.h"
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
class daE_YD_HIO_c {
public:
daE_YD_HIO_c();
@@ -73,6 +77,22 @@ static s32 leaf_anm_init(e_yd_class* i_this, int param_1, f32 param_2, u8 param_
return false;
}
+#if TARGET_PC
+static void daE_YD_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_yd_class* i_this = (e_yd_class*)pUserWork;
+ if (!i_this->mLineMatInterpPrevValid || !i_this->mLineMatInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ cXyz* dst = i_this->mLineMat.getPos(0);
+ for (int i = 0; i < 12; i++) {
+ const cXyz& p0 = i_this->mLineMatInterpPrev[i];
+ const cXyz& p1 = i_this->mLineMatInterpCurr[i];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+}
+#endif
+
static s32 daE_YD_Draw(e_yd_class* i_this) {
static GXColor l_color = { 0x14, 0x0F, 0x00, 0xFF };
@@ -86,6 +106,17 @@ static s32 daE_YD_Draw(e_yd_class* i_this) {
i_this->mpMorf->entryDL();
i_this->mLineMat.update(12, l_color, &i_this->actor.tevStr);
dComIfGd_set3DlineMat(&i_this->mLineMat);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mLineMatInterpCurrValid) {
+ memcpy(i_this->mLineMatInterpPrev, i_this->mLineMatInterpCurr, sizeof(i_this->mLineMatInterpCurr));
+ i_this->mLineMatInterpPrevValid = true;
+ }
+ memcpy(i_this->mLineMatInterpCurr, i_this->mLineMat.getPos(0), 12 * sizeof(cXyz));
+ i_this->mLineMatInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&daE_YD_interp_callback, i_this);
+ }
+#endif
for (s32 i = 1; i < 11; i++) {
if (i_this->field_0x77c[i] != 0) {
g_env_light.setLightTevColorType_MAJI(i_this->field_0x77c[i], &i_this->actor.tevStr);
diff --git a/src/d/actor/d_a_e_yg.cpp b/src/d/actor/d_a_e_yg.cpp
index 9ab6c80e6d..44a8aee249 100644
--- a/src/d/actor/d_a_e_yg.cpp
+++ b/src/d/actor/d_a_e_yg.cpp
@@ -10,6 +10,8 @@
#include "f_op/f_op_kankyo_mng.h"
#include "d/actor/d_a_obj_carry.h"
#include "Z2AudioLib/Z2Instances.h"
+#include "dusk/frame_interpolation.h"
+#include "dusk/settings.h"
#include "f_op/f_op_actor_enemy.h"
enum E_yg_RES_File_ID {
@@ -134,7 +136,26 @@ static BOOL pl_check(e_yg_class* i_this, f32 i_dist) {
return FALSE;
}
-static int daE_YG_Draw(e_yg_class* i_this) {
+#if TARGET_PC
+static void daE_YG_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_yg_class* i_this = (e_yg_class*)pUserWork;
+ if (!i_this->mTentacleInterpPrevValid || !i_this->mTentacleInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ for (int s = 0; s < e_yg_class::TENTACLE_STRAND_COUNT; s++) {
+ cXyz* dst = i_this->mLineMat.getPos(s);
+ for (int i = 0; i < e_yg_class::TENTACLE_SEGMENT_COUNT; i++) {
+ int idx = s * e_yg_class::TENTACLE_SEGMENT_COUNT + i;
+ const cXyz& p0 = i_this->mTentacleInterpPrev[idx];
+ const cXyz& p1 = i_this->mTentacleInterpCurr[idx];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+ }
+}
+#endif
+
+static int daE_YG_Draw(e_yg_class* i_this) {
if (i_this->mDispFlag) {
return 1;
}
@@ -160,6 +181,23 @@ static int daE_YG_Draw(e_yg_class* i_this) {
color.a = 0xFF;
i_this->mLineMat.update(10, color, &actor->tevStr);
dComIfGd_set3DlineMatDark(&i_this->mLineMat);
+
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mTentacleInterpCurrValid) {
+ memcpy(i_this->mTentacleInterpPrev, i_this->mTentacleInterpCurr, sizeof(i_this->mTentacleInterpCurr));
+ i_this->mTentacleInterpPrevValid = true;
+ }
+ for (int s = 0; s < e_yg_class::TENTACLE_STRAND_COUNT; s++) {
+ cXyz* src = i_this->mLineMat.getPos(s);
+ memcpy(&i_this->mTentacleInterpCurr[s * e_yg_class::TENTACLE_SEGMENT_COUNT], src,
+ e_yg_class::TENTACLE_SEGMENT_COUNT * sizeof(cXyz));
+ }
+ i_this->mTentacleInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&daE_YG_interp_callback, i_this);
+ }
+#endif
+
dComIfGd_setList();
return 1;
@@ -1378,6 +1416,11 @@ static cPhs_Step daE_YG_Create(fopAc_ac_c* actor) {
return cPhs_ERROR_e;
}
+#if TARGET_PC
+ i_this->mTentacleInterpPrevValid = false;
+ i_this->mTentacleInterpCurrValid = false;
+#endif
+
if (!hio_set) {
i_this->mIsFirstSpawn = 1;
hio_set = true;
diff --git a/src/d/actor/d_a_e_yh.cpp b/src/d/actor/d_a_e_yh.cpp
index bbbd3e5129..cf9955a7db 100644
--- a/src/d/actor/d_a_e_yh.cpp
+++ b/src/d/actor/d_a_e_yh.cpp
@@ -12,6 +12,10 @@
#include "f_op/f_op_actor_enemy.h"
#include "f_op/f_op_kankyo_mng.h"
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
class daE_YH_HIO_c : public JORReflexible {
public:
daE_YH_HIO_c();
@@ -85,6 +89,22 @@ static BOOL leaf_anm_init(e_yh_class* i_this, int param_2, f32 param_3, u8 param
return FALSE;
}
+#if TARGET_PC
+static void daE_YH_interp_callback(bool isSimFrame, void* pUserWork) {
+ e_yh_class* i_this = (e_yh_class*)pUserWork;
+ if (!i_this->mLineInterpPrevValid || !i_this->mLineInterpCurrValid) {
+ return;
+ }
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ cXyz* dst = i_this->mLine.getPos(0);
+ for (int i = 0; i < 12; i++) {
+ const cXyz& p0 = i_this->mLineInterpPrev[i];
+ const cXyz& p1 = i_this->mLineInterpCurr[i];
+ dst[i] = p0 + (p1 - p0) * alpha;
+ }
+}
+#endif
+
static int daE_YH_Draw(e_yh_class* i_this) {
fopAc_ac_c* a_this = (fopAc_ac_c*)i_this;
@@ -114,6 +134,17 @@ static int daE_YH_Draw(e_yh_class* i_this) {
i_this->mLine.update(12, l_color, &a_this->tevStr);
dComIfGd_set3DlineMat(&i_this->mLine);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (i_this->mLineInterpCurrValid) {
+ memcpy(i_this->mLineInterpPrev, i_this->mLineInterpCurr, sizeof(i_this->mLineInterpCurr));
+ i_this->mLineInterpPrevValid = true;
+ }
+ memcpy(i_this->mLineInterpCurr, i_this->mLine.getPos(0), 12 * sizeof(cXyz));
+ i_this->mLineInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&daE_YH_interp_callback, i_this);
+ }
+#endif
for (int i = 1; i < 11; i++) {
if (i_this->mModels[i] != NULL) {
diff --git a/src/d/actor/d_a_e_ym.cpp b/src/d/actor/d_a_e_ym.cpp
index 758bec6269..3f6bef6788 100644
--- a/src/d/actor/d_a_e_ym.cpp
+++ b/src/d/actor/d_a_e_ym.cpp
@@ -1069,7 +1069,7 @@ void daE_YM_c::executeDown() {
if (mAcch.ChkGroundHit()) {
if (mFlyType != 1) {
#if TARGET_PC
- bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f);
+ bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f);
#else
bckSet(6, 0, 0.0f, 1.0f);
#endif
@@ -1093,7 +1093,7 @@ void daE_YM_c::executeDown() {
mSound.startCreatureSound(Z2SE_CM_BODYFALL_WATER_M, 0, -1);
mSound.startCreatureSound(Z2SE_EN_YM_MOGAKU, 0, -1);
#if TARGET_PC
- bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f);
+ bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f);
#else
bckSet(6, 0, 0.0f, 1.0f);
#endif
@@ -1115,7 +1115,7 @@ void daE_YM_c::executeDown() {
|| dComIfG_Bgsp().GetGroundCode(gnd_chk) == 5)
{
#if TARGET_PC
- bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f);
+ bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f);
#else
bckSet(6, 0, 0.0f, 1.0f);
#endif
diff --git a/src/d/actor/d_a_kago.cpp b/src/d/actor/d_a_kago.cpp
index 147a031f7e..9444237f12 100644
--- a/src/d/actor/d_a_kago.cpp
+++ b/src/d/actor/d_a_kago.cpp
@@ -3519,7 +3519,15 @@ void daKago_c::action() {
checkSizeBg();
setFlyEffect();
+#if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ mStickX = -mDoCPd_c::getStickX3D(PAD_1);
+ } else {
+ mStickX = mDoCPd_c::getStickX3D(PAD_1);
+ }
+#else
mStickX = mDoCPd_c::getStickX3D(PAD_1);
+#endif
mStickY = mDoCPd_c::getStickY(PAD_1);
u8 prevIsWaterfall = mIsWaterfall;
diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp
index 282502419f..6a559c0e1c 100644
--- a/src/d/actor/d_a_mant.cpp
+++ b/src/d/actor/d_a_mant.cpp
@@ -13,9 +13,12 @@
#if TARGET_PC
#include "dusk/dvd_asset.hpp"
#include "dusk/frame_interpolation.h"
-static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000), true); return buf; }
-static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000), true); return buf; }
-static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60), true); return buf; }
+
+using GameVersion = dusk::version::GameVersion;
+
+static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x1C00}, {GameVersion::GcnPal, 0x1C00}}, 0x4000), true); return buf; }
+static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x5C00}, {GameVersion::GcnPal, 0x5C00}}, 0x4000), true); return buf; }
+static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x9C00}, {GameVersion::GcnPal, 0x9C00}}, 0x60), true); return buf; }
#define l_Egnd_mantTEX (l_Egnd_mantTEX_get())
#define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get())
#define l_Egnd_mantPAL (l_Egnd_mantPAL_get())
@@ -251,7 +254,9 @@ static u32 l_texCoord[338] = {
};
#if TARGET_PC
-static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC), true); return buf; }
+using GameVersion = dusk::version::GameVersion;
+
+static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA9A0}, {GameVersion::GcnPal, 0xA9A0}}, 0x3EC), true); return buf; }
#define l_Egnd_mantDL (l_Egnd_mantDL_get())
#else
#include "assets/l_Egnd_mantDL.h"
diff --git a/src/d/actor/d_a_mg_fish.cpp b/src/d/actor/d_a_mg_fish.cpp
index 90781345ad..6791bcb61c 100644
--- a/src/d/actor/d_a_mg_fish.cpp
+++ b/src/d/actor/d_a_mg_fish.cpp
@@ -22,6 +22,7 @@
#include "d/d_s_play.h"
#include "d/d_vibration.h"
#include "f_op/f_op_kankyo_mng.h"
+#include "dusk/version.hpp"
#include
#define ANM_MG_FISH_MOUTH_CLOSE 4
@@ -3230,7 +3231,17 @@ static int daMg_Fish_Execute(mg_fish_class* i_this) {
daPy_py_c* player = daPy_getPlayerActorClass();
-#if VERSION == VERSION_GCN_JPN
+#if TARGET_PC
+ if (dusk::version::isRegionJpn()) {
+ lit_1008 = 0;
+ } else if (dusk::version::isRegionPal()) {
+ if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
+ lit_1008 = 2;
+ } else {
+ lit_1008 = 0;
+ }
+ }
+#elif VERSION == VERSION_GCN_JPN
lit_1008 = 0;
#elif VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
@@ -3843,7 +3854,19 @@ static int daMg_Fish_Create(fopAc_ac_c* i_this) {
a_this->mResName = "O_gD_bott";
}
-#if VERSION == VERSION_GCN_JPN
+#if TARGET_PC
+ if (dusk::version::isRegionJpn()) {
+ lit_1008 = 0;
+ } else if (dusk::version::isRegionPal()) {
+ if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
+ lit_1008 = 2;
+ } else {
+ lit_1008 = 0;
+ }
+ } else {
+ lit_1008 = 1;
+ }
+#elif VERSION == VERSION_GCN_JPN
lit_1008 = 0;
#elif VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
diff --git a/src/d/actor/d_a_mg_fshop.cpp b/src/d/actor/d_a_mg_fshop.cpp
index 99f39e410e..0d5b6dd2c9 100644
--- a/src/d/actor/d_a_mg_fshop.cpp
+++ b/src/d/actor/d_a_mg_fshop.cpp
@@ -5,14 +5,15 @@
#include "d/dolzel_rel.h" // IWYU pragma: keep
+#include "Z2AudioLib/Z2Instances.h"
+#include "d/actor/d_a_mg_fish.h"
#include "d/actor/d_a_mg_fshop.h"
#include "d/actor/d_a_npc_henna.h"
-#include "d/actor/d_a_mg_fish.h"
#include "d/actor/d_a_player.h"
-#include "f_op/f_op_camera_mng.h"
-#include "d/d_timer.h"
#include "d/d_s_play.h"
-#include "Z2AudioLib/Z2Instances.h"
+#include "d/d_timer.h"
+#include "dusk/version.hpp"
+#include "f_op/f_op_camera_mng.h"
#if TARGET_PC
#include "dusk/gyro.h"
@@ -728,10 +729,12 @@ static void koro2_game(fshop_class* i_this) {
cLib_addCalcAngleS2(&i_this->field_0x4020.z, 0, 2, 0x200);
case 2:
#if TARGET_PC
- if (dusk::getSettings().game.enableGyroRollgoal) {
+ if (dusk::gyro::rollgoal_gyro_enabled()) {
if (!dusk::gyro::get_sensor_keep_alive()) {
dusk::gyro::set_sensor_keep_alive(true);
}
+ } else if (dusk::gyro::get_sensor_keep_alive()) {
+ dusk::gyro::set_sensor_keep_alive(false);
}
#endif
@@ -752,7 +755,7 @@ static void koro2_game(fshop_class* i_this) {
old_stick_x = mDoCPd_c::getSubStickX(PAD_1);
cLib_addCalcAngleS2(&i_this->field_0x4060, i_this->field_0x4062, 4, 0x1000);
#if TARGET_PC
- if (dusk::getSettings().game.enableGyroRollgoal) {
+ if (dusk::gyro::rollgoal_gyro_enabled()) {
dusk::gyro::rollgoalTick(true, i_this->field_0x4060);
}
#endif
@@ -790,7 +793,7 @@ static void koro2_game(fshop_class* i_this) {
s16 gyro_ax = 0;
s16 gyro_az = 0;
#if TARGET_PC
- if (dusk::getSettings().game.enableGyroRollgoal) {
+ if (dusk::gyro::rollgoal_gyro_enabled()) {
dusk::gyro::rollgoalTableOffset(gyro_ax, gyro_az);
}
#endif
@@ -1742,7 +1745,18 @@ static int daFshop_Create(fopAc_ac_c* actor) {
fopAcM_createChild(fpcNm_FSHOP_e, fopAcM_GetID(actor), 0xFFFFFF23, &actor->current.pos, fopAcM_GetRoomNo(actor), NULL, NULL, -1, NULL);
u8 sp10;
-#if VERSION == VERSION_GCN_PAL || VERSION == VERSION_WII_PAL
+#if TARGET_PC
+ if (dusk::version::isRegionPal()) {
+ if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
+ sp10 = 2;
+ } else {
+ sp10 = 0;
+ }
+ } else {
+ sp10 = 1;
+ }
+
+#elif VERSION == VERSION_GCN_PAL || VERSION == VERSION_WII_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
sp10 = 2;
} else {
diff --git a/src/d/actor/d_a_mg_rod.cpp b/src/d/actor/d_a_mg_rod.cpp
index 96ad7aa148..956a9e3cff 100644
--- a/src/d/actor/d_a_mg_rod.cpp
+++ b/src/d/actor/d_a_mg_rod.cpp
@@ -25,6 +25,8 @@
#include
#include
+#include "dusk/version.hpp"
+
class dmg_rod_HIO_c : public JORReflexible {
public:
dmg_rod_HIO_c();
@@ -5734,10 +5736,22 @@ static void play_camera_u(dmg_rod_class* i_this) {
static int dmg_rod_Execute(dmg_rod_class* i_this) {
fopAc_ac_c* actor = &i_this->actor;
+ #if TARGET_PC
+ if (dusk::version::isPalOrAtLeastWiiR2()) {
+ if (dComIfGs_getPalLanguage() == 0) {
+ data_804BBBD4 = 2;
+ } else {
+ data_804BBBD4 = 0;
+ }
+ } else if (dusk::version::isRegionJpn()) {
+ data_804BBBD4 = 0;
+ } else {
+ data_804BBBD4 = 1;
+ }
//TODO: It seems possible that dComIfGs_getPalLanguage returns a constant value for non-PAL
// versions (causing the first block to be elided), and it's also possible that the value
// being compared against is an enum value with per-version definitions.
- #if VERSION == VERSION_SHIELD_DEBUG
+ #elif VERSION == VERSION_SHIELD_DEBUG
if (dComIfGs_getPalLanguage() == 1) {
data_804BBBD4 = 2;
} else {
@@ -6303,7 +6317,19 @@ static int dmg_rod_Create(fopAc_ac_c* i_this) {
heap_size = 0xC9A0;
}
- #if VERSION == VERSION_SHIELD_DEBUG
+ #if TARGET_PC
+ if (dusk::version::isPalOrAtLeastWiiR2()) {
+ if (dComIfGs_getPalLanguage() == 0) {
+ data_804BBBD4 = 2;
+ } else {
+ data_804BBBD4 = 0;
+ }
+ } else if (dusk::version::isRegionJpn()) {
+ data_804BBBD4 = 0;
+ } else {
+ data_804BBBD4 = 1;
+ }
+ #elif VERSION == VERSION_SHIELD_DEBUG
if (dComIfGs_getPalLanguage() == 1) {
data_804BBBD4 = 2;
} else {
diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp
index 7bb844932e..3bc500991d 100644
--- a/src/d/actor/d_a_midna.cpp
+++ b/src/d/actor/d_a_midna.cpp
@@ -419,9 +419,30 @@ int daMidna_c::createHeap() {
return 0;
}
+#if TARGET_PC
+ if (mpDemoFCTongueBmd != NULL) {
+ if(!daAlink_c::initDemoBck(&mpDemoFCTmpBck, "demo00_Midna_cut00_FC_tmp.bck")) {
+ return 0;
+ }
+
+ // Update Midna's eye maxLOD to prevent the eyes from disappearing
+ J3DTexture* tex = mpDemoFCTongueBmd->getModelData()->getTexture();
+ JUTNameTab* nametable = mpDemoFCTongueBmd->getModelData()->getTextureName();
+ if (tex != nullptr && nametable != nullptr) {
+ for (u16 i = 0; i < tex->getNum(); i++) {
+ const char* tex_name = nametable->getName(i);
+ if (tex_name != NULL && strcmp(tex_name, "midona_eyeball") == 0) {
+ ResTIMG* timg = tex->getResTIMG(i);
+ timg->maxLOD = 0;
+ }
+ }
+ }
+ }
+#else
if (mpDemoFCTongueBmd != NULL && !daAlink_c::initDemoBck(&mpDemoFCTmpBck, "demo00_Midna_cut00_FC_tmp.bck")) {
return 0;
}
+#endif
modelData =
(J3DModelData*)dComIfG_getObjectRes(dStage_roomControl_c::getDemoArcName(), "demo00_Midna_cut00_BD_tmp.bmd");
@@ -433,6 +454,21 @@ int daMidna_c::createHeap() {
modelData->getMaterialNodePointer(2)->setMaterialAnm(mpEyeMatAnm[0]);
modelData->getMaterialNodePointer(3)->setMaterialAnm(mpEyeMatAnm[1]);
+
+#if TARGET_PC
+ // Update Midna's eye maxLOD to prevent the eyes from disappearing
+ J3DTexture* tex = modelData->getTexture();
+ JUTNameTab* nametable = modelData->getTextureName();
+ if (tex != nullptr && nametable != nullptr) {
+ for (u16 i = 0; i < tex->getNum(); i++) {
+ const char* tex_name = nametable->getName(i);
+ if (tex_name != NULL && strcmp(tex_name, "midona_eyeball") == 0) {
+ ResTIMG* timg = tex->getResTIMG(i);
+ timg->maxLOD = 0;
+ }
+ }
+ }
+#endif
}
if (!initDemoModel(&mpDemoBDMaskBmd, "demo00_Midna_cut00_BD_mask.bmd", 0)) {
@@ -463,6 +499,23 @@ int daMidna_c::createHeap() {
JKRReadIdxResource(mBckHeap[0].getBuffer(), mBckHeap[0].getBufferSize(), 0x1DC, dComIfGp_getAnmArchive());
J3DAnmTransform* md_anm = (J3DAnmTransform*)J3DAnmLoaderDataBase::load(mBckHeap[0].getBuffer());
modelData = (J3DModelData*)dComIfG_getObjectRes(l_arcName, 14);
+
+#if TARGET_PC
+ J3DTexture* tex = modelData->getTexture();
+ JUTNameTab* nametable = modelData->getTextureName();
+ if (tex != NULL && nametable != NULL) {
+ for (u16 i = 0; i < tex->getNum(); i++) {
+ const char* name = nametable->getName(i);
+ if (name != NULL && strcmp(name, "midona_eye") == 0) {
+ ResTIMG* timg = tex->getResTIMG(i);
+ timg->mipmapEnabled = false;
+ tex->loadGXTexObj(i);
+ break;
+ }
+ }
+ }
+#endif
+
JUT_ASSERT(852, modelData != NULL);
mpMorf = JKR_NEW mDoExt_McaMorfSO(modelData, &mMorfCB, NULL, md_anm, J3DFrameCtrl::EMode_LOOP, 1.0f, 0, -1, NULL, 0, 0x11000284);
if (mpMorf == NULL || mpMorf->getModel() == NULL) {
diff --git a/src/d/actor/d_a_mirror.cpp b/src/d/actor/d_a_mirror.cpp
index fb4142c0bb..5c5218328e 100644
--- a/src/d/actor/d_a_mirror.cpp
+++ b/src/d/actor/d_a_mirror.cpp
@@ -40,6 +40,7 @@ dMirror_packet_c::dMirror_packet_c() {
void dMirror_packet_c::reset() {
#if TARGET_PC
mbReset = true;
+ mbHadEntry = false;
#else
mModelCount = 0;
#endif
@@ -96,6 +97,9 @@ int dMirror_packet_c::entryModel(J3DModel* i_model) {
}
mModels[mModelCount++] = i_model;
+#if TARGET_PC
+ mbHadEntry = true;
+#endif
return 1;
}
@@ -624,6 +628,12 @@ int daMirror_c::draw() {
mDoExt_modelUpdateDL(mpModel);
}
+#if TARGET_PC
+ if (mPacket.mbReset && !mPacket.mbHadEntry) {
+ mPacket.mModelCount = 0;
+ }
+ mPacket.mbHadEntry = true;
+#endif
dComIfGd_getOpaListBG()->entryImm(&mPacket, 0);
return 1;
}
diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp
index 079584f714..1f8bdeecd7 100644
--- a/src/d/actor/d_a_movie_player.cpp
+++ b/src/d/actor/d_a_movie_player.cpp
@@ -2855,7 +2855,7 @@ void* daMP_Reader(void*) {
#endif
}
-static u8 daMP_ReadThreadStack[0x2000];
+static u8 daMP_ReadThreadStack[DUSK_IF_ELSE(8, 0x2000)];
#if TARGET_PC
static BOOL VideoThreadCancelled;
@@ -2880,7 +2880,7 @@ static BOOL daMP_CreateReadThread(s32 param_0) {
static OSThread daMP_VideoDecodeThread;
-static u8 daMP_VideoDecodeThreadStack[0x64000];
+static u8 daMP_VideoDecodeThreadStack[DUSK_IF_ELSE(8, 0x64000)];
static OSMessageQueue daMP_FreeTextureSetQueue;
@@ -3132,7 +3132,7 @@ static BOOL AudioThreadCancelled;
static OSThread daMP_AudioDecodeThread;
-static u8 daMP_AudioDecodeThreadStack[0x64000];
+static u8 daMP_AudioDecodeThreadStack[DUSK_IF_ELSE(8, 0x64000)];
static OSMessageQueue daMP_FreeAudioBufferQueue;
diff --git a/src/d/actor/d_a_npc_ne.cpp b/src/d/actor/d_a_npc_ne.cpp
index 1324c56cf5..90e33adea5 100644
--- a/src/d/actor/d_a_npc_ne.cpp
+++ b/src/d/actor/d_a_npc_ne.cpp
@@ -956,7 +956,7 @@ static void npc_ne_tame(npc_ne_class* i_this) {
i_this->mpMorf->setPlaySpeed(i_this->mAnmSpeed);
/* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */
- if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) {
+ if (IF_DUSK(dusk::getSettings().game.no2ndFishForCat) || dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) {
if (fpcEx_Search(s_fish_sub, _this) != NULL) {
i_this->mAction = npc_ne_class::ACT_HOME;
i_this->mMode = 10;
@@ -2948,8 +2948,7 @@ static int daNpc_Ne_Execute(npc_ne_class* i_this) {
if (i_this->mWantsFish && (i_this->mCounter & 0xf) == 0) {
/* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */
- if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])
- && i_this->mDistToTarget < 1500.0f) {
+ if ((IF_DUSK(dusk::getSettings().game.no2ndFishForCat) || dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) && i_this->mDistToTarget < 1500.0f) {
if (fopAcM_SearchByName(fpcNm_MG_ROD_e) != NULL) {
i_this->mNoFollow = false;
} else {
diff --git a/src/d/actor/d_a_npc_toby.cpp b/src/d/actor/d_a_npc_toby.cpp
index 543d4b9abf..d175d199b7 100644
--- a/src/d/actor/d_a_npc_toby.cpp
+++ b/src/d/actor/d_a_npc_toby.cpp
@@ -14,6 +14,7 @@
#include "d/actor/d_a_obj_automata.h"
#include "d/d_msg_object.h"
#include "d/actor/d_a_obj_scannon.h"
+#include "dusk/frame_interpolation.h"
#include
const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = {
@@ -1398,6 +1399,7 @@ int daNpc_Toby_c::cutRepairSCannon(int arg0) {
old.pos = current.pos;
setAngle(cM_deg2s(5.0f * f32(mPath.getArg0())));
mEventTimer = mPath.getArg2();
+ dusk::frame_interp::request_presentation_sync();
}
} else if (!mHide) {
mHide = 1;
diff --git a/src/d/actor/d_a_obj_balloon.cpp b/src/d/actor/d_a_obj_balloon.cpp
index 41430f97f0..d857a569ba 100644
--- a/src/d/actor/d_a_obj_balloon.cpp
+++ b/src/d/actor/d_a_obj_balloon.cpp
@@ -62,6 +62,16 @@ void daObj_Balloon_c::saveBestScore() {
dComIfGp_setMessageCountNumber(m_balloon_score);
}
+#if TARGET_PC
+static void minigameReset() {
+ // !@bug d_a_obj_balloon.rel unload used to zero these file-statics; with static linking they dangle across scenes.
+ m_combo_type = 0xFFFFFFFF;
+ m_combo_count = 0;
+ m_combo_next_score = 0;
+ m_balloon_score = 0;
+}
+#endif
+
static u8 hio_set;
static daObj_Balloon_HIO_c l_HIO;
@@ -246,6 +256,7 @@ int daObj_Balloon_c::create() {
}
if (!hio_set) {
+ IF_DUSK(minigameReset());
mHIOInit = true;
hio_set = true;
l_HIO.field_0x04 = -1;
diff --git a/src/d/actor/d_a_obj_drop.cpp b/src/d/actor/d_a_obj_drop.cpp
index 275a3d6e12..18d27840f7 100644
--- a/src/d/actor/d_a_obj_drop.cpp
+++ b/src/d/actor/d_a_obj_drop.cpp
@@ -265,8 +265,8 @@ int daObjDrop_c::modeParentWait() {
mModeAction = 1;
#if TARGET_PC
- mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40;
- if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) {
+ mModeTimer = dusk::getSettings().game.fastTears ? 0 : 40;
+ if (dusk::getSettings().game.fastTears) {
current.pos.y += 100.0f;
} else {
current.pos.y += 300.0f;
@@ -285,7 +285,7 @@ int daObjDrop_c::modeParentWait() {
case 2:
createBodyEffect();
#if TARGET_PC
- mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45;
+ mModeTimer = dusk::getSettings().game.fastTears ? 0 : 45;
#else
mModeTimer = 45;
#endif
@@ -331,7 +331,7 @@ int daObjDrop_c::modeWait() {
case 2:
case 50:
#if TARGET_PC
- if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) {
+ if (dusk::getSettings().game.fastTears) {
f32 player_dist = current.pos.abs(daPy_getPlayerActorClass()->current.pos);
f32 home_dist = current.pos.abs(home.pos);
diff --git a/src/d/actor/d_a_obj_fchain.cpp b/src/d/actor/d_a_obj_fchain.cpp
index e681fec4e7..024b4ecf8b 100644
--- a/src/d/actor/d_a_obj_fchain.cpp
+++ b/src/d/actor/d_a_obj_fchain.cpp
@@ -10,6 +10,8 @@
#include "JSystem/J3DGraphBase/J3DDrawBuffer.h"
#include "SSystem/SComponent/c_math.h"
#include "d/d_com_inf_game.h"
+#include "dusk/frame_interpolation.h"
+#include "dusk/settings.h"
#include
static char const l_arcName[] = "Fchain";
@@ -65,6 +67,10 @@ int daObjFchain_c::create() {
local_48++;
}
rv = cPhs_COMPLEATE_e;
+#if TARGET_PC
+ mChainInterpPrevValid = false;
+ mChainInterpCurrValid = false;
+#endif
break;
}
return rv;
@@ -289,6 +295,26 @@ void daObjFchain_shape_c::draw() {
}
}
+#if TARGET_PC
+static void fchain_interp_callback(bool isSimFrame, void* pUserWork) {
+ static_cast(pUserWork)->onInterpCallback();
+}
+
+void daObjFchain_c::onInterpCallback() {
+ if (!mChainInterpPrevValid || !mChainInterpCurrValid) {
+ return;
+ }
+
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+
+ for (int i = 0; i < CHAIN_COUNT; i++) {
+ const cXyz& p0 = mChainInterpPrev[i];
+ const cXyz& p1 = mChainInterpCurr[i];
+ field_0x694[i] = p0 + (p1 - p0) * alpha;
+ }
+}
+#endif
+
int daObjFchain_c::draw() {
if (field_0x584 != 0) {
g_env_light.settingTevStruct(0, ¤t.pos, &tevStr);
@@ -297,6 +323,19 @@ int daObjFchain_c::draw() {
return 1;
}
dComIfGd_getOpaListDark()->entryImm(&mShape, 0);
+
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (mChainInterpCurrValid) {
+ memcpy(mChainInterpPrev, mChainInterpCurr, sizeof(mChainInterpCurr));
+ mChainInterpPrevValid = true;
+ }
+
+ memcpy(mChainInterpCurr, field_0x694, sizeof(mChainInterpCurr));
+ mChainInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&fchain_interp_callback, this);
+ }
+#endif
}
return 1;
}
diff --git a/src/d/actor/d_a_obj_item.cpp b/src/d/actor/d_a_obj_item.cpp
index 6c7e82ab3e..e45de1933d 100644
--- a/src/d/actor/d_a_obj_item.cpp
+++ b/src/d/actor/d_a_obj_item.cpp
@@ -16,6 +16,10 @@
#include "f_op/f_op_camera_mng.h"
#include "m_Do/m_Do_mtx.h"
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) {
cM3dGPla plane;
@@ -31,6 +35,27 @@ static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) {
return 0.0f;
}
+#if TARGET_PC
+static void d_a_obj_item_interp_callback(bool isSimFrame, void* pUserWork) {
+ daItem_c* item = static_cast(pUserWork);
+ if (item == NULL || item->mpModel == NULL || !item->chkDraw()) {
+ return;
+ }
+ item->setTevStr();
+ if (item->mpBrkAnm != NULL) {
+ s8 tevFrm = item->getTevFrm();
+ if (tevFrm != -1) {
+ item->mpBrkAnm->entry(item->mpModel->getModelData(), tevFrm);
+ } else {
+ item->mpBrkAnm->entry(item->mpModel->getModelData());
+ }
+ }
+ if (item->chkFlag(4)) {
+ fopAcM_setEffectMtx(item, item->mpModel->getModelData());
+ }
+}
+#endif
+
const daItemBase_data& daItemBase_c::getData() {
return m_data;
}
@@ -353,6 +378,10 @@ int daItem_c::_daItem_draw() {
return 1;
}
+#if TARGET_PC
+ dusk::frame_interp::add_interpolation_callback(&d_a_obj_item_interp_callback, this);
+#endif
+
if (chkDraw()) {
return DrawBase();
}
diff --git a/src/d/actor/d_a_obj_klift00.cpp b/src/d/actor/d_a_obj_klift00.cpp
index 99b204592e..e44cce7467 100644
--- a/src/d/actor/d_a_obj_klift00.cpp
+++ b/src/d/actor/d_a_obj_klift00.cpp
@@ -11,6 +11,8 @@
#include "d/d_bg_w.h"
#include "d/d_cc_uty.h"
#include "d/d_com_inf_game.h"
+#include "dusk/frame_interpolation.h"
+#include "dusk/settings.h"
struct daObjKLift00_HIO_c : public mDoHIO_entry_c {
daObjKLift00_HIO_c();
@@ -295,6 +297,11 @@ int daObjKLift00_c::Create() {
if(getLock())
mStopSwingingFrames = 5;
+#if TARGET_PC
+ mChainInterpPrevValid = false;
+ mChainInterpCurrValid = false;
+#endif
+
return 1;
}
@@ -436,6 +443,34 @@ int daObjKLift00_c::Execute(Mtx** i_mtx) {
return 1;
}
+#if TARGET_PC
+static void klift00_interp_callback(bool isSimFrame, void* pUserWork) {
+ static_cast(pUserWork)->onInterpCallback();
+}
+
+void daObjKLift00_c::onInterpCallback() {
+ if (!mChainInterpPrevValid || !mChainInterpCurrValid) {
+ return;
+ }
+
+ const f32 alpha = dusk::frame_interp::get_interpolation_step();
+ cXyz savedPositions[64];
+
+ for (int i = 0; i < mNumChains; i++) {
+ savedPositions[i] = mChainPositions[i].mCurrentPos;
+ const cXyz& p0 = mChainInterpPrev[i];
+ const cXyz& p1 = mChainInterpCurr[i];
+ mChainPositions[i].mCurrentPos = p0 + (p1 - p0) * alpha;
+ }
+
+ setMtx();
+
+ for (int i = 0; i < mNumChains; i++) {
+ mChainPositions[i].mCurrentPos = savedPositions[i];
+ }
+}
+#endif
+
int daObjKLift00_c::Draw() {
g_env_light.settingTevStruct(16, ¤t.pos, &tevStr);
g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr);
@@ -457,6 +492,22 @@ int daObjKLift00_c::Draw() {
dComIfGd_setList();
+#if TARGET_PC
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ if (mChainInterpCurrValid) {
+ memcpy(mChainInterpPrev, mChainInterpCurr, mNumChains * sizeof(cXyz));
+ mChainInterpPrevValid = true;
+ }
+
+ for (int i = 0; i < mNumChains; i++) {
+ mChainInterpCurr[i] = mChainPositions[i].mCurrentPos;
+ }
+
+ mChainInterpCurrValid = true;
+ dusk::frame_interp::add_interpolation_callback(&klift00_interp_callback, this);
+ }
+#endif
+
return 1;
}
diff --git a/src/d/actor/d_a_obj_lv8Lift.cpp b/src/d/actor/d_a_obj_lv8Lift.cpp
index 4f475a626c..8fa9422325 100644
--- a/src/d/actor/d_a_obj_lv8Lift.cpp
+++ b/src/d/actor/d_a_obj_lv8Lift.cpp
@@ -10,6 +10,10 @@
#include "d/d_path.h"
#include "d/d_bg_w.h"
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
daL8Lift_HIO_c::daL8Lift_HIO_c() {
mStopDisappearTime = 30;
mStartMoveTime = 60;
@@ -380,7 +384,44 @@ void daL8Lift_c::setNextPoint() {
mCurrentPoint = next_point;
}
+#if TARGET_PC
+void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork) {
+ daL8Lift_c* lift = static_cast(pUserWork);
+ if (lift == NULL || lift->mpModel == NULL) {
+ return;
+ }
+
+ g_env_light.settingTevStruct(0x10, &lift->current.pos, &lift->tevStr);
+ g_env_light.setLightTevColorType_MAJI(lift->mpModel, &lift->tevStr);
+
+ J3DModelData* modelData = lift->mpModel->getModelData();
+ J3DMaterial* materialp = modelData->getMaterialNodePointer(0);
+
+ if (materialp->getTexGenBlock()->getTexMtx(1) != NULL) {
+ J3DTexMtxInfo* mtx_info = &materialp->getTexGenBlock()->getTexMtx(1)->getTexMtxInfo();
+ if (mtx_info != NULL) {
+ Mtx m;
+ C_MTXLightOrtho(m, 100.0f, -100.0f, -100.0f, 100.0f, 1.0f, 1.0f, 0.0f, 0.0f);
+ mDoMtx_stack_c::XrotS(0x4000);
+ mDoMtx_stack_c::transM(-lift->current.pos.x, -lift->current.pos.y, -lift->current.pos.z);
+ cMtx_concat(m, mDoMtx_stack_c::get(), mtx_info->mEffectMtx);
+ }
+ }
+
+ lift->mBtk.entry(modelData);
+
+ J3DGXColor* color = materialp->getTevKColor(1);
+ color->r = l_HIO.mColorR;
+ color->g = l_HIO.mColorG;
+ color->b = l_HIO.mColorB;
+}
+#endif
+
int daL8Lift_c::Draw() {
+#if TARGET_PC
+ dusk::frame_interp::add_interpolation_callback(&daL8Lift_interp_callback, this);
+#endif
+
g_env_light.settingTevStruct(16, ¤t.pos, &tevStr);
g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr);
J3DModelData* modelData = mpModel->getModelData();
diff --git a/src/d/actor/d_a_obj_lv8OptiLift.cpp b/src/d/actor/d_a_obj_lv8OptiLift.cpp
index a8610989a7..6b6c806d64 100644
--- a/src/d/actor/d_a_obj_lv8OptiLift.cpp
+++ b/src/d/actor/d_a_obj_lv8OptiLift.cpp
@@ -11,6 +11,10 @@
#include "d/d_com_inf_game.h"
#include "d/d_path.h"
+#if TARGET_PC
+#include "dusk/frame_interpolation.h"
+#endif
+
daOptiLift_HIO_c::daOptiLift_HIO_c() {
mStopDisappearTime = 30;
mStartMoveTime = 30;
@@ -412,7 +416,44 @@ void daOptiLift_c::setNextPoint() {
mCurrentPoint = next_point;
}
+#if TARGET_PC
+static void daOptiLift_interp_callback(bool isSimFrame, void* pUserWork) {
+ daOptiLift_c* lift = static_cast(pUserWork);
+ if (lift == NULL || lift->mpModel == NULL) {
+ return;
+ }
+
+ g_env_light.settingTevStruct(0x10, &lift->current.pos, &lift->tevStr);
+ g_env_light.setLightTevColorType_MAJI(lift->mpModel, &lift->tevStr);
+
+ J3DModelData* modelData = lift->mpModel->getModelData();
+ J3DMaterial* materialp = modelData->getMaterialNodePointer(0);
+
+ if (materialp->getTexGenBlock()->getTexMtx(1) != NULL) {
+ J3DTexMtxInfo* mtx_info = &materialp->getTexGenBlock()->getTexMtx(1)->getTexMtxInfo();
+ if (mtx_info != NULL) {
+ Mtx m;
+ C_MTXLightOrtho(m, 100.0f, -100.0f, -100.0f, 100.0f, 1.0f, 1.0f, 0.0f, 0.0f);
+ mDoMtx_stack_c::XrotS(0x4000);
+ mDoMtx_stack_c::transM(-lift->current.pos.x, -lift->current.pos.y, -lift->current.pos.z);
+ cMtx_concat(m, mDoMtx_stack_c::get(), mtx_info->mEffectMtx);
+ }
+ }
+
+ lift->mBtk.entry(modelData);
+
+ J3DGXColor* color = materialp->getTevKColor(1);
+ color->r = l_HIO.mColorR;
+ color->g = l_HIO.mColorG;
+ color->b = l_HIO.mColorB;
+}
+#endif
+
int daOptiLift_c::Draw() {
+#if TARGET_PC
+ dusk::frame_interp::add_interpolation_callback(&daOptiLift_interp_callback, this);
+#endif
+
g_env_light.settingTevStruct(0x10, ¤t.pos, &tevStr);
g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr);
diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp
index 8ceb0ef237..6148d23e3a 100644
--- a/src/d/actor/d_a_player.cpp
+++ b/src/d/actor/d_a_player.cpp
@@ -369,18 +369,17 @@ JKRHeap* daPy_anmHeap_c::setAnimeHeap() {
#if !PLATFORM_WII
#if TARGET_PC
#include "dusk/dvd_asset.hpp"
+using GameVersion = dusk::version::GameVersion;
static const u8* l_sightDL_get() {
static u8 buf[0x89];
static bool _ = (
dusk::LoadDolAsset(
buf,
- #if VERSION == VERSION_GCN_PAL
- 0x803BBDA0,
- #elif VERSION == VERSION_GCN_JPN
- 0x803B4220,
- #elif VERSION == VERSION_GCN_USA
- 0x803BA0C0,
- #endif
+{
+ {GameVersion::GcnUsa, 0x803BA0C0},
+ {GameVersion::GcnPal, 0x803BBDA0},
+ {GameVersion::GcnJpn, 0x803B4220}
+ },
0x89
),
true
diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp
index 6df52abc5b..a75a96cb2e 100644
--- a/src/d/actor/d_a_title.cpp
+++ b/src/d/actor/d_a_title.cpp
@@ -1,21 +1,22 @@
#include "d/dolzel_rel.h" // IWYU pragma: keep
+#include "JSystem/J2DGraph/J2DScreen.h"
+#include "JSystem/J2DGraph/J2DTextBox.h"
+#include "JSystem/JKernel/JKRExpHeap.h"
+#include "JSystem/JKernel/JKRMemArchive.h"
#include "d/actor/d_a_title.h"
+#include "d/d_com_inf_game.h"
+#include "d/d_demo.h"
+#include "d/d_menu_collect.h"
+#include "d/d_pane_class_alpha.h"
#include "d/d_s_logo.h"
#include "d/d_s_play.h"
-#include "d/d_demo.h"
-#include "d/d_pane_class_alpha.h"
-#include "d/d_menu_collect.h"
+#include "dusk/version.hpp"
+#include "f_op/f_op_msg_mng.h"
+#include "f_op/f_op_overlap_mng.h"
+#include "f_op/f_op_scene_mng.h"
#include "m_Do/m_Do_Reset.h"
#include "m_Do/m_Do_controller_pad.h"
-#include "d/d_com_inf_game.h"
-#include "JSystem/JKernel/JKRExpHeap.h"
-#include "f_op/f_op_overlap_mng.h"
-#include "f_op/f_op_msg_mng.h"
-#include "f_op/f_op_scene_mng.h"
-#include "JSystem/J2DGraph/J2DScreen.h"
-#include "JSystem/JKernel/JKRMemArchive.h"
-#include "JSystem/J2DGraph/J2DTextBox.h"
#include "m_Do/m_Do_graphic.h"
#ifdef TARGET_PC
@@ -49,7 +50,10 @@ static u8 const lit_3772[12] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
-#if VERSION == VERSION_GCN_PAL
+#if TARGET_PC
+using namespace dusk::version;
+#define l_arcName versionSelect({{GameVersion::GcnPal, "TitlePal"}}, "Title")
+#elif VERSION == VERSION_GCN_PAL
static char const l_arcName[] = "TitlePal";
#else
static char const l_arcName[] = "Title";
@@ -59,7 +63,7 @@ daTit_HIO_c::daTit_HIO_c() {
mPSScaleX = 1.0f;
mPSScaleY = 1.0f;
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
switch (OSGetLanguage()) {
case OS_LANGUAGE_ENGLISH:
case OS_LANGUAGE_GERMAN:
diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc
index 58daa354a1..4024d5ad6a 100644
--- a/src/d/actor/d_flower.inc
+++ b/src/d/actor/d_flower.inc
@@ -7,17 +7,13 @@
#include "dusk/frame_interpolation.h"
-#if TARGET_PC
-const u16 l_J_Ohana00_64TEX__width = 64;
-const u16 l_J_Ohana00_64TEX__height = 64;
-#else
const u16 l_J_Ohana00_64TEX__width = 63;
const u16 l_J_Ohana00_64TEX__height = 63;
-#endif
#if TARGET_PC
#include "dusk/dvd_asset.hpp"
-static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9060, 0x800), true); return buf; }
+using GameVersion = dusk::version::GameVersion;
+static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9060}, {GameVersion::GcnPal, 0x9060}}, 0x800), true); return buf; }
#define l_J_Ohana00_64TEX (l_J_Ohana00_64TEX_get())
#else
#include "assets/l_J_Ohana00_64TEX.h"
@@ -113,10 +109,12 @@ static u8 l_flowerTexCoord[] = {
0x3E, 0xA7, 0x72, 0xD6, 0xBD, 0x2F, 0x46, 0xAA};
#if TARGET_PC
-static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9D20, 0x150), true); return buf; }
-static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9E80, 0xDE), true); return buf; }
-static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9F60, 0x99), true); return buf; }
-static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA000, 0x99), true); return buf; }
+using GameVersion = dusk::version::GameVersion;
+
+static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9D20}, {GameVersion::GcnPal, 0x9D20}}, 0x150), true); return buf; }
+static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9E80}, {GameVersion::GcnPal, 0x9E80}}, 0xDE), true); return buf; }
+static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9F60}, {GameVersion::GcnPal, 0x9F60}}, 0x99), true); return buf; }
+static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xA000}, {GameVersion::GcnPal, 0xA000}}, 0x99), true); return buf; }
#define l_J_hana00DL (l_J_hana00DL_get())
#define l_J_hana00_cDL (l_J_hana00_cDL_get())
#define l_matDL (l_matDL_get())
@@ -133,16 +131,12 @@ l_matDL__d_a_grass(l_J_Ohana00_64TEX)
l_matLight4DL(l_J_Ohana00_64TEX)
#endif
-#if TARGET_PC
-const u16 l_J_Ohana01_64128_0419TEX__width = 64;
-const u16 l_J_Ohana01_64128_0419TEX__height = 128;
-#else
const u16 l_J_Ohana01_64128_0419TEX__width = 63;
const u16 l_J_Ohana01_64128_0419TEX__height = 127;
-#endif
#if TARGET_PC
-static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA0A0, 0x1000), true); return buf; }
+using GameVersion = dusk::version::GameVersion;
+static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xA0A0}, {GameVersion::GcnPal, 0xA0A0}}, 0x1000), true); return buf; }
#define l_J_Ohana01_64128_0419TEX (l_J_Ohana01_64128_0419TEX_get())
#else
#include "assets/l_J_Ohana01_64128_0419TEX.h"
@@ -274,11 +268,13 @@ static u8 l_flowerTexCoord2[] = {
0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x80, 0x3F, 0x79, 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x51, 0x10, 0x6F};
#if TARGET_PC
-static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB7C0, 0x138), true); return buf; }
-static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB900, 0xDE), true); return buf; }
-static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB9E0, 0x128), true); return buf; }
-static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBB20, 0x99), true); return buf; }
-static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBBC0, 0x99), true); return buf; }
+using GameVersion = dusk::version::GameVersion;
+
+static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB7C0}, {GameVersion::GcnPal, 0xB7C0}}, 0x138), true); return buf; }
+static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB900}, {GameVersion::GcnPal, 0xB900}}, 0xDE), true); return buf; }
+static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB9E0}, {GameVersion::GcnPal, 0xB9E0}}, 0x128), true); return buf; }
+static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xBB20}, {GameVersion::GcnPal, 0xBB20}}, 0x99), true); return buf; }
+static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xBBC0}, {GameVersion::GcnPal, 0xBBC0}}, 0x99), true); return buf; }
#define l_J_hana01DL (l_J_hana01DL_get())
#define l_J_hana01_c_00DL (l_J_hana01_c_00DL_get())
#define l_J_hana01_c_01DL (l_J_hana01_c_01DL_get())
@@ -586,11 +582,11 @@ dFlower_packet_c::dFlower_packet_c() {
#if TARGET_PC
GXInitTexObj(&mTexObj_l_J_Ohana00_64TEX, l_J_Ohana00_64TEX,
- l_J_Ohana00_64TEX__width, l_J_Ohana00_64TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE
+ l_J_Ohana00_64TEX__width + 1, l_J_Ohana00_64TEX__height + 1, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE
);
GXInitTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, l_J_Ohana01_64128_0419TEX,
- l_J_Ohana01_64128_0419TEX__width, l_J_Ohana01_64128_0419TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE
+ l_J_Ohana01_64128_0419TEX__width + 1, l_J_Ohana01_64128_0419TEX__height + 1, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE
);
#endif
@@ -789,6 +785,10 @@ void dFlower_packet_c::draw() {
GXColor sp28;
+ #if AVOID_UB
+ sp28 = {1, 1, 1, 1};
+ #endif
+
//u8 sp26, sp25, sp24;
GXColor sp24;
sp24.r = -0.4f * temp_r29->AmbCol.r * var_f29;
diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc
index ab2724060f..3d67a57007 100644
--- a/src/d/actor/d_grass.inc
+++ b/src/d/actor/d_grass.inc
@@ -20,8 +20,9 @@ const u16 l_M_kusa05_RGBATEX__height = 31;
#if TARGET_PC
#include "dusk/dvd_asset.hpp"
-static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7680, 0x800), true); return buf; }
-static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7E80, 0x800), true); return buf; }
+using GameVersion = dusk::version::GameVersion;
+static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x7680}, {GameVersion::GcnPal, 0x7680}}, 0x800), true); return buf; }
+static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x7E80}, {GameVersion::GcnPal, 0x7E80}}, 0x800), true); return buf; }
#define l_M_kusa05_RGBATEX (l_M_kusa05_RGBATEX_get())
#define l_M_Hijiki00TEX (l_M_Hijiki00TEX_get())
#else
@@ -113,12 +114,14 @@ static u8 l_texCoord[160] = {
};
#if TARGET_PC
-static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8B00, 0xCB), true); return buf; }
-static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8BE0, 0xCB), true); return buf; }
-static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8CC0, 0xD4), true); return buf; }
-static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8DA0, 0xA8), true); return buf; }
-static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8E60, 0xA8), true); return buf; }
-static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8F20, 0xA8), true); return buf; }
+using GameVersion = dusk::version::GameVersion;
+
+static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8B00}, {GameVersion::GcnPal, 0x8B00}}, 0xCB), true); return buf; }
+static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8BE0}, {GameVersion::GcnPal, 0x8BE0}}, 0xCB), true); return buf; }
+static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8CC0}, {GameVersion::GcnPal, 0x8CC0}}, 0xD4), true); return buf; }
+static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8DA0}, {GameVersion::GcnPal, 0x8DA0}}, 0xA8), true); return buf; }
+static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8E60}, {GameVersion::GcnPal, 0x8E60}}, 0xA8), true); return buf; }
+static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8F20}, {GameVersion::GcnPal, 0x8F20}}, 0xA8), true); return buf; }
#define l_M_Kusa_9qDL (l_M_Kusa_9qDL_get())
#define l_M_Kusa_9q_cDL (l_M_Kusa_9q_cDL_get())
#define l_M_TenGusaDL (l_M_TenGusaDL_get())
@@ -491,11 +494,11 @@ dGrass_packet_c::dGrass_packet_c() {
#if TARGET_PC
GXInitTexObj(&mTexObj_l_M_kusa05_RGBATEX, l_M_kusa05_RGBATEX,
- l_M_kusa05_RGBATEX__width, l_M_kusa05_RGBATEX__height, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE
+ l_M_kusa05_RGBATEX__width + 1, l_M_kusa05_RGBATEX__height + 1, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE
);
GXInitTexObj(&mTexObj_l_M_Hijiki00TEX, l_M_Hijiki00TEX,
- l_M_Hijiki00TEX__width, l_M_Hijiki00TEX__height, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE
+ l_M_Hijiki00TEX__width + 1, l_M_Hijiki00TEX__height + 1, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE
);
#endif
@@ -643,18 +646,14 @@ void dGrass_packet_c::draw() {
}
if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) {
-#if TARGET_PC
- GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0);
-#endif
+ IF_DUSK(GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0));
if (sp48 <= 3) {
GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size);
} else {
GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size);
}
} else {
-#if TARGET_PC
- GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0);
-#endif
+ IF_DUSK(GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0));
GXCallDisplayList(l_Tengusa_matDL, 0xA0);
}
@@ -680,12 +679,14 @@ void dGrass_packet_c::draw() {
while (var_r29 != NULL) {
if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) {
+ IF_DUSK(GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0));
if (sp48 <= 2) {
GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size);
} else {
GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size);
}
} else {
+ IF_DUSK(GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0));
GXCallDisplayList(l_Tengusa_matDL, 0xA0);
}
diff --git a/src/d/d_attention.cpp b/src/d/d_attention.cpp
index efa3bfec26..1bb9e84f70 100644
--- a/src/d/d_attention.cpp
+++ b/src/d/d_attention.cpp
@@ -12,6 +12,10 @@
#include "SSystem/SComponent/c_counter.h"
#include
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
#define DRAW_TYPE_YELLOW 0
#define DRAW_TYPE_RED 1
@@ -1422,6 +1426,11 @@ int dAttention_c::Run() {
}
void dAttention_c::Draw() {
+#if TARGET_PC
+if (dusk::getSettings().game.recordingMode) {
+ return;
+}
+#endif
if (mAttParam.CheckFlag(dAttParam_c::EFlag_ARROW_OFF)) {
draw[0].field_0x173 = 3;
draw[1].field_0x173 = 3;
diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp
index c3fcc806d5..3ee98be5c7 100644
--- a/src/d/d_bright_check.cpp
+++ b/src/d/d_bright_check.cpp
@@ -9,6 +9,8 @@
#include "JSystem/J2DGraph/J2DScreen.h"
#include "JSystem/J2DGraph/J2DTextBox.h"
#include "d/d_msg_string.h"
+#include "dusk/livesplit.h"
+#include "dusk/imgui/ImGuiConsole.hpp"
#include "m_Do/m_Do_controller_pad.h"
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
@@ -138,6 +140,17 @@ void dBrightCheck_c::modeWait() {}
void dBrightCheck_c::modeMove() {
if (mDoCPd_c::getTrigA(PAD_1) || mDoCPd_c::getTrigStart(PAD_1)) {
mDoAud_seStart(Z2SE_ENTER_GAME, NULL, 0, 0);
+#ifdef TARGET_PC
+ dusk::speedrun::start();
+
+ if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) {
+ // start a new run if a run isn't already in progress
+ if (!dusk::m_speedrunInfo.m_isRunStarted) {
+ dusk::ImGuiMenuGame::resetForSpeedrunMode();
+ dusk::m_speedrunInfo.startRun();
+ }
+ }
+#endif
mCompleteCheck = true;
mMode = MODE_WAIT_e;
}
diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp
index f805a6a08b..0adf6d4bd6 100644
--- a/src/d/d_camera.cpp
+++ b/src/d/d_camera.cpp
@@ -31,6 +31,7 @@
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/logging.h"
+#include "imgui.h"
#endif
namespace {
@@ -794,6 +795,9 @@ void dCamera_c::updatePad() {
if (mTriggerLeftLast > mCamSetup.ManualEndVal()) {
if (mLockLActive == 0) {
+ #if TARGET_PC
+ mCamParam.mManualMode = 0;
+ #endif
mLockLJustActivated = 1;
} else {
mLockLJustActivated = 0;
@@ -833,6 +837,12 @@ void dCamera_c::updatePad() {
mHoldB = mDoCPd_c::getHoldB(mPadID) ? true : false;
mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false;
+ #if TARGET_PC
+ if (mCamParam.mManualMode) {
+ return;
+ }
+ #endif
+
bool sp6B = true;
bool sp6C = true;
int temp1;
@@ -1031,6 +1041,11 @@ void dCamera_c::debugDrawInit() {
bool dCamera_c::Run() {
#if TARGET_PC
ResetView();
+ if (executeDebugFlyCam()) {
+ mFrameCounter++;
+ mTicks++;
+ return true;
+ }
#endif
daAlink_c* link = daAlink_getAlinkActorClass();
@@ -1166,7 +1181,14 @@ bool dCamera_c::Run() {
clrFlag(0x200000);
}
} else {
+ #if TARGET_PC
+ if (mCamParam.Algorythmn(mCamStyle) != 1) {
+ mCamParam.mManualMode = 0;
+ }
+ #endif
+
sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle);
+
field_0x170++;
field_0x160++;
mCurCamStyleTimer++;
@@ -1471,7 +1493,7 @@ void dCamera_c::CalcTrimSize() {
mTrimHeight += -mTrimHeight * 0.25f;
break;
case 2:
-#if WIDESCREEN_SUPPORT
+#if !TARGET_PC && WIDESCREEN_SUPPORT
if (mDoGph_gInf_c::isWide() && mDoGph_gInf_c::isWideZoom()) {
mTrimHeight += (16.0f - mTrimHeight) * 0.25f;
break;
@@ -3078,6 +3100,7 @@ bool dCamera_c::bumpCheck(u32 i_flags) {
} else {
field_0x968 *= mMonitor.field_0xc / 5.0f;
}
+
f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f);
center += vec3.norm() * (tmp * globe.V().Sin());
cSGlobe globe2(vec2 - center);
@@ -4182,6 +4205,11 @@ bool dCamera_c::chaseCamera(s32 param_0) {
chase->field_0x8 -= chase->field_0xc;
chase->field_0x8c = 0;
chase->field_0x90 = false;
+
+ #if TARGET_PC
+ freeCamera();
+ #endif
+
return true;
}
@@ -4604,6 +4632,7 @@ bool dCamera_c::chaseCamera(s32 param_0) {
sp110 = mViewCache.mDirection.R();
mViewCache.mDirection.R(mViewCache.mDirection.R() + (fVar55 - mViewCache.mDirection.R()) * chase->field_0x74);
+
chase->field_0x64 = mViewCache.mCenter + mViewCache.mDirection.Xyz();
mViewCache.mEye = chase->field_0x64;
@@ -4618,6 +4647,11 @@ bool dCamera_c::chaseCamera(s32 param_0) {
if (chase->field_0x1c != 0) {
chase->field_0x1c--;
}
+
+ #if TARGET_PC
+ freeCamera();
+ #endif
+
return true;
}
@@ -7065,10 +7099,12 @@ bool dCamera_c::subjectCamera(s32 param_0) {
cXyz sp1E0(val0, val2, val1);
#if TARGET_PC
- f32 aspect = mDoGph_gInf_c::getAspect();
- f32 baseAspect = FB_WIDTH / FB_HEIGHT;
- if (aspect > baseAspect) {
- sp1E0.z += (aspect - baseAspect) * 4;
+ if (sp13) {
+ f32 aspect = mDoGph_gInf_c::getAspect();
+ f32 baseAspect = FB_WIDTH / FB_HEIGHT;
+ if (aspect > baseAspect) {
+ sp1E0.z += (aspect - baseAspect) * 4;
+ }
}
#endif
@@ -7444,6 +7480,201 @@ bool dCamera_c::test2Camera(s32 param_0) {
return false;
}
+static constexpr f32 FLYCAM_SPEED = 0.5f;
+static constexpr f32 FLYCAM_FAST_SPEED = 4.0f;
+static constexpr f32 FLYCAM_ROTATION_SPEED = 0.002f;
+static constexpr f32 FLYCAM_TRIGGER_DEADZONE = 20.0f;
+static constexpr s16 FLYCAM_ROLL_SPEED = 256;
+static ImVec2 sFlyCamLastMousePos = {-1.f, -1.f};
+
+#if TARGET_PC
+bool dCamera_c::executeDebugFlyCam() {
+ if (!dusk::getSettings().game.debugFlyCam) {
+ if (mDebugFlyCam.initialized) {
+ deactivateDebugFlyCam();
+ }
+ sFlyCamLastMousePos = {-1.f, -1.f};
+ return false;
+ }
+
+ dEvt_control_c* event = dComIfGp_getEvent();
+ if (event == nullptr) {
+ return false;
+ }
+
+ if (!mDebugFlyCam.initialized && (event->mEventStatus != 0 || dComIfGp_isPauseFlag())) {
+ dusk::getSettings().game.debugFlyCam.setValue(false);
+ return false;
+ }
+
+ if (!mDebugFlyCam.initialized) {
+ mDebugFlyCam.savedCenter = mCenter;
+ mDebugFlyCam.savedEye = mEye;
+ mDebugFlyCam.savedFovy = mFovy;
+ mDebugFlyCam.savedBank = mBank;
+
+ f32 dx = mCenter.x - mEye.x;
+ f32 dy = mCenter.y - mEye.y;
+ f32 dz = mCenter.z - mEye.z;
+ mDebugFlyCam.yaw = atan2f(dz, dx);
+ f32 horizontal = sqrtf(dx * dx + dz * dz);
+ mDebugFlyCam.pitch = atan2f(dy, horizontal);
+
+ mDebugFlyCam.initialized = true;
+ }
+
+ if (dusk::getSettings().game.debugFlyCamLockEvents) {
+ event->mEventStatus = 1;
+ dComIfGp_getEventManager().setCameraPlay(1);
+ } else {
+ if (event->mEventStatus != 0) {
+ event->mEventStatus = 0;
+ }
+ dComIfGp_getEventManager().setCameraPlay(0);
+ }
+
+ f32 stickY = 0.f;
+ f32 stickX = 0.f;
+ f32 cStickY = 0.f;
+ f32 cStickX = 0.f;
+ f32 trigL = 0.f;
+ f32 trigR = 0.f;
+ f32 rollInput = 0.f;
+ bool fast = false;
+
+ if (dusk::getSettings().game.debugFlyCamLockEvents) {
+ interface_of_controller_pad& pad = mDoCPd_c::getCpadInfo(0);
+ stickY = pad.mMainStickPosY * 72.0f;
+ stickX = pad.mMainStickPosX * 72.0f;
+ cStickY = pad.mCStickPosY * 59.0f;
+ cStickX = pad.mCStickPosX * 59.0f;
+ trigL = pad.mTriggerLeft * 150.0f;
+ trigR = pad.mTriggerRight * 150.0f;
+ fast = mDoCPd_c::getHoldZ(PAD_1);
+ if (mDoCPd_c::getHoldY(PAD_1)) rollInput -= 1.f;
+ if (mDoCPd_c::getHoldX(PAD_1)) rollInput += 1.f;
+ }
+
+ {
+ ImGuiIO& io = ImGui::GetIO();
+ if (!io.WantCaptureKeyboard) {
+ f32 kbX = 0.0f, kbY = 0.0f;
+ if (ImGui::IsKeyDown(ImGuiKey_W) || ImGui::IsKeyDown(ImGuiKey_UpArrow)) kbY += 1.f;
+ if (ImGui::IsKeyDown(ImGuiKey_S) || ImGui::IsKeyDown(ImGuiKey_DownArrow)) kbY -= 1.f;
+ if (ImGui::IsKeyDown(ImGuiKey_D) || ImGui::IsKeyDown(ImGuiKey_RightArrow)) kbX += 1.f;
+ if (ImGui::IsKeyDown(ImGuiKey_A) || ImGui::IsKeyDown(ImGuiKey_LeftArrow)) kbX -= 1.f;
+ f32 len = sqrtf(kbX * kbX + kbY * kbY);
+ if (len > 1.f) { kbX /= len; kbY /= len; }
+ stickX += kbX * 72.0f;
+ stickY += kbY * 72.0f;
+ if (ImGui::IsKeyDown(ImGuiKey_Space)) trigR += 150.0f;
+ if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) trigL += 150.0f;
+ if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) fast = true;
+ if (ImGui::IsKeyDown(ImGuiKey_Q)) rollInput -= 1.0f;
+ if (ImGui::IsKeyDown(ImGuiKey_E)) rollInput += 1.0f;
+ }
+ bool mouseValid = !io.WantCaptureMouse && io.MousePos.x >= 0.0f && io.MousePos.y >= 0.0f;
+ if (mouseValid && sFlyCamLastMousePos.x >= 0.0f) {
+ cStickX -= (io.MousePos.x - sFlyCamLastMousePos.x) * 2.0f;
+ cStickY -= (io.MousePos.y - sFlyCamLastMousePos.y) * 2.0f;
+ }
+ sFlyCamLastMousePos = mouseValid ? io.MousePos : ImVec2{-1.0f, -1.0f};
+ }
+
+ f32 verticalDisp = 0.0f;
+ if (trigR >= FLYCAM_TRIGGER_DEADZONE) {
+ verticalDisp += trigR;
+ }
+ if (trigL >= FLYCAM_TRIGGER_DEADZONE) {
+ verticalDisp -= trigL;
+ }
+
+ f32 moveDy = stickY * sinf(mDebugFlyCam.pitch) + verticalDisp;
+ f32 moveDx = stickY * cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) - stickX * sinf(mDebugFlyCam.yaw);
+ f32 moveDz = stickY * sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) + stickX * cosf(mDebugFlyCam.yaw);
+
+ f32 speed = fast ? FLYCAM_FAST_SPEED : FLYCAM_SPEED;
+
+ mEye.x += speed * moveDx;
+ mEye.y += speed * moveDy;
+ mEye.z += speed * moveDz;
+
+ static constexpr f32 FLYCAM_TARGET_DIST = 100.0f;
+ mCenter.x = mEye.x + cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
+ mCenter.z = mEye.z + sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
+ mCenter.y = mEye.y + sinf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
+
+ mBank = mBank + static_cast(rollInput * FLYCAM_ROLL_SPEED * (fast ? FLYCAM_FAST_SPEED / FLYCAM_SPEED : 1.f));
+ Reset(mCenter, mEye);
+
+ f32 yawInput = dusk::getSettings().game.invertCameraXAxis ? cStickX : -cStickX;
+ mDebugFlyCam.yaw += yawInput * FLYCAM_ROTATION_SPEED;
+ mDebugFlyCam.yaw = fmodf(mDebugFlyCam.yaw + 2.0f * (f32)M_PI, 2.0f * (f32)M_PI);
+
+ f32 maxPitch = (f32)M_PI / 2.0f - 0.1f;
+ f32 minPitch = -(f32)M_PI / 2.0f + 0.1f;
+ mDebugFlyCam.pitch = std::clamp(mDebugFlyCam.pitch + cStickY * FLYCAM_ROTATION_SPEED, minPitch, maxPitch);
+
+ return true;
+}
+
+void dCamera_c::deactivateDebugFlyCam() {
+ Reset(mDebugFlyCam.savedCenter, mDebugFlyCam.savedEye, mDebugFlyCam.savedFovy, mDebugFlyCam.savedBank.Val());
+
+ dEvt_control_c* event = dComIfGp_getEvent();
+ if (event != nullptr && event->mEventStatus != 0) {
+ event->mEventStatus = 0;
+ }
+ dComIfGp_getEventManager().setCameraPlay(0);
+ mDebugFlyCam.initialized = false;
+}
+
+bool dCamera_c::freeCamera() {
+ if (dusk::getSettings().game.freeCamera && mGear == 1) {
+ mGear = 0;
+ }
+
+ if (!dusk::getSettings().game.freeCamera || mCamStyle == 70)
+ {
+ mCamParam.mManualMode = 0;
+ return false;
+ }
+
+ if (!mCamParam.mManualMode) {
+ mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree();
+ mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree();
+ }
+
+ cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f};
+ f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
+
+ if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) {
+ mCamParam.mManualMode = 1;
+ camMovement = camMovement.normalize();
+ camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
+ mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f;
+ mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f;
+ }
+
+ fopAc_ac_c* player = dComIfGp_getPlayer(0);
+ if (!mCamParam.mManualMode || player == nullptr) {
+ return false;
+ }
+
+ f32 minYAngle = -30.0f;
+ f32 maxAngle = 50.0f;
+
+ mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, minYAngle, maxAngle);
+ mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle);
+ mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle);
+
+ cXyz finalEye = mViewCache.mCenter + mViewCache.mDirection.Xyz();
+ mViewCache.mEye = finalEye;
+
+ return true;
+}
+#endif
+
bool dCamera_c::towerCamera(s32 param_0) {
cSAngle stack_444 = cSAngle(mCamSetup.ChargeLatitude());
f32 sp224 = mCamSetup.ChargeBRatio();
@@ -11031,6 +11262,26 @@ static int camera_execute(camera_process_class* i_this) {
return 1;
}
+#ifdef TARGET_PC
+void set_ar_corrected_trim(dDlst_window_c* window, float trim_height) {
+ const auto viewport = window->getViewPort();
+
+ if (mDoGph_gInf_c::isWideZoom()) {
+ const auto target_ar = FB_WIDTH / (FB_HEIGHT - trim_height * 2.0f);
+ const auto current_ar = mDoGph_gInf_c::m_safeWidthF / mDoGph_gInf_c::m_safeHeightF;
+
+ if (current_ar < target_ar) {
+ trim_height = FB_HEIGHT / 2.0f * (1.0f - current_ar / target_ar);
+ } else {
+ trim_height = 0.0f;
+ }
+ }
+
+ trim_height *= viewport->height / FB_HEIGHT;
+ window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f);
+}
+#endif
+
static int camera_draw(camera_process_class* i_this) {
camera_class* a_this = (camera_class*)i_this;
dCamera_c* body = &i_this->mCamera;
@@ -11083,12 +11334,44 @@ static int camera_draw(camera_process_class* i_this) {
}
#endif
+#if TARGET_PC
+ set_ar_corrected_trim(window, body->TrimHeight());
+
+ if (dusk::getSettings().game.enableFrameInterpolation) {
+ dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) {
+ const auto i_this = static_cast(pUserWork);
+ const auto camera = &i_this->mCamera;
+
+ const auto trim_size = camera->mTrimSize;
+
+ if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) {
+ // derive trim height at previous tick using current camera state
+ f32 target;
+ switch (trim_size) {
+ case 0:
+ target = 0.0f;
+ break;
+ case 1:
+ target = camera->mCamSetup.VistaTrimHeight();
+ break;
+ case 2:
+ case 3:
+ target = camera->mCamSetup.CinemaScopeTrimHeight();
+ break;
+ }
+
+ const auto step = dusk::frame_interp::get_interpolation_step();
+ const auto cur = camera->TrimHeight();
+ const auto prev = (4.0f * cur - target) / 3.0f;
+ const auto trim_height = prev + (cur - prev) * step;
+
+ set_ar_corrected_trim(get_window((camera_class*)i_this), trim_height);
+ }
+ }, i_this);
+ }
+#else
int trim_height = body->TrimHeight();
-#if TARGET_PC
- trim_height *= viewport->height / FB_HEIGHT;
- window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f);
-#else
window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f);
#endif
diff --git a/src/d/d_cc_uty.cpp b/src/d/d_cc_uty.cpp
index 7d71e490ac..ec9a177683 100644
--- a/src/d/d_cc_uty.cpp
+++ b/src/d/d_cc_uty.cpp
@@ -13,6 +13,9 @@
#include "d/d_s_play.h"
#include "d/d_com_inf_game.h"
#include "f_op/f_op_actor_mng.h"
+#if TARGET_PC
+#include "dusk/achievements.h"
+#endif
static int plCutLRC[58] = {
0, //
@@ -434,6 +437,11 @@ fopAc_ac_c* cc_at_check(fopAc_ac_c* i_enemy, dCcU_AtInfo* i_AtInfo) {
if (i_AtInfo->mAttackPower != 0 && i_enemy->health <= 0) {
i_AtInfo->mHitStatus = 2;
i_enemy->health = 0;
+#if TARGET_PC
+ if (fopAcM_GetGroup(i_enemy) == fopAc_ENEMY_e) {
+ dusk::AchievementSystem::get().signal("enemy_killed");
+ }
+#endif
}
int uvar8;
diff --git a/src/d/d_demo.cpp b/src/d/d_demo.cpp
index d6425848d3..2f099f2c2a 100644
--- a/src/d/d_demo.cpp
+++ b/src/d/d_demo.cpp
@@ -11,6 +11,62 @@
#include "JSystem/JGadget/define.h"
#include
+#include "dusk/logging.h"
+
+#if TARGET_PC
+#include "dusk/ui/ui.hpp"
+
+namespace {
+static int sJaiSkip = -1;
+
+static JSUList* get_stream_list() {
+ return Z2GetSoundMgr()->getStreamMgr()->getStreamList();
+}
+
+static int get_stream_count(JSUList* list) {
+ int i = 0;
+ for (JSULink* l = list != nullptr ? list->getFirst() : nullptr; l != nullptr;
+ l = l->getNext()) {
+ i++;
+ }
+ return i;
+}
+
+static void pause_stream(int skip_first, bool paused) {
+ int i = 0;
+ JSUList* list = get_stream_list();
+ for (JSULink* l = list->getFirst(); l != nullptr; l = l->getNext(), ++i) {
+ if (i >= skip_first) {
+ l->getObject()->pause(paused);
+ }
+ }
+}
+
+static void pause_streams(int skip_first) {
+ if (!dusk::ui::is_prelaunch_open()) {
+ return;
+ }
+ JSUList* list = get_stream_list();
+ if (list == nullptr || get_stream_count(list) <= skip_first) {
+ return;
+ }
+ pause_stream(skip_first, true);
+ sJaiSkip = skip_first;
+}
+
+static void unpause_streams(bool require_prelaunch_hidden) {
+ if (sJaiSkip < 0) {
+ return;
+ }
+ if (require_prelaunch_hidden && dusk::ui::is_prelaunch_open()) {
+ return;
+ }
+ pause_stream(sJaiSkip, false);
+ sJaiSkip = -1;
+}
+} // namespace
+#endif
+
s16 dDemo_c::m_branchId = -1;
namespace {
@@ -1006,7 +1062,16 @@ int dDemo_c::start(u8 const* p_data, cXyz* p_translation, f32 rotationY) {
m_control->setSuspend(0);
}
+#if TARGET_PC
+ const int existing_streams = get_stream_count(get_stream_list());
+#endif
+
m_control->forward(0);
+
+#if TARGET_PC
+ pause_streams(existing_streams);
+#endif
+
m_translation = p_translation;
if (m_translation != NULL) {
@@ -1034,6 +1099,10 @@ static void dummyString2() {
void dDemo_c::end() {
JUT_ASSERT(1956, m_system != NULL);
+#if TARGET_PC
+ unpause_streams(false);
+#endif
+
m_control->destroyObject_all();
m_object->remove();
m_data = NULL;
@@ -1054,6 +1123,10 @@ void dDemo_c::branch() {
int dDemo_c::update() {
JUT_ASSERT(2064, m_system != NULL);
+#if TARGET_PC
+ unpause_streams(true);
+#endif
+
if (m_data == NULL) {
if (m_branchData == NULL) {
return 0;
diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp
index 373791f994..1f16b2f655 100644
--- a/src/d/d_drawlist.cpp
+++ b/src/d/d_drawlist.cpp
@@ -22,6 +22,10 @@
#include "dusk/frame_interpolation.h"
#include "dusk/gx_helper.h"
#include "dusk/logging.h"
+
+static const void* getInterpKey(const void* base, int idx) {
+ return reinterpret_cast(reinterpret_cast(base) ^ idx);
+}
#endif
class dDlst_2Dm_c {
@@ -1062,7 +1066,15 @@ void dDlst_shadowReal_c::reset() {
}
void dDlst_shadowReal_c::imageDraw(Mtx param_0) {
- GXSetProjection(mRenderProjMtx, GX_ORTHOGRAPHIC);
+#ifdef TARGET_PC
+ Mtx render_proj_mtx;
+ if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 2), render_proj_mtx)) {
+ GXSetProjection(render_proj_mtx, GX_ORTHOGRAPHIC);
+ } else
+#endif
+ {
+ GXSetProjection(mRenderProjMtx, GX_ORTHOGRAPHIC);
+ }
JUT_ASSERT(1916, mModelNum);
J3DModelData* model_data;
J3DModel** models = mpModels;
@@ -1075,7 +1087,15 @@ void dDlst_shadowReal_c::imageDraw(Mtx param_0) {
for (u16 j = 0; j < model_data->getShapeNum(); j++) {
if (!model_data->getShapeNodePointer(j)->checkFlag(1)) {
shape_pkt = (*models)->getShapePacket(j);
- shape_pkt->setBaseMtxPtr(&mViewMtx);
+#ifdef TARGET_PC
+ Mtx view_mtx;
+ if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx)) {
+ shape_pkt->setBaseMtxPtr(&view_mtx);
+ } else
+#endif
+ {
+ shape_pkt->setBaseMtxPtr(&mViewMtx);
+ }
shape_pkt->drawFast();
shape_pkt->setBaseMtxPtr((Mtx*)param_0);
}
@@ -1096,7 +1116,18 @@ void dDlst_shadowReal_c::draw() {
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetCurrentMtx(GX_PNMTX0);
- GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4);
+#ifdef TARGET_PC
+ Mtx view_mtx, recv_proj_mtx;
+ const auto have_view_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx);
+ const auto have_recv_proj_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 3), recv_proj_mtx);
+ if (have_view_mtx && have_recv_proj_mtx) {
+ cMtx_concat(recv_proj_mtx, view_mtx, recv_proj_mtx);
+ GXLoadTexMtxImm(recv_proj_mtx, GX_TEXMTX0, GX_MTX3x4);
+ } else
+#endif
+ {
+ GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4);
+ }
mShadowRealPoly.draw();
}
@@ -1253,6 +1284,13 @@ u8 dDlst_shadowReal_c::setShadowRealMtx(cXyz* param_0, cXyz* param_1, f32 param_
cMtx_lookAt(mViewMtx, &local_64, param_1, 0);
C_MTXOrtho(mRenderProjMtx, param_2, -param_2, -param_2, param_2, 1.0f, 10000.0f);
C_MTXLightOrtho(mReceiverProjMtx, param_2, -param_2, -param_2, param_2, 0.5f, -0.5f, 0.5f, 0.5f);
+
+#ifdef TARGET_PC
+ const auto keybase = mpModels[0];
+ dusk::frame_interp::record_final_mtx(mViewMtx, getInterpKey(keybase, 1));
+ dusk::frame_interp::record_final_mtx(mRenderProjMtx, getInterpKey(keybase, 2));
+ dusk::frame_interp::record_final_mtx(mReceiverProjMtx, getInterpKey(keybase, 3));
+#endif
cMtx_concat(mReceiverProjMtx, mViewMtx, mReceiverProjMtx);
return r29;
}
@@ -1277,6 +1315,10 @@ u32 dDlst_shadowReal_c::set(u32 i_key, J3DModel* i_model, cXyz* param_2, f32 par
}
}
+#ifdef TARGET_PC
+ // provide a stable key for interpolation
+ mpModels[0] = i_model;
+#endif
field_0x1 = setShadowRealMtx(&sp60, param_2, param_3, param_4, param_7, param_5);
if (!field_0x1) {
@@ -1370,12 +1412,6 @@ void dDlst_shadowSimple_c::draw() {
GXCallDisplayList(l_shadowVolumeDL, 0x40);
}
-#if TARGET_PC
-static const void* getInterpKey(const void* base, int idx) {
- return reinterpret_cast(reinterpret_cast(base) ^ idx);
-}
-#endif
-
void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* param_3,
s16 param_4, f32 param_5, TGXTexObj* param_6) {
if (param_5 < 0.0f) {
diff --git a/src/d/d_file_sel_info.cpp b/src/d/d_file_sel_info.cpp
index f305f59a9e..0c987e6d63 100644
--- a/src/d/d_file_sel_info.cpp
+++ b/src/d/d_file_sel_info.cpp
@@ -14,6 +14,8 @@
#include
#include
+#include "dusk/version.hpp"
+
dFile_info_c::dFile_info_c(JKRArchive* i_archive, u8 param_1) {
mArchive = i_archive;
field_0x22 = param_1;
@@ -169,7 +171,18 @@ void dFile_info_c::setSaveDate(dSv_save_c* i_savedata) {
OSCalendarTime time;
OSTicksToCalendarTime(i_savedata->getPlayer().getPlayerStatusB().getDateIpl(), &time);
- #if (VERSION == VERSION_GCN_JPN) || (VERSION == VERSION_WII_JPN)
+ #if TARGET_PC
+ if (dusk::version::isRegionJpn()) {
+ sprintf(mSaveDate, "%d.%02d.%02d %02d:%02d", time.year, time.mon + 1, time.mday,
+ time.hour, time.min);
+ } else if (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() != dSv_player_config_c::LANGUAGE_ENGLISH) {
+ sprintf(mSaveDate, "%02d/%02d/%d %02d:%02d", time.mday, time.mon + 1, time.year, time.hour,
+ time.min);
+ } else {
+ sprintf(mSaveDate, "%02d/%02d/%d %02d:%02d", time.mon + 1, time.mday, time.year, time.hour,
+ time.min);
+ }
+ #elif (VERSION == VERSION_GCN_JPN) || (VERSION == VERSION_WII_JPN)
sprintf(mSaveDate, "%d.%02d.%02d %02d:%02d", time.year, time.mon + 1, time.mday,
time.hour, time.min);
#elif VERSION == VERSION_GCN_PAL
diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp
index 64cf2247e0..09e230c9fd 100644
--- a/src/d/d_kankyo_rain.cpp
+++ b/src/d/d_kankyo_rain.cpp
@@ -5962,6 +5962,8 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) {
fopAc_ac_c* player = dComIfGp_getPlayer(0);
if (evil_packet != NULL) {
+ IF_DUSK(GXPushDebugGroup("dKyr_evil_draw2"));
+
j3dSys.reinitGX();
if (dComIfGd_getView() != NULL) {
MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx);
@@ -5986,7 +5988,12 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) {
TGXTexObj texobj;
dKyr_set_btitex(&texobj, (ResTIMG*)tex[1]);
- rot += 0.7f;
+#if TARGET_PC
+ if (dusk::frame_interp::get_ui_tick_pending())
+#endif
+ {
+ rot += 0.7f;
+ }
MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot));
MTXConcat(camMtx, rotMtx, camMtx);
@@ -6037,7 +6044,14 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) {
sp34.y = 80.0f;
sp34.z = 80.0f;
mDoLib_project(&sp7C, &proj);
- if (!(proj.x > -sp34.x) || !(proj.x < (FB_WIDTH + sp34.x)) ||
+#if TARGET_PC
+ f32 cullMinX = mDoGph_gInf_c::getSafeMinXF() - sp34.x;
+ f32 cullMaxX = mDoGph_gInf_c::getSafeMinXF() + mDoGph_gInf_c::getSafeWidthF() + sp34.x;
+#else
+ f32 cullMinX = -sp34.x;
+ f32 cullMaxX = FB_WIDTH + sp34.x;
+#endif
+ if (!(proj.x > cullMinX) || !(proj.x < cullMaxX) ||
!(proj.y > -sp34.y) || !(proj.y < (458.0f + sp34.z)))
{
continue;
@@ -6150,6 +6164,8 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) {
}
}
+ IF_DUSK(GXPopDebugGroup());
+
GXSetClipMode(GX_CLIP_ENABLE);
J3DShape::resetVcdVatCache();
}
@@ -6187,6 +6203,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) {
f32 sp60 = fabsf(cM_ssin(g_Counter.mCounter0 * 215));
if (evil_packet != NULL) {
+ IF_DUSK(GXPushDebugGroup("dKyr_evil_draw"));
+
j3dSys.reinitGX();
if (dComIfGd_getView() != NULL) {
MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx);
@@ -6208,14 +6226,19 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) {
TGXTexObj texobj;
dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]);
- rot += 1.0f;
+#if TARGET_PC
+ if (dusk::frame_interp::get_ui_tick_pending())
+#endif
+ {
+ rot += 1.0f;
+ }
MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot));
MTXConcat(camMtx, rotMtx, camMtx);
GXLoadPosMtxImm(drawMtx, GX_PNMTX0);
GXSetCurrentMtx(GX_PNMTX0);
- GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_F32, 0);
- GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGBA4, 8);
+ GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
+ GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_RGBA4, 8);
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
@@ -6238,6 +6261,19 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) {
GXSetClipMode(GX_CLIP_DISABLE);
GXSetNumIndStages(0);
+#if TARGET_PC
+ // move color_reg0 to vtx for perf
+ GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
+ GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
+ GXSetNumChans(1);
+ GXSetChanCtrl(GX_COLOR0A0, GX_FALSE, GX_SRC_REG, GX_SRC_VTX, 0, GX_DF_NONE, GX_AF_NONE);
+ GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+ GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_C1, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);
+ GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
+ GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO);
+ GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
+#endif
+
dComIfG_Ccsp()->PrepareMass();
for (int i = 0; i < g_env_light.field_0x1054; i++) {
@@ -6262,7 +6298,14 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) {
sp44.z = 80.0f;
mDoLib_project(&spA4, &proj);
- if (!(proj.x > -sp44.x) || !(proj.x < (FB_WIDTH + sp44.x)) ||
+#if TARGET_PC
+ f32 cullMinX = mDoGph_gInf_c::getSafeMinXF() - sp44.x;
+ f32 cullMaxX = mDoGph_gInf_c::getSafeMinXF() + mDoGph_gInf_c::getSafeWidthF() + sp44.x;
+#else
+ f32 cullMinX = -sp44.x;
+ f32 cullMaxX = FB_WIDTH + sp44.x;
+#endif
+ if (!(proj.x > cullMinX) || !(proj.x < cullMaxX) ||
!(proj.y > -sp44.y) || !(proj.y < (458.0f + sp44.z)))
{
continue;
@@ -6349,7 +6392,7 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) {
color_reg0.b = (115.0f * sp28) + (15.0f * fabsf(sp2C - sp64));
}
- GXSetTevColor(GX_TEVREG0, color_reg0);
+ IF_NOT_DUSK(GXSetTevColor(GX_TEVREG0, color_reg0));
GXSetTevColor(GX_TEVREG1, color_reg1);
spC8 = spA4;
@@ -6388,12 +6431,16 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) {
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
GXPosition3f32(pos[0].x, pos[0].y, pos[0].z);
+ IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a));
GXTexCoord2s16(0, 0);
GXPosition3f32(pos[1].x, pos[1].y, pos[1].z);
+ IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a));
GXTexCoord2s16(0xFF, 0);
GXPosition3f32(pos[2].x, pos[2].y, pos[2].z);
+ IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a));
GXTexCoord2s16(0xFF, 0xFF);
GXPosition3f32(pos[3].x, pos[3].y, pos[3].z);
+ IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a));
GXTexCoord2s16(0, 0xFF);
GXEnd();
}
@@ -6401,6 +6448,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) {
}
}
+ IF_DUSK(GXPopDebugGroup());
+
J3DShape::resetVcdVatCache();
GXSetClipMode(GX_CLIP_ENABLE);
diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp
index 7dc5187a88..25d792f309 100644
--- a/src/d/d_map.cpp
+++ b/src/d/d_map.cpp
@@ -13,6 +13,9 @@
#include "SSystem/SComponent/c_math.h"
#include "d/actor/d_a_player.h"
#include "d/d_com_inf_game.h"
+#if TARGET_PC
+#include
+#endif
#include
#if DEBUG
@@ -539,17 +542,14 @@ void renderingAmap_c::rendering(dDrawPath_c::poly_class const* i_poly) {
}
}
-/* Enabling the following definition will modify the following function to
- * make the map look worse for extra speed in the emulator, especially in large
- * areas such as hyrule field.
- */
-#define HYRULE_FIELD_SPEEDHACK
bool renderingAmap_c::isDrawOutSideTrim() {
bool rt = false;
- #ifdef HYRULE_FIELD_SPEEDHACK
- return 0;
+ #if TARGET_PC
+ if (!dusk::getSettings().game.enableMapBackground) {
+ return 0;
+ }
#endif
if (getDispType() == 0 || getDispType() == 4 || getDispType() == 3 || getDispType() == 2 ||
@@ -1218,6 +1218,9 @@ void dMap_c::changeTextureSize(int param_1, int param_2, int param_3) {
void dMap_c::_remove() {
if (mImage_p != NULL) {
+#if TARGET_PC
+ GXDestroyCopyTex(mImage_p);
+#endif
JKR_DELETE_ARRAY(mImage_p);
mImage_p = NULL;
}
@@ -1592,7 +1595,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
calcMapCmPerTexel(field_0x80, &field_0x58);
getPack(field_0x80, &mPackX, &mPackZ);
- mCenterX += mPackX;
+ mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX;
mCenterZ -= mPackZ;
mCenterX += field_0x64;
mCenterZ += mPackPlusZ;
@@ -1654,7 +1657,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
calcMapCmPerTexel(field_0x80, &field_0x58);
getPack(field_0x80, &mPackX, &mPackZ);
- mCenterX += mPackX;
+ mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX;
mCenterZ -= mPackZ;
}
break;
@@ -1734,7 +1737,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
calcMapCmPerTexel(field_0x80, &field_0x58);
getPack(field_0x80, &mPackX, &mPackZ);
- mCenterX += mPackX;
+ mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -mPackX :) mPackX;
mCenterZ -= mPackZ;
field_0x8f = 4;
#if DEBUG
@@ -1826,7 +1829,7 @@ void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
sp14 += temp_f31_2 * (spC - sp14);
sp10 += temp_f31_2 * (sp8 - sp10);
- mCenterX += sp14;
+ mCenterX += IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -sp14 :) sp14;
mCenterZ -= sp10;
break;
}
diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp
index 8288a2ef9f..f5e447a2aa 100644
--- a/src/d/d_map_path.cpp
+++ b/src/d/d_map_path.cpp
@@ -15,12 +15,47 @@
#include
#ifdef TARGET_PC
+#include
+#include
+#include
+
constexpr u16 kMapResolutionMultiplier = 4;
+constexpr u16 kMapImageSide = 16 * kMapResolutionMultiplier;
+constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide;
+
+typedef std::function PaintI8Fn;
+
+void paint_i8(std::span dst, size_t width, PaintI8Fn paint) {
+ const auto blocksAcross = width >> 3;
+
+ for (size_t i = 0; i < dst.size(); i++) {
+ // 8x4 block swizzling for I8
+ const auto blockIdx = i >> 5;
+ const auto localIdx = i & 31;
+
+ const auto blockY = blockIdx / blocksAcross;
+ const auto blockX = blockIdx % blocksAcross;
+
+ const auto localY = localIdx >> 3;
+ const auto localX = localIdx & 7;
+
+ const auto x = (blockX << 3) + localX;
+ const auto y = (blockY << 2) + localY;
+
+ dst[i] = paint(x, y);
+ }
+}
#endif
void dMpath_n::dTexObjAggregate_c::create() {
static int const data[7] = {
- 79, 80, 77, 78, 76, 81, 82,
+ 79, // 0: im_map_icon_square_4i.bti
+ 80, // 1: im_map_icon_tresurebox_4i.bti
+ 77, // 2: im_map_icon_enter_4i.bti
+ 78, // 3: im_map_icon_nijumaru_4i.bti
+ 76, // 4: im_map_icon_circle_4i.bti
+ 81, // 5: im_map_icon_try_force_4i.bti
+ 82, // 6: map_icon_circle16x16_4i.bti
};
for (int lp1 = 0; lp1 < 7; lp1++) {
@@ -32,6 +67,104 @@ void dMpath_n::dTexObjAggregate_c::create() {
JUT_ASSERT(74, image->magFilter == GX_NEAR);
mDoLib_setResTimgObj(image, mp_texObj[lp1], 0, NULL);
}
+
+#if TARGET_PC
+ static bool hqTexsDrawn = false;
+
+ static u8 hqCircleData[kMapImageTotalPixels];
+ static u8 hqCircleAltData[kMapImageTotalPixels];
+ static u8 hqNijumaruData[kMapImageTotalPixels];
+ static u8 hqEnterData[kMapImageTotalPixels];
+ static u8 hqTryForceData[kMapImageTotalPixels];
+
+ if (!hqTexsDrawn) {
+ constexpr auto center = kMapImageSide / 2.0f;
+ constexpr auto radiusSq = center * center;
+
+ // 6: map_icon_circle16x16_4i.bti - simple circle
+ paint_i8(std::span{hqCircleData}, kMapImageSide, [=](auto x, auto y) {
+ const auto dx = (x + 0.5f) - center;
+ const auto dy = (y + 0.5f) - center;
+ return (dx * dx + dy * dy < radiusSq) ? 0x11 : 0;
+ });
+
+ // 4: im_map_icon_circle_4i.bti - outlined circle
+ paint_i8(std::span{hqCircleAltData}, kMapImageSide, [=](auto x, auto y) {
+ constexpr auto innerRadius = kMapImageSide * 3.0f / 8.0f;
+ constexpr auto innerRadiusSq = innerRadius * innerRadius;
+
+ const auto dx = (x + 0.5f) - center;
+ const auto dy = (y + 0.5f) - center;
+ const auto dSq = dx * dx + dy * dy;
+
+ return dSq < radiusSq ? (dSq < innerRadiusSq ? 0x22 : 0x11) : 0;
+ });
+
+ // 3: im_map_icon_nijumaru_4i.bti - concentric rings
+ paint_i8(std::span{hqNijumaruData}, kMapImageSide, [=](auto x, auto y) {
+ constexpr u8 nijumaruRings[] = {0x11, 0x22, 0x11, 0x11, 0x22, 0x22};
+
+ const auto dx = (x + 0.5f) - center;
+ const auto dy = (y + 0.5f) - center;
+ const auto dSq = dx * dx + dy * dy;
+
+ if (dSq < radiusSq) {
+ const auto ringIndex =
+ static_cast(std::trunc(std::sqrt(dSq) / kMapImageSide * 12));
+ return nijumaruRings[ringIndex];
+ }
+ return u8{0};
+ });
+
+ // 2: im_map_icon_enter_4i.bti - outlined octagram
+ paint_i8(std::span{hqEnterData}, kMapImageSide, [=](auto x, auto y) {
+ constexpr auto outlineWidth = kMapImageSide / 6.0f;
+
+ const auto adx = std::abs((x + 0.5f) - center);
+ const auto ady = std::abs((y + 0.5f) - center);
+ const auto dist =
+ std::min(adx + ady, std::max(adx, ady) * std::numbers::sqrt2_v) -
+ kMapImageSide / 2.0f;
+
+ return dist > 0.0f ? 0 : (dist > -outlineWidth ? 0x22 : 0x33);
+ });
+
+ // 5: im_map_icon_try_force_4i.bti - outlined circle with triangle
+ paint_i8(std::span{hqTryForceData}, kMapImageSide, [=](auto x, auto y) {
+ constexpr auto innerRadiusNorm = 5.0f / 12.0f;
+ constexpr auto innerRadius = kMapImageSide * innerRadiusNorm;
+ constexpr auto innerRadiusSq = innerRadius * innerRadius;
+ constexpr auto triRadius = kMapImageSide * innerRadiusNorm / 2.0f;
+
+ const auto dx = (x + 0.5f) - center;
+ const auto dy = (y + 0.5f) - center;
+ const auto dSq = dx * dx + dy * dy;
+ const auto triSideDist = (std::numbers::sqrt3_v * std::abs(dx) - dy) * 0.5f;
+ const auto insideTri = std::max(dy, triSideDist) < triRadius;
+
+ return insideTri ? 0x22 : (dSq < radiusSq ? (dSq < innerRadiusSq ? 0x33 : 0x22) : 0);
+ });
+
+ hqTexsDrawn = true;
+ }
+
+ constexpr auto replacements = std::to_array >({
+ {2, hqEnterData},
+ {3, hqNijumaruData},
+ {4, hqCircleAltData},
+ {5, hqTryForceData},
+ {6, hqCircleData},
+ });
+
+ for (const auto& [idx, data] : replacements) {
+ JKR_DELETE(mp_texObj[idx]);
+ const auto texobj = JKR_NEW TGXTexObj();
+ GXInitTexObj(
+ texobj, data, kMapImageSide, kMapImageSide, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
+ GXInitTexObjLOD(texobj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
+ mp_texObj[idx] = texobj;
+ }
+#endif
}
void dMpath_n::dTexObjAggregate_c::remove() {
@@ -497,12 +630,6 @@ void dRenderingFDAmap_c::postRenderingMap() {
dMpath_n::dTexObjAggregate_c dMpath_n::m_texObjAgg;
-/* Enabling the following definition will modify the following function to
- * make the map look worse for extra speed in the emulator, especially in large
- * areas such as hyrule field.
- */
-#define HYRULE_FIELD_SPEEDHACK
-
void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_line) {
s32 width = getDecorationLineWidth(p_line->field_0x1);
if (width <= 0) {
@@ -527,8 +654,32 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li
lineColor.r = lineColor.r - 4;
GXSetTevColor(GX_TEVREG1, lineColor);
+#if TARGET_PC
+ GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0);
+ GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
+ GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST);
+ GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
+ GXBegin(GX_LINESTRIP, GX_VTXFMT0, 2 * (data_num - 1));
+ for (int i = 0; i < data_num - 1; i++) {
+ GXPosition1x16(data_p[i]);
+ GXTexCoord2f32(0, 0);
+ GXPosition1x16(data_p[i + 1]);
+ GXTexCoord2f32(0, 0);
+ }
+ GXEnd();
+
+ GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_KONST, GX_CC_TEXC, GX_CC_C1);
+ GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
+ GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);
+ GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
+ GXBegin(GX_POINTS, GX_VTXFMT0, data_num);
+ for (int i = 0; i < data_num; i++) {
+ GXPosition1x16(data_p[i]);
+ GXTexCoord2f32(0, 0);
+ }
+ GXEnd();
+#else
for (int i = 0; i < data_num; i++) {
-#ifndef HYRULE_FIELD_SPEEDHACK
if (i < data_num - 1) {
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0);
GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE,
@@ -547,7 +698,6 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li
GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);
GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
-#endif
GXBegin(GX_POINTS, GX_VTXFMT0, 1);
GXPosition1x16(data_p[0]);
@@ -555,6 +705,7 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li
GXEnd();
data_p++;
}
+#endif
setTevSettingNonTextureDirectColor();
GXClearVtxDesc();
diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp
index bb557efdac..b39c9bb287 100644
--- a/src/d/d_menu_dmap.cpp
+++ b/src/d/d_menu_dmap.cpp
@@ -856,7 +856,46 @@ void dMenu_DmapBg_c::decGoldFrameAlphaRate() {
setGoldFrameAlphaRate(rate);
}
+void dMenu_DmapBg_c::dMapBgWide() {
+ // Scale Base HUD
+ mBaseScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
+ mBaseScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
+
+ // Boss Key, Compass & Map icons
+ mBaseScreen->search(MULTI_CHAR('key_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+ mBaseScreen->search(MULTI_CHAR('con_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+ mBaseScreen->search(MULTI_CHAR('map_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+
+ // Text Header
+ mBaseScreen->search(MULTI_CHAR('t_t00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+ mBaseScreen->search(MULTI_CHAR('f_t_00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+
+ // C Button
+ mBaseScreen->search(MULTI_CHAR('c_btn2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+
+ // Scale Buttons HUD
+ mButtonScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
+ mButtonScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
+
+ // Buttons
+ mButtonScreen->search(MULTI_CHAR('cont_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+
+ // C Button
+ mButtonScreen->search(MULTI_CHAR('c_btn'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+ mButtonScreen->search(MULTI_CHAR('c_text_s'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+ mButtonScreen->search(MULTI_CHAR('c_text'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+ mButtonScreen->search(MULTI_CHAR('f_text_s'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+ mButtonScreen->search(MULTI_CHAR('f_text'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+
+ // Decorations
+ mButtonScreen->search(MULTI_CHAR('kazari_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
+}
+
void dMenu_DmapBg_c::draw() {
+ #if TARGET_PC
+ dMapBgWide();
+ #endif
+
u32 scissor_left;
u32 scissor_top;
u32 scissor_width;
@@ -903,7 +942,10 @@ void dMenu_DmapBg_c::draw() {
f32 local_28c = mpBackTexture->getBounds().i.x;
mpBackTexture->setBlackWhite(color_black, color_white);
mpBackTexture->draw(local_28c, field_0xd94 + mpBackTexture->getBounds().i.y, mpBackTexture->getWidth(),
- mpBackTexture->getHeight(), false, false, false);
+ mpBackTexture->getHeight(),
+ IF_DUSK(dusk::getSettings().game.enableMirrorMode ? true :) false,
+ false,
+ false);
grafContext->scissor(field_0xd94 + mDoGph_gInf_c::getMinXF(),
scissor_top, mDoGph_gInf_c::getWidthF(),
diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp
index 8971b2630d..0bb9717952 100644
--- a/src/d/d_menu_dmap_map.cpp
+++ b/src/d/d_menu_dmap_map.cpp
@@ -11,6 +11,9 @@
#include "d/d_menu_dmap_map.h"
#include "f_op/f_op_msg_mng.h"
#include "m_Do/m_Do_graphic.h"
+#if TARGET_PC
+#include
+#endif
struct dMdm_HIO_prm_res_dst_s {
static void* m_res;
@@ -291,6 +294,9 @@ void dMenu_DmapMap_c::_create(u16 param_0, u16 param_1, u16 param_2, u16 param_3
void dMenu_DmapMap_c::_delete() {
for (int i = 0; i < 2; i++) {
if (mMapImage_p[i] != NULL) {
+#if TARGET_PC
+ GXDestroyCopyTex(mMapImage_p[i]);
+#endif
JKR_DELETE_ARRAY(mMapImage_p[i]);
}
@@ -362,7 +368,7 @@ void dMenu_StageMapCtrl_c::initGetTreasureList(u8 param_0, s8 param_1) {
}
inline static s16 rightModeCnvRot(s16 param_0) {
- return param_0;
+ return IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -param_0 :) param_0;
}
bool dMenu_StageMapCtrl_c::getTreasureList(f32* o_posX, f32* o_posY, s8* param_2, u8* o_swbit,
@@ -399,7 +405,7 @@ bool dMenu_StageMapCtrl_c::getTreasureList(f32* o_posX, f32* o_posY, s8* param_2
}
inline static f32 rightModeCnvPos(f32 param_0) {
- return param_0;
+ return IF_DUSK(dusk::getSettings().game.enableMirrorMode ? -param_0 :) param_0;
}
void dMenu_StageMapCtrl_c::cnvPosTo2Dpos(f32 param_0, f32 param_1, f32* param_2,
diff --git a/src/d/d_menu_fishing.cpp b/src/d/d_menu_fishing.cpp
index 615323767b..b76002d540 100644
--- a/src/d/d_menu_fishing.cpp
+++ b/src/d/d_menu_fishing.cpp
@@ -16,6 +16,8 @@
#include "m_Do/m_Do_graphic.h"
#include
+#include "dusk/version.hpp"
+
typedef void (dMenu_Fishing_c::*initFunc)();
initFunc map_init_process[] = {
&dMenu_Fishing_c::wait_init,
@@ -135,9 +137,9 @@ bool dMenu_Fishing_c::isSync() {
}
void dMenu_Fishing_c::init() {
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
BOOL isEnglish = FALSE;
- if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) {
+ if (dusk::version::isRegionUsa() || (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH)) {
isEnglish = TRUE;
}
#endif
@@ -145,7 +147,7 @@ void dMenu_Fishing_c::init() {
for (int i = 0; i < MAX_FINDABLE_FISHES; i++) {
if (dComIfGs_getFishNum(i) != 0) {
// Fish has been caught once, display it along with it's params
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
if (isEnglish) {
setFishParam(i, dComIfGs_getFishNum(i), dComIfGs_getFishSize(i) / 2.54f);
} else {
diff --git a/src/d/d_menu_fmap.cpp b/src/d/d_menu_fmap.cpp
index 149e03349d..d2b06e962c 100644
--- a/src/d/d_menu_fmap.cpp
+++ b/src/d/d_menu_fmap.cpp
@@ -919,9 +919,20 @@ void dMenu_Fmap_c::region_map_proc() {
}
mpDraw2DBack->regionMapMove(mpStick);
int stage_no, room_no;
+
+#if TARGET_PC
+ f32 arrow_pos_x = mpDraw2DBack->getArrowPos2DX();
+ if (dusk::getSettings().game.enableMirrorMode) {
+ arrow_pos_x = mpDraw2DBack->getMirrorPosX(arrow_pos_x, 0.0f);
+ }
+
+ f32 pos_x = arrow_pos_x - mDoGph_gInf_c::getMinXF() - mDoGph_gInf_c::getWidthF() * 0.5f;
+#else
f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF()
- mDoGph_gInf_c::getWidthF() * 0.5f;
+#endif
f32 pos_y = mpDraw2DBack->getArrowPos2DY() - mDoGph_gInf_c::getHeightF() * 0.5f;
+
mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y,
mStayStageNo, &stage_no, &room_no);
if (mStageCursor != stage_no || mRoomCursor != room_no || mResetAreaName) {
@@ -2464,6 +2475,13 @@ void dMenu_Fmap_c::portalWarpMapMove(STControl* i_stick) {
f32 arrow_y = mpDraw2DBack->getArrowPos2DY();
u8 uVar6 = 0xff;
+#if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ arrow_x = mpDraw2DBack->getMirrorPosX(arrow_x, 0.0f);
+ }
+#endif
+
+
for (int i = 0; i < portal_dat->mCount; i++) {
if (portals[i].mRegionNo == mpDraw2DBack->getRegionCursor() + 1
&& checkDrawPortalIcon(portals[i].mStageNo, portals[i].mSwitchNo))
diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp
index 8692156daf..d62f7d1037 100644
--- a/src/d/d_menu_fmap2D.cpp
+++ b/src/d/d_menu_fmap2D.cpp
@@ -20,6 +20,15 @@
#include "dusk/frame_interpolation.h"
#include
+#if TARGET_PC
+void dMenu_Fmap2DBack_c::fMapBackWide() {
+ mpBaseScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
+ mpBaseScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
+ mpBackScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
+ mpBackScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f);
+}
+#endif
+
dMenu_Fmap2DBack_c::dMenu_Fmap2DBack_c() {
dMeter2Info_setMapDrugFlag(0);
@@ -267,6 +276,10 @@ dMenu_Fmap2DBack_c::~dMenu_Fmap2DBack_c() {
}
void dMenu_Fmap2DBack_c::draw() {
+ #if TARGET_PC
+ fMapBackWide();
+ #endif
+
calcBlink();
J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort();
@@ -1030,6 +1043,12 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) {
calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX,
(mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10);
+#if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ sp14 = getMirrorPosX(sp14, 0.0f);
+ }
+#endif
+
mSelectRegion = 0xff;
for (int i = 7; i >= 0; i--) {
int val = field_0x1230[i];
@@ -1199,7 +1218,7 @@ f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeX() {
}
f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeRealX() {
-#if PLATFORM_GCN && !TARGET_PC
+#if PLATFORM_GCN
return getMapScissorAreaSizeX();
#else
return getMapScissorAreaSizeX() * mDoGph_gInf_c::getScale();
@@ -1384,6 +1403,15 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() {
if (uVar10 != uVar9) {
bool b = 0;
f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10]));
+
+ #if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ b = true;
+ v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10])),
+ mRegionMapSizeX[uVar10] * mZoom * 0.5f);
+ }
+ #endif
+
mpAreaTex[uVar10]->draw(
v, mTransZ + (dVar13 + (mRegionMinMapY[uVar10] + field_0xf2c[uVar10])),
mRegionMapSizeX[uVar10] * mZoom, mRegionMapSizeY[uVar10] * mZoom, b, false,
@@ -1391,6 +1419,15 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() {
} else {
bool b = 0;
f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9]));
+
+ #if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ b = true;
+ v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9])),
+ mRegionMapSizeX[uVar9] * mZoom * 0.5f);
+ }
+ #endif
+
mpAreaTex[uVar9]->draw(
v, mTransZ + (dVar13 + (mRegionMinMapY[uVar9] + field_0xf2c[uVar9])),
mRegionMapSizeX[uVar9] * mZoom, mRegionMapSizeY[uVar9] * mZoom, b, false,
@@ -1407,6 +1444,11 @@ void dMenu_Fmap2DBack_c::stageTextureDraw() {
mpSpotTexture->setAlpha(mAlphaRate * 255.0f * field_0xfa8 * mSpotTextureFadeAlpha);
}
+#if TARGET_PC
+ JUTPalette* pPalette = mpSpotTexture->getTexture(0)->getPalette();
+ pPalette->dataUploaded();
+#endif
+
mpSpotTexture->draw(mTransX + getMapScissorAreaLX(), mTransZ + getMapScissorAreaLY(),
getMapScissorAreaSizeRealX(), getMapScissorAreaSizeRealY(), false, false,
false);
@@ -2179,6 +2221,17 @@ void dMenu_Fmap2DBack_c::setArrowPosAxis(f32 i_posX, f32 i_posZ) {
control_ypos = 0.0f;
}
+#if TARGET_PC
+void dMenu_Fmap2DTop_c::fMapTopWide() {
+ mpTitleScreen->search(MULTI_CHAR('spot0_n'))->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
+ mpTitleScreen->search(MULTI_CHAR('spot2_n'))->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f);
+ mpTitleScreen->search(MULTI_CHAR('name_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-243.0f), -169.0f);
+ mpTitleScreen->search(MULTI_CHAR('sub_n_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-80.0f), -154.0f);
+ mpTitleScreen->search(MULTI_CHAR('btn_i_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-241.0f), 177.0f);
+ mpTitleScreen->search(MULTI_CHAR('cont_n'))->translate(mDoGph_gInf_c::ScaleHUDXRight(515.0f), 83.0f);
+}
+#endif
+
dMenu_Fmap2DTop_c::dMenu_Fmap2DTop_c(JKRExpHeap* i_heap, STControl* i_stick) {
mpHeap = i_heap;
mTransX = 0.0f;
@@ -2572,6 +2625,10 @@ void dMenu_Fmap2DTop_c::setAllAlphaRate(f32 i_rate, bool i_init) {
}
void dMenu_Fmap2DTop_c::draw() {
+ #if TARGET_PC
+ fMapTopWide();
+ #endif
+
u32 scissor_left, scissor_top, scissor_width, scissor_height;
J2DOrthoGraph* ctx = static_cast(dComIfGp_getCurrentGrafPort());
ctx->setup2D();
diff --git a/src/d/d_menu_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp
index 3d1a9e2523..c500aff0be 100644
--- a/src/d/d_menu_fmap_map.cpp
+++ b/src/d/d_menu_fmap_map.cpp
@@ -8,6 +8,9 @@
#include "d/d_debug_viewer.h"
#include "d/d_menu_fmap_map.h"
#include "m_Do/m_Do_graphic.h"
+#if TARGET_PC
+#include
+#endif
#include
static u8 twoValueLineInterpolation(u8 i_value1, u8 i_value2, f32 i_param) {
@@ -494,6 +497,9 @@ void dMenu_FmapMap_c::_delete() {
mResTIMG = NULL;
}
if (mMapImage_p != NULL) {
+#if TARGET_PC
+ GXDestroyCopyTex(mMapImage_p);
+#endif
JKR_DELETE_ARRAY(mMapImage_p);
mMapImage_p = NULL;
}
diff --git a/src/d/d_menu_letter.cpp b/src/d/d_menu_letter.cpp
index a0155ef6b4..4bfda4b9cd 100644
--- a/src/d/d_menu_letter.cpp
+++ b/src/d/d_menu_letter.cpp
@@ -17,6 +17,10 @@
#include "d/d_msg_scrn_arrow.h"
#include "d/d_lib.h"
+#ifdef TARGET_PC
+#include "dusk/achievements.h"
+#endif
+
#if VERSION == VERSION_GCN_JPN
#define D_MENU_LETTER_LINE_MAX 9
#else
@@ -514,6 +518,10 @@ void dMenu_Letter_c::read_open_init() {
setAButtonString(0);
setBButtonString(0);
mpBlackTex->setAlpha(0);
+
+ #ifdef TARGET_PC
+ dusk::AchievementSystem::get().signal("open_letter");
+ #endif
}
void dMenu_Letter_c::read_open_move() {
@@ -957,7 +965,8 @@ void dMenu_Letter_c::screenSetBase() {
}
if (field_0x374 > 1) {
J2DPane* pJVar6 = mpBaseScreen->search('pi_n');
- f32 dVar18 = field_0x1f0[1]->getBounds().i.x - field_0x1f0[0]->getBounds().i.x;
+ f32 x1 = field_0x1f0[1]->getBounds().i.x;
+ f32 dVar18 = x1 - field_0x1f0[0]->getBounds().i.x;
f32 dVar17 = dVar18 * (field_0x374 - 1);
f32 dVar16 = (pJVar6->getWidth() / 2) - (dVar17 / 2);
for (int i = 0; i < 9; i++) {
diff --git a/src/d/d_menu_map_common.cpp b/src/d/d_menu_map_common.cpp
index e7d0fef7ad..0017975c4e 100644
--- a/src/d/d_menu_map_common.cpp
+++ b/src/d/d_menu_map_common.cpp
@@ -343,6 +343,11 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
}
f32 pos_x = icon_pos_x + i_posX;
+ #if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ pos_x = getMirrorCenterPosX(pos_x, 0.0f);
+ }
+ #endif
mpDrawCursor->setPos(pos_x, icon_pos_y + i_posY);
mpDrawCursor->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalCursorScale);
mpDrawCursor->draw();
@@ -364,6 +369,12 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
}
f32 pos_x = (icon_pos_x + i_posX);
+ #if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ pos_x = getMirrorCenterPosX(pos_x, 0.0f);
+ }
+ #endif
+
mpPortalIcon->setPos(pos_x, icon_pos_y + i_posY);
mpPortalIcon->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalIconScale);
mpPortalIcon->draw();
@@ -399,6 +410,12 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4
}
f32 pos_x = i_posX + (icon_pos_x - (icon_size_x / 2));
+ #if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ pos_x = getMirrorCenterPosX(i_posX + (icon_pos_x - (icon_size_x / 2)), icon_size_x / 2);
+ }
+ #endif
+
mPictures[mIconInfo[info_idx].icon_no]->draw(pos_x, (i_posY + (icon_pos_y - icon_size_y / 2)),
icon_size_x, icon_size_y, false, false, false);
diff --git a/src/d/d_meter2.cpp b/src/d/d_meter2.cpp
index 5bd321e7a8..097cde7c37 100644
--- a/src/d/d_meter2.cpp
+++ b/src/d/d_meter2.cpp
@@ -24,9 +24,10 @@
#include "d/actor/d_a_horse.h"
#include
+#if TARGET_PC
#include "dusk/memory.h"
-
-#include "dusk/memory.h"
+#include "dusk/settings.h"
+#endif
int dMeter2_c::_create() {
stage_stag_info_class* stag_info = dComIfGp_getStageStagInfo();
@@ -316,6 +317,14 @@ int dMeter2_c::_execute() {
}
int dMeter2_c::_draw() {
+ #if TARGET_PC
+ if (dusk::getSettings().game.recordingMode || dusk::getSettings().game.minimalHUD ||
+ dusk::getSettings().game.debugFlyCam)
+ {
+ return 1;
+ }
+ #endif
+
if (mpMap != NULL) {
mpMap->_draw();
}
@@ -424,12 +433,6 @@ void dMeter2_c::setLifeZero() {
void dMeter2_c::checkStatus() {
mStatus = 0;
- #if TARGET_PC
- if (dusk::getSettings().game.disableMainHUD) {
- mStatus |= 0xF0000000;
- }
- #endif
-
field_0x12c = field_0x128;
field_0x128 = daPy_py_c::checkNowWolf();
diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp
index 2df28b2655..584a2852f6 100644
--- a/src/d/d_meter_HIO.cpp
+++ b/src/d/d_meter_HIO.cpp
@@ -2306,6 +2306,10 @@ void dMeter_drawHIO_c::updateOnWide() {
// River Canoe Minigame
g_drawHIO.mMiniGame.mCounterPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[1]);
g_drawHIO.mMiniGame.mIconPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[1]);
+
+ // Bulblin Count in Hidden Village
+ g_drawHIO.mMiniGame.mCounterPosX[2] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[2]);
+ g_drawHIO.mMiniGame.mIconPosX[2] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[2]);
#endif
}
diff --git a/src/d/d_meter_button.cpp b/src/d/d_meter_button.cpp
index d36a124d17..e55b42bc1a 100644
--- a/src/d/d_meter_button.cpp
+++ b/src/d/d_meter_button.cpp
@@ -368,8 +368,22 @@ void dMeterButton_c::draw() {
}
if (var_r3) {
+#ifdef TARGET_PC
+ if (dusk::frame_interp::get_ui_tick_pending()) {
+ mWasListen[i] = var_r22;
+ mWasRepeat[i] = var_r23;
+ } else {
+ var_r22 = mWasListen[i];
+ var_r23 = mWasRepeat[i];
+ }
+#endif
if (var_r22) {
- if (field_0x2e8[i] == 18.0f) {
+#ifdef TARGET_PC
+ if (field_0x2e8[i] == 18.0f && dusk::frame_interp::get_ui_tick_pending())
+#else
+ if (field_0x2e8[i] == 18.0f)
+#endif
+ {
mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 0);
}
diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp
index 7af18f0732..f67d6054e6 100644
--- a/src/d/d_msg_class.cpp
+++ b/src/d/d_msg_class.cpp
@@ -307,7 +307,7 @@ static u8 getOutFontNumberType(int param_0) {
}
}
-#if VERSION == VERSION_GCN_PAL
+#if TARGET_PC || VERSION == VERSION_GCN_PAL
static void setPlayerName(char* i_player_name, u8 param_2) {
if (param_2 != 0) {
strcpy(i_player_name, dComIfGs_getPlayerName());
@@ -1485,7 +1485,7 @@ bool jmessage_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_siz
char buffer[40];
switch (i_tag & 0xFF00FFFF) {
case MSGTAG_PLAYER_GENITIV:
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) {
setPlayerName(buffer, 1);
} else {
@@ -1495,7 +1495,7 @@ bool jmessage_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_siz
push_word(buffer);
return true;
case MSGTAG_HORSE_GENITIV:
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) {
setHorseName(buffer, 1);
} else {
@@ -2066,7 +2066,7 @@ bool jmessage_tSequenceProcessor::do_isReady() {
case 0:
case 5:
case 6:
- if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0) {
+ if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0 IF_DUSK(|| (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)))) {
field_0xa4 = 0;
pReference->onBatchFlag();
pReference->setCharCnt(D_MSG_CLASS_CHAR_CNT_MAX);
@@ -4270,7 +4270,7 @@ bool jmessage_string_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u3
char buffer[40];
switch (i_tag & 0xFF00FFFF) {
case MSGTAG_PLAYER_GENITIV:
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) {
setPlayerName(buffer, 1);
} else {
@@ -4281,7 +4281,7 @@ bool jmessage_string_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u3
stack_pushCurrent(buffer);
break;
case MSGTAG_HORSE_GENITIV:
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) {
setHorseName(buffer, 1);
} else {
@@ -4878,7 +4878,7 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data,
char buffer[40];
switch (i_tag & 0xFF00FFFF) {
case MSGTAG_PLAYER_GENITIV:
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) {
setPlayerName(buffer, 1);
} else {
@@ -4889,7 +4889,7 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data,
push_word(buffer);
break;
case MSGTAG_HORSE_GENITIV:
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) {
setHorseName(buffer, 1);
} else {
diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp
index 9b7b64ebda..44b5c66571 100644
--- a/src/d/d_msg_object.cpp
+++ b/src/d/d_msg_object.cpp
@@ -24,12 +24,17 @@
#include "f_op/f_op_msg_mng.h"
#include
#include
+
+#include "JSystem/JKernel/JKRExpHeap.h"
+#include "dusk/version.hpp"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_lib.h"
-#include "JSystem/JKernel/JKRExpHeap.h"
#if TARGET_PC
#include "dusk/settings.h"
+#include
+#include
+#include
#endif
static void dMsgObject_addFundRaising(s16 param_0);
@@ -422,6 +427,17 @@ static void dummyStrings() {
dMsgObject_HIO_c g_MsgObject_HIO_c;
int dMsgObject_c::_execute() {
+// TODO: enabling wii message overrides fixes direction text, but gives wrong item control text
+/*#if TARGET_PC
+ if (dusk::getSettings().game.enableMirrorMode) {
+ // enable wii message index override
+ g_MsgObject_HIO_c.mMessageDisplay = 1;
+ } else if (!dusk::getSettings().game.enableMirrorMode && g_MsgObject_HIO_c.mMessageDisplay == 1) {
+ g_MsgObject_HIO_c.mMessageDisplay = 0;
+ }
+#endif*/
+
+
field_0x4c7 = 0;
if (mpTalkHeap != NULL) {
field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap);
@@ -1592,7 +1608,7 @@ u8 dMsgObject_c::isSend() {
return 2;
}
} else {
- if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) ||)
+ if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0) && !isShopItemMessage()) ||)
mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) {
return 2;
}
@@ -1643,7 +1659,30 @@ void dMsgObject_c::readMessageGroupLocal(mDoDvdThd_mountXArchive_c** p_arcMount)
#else
#if TARGET_PC
// Original game UB
- snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup);
+
+ if (dusk::version::isRegionPal()) {
+ switch (dComIfGs_getPalLanguage()) {
+ case dSv_player_config_c::LANGUAGE_GERMAN:
+ snprintf(arcName, sizeof(arcName), "/res/Msgde/bmgres%d.arc", msgGroup);
+ break;
+ case dSv_player_config_c::LANGUAGE_FRENCH:
+ snprintf(arcName, sizeof(arcName), "/res/Msgfr/bmgres%d.arc", msgGroup);
+ break;
+ case dSv_player_config_c::LANGUAGE_SPANISH:
+ snprintf(arcName, sizeof(arcName), "/res/Msgsp/bmgres%d.arc", msgGroup);
+ break;
+ case dSv_player_config_c::LANGUAGE_ITALIAN:
+ snprintf(arcName, sizeof(arcName), "/res/Msgit/bmgres%d.arc", msgGroup);
+ break;
+ default:
+ snprintf(arcName, sizeof(arcName), "/res/Msguk/bmgres%d.arc", msgGroup);
+ }
+ } else if (dusk::version::isRegionJpn()) {
+ snprintf(arcName, sizeof(arcName), "/res/Msgjp/bmgres%d.arc", msgGroup);
+ } else {
+ snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup);
+ }
+
#else
sprintf(arcName, "/res/Msgus/bmgres%d.arc", msgGroup);
#endif
@@ -1841,6 +1880,39 @@ bool dMsgObject_c::isTalkMessage() {
return true;
}
+#if TARGET_PC
+bool dMsgObject_c::isShopItemMessage() {
+
+ // Probably a better way to do this than just listing every message id, but this works for now
+ // Note: Keep contents sorted so we can use binary search
+ const auto shopMsgIds = std::to_array>({
+ {},
+ // zel_01.bmg - Seras Shop
+ {7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7013, 7014, 7022, 7023, 7028, 7029,
+ 7044, 7045, 7053},
+ // zel_02.bmg - Kakariko Shops
+ {5181, 5182, 5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665,
+ 5697, 5698, 5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988,
+ 5989, 5990, 5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999},
+ // zel_03.bmg - Death Mountain Shop
+ {5303, 5304, 5306, 5310, 5311, 5314, 5315, 5322, 5323, 5324, 5496, 5497, 5498, 5499},
+ // zel_04.bmg - Castle Town Shops
+ {5407, 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, 5420, 5431,
+ 5432, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452, 5462},
+ // zel_05.bmg - Oocca Shop
+ {9428, 9429, 9430, 9431, 9432, 9437, 9443, 9448, 9449, 9451, 9459}
+ });
+
+ u16 id = mMessageID;
+ s16 group = dMsgObject_getGroupID();
+ if (group < shopMsgIds.size()) {
+ return std::ranges::binary_search(shopMsgIds[group], id);
+ }
+ return false;
+
+}
+#endif
+
const char* dMsgObject_c::getSmellName() {
JMSMesgInfo_c* info_header_p = (JMSMesgInfo_c*)((char*)mpMsgRes + 0x20);
char* data_ptr = (char*)info_header_p + info_header_p->header.size;
diff --git a/src/d/d_msg_scrn_arrow.cpp b/src/d/d_msg_scrn_arrow.cpp
index 7ee3b0503c..cea0dae145 100644
--- a/src/d/d_msg_scrn_arrow.cpp
+++ b/src/d/d_msg_scrn_arrow.cpp
@@ -6,6 +6,10 @@
#include "d/d_com_inf_game.h"
#include "d/d_pane_class.h"
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
dMsgScrnArrow_c::dMsgScrnArrow_c() {
mpScreen = JKR_NEW J2DScreen();
JUT_ASSERT(0, mpScreen != NULL);
@@ -65,6 +69,11 @@ dMsgScrnArrow_c::~dMsgScrnArrow_c() {
}
void dMsgScrnArrow_c::draw() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
J2DGrafContext* graf_ctx = dComIfGp_getCurrentGrafPort();
mpScreen->draw(0.0f, 0.0f, graf_ctx);
}
diff --git a/src/d/d_msg_scrn_base.cpp b/src/d/d_msg_scrn_base.cpp
index 84899c2824..fdd9e57f9c 100644
--- a/src/d/d_msg_scrn_base.cpp
+++ b/src/d/d_msg_scrn_base.cpp
@@ -8,6 +8,10 @@
#include "d/d_pane_class.h"
#include
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
dMsgScrnBase_c::dMsgScrnBase_c() {
init();
}
@@ -57,12 +61,22 @@ void dMsgScrnBase_c::init() {
}
void dMsgScrnBase_c::multiDraw() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
if (field_0x48 != NULL) {
dComIfGd_set2DOpa(field_0x48);
}
}
void dMsgScrnBase_c::draw() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
J2DGrafContext* ctx = dComIfGp_getCurrentGrafPort();
ctx->setup2D();
@@ -72,10 +86,20 @@ void dMsgScrnBase_c::draw() {
}
void dMsgScrnBase_c::drawSelf() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
drawOutFont(0.0f, 0.0f, 1.0f);
}
void dMsgScrnBase_c::drawOutFont(f32 param_0, f32 param_1, f32 param_2) {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
mpOutFont->draw(NULL, param_0, param_1, param_2);
}
diff --git a/src/d/d_msg_scrn_boss.cpp b/src/d/d_msg_scrn_boss.cpp
index 64141770b3..54938ceb72 100644
--- a/src/d/d_msg_scrn_boss.cpp
+++ b/src/d/d_msg_scrn_boss.cpp
@@ -6,6 +6,10 @@
#include "d/d_msg_object.h"
#include "d/d_pane_class.h"
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
dMsgScrnBoss_c::dMsgScrnBoss_c() {
static u64 t_tag[7] = {
MULTI_CHAR('sfontb0'), MULTI_CHAR('sfontb1'), MULTI_CHAR('sfontb2'), MULTI_CHAR('sfontl0'), MULTI_CHAR('sfontl1'), MULTI_CHAR('sfontl2'), MULTI_CHAR('sfont00'),
@@ -91,6 +95,11 @@ void dMsgScrnBoss_c::exec() {
}
void dMsgScrnBoss_c::drawSelf() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
J2DGrafContext* ctx = dComIfGp_getCurrentGrafPort();
ctx->setup2D();
drawOutFont(0.0f, 0.0f, 1.0f);
diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp
index 1d39cba1a1..c701ea0b2b 100644
--- a/src/d/d_msg_scrn_howl.cpp
+++ b/src/d/d_msg_scrn_howl.cpp
@@ -20,6 +20,7 @@
#include "Z2AudioLib/Z2WolfHowlMgr.h"
#if TARGET_PC
+#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
#endif
@@ -580,9 +581,14 @@ void dMsgScrnHowl_c::drawWave() {
f17 = local_60;
f18 = local_64;
} else {
- field_0x2134++;
- if (field_0x2134 > 30) {
- field_0x2134 = 0;
+#if TARGET_PC
+ if (dusk::frame_interp::get_ui_tick_pending())
+#endif
+ {
+ field_0x2134++;
+ if (field_0x2134 > 30) {
+ field_0x2134 = 0;
+ }
}
if (field_0x2134 < 15) {
local_dc = field_0x2134 / 15.0f;
diff --git a/src/d/d_msg_scrn_kanban.cpp b/src/d/d_msg_scrn_kanban.cpp
index a9edbef7df..d6e91b12f6 100644
--- a/src/d/d_msg_scrn_kanban.cpp
+++ b/src/d/d_msg_scrn_kanban.cpp
@@ -13,6 +13,10 @@
#include "d/d_msg_out_font.h"
#include "d/d_pane_class.h"
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
dMsgScrnKanban_c::dMsgScrnKanban_c(JKRExpHeap* param_0) {
if (param_0 != NULL) {
field_0xd4 = param_0;
@@ -176,6 +180,11 @@ void dMsgScrnKanban_c::exec() {
}
void dMsgScrnKanban_c::draw() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
J2DGrafContext* grafContext = dComIfGp_getCurrentGrafPort();
grafContext->setup2D();
mpScreen->draw(0.0f, 0.0f, grafContext);
diff --git a/src/d/d_msg_scrn_place.cpp b/src/d/d_msg_scrn_place.cpp
index b2c28efc95..93c2ec7c20 100644
--- a/src/d/d_msg_scrn_place.cpp
+++ b/src/d/d_msg_scrn_place.cpp
@@ -12,6 +12,10 @@
#include "d/d_msg_object.h"
#include "d/d_pane_class.h"
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
dMsgScrnPlace_c::dMsgScrnPlace_c() {
static u64 t_tag[7] = {
MULTI_CHAR('sfontb0'), MULTI_CHAR('sfontb1'), MULTI_CHAR('sfontb2'), MULTI_CHAR('sfontl0'), MULTI_CHAR('sfontl1'), MULTI_CHAR('sfontl2'), MULTI_CHAR('sfont00'),
@@ -127,6 +131,11 @@ void dMsgScrnPlace_c::exec() {
}
void dMsgScrnPlace_c::drawSelf() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
J2DGrafContext* grafContext = dComIfGp_getCurrentGrafPort();
grafContext->setup2D();
drawOutFont(0.0f, 0.0f, 1.0f);
diff --git a/src/d/d_msg_scrn_talk.cpp b/src/d/d_msg_scrn_talk.cpp
index c9f925e784..ace5abd3bb 100644
--- a/src/d/d_msg_scrn_talk.cpp
+++ b/src/d/d_msg_scrn_talk.cpp
@@ -20,6 +20,10 @@
#include "JSystem/J2DGraph/J2DScreen.h"
#include
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
dMsgScrnTalk_c::dMsgScrnTalk_c(u8 param_1, u8 param_2, JKRExpHeap* param_3) {
if (param_3 != NULL) {
field_0xe4 = param_3;
@@ -303,6 +307,11 @@ void dMsgScrnTalk_c::exec() {
}
void dMsgScrnTalk_c::drawSelf() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
J2DGrafContext* grafContext[1];
grafContext[0] = dComIfGp_getCurrentGrafPort();
grafContext[0]->setup2D();
diff --git a/src/d/d_msg_scrn_tree.cpp b/src/d/d_msg_scrn_tree.cpp
index 0dee41ef5a..d17ced9bd8 100644
--- a/src/d/d_msg_scrn_tree.cpp
+++ b/src/d/d_msg_scrn_tree.cpp
@@ -9,6 +9,10 @@
#include "d/d_msg_out_font.h"
#include "d/d_pane_class.h"
+#if TARGET_PC
+#include "dusk/settings.h"
+#endif
+
dMsgScrnTree_c::dMsgScrnTree_c(JUTFont* param_0, JKRExpHeap* param_1) {
if (param_1 != NULL) {
field_0xd8 = param_1;
@@ -187,6 +191,11 @@ void dMsgScrnTree_c::exec() {
}
void dMsgScrnTree_c::draw() {
+#if TARGET_PC
+ if (dusk::getSettings().game.recordingMode) {
+ return;
+ }
+#endif
J2DGrafContext* grafContext = dComIfGp_getCurrentGrafPort();
grafContext->setup2D();
mpScreen->draw(0.0f, 0.0f, grafContext);
diff --git a/src/d/d_msg_unit.cpp b/src/d/d_msg_unit.cpp
index f773abd373..7bbe8b3d70 100644
--- a/src/d/d_msg_unit.cpp
+++ b/src/d/d_msg_unit.cpp
@@ -334,11 +334,11 @@ void dMsgUnit_c::setTag(int i_type, int i_value, char* o_buffer, bool param_4) {
vals[1] = ((dMsgUnit_inf1_section_t*)pInfoBlock)->entries[i_type].endFrame;
#endif
-#if REGION_PAL
+#if TARGET_PC || REGION_PAL
if (i_value == 1 ||
(dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_FRENCH &&
i_value == 0)) {
-#elif !REGION_USA
+#elif !REGION_USA // Dusk TODO: What? This checks for Spanish when *not* PAL or USA?
if (i_value == 1 ||
(dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_SPANISH &&
i_value == 0)) {
diff --git a/src/d/d_name.cpp b/src/d/d_name.cpp
index e4232eff34..1f3ccbb1d7 100644
--- a/src/d/d_name.cpp
+++ b/src/d/d_name.cpp
@@ -8,9 +8,15 @@
#include "m_Do/m_Do_controller_pad.h"
#include
#include
+
#include "JSystem/J2DGraph/J2DAnmLoader.h"
+#include "dusk/version.hpp"
#include "f_op/f_op_msg_mng.h"
+static bool isPalOrJpn() {
+ return dusk::version::isRegionPal() || dusk::version::isRegionJpn();
+}
+
static const char* l_mojiHira[65] = {
"あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す",
"せ", "そ", "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は",
@@ -66,15 +72,31 @@ static const char* l_mojiEisu[65] = {
"X", "k", "x", ",", "L", "Y", "l", "y", ".", "M", "Z", "m", "z", " ",
};
-#if REGION_PAL
-static char* l_mojiEisuPal_1[65] = {
+#if TARGET_PC
+// The game normally mutates this string list to fill in the real character codes.
+// That can't work on a modern platform, so instead I've filled them out ahead of time.
+static const char* l_mojiEisuPal_1[65] = {
+ "A", "N", "\xC0", "\xCF", "1", "B", "O", "\xC1", "\xD0", "2", "C", "P", "\xC2", "\xD1", "3", "D", "Q",
+ "\xC3", "\xD2", "4", "E", "R", "\xC4", "\xD3", "5", "F", "S", "\xC5", "\xD4", "6", "G", "T", "\xC6", "\xD5",
+ "7", "H", "U", "\xC7", "\xD6", "8", "I", "V", "\xC8", "\xD7", "9", "J", "W", "\xC9", "\xD8", "0", "K",
+ "X", "\xCA", "\xD9", ",", "L", "Y", "\xCB", "\xDA", ".", "M", "Z", "\xCC", "\xDB", " ",
+};
+
+static const char* l_mojiEisuPal_2[65] = {
+ "a", "n", "\xE0", "\xEF", "1", "b", "o", "\xE1", "\xF0", "2", "c", "p", "\xE2", "\xF1", "3", "d", "q",
+ "\xE3", "\xF2", "4", "e", "r", "\xE4", "\xF3", "5", "f", "s", "\xE5", "\xF4", "6", "g", "t", "\xE6",
+ "\xF5", "7", "h", "u", "\xE7", "\xF6", "8", "i", "v", "\xE8", "\xF7", "9", "j", "w", "\xE9", "\xF8", "0",
+ "k", "x", "\xEA", "\xF9", ",", "l", "y", "\xEB", "\xFA", ".", "m", "z", "\xEC", "\xFB", " ",
+};
+#elif REGION_PAL
+static const char* l_mojiEisuPal_1[65] = {
"A", "N", "AA", "BB", "1", "B", "O", "CC", "DD", "2", "C", "P", "EE", "FF", "3", "D", "Q",
"GG", "HH", "4", "E", "R", "II", "JJ", "5", "F", "S", "KK", "LL", "6", "G", "T", "MM", "NN",
"7", "H", "U", "OO", "PP", "8", "I", "V", "QQ", "RR", "9", "J", "W", "SS", "TT", "0", "K",
"X", "UU", "VV", ",", "L", "Y", "WW", "XX", ".", "M", "Z", "YY", "ZZ", " ",
};
-static char* l_mojiEisuPal_2[65] = {
+static const char* l_mojiEisuPal_2[65] = {
"a", "n", "aa", "bb", "1", "b", "o", "cc", "dd", "2", "c", "p", "ee", "ff", "3", "d", "q",
"gg", "hh", "4", "e", "r", "ii", "jj", "5", "f", "s", "kk", "ll", "6", "g", "t", "mm",
"nn", "7", "h", "u", "oo", "pp", "8", "i", "v", "qq", "rr", "9", "j", "w", "ss", "tt", "0",
@@ -82,6 +104,16 @@ static char* l_mojiEisuPal_2[65] = {
};
#endif
+#if TARGET_PC
+// ' ' (full-width space)
+#define SPACE_MAYBE_FULL (dusk::version::isRegionJpn() ? '\x81\x40' : ' ')
+#elif REGION_JPN
+// ' ' (full-width space)
+#define SPACE_MAYBE_FULL '\x81\x40'
+#else
+#define SPACE_MAYBE_FULL ' '
+#endif
+
static dNm_HIO_c g_nmHIO;
typedef void (dName_c::*selProcFunc)(void);
@@ -163,13 +195,18 @@ void dName_c::init() {
field_0x2ac = mSelProc;
field_0x2ad = mSelProc;
field_0x2ae = field_0x2ac;
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC
+ mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO;
+ #elif REGION_PAL || REGION_JPN
mMojiSet = MOJI_HIRA;
#else
mMojiSet = MOJI_EIGO;
#endif
mPrevMojiSet = 255;
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC
+ mSelMenu = isPalOrJpn() ? MENU_HIRA : MENU_END;
+ mPrevSelMenu = isPalOrJpn() ? MENU_HIRA : MENU_END;
+ #elif REGION_PAL || REGION_JPN
mSelMenu = MENU_HIRA;
mPrevSelMenu = MENU_HIRA;
#else
@@ -187,7 +224,8 @@ void dName_c::initial() {
mNextNameStr[0] = 0;
}
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC || REGION_PAL || REGION_JPN
+ IF_DUSK_BLOCK(isPalOrJpn())
if (mSelProc == PROC_MOJI_SELECT) {
mMenuIcon[mMojiSet]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale);
mMenuText[mMojiSet]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF));
@@ -196,6 +234,7 @@ void dName_c::initial() {
mMenuText[mPrevMojiSet]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF));
}
}
+ IF_DUSK_BLOCK_END
#endif
}
@@ -230,8 +269,8 @@ void dName_c::_move() {
stick->checkTrigger();
(this->*SelProc[mSelProc])();
- #if REGION_PAL || REGION_JPN
- if (mDoCPd_c::getTrigY(PAD_1)) {
+ #if TARGET_PC || REGION_PAL || REGION_JPN
+ if (IF_DUSK(isPalOrJpn() &&) mDoCPd_c::getTrigY(PAD_1)) {
mDoAud_seStart(Z2SE_SY_DUMMY, 0, 0, 0);
mPrevMojiSet = mMojiSet;
mMojiSet++;
@@ -245,8 +284,8 @@ void dName_c::_move() {
mojiListChange();
} else {
#endif
- #if REGION_JPN
- if (mDoCPd_c::getTrigX(PAD_1)) {
+ #if TARGET_PC || REGION_JPN
+ if (IF_DUSK(dusk::version::isRegionJpn() &&) mDoCPd_c::getTrigX(PAD_1)) {
if (mCurPos != 0) {
if (mojiChange(mCurPos - 1) == 1) {
mDoAud_seStart(Z2SE_SY_DUMMY, 0, 0, 0);
@@ -283,12 +322,14 @@ void dName_c::_move() {
backSpace();
}
} else if (mDoCPd_c::getTrigStart(PAD_1)) {
+#define EIGO_OR_END DUSK_IF_ELSE((dusk::version::isRegionPal() ? MENU_EIGO : MENU_END), MENU_END)
+
#if REGION_PAL
if ((mSelProc != PROC_MENU_SELECT || mSelMenu != MENU_EIGO) &&
(mSelProc == PROC_MENU_SELECT || mSelProc == PROC_MOJI_SELECT))
{
#else
- if ((mSelProc != PROC_MENU_SELECT || mSelMenu != MENU_END) &&
+ if ((mSelProc != PROC_MENU_SELECT || mSelMenu != EIGO_OR_END) &&
(mSelProc == PROC_MENU_SELECT || mSelProc == PROC_MOJI_SELECT))
{
#endif
@@ -297,7 +338,7 @@ void dName_c::_move() {
#if REGION_PAL
mSelMenu = MENU_EIGO;
#else
- mSelMenu = MENU_END;
+ mSelMenu = EIGO_OR_END;
#endif
switch (mSelProc) {
@@ -314,10 +355,10 @@ void dName_c::_move() {
}
}
}
- #if REGION_JPN
+ #if TARGET_PC || REGION_JPN
}
#endif
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC || REGION_PAL || REGION_JPN
}
#endif
@@ -327,10 +368,9 @@ void dName_c::_move() {
int dName_c::nameCheck() {
for (int i = 8, len = 7; i > 0; i--) {
#if REGION_JPN
- // ' ' (full-width space)
if (mChrInfo[len].mCharacter != ' ' && mChrInfo[len].mCharacter != '\x81\x40') {
#else
- if (mChrInfo[len].mCharacter != ' ') {
+ if (mChrInfo[len].mCharacter != ' ' IF_DUSK(&& (!dusk::version::isRegionJpn() || mChrInfo[len].mCharacter != '\x81\x40'))) {
#endif
return len + 1;
}
@@ -344,8 +384,8 @@ void dName_c::playNameSet(int nameLength) {
char* str = mInputStr;
for (int i = 0; i < nameLength; i++) {
- #if REGION_JPN
- if (mChrInfo[i].mMojiSet == 2) {
+ #if TARGET_PC || REGION_JPN
+ if (!dusk::version::isRegionJpn() || mChrInfo[i].mMojiSet == 2) {
*str = mChrInfo[i].mCharacter;
str += 1;
} else {
@@ -727,7 +767,34 @@ int dName_c::getMoji() {
int result = -1;
const char* moji;
- #if REGION_PAL
+ #if TARGET_PC
+ if (dusk::version::isRegionPal()) {
+ switch (mMojiSet) {
+ case MOJI_HIRA:
+ moji = l_mojiEisuPal_1[mCharRow + mCharColumn * 5];
+ break;
+ case MOJI_KATA:
+ moji = l_mojiEisuPal_2[mCharRow + mCharColumn * 5];
+ break;
+ default:
+ abort();
+ }
+ } else {
+ switch (mMojiSet) {
+ case MOJI_HIRA:
+ moji = l_mojiHira[mCharRow + mCharColumn * 5];
+ break;
+ case MOJI_KATA:
+ moji = l_mojikata[mCharRow + mCharColumn * 5];
+ break;
+ case MOJI_EIGO:
+ moji = l_mojiEisu[mCharRow + mCharColumn * 5];
+ break;
+ default:
+ abort();
+ }
+ }
+ #elif REGION_PAL
switch (mMojiSet) {
case MOJI_HIRA:
moji = l_mojiEisuPal_1[mCharRow + mCharColumn * 5];
@@ -750,7 +817,17 @@ int dName_c::getMoji() {
}
#endif
- #if REGION_JPN
+ #if TARGET_PC
+ if (dusk::version::isRegionJpn()) {
+ if (*(u8*)moji >> 4 == 0x8 || *(u8*)moji >> 4 == 0x9) {
+ result = *(u16*)moji;
+ } else {
+ result = *moji;
+ }
+ } else {
+ result = *moji;
+ }
+ #elif REGION_JPN
if (*(u8*)moji >> 4 == 0x8 || *(u8*)moji >> 4 == 0x9) {
result = *(u16*)moji;
} else {
@@ -763,6 +840,14 @@ int dName_c::getMoji() {
return result;
}
+#if TARGET_PC
+#define CHAR_TRUNC(val) (dusk::version::isRegionPal() ? val & 0xFF : val)
+#elif REGION_PAL
+#define CHAR_TRUNC(val) (val & 0xFF)
+#else
+#define CHAR_TRUNC(val) val
+#endif
+
void dName_c::setMoji(int moji) {
if (mCurPos == 8 || nameCheck() == 8) {
mDoAud_seStart(Z2SE_SYS_ERROR, NULL, 0, 0);
@@ -771,24 +856,14 @@ void dName_c::setMoji(int moji) {
s32 notEmpty = false;
for (int i = mCurPos; i < 8; i++) {
- #if REGION_JPN
- // ' ' (full-width space)
- if (mChrInfo[i].mCharacter != '\x81\x40') {
- #else
- if (mChrInfo[i].mCharacter != ' ') {
- #endif
+ if (mChrInfo[i].mCharacter != SPACE_MAYBE_FULL) {
notEmpty = true;
break;
}
}
if (notEmpty) {
- #if REGION_JPN
- // ' ' (full-width space)
- if (mChrInfo[7].mCharacter == '\x81\x40') {
- #else
- if (mChrInfo[7].mCharacter == ' ') {
- #endif
+ if (mChrInfo[7].mCharacter == SPACE_MAYBE_FULL) {
for (int i = 6; i >= mCurPos; i--) {
mChrInfo[i + 1] = mChrInfo[i];
}
@@ -797,11 +872,7 @@ void dName_c::setMoji(int moji) {
mChrInfo[mCurPos].mRow = mCharRow;
mChrInfo[mCurPos].mMojiSet = mMojiSet;
mChrInfo[mCurPos].field_0x3 = 1;
- #if REGION_PAL
- mChrInfo[mCurPos].mCharacter = moji & 0xFF;
- #else
- mChrInfo[mCurPos].mCharacter = moji;
- #endif
+ mChrInfo[mCurPos].mCharacter = CHAR_TRUNC(moji);
if (mCurPos != 8) {
mLastCurPos = mCurPos;
@@ -814,11 +885,7 @@ void dName_c::setMoji(int moji) {
mChrInfo[mCurPos].mRow = mCharRow;
mChrInfo[mCurPos].mMojiSet = mMojiSet;
mChrInfo[mCurPos].field_0x3 = 1;
- #if REGION_PAL
- mChrInfo[mCurPos].mCharacter = moji & 0xFF;
- #else
- mChrInfo[mCurPos].mCharacter = moji;
- #endif
+ mChrInfo[mCurPos].mCharacter = CHAR_TRUNC(moji);
if (mCurPos != 8) {
mLastCurPos = mCurPos;
@@ -844,13 +911,8 @@ void dName_c::setNameText() {
"CR\x1b"
"CC[000000]\x1bGM[0]%c\x1bHM\x1b"
"CC[ffffff]\x1bGM[0]%c",
- #if REGION_PAL
- (u8)mChrInfo[i].mCharacter & 0xFF,
- (u8)mChrInfo[i].mCharacter & 0xFF
- #else
- (u8)mChrInfo[i].mCharacter,
- (u8)mChrInfo[i].mCharacter
- #endif
+ CHAR_TRUNC((u8)mChrInfo[i].mCharacter),
+ CHAR_TRUNC((u8)mChrInfo[i].mCharacter)
);
#if REGION_JPN
} else {
@@ -889,7 +951,29 @@ void dName_c::nameCursorMove() {
void dName_c::selectCursorMove() {
int idx;
- #if REGION_PAL
+ #if TARGET_PC
+ if (dusk::version::isRegionPal()) {
+ if (mCharColumn < 3) {
+ idx = 0;
+ } else if (mCharColumn < 6) {
+ idx = 1;
+ } else if (mCharColumn >= 6) {
+ idx = 2;
+ }
+ } else if (dusk::version::isRegionJpn()) {
+ if (mCharColumn < 3) {
+ idx = 0;
+ } else if (mCharColumn < 6) {
+ idx = 1;
+ } else if (mCharColumn < 8) {
+ idx = 2;
+ } else if (mCharColumn >= 8) {
+ idx = 3;
+ }
+ } else {
+ idx = 3;
+ }
+ #elif REGION_PAL
if (mCharColumn < 3) {
idx = 0;
} else if (mCharColumn < 6) {
@@ -930,7 +1014,36 @@ void dName_c::selectCursorMove() {
void dName_c::menuCursorPosSet() {
mPrevSelMenu = mSelMenu;
- #if REGION_PAL
+ #if TARGET_PC
+ if (dusk::version::isRegionPal()) {
+ if (mCharColumn < 3) {
+ mSelMenu = MENU_HIRA;
+ } else if (mCharColumn < 6) {
+ mSelMenu = MENU_KATA;
+ } else if (mCharColumn >= 6) {
+ mSelMenu = MENU_EIGO;
+ }
+ } else if (dusk::version::isRegionJpn()) {
+ if (mCharColumn < 3) {
+ mSelMenu = MENU_HIRA;
+ return;
+ }
+ if (mCharColumn < 6) {
+ mSelMenu = MENU_KATA;
+ return;
+ }
+ if (mCharColumn < 8) {
+ mSelMenu = MENU_EIGO;
+ return;
+ }
+ if (mCharColumn >= 8) {
+ mSelMenu = MENU_END;
+ return;
+ }
+ } else {
+ mSelMenu = MENU_END;
+ }
+ #elif REGION_PAL
if (mCharColumn < 3) {
mSelMenu = MENU_HIRA;
} else if (mCharColumn < 6) {
@@ -961,28 +1074,28 @@ void dName_c::menuCursorPosSet() {
}
void dName_c::MenuSelect() {
- #if REGION_PAL || REGION_JPN
- if (stick->checkRightTrigger()) {
+ #if TARGET_PC || REGION_PAL || REGION_JPN
+ if (isPalOrJpn() && stick->checkRightTrigger()) {
mDoAud_seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0);
mPrevSelMenu = mSelMenu;
mSelMenu++;
#if REGION_PAL
if (mSelMenu > MENU_EIGO) {
#else
- if (mSelMenu > MENU_END) {
+ if (mSelMenu > EIGO_OR_END) {
#endif
mSelMenu = MENU_HIRA;
}
MenuSelectAnmInit();
mSelProc = PROC_MENU_SEL_ANM;
- } else if (stick->checkLeftTrigger()) {
+ } else if (isPalOrJpn() && stick->checkLeftTrigger()) {
mDoAud_seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0);
mPrevSelMenu = mSelMenu;
if (mSelMenu == MENU_HIRA) {
#if REGION_JPN
mSelMenu = MENU_END;
#else
- mSelMenu = MENU_EIGO;
+ mSelMenu = dusk::version::isRegionJpn() ? MENU_END : MENU_EIGO;
#endif
} else {
mSelMenu--;
@@ -1009,7 +1122,7 @@ void dName_c::MenuSelect() {
#if REGION_PAL
if (mSelMenu == MENU_EIGO) {
#else
- if (mSelMenu == MENU_END) {
+ if (mSelMenu == EIGO_OR_END) {
#endif
if (nameCheck() != 0) {
mDoAud_seStart(Z2SE_SY_NAME_OK, NULL, 0, 0);
@@ -1024,7 +1137,7 @@ void dName_c::MenuSelect() {
#if REGION_PAL
if (mSelMenu == MENU_EIGO) {
#else
- if (mSelMenu == MENU_END) {
+ if (mSelMenu == EIGO_OR_END) {
#endif
if (nameCheck() != 0) {
mDoAud_seStart(Z2SE_SY_NAME_OK, NULL, 0, 0);
@@ -1067,9 +1180,11 @@ void dName_c::MenuSelectAnm2() {
if (canMove == true) {
if (prevMenu_i != mojiSet_i) {
mMenuText[prevMenu_i]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF));
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC || REGION_PAL || REGION_JPN
+ IF_DUSK_BLOCK(isPalOrJpn())
mMenuIcon[mojiSet_i]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale);
mMenuText[mojiSet_i]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF));
+ IF_DUSK_BLOCK_END
#endif
}
selectCursorMove();
@@ -1081,6 +1196,11 @@ void dName_c::MenuSelectAnm2() {
void dName_c::MenuSelectAnm3() {}
void dName_c::menuAbtnSelect() {
+#if TARGET_PC
+ if (dusk::version::isRegionPal() && mSelMenu == MENU_EIGO) {
+ goto pal_eigo;
+ }
+#endif
switch (mSelMenu) {
case MENU_HIRA:
case MENU_KATA:
@@ -1098,6 +1218,7 @@ void dName_c::menuAbtnSelect() {
#else
case MENU_END:
#endif
+ IF_DUSK(pal_eigo:)
int nameNum = nameCheck();
if (nameNum != 0) {
playNameSet(nameNum);
@@ -1116,44 +1237,33 @@ void dName_c::backSpace() {
if (mCurPos != 0) {
mDoAud_seStart(Z2SE_SY_NAME_DELETE, NULL, 0, 0);
- #if REGION_JPN
- // ' ' (full-width space)
- if (mCurPos == 8 && mChrInfo[7].mCharacter != '\x81\x40') {
- #else
- if (mCurPos == 8 && mChrInfo[7].mCharacter != ' ') {
- #endif
+ if (mCurPos == 8 && mChrInfo[7].mCharacter != SPACE_MAYBE_FULL) {
mChrInfo[7].mColumn = 7;
mChrInfo[7].mRow = 1;
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC
+ mChrInfo[7].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO;
+ #elif REGION_PAL || REGION_JPN
mChrInfo[7].mMojiSet = MOJI_HIRA;
#else
mChrInfo[7].mMojiSet = MOJI_EIGO;
#endif
mChrInfo[7].field_0x3 = 1;
- #if REGION_JPN
- // ' ' (full-width space)
- mChrInfo[7].mCharacter = '\x81\x40';
- #else
- mChrInfo[7].mCharacter = ' ';
- #endif
+ mChrInfo[7].mCharacter = SPACE_MAYBE_FULL;
} else {
for (int i = mCurPos - 1; i < 7; i++) {
mChrInfo[i] = mChrInfo[i + 1];
}
mChrInfo[7].mColumn = 7;
mChrInfo[7].mRow = 1;
- #if REGION_PAL || REGION_JPN
+#if TARGET_PC
+ mChrInfo[7].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO;
+#elif REGION_PAL || REGION_JPN
mChrInfo[7].mMojiSet = MOJI_HIRA;
#else
mChrInfo[7].mMojiSet = MOJI_EIGO;
#endif
mChrInfo[7].field_0x3 = 1;
- #if REGION_JPN
- // ' ' (full-width space)
- mChrInfo[7].mCharacter = '\x81\x40';
- #else
- mChrInfo[7].mCharacter = ' ';
- #endif
+ mChrInfo[7].mCharacter = SPACE_MAYBE_FULL;
}
setNameText();
@@ -1164,7 +1274,31 @@ void dName_c::backSpace() {
}
void dName_c::mojiListChange() {
- #if REGION_PAL
+ #if TARGET_PC
+ const char** mojiSet;
+ if (dusk::version::isRegionPal()) {
+ switch (mMojiSet) {
+ case MOJI_HIRA:
+ mojiSet = l_mojiEisuPal_1;
+ break;
+ case MOJI_KATA:
+ mojiSet = l_mojiEisuPal_2;
+ break;
+ }
+ } else {
+ switch (mMojiSet) {
+ case MOJI_HIRA:
+ mojiSet = l_mojiHira;
+ break;
+ case MOJI_KATA:
+ mojiSet = l_mojikata;
+ break;
+ case MOJI_EIGO:
+ mojiSet = l_mojiEisu;
+ break;
+ }
+ }
+ #elif REGION_PAL
char** mojiSet;
switch (mMojiSet) {
@@ -1212,7 +1346,8 @@ void dName_c::mojiListChange() {
strcpy(mMojiText[i], buf);
}
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC || REGION_PAL || REGION_JPN
+ IF_DUSK_BLOCK(isPalOrJpn())
if (mSelProc == PROC_MOJI_SELECT) {
mMenuIcon[mMojiSet]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale);
mMenuText[mMojiSet]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF));
@@ -1221,6 +1356,7 @@ void dName_c::mojiListChange() {
mMenuText[mPrevMojiSet]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF));
}
}
+ IF_DUSK_BLOCK_END
#endif
}
@@ -1241,9 +1377,11 @@ void dName_c::menuCursorMove2() {
if (menu_i != mojiSet_i) {
mMenuIcon[menu_i]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale);
mMenuText[menu_i]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF));
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC || REGION_PAL || REGION_JPN
+ IF_DUSK_BLOCK(isPalOrJpn())
mMenuIcon[mojiSet_i]->scale(1.0f, 1.0f);
mMenuText[mojiSet_i]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF));
+ IF_DUSK_BLOCK_END
#endif
}
@@ -1265,7 +1403,9 @@ void dName_c::selectCursorPosSet(int row) {
mCharColumn = 3;
break;
case MENU_EIGO:
- #if REGION_PAL
+ #if TARGET_PC
+ mCharColumn = dusk::version::isRegionPal() ? 8 : 6;
+ #elif REGION_PAL
mCharColumn = 8;
#else
mCharColumn = 6;
@@ -1481,9 +1621,11 @@ void dName_c::screenSet() {
#endif
}
- #if !(REGION_PAL || REGION_JPN)
+ #if TARGET_PC || !(REGION_PAL || REGION_JPN)
+ IF_DUSK_BLOCK(!isPalOrJpn())
mMenuIcon[0]->hide();
mMenuIcon[1]->hide();
+ IF_DUSK_BLOCK_END
#endif
mMojiPane = nameIn.NameInScr->search(MULTI_CHAR('moji_n'));
@@ -1501,25 +1643,28 @@ void dName_c::screenSet() {
((J2DTextBox*)nameTagPane[i])->setFont(nameIn.font);
((J2DTextBox*)nameTagPane[i])->setString(72, "");
((J2DTextBox*)nameTagPane[i])->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF));
- #if REGION_PAL
+ #if TARGET_PC || REGION_PAL
+ IF_DUSK_BLOCK(dusk::version::isRegionPal())
((J2DTextBox*)nameTagPane[i])->resize(24.0f, 23.0f);
+ IF_DUSK_BLOCK_END
#endif
mNameText[i] = ((J2DTextBox*)nameTagPane[i])->getStringPtr();
}
- #if REGION_PAL
+ #if REGION_PAL // DUSK version note: this code mutates strings. We just edit the table.
+ IF_DUSK_BLOCK(dusk::version::isRegionPal())
int idx = 2;
- static u8 palMoji00[13] = {
+ static const u8 palMoji00[13] = {
0xC0, 0xC1, 0xC2, 0xC4, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE,
};
- static u8 palMoji01[13] = {
+ static const u8 palMoji01[13] = {
0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD6, 0x8C, 0xD9, 0xDA, 0xDB, 0xDC, 0x2D,
};
- static u8 palMoji10[13] = {
+ static const u8 palMoji10[13] = {
0xE0, 0xE1, 0xE2, 0xE4, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE,
};
- static u8 palMoji11[13] = {
+ static const u8 palMoji11[13] = {
0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF6, 0x9C, 0xF9, 0xFA, 0xFB, 0xFC, 0xDF,
};
@@ -1536,6 +1681,7 @@ void dName_c::screenSet() {
l_mojiEisuPal_2[idx + 1][0] = palMoji11[i];
l_mojiEisuPal_2[idx + 1][1] = 0;
}
+ IF_DUSK_BLOCK_END
#endif
mCharColumn = 0;
@@ -1574,18 +1720,15 @@ void dName_c::displayInit() {
mNameCursor[i]->hide();
mChrInfo[i].mColumn = 7;
mChrInfo[i].mRow = 1;
- #if REGION_PAL || REGION_JPN
+ #if TARGET_PC
+ mChrInfo[i].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO;
+ #elif REGION_PAL || REGION_JPN
mChrInfo[i].mMojiSet = MOJI_HIRA;
#else
mChrInfo[i].mMojiSet = MOJI_EIGO;
#endif
mChrInfo[i].field_0x3 = 1;
- #if REGION_JPN
- // ' ' (full-width space)
- mChrInfo[i].mCharacter = '\x81\x40';
- #else
- mChrInfo[i].mCharacter = ' ';
- #endif
+ mChrInfo[i].mCharacter = SPACE_MAYBE_FULL;
}
mIsInputEnd = false;
@@ -1596,7 +1739,63 @@ void dName_c::NameStrSet() {
int i = 0;
while (*moji != 0) {
- #if REGION_PAL
+ #if TARGET_PC
+ if (dusk::version::isRegionPal()) {
+ mChrInfo[i].mCharacter = static_cast(*moji);
+
+ for (int j = 0; j < 65; j++) {
+ if (mChrInfo[i].mCharacter == *(u8*)l_mojiEisuPal_1[j] ||
+ mChrInfo[i].mCharacter == *(u16*)l_mojiEisuPal_2[j])
+ {
+ mChrInfo[i].mColumn = j / 5;
+ mChrInfo[i].mRow = j % 5;
+ mChrInfo[i].mMojiSet = MOJI_HIRA;
+ break;
+ }
+ }
+ moji++;
+ i++;
+ } else {
+ if (*(u8*)moji >> 4 == 8 || *(u8*)moji >> 4 == 9) {
+ mChrInfo[i].mCharacter = *(u16*)moji;
+
+ for (int j = 0; j < 65; j++) {
+ if (mChrInfo[i].mCharacter == *(u16*)l_mojiHira[j] ||
+ mChrInfo[i].mCharacter == *(u16*)l_mojiHira2[j] ||
+ mChrInfo[i].mCharacter == *(u16*)l_mojiHira3[j])
+ {
+ mChrInfo[i].mColumn = j / 5;
+ mChrInfo[i].mRow = j % 5;
+ mChrInfo[i].mMojiSet = MOJI_HIRA;
+ break;
+ } else if (mChrInfo[i].mCharacter == *(u16*)l_mojikata[j] ||
+ mChrInfo[i].mCharacter == *(u16*)l_mojikata2[j] ||
+ mChrInfo[i].mCharacter == *(u16*)l_mojikata3[j])
+ {
+ mChrInfo[i].mColumn = j / 5;
+ mChrInfo[i].mRow = j % 5;
+ mChrInfo[i].mMojiSet = MOJI_KATA;
+ break;
+ }
+ }
+ moji += 2;
+ i++;
+ } else {
+ mChrInfo[i].mCharacter = *moji;
+
+ for (int j = 0; j < 65; j++) {
+ if (mChrInfo[i].mCharacter == *(u8*)l_mojiEisu[j]) {
+ mChrInfo[i].mColumn = j / 5;
+ mChrInfo[i].mRow = j % 5;
+ mChrInfo[i].mMojiSet = MOJI_EIGO;
+ break;
+ }
+ }
+ moji++;
+ i++;
+ }
+ }
+ #elif REGION_PAL
mChrInfo[i].mCharacter = static_cast(*moji);
for (int j = 0; j < 65; j++) {
@@ -1669,7 +1868,9 @@ s32 dName_c::getMenuPosIdx(u8 selPos) {
result = 1;
break;
case 2:
- #if REGION_PAL
+ #if TARGET_PC
+ result = dusk::version::isRegionPal() ? 3 : 2;
+ #elif REGION_PAL
result = 3;
#else
result = 2;
diff --git a/src/d/d_particle.cpp b/src/d/d_particle.cpp
index 248424e9a2..9b89afc7b1 100644
--- a/src/d/d_particle.cpp
+++ b/src/d/d_particle.cpp
@@ -1972,7 +1972,12 @@ void dPa_light8PcallBack::draw(JPABaseEmitter* param_1, JPABaseParticle* param_2
JGeometry::TVec3 local_154;
JGeometry::TVec3 local_160;
JGeometry::TVec3 local_16c;
- dPa_setWindPower(param_2);
+#if TARGET_PC
+ if (dusk::frame_interp::is_sim_frame())
+#endif
+ {
+ dPa_setWindPower(param_2);
+ }
MTXIdentity(local_60);
MTXIdentity(auStack_90);
param_2->getBaseAxis(&local_10c);
diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp
index 73f031beae..bb982c4afb 100644
--- a/src/d/d_resorce.cpp
+++ b/src/d/d_resorce.cpp
@@ -102,11 +102,7 @@ static void setIndirectTex(J3DModelData* i_modelData) {
texture->setResTIMG(i, *mDoGph_gInf_c::getFrameBufferTimg());
}
if (memcmp(textureName, "Zbuffer", 8) == 0) {
-#if !TARGET_PC
texture->setResTIMG(i, *mDoGph_gInf_c::getZbufferTimg());
-#else
- DuskLog.warn("Zbuffer texture binding not yet supported");
-#endif
}
}
}
diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp
index f04df0fa39..db0cfa1387 100644
--- a/src/d/d_s_logo.cpp
+++ b/src/d/d_s_logo.cpp
@@ -24,6 +24,7 @@
#include "JSystem/JUtility/JUTConsole.h"
#include "dusk/logging.h"
+#include "dusk/version.hpp"
#if !PLATFORM_GCN
#include
@@ -44,7 +45,12 @@ struct homeBtnData {
};
#endif
-#if VERSION == VERSION_SHIELD
+#if TARGET_PC
+using namespace dusk::version;
+
+#define LOGO_ARC versionSelect({{GameVersion::GcnJpn, "Logo"}, {GameVersion::GcnPal, "LogoPal"}}, "LogoUs")
+#define MSG_PATH versionSelect({{GameVersion::GcnJpn, "/res/Msgjp/bmgres.arc"}}, "/res/Msgus/bmgres.arc")
+#elif VERSION == VERSION_SHIELD
#define LOGO_ARC "LogoUs"
#define MSG_PATH "/res/Msgcn/bmgres.arc"
#elif VERSION == VERSION_GCN_JPN
@@ -789,7 +795,13 @@ dScnLogo_c::~dScnLogo_c() {
JKR_DELETE(mProgressiveNo);
JKR_DELETE(mProgressiveSel);
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC
+ if (getGameVersion() == GameVersion::GcnPal) {
+ mpPalLogoResCommand->getArchive()->removeResourceAll();
+ mpPalLogoResCommand->getArchive()->unmount();
+ mpPalLogoResCommand->destroy();
+ }
+ #elif VERSION == VERSION_GCN_PAL
mpPalLogoResCommand->getArchive()->removeResourceAll();
mpPalLogoResCommand->getArchive()->unmount();
mpPalLogoResCommand->destroy();
@@ -951,7 +963,8 @@ static int phase_0(dScnLogo_c* i_this) {
JUT_ASSERT(1528, i_this->mLogo01Heap != NULL);
JKRHEAP_NAME(i_this->mLogo01Heap, "Logo01");
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
+ IF_DUSK_BLOCK(getGameVersion() == GameVersion::GcnPal)
switch (i_this->getPalLanguage()) {
case 1:
i_this->mpPalLogoResCommand = mDoDvdThd_mountArchive_c::create("/res/Layout/LogoPalGm.arc", 0, NULL);
@@ -970,6 +983,7 @@ static int phase_0(dScnLogo_c* i_this) {
i_this->mpPalLogoResCommand = mDoDvdThd_mountArchive_c::create("/res/Layout/LogoPalUk.arc", 0, NULL);
break;
}
+ IF_DUSK_BLOCK_END
#endif
#if PLATFORM_WII || PLATFORM_SHIELD
@@ -990,7 +1004,8 @@ static int phase_1(dScnLogo_c* i_this) {
}
#endif
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC || VERSION == VERSION_GCN_PAL
+ IF_DUSK_BLOCK(getGameVersion() == GameVersion::GcnPal)
if (!mDoDvdThd::SyncWidthSound) {
return cPhs_INIT_e;
}
@@ -998,6 +1013,7 @@ static int phase_1(dScnLogo_c* i_this) {
if (!i_this->mpPalLogoResCommand->sync()) {
return cPhs_INIT_e;
}
+ IF_DUSK_BLOCK_END
#endif
int rt;
@@ -1104,26 +1120,12 @@ int dScnLogo_c::create() {
checkProgSelect();
if (field_0x20a != 0) {
mExecCommand = EXEC_PROG_IN;
- #if TARGET_PC
- mTimer = dusk::getSettings().game.skipWarningScreen ? 1 : 30;
- #else
mTimer = 30;
- #endif
field_0x218 = getProgressiveMode();
} else {
#if TARGET_PC
- if (dusk::getSettings().game.skipWarningScreen) {
- mTimer = 0; // Possibly unnecessary but just in case
- mExecCommand = EXEC_DVD_WAIT;
- } else {
- if (mDoRst::getWarningDispFlag()) {
- mTimer = 90;
- mExecCommand = EXEC_NINTENDO_IN;
- } else {
- mTimer = 120;
- mExecCommand = EXEC_WARNING_IN;
- }
- }
+ mTimer = 0; // Possibly unnecessary but just in case
+ mExecCommand = EXEC_DVD_WAIT;
#else
if (mDoRst::getWarningDispFlag()) {
mTimer = 90;
@@ -1237,7 +1239,113 @@ void dScnLogo_c::logoInitGC() {
ResTIMG* dolbyImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 3);
mDolbyLogo = JKR_NEW dDlst_2D_c(dolbyImg, 189, 150, 232, 112, 255);
-#if VERSION == VERSION_GCN_PAL
+#if TARGET_PC
+ if (getGameVersion() == GameVersion::GcnPal) {
+ u8 language = getPalLanguage();
+ if (language >= 5) {
+ language = 0;
+ }
+
+ static const char* choice[] = {
+ "50_60_choice_eng.bti",
+ "50_60_choice_ger.bti",
+ "50_60_choice_fra.bti",
+ "50_60_choice_spa.bti",
+ "50_60_choice_ita.bti",
+ };
+
+ static const char* yes[] = {
+ "60_set_eng.bti",
+ "60_set_ger.bti",
+ "60_set_fra.bti",
+ "60_set_spa.bti",
+ "60_set_ita.bti",
+ };
+
+ static const char* no[] = {
+ "50_set_eng.bti",
+ "50_set_ger.bti",
+ "50_set_fra.bti",
+ "50_set_spa.bti",
+ "50_set_ita.bti",
+ };
+
+ static const char* prog[] = {
+ "progressive_pro.bti",
+ "progressive_pro_gm.bti",
+ "progressive_pro_fr.bti",
+ "progressive_pro_sp.bti",
+ "progressive_pro_it.bti",
+ };
+
+ static const char* intr[] = {
+ "progressive_inter.bti",
+ "progressive_inter_gm.bti",
+ "progressive_inter_fr.bti",
+ "progressive_inter_sp.bti",
+ "progressive_inter_it.bti",
+ };
+
+ static const char* warning[] = {
+ "warning.bti",
+ "warning_gm.bti",
+ "warning_fr.bti",
+ "warning_sp.bti",
+ "warning_it.bti",
+ };
+
+ static const char* warningPs[] = {
+ "warning_pstart.bti",
+ "warning_pstart_gm.bti",
+ "warning_pstart_fr.bti",
+ "warning_pstart_sp.bti",
+ "warning_pstart_it.bti",
+ };
+
+ ResTIMG* warningImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', warning[language]);
+ mWarning = JKR_NEW dDlst_2D_c(warningImg, 0, 0, FB_WIDTH, FB_HEIGHT, 255);
+
+ ResTIMG* warnStartImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', warningPs[language]);
+ mWarningStart = JKR_NEW dDlst_2D_c(warnStartImg, 0, 359, FB_WIDTH, 48, 255);
+
+ ResTIMG* progChoiceImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', choice[language]);
+ mProgressiveChoice = JKR_NEW dDlst_2D_c(progChoiceImg, 113, 143, 416, 210, 255);
+
+ ResTIMG* progYesImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', yes[language]);
+ mProgressiveYes = JKR_NEW dDlst_2D_c(progYesImg, 121, 352, 200, 72, 255);
+ mProgressiveYes->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255));
+
+ ResTIMG* progNoImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', no[language]);
+ mProgressiveNo = JKR_NEW dDlst_2D_c(progNoImg, 320, 352, 200, 72, 255);
+ mProgressiveNo->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255));
+
+ mProgressivePro = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', prog[language]);
+ mProgressiveInter = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', intr[language]);
+ mProgressiveSel = JKR_NEW dDlst_2D_c(mProgressivePro, 153, 309, 336, 88, 255);
+ } else {
+ ResTIMG* warningImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 10);
+ mWarning = JKR_NEW dDlst_2D_c(warningImg, 0, 0, FB_WIDTH, FB_HEIGHT, 255);
+
+ ResTIMG* warnStartImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 11);
+ mWarningStart = JKR_NEW dDlst_2D_c(warnStartImg, 0, 359, FB_WIDTH, 48, 255);
+
+ ResTIMG* progChoiceImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 5);
+ mProgressiveChoice = JKR_NEW dDlst_2D_c(progChoiceImg, 113, 281, 416, 72, 255);
+
+ ResTIMG* progYesImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 9);
+ mProgressiveYes = JKR_NEW dDlst_2D_c(progYesImg, 211, 372, 80, 32, 255);
+ mProgressiveYes->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255));
+
+ ResTIMG* progNoImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 7);
+ mProgressiveNo = JKR_NEW dDlst_2D_c(progNoImg, 350, 372, 80, 32, 255);
+ mProgressiveNo->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255));
+
+ mProgressivePro = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 8);
+ mProgressiveInter = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 6);
+ mProgressiveSel = JKR_NEW dDlst_2D_c(mProgressivePro, 153, 309, 336, 88, 255);
+ }
+
+#elif VERSION == VERSION_GCN_PAL
u8 language = getPalLanguage();
if (language >= 5) {
language = 0;
@@ -1395,7 +1503,30 @@ void dScnLogo_c::dvdDataLoad() {
mpButtonCommand = aramMount(BUTTON_RES_PATH, mDoExt_getJ2dHeap());
mpCardIconCommand = aramMount(ICON_RES_PATH, mDoExt_getJ2dHeap());
- #if VERSION == VERSION_GCN_PAL
+ #if TARGET_PC
+ if (getGameVersion() == GameVersion::GcnPal) {
+ switch (getPalLanguage()) {
+ case 1:
+ mpBmgResCommand = onMemMount("/res/Msgde/bmgres.arc");
+ break;
+ case 2:
+ mpBmgResCommand = onMemMount("/res/Msgfr/bmgres.arc");
+ break;
+ case 3:
+ mpBmgResCommand = onMemMount("/res/Msgsp/bmgres.arc");
+ break;
+ case 4:
+ mpBmgResCommand = onMemMount("/res/Msgit/bmgres.arc");
+ break;
+ case 0:
+ default:
+ mpBmgResCommand = onMemMount("/res/Msguk/bmgres.arc");
+ break;
+ }
+ } else {
+ mpBmgResCommand = onMemMount(MSG_PATH);
+ }
+ #elif VERSION == VERSION_GCN_PAL
switch (getPalLanguage()) {
case 1:
mpBmgResCommand = onMemMount("/res/Msgde/bmgres.arc");
@@ -1435,7 +1566,10 @@ void dScnLogo_c::dvdDataLoad() {
mpMsgResCommand[1] = aramMount(MSG_RES1_PATH, mDoExt_getJ2dHeap());
mpMsgResCommand[2] = aramMount(MSG_RES2_PATH, mDoExt_getJ2dHeap());
mpMsgResCommand[3] = aramMount(MSG_RES3_PATH, mDoExt_getJ2dHeap());
-#if VERSION == VERSION_GCN_JPN
+#if TARGET_PC
+ const auto res4Path = versionSelect({{GameVersion::GcnJpn, "/res/Layout/msgres04.arc"}}, "/res/Layout/msgres04F.arc");
+ mpMsgResCommand[4] = aramMount( res4Path, mDoExt_getJ2dHeap());
+#elif VERSION == VERSION_GCN_JPN
mpMsgResCommand[4] = aramMount("/res/Layout/msgres04.arc", mDoExt_getJ2dHeap());
#else
mpMsgResCommand[4] = aramMount("/res/Layout/msgres04F.arc", mDoExt_getJ2dHeap());
@@ -1445,7 +1579,23 @@ void dScnLogo_c::dvdDataLoad() {
mpMain2DCommand = onMemMount(MAIN2D_PATH);
-#if VERSION == VERSION_GCN_JPN
+#if TARGET_PC
+ const auto fontResPath = versionSelect(
+ {
+ {GameVersion::GcnJpn, "/res/Fontjp/fontres.arc"},
+ {GameVersion::GcnPal, "/res/Fonteu/fontres.arc"},
+ }, "/res/Fontus/fontres.arc");
+ const auto fontRubyPath = versionSelect(
+ {
+ {GameVersion::GcnJpn, "/res/Fontjp/rubyres.arc"},
+ {GameVersion::GcnPal, "/res/Fonteu/rubyres.arc"},
+ }, "/res/Fontus/rubyres.arc");
+
+ // Note: GCN_JPN mounts this archive as tail instead of head.
+ // I'm guessing this is fine since we have more RAM.
+ mpFontResCommand = onMemMount(fontResPath);
+ mpRubyResCommand = onMemMount(fontRubyPath);
+#elif VERSION == VERSION_GCN_JPN
mpFontResCommand = mDoDvdThd_mountXArchive_c::create("/res/Fontjp/fontres.arc", 1, JKRArchive::MOUNT_MEM, NULL);
mpRubyResCommand = onMemMount("/res/Fontjp/rubyres.arc");
#elif VERSION == VERSION_GCN_PAL
@@ -1545,7 +1695,7 @@ static int dScnLogo_IsDelete(dScnLogo_c* i_this) {
return 1;
}
-#if VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD
+#if TARGET_PC || VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD
u8 dScnLogo_c::getPalLanguage() {
u8 language;
diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp
index 735ce4d0ca..6baa07da30 100644
--- a/src/d/d_s_name.cpp
+++ b/src/d/d_s_name.cpp
@@ -5,19 +5,20 @@
#include "d/dolzel.h" // IWYU pragma: keep
-#include "d/d_s_name.h"
#include "JSystem/JKernel/JKRExpHeap.h"
#include "d/d_com_inf_game.h"
#include "d/d_meter2_info.h"
+#include "d/d_s_name.h"
+#include "dusk/imgui/ImGuiConsole.hpp"
+#include "dusk/memory.h"
+#include "dusk/settings.h"
+#include "f_op/f_op_overlap_mng.h"
#include "f_op/f_op_scene_mng.h"
#include "m_Do/m_Do_Reset.h"
#include "m_Do/m_Do_graphic.h"
#include "m_Do/m_Do_machine.h"
-#include "m_Do/m_Do_mtx.h"
#include "m_Do/m_Do_main.h"
-#include "f_op/f_op_overlap_mng.h"
-#include "dusk/memory.h"
-#include "dusk/settings.h"
+#include "m_Do/m_Do_mtx.h"
#if TARGET_PC
#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen)
@@ -412,6 +413,16 @@ void dScnName_c::changeGameScene() {
dKy_clear_game_init();
dComIfGs_resetDan();
dComIfGs_setRestartRoomParam(0);
+
+#if TARGET_PC
+ if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) {
+ // start a new run on file load if a run isn't already in progress
+ if (!dusk::m_speedrunInfo.m_isRunStarted) {
+ dusk::ImGuiMenuGame::resetForSpeedrunMode();
+ dusk::m_speedrunInfo.startRun();
+ }
+ }
+#endif
}
}
diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp
index 38308d0668..f7ebf6a20d 100644
--- a/src/d/d_s_play.cpp
+++ b/src/d/d_s_play.cpp
@@ -40,7 +40,9 @@
#include "JSystem/JKernel/JKRAramArchive.h"
#if TARGET_PC
+#include "dusk/autosave.h"
#include "dusk/memory.h"
+#include "dusk/ui/ui.hpp"
#endif
#if DEBUG
@@ -700,6 +702,10 @@ static u8 lbl_8074CAE4;
static u32 l_sceneChangeStartTick;
#endif
+#if TARGET_PC
+static BOOL autoSaved;
+#endif
+
static int dScnPly_Execute(dScnPly_c* i_this) {
#if DEBUG
fapGm_HIO_c::startCpuTimer();
@@ -742,6 +748,15 @@ static int dScnPly_Execute(dScnPly_c* i_this) {
}
}
+ #if TARGET_PC
+ if (!dComIfGp_event_runCheck() && !fopOvlpM_IsPeek() && !dComIfG_resetToOpening(i_this) &&
+ !dComIfGp_isEnableNextStage() && autoSaved == FALSE)
+ {
+ triggerAutoSave();
+ autoSaved = TRUE;
+ }
+ #endif
+
dKy_itudemo_se();
#if DEBUG
@@ -780,7 +795,17 @@ static int dScnPly_Execute(dScnPly_c* i_this) {
dJprev_c::get()->update();
#endif
+#if TARGET_PC
+ if (!dusk::ui::is_prelaunch_open()) {
+ dDemo_c::update();
+ } else if (dusk::getSettings().audio.menuSounds) {
+ s8 reverb = dComIfGp_getReverb(dComIfGp_roomControl_getStayNo());
+ f32 fxMix = reverb / 127.0f;
+ g_mEnvSeMgr.field_0x144.startEnvSeDirLevel(JA_SE_ATM_WIND_1, fxMix, 1.0f);
+ }
+#else
dDemo_c::update();
+#endif
#if DEBUG
dJcame_c::get()->update();
@@ -1593,6 +1618,11 @@ static int dScnPly_Create(scene_class* i_this) {
dScnPly_c* a_this = (dScnPly_c*)i_this;
int phase_state = dComLbG_PhaseHandler(&a_this->field_0x1c4, l_method, a_this);
+
+ #if TARGET_PC
+ autoSaved = FALSE;
+ #endif
+
return phase_state;
}
diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp
index 3289a49c64..8e4af75322 100644
--- a/src/d/d_save.cpp
+++ b/src/d/d_save.cpp
@@ -16,6 +16,8 @@
#include
#include
+#include "dusk/version.hpp"
+
#if PLATFORM_WII || PLATFORM_SHIELD
#include
#include
@@ -25,7 +27,11 @@
#include "lingcod/lingcod.h"
#endif
+#if TARGET_PC
#include "dusk/settings.h"
+#include
+#include
+#endif
static u8 dSv_item_rename(u8 i_itemNo) {
switch (i_itemNo) {
@@ -343,6 +349,10 @@ void dSv_player_item_c::setItem(int i_slotNo, u8 i_itemNo) {
dComIfGp_setSelectItem(i);
}
}
+
+ #if TARGET_PC
+ triggerAutoSave();
+ #endif
}
u8 dSv_player_item_c::getItem(int i_slotNo, bool i_checkCombo) const {
@@ -1027,7 +1037,7 @@ void dSv_player_config_c::init() {
mAttentionType = 0;
mVibration = 1;
-#if DEBUG
+#if DEBUG // DUSK VERSION SUPPORT: This field isn't used, so we can ignore it.
mLanguage = SCGetLanguage();
#elif REGION_PAL || VERSION >= VERSION_WII_USA_R2
mLanguage = OSGetLanguage();
@@ -1072,7 +1082,8 @@ void dSv_player_config_c::setVibration(u8 i_status) {
}
u8 dSv_player_config_c::getPalLanguage() const {
-#if VERSION == VERSION_GCN_PAL
+#if TARGET_PC || VERSION == VERSION_GCN_PAL
+ IF_DUSK_BLOCK(dusk::version::getGameVersion() == dusk::version::GameVersion::GcnPal)
switch (OSGetLanguage()) {
case 0:
return LANGUAGE_ENGLISH;
@@ -1085,6 +1096,7 @@ u8 dSv_player_config_c::getPalLanguage() const {
case 4:
return LANGUAGE_ITALIAN;
}
+ IF_DUSK_BLOCK_END
#elif VERSION >= VERSION_WII_USA_R0
switch (SCGetLanguage()) {
case 1:
diff --git a/src/dusk/OSReport.cpp b/src/dusk/OSReport.cpp
new file mode 100644
index 0000000000..aee1e17bc6
--- /dev/null
+++ b/src/dusk/OSReport.cpp
@@ -0,0 +1,141 @@
+#include
+
+#include "aurora/lib/logging.hpp"
+#include "os_report.h"
+
+aurora::Module Log("dusk::osReport");
+
+bool dusk::OSReportReallyForceEnable = false;
+
+u8 __OSReport_disable;
+
+void OSReportDisable() {
+ __OSReport_disable = true;
+}
+
+void OSReportEnable() {
+ __OSReport_disable = false;
+}
+
+static bool checkEnabled() {
+ return !__OSReport_disable || dusk::OSReportReallyForceEnable;
+}
+
+#ifndef va_copy
+#define va_copy(d, s) ((d) = (s))
+#endif
+
+static std::string FormatToString(const char* msg, va_list list) {
+ size_t size = (strlen(msg) * 2) + 50;
+ std::string str;
+ va_list ap;
+ int attempts = 0;
+ while (true) {
+ str.resize(size);
+ va_copy(ap, list);
+ int n = vsnprintf(str.data(), size, msg, ap);
+ va_end(ap);
+ if (n > -1 && n < size) {
+ str.resize(n);
+ break;
+ }
+
+ ++attempts;
+ if (attempts >= 3) {
+ if (n == -1) {
+ str.clear();
+ }
+ break;
+ }
+ if (n > -1) {
+ size = n + 1;
+ } else {
+ size *= 2;
+ }
+ }
+ return str;
+}
+
+void OSReport(const char* fmt, ...) {
+ if (!checkEnabled()) {
+ return;
+ }
+ va_list args;
+ va_start(args, fmt);
+ const auto str = FormatToString(fmt, args);
+ va_end(args);
+
+ Log.info("{}", str);
+}
+
+void OSPanic(const char* file, int line, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ const auto str = FormatToString(fmt, args);
+ va_end(args);
+
+ Log.fatal("[{}:{}] {}", file, line, str);
+}
+
+void OSReport_Error(const char* fmt, ...) {
+ if (!checkEnabled()) {
+ return;
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ const auto str = FormatToString(fmt, args);
+ va_end(args);
+
+ Log.error("{}", str);
+}
+
+void OSReport_FatalError(const char* fmt, ...) {
+ if (!checkEnabled()) {
+ return;
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ const auto str = FormatToString(fmt, args);
+ va_end(args);
+
+ Log.fatal("{}", str);
+}
+
+void OSReport_Warning(const char* fmt, ...) {
+ if (!checkEnabled()) {
+ return;
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ const auto str = FormatToString(fmt, args);
+ va_end(args);
+
+ Log.warn("{}", str);
+}
+
+void OSReport_System(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ OSVAttention(fmt, args);
+ va_end(args);
+}
+
+void OSVAttention(const char* fmt, va_list args) {
+ if (!checkEnabled()) {
+ return;
+ }
+
+ const auto str = FormatToString(fmt, args);
+
+ Log.info("{}", str);
+}
+
+void OSAttention(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ OSVAttention(fmt, args);
+ va_end(args);
+}
\ No newline at end of file
diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp
new file mode 100644
index 0000000000..28e66c891e
--- /dev/null
+++ b/src/dusk/achievements.cpp
@@ -0,0 +1,1147 @@
+#include "dusk/achievements.h"
+#include "dusk/io.hpp"
+#include "dusk/main.h"
+#include "d/d_com_inf_game.h"
+#include "d/d_item_data.h"
+#include "d/d_map_path_fmap.h"
+#include "d/d_stage.h"
+#include "d/d_menu_fmap.h"
+#include "JSystem/JKernel/JKRArchive.h"
+#include "d/d_meter2_info.h"
+#include "d/actor/d_a_alink.h"
+#include "d/actor/d_a_ni.h"
+#include "d/actor/d_a_npc4.h"
+#include "d/actor/d_a_b_ob.h"
+#include "d/actor/d_a_player.h"
+#include "d/d_demo.h"
+#include "dusk/ui/ui.hpp"
+#include "f_pc/f_pc_name.h"
+#include "f_op/f_op_actor_mng.h"
+#include "f_pc/f_pc_name.h"
+
+#include
+#include
+
+namespace dusk {
+
+using json = nlohmann::json;
+
+static void* s_cucco_play_search(void* i_actor, void*) {
+ if (!fopAcM_IsActor(i_actor) || fopAcM_GetName((fopAc_ac_c*)i_actor) != fpcNm_NI_e) {
+ return nullptr;
+ }
+ auto* ni = static_cast(i_actor);
+ return ni->mAction == ACTION_PLAY_e ? i_actor : nullptr;
+}
+
+static void checkGoatHerding(Achievement& a, int32_t threshMs) {
+ if (dMeter2Info_getMaxCount() != 20 || dMeter2Info_getNowCount() != 20) {
+ return;
+ }
+ const int32_t elapsed = dMeter2Info_getTimeMs();
+ if (elapsed > 0 && elapsed <= threshMs) {
+ a.progress = 1;
+ }
+}
+
+static constexpr auto ACHIEVEMENTS_FILENAME = "achievements.json";
+
+std::vector AchievementSystem::makeEntries() {
+ return {
+ // Challenge
+ {
+ {
+ "hero_of_twilight",
+ "Hero of Twilight",
+ "Deliver the finishing blow to Ganondorf.",
+ AchievementCategory::Challenge,
+ false, 0, 0, false
+ },
+ [](Achievement& a, json&) {
+ const auto* link = static_cast(daPy_getPlayerActorClass());
+ if (link != nullptr && link->mProcID == daAlink_c::PROC_GANON_FINISH) {
+ a.progress = 1;
+ }
+ },
+ {}
+ },
+ {
+ {
+ "completionist",
+ "Completionist",
+ "Complete the game after collecting all equipment, heart containers, portals, bugs, poes, and hidden skills.",
+ AchievementCategory::Challenge,
+ false, 0, 0, false
+ },
+ [](Achievement& a, json&) {
+ const auto* link = static_cast(daPy_getPlayerActorClass());
+ if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
+ return;
+ }
+ if (dComIfGs_getMaxLife() < 100) {
+ return;
+ }
+ for (int i = 0; i < 4; ++i) {
+ if (!dComIfGs_isCollectMirror(i)) {
+ return;
+ }
+ }
+ for (int i = 0; i < 3; ++i) {
+ if (!dComIfGs_isCollectCrystal(i)) {
+ return;
+ }
+ }
+ static const u16 skillBits[] = {
+ dSv_event_flag_c::F_0338, dSv_event_flag_c::F_0339,
+ dSv_event_flag_c::F_0340, dSv_event_flag_c::F_0341,
+ dSv_event_flag_c::F_0342, dSv_event_flag_c::F_0343,
+ dSv_event_flag_c::F_0344
+ };
+ for (u16 bit : skillBits) {
+ if (!dComIfGs_isEventBit(bit)) {
+ return;
+ }
+ }
+ if (dComIfGs_checkGetInsectNum() < 24) {
+ return;
+ }
+ if (dComIfGs_getPohSpiritNum() < 60) {
+ return;
+ }
+ if (dComIfGs_getWalletSize() < 2) {
+ return;
+ }
+ if (dComIfGs_getArrowMax() < 100) {
+ return;
+ }
+ if (!dComIfGs_isCollectSword(COLLECT_MASTER_SWORD)) {
+ return;
+ }
+ if (!dComIfGs_isCollectShield(COLLECT_HYLIAN_SHIELD)) {
+ return;
+ }
+ if (!dComIfGs_isCollectClothes(KOKIRI_CLOTHES_FLAG)) {
+ return;
+ }
+ if (!dComIfGs_isItemFirstBit(dItemNo_WEAR_ZORA_e)) {
+ return;
+ }
+ if (!dComIfGs_isItemFirstBit(dItemNo_ARMOR_e)) {
+ return;
+ }
+ static const struct { int stage; int sw; } warpPortals[] = {
+ { dStage_SaveTbl_ORDON, 52 }, // Ordon Spring
+ { dStage_SaveTbl_FARON, 71 }, // South Faron Woods
+ { dStage_SaveTbl_FARON, 2 }, // North Faron Woods
+ { dStage_SaveTbl_GROVE, 100 }, // Sacred Grove
+ { dStage_SaveTbl_FIELD, 21 }, // Gorge
+ { dStage_SaveTbl_ELDIN, 31 }, // Kakariko Village
+ { dStage_SaveTbl_ELDIN, 21 }, // Death Mountain
+ { dStage_SaveTbl_FIELD, 99 }, // Bridge of Eldin
+ { dStage_SaveTbl_FIELD, 3 }, // Castle Town
+ { dStage_SaveTbl_LANAYRU, 10 }, // Lake Hylia
+ { dStage_SaveTbl_LANAYRU, 2 }, // Zora's Domain
+ { dStage_SaveTbl_LANAYRU, 21 }, // Upper Zora's River
+ { dStage_SaveTbl_SNOWPEAK, 21 }, // Snowpeak
+ { dStage_SaveTbl_DESERT, 21 }, // Gerudo Mesa
+ { dStage_SaveTbl_DESERT, 40 }, // Mirror Chamber
+ };
+ for (const auto& p : warpPortals) {
+ if (!g_dComIfG_gameInfo.info.getSavedata().getSave(p.stage).getBit().isSwitch(p.sw)) {
+ return;
+ }
+ }
+ if (dComIfGs_getCollectSmell() == dItemNo_NONE_e) {
+ return;
+ }
+
+ if (dMeter2Info_getRecieveLetterNum() == 0) {
+ return;
+ }
+
+ bool hasJournal = false;
+ for (int fi = 0; fi < 6; ++fi) {
+ if (dComIfGs_getFishNum(fi) != 0) {
+ hasJournal = true;
+ break;
+ }
+ }
+ if (!hasJournal) {
+ return;
+ }
+
+ int bottleCount = 0;
+ for (int i = 0; i < dSv_player_item_c::BOTTLE_MAX; ++i) {
+ if (dComIfGs_getItem(SLOT_11 + i, false) != dItemNo_NONE_e) {
+ bottleCount++;
+ }
+ }
+ if (bottleCount < 4) {
+ return;
+ }
+
+ int bombBagCount = 0;
+ for (int i = 0; i < dSv_player_item_c::BOMB_BAG_MAX; ++i) {
+ if (dComIfGs_getItem(SLOT_15 + i, false) != dItemNo_NONE_e) {
+ bombBagCount++;
+ }
+ }
+ if (bombBagCount < 3) {
+ return;
+ }
+
+ bool hasJewelRod = false;
+ for (int slot = 0; slot < 24 && !hasJewelRod; ++slot) {
+ const u8 item = dComIfGs_getItem(slot, false);
+ if (item == dItemNo_JEWEL_ROD_e || item == dItemNo_JEWEL_BEE_ROD_e || item == dItemNo_JEWEL_WORM_ROD_e) {
+ hasJewelRod = true;
+ }
+ }
+ if (!hasJewelRod) {
+ return;
+ }
+
+ static const u8 requiredWheelItems[] = {
+ dItemNo_BOOMERANG_e,
+ dItemNo_BOW_e,
+ dItemNo_W_HOOKSHOT_e,
+ dItemNo_SPINNER_e,
+ dItemNo_IRONBALL_e,
+ dItemNo_COPY_ROD_e,
+ dItemNo_HVY_BOOTS_e,
+ dItemNo_KANTERA_e,
+ dItemNo_PACHINKO_e,
+ dItemNo_HAWK_EYE_e,
+ dItemNo_ANCIENT_DOCUMENT_e,
+ dItemNo_HORSE_FLUTE_e,
+ };
+ for (u8 required : requiredWheelItems) {
+ bool found = false;
+ for (int slot = 0; slot < 24; ++slot) {
+ if (dComIfGs_getItem(slot, false) == required) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return;
+ }
+ }
+ a.progress = 1;
+ },
+ {}
+ },
+ // Collection
+ {
+ {
+ "princess_of_bugs",
+ "The Princess of Bugs",
+ "Deliver all 24 golden bugs to Agitha.",
+ AchievementCategory::Collection,
+ true, 24, 0, false
+ },
+ [](Achievement& a, json&) {
+ a.progress = dComIfGs_checkGetInsectNum();
+ },
+ {}
+ },
+ {
+ {
+ "all_poes",
+ "Poe Collector",
+ "Collect all 60 Poe Souls.",
+ AchievementCategory::Collection,
+ true, 60, 0, false
+ },
+ [](Achievement& a, json&) {
+ a.progress = dComIfGs_getPohSpiritNum();
+ },
+ {}
+ },
+ {
+ {
+ "hylian_loach",
+ "Legendary Catch",
+ "Catch a Hylian Loach.",
+ AchievementCategory::Collection,
+ false, 0, 0, false
+ },
+ [](Achievement& a, json&) {
+ if (dComIfGs_getFishNum(1) > 0) {
+ a.progress = 1;
+ }
+ },
+ {}
+ },
+ {
+ {
+ "all_fish",
+ "Gone Fishin'",
+ "Catch all 6 species of fish.",
+ AchievementCategory::Collection,
+ true, 6, 0, false
+ },
+ [](Achievement& a, json&) {
+ int nUniqueFish = 0;
+ for (int i = 0; i < 6; ++i) {
+ if (dComIfGs_getFishNum(i) != 0) {
+ nUniqueFish++;
+ }
+ }
+ a.progress = nUniqueFish;
+ },
+ {}
+ },
+ {
+ {
+ "a_big_heart",
+ "A Big Heart",
+ "Reach maximum health with all 20 heart containers.",
+ AchievementCategory::Collection,
+ true, 20, 0, false
+ },
+ [](Achievement& a, json&) {
+ a.progress = dComIfGs_getMaxLife() / 5;
+ },
+ {}
+ },
+ {
+ {
+ "all_bottles",
+ "Glassware Guardian",
+ "Obtain all 4 bottles.",
+ AchievementCategory::Collection,
+ true, 4, 0, false
+ },
+ [](Achievement& a, json&) {
+ if (daPy_getPlayerActorClass() == nullptr) {
+ return;
+ }
+ int count = 0;
+ for (int i = 0; i < dSv_player_item_c::BOTTLE_MAX; ++i) {
+ if (dComIfGs_getItem(SLOT_11 + i, false) != dItemNo_NONE_e) {
+ count++;
+ }
+ }
+ a.progress = count;
+ },
+ {}
+ },
+ {
+ {
+ "all_hidden_skills",
+ "Master of Secrets",
+ "Learn all 7 Hidden Skills.",
+ AchievementCategory::Collection,
+ true, 7, 0, false
+ },
+ [](Achievement& a, json&) {
+ static const u16 skillBits[] = {
+ dSv_event_flag_c::F_0338, dSv_event_flag_c::F_0339,
+ dSv_event_flag_c::F_0340, dSv_event_flag_c::F_0341,
+ dSv_event_flag_c::F_0342, dSv_event_flag_c::F_0343,
+ dSv_event_flag_c::F_0344
+ };
+ int count = 0;
+ for (u16 bit : skillBits) {
+ if (dComIfGs_isEventBit(bit)) {
+ count++;
+ }
+ }
+ a.progress = count;
+ },
+ {}
+ },
+ {
+ {
+ "all_letters",
+ "We Deliver!",
+ "Collect all 16 postman letters.",
+ AchievementCategory::Collection,
+ true, 16, 0, false
+ },
+ [](Achievement& a, json&) {
+ a.progress = dMeter2Info_getRecieveLetterNum();
+ },
+ {}
+ },
+ {
+ {
+ "cave_of_ordeals",
+ "Conqueror of Ordeals",
+ "Clear all 50 floors of the Cave of Ordeals.",
+ AchievementCategory::Challenge,
+ false, 0, 0, false
+ },
+ [](Achievement& a, json&) {
+ if (daNpcF_chkEvtBit(0x1F9)) {
+ a.progress = 1;
+ }
+ },
+ {}
+ },
+ {
+ {
+ "cave_of_ordeals_heartless",
+ "Indomitable",
+ "Clear all 50 floors of the Cave of Ordeals with only 3 heart containers.",
+ AchievementCategory::Challenge,
+ false, 0, 0, false
+ },
+ [](Achievement& a, json&) {
+ if (daNpcF_chkEvtBit(0x1F9) && dComIfGs_getMaxLife() <= 15) {
+ a.progress = 1;
+ }
+ },
+ {}
+ },
+ {
+ {
+ "speedrun_12h",
+ "Been There Done That",
+ "Defeat Ganondorf with a total save file play time under 12 hours.",
+ AchievementCategory::Challenge,
+ false, 0, 0, false
+ },
+ [](Achievement& a, json&) {
+ const auto* link = static_cast(daPy_getPlayerActorClass());
+ if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
+ return;
+ }
+ const int64_t ticks = (static_cast(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime();
+ if (ticks / OS_TIMER_CLOCK < 12 * 3600) {
+ a.progress = 1;
+ }
+ },
+ {}
+ },
+ {
+ {
+ "speedrun_8h",
+ "Swift Blade",
+ "Defeat Ganondorf with a total save file play time under 6 hours.",
+ AchievementCategory::Challenge,
+ false, 0, 0, false
+ },
+ [](Achievement& a, json&) {
+ const auto* link = static_cast