diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6045ebc734..6956574919 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -69,7 +69,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
@@ -102,12 +102,19 @@ 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)
+if (ANDROID)
+ set(DUSK_ENABLE_UPDATE_CHECKER_DEFAULT OFF)
+else ()
+ set(DUSK_ENABLE_UPDATE_CHECKER_DEFAULT ON)
+endif ()
+option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ${DUSK_ENABLE_UPDATE_CHECKER_DEFAULT})
if(ANDROID)
set(DUSK_MOVIE_SUPPORT OFF)
@@ -283,9 +290,9 @@ set(DUSK_PRODUCT_NAME "Dusk")
set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors")
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)
+set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 MTX_USE_PS=1)
set(GAME_INCLUDE_DIRS
include
@@ -313,6 +320,37 @@ 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 (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)
diff --git a/README.md b/README.md
index 02b1ded875..5e7412e6aa 100644
--- a/README.md
+++ b/README.md
@@ -34,8 +34,8 @@ First, make sure your dump of the game is clean and supported by Dusk. You can d
- Extract the .zip file
- Launch Dusk
-- Press **Select Disc Image**, navigate to your game dump, and select the file
-- Press **Start Game** to play!
+- Press **Select Disc Image** and provide the path to your supported game dump.
+- Press **Play**!
# Building
@@ -46,3 +46,10 @@ Pull requests are welcomed! Note that we do not accept contributions that are pr
# 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/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/extern/aurora b/extern/aurora
index 518747aa86..17be93f0ae 160000
--- a/extern/aurora
+++ b/extern/aurora
@@ -1 +1 @@
-Subproject commit 518747aa86a50be62ecf92aeb309309b0d58a54a
+Subproject commit 17be93f0ae011fc3202e87e3f2efda4aae250fa5
diff --git a/files.cmake b/files.cmake
index a463ce5129..53aa7a2636 100644
--- a/files.cmake
+++ b/files.cmake
@@ -1430,11 +1430,14 @@ set(DUSK_FILES
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
@@ -1447,18 +1450,17 @@ 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/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
@@ -1469,16 +1471,16 @@ set(DUSK_FILES
src/dusk/ui/controller_config.hpp
src/dusk/ui/document.cpp
src/dusk/ui/document.hpp
- src/dusk/ui/achievements.cpp
- src/dusk/ui/achievements.hpp
- src/dusk/ui/preset.cpp
- src/dusk/ui/preset.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
@@ -1486,12 +1488,12 @@ set(DUSK_FILES
src/dusk/ui/overlay.hpp
src/dusk/ui/pane.cpp
src/dusk/ui/pane.hpp
- src/dusk/ui/popup.cpp
- src/dusk/ui/popup.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/prelaunch_options.cpp
- src/dusk/ui/prelaunch_options.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
@@ -1517,3 +1519,10 @@ set(DUSK_FILES
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/include/d/d_menu_fmap2D.h b/include/d/d_menu_fmap2D.h
index d0152f0229..7df78bb6d0 100644
--- a/include/d/d_menu_fmap2D.h
+++ b/include/d/d_menu_fmap2D.h
@@ -169,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;
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/dusk/achievements.h b/include/dusk/achievements.h
index ca4676ab2d..bf0021c9d7 100644
--- a/include/dusk/achievements.h
+++ b/include/dusk/achievements.h
@@ -50,8 +50,6 @@ public:
bool hasSignal(const char* key) const;
std::vector getAchievements() const;
- bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); }
- std::string consumePendingUnlock();
private:
struct Entry {
@@ -68,7 +66,6 @@ private:
std::unordered_set m_signals;
bool m_loaded = false;
bool m_dirty = false;
- std::queue m_pendingUnlocks;
};
} // namespace dusk
diff --git a/include/dusk/main.h b/include/dusk/main.h
index 065f507d36..d6b9c9927f 100644
--- a/include/dusk/main.h
+++ b/include/dusk/main.h
@@ -1,6 +1,10 @@
#ifndef DUSK_MAIN_H
#define DUSK_MAIN_H
+#if defined(__APPLE__)
+#include
+#endif
+
#include
namespace dusk {
@@ -8,7 +12,17 @@ namespace dusk {
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;
}
#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 7f7aa8cd69..78c5540e58 100644
--- a/include/dusk/settings.h
+++ b/include/dusk/settings.h
@@ -21,6 +21,12 @@ enum class GameLanguage : u8 {
Italian = OS_LANGUAGE_ITALIAN,
};
+enum class DiscVerificationState : u8 {
+ Unknown = 0,
+ Success,
+ HashMismatch,
+};
+
namespace config {
template <>
struct ConfigEnumRange {
@@ -33,6 +39,12 @@ 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;
+};
}
// Persistent user settings
@@ -45,6 +57,8 @@ struct UserSettings {
ConfigVar enableFullscreen;
ConfigVar enableVsync;
ConfigVar lockAspectRatio;
+ ConfigVar enableFpsOverlay;
+ ConfigVar fpsOverlayCorner;
} video;
struct {
@@ -67,7 +81,6 @@ struct UserSettings {
// QoL
ConfigVar enableQuickTransform;
ConfigVar hideTvSettingsScreen;
- ConfigVar skipWarningScreen;
ConfigVar biggerWallets;
ConfigVar noReturnRupees;
ConfigVar disableRupeeCutscenes;
@@ -86,7 +99,7 @@ struct UserSettings {
// Preferences
ConfigVar enableMirrorMode;
- ConfigVar disableMainHUD;
+ ConfigVar minimalHUD;
ConfigVar pauseOnFocusLost;
ConfigVar enableLinkDollRotation;
ConfigVar enableAchievementNotifications;
@@ -121,6 +134,7 @@ struct UserSettings {
ConfigVar invertCameraYAxis;
ConfigVar freeCameraSensitivity;
ConfigVar debugFlyCam;
+ ConfigVar debugFlyCamLockEvents;
// Cheats
ConfigVar infiniteHearts;
@@ -147,16 +161,20 @@ struct UserSettings {
// Tools
ConfigVar speedrunMode;
ConfigVar liveSplitEnabled;
+ ConfigVar recordingMode;
} game;
struct {
ConfigVar isoPath;
+ ConfigVar isoVerification;
ConfigVar graphicsBackend;
ConfigVar skipPreLaunchUI;
ConfigVar showPipelineCompilation;
ConfigVar wasPresetChosen;
ConfigVar enableCrashReporting;
+ ConfigVar checkForUpdates;
ConfigVar cardFileType;
+ ConfigVar enableAdvancedSettings;
} backend;
};
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/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/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/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/rml/overlay.rcss b/res/rml/overlay.rcss
index e18bb436e5..05c8440b55 100644
--- a/res/rml/overlay.rcss
+++ b/res/rml/overlay.rcss
@@ -8,140 +8,269 @@ body {
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;
-}
-
-.overlay-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;
-}
-
-.overlay-root[open] {
- filter: opacity(1);
-}
-
-.overlay {
- 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) {
- .overlay-root {
- min-height: 38%;
- }
-
- .overlay {
- 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-family: "Fira Sans";
+ font-weight: normal;
font-size: 20dp;
- line-height: 24dp;
- text-transform: uppercase;
- color: #FFFFFF;
- opacity: 1;
+ 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%);
}
-footer-button.return {
- text-align: left;
+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;
}
-footer-button.reset {
- text-align: right;
+toast heading > span {
+ flex: 1 0 auto;
}
-.stepped-carousel {
+toast heading > row {
+ flex: 1 0 auto;
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;
+ gap: 4dp;
}
-.stepped-carousel-value {
- line-height: 29dp;
- min-width: 166dp;
+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;
- 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;
+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: 24dp;
+ height: 24dp;
+ font-size: 24dp;
+ decorator: text("" center center);
+}
+
+icon.trophy {
+ width: 24dp;
+ height: 24dp;
+ font-size: 24dp;
+ decorator: text("" center center);
+}
+
+icon.controller {
+ width: 24dp;
+ height: 24dp;
+ font-size: 24dp;
+ decorator: text("" center center);
+}
+
+icon.warning {
+ width: 24dp;
+ height: 24dp;
+ font-size: 24dp;
+ 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);
+ }
}
diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss
index 9556e74ef2..9b83db4660 100644
--- a/res/rml/prelaunch.rcss
+++ b/res/rml/prelaunch.rcss
@@ -9,17 +9,44 @@ body {
font-weight: normal;
font-size: 20dp;
color: #FFFFFF;
- background-color: #000000;
- decorator: image(../prelaunch-bg.png cover left center);
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%;
@@ -35,6 +62,7 @@ content[open] {
menu {
position: absolute;
left: 96dp;
+ right: auto;
top: 50%;
transform: translateY(-50%);
/* Scale based on a reference screen width, 428/1216 */
@@ -47,6 +75,11 @@ menu {
gap: 48dp;
}
+body.mirrored menu {
+ left: auto;
+ right: 96dp;
+}
+
hero {
display: flex;
flex-direction: column;
@@ -55,6 +88,10 @@ hero {
gap: 8dp;
}
+body.mirrored hero {
+ align-items: flex-end;
+}
+
hero img {
width: 100%;
}
@@ -79,6 +116,7 @@ hero img {
display: flex;
flex-direction: column;
gap: 12dp;
+ align-items: flex-start;
}
#menu-list button {
@@ -86,6 +124,7 @@ hero img {
height: 54dp;
padding: 8dp 16dp;
border-radius: 8dp;
+ text-align: left;
text-transform: uppercase;
font-family: "Fira Sans Condensed";
font-size: 32dp;
@@ -95,8 +134,14 @@ hero img {
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 0.1s linear-in-out;
+ transition: decorator color opacity 0.1s linear-in-out;
}
#menu-list button:hover,
@@ -105,25 +150,56 @@ hero img {
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 {
@@ -140,8 +216,24 @@ version-info {
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: block;
+ display: none;
width: 24dp;
height: 24dp;
font-family: "Material Symbols Rounded";
@@ -149,6 +241,10 @@ version-info {
font-size: 24dp;
}
+#disc-status[status] icon {
+ display: block;
+}
+
#disc-status[status=good] icon {
decorator: text("" center center);
}
@@ -157,24 +253,81 @@ version-info {
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;
}
-/* TODO: Hidden until an actual update checker is introduced */
.update {
display: none;
font-size: 16dp;
- font-weight: bold;
- cursor: pointer;
- color: #D8F999;
+ color: #A6A09B;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8dp;
+ font-size: 20dp;
}
-.detail,
-.update span {
+.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;
@@ -210,3 +363,84 @@ body.animate-in .intro-item {
.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: 20dp;
+ right: 20dp;
+ width: auto;
+ min-width: 0;
+ max-width: none;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16dp;
+ }
+
+ body.mirrored menu {
+ left: 20dp;
+ right: 20dp;
+ flex-direction: row-reverse;
+ }
+
+ hero {
+ flex: 1 1 0;
+ min-width: 0;
+ max-width: 48%;
+
+ }
+
+ 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,
+ disc-info,
+ version-info {
+ display: none;
+ }
+}
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
index db3558778a..817f3dc333 100644
--- a/res/rml/window.rcss
+++ b/res/rml/window.rcss
@@ -39,8 +39,8 @@ window.small {
width: auto;
}
-window.preset {
- min-width: 650dp;
+window.modal {
+ max-width: 640dp;
}
window[open] {
@@ -100,6 +100,10 @@ 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. */
@@ -194,6 +198,11 @@ button:not(:disabled):active {
box-shadow: #C2A42D 0 0 0 2dp;
}
+button.modal-btn {
+ flex: 1 1 0;
+ text-align: center;
+}
+
select-button {
display: flex;
align-items: center;
@@ -254,6 +263,8 @@ select-button input {
}
icon {
+ width: 1em;
+ height: 1em;
font-family: "Material Symbols Rounded";
font-weight: normal;
display: inline-block;
@@ -261,10 +272,23 @@ icon {
}
icon.warning {
- width: 1em;
- height: 1em;
decorator: text("" center center);
- color: #ffcc00;
+}
+
+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-row {
@@ -323,7 +347,7 @@ icon.warning {
color: rgba(224, 219, 200, 45%);
}
-progressbar {
+progress {
display: block;
width: 100%;
height: 6dp;
@@ -332,12 +356,12 @@ progressbar {
margin: 6dp 0 2dp 0;
}
-progressbar.progress-done fill {
+progress.progress-done fill {
background-color: #44aa22;
border-radius: 3dp;
}
-progressbar.progress-ongoing fill {
+progress.progress-ongoing fill {
background-color: #2255bb;
border-radius: 3dp;
}
@@ -350,35 +374,13 @@ button.achievement-clear {
opacity: 0.45;
}
-.preset-dialog {
- display: flex;
- flex-flow: column;
- padding: 32dp;
- gap: 20dp;
- flex: 0 1 auto;
-}
-
-.preset-title {
- display: block;
- font-family: "Fira Sans Condensed";
- font-weight: bold;
- font-size: 30dp;
- text-align: center;
-}
-
-.preset-intro {
- display: block;
- font-size: 18dp;
- text-align: center;
- color: rgba(224, 219, 200, 65%);
-}
-
.preset-grid {
display: flex;
flex-direction: row;
gap: 20dp;
flex: 0 1 auto;
align-items: flex-start;
+ width: 100%;
}
.preset-col {
@@ -388,14 +390,104 @@ button.achievement-clear {
flex: 1 1 0;
}
-button.preset-btn {
- font-size: 22dp;
- padding: 20dp 16dp;
-}
-
.preset-desc {
display: block;
font-size: 16dp;
- color: rgba(224, 219, 200, 65%);
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;
+ justify-content: stretch;
+ align-items: stretch;
+ gap: 12dp;
+ width: 100%;
+ flex: 0 0 auto;
+ padding-top: 4dp;
+}
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 73049af08b..c960d37a7b 100644
--- a/src/d/actor/d_a_alink_hook.inc
+++ b/src/d/actor/d_a_alink_hook.inc
@@ -17,11 +17,11 @@ enum {
HS_MODE_RETURN_e = 6,
};
-void daAlink_c::hsChainShape_c::draw() {
- if (dusk::getSettings().game.superClawshot) {
- return;
- }
+#if TARGET_PC
+static const int HS_CHAIN_MAX_LINKS = 600;
+#endif
+void daAlink_c::hsChainShape_c::draw() {
static const int dummy = 0;
daAlink_c* alink = (daAlink_c*)getUserArea();
@@ -165,7 +165,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;
@@ -187,6 +191,10 @@ void daAlink_c::hsChainShape_c::draw() {
var_f26 = temp_f27;
var_f30 += fabsf(cM_scos(spC)) * 5.0f;
+
+#if TARGET_PC
+ chainLinks++;
+#endif
}
}
@@ -202,7 +210,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);
@@ -215,6 +227,9 @@ 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
}
}
}
diff --git a/src/d/actor/d_a_alink_swindow.inc b/src/d/actor/d_a_alink_swindow.inc
index 4f651e16e2..9016f14205 100644
--- a/src/d/actor/d_a_alink_swindow.inc
+++ b/src/d/actor/d_a_alink_swindow.inc
@@ -41,7 +41,7 @@ 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();
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_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/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_camera.cpp b/src/d/d_camera.cpp
index e559a56d95..8e13c6271f 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 {
@@ -7483,6 +7484,8 @@ 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() {
@@ -7490,6 +7493,7 @@ bool dCamera_c::executeDebugFlyCam() {
if (mDebugFlyCam.initialized) {
deactivateDebugFlyCam();
}
+ sFlyCamLastMousePos = {-1.f, -1.f};
return false;
}
@@ -7519,16 +7523,63 @@ bool dCamera_c::executeDebugFlyCam() {
mDebugFlyCam.initialized = true;
}
- event->mEventStatus = 1;
- dComIfGp_getEventManager().setCameraPlay(1);
+ if (dusk::getSettings().game.debugFlyCamLockEvents) {
+ event->mEventStatus = 1;
+ dComIfGp_getEventManager().setCameraPlay(1);
+ } else {
+ if (event->mEventStatus != 0) {
+ event->mEventStatus = 0;
+ }
+ dComIfGp_getEventManager().setCameraPlay(0);
+ }
- interface_of_controller_pad& pad = mDoCPd_c::getCpadInfo(0);
- f32 stickY = pad.mMainStickPosY * 72.0f;
- f32 stickX = pad.mMainStickPosX * 72.0f;
- f32 cStickY = pad.mCStickPosY * 59.0f;
- f32 cStickX = pad.mCStickPosX * 59.0f;
- f32 trigL = pad.mTriggerLeft * 150.0f;
- f32 trigR = pad.mTriggerRight * 150.0f;
+ 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) {
@@ -7542,7 +7593,7 @@ bool dCamera_c::executeDebugFlyCam() {
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 = mDoCPd_c::getHoldZ(PAD_1) ? FLYCAM_FAST_SPEED : FLYCAM_SPEED;
+ f32 speed = fast ? FLYCAM_FAST_SPEED : FLYCAM_SPEED;
mEye.x += speed * moveDx;
mEye.y += speed * moveDy;
@@ -7553,6 +7604,7 @@ bool dCamera_c::executeDebugFlyCam() {
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;
@@ -7570,7 +7622,7 @@ void dCamera_c::deactivateDebugFlyCam() {
Reset(mDebugFlyCam.savedCenter, mDebugFlyCam.savedEye, mDebugFlyCam.savedFovy, mDebugFlyCam.savedBank.Val());
dEvt_control_c* event = dComIfGp_getEvent();
- if (event != nullptr) {
+ if (event != nullptr && event->mEventStatus != 0) {
event->mEventStatus = 0;
}
dComIfGp_getEventManager().setCameraPlay(0);
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_map.cpp b/src/d/d_map.cpp
index d4e5a98888..25d792f309 100644
--- a/src/d/d_map.cpp
+++ b/src/d/d_map.cpp
@@ -1595,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;
@@ -1657,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;
@@ -1737,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
@@ -1829,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 eca620ef98..f5e447a2aa 100644
--- a/src/d/d_map_path.cpp
+++ b/src/d/d_map_path.cpp
@@ -15,13 +15,47 @@
#include
#ifdef TARGET_PC
+#include
+#include
+#include
+
constexpr u16 kMapResolutionMultiplier = 4;
-constexpr u16 kMapCircleSize = 16 * kMapResolutionMultiplier;
+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++) {
@@ -35,45 +69,101 @@ void dMpath_n::dTexObjAggregate_c::create() {
}
#if TARGET_PC
- auto hqCircle = JKR_NEW TGXTexObj();
+ static bool hqTexsDrawn = false;
- static bool hqCircleDrawn = false;
- static u8 hqCircleData[kMapCircleSize * kMapCircleSize];
+ static u8 hqCircleData[kMapImageTotalPixels];
+ static u8 hqCircleAltData[kMapImageTotalPixels];
+ static u8 hqNijumaruData[kMapImageTotalPixels];
+ static u8 hqEnterData[kMapImageTotalPixels];
+ static u8 hqTryForceData[kMapImageTotalPixels];
- if (!hqCircleDrawn) {
- const auto center = kMapCircleSize / 2.0f;
- const auto radiusSq = center * center;
- const auto blocksAcross = kMapCircleSize >> 3;
- const auto totalPixels = sizeof(hqCircleData);
+ if (!hqTexsDrawn) {
+ constexpr auto center = kMapImageSide / 2.0f;
+ constexpr auto radiusSq = center * center;
- for (size_t i = 0; i < totalPixels; i++) {
- // 8x4 block swizzling for I8
- const auto blockIdx = i >> 5;
- const auto localIdx = i & 31;
+ // 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;
+ });
- 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;
+ // 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;
- // the original texture is in I4 format and uses 1 to indicate if inside the circle
- // so we scale to I8 range: 255 / 15 = 17
- hqCircleData[i] = (dx * dx + dy * dy < radiusSq) ? 17 : 0;
- }
- hqCircleDrawn = true;
+ 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;
}
- GXInitTexObj(hqCircle, hqCircleData, kMapCircleSize, kMapCircleSize, GX_TF_I8, GX_CLAMP,
- GX_CLAMP, GX_FALSE);
- GXInitTexObjLOD(hqCircle, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
- mp_texObj[6] = hqCircle;
+ 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
}
diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp
index 8ef7fadd4b..b39c9bb287 100644
--- a/src/d/d_menu_dmap.cpp
+++ b/src/d/d_menu_dmap.cpp
@@ -942,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 9100a7d63a..0bb9717952 100644
--- a/src/d/d_menu_dmap_map.cpp
+++ b/src/d/d_menu_dmap_map.cpp
@@ -368,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,
@@ -405,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_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 ea9912998b..d62f7d1037 100644
--- a/src/d/d_menu_fmap2D.cpp
+++ b/src/d/d_menu_fmap2D.cpp
@@ -1043,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];
@@ -1397,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,
@@ -1404,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,
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 fa6774d0a5..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();
@@ -317,7 +318,9 @@ int dMeter2_c::_execute() {
int dMeter2_c::_draw() {
#if TARGET_PC
- if (dusk::getSettings().game.disableMainHUD) {
+ if (dusk::getSettings().game.recordingMode || dusk::getSettings().game.minimalHUD ||
+ dusk::getSettings().game.debugFlyCam)
+ {
return 1;
}
#endif
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_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_s_logo.cpp b/src/d/d_s_logo.cpp
index 8c4d706b7f..672393b033 100644
--- a/src/d/d_s_logo.cpp
+++ b/src/d/d_s_logo.cpp
@@ -1120,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;
diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp
index f0e2330a15..f7ebf6a20d 100644
--- a/src/d/d_s_play.cpp
+++ b/src/d/d_s_play.cpp
@@ -40,8 +40,9 @@
#include "JSystem/JKernel/JKRAramArchive.h"
#if TARGET_PC
+#include "dusk/autosave.h"
#include "dusk/memory.h"
-#include
+#include "dusk/ui/ui.hpp"
#endif
#if DEBUG
@@ -794,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();
diff --git a/src/dusk/OSReport.cpp b/src/dusk/OSReport.cpp
index a54bcf0c78..aee1e17bc6 100644
--- a/src/dusk/OSReport.cpp
+++ b/src/dusk/OSReport.cpp
@@ -21,16 +21,39 @@ 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) {
- int ret = vsnprintf(nullptr, 0, msg, list);
- if (ret <= 0) {
- return {};
+ 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;
+ }
}
- ++ret;
- std::unique_ptr buf(new char[ret]);
- vsnprintf(buf.get(), ret, msg, list);
- buf[ret - 1] = '\0';
- return {buf.get()};
+ return str;
}
void OSReport(const char* fmt, ...) {
diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp
index 26a302678e..a090a40969 100644
--- a/src/dusk/achievements.cpp
+++ b/src/dusk/achievements.cpp
@@ -1,14 +1,15 @@
#include "dusk/achievements.h"
-#include "dusk/io.hpp"
-#include "dusk/main.h"
-#include "d/d_com_inf_game.h"
-#include "d/d_meter2_info.h"
#include "d/actor/d_a_alink.h"
#include "d/actor/d_a_npc4.h"
#include "d/actor/d_a_player.h"
+#include "d/d_com_inf_game.h"
#include "d/d_demo.h"
-#include "f_pc/f_pc_name.h"
+#include "d/d_meter2_info.h"
+#include "dusk/io.hpp"
+#include "dusk/main.h"
+#include "dusk/ui/ui.hpp"
#include "f_op/f_op_actor_mng.h"
+#include "f_pc/f_pc_name.h"
#include
#include
@@ -454,12 +455,6 @@ AchievementSystem& AchievementSystem::get() {
return instance;
}
-std::string AchievementSystem::consumePendingUnlock() {
- std::string msg = std::move(m_pendingUnlocks.front());
- m_pendingUnlocks.pop();
- return msg;
-}
-
std::vector AchievementSystem::getAchievements() const {
std::vector result;
result.reserve(m_entries.size());
@@ -559,7 +554,14 @@ void AchievementSystem::processEntry(Entry& e) {
if (nowUnlocked) {
e.achievement.progress = e.achievement.isCounter ? e.achievement.goal : 1;
e.achievement.unlocked = true;
- m_pendingUnlocks.push(e.achievement.name);
+ if (getSettings().game.enableAchievementNotifications) {
+ ui::push_toast({
+ .type = "achievement",
+ .title = "Achievement Unlocked!",
+ .content = e.achievement.name,
+ .duration = std::chrono::seconds(5),
+ });
+ }
m_dirty = true;
} else if (progressChanged) {
m_dirty = true;
diff --git a/src/dusk/autosave.cpp b/src/dusk/autosave.cpp
index f9b76f4c67..779cb19915 100644
--- a/src/dusk/autosave.cpp
+++ b/src/dusk/autosave.cpp
@@ -1,4 +1,5 @@
#include "dusk/autosave.h"
+#include "dusk/ui/ui.hpp"
#include "imgui/ImGuiConsole.hpp"
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
@@ -83,6 +84,9 @@ void waitingForWrite() {
}
void endAutoSave() {
- dusk::g_imguiConsole.AddToast("Saving...", 2.0f);
+ dusk::ui::push_toast({
+ .type = "autosave",
+ .duration = std::chrono::milliseconds(1500),
+ });
mAutoSaveProc = 0;
}
\ No newline at end of file
diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp
index 856cdce4a3..f4af7a2961 100644
--- a/src/dusk/config.cpp
+++ b/src/dusk/config.cpp
@@ -154,6 +154,7 @@ namespace dusk::config {
template class ConfigImpl;
template class ConfigImpl;
template class ConfigImpl;
+ template class ConfigImpl;
template class ConfigImpl;
}
diff --git a/src/dusk/http/curl.cpp b/src/dusk/http/curl.cpp
new file mode 100644
index 0000000000..5fdaf88a64
--- /dev/null
+++ b/src/dusk/http/curl.cpp
@@ -0,0 +1,206 @@
+#include "http.hpp"
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace dusk::http {
+namespace {
+
+struct CurlHeaders {
+ curl_slist* list = nullptr;
+
+ ~CurlHeaders() {
+ if (list != nullptr) {
+ curl_slist_free_all(list);
+ }
+ }
+
+ bool append(const std::string& header) {
+ curl_slist* next = curl_slist_append(list, header.c_str());
+ if (next == nullptr) {
+ return false;
+ }
+ list = next;
+ return true;
+ }
+};
+
+struct CurlContext {
+ Response response;
+ size_t maxBodyBytes = 0;
+ bool tooLarge = false;
+};
+
+void initialize_curl() {
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+}
+
+std::string trim_header_value(std::string_view value) {
+ while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) {
+ value.remove_prefix(1);
+ }
+ while (!value.empty() &&
+ (value.back() == '\r' || value.back() == '\n' || value.back() == ' ' ||
+ value.back() == '\t')) {
+ value.remove_suffix(1);
+ }
+ return std::string(value);
+}
+
+size_t write_body(char* ptr, size_t size, size_t nmemb, void* userdata) {
+ auto* context = static_cast(userdata);
+ const size_t bytes = size * nmemb;
+ if (bytes > context->maxBodyBytes ||
+ context->response.body.size() > context->maxBodyBytes - bytes) {
+ context->tooLarge = true;
+ return 0;
+ }
+
+ context->response.body.append(ptr, bytes);
+ return bytes;
+}
+
+size_t write_header(char* ptr, size_t size, size_t nmemb, void* userdata) {
+ auto* context = static_cast(userdata);
+ const std::string_view line(ptr, size * nmemb);
+ if (line.starts_with("HTTP/")) {
+ context->response.headers.clear();
+ return size * nmemb;
+ }
+
+ const size_t colon = line.find(':');
+ if (colon == std::string_view::npos) {
+ return size * nmemb;
+ }
+
+ context->response.headers.push_back({
+ .name = std::string(line.substr(0, colon)),
+ .value = trim_header_value(line.substr(colon + 1)),
+ });
+ return size * nmemb;
+}
+
+Error map_curl_error(CURLcode code, bool tooLarge) {
+ if (tooLarge) {
+ return Error::TooLarge;
+ }
+
+ switch (code) {
+ case CURLE_OK:
+ return Error::None;
+ case CURLE_URL_MALFORMAT:
+ return Error::InvalidUrl;
+ case CURLE_UNSUPPORTED_PROTOCOL:
+ return Error::UnsupportedScheme;
+ case CURLE_OPERATION_TIMEDOUT:
+ return Error::Timeout;
+ default:
+ return Error::Network;
+ }
+}
+
+long timeout_ms(std::chrono::milliseconds timeout) {
+ return std::max(1, timeout.count());
+}
+
+} // namespace
+
+bool available() noexcept {
+ return true;
+}
+
+Backend backend() noexcept {
+ return Backend::LibCurl;
+}
+
+const char* backend_name() noexcept {
+ return "libcurl";
+}
+
+Result get(const Request& request) {
+ if (request.url.empty()) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "URL is empty",
+ };
+ }
+ if (!request.url.starts_with("https://")) {
+ return {
+ .error = Error::UnsupportedScheme,
+ .message = "Only https:// URLs are supported",
+ };
+ }
+
+ static std::once_flag initFlag;
+ std::call_once(initFlag, initialize_curl);
+
+ CURL* curl = curl_easy_init();
+ if (curl == nullptr) {
+ return {
+ .error = Error::Network,
+ .message = "Failed to create libcurl request",
+ };
+ }
+
+ CurlHeaders headers;
+ for (const Header& header : request.headers) {
+ if (!headers.append(header.name + ": " + header.value)) {
+ curl_easy_cleanup(curl);
+ return {
+ .error = Error::Network,
+ .message = "Failed to allocate libcurl headers",
+ };
+ }
+ }
+
+ CurlContext context{
+ .maxBodyBytes = request.maxBodyBytes,
+ };
+
+ curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers.list);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout_ms(request.timeout));
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_ms(request.timeout));
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_body);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &context);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, write_header);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, &context);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
+#if CURL_AT_LEAST_VERSION(7, 85, 0)
+ curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "https");
+ curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https");
+#else
+ curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
+ curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
+#endif
+
+ const CURLcode code = curl_easy_perform(curl);
+ long statusCode = 0;
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
+ curl_easy_cleanup(curl);
+
+ context.response.statusCode = static_cast(statusCode);
+ if (code == CURLE_OK) {
+ return {
+ .response = std::move(context.response),
+ };
+ }
+
+ const Error error = map_curl_error(code, context.tooLarge);
+ return {
+ .error = error,
+ .message = error == Error::TooLarge ? "Response body exceeded the configured limit"
+ : curl_easy_strerror(code),
+ .response = std::move(context.response),
+ };
+}
+
+} // namespace dusk::http
diff --git a/src/dusk/http/http.hpp b/src/dusk/http/http.hpp
new file mode 100644
index 0000000000..d62b031fbc
--- /dev/null
+++ b/src/dusk/http/http.hpp
@@ -0,0 +1,59 @@
+#ifndef DUSK_HTTP_HTTP_HPP
+#define DUSK_HTTP_HTTP_HPP
+
+#include
+#include
+#include
+#include
+
+namespace dusk::http {
+
+enum class Backend {
+ None,
+ WinHttp,
+ UrlSession,
+ LibCurl,
+};
+
+enum class Error {
+ None,
+ NoBackend,
+ InvalidUrl,
+ UnsupportedScheme,
+ Timeout,
+ TooLarge,
+ Network,
+};
+
+struct Header {
+ std::string name;
+ std::string value;
+};
+
+struct Request {
+ std::string url;
+ std::vector headers;
+ std::chrono::milliseconds timeout{10000};
+ size_t maxBodyBytes = 1024 * 1024;
+};
+
+struct Response {
+ int statusCode = 0;
+ std::vector headers;
+ std::string body;
+};
+
+struct Result {
+ Error error = Error::None;
+ std::string message;
+ Response response;
+};
+
+bool available() noexcept;
+Backend backend() noexcept;
+const char* backend_name() noexcept;
+Result get(const Request& request);
+
+} // namespace dusk::http
+
+#endif // DUSK_HTTP_HTTP_HPP
diff --git a/src/dusk/http/no_backend.cpp b/src/dusk/http/no_backend.cpp
new file mode 100644
index 0000000000..4b42afb3c7
--- /dev/null
+++ b/src/dusk/http/no_backend.cpp
@@ -0,0 +1,24 @@
+#include "http.hpp"
+
+namespace dusk::http {
+
+bool available() noexcept {
+ return false;
+}
+
+Backend backend() noexcept {
+ return Backend::None;
+}
+
+const char* backend_name() noexcept {
+ return "none";
+}
+
+Result get(const Request&) {
+ return {
+ .error = Error::NoBackend,
+ .message = "No HTTP backend is available",
+ };
+}
+
+} // namespace dusk::http
diff --git a/src/dusk/http/url_session.mm b/src/dusk/http/url_session.mm
new file mode 100644
index 0000000000..01dd699a9c
--- /dev/null
+++ b/src/dusk/http/url_session.mm
@@ -0,0 +1,238 @@
+#include "http.hpp"
+
+#import
+
+#include
+#include
+#include
+
+@interface DuskHttpRequestDelegate : NSObject
+@property(nonatomic) dispatch_semaphore_t semaphore;
+@property(nonatomic) size_t maxBodyBytes;
+@property(nonatomic, strong) NSMutableData* data;
+@property(nonatomic, strong) NSURLResponse* response;
+@property(nonatomic, strong) NSError* error;
+@property(nonatomic) BOOL tooLarge;
+- (instancetype)initWithMaxBodyBytes:(size_t)maxBodyBytes;
+@end
+
+@implementation DuskHttpRequestDelegate
+
+- (instancetype)initWithMaxBodyBytes:(size_t)maxBodyBytes {
+ self = [super init];
+ if (self != nil) {
+ _semaphore = dispatch_semaphore_create(0);
+ _maxBodyBytes = maxBodyBytes;
+ _data = [NSMutableData data];
+ }
+ return self;
+}
+
+- (void)URLSession:(NSURLSession*)session
+ task:(NSURLSessionTask*)task
+ willPerformHTTPRedirection:(NSHTTPURLResponse*)response
+ newRequest:(NSURLRequest*)request
+ completionHandler:(void (^)(NSURLRequest*))completionHandler {
+ if ([[request.URL.scheme lowercaseString] isEqualToString:@"https"]) {
+ completionHandler(request);
+ } else {
+ completionHandler(nil);
+ }
+}
+
+- (void)URLSession:(NSURLSession*)session
+ dataTask:(NSURLSessionDataTask*)dataTask
+didReceiveResponse:(NSURLResponse*)response
+ completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
+ self.response = response;
+ completionHandler(NSURLSessionResponseAllow);
+}
+
+- (void)URLSession:(NSURLSession*)session
+ dataTask:(NSURLSessionDataTask*)dataTask
+ didReceiveData:(NSData*)data {
+ if (data.length > self.maxBodyBytes ||
+ self.data.length > self.maxBodyBytes - data.length) {
+ self.tooLarge = YES;
+ [dataTask cancel];
+ return;
+ }
+ [self.data appendData:data];
+}
+
+- (void)URLSession:(NSURLSession*)session
+ task:(NSURLSessionTask*)task
+ didCompleteWithError:(NSError*)error {
+ if (error != nil && !self.tooLarge) {
+ self.error = error;
+ }
+ dispatch_semaphore_signal(self.semaphore);
+}
+
+@end
+
+namespace dusk::http {
+namespace {
+
+NSString* to_nsstring(std::string_view value) {
+ return [[NSString alloc] initWithBytes:value.data()
+ length:value.size()
+ encoding:NSUTF8StringEncoding];
+}
+
+std::string to_string(NSString* value) {
+ if (value == nil) {
+ return {};
+ }
+
+ const char* utf8 = [value UTF8String];
+ return utf8 == nullptr ? std::string() : std::string(utf8);
+}
+
+Error map_nsurl_error(NSError* error) {
+ if (error == nil || ![error.domain isEqualToString:NSURLErrorDomain]) {
+ return Error::Network;
+ }
+
+ switch (error.code) {
+ case NSURLErrorTimedOut:
+ return Error::Timeout;
+ case NSURLErrorBadURL:
+ case NSURLErrorUnsupportedURL:
+ return Error::InvalidUrl;
+ default:
+ return Error::Network;
+ }
+}
+
+dispatch_time_t timeout_deadline(std::chrono::milliseconds timeout) {
+ const auto milliseconds = std::max(1, timeout.count());
+ return dispatch_time(DISPATCH_TIME_NOW,
+ static_cast(milliseconds) * static_cast(NSEC_PER_MSEC));
+}
+
+} // namespace
+
+bool available() noexcept {
+ return true;
+}
+
+Backend backend() noexcept {
+ return Backend::UrlSession;
+}
+
+const char* backend_name() noexcept {
+ return "NSURLSession";
+}
+
+Result get(const Request& request) {
+ @autoreleasepool {
+ if (request.url.empty()) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "URL is empty",
+ };
+ }
+ if (!request.url.starts_with("https://")) {
+ return {
+ .error = Error::UnsupportedScheme,
+ .message = "Only https:// URLs are supported",
+ };
+ }
+
+ NSString* urlString = to_nsstring(request.url);
+ if (urlString == nil) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "URL is not valid UTF-8",
+ };
+ }
+
+ NSURL* url = [NSURL URLWithString:urlString];
+ if (url == nil || ![[url.scheme lowercaseString] isEqualToString:@"https"]) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "Failed to parse URL",
+ };
+ }
+
+ NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
+ urlRequest.HTTPMethod = @"GET";
+ urlRequest.timeoutInterval = request.timeout.count() / 1000.0;
+ urlRequest.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
+ for (const Header& header : request.headers) {
+ NSString* name = to_nsstring(header.name);
+ NSString* value = to_nsstring(header.value);
+ if (name == nil || value == nil) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "Request header is not valid UTF-8",
+ };
+ }
+ [urlRequest setValue:value forHTTPHeaderField:name];
+ }
+
+ NSURLSessionConfiguration* configuration =
+ [NSURLSessionConfiguration ephemeralSessionConfiguration];
+ configuration.timeoutIntervalForRequest = request.timeout.count() / 1000.0;
+ configuration.timeoutIntervalForResource = request.timeout.count() / 1000.0;
+
+ DuskHttpRequestDelegate* delegate =
+ [[DuskHttpRequestDelegate alloc] initWithMaxBodyBytes:request.maxBodyBytes];
+ NSURLSession* session = [NSURLSession sessionWithConfiguration:configuration
+ delegate:delegate
+ delegateQueue:nil];
+ NSURLSessionDataTask* task = [session dataTaskWithRequest:urlRequest];
+ [task resume];
+
+ if (dispatch_semaphore_wait(delegate.semaphore, timeout_deadline(request.timeout)) != 0) {
+ [task cancel];
+ [session invalidateAndCancel];
+ return {
+ .error = Error::Timeout,
+ .message = "Request timed out",
+ };
+ }
+
+ [session finishTasksAndInvalidate];
+
+ Response response;
+ if ([delegate.response isKindOfClass:[NSHTTPURLResponse class]]) {
+ NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)delegate.response;
+ response.statusCode = static_cast(httpResponse.statusCode);
+ NSDictionary* headers = httpResponse.allHeaderFields;
+ for (id key in headers) {
+ id value = headers[key];
+ response.headers.push_back({
+ .name = to_string([key description]),
+ .value = to_string([value description]),
+ });
+ }
+ }
+ if (delegate.data != nil && delegate.data.length > 0) {
+ response.body.assign(static_cast(delegate.data.bytes),
+ static_cast(delegate.data.length));
+ }
+
+ if (delegate.tooLarge) {
+ return {
+ .error = Error::TooLarge,
+ .message = "Response body exceeded the configured limit",
+ .response = std::move(response),
+ };
+ }
+ if (delegate.error != nil) {
+ return {
+ .error = map_nsurl_error(delegate.error),
+ .message = to_string(delegate.error.localizedDescription),
+ .response = std::move(response),
+ };
+ }
+
+ return {
+ .response = std::move(response),
+ };
+ }
+}
+
+} // namespace dusk::http
diff --git a/src/dusk/http/winhttp.cpp b/src/dusk/http/winhttp.cpp
new file mode 100644
index 0000000000..937579f076
--- /dev/null
+++ b/src/dusk/http/winhttp.cpp
@@ -0,0 +1,320 @@
+#include "http.hpp"
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+namespace dusk::http {
+namespace {
+
+struct WinHttpHandle {
+ HINTERNET handle = nullptr;
+
+ WinHttpHandle() = default;
+ explicit WinHttpHandle(HINTERNET handle) : handle(handle) {}
+ WinHttpHandle(const WinHttpHandle&) = delete;
+ WinHttpHandle& operator=(const WinHttpHandle&) = delete;
+
+ ~WinHttpHandle() {
+ if (handle != nullptr) {
+ WinHttpCloseHandle(handle);
+ }
+ }
+
+ operator HINTERNET() const { return handle; }
+};
+
+std::wstring utf8_to_wide(std::string_view value) {
+ if (value.empty()) {
+ return {};
+ }
+
+ const int required = MultiByteToWideChar(
+ CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast(value.size()), nullptr, 0);
+ if (required <= 0) {
+ return {};
+ }
+
+ std::wstring result(static_cast(required), L'\0');
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast(value.size()),
+ result.data(), required);
+ return result;
+}
+
+std::string wide_to_utf8(std::wstring_view value) {
+ if (value.empty()) {
+ return {};
+ }
+
+ const int required = WideCharToMultiByte(
+ CP_UTF8, 0, value.data(), static_cast(value.size()), nullptr, 0, nullptr, nullptr);
+ if (required <= 0) {
+ return {};
+ }
+
+ std::string result(static_cast(required), '\0');
+ WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast(value.size()), result.data(),
+ required, nullptr, nullptr);
+ return result;
+}
+
+DWORD timeout_ms(std::chrono::milliseconds timeout) {
+ const auto count = std::max(1, timeout.count());
+ return static_cast(
+ std::min(count, std::numeric_limits::max()));
+}
+
+Error map_winhttp_error(DWORD error) {
+ switch (error) {
+ case ERROR_WINHTTP_TIMEOUT:
+ return Error::Timeout;
+ case ERROR_WINHTTP_INVALID_URL:
+ case ERROR_WINHTTP_UNRECOGNIZED_SCHEME:
+ return Error::InvalidUrl;
+ case ERROR_WINHTTP_SECURE_FAILURE:
+ case ERROR_WINHTTP_CANNOT_CONNECT:
+ case ERROR_WINHTTP_CONNECTION_ERROR:
+ default:
+ return Error::Network;
+ }
+}
+
+Result fail_from_last_error(const char* message) {
+ const DWORD error = GetLastError();
+ return {
+ .error = map_winhttp_error(error),
+ .message = std::string(message) + " (" + std::to_string(error) + ")",
+ };
+}
+
+std::string trim_header_value(std::string_view value) {
+ while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) {
+ value.remove_prefix(1);
+ }
+ while (!value.empty() && (value.back() == '\r' || value.back() == '\n' || value.back() == ' ' ||
+ value.back() == '\t'))
+ {
+ value.remove_suffix(1);
+ }
+ return std::string(value);
+}
+
+void parse_headers(std::wstring_view rawHeaders, Response& response) {
+ size_t start = 0;
+ bool firstLine = true;
+ while (start < rawHeaders.size()) {
+ size_t end = rawHeaders.find(L"\r\n", start);
+ if (end == std::wstring_view::npos) {
+ end = rawHeaders.size();
+ }
+
+ const std::wstring_view line = rawHeaders.substr(start, end - start);
+ if (!line.empty() && !firstLine) {
+ const size_t colon = line.find(L':');
+ if (colon != std::wstring_view::npos) {
+ response.headers.push_back({
+ .name = wide_to_utf8(line.substr(0, colon)),
+ .value = trim_header_value(wide_to_utf8(line.substr(colon + 1))),
+ });
+ }
+ }
+ firstLine = false;
+
+ if (end == rawHeaders.size()) {
+ break;
+ }
+ start = end + 2;
+ }
+}
+
+bool read_status(HINTERNET request, Response& response) {
+ DWORD statusCode = 0;
+ DWORD statusCodeSize = sizeof(statusCode);
+ if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
+ WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX))
+ {
+ return false;
+ }
+ response.statusCode = static_cast(statusCode);
+ return true;
+}
+
+bool read_headers(HINTERNET request, Response& response) {
+ DWORD headerBytes = 0;
+ WinHttpQueryHeaders(request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX,
+ WINHTTP_NO_OUTPUT_BUFFER, &headerBytes, WINHTTP_NO_HEADER_INDEX);
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ std::wstring rawHeaders(headerBytes / sizeof(wchar_t), L'\0');
+ if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX,
+ rawHeaders.data(), &headerBytes, WINHTTP_NO_HEADER_INDEX))
+ {
+ return false;
+ }
+ if (!rawHeaders.empty() && rawHeaders.back() == L'\0') {
+ rawHeaders.pop_back();
+ }
+ parse_headers(rawHeaders, response);
+ return true;
+}
+
+} // namespace
+
+bool available() noexcept {
+ return true;
+}
+
+Backend backend() noexcept {
+ return Backend::WinHttp;
+}
+
+const char* backend_name() noexcept {
+ return "WinHTTP";
+}
+
+Result get(const Request& request) {
+ if (request.url.empty()) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "URL is empty",
+ };
+ }
+
+ std::wstring wideUrl = utf8_to_wide(request.url);
+ if (wideUrl.empty()) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "URL is not valid UTF-8",
+ };
+ }
+
+ URL_COMPONENTS components{};
+ components.dwStructSize = sizeof(components);
+ components.dwSchemeLength = static_cast(-1);
+ components.dwHostNameLength = static_cast(-1);
+ components.dwUrlPathLength = static_cast(-1);
+ components.dwExtraInfoLength = static_cast(-1);
+ if (!WinHttpCrackUrl(wideUrl.c_str(), static_cast(wideUrl.size()), 0, &components)) {
+ return fail_from_last_error("Failed to parse URL");
+ }
+ if (components.nScheme != INTERNET_SCHEME_HTTPS) {
+ return {
+ .error = Error::UnsupportedScheme,
+ .message = "Only https:// URLs are supported",
+ };
+ }
+
+ const std::wstring host(components.lpszHostName, components.dwHostNameLength);
+ std::wstring path;
+ if (components.lpszUrlPath != nullptr && components.dwUrlPathLength > 0) {
+ path.assign(components.lpszUrlPath, components.dwUrlPathLength);
+ }
+ if (components.lpszExtraInfo != nullptr && components.dwExtraInfoLength > 0) {
+ path.append(components.lpszExtraInfo, components.dwExtraInfoLength);
+ }
+ if (path.empty()) {
+ path = L"/";
+ }
+
+ WinHttpHandle session(WinHttpOpen(L"Dusk", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
+ WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0));
+ if (session.handle == nullptr) {
+ return fail_from_last_error("Failed to create WinHTTP session");
+ }
+
+ const DWORD timeout = timeout_ms(request.timeout);
+ WinHttpSetTimeouts(session, timeout, timeout, timeout, timeout);
+
+ WinHttpHandle connection(WinHttpConnect(session, host.c_str(), components.nPort, 0));
+ if (connection.handle == nullptr) {
+ return fail_from_last_error("Failed to connect");
+ }
+
+ WinHttpHandle httpRequest(WinHttpOpenRequest(connection, L"GET", path.c_str(), nullptr,
+ WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE));
+ if (httpRequest.handle == nullptr) {
+ return fail_from_last_error("Failed to create request");
+ }
+
+ DWORD redirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP;
+ WinHttpSetOption(
+ httpRequest, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, sizeof(redirectPolicy));
+ DWORD maxRedirects = 5;
+ WinHttpSetOption(httpRequest, WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS, &maxRedirects,
+ sizeof(maxRedirects));
+
+ for (const Header& header : request.headers) {
+ const std::wstring wideHeader = utf8_to_wide(header.name + ": " + header.value);
+ if (wideHeader.empty()) {
+ return {
+ .error = Error::InvalidUrl,
+ .message = "Request header is not valid UTF-8",
+ };
+ }
+ if (!WinHttpAddRequestHeaders(httpRequest, wideHeader.c_str(),
+ static_cast(wideHeader.size()), WINHTTP_ADDREQ_FLAG_ADD))
+ {
+ return fail_from_last_error("Failed to add request header");
+ }
+ }
+
+ if (!WinHttpSendRequest(
+ httpRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
+ {
+ return fail_from_last_error("Failed to send request");
+ }
+ if (!WinHttpReceiveResponse(httpRequest, nullptr)) {
+ return fail_from_last_error("Failed to receive response");
+ }
+
+ Response response;
+ if (!read_status(httpRequest, response)) {
+ return fail_from_last_error("Failed to read response status");
+ }
+ read_headers(httpRequest, response);
+
+ for (;;) {
+ DWORD availableBytes = 0;
+ if (!WinHttpQueryDataAvailable(httpRequest, &availableBytes)) {
+ return fail_from_last_error("Failed to query response body");
+ }
+ if (availableBytes == 0) {
+ break;
+ }
+ if (availableBytes > request.maxBodyBytes ||
+ response.body.size() > request.maxBodyBytes - availableBytes)
+ {
+ return {
+ .error = Error::TooLarge,
+ .message = "Response body exceeded the configured limit",
+ .response = std::move(response),
+ };
+ }
+
+ std::vector buffer(availableBytes);
+ DWORD bytesRead = 0;
+ if (!WinHttpReadData(httpRequest, buffer.data(), availableBytes, &bytesRead)) {
+ return fail_from_last_error("Failed to read response body");
+ }
+ response.body.append(buffer.data(), bytesRead);
+ }
+
+ return {
+ .response = std::move(response),
+ };
+}
+
+} // namespace dusk::http
diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp
index 6bddc6f07c..ce9290efa5 100644
--- a/src/dusk/imgui/ImGuiAudio.cpp
+++ b/src/dusk/imgui/ImGuiAudio.cpp
@@ -282,7 +282,9 @@ static void ShowAllJAISeqs() {
}
void dusk::ImGuiMenuTools::ShowAudioDebug() {
- if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F10, m_showAudioDebug)) {
+ if (!getSettings().backend.enableAdvancedSettings ||
+ !ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F10, m_showAudioDebug))
+ {
return;
}
@@ -328,7 +330,9 @@ void dusk::ImGuiMenuTools::ShowAudioDebug() {
}
void dusk::ImGuiMenuTools::ShowSaveEditor() {
- if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F6, m_showSaveEditor)) {
+ if (!getSettings().backend.enableAdvancedSettings ||
+ !ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F6, m_showSaveEditor))
+ {
return;
}
m_saveEditor.draw(m_showSaveEditor);
diff --git a/src/dusk/imgui/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp
index 2a39ef85eb..0d2168924c 100644
--- a/src/dusk/imgui/ImGuiCameraOverlay.cpp
+++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp
@@ -10,7 +10,9 @@
namespace dusk {
void ImGuiMenuTools::ShowCameraOverlay() {
- if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F9, m_showCameraOverlay)) {
+ if (!getSettings().backend.enableAdvancedSettings ||
+ !ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F9, m_showCameraOverlay))
+ {
return;
}
@@ -61,16 +63,32 @@ namespace dusk {
ImGui::SetTooltip("Cannot enable while paused or during an active event.");
} else {
ImGui::SetTooltip("Detach camera and fly freely.\n"
- "Left stick: move, C-stick: look\n"
- "L/R triggers: up/down, Z: fast");
+ "WASD/Arrows/Left stick: move, Mouse/C-stick: look\n"
+ "Ctrl/L: down, Space/R: up, Shift/Z: fast\n"
+ "Q Key/Y: roll left, R Key/X: roll right");
}
}
if (eventRunning) {
ImGui::EndDisabled();
}
+ if (!getSettings().game.debugFlyCam) {
+ ImGui::BeginDisabled();
+ }
+ config::ImGuiCheckbox("Lock Events", getSettings().game.debugFlyCamLockEvents);
+ if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
+ if (!getSettings().game.debugFlyCam) {
+ ImGui::SetTooltip("Enable Fly Mode first.");
+ } else {
+ ImGui::SetTooltip("Freeze game events while flying.");
+ }
+ }
+ if (!getSettings().game.debugFlyCam) {
+ ImGui::EndDisabled();
+ }
+
ShowCornerContextMenu(m_cameraOverlayCorner, 0);
ImGui::End();
}
-}
\ No newline at end of file
+}
diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp
index 355ef14450..e933e314e4 100644
--- a/src/dusk/imgui/ImGuiConsole.cpp
+++ b/src/dusk/imgui/ImGuiConsole.cpp
@@ -10,11 +10,9 @@
#include "fmt/format.h"
#include "ImGuiConsole.hpp"
-#include "dusk/ui/preset.hpp"
-#include "dusk/ui/ui.hpp"
+#include "ImGuiEngine.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_mouse.h"
-#include "dusk/achievements.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp"
#include "dusk/dusk.h"
@@ -22,6 +20,7 @@
#include "dusk/livesplit.h"
#include "dusk/main.h"
#include "dusk/settings.h"
+#include "dusk/ui/ui.hpp"
#include "f_pc/f_pc_manager.h"
#include "f_pc/f_pc_name.h"
#include "m_Do/m_Do_controller_pad.h"
@@ -254,15 +253,6 @@ namespace dusk {
UpdateSettings();
- AchievementSystem::get().tick();
- while (AchievementSystem::get().hasPendingUnlock()) {
- if (getSettings().game.enableAchievementNotifications) {
- m_menuTools.notifyAchievement(AchievementSystem::get().consumePendingUnlock());
- } else {
- AchievementSystem::get().consumePendingUnlock();
- }
- }
-
if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) &&
(ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
ImGui::IsKeyPressed(ImGuiKey_R))
@@ -271,16 +261,19 @@ namespace dusk {
}
if (ImGui::IsKeyPressed(ImGuiKey_F11)) {
- ImGuiMenuGame::ToggleFullscreen();
+ getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
+ VISetWindowFullscreen(getSettings().video.enableFullscreen);
+ config::Save();
}
- // if (!dusk::IsGameLaunched) {
- // m_preLaunchWindow.draw();
- // }
-
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
- m_isHidden = !m_isHidden;
+ if (getSettings().backend.enableAdvancedSettings) {
+ m_isHidden = !m_isHidden;
+ } else {
+ m_isHidden = true;
+ }
}
+
bool showMenu = !m_isHidden;
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
@@ -290,25 +283,11 @@ namespace dusk {
m_menuGame.draw();
m_menuTools.draw();
- const auto fpsLabel =
- fmt::format(FMT_STRING("FPS: {:.2f}\n"), ImGui::GetIO().Framerate);
- const auto fpsSize =
- ImGui::CalcTextSize(fpsLabel.data(), fpsLabel.data() + fpsLabel.size());
- ImGui::SetCursorPosX(
- ImMax(ImGui::GetCursorPosX(), ImGui::GetWindowWidth() -
- ImGui::GetStyle().DisplaySafeAreaPadding.x -
- fpsSize.x - ImGui::GetStyle().ItemSpacing.x));
- ImGuiStringViewText(fpsLabel);
-
ImGui::EndMainMenuBar();
}
ImGui::PopStyleColor();
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
- AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ?
- "3-finger tap to toggle menu"s :
- "Press F1 to toggle menu"s,
- 4.f);
m_isLaunchInitialized = true;
if (getSettings().game.liveSplitEnabled) {
dusk::speedrun::connectLiveSplit();
@@ -317,8 +296,68 @@ namespace dusk {
UpdateDragScroll();
- m_menuGame.windowControllerConfig();
- m_menuGame.windowInputViewer();
+ // Show message when Aurora backend is Null
+ if (aurora_get_backend() == BACKEND_NULL) {
+ auto& io = ImGui::GetIO();
+ ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y));
+ ImGui::SetNextWindowPos(ImVec2(0, 0));
+ ImGui::SetNextWindowBgAlpha(0.65f);
+ ImGui::Begin("Pre Launch Window", nullptr,
+ ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
+ ImGuiWindowFlags_NoBringToFrontOnFocus);
+ ImGui::NewLine();
+ if (ImGuiEngine::duskLogo) {
+ const auto& windowSize = ImGui::GetWindowSize();
+ ImGui::NewLine();
+ float iconSize = 150.f;
+ float width = iconSize * 2.5f;
+ ImGui::SameLine(windowSize.x / 2 - width + (width / 2));
+ ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize});
+ } else {
+ ImGui::PushFont(ImGuiEngine::fontExtraLarge);
+ ImGuiTextCenter("Dusk");
+ ImGui::PopFont();
+ }
+ ImGui::PushFont(ImGuiEngine::fontLarge);
+ ImGuiTextCenter("Failed to initialize any graphics backend");
+ const auto& style = ImGui::GetStyle();
+ const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)");
+ const auto quitSize = ImGui::CalcTextSize("Quit");
+ float buttonsWidth = quitSize.x + style.FramePadding.x * 2.0f;
+ if constexpr (SupportsProcessRestart) {
+ buttonsWidth += retrySize.x + style.FramePadding.x * 2.0f + style.ItemSpacing.x;
+ }
+#if DUSK_CAN_OPEN_DATA_FOLDER
+ const auto openSize = ImGui::CalcTextSize("Open Data Folder");
+ buttonsWidth += openSize.x + style.FramePadding.x * 2.0f + style.ItemSpacing.x;
+#endif
+ ImGui::NewLine();
+ ImGui::SetCursorPosX(
+ ImMax(style.WindowPadding.x, (ImGui::GetWindowSize().x - buttonsWidth) * 0.5f));
+ if constexpr (SupportsProcessRestart) {
+ if (ImGui::Button("Retry (Auto backend)")) {
+ getSettings().backend.graphicsBackend.setValue("auto");
+ config::Save();
+ RestartRequested = true;
+ IsRunning = false;
+ }
+ ImGui::SameLine();
+ }
+#if DUSK_CAN_OPEN_DATA_FOLDER
+ if (ImGui::Button("Open Data Folder")) {
+ OpenDataFolder();
+ }
+ ImGui::SameLine();
+#endif
+ if (ImGui::Button("Quit")) {
+ IsRunning = false;
+ }
+ ImGui::PopFont();
+ ImGui::End();
+ }
+
+ m_menuTools.ShowInputViewer();
m_menuGame.drawSpeedrunTimerOverlay();
if (getSettings().game.liveSplitEnabled) {
@@ -342,8 +381,6 @@ namespace dusk {
m_menuTools.ShowSaveEditor();
m_menuTools.ShowStateShare();
}
- m_menuTools.showAchievementNotification();
- DuskDebugPad(); // temporary, remove later
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
ImGuiIO& io = ImGui::GetIO();
diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp
index 1755c02856..6715846e7b 100644
--- a/src/dusk/imgui/ImGuiConsole.hpp
+++ b/src/dusk/imgui/ImGuiConsole.hpp
@@ -2,14 +2,16 @@
#define DUSK_IMGUI_HPP
#include
+#include
#include
#include
+#include
#include
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp"
-#include "ImGuiPreLaunchWindow.hpp"
+#include "dusk/main.h"
#include "imgui.h"
union SDL_Event;
@@ -24,7 +26,7 @@ public:
void PreDraw();
void PostDraw();
- static bool CheckMenuViewToggle(ImGuiKey key, bool& active);
+ static bool CheckMenuViewToggle(ImGuiKey key, bool& active);
void AddToast(std::string_view message, float duration = 3.f);
private:
@@ -45,7 +47,6 @@ private:
std::deque m_toasts;
ImGuiMenuGame m_menuGame;
- ImGuiPreLaunchWindow m_preLaunchWindow;
// Keep always last
ImGuiMenuTools m_menuTools;
@@ -72,6 +73,24 @@ bool ImGuiButtonCenter(std::string_view text);
float ImGuiScale();
} // namespace dusk
-void DuskDebugPad();
+#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
+
+namespace fs = std::filesystem;
+
+static void OpenDataFolder() {
+ const std::string path = fs::absolute(dusk::ConfigPath).generic_string();
+#if defined(_WIN32)
+ const std::string url = std::string("file:///") + path;
+#else
+ const std::string url = std::string("file://") + path;
+#endif
+ (void)SDL_OpenURL(url.c_str());
+}
+#else
+#define DUSK_CAN_OPEN_DATA_FOLDER 0
+#endif
#endif // DUSK_IMGUI_HPP
diff --git a/src/dusk/imgui/ImGuiControllerOverlay.cpp b/src/dusk/imgui/ImGuiControllerOverlay.cpp
index dd0a15806c..75637e5e61 100644
--- a/src/dusk/imgui/ImGuiControllerOverlay.cpp
+++ b/src/dusk/imgui/ImGuiControllerOverlay.cpp
@@ -3,12 +3,11 @@
#include "imgui.h"
#include
#include "ImGuiConsole.hpp"
-#include "ImGuiMenuGame.hpp"
#include
namespace dusk {
- void ImGuiMenuGame::windowInputViewer() {
+ void ImGuiMenuTools::ShowInputViewer() {
if (!m_showInputViewer) {
return;
}
diff --git a/src/dusk/imgui/ImGuiDebugPad.cpp b/src/dusk/imgui/ImGuiDebugPad.cpp
deleted file mode 100644
index b1591f1968..0000000000
--- a/src/dusk/imgui/ImGuiDebugPad.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-#include "m_Do/m_Do_controller_pad.h"
-
-#include "imgui.h"
-#include "ImGuiConsole.hpp"
-
-void DuskDebugPad() {
- auto& pad = mDoCPd_c::getCpadInfo(PAD_1);
- auto KeyFlag = [&](ImGuiKey key, u32 padFlag) {
- if (ImGui::IsKeyDown(key))
- pad.mButtonFlags |= padFlag;
- if (ImGui::IsKeyPressed(key))
- pad.mPressedButtonFlags |= padFlag;
-
- };
-
- KeyFlag(ImGuiKey_K, PAD_BUTTON_A);
- KeyFlag(ImGuiKey_J, PAD_BUTTON_B);
- KeyFlag(ImGuiKey_L, PAD_BUTTON_X);
- KeyFlag(ImGuiKey_I, PAD_BUTTON_Y);
- KeyFlag(ImGuiKey_H, PAD_BUTTON_START);
- KeyFlag(ImGuiKey_O, PAD_TRIGGER_Z);
- KeyFlag(ImGuiKey_Keypad8, PAD_BUTTON_UP);
- KeyFlag(ImGuiKey_Keypad2, PAD_BUTTON_DOWN);
- KeyFlag(ImGuiKey_Keypad6, PAD_BUTTON_RIGHT);
- KeyFlag(ImGuiKey_Keypad4, PAD_BUTTON_LEFT);
-
- if (ImGui::IsKeyDown(ImGuiKey_W)) {
- pad.mMainStickPosY = 1.0f;
- pad.mMainStickValue = 1.0f;
- pad.mMainStickAngle = 0x8000;
- }
-
- if (ImGui::IsKeyDown(ImGuiKey_S)) {
- pad.mMainStickPosY = -1.0f;
- pad.mMainStickValue = 1.0f;
- pad.mMainStickAngle = 0;
- }
-
- if (ImGui::IsKeyDown(ImGuiKey_D)) {
- pad.mMainStickPosX = 1.0f;
- pad.mMainStickValue = 1.0f;
- pad.mMainStickAngle = 0x4000;
- }
-
- if (ImGui::IsKeyDown(ImGuiKey_A)) {
- pad.mMainStickPosX = -1.0f;
- pad.mMainStickValue = 1.0f;
- pad.mMainStickAngle = -0x4000;
- }
-
- if (ImGui::IsKeyDown(ImGuiKey_Q)) {
- pad.mTriggerLeft = 1.0;
- pad.mTrigLockL = 1;
- pad.mHoldLockL = 1;
- }
- if (ImGui::IsKeyDown(ImGuiKey_E)) {
- pad.mTriggerRight = 1.0;
- pad.mTrigLockR = 1;
- pad.mHoldLockR = 1;
- }
-}
diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp
index 4b6a7fb531..3e742de853 100644
--- a/src/dusk/imgui/ImGuiEngine.cpp
+++ b/src/dusk/imgui/ImGuiEngine.cpp
@@ -219,7 +219,7 @@ void ImGuiEngine_AddTextures() {
ImGuiEngine::orgIcon = AddTexture("org-icon.png");
}
if (ImGuiEngine::duskLogo == 0) {
- ImGuiEngine::duskLogo = AddTexture("logo.png");
+ ImGuiEngine::duskLogo = AddTexture("logo-mascot.png");
}
}
} // namespace dusk
diff --git a/src/dusk/imgui/ImGuiEventFlags.hpp b/src/dusk/imgui/ImGuiEventFlags.hpp
index 9928690b9b..5c110821f6 100644
--- a/src/dusk/imgui/ImGuiEventFlags.hpp
+++ b/src/dusk/imgui/ImGuiEventFlags.hpp
@@ -4,8 +4,9 @@
#include
#include
#include