Merge remote-tracking branch 'origin/main' into rando/new-item

This commit is contained in:
MelonSpeedruns
2026-05-08 15:38:02 -04:00
135 changed files with 8616 additions and 4856 deletions
+41 -3
View File
@@ -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)
+9 -2
View File
@@ -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).
<br/>
<div align="center">
<a href="https://github.com/encounter/aurora">
<img src="assets/aurora-powered.png" alt="Powered by Aurora" width="800">
</a>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

+66
View File
@@ -0,0 +1,66 @@
<svg width="600" height="600" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<circle cx="150" cy="150" r="105" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="95" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="60" fill="none" stroke="white" stroke-width="4"/>
<circle cx="150" cy="150" r="75" fill="none" stroke="white" stroke-width="4"/>
<defs>
<line id="ray" x1="150" y1="55" x2="150" y2="45"/>
<clipPath id="zigzag-clip">
<circle cx="150" cy="150" r="75"/>
</clipPath>
</defs>
<g stroke="white" stroke-width="3">
<use href="#ray"/>
<use href="#ray" transform="rotate(18 150 150)"/>
<use href="#ray" transform="rotate(36 150 150)"/>
<use href="#ray" transform="rotate(54 150 150)"/>
<use href="#ray" transform="rotate(72 150 150)"/>
<use href="#ray" transform="rotate(90 150 150)"/>
<use href="#ray" transform="rotate(108 150 150)"/>
<use href="#ray" transform="rotate(126 150 150)"/>
<use href="#ray" transform="rotate(144 150 150)"/>
<use href="#ray" transform="rotate(162 150 150)"/>
<use href="#ray" transform="rotate(180 150 150)"/>
<use href="#ray" transform="rotate(198 150 150)"/>
<use href="#ray" transform="rotate(216 150 150)"/>
<use href="#ray" transform="rotate(234 150 150)"/>
<use href="#ray" transform="rotate(252 150 150)"/>
<use href="#ray" transform="rotate(270 150 150)"/>
<use href="#ray" transform="rotate(288 150 150)"/>
<use href="#ray" transform="rotate(306 150 150)"/>
<use href="#ray" transform="rotate(324 150 150)"/>
<use href="#ray" transform="rotate(342 150 150)"/>
</g>
<polygon fill="none" stroke="white" stroke-width="4" opacity="1" clip-path="url(#zigzag-clip)"
points="
126.82,78.67
150,90
173.18,78.67
185.27,101.46
210.68,105.92
207.06,131.46
225,150
207.06,168.54
210.68,194.08
185.27,198.54
173.18,221.33
150,210
126.82,221.33
114.73,198.54
89.32,194.08
92.94,168.54
75,150
92.94,131.46
89.32,105.92
114.73,101.46
"/>
<g fill="none" stroke="white" stroke-width="4">
<polygon points="150,105 130,140 170,140"/>
<polygon points="130,140 110,175 150,175"/>
<polygon points="170,140 150,175 190,175"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+1 -1
+20 -11
View File
@@ -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
)
+6
View File
@@ -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;
+10
View File
@@ -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
-3
View File
@@ -50,8 +50,6 @@ public:
bool hasSignal(const char* key) const;
std::vector<Achievement> getAchievements() const;
bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); }
std::string consumePendingUnlock();
private:
struct Entry {
@@ -68,7 +66,6 @@ private:
std::unordered_set<std::string_view> m_signals;
bool m_loaded = false;
bool m_dirty = false;
std::queue<std::string> m_pendingUnlocks;
};
} // namespace dusk
+14
View File
@@ -1,6 +1,10 @@
#ifndef DUSK_MAIN_H
#define DUSK_MAIN_H
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#include <filesystem>
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
+2
View File
@@ -1,5 +1,7 @@
#pragma once
#include <array>
struct RoomEntry {
u8 roomNo;
std::vector<s16> roomPoints = {};
+1 -137
View File
@@ -2,9 +2,6 @@
#define _SRC_DUSK_MATH_H_
#include <cmath>
#include <array>
#include <limits>
#include <bit>
#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<BaseAndDec64, 32> 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<double>::infinity(), bits.f);
}
// Handle NaN-like
if (exponent == EXPONENT_MASK_F64) {
if (mantissa == 0) {
return sign ? std::numeric_limits<double>::quiet_NaN() : 0.0;
}
return val;
}
// Handle negative inputs
if (sign) {
return std::numeric_limits<double>::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<uint32_t>(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<int64_t>(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<uint32_t>((static_cast<uint64_t>(exponent) | mantissa) >> 37);
uint64_t new_exp =
(static_cast<uint64_t>((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<uint64_t>(entry.base + entry.dec * static_cast<int64_t>(key & 0x7ff));
return c64(new_exp | new_mantissa).f;
}
#include <dolphin/ppc_math.h>
#endif // _SRC_DUSK_MATH_H_
+20 -2
View File
@@ -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<BloomMode> {
@@ -33,6 +39,12 @@ struct ConfigEnumRange<GameLanguage> {
static constexpr auto min = GameLanguage::English;
static constexpr auto max = GameLanguage::Italian;
};
template <>
struct ConfigEnumRange<DiscVerificationState> {
static constexpr auto min = DiscVerificationState::Unknown;
static constexpr auto max = DiscVerificationState::HashMismatch;
};
}
// Persistent user settings
@@ -45,6 +57,8 @@ struct UserSettings {
ConfigVar<bool> enableFullscreen;
ConfigVar<bool> enableVsync;
ConfigVar<bool> lockAspectRatio;
ConfigVar<bool> enableFpsOverlay;
ConfigVar<int> fpsOverlayCorner;
} video;
struct {
@@ -67,7 +81,6 @@ struct UserSettings {
// QoL
ConfigVar<bool> enableQuickTransform;
ConfigVar<bool> hideTvSettingsScreen;
ConfigVar<bool> skipWarningScreen;
ConfigVar<bool> biggerWallets;
ConfigVar<bool> noReturnRupees;
ConfigVar<bool> disableRupeeCutscenes;
@@ -86,7 +99,7 @@ struct UserSettings {
// Preferences
ConfigVar<bool> enableMirrorMode;
ConfigVar<bool> disableMainHUD;
ConfigVar<bool> minimalHUD;
ConfigVar<bool> pauseOnFocusLost;
ConfigVar<bool> enableLinkDollRotation;
ConfigVar<bool> enableAchievementNotifications;
@@ -121,6 +134,7 @@ struct UserSettings {
ConfigVar<bool> invertCameraYAxis;
ConfigVar<float> freeCameraSensitivity;
ConfigVar<bool> debugFlyCam;
ConfigVar<bool> debugFlyCamLockEvents;
// Cheats
ConfigVar<bool> infiniteHearts;
@@ -147,16 +161,20 @@ struct UserSettings {
// Tools
ConfigVar<bool> speedrunMode;
ConfigVar<bool> liveSplitEnabled;
ConfigVar<bool> recordingMode;
} game;
struct {
ConfigVar<std::string> isoPath;
ConfigVar<DiscVerificationState> isoVerification;
ConfigVar<std::string> graphicsBackend;
ConfigVar<bool> skipPreLaunchUI;
ConfigVar<bool> showPipelineCompilation;
ConfigVar<bool> wasPresetChosen;
ConfigVar<bool> enableCrashReporting;
ConfigVar<bool> checkForUpdates;
ConfigVar<int> cardFileType;
ConfigVar<bool> enableAdvancedSettings;
} backend;
};
@@ -1,9 +1,9 @@
#ifndef J3DSTRUCT_H
#define J3DSTRUCT_H
#include <cstring>
#include <gx.h>
#include <mtx.h>
#include <mtx.h>
#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) {
@@ -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) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 B

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 8.7 KiB

+1 -1
View File
@@ -6,4 +6,4 @@ Exec=dusk
Icon=dusk
Terminal=false
Type=Application
Categories=Graphics;3DGraphics;Game
Categories=Game;
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 48 KiB

+248 -119
View File
@@ -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("&#xe5c8;" center center);
}
icon.trophy {
width: 24dp;
height: 24dp;
font-size: 24dp;
decorator: text("&#xe71a;" center center);
}
icon.controller {
width: 24dp;
height: 24dp;
font-size: 24dp;
decorator: text("&#xf135;" center center);
}
icon.warning {
width: 24dp;
height: 24dp;
font-size: 24dp;
decorator: text("&#xe002;" 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);
}
}
+244 -10
View File
@@ -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("&#xe5ca;" center center);
}
@@ -157,24 +253,81 @@ version-info {
decorator: text("&#xe5cd;" center center);
}
#disc-status[status=verifying] icon {
decorator: text("&#xe8b5;" center center);
}
#disc-status[status=mismatch] icon {
decorator: text("&#xe002;" center center);
}
#disc-status[status=unknown] icon {
decorator: text("&#xe887;" center center);
}
#disc-status[status=pending] icon {
decorator: text("&#xe863;" 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("&#xe5c8;" 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;
}
}
+147
View File
@@ -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;
}
+129 -37
View File
@@ -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("&#xe002;" center center);
color: #ffcc00;
}
icon.error {
decorator: text("&#xe000;" center center);
}
icon.verifying {
decorator: text("&#xe8b5;" center center);
}
icon.celebration {
decorator: text("&#xea65;" center center);
}
icon.question-mark {
decorator: text("&#xeb8b;" 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;
}
+16 -20
View File
@@ -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;
}
}
}
+21 -6
View File
@@ -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
}
}
}
+1 -1
View File
@@ -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();
+4
View File
@@ -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() {
+4
View File
@@ -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;
+4
View File
@@ -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;
+5 -1
View File
@@ -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;
+2
View File
@@ -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;
}
+9
View File
@@ -12,6 +12,10 @@
#include "SSystem/SComponent/c_counter.h"
#include <cstring>
#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;
+63 -11
View File
@@ -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<s16>(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);
+73
View File
@@ -11,6 +11,62 @@
#include "JSystem/JGadget/define.h"
#include <cstring>
#include "dusk/logging.h"
#if TARGET_PC
#include "dusk/ui/ui.hpp"
namespace {
static int sJaiSkip = -1;
static JSUList<JAIStream>* get_stream_list() {
return Z2GetSoundMgr()->getStreamMgr()->getStreamList();
}
static int get_stream_count(JSUList<JAIStream>* list) {
int i = 0;
for (JSULink<JAIStream>* 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<JAIStream>* list = get_stream_list();
for (JSULink<JAIStream>* 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<JAIStream>* 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;
+4 -4
View File
@@ -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;
}
+121 -31
View File
@@ -15,13 +15,47 @@
#include <cstring>
#ifdef TARGET_PC
#include <span>
#include <numbers>
#include <array>
constexpr u16 kMapResolutionMultiplier = 4;
constexpr u16 kMapCircleSize = 16 * kMapResolutionMultiplier;
constexpr u16 kMapImageSide = 16 * kMapResolutionMultiplier;
constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide;
typedef std::function<u8(size_t, size_t)> PaintI8Fn;
void paint_i8(std::span<u8> 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<size_t>(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<float>) -
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<float> * 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<std::pair<size_t, const u8*> >({
{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
}
+4 -1
View File
@@ -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(),
+2 -2
View File
@@ -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,
+18
View File
@@ -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))
+24
View File
@@ -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,
+17
View File
@@ -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);
+6 -3
View File
@@ -24,9 +24,10 @@
#include "d/actor/d_a_horse.h"
#include <cstring>
#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
+9
View File
@@ -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);
}
+24
View File
@@ -8,6 +8,10 @@
#include "d/d_pane_class.h"
#include <cstring>
#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);
}
+9
View File
@@ -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);
+9
View File
@@ -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);
+9
View File
@@ -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);
+9
View File
@@ -20,6 +20,10 @@
#include "JSystem/J2DGraph/J2DScreen.h"
#include <cstring>
#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();
+9
View File
@@ -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);
+2 -16
View File
@@ -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;
+12 -1
View File
@@ -40,8 +40,9 @@
#include "JSystem/JKernel/JKRAramArchive.h"
#if TARGET_PC
#include "dusk/autosave.h"
#include "dusk/memory.h"
#include <dusk/autosave.h>
#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();
+31 -8
View File
@@ -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<char[]> buf(new char[ret]);
vsnprintf(buf.get(), ret, msg, list);
buf[ret - 1] = '\0';
return {buf.get()};
return str;
}
void OSReport(const char* fmt, ...) {
+14 -12
View File
@@ -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 <filesystem>
#include <algorithm>
@@ -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<Achievement> AchievementSystem::getAchievements() const {
std::vector<Achievement> 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;
+5 -1
View File
@@ -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;
}
+1
View File
@@ -154,6 +154,7 @@ namespace dusk::config {
template class ConfigImpl<f64>;
template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>;
template class ConfigImpl<dusk::DiscVerificationState>;
template class ConfigImpl<dusk::GameLanguage>;
}
+206
View File
@@ -0,0 +1,206 @@
#include "http.hpp"
#include <curl/curl.h>
#include <algorithm>
#include <mutex>
#include <string_view>
#include <utility>
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<CurlContext*>(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<CurlContext*>(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<std::chrono::milliseconds::rep>(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<int>(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
+59
View File
@@ -0,0 +1,59 @@
#ifndef DUSK_HTTP_HTTP_HPP
#define DUSK_HTTP_HTTP_HPP
#include <chrono>
#include <cstddef>
#include <string>
#include <vector>
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<Header> headers;
std::chrono::milliseconds timeout{10000};
size_t maxBodyBytes = 1024 * 1024;
};
struct Response {
int statusCode = 0;
std::vector<Header> 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
+24
View File
@@ -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
+238
View File
@@ -0,0 +1,238 @@
#include "http.hpp"
#import <Foundation/Foundation.h>
#include <algorithm>
#include <string_view>
#include <utility>
@interface DuskHttpRequestDelegate : NSObject <NSURLSessionDataDelegate, NSURLSessionTaskDelegate>
@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<std::chrono::milliseconds::rep>(1, timeout.count());
return dispatch_time(DISPATCH_TIME_NOW,
static_cast<int64_t>(milliseconds) * static_cast<int64_t>(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<int>(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<const char*>(delegate.data.bytes),
static_cast<size_t>(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
+320
View File
@@ -0,0 +1,320 @@
#include "http.hpp"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#include <winhttp.h>
#include <algorithm>
#include <limits>
#include <string_view>
#include <utility>
#include <vector>
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<int>(value.size()), nullptr, 0);
if (required <= 0) {
return {};
}
std::wstring result(static_cast<size_t>(required), L'\0');
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast<int>(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<int>(value.size()), nullptr, 0, nullptr, nullptr);
if (required <= 0) {
return {};
}
std::string result(static_cast<size_t>(required), '\0');
WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), result.data(),
required, nullptr, nullptr);
return result;
}
DWORD timeout_ms(std::chrono::milliseconds timeout) {
const auto count = std::max<std::chrono::milliseconds::rep>(1, timeout.count());
return static_cast<DWORD>(
std::min<std::chrono::milliseconds::rep>(count, std::numeric_limits<int>::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<int>(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<DWORD>(-1);
components.dwHostNameLength = static_cast<DWORD>(-1);
components.dwUrlPathLength = static_cast<DWORD>(-1);
components.dwExtraInfoLength = static_cast<DWORD>(-1);
if (!WinHttpCrackUrl(wideUrl.c_str(), static_cast<DWORD>(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<DWORD>(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<char> 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
+6 -2
View File
@@ -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);
+22 -4
View File
@@ -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();
}
}
}
+73 -36
View File
@@ -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();
+23 -4
View File
@@ -2,14 +2,16 @@
#define DUSK_IMGUI_HPP
#include <deque>
#include <filesystem>
#include <string>
#include <string_view>
#include <SDL3/SDL_misc.h>
#include <aurora/aurora.h>
#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<Toast> 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
+1 -2
View File
@@ -3,12 +3,11 @@
#include "imgui.h"
#include <imgui_internal.h>
#include "ImGuiConsole.hpp"
#include "ImGuiMenuGame.hpp"
#include <dolphin/pad.h>
namespace dusk {
void ImGuiMenuGame::windowInputViewer() {
void ImGuiMenuTools::ShowInputViewer() {
if (!m_showInputViewer) {
return;
}
-61
View File
@@ -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;
}
}
+1 -1
View File
@@ -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
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -22,7 +22,9 @@ namespace dusk {
void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open);
void ImGuiMenuTools::ShowHeapOverlay() {
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F4, m_showHeapOverlay)) {
if (!getSettings().backend.enableAdvancedSettings ||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F4, m_showHeapOverlay))
{
return;
}
+3 -1
View File
@@ -9,7 +9,9 @@
namespace dusk {
void ImGuiMenuTools::ShowMapLoader() {
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showMapLoader)) {
if (!getSettings().backend.enableAdvancedSettings ||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showMapLoader))
{
return;
}
+2 -558
View File
@@ -1,573 +1,16 @@
#include "fmt/format.h"
#include "imgui.h"
#include "ImGuiEngine.hpp"
#include "ImGuiConsole.hpp"
#include "ImGuiConfig.hpp"
#include "dusk/main.h"
#include "dusk/hotkeys.h"
#include "m_Do/m_Do_main.h"
#include <SDL3/SDL_gamepad.h>
namespace dusk {
void ImGuiMenuGame::ToggleFullscreen() {
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
VISetWindowFullscreen(getSettings().video.enableFullscreen);
config::Save();
}
ImGuiMenuGame::ImGuiMenuGame() {}
void ImGuiMenuGame::draw() {
if (ImGui::BeginMenu("Settings")) {
// TODO: Remove this once Controller Config exists in RmlUi
if (ImGui::Button("Configure Controller")){
m_showControllerConfig = !m_showControllerConfig;
}
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
ImGui::EndMenu();
}
}
static void drawVirtualStick(const char* id, const ImVec2& stick) {
float scale = ImGuiScale();
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10));
ImGui::BeginChild(id, ImVec2(80 * scale, 80 * scale), 0, ImGuiWindowFlags_NoBackground);
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 p = ImGui::GetCursorScreenPos();
float radius = 30.0f * scale;
ImVec2 pos = ImVec2(p.x + radius, p.y + radius);
constexpr ImU32 stickGray = IM_COL32(150, 150, 150, 255);
constexpr ImU32 white = IM_COL32(255, 255, 255, 255);
constexpr ImU32 red = IM_COL32(230, 0, 0, 255);
dl->AddCircleFilled(pos, radius, stickGray, 8);
dl->AddCircleFilled(ImVec2(pos.x + stick.x * (radius), pos.y + -stick.y * (radius)), 3 * scale, red);
ImGui::EndChild();
}
struct SpecificButtonName {
SDL_GamepadType Type;
const char* Name;
};
struct ButtonNames {
SDL_GamepadButton Button;
std::vector<SpecificButtonName> Names;
};
// clang-format off
static const std::vector<ButtonNames> GamepadButtonNames = {
{ SDL_GAMEPAD_BUTTON_LEFT_STICK, {
{SDL_GAMEPAD_TYPE_PS3, "L3"},
{SDL_GAMEPAD_TYPE_PS4, "L3"},
{SDL_GAMEPAD_TYPE_PS5, "L3"},
{SDL_GAMEPAD_TYPE_XBOX360, "Left Stick"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Left Stick"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Control Stick"},
}},
{ SDL_GAMEPAD_BUTTON_RIGHT_STICK, {
{SDL_GAMEPAD_TYPE_PS3, "R3"},
{SDL_GAMEPAD_TYPE_PS4, "R3"},
{SDL_GAMEPAD_TYPE_PS5, "R3"},
{SDL_GAMEPAD_TYPE_XBOX360, "Right Stick"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Right Stick"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "C Stick"},
}},
{ SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, {
{SDL_GAMEPAD_TYPE_PS3, "L1"},
{SDL_GAMEPAD_TYPE_PS4, "L1"},
{SDL_GAMEPAD_TYPE_PS5, "L1"},
{SDL_GAMEPAD_TYPE_XBOX360, "LB"},
{SDL_GAMEPAD_TYPE_XBOXONE, "LB"},
}},
{ SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, {
{SDL_GAMEPAD_TYPE_PS3, "R1"},
{SDL_GAMEPAD_TYPE_PS4, "R1"},
{SDL_GAMEPAD_TYPE_PS5, "R1"},
{SDL_GAMEPAD_TYPE_XBOX360, "RB"},
{SDL_GAMEPAD_TYPE_XBOXONE, "RB"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Z"},
}},
{ SDL_GAMEPAD_BUTTON_BACK, {
{SDL_GAMEPAD_TYPE_PS3, "Select"},
{SDL_GAMEPAD_TYPE_PS4, "Share"},
{SDL_GAMEPAD_TYPE_PS5, "Create"},
{SDL_GAMEPAD_TYPE_XBOX360, "Back"},
{SDL_GAMEPAD_TYPE_XBOXONE, "View"},
}},
{ SDL_GAMEPAD_BUTTON_START, {
{SDL_GAMEPAD_TYPE_PS3, "Start"},
{SDL_GAMEPAD_TYPE_PS4, "Options"},
{SDL_GAMEPAD_TYPE_PS5, "Options"},
{SDL_GAMEPAD_TYPE_XBOX360, "Start"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Menu"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Start/Pause"},
}},
};
// clang-format on
static const char* GetNameForGamepadButton(SDL_Gamepad* gamepad, u32 buttonUntyped) {
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
return "Not bound";
}
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
auto label = SDL_GetGamepadButtonLabel(gamepad, button);
switch (label) {
case SDL_GAMEPAD_BUTTON_LABEL_A:
return "A";
case SDL_GAMEPAD_BUTTON_LABEL_B:
return "B";
case SDL_GAMEPAD_BUTTON_LABEL_X:
return "X";
case SDL_GAMEPAD_BUTTON_LABEL_Y:
return "Y";
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
return "Cross";
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
return "Circle";
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
return "Triangle";
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
return "Square";
default:; // Fall through
}
auto padType = SDL_GetGamepadType(gamepad);
for (const auto& buttonNames : GamepadButtonNames) {
if (buttonNames.Button != button) {
continue;
}
for (const auto& name : buttonNames.Names) {
if (name.Type == padType) {
return name.Name;
}
}
}
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return "D-pad left";
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return "D-pad right";
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return "D-pad up";
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return "D-pad down";
default:
return PADGetNativeButtonName(buttonUntyped);
}
}
void ImGuiMenuGame::windowControllerConfig() {
if (!m_showControllerConfig) {
return;
}
// if pending for a button mapping, check to set new input
if (m_controllerConfig.m_pendingButtonMapping != nullptr) {
s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort);
if (nativeButton != -1) {
m_controllerConfig.m_pendingButtonMapping->nativeButton = nativeButton;
m_controllerConfig.m_pendingButtonMapping = nullptr;
m_controllerConfig.m_pendingPort = -1;
PADBlockInput(false);
PADSerializeMappings();
}
}
// if pending for an axis mapping, check to set new input
if (m_controllerConfig.m_pendingAxisMapping != nullptr) {
auto nativeAxis = PADGetNativeAxisPulled(m_controllerConfig.m_pendingPort);
if (nativeAxis.nativeAxis != -1) {
m_controllerConfig.m_pendingAxisMapping->nativeAxis = nativeAxis;
m_controllerConfig.m_pendingAxisMapping->nativeButton = -1;
m_controllerConfig.m_pendingAxisMapping = nullptr;
m_controllerConfig.m_pendingPort = -1;
PADBlockInput(false);
PADSerializeMappings();
} else {
auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort);
if (nativeButton != -1) {
m_controllerConfig.m_pendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
m_controllerConfig.m_pendingAxisMapping->nativeButton = nativeButton;
m_controllerConfig.m_pendingAxisMapping = nullptr;
m_controllerConfig.m_pendingPort = -1;
PADBlockInput(false);
PADSerializeMappings();
}
}
}
float scale = ImGuiScale();
ImGuiWindowFlags windowFlags =
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize;
// ImGui::SetNextWindowBgAlpha(0.65f);
if (!ImGui::Begin("Controller Config", &m_showControllerConfig, windowFlags)) {
ImGui::End();
return;
}
// port tabs
ImGui::BeginTabBar("##ControllerTabs");
for (int i = PAD_1; i <= PAD_4; i++) {
if (ImGui::BeginTabItem(fmt::format("Port {}", i + 1).c_str())) {
m_controllerConfig.m_selectedPort = i;
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
// if tab is changed while waiting for input, cancel pending
if ((m_controllerConfig.m_pendingButtonMapping != nullptr ||
m_controllerConfig.m_pendingAxisMapping != nullptr) &&
m_controllerConfig.m_pendingPort != m_controllerConfig.m_selectedPort)
{
m_controllerConfig.m_pendingButtonMapping = nullptr;
m_controllerConfig.m_pendingAxisMapping = nullptr;
m_controllerConfig.m_pendingPort = -1;
PADBlockInput(false);
}
// get a list of all available controller's names
std::vector<std::string> controllerList;
controllerList.push_back("None");
for (int i = 0; i < PADCount(); i++) {
// attach index to name for unique name
controllerList.push_back(fmt::format("{}-{}", PADGetNameForControllerIndex(i), i));
}
// get current controller name
const char* tmpName = PADGetName(m_controllerConfig.m_selectedPort);
std::string currentName = "None";
if (tmpName != nullptr) {
currentName = fmt::format("{}-{}", tmpName, PADGetIndexForPort(m_controllerConfig.m_selectedPort));
}
// controller selection combo box
bool changedController = false;
int changedControllerIndex = 0;
ImGui::SetNextItemWidth(400.0f * scale);
if (ImGui::BeginCombo("##ControllerDeviceList", currentName.c_str())) {
for (int i = 0; const auto& name : controllerList) {
if (ImGui::Selectable(name.c_str(), currentName == name)) {
changedControllerIndex = i;
changedController = true;
}
i++;
}
ImGui::EndCombo();
}
// handle controller change
if (changedController) {
if (changedControllerIndex > 0) {
PADSetPortForIndex(changedControllerIndex - 1, m_controllerConfig.m_selectedPort);
}
else if (changedControllerIndex == 0) {
// if "None" selected
PADClearPort(m_controllerConfig.m_selectedPort);
}
PADSerializeMappings();
}
// restore defaults button
ImGui::SameLine();
if (ImGui::Button("Default")) {
PADRestoreDefaultMapping(m_controllerConfig.m_selectedPort);
PADSerializeMappings();
}
// buttons panel
const float uiButtonSize = 40 * scale;
ImVec2 btnSize(110.0f * scale, 30.0f * scale);
ImGuiBeginGroupPanel("Buttons", ImVec2(150 * scale, 20 * scale));
SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(PADGetIndexForPort(m_controllerConfig.m_selectedPort));
u32 buttonCount;
PADButtonMapping* btnMappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount);
if (btnMappingList != nullptr) {
for (int i = 0; i < buttonCount; i++) {
const char* btnName = PADGetButtonName(btnMappingList[i].padButton);
ImVec2 len = ImGui::CalcTextSize(btnName);
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosY(pos.y + len.y / 4);
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
ImGui::Text("%s", btnName);
ImGui::SameLine();
ImGui::SetCursorPosY(pos.y);
std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) {
dispName = fmt::format("Press a Key...##{}", btnName);
} else {
const char* nativeName = GetNameForGamepadButton(gamepad, btnMappingList[i].nativeButton);
if (nativeName == nullptr) {
nativeName = "[unbound]";
}
dispName = fmt::format("{0}##-{1}", nativeName, i);
}
bool pressed = ImGui::Button(dispName.c_str(),
btnSize);
if (pressed) {
m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingButtonMapping = &btnMappingList[i];
PADBlockInput(true);
}
}
}
ImGuiEndGroupPanel();
ImGui::SameLine();
uint32_t axisCount;
PADAxisMapping* axisMappingList = PADGetAxisMappings(m_controllerConfig.m_selectedPort, &axisCount);
ImGuiBeginGroupPanel("Analog Triggers", ImVec2(150 * scale, 20 * scale));
PADAxis triggers[] = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R};
if (axisMappingList != nullptr) {
for (PADAxis trigger : triggers) {
const char* axisName = PADGetAxisName(axisMappingList[trigger].padAxis);
ImVec2 len = ImGui::CalcTextSize(axisName);
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosY(pos.y + len.y / 4);
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
ImGui::Text("%s", axisName);
ImGui::SameLine();
ImGui::SetCursorPosY(pos.y);
std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[trigger]) {
dispName = fmt::format("Press a Key...##{}", axisName);
} else {
dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger);
}
bool pressed = ImGui::Button(dispName.c_str(),
btnSize);
if (pressed) {
m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[trigger];
PADBlockInput(true);
}
}
}
int port = m_controllerConfig.m_selectedPort;
PADDeadZones* deadZones = PADGetDeadZones(port);
if (deadZones != nullptr) {
ImGui::Text("L Threshold");
ImGui::SameLine();
{
float tmp = static_cast<float>(deadZones->leftTriggerActivationZone * 100.f) / 32767.f;
if (ImGui::DragFloat("##LThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->leftTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);
PADSerializeMappings();
}
}
}
if (deadZones != nullptr) {
ImGui::Text("R Threshold");
ImGui::SameLine();
{
float tmp = static_cast<float>(deadZones->rightTriggerActivationZone * 100.f) / 32767.f;
if (ImGui::DragFloat("##RThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->rightTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);
PADSerializeMappings();
}
}
}
ImGuiEndGroupPanel();
ImGui::SameLine();
// main stick panel
ImGuiBeginGroupPanel("Control Stick", ImVec2(150 * scale, 20 * scale));
drawVirtualStick("##mainStick", ImVec2{ mDoCPd_c::getStickX(port), mDoCPd_c::getStickY(port) });
if (axisMappingList != nullptr) {
const PADAxis lStickAxes[] = {PAD_AXIS_LEFT_Y_POS, PAD_AXIS_LEFT_Y_NEG, PAD_AXIS_LEFT_X_NEG, PAD_AXIS_LEFT_X_POS};
for (auto axis : lStickAxes) {
const char* label = PADGetAxisDirectionLabel(axis);
ImVec2 len = ImGui::CalcTextSize(label);
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosY(pos.y + len.y / 4);
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
ImGui::Text("%s", label);
ImGui::SameLine();
ImGui::SetCursorPosY(pos.y);
std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) {
dispName = fmt::format("Press a Key...##{}", label);
} else {
if (axisMappingList[axis].nativeAxis.nativeAxis != -1) {
const char* signStr;
if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) {
signStr = "";
} else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) {
signStr = "+";
} else {
signStr = "-";
}
dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis);
} else {
assert(axisMappingList[axis].nativeButton != -1);
dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis);
}
}
bool pressed = ImGui::Button(dispName.c_str(), btnSize);
if (pressed) {
m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis];
PADBlockInput(true);
}
}
}
if (deadZones != nullptr) {
ImGui::Text("Dead Zone");
{
float tmp = static_cast<float>(deadZones->stickDeadZone * 100.f) / 32767.f;
if (ImGui::DragFloat("##mainDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->stickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);
PADSerializeMappings();
}
}
}
ImGuiEndGroupPanel();
ImGui::SameLine();
// sub stick panel
ImGuiBeginGroupPanel("C Stick", ImVec2(150 * scale, 20 * scale));
drawVirtualStick("##subStick", ImVec2{ mDoCPd_c::getSubStickX(port), mDoCPd_c::getSubStickY(port) });
if (axisMappingList != nullptr) {
const PADAxis rStickAxes[] = {PAD_AXIS_RIGHT_Y_POS, PAD_AXIS_RIGHT_Y_NEG, PAD_AXIS_RIGHT_X_NEG, PAD_AXIS_RIGHT_X_POS};
for (auto axis : rStickAxes) {
const char* label = PADGetAxisDirectionLabel(axisMappingList[axis].padAxis);
ImVec2 len = ImGui::CalcTextSize(label);
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosY(pos.y + len.y / 4);
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
ImGui::Text("%s", label);
ImGui::SameLine();
ImGui::SetCursorPosY(pos.y);
std::string dispName;
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) {
dispName = fmt::format("Press a Key...##sub{}", label);
} else {
if (axisMappingList[axis].nativeAxis.nativeAxis != -1) {
const char* signStr;
if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) {
signStr = "";
} else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) {
signStr = "+";
} else {
signStr = "-";
}
dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis);
} else {
assert(axisMappingList[axis].nativeButton != -1);
dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis);
}
}
bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), btnSize);
if (pressed) {
m_controllerConfig.m_isReading = true;
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis];
PADBlockInput(true);
}
}
}
if (deadZones != nullptr) {
ImGui::Text("Dead Zone");
{
float tmp = static_cast<float>(deadZones->substickDeadZone * 100.f) / 32767.f;
if (ImGui::DragFloat("##subDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->substickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);
PADSerializeMappings();
}
}
}
ImGuiEndGroupPanel();
ImGui::SameLine();
// Options panel
ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, -1));
if (deadZones != nullptr) {
if (ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones)) {
PADSerializeMappings();
}
if (ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers)) {
PADSerializeMappings();
}
}
if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) {
ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1));
u16 low;
u16 high;
(void)PADGetRumbleIntensity(m_controllerConfig.m_selectedPort, &low, &high);
float fLow = (static_cast<float>(low) / 32767.f) * 100.f;
bool changed = ImGui::SliderFloat("Low", &fLow, 0.f, 100.f, "%.0f%%");
float fHigh = (static_cast<float>(high) / 32767.f) * 100.f;
changed |= ImGui::SliderFloat("High", &fHigh, 0.f, 100.f, "%.0f%%");
if (changed) {
PADSetRumbleIntensity(m_controllerConfig.m_selectedPort,
static_cast<u16>((fLow / 100) * 32767),
static_cast<u16>((fHigh / 100) * 32767));
PADSerializeMappings();
}
if (ImGui::Button(fmt::format("{0}...##rumbleTest", m_controllerConfig.m_isRumbling ? "Stop": "Test").c_str(), {-1, 0})) {
PADControlMotor(m_controllerConfig.m_selectedPort, !m_controllerConfig.m_isRumbling ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP_HARD);
m_controllerConfig.m_isRumbling ^= 1;
}
ImGuiEndGroupPanel();
}
ImGuiEndGroupPanel();
ImGui::End();
}
void ImGuiMenuGame::draw() {}
static std::string GetFormattedTime(OSTime ticks) {
OSCalendarTime time;
@@ -602,6 +45,7 @@ namespace dusk {
getSettings().game.enableTurboKeybind.setValue(false);
getSettings().game.debugFlyCam.setValue(false);
getSettings().game.autoSave.setValue(false);
}
SpeedrunInfo m_speedrunInfo;
-20
View File
@@ -46,31 +46,11 @@ namespace dusk {
ImGuiMenuGame();
void draw();
void windowInputViewer();
void windowControllerConfig();
void drawSpeedrunTimerOverlay();
static void ToggleFullscreen();
static void resetForSpeedrunMode();
private:
struct {
int m_selectedPort = 0;
bool m_isReading = false;
PADButtonMapping* m_pendingButtonMapping = nullptr;
PADAxisMapping* m_pendingAxisMapping = nullptr;
int m_pendingPort = -1;
bool m_isRumbling = false;
} m_controllerConfig;
bool m_showControllerConfig = false;
bool m_showInputViewer = false;
bool m_showInputViewerGyro = false;
int m_inputOverlayCorner = 3;
std::string m_controllerName;
bool m_showTimerWindow = false;
};
}
+8 -82
View File
@@ -23,24 +23,6 @@
#include <TargetConditionals.h>
#endif
#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IOS && !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
namespace aurora::gx {
extern bool enableLodBias;
}
@@ -66,6 +48,8 @@ namespace dusk {
ImGui::EndDisabled();
}
ImGui::Separator();
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
#if DUSK_CAN_OPEN_DATA_FOLDER
ImGui::Separator();
@@ -137,7 +121,9 @@ namespace dusk {
}
void ImGuiMenuTools::ShowDebugOverlay() {
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F3, m_showDebugOverlay)) {
if (!getSettings().backend.enableAdvancedSettings ||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F3, m_showDebugOverlay))
{
return;
}
@@ -201,7 +187,9 @@ namespace dusk {
}
void ImGuiMenuTools::ShowPlayerInfo() {
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F5, m_showPlayerInfo)) {
if (!getSettings().backend.enableAdvancedSettings ||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F5, m_showPlayerInfo))
{
return;
}
@@ -267,66 +255,4 @@ namespace dusk {
ImGui::End();
ImGui::PopFont();
}
void ImGuiMenuTools::notifyAchievement(std::string name) {
if (m_notifyTimer <= 0.f) {
m_notifyName = std::move(name);
m_notifyTimer = NOTIFY_DURATION;
} else {
m_notifyQueue.push(std::move(name));
}
}
void ImGuiMenuTools::showAchievementNotification() {
if (!getSettings().game.enableAchievementNotifications.getValue()) {
return;
}
if (m_notifyTimer <= 0.f) {
if (m_notifyQueue.empty()) {
return;
}
m_notifyName = std::move(m_notifyQueue.front());
m_notifyQueue.pop();
m_notifyTimer = NOTIFY_DURATION;
}
m_notifyTimer -= ImGui::GetIO().DeltaTime;
const float alpha = std::min({
m_notifyTimer / NOTIFY_FADE_TIME,
(NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME,
1.0f
});
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float padding = 12.0f;
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding),
ImGuiCond_Always, ImVec2(1.0f, 0.0f)
);
ImGui::SetNextWindowBgAlpha(alpha * 0.92f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f));
constexpr ImGuiWindowFlags flags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
if (ImGui::Begin("##achievement_notify", nullptr, flags)) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha));
ImGui::TextUnformatted("Achievement Unlocked!");
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::TextUnformatted(m_notifyName.c_str());
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
}
}
+5 -7
View File
@@ -27,8 +27,7 @@ namespace dusk {
void ShowAudioDebug();
void ShowSaveEditor();
void ShowStateShare();
void notifyAchievement(std::string name);
void showAchievementNotification();
void ShowInputViewer();
private:
bool m_showDebugOverlay = false;
@@ -69,11 +68,10 @@ namespace dusk {
bool m_showStateShare = false;
ImGuiStateShare m_stateShare;
std::string m_notifyName;
float m_notifyTimer = 0.f;
std::queue<std::string> m_notifyQueue;
static constexpr float NOTIFY_DURATION = 4.0f;
static constexpr float NOTIFY_FADE_TIME = 0.5f;
bool m_showInputViewer = false;
bool m_showInputViewerGyro = false;
int m_inputOverlayCorner = 3;
std::string m_controllerName;
};
}
-282
View File
@@ -1,282 +0,0 @@
#include "imgui.h"
#include "ImGuiConfig.hpp"
#include "ImGuiEngine.hpp"
#include "ImGuiPreLaunchWindow.hpp"
#include "../file_select.hpp"
#include "../iso_validate.hpp"
#include "ImGuiConsole.hpp"
#include "dusk/main.h"
#include "dusk/settings.h"
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_filesystem.h>
#include "aurora/lib/internal.hpp"
#include "aurora/lib/window.hpp"
namespace dusk {
typedef void (ImGuiPreLaunchWindow::*drawFunc)();
drawFunc drawTable[2] = {&ImGuiPreLaunchWindow::drawMainMenu, &ImGuiPreLaunchWindow::drawOptions};
static constexpr std::array<const char*, 5> skLanguageNames = {
"English", "German", "French", "Spanish", "Italian"
};
static constexpr std::array<SDL_DialogFileFilter, 2> skGameDiscFileFilters{{
{"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"},
{"All Files", "*"},
}};
static std::string ShowIsoInvalidError(const iso::ValidationError code) {
using namespace std::literals::string_literals;
switch (code) {
case iso::ValidationError::IOError:
return "Unknown IO error occurred"s;
case iso::ValidationError::InvalidImage:
return "Unable to interpret selected file as a disc image"s;
case iso::ValidationError::WrongGame:
return "Selected disc image is for a different game"s;
case iso::ValidationError::WrongVersion:
return "Selected disc image is for an unsupported version of the game. Only North American GameCube (NTSC/GZ2E01) is supported at this time."s;
case iso::ValidationError::ExecutableMismatch:
return "Selected disc image contains modified executable files."s;
default:
return "Unknown error"s;
}
}
static std::string_view card_type_name(CARDFileType type) {
switch (type) {
case CARD_GCIFOLDER:
return "GCI Folder"sv;
case CARD_RAWIMAGE:
return "Card Image"sv;
default:
return ""sv;
}
}
void fileDialogCallback(void* userdata, const char* path, const char* error) {
auto* self = static_cast<ImGuiPreLaunchWindow*>(userdata);
if (error != nullptr) {
self->m_selectedIsoPath.clear();
self->m_errorString = fmt::format("File dialog error: {}", error);
return;
}
if (path == nullptr) {
self->m_selectedIsoPath.clear();
return;
}
self->m_selectedIsoPath = path;
self->m_isPal = iso::isPal(path);
getSettings().backend.isoPath.setValue(self->m_selectedIsoPath);
config::Save();
}
ImGuiPreLaunchWindow::ImGuiPreLaunchWindow() = default;
bool ImGuiPreLaunchWindow::isSelectedPathValid() const {
#if TARGET_ANDROID
return !m_selectedIsoPath.empty(); // unsure why SDL_GetPathInfo doesnt work here
#else
return !m_selectedIsoPath.empty() && SDL_GetPathInfo(m_selectedIsoPath.c_str(), nullptr);
#endif
}
void ImGuiPreLaunchWindow::draw() {
if (m_IsFirstDraw) {
m_selectedIsoPath = getSettings().backend.isoPath;
m_isPal = !m_selectedIsoPath.empty() && iso::isPal(m_selectedIsoPath.c_str());
m_initialGraphicsBackend = getSettings().backend.graphicsBackend;
m_IsFirstDraw = false;
}
if (isSelectedPathValid() && getSettings().backend.skipPreLaunchUI) {
dusk::IsGameLaunched = true;
return;
}
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);
const auto& windowSize = ImGui::GetWindowSize();
for (int i = 0; i < 5; i++)
ImGui::NewLine();
float iconSize = 150.f;
ImGui::SameLine(windowSize.x / 2 - iconSize + (iconSize / 2));
if (ImGuiEngine::orgIcon != 0) {
ImGui::Image(ImGuiEngine::orgIcon, ImVec2{iconSize, iconSize});
}
ImGuiTextCenter("Twilit Realm presents");
if (ImGuiEngine::duskLogo) {
ImGui::NewLine();
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();
}
(this->*drawTable[m_CurMenu])();
ImGui::End();
}
void ImGuiPreLaunchWindow::drawMainMenu() {
const auto& windowSize = ImGui::GetWindowSize();
ImGui::SetCursorPosY(windowSize.y - 200);
ImGui::PushFont(ImGuiEngine::fontLarge);
if (!isSelectedPathValid()) {
if (!m_errorString.empty()) {
ImGuiTextCenter(m_errorString);
}
if (ImGuiButtonCenter("Select disc image...")) {
ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(),
skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr,
false);
}
} else {
if (ImGuiButtonCenter("Start game")) {
dusk::IsGameLaunched = true;
}
}
if (ImGuiButtonCenter("Options")) {
m_CurMenu = 1;
}
ImGui::PopFont();
}
void ImGuiPreLaunchWindow::drawOptions() {
const auto& windowSize = ImGui::GetWindowSize();
ImGui::NewLine();
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGuiTextCenter("Options");
ImGui::Separator();
ImGui::PopFont();
auto cursorY = ImGui::GetCursorPosY();
float endCursorY = windowSize.y - 100;
float childWidth = windowSize.x - 400;
ImGui::SetCursorPosX(windowSize.x / 2 - (childWidth / 2));
if (ImGui::BeginChild("OptionsChild", ImVec2(childWidth, endCursorY - cursorY),
ImGuiChildFlags_None, ImGuiWindowFlags_NoBackground))
{
if (!m_errorString.empty()) {
ImGuiTextCenter(m_errorString);
}
ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
if (ImGui::Button(m_selectedIsoPath == "" ? "Set" : "Change")) {
ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(),
skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr,
false);
}
if (m_isPal) {
auto selectedLanguage = getSettings().game.language.getValue();
if (ImGui::BeginCombo("Language", skLanguageNames[static_cast<u8>(selectedLanguage)])) {
for (u8 i = 0; i < skLanguageNames.size(); ++i) {
if (ImGui::Selectable(skLanguageNames[i])) {
getSettings().game.language.setValue(static_cast<GameLanguage>(i));
config::Save();
}
}
ImGui::EndCombo();
}
}
AuroraBackend configuredBackend = BACKEND_AUTO;
const std::string& configuredBackendId = getSettings().backend.graphicsBackend;
if (!try_parse_backend(configuredBackendId, configuredBackend)) {
configuredBackend = BACKEND_AUTO;
}
if (ImGui::BeginCombo("Graphics Backend", backend_name(configuredBackend).data())) {
if (ImGui::Selectable("Auto", configuredBackend == BACKEND_AUTO)) {
getSettings().backend.graphicsBackend.setValue("auto");
config::Save();
}
size_t backendCount = 0;
const AuroraBackend* availableBackends = aurora_get_available_backends(&backendCount);
for (size_t i = 0; i < backendCount; ++i) {
const AuroraBackend backend = availableBackends[i];
const bool isSelected = configuredBackend == backend;
if (ImGui::Selectable(backend_name(backend).data(), isSelected)) {
getSettings().backend.graphicsBackend.setValue(
std::string(backend_id(backend)));
config::Save();
}
if (isSelected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
if (configuredBackendId != m_initialGraphicsBackend) {
ImGui::TextDisabled("Restart Required");
}
auto curFileType = (CARDFileType)getSettings().backend.cardFileType.getValue();
if (ImGui::BeginCombo("Save File Type", card_type_name(curFileType).data())) {
if (ImGui::Selectable("GCI Folder", curFileType == CARD_GCIFOLDER)) {
getSettings().backend.cardFileType.setValue(CARD_GCIFOLDER);
config::Save();
}
if (ImGui::Selectable("Card Image", curFileType == CARD_RAWIMAGE)) {
getSettings().backend.cardFileType.setValue(CARD_RAWIMAGE);
config::Save();
}
ImGui::EndCombo();
}
ImGui::EndChild();
}
ImGui::SetCursorPosY(endCursorY);
ImGui::NewLine();
ImGui::Separator();
ImGui::PushFont(ImGuiEngine::fontLarge);
if (ImGuiButtonCenter("Back")) {
m_CurMenu = 0;
}
ImGui::PopFont();
}
} // namespace dusk
-23
View File
@@ -1,23 +0,0 @@
#pragma once
namespace dusk {
class ImGuiPreLaunchWindow {
private:
int m_CurMenu = 0;
bool m_IsFirstDraw = true;
std::string m_initialGraphicsBackend;
bool isSelectedPathValid() const;
public:
ImGuiPreLaunchWindow();
void draw();
void drawMainMenu();
void drawOptions();
std::string m_selectedIsoPath;
std::string m_errorString;
bool m_isPal = false;
};
} // namespace dusk
+3 -1
View File
@@ -126,7 +126,9 @@ namespace dusk {
}
void ImGuiMenuTools::ShowProcessManager() {
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F2, m_showProcessManagement)) {
if (!getSettings().backend.enableAdvancedSettings ||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F2, m_showProcessManagement))
{
return;
}
+79 -126
View File
@@ -13,7 +13,6 @@
#include "d/actor/d_a_player.h"
#include <map>
#include <bit>
namespace dusk {
enum ItemType {
@@ -1355,38 +1354,30 @@ namespace dusk {
// genCommonAreaFlags(membit);
}
template <typename T>
concept FlagIter = requires(T t) {
++t;
--t;
t + 1;
t < t;
{ t->flagID } -> std::convertible_to<u16>;
};
template <typename T>
concept FlagTester = requires(T t, u16 flagID) {
{ t(flagID) } -> std::convertible_to<bool>;
};
static void sortByFlags(FlagIter auto begin, FlagIter auto end, FlagTester auto&& flagTester) {
template <typename FlagIter, typename FlagTester>
requires requires(FlagIter a, FlagTester tester) {
--a; ++a; a < a; *a;
a + 1;
{ tester(*a) } -> std::convertible_to<bool>;
}
static void sortByFlags(FlagIter begin, FlagIter end, FlagTester&& flagTester) {
if (begin == end) return;
FlagIter auto fullEnd = end;
auto fullEnd = end;
// We want to find the location of where we can swap our `On` flags to.
// We're gonna put the `Off` bits first, and the `On` bits last. 0 < 1
// We can achieve this by skipping all the `On` bits at the end.
// backtrack until we find a bit that is off
while (begin < --end && flagTester(end->flagID)) {
while (begin < --end && flagTester(*end)) {
// move the end pointer back while we find on bits
}
// end should now be pointing to a bit that is off
while (begin < end) {
// if there's a flag that's on
if (flagTester(begin->flagID)) {
if (flagTester(*begin)) {
// move it to the end
std::rotate(begin, begin + 1, fullEnd);
// move back the end of where we're checking
@@ -1402,122 +1393,82 @@ namespace dusk {
static void genAreaFlagTable(uint8_t areaIndex, dSv_memBit_c& membit) {
constexpr auto makeMask = [](uint8_t size) -> uint16_t { return (1 << size) - 1; };
constexpr auto getByteIndexFromFlag = [](uint16_t f) -> uint8_t { return f >> 8; };
constexpr auto getBitMaskFromFlag = [](uint16_t f) -> uint8_t { return f & 0xff; };
constexpr auto getValueSize = [getBitMaskFromFlag](uint16_t f) -> uint8_t {
return std::popcount(getBitMaskFromFlag(f));
};
constexpr auto makeEventFlag = [](uint8_t byteIndex, uint8_t bitIndices) -> uint16_t {
return (byteIndex << 8) | bitIndices;
};
const auto eventFlagToAreaFlag = [&](uint16_t areaFlag) -> int {
auto byteInd = getByteIndexFromFlag(areaFlag);
constexpr size_t areaIndexSize = 5;
// if we're looking at 0x580, that would be byte 5, and check if 0x80 is set on that byte
// the event flags are structured differently than area flags
// B is byte index, b is the flag mask to check
// event flags are BBBBBBBB bbbbbbbb
// for area flags, they check bitIndex, not mask, i is index
// also area uses u32 index, not byte index
// area flags are BBBiiiii
// so we need to convert from bit mask to index
// also our byte index has to become a u32 index
// dividing byte index by sizeof(u32) gets us the u32 index
// but in big endian, the first byte is the highest order byte of the u32
// so we skip 24 bytes for the first byte, 16 for the second, etc
// essentially (3 - (x % 4)), reversing the modulus, 0=3, 1=2
auto bitsToSkip = 8 * ((sizeof(u32) - 1) - (byteInd % sizeof(u32)));
return ((byteInd / sizeof(u32)) << areaIndexSize) | ((std::countr_zero(areaFlag) + bitsToSkip) & makeMask(areaIndexSize));
};
constexpr uint8_t validTbox = sizeof(membit.mTbox);
constexpr uint8_t validSwitch = validTbox + sizeof(membit.mSwitch);
constexpr uint8_t validItem = validSwitch + sizeof(membit.mItem);
constexpr uint16_t tboxConvert = 0;
constexpr uint16_t switchConvert = sizeof(membit.mTbox) << 8;
constexpr uint16_t itemConvert = switchConvert + (sizeof(membit.mItem) << 8);
const auto LoadFlag = [&](uint16_t flag) -> bool {
const auto byteIndex = getByteIndexFromFlag(flag);
if (byteIndex < validTbox) {
return membit.isTbox(eventFlagToAreaFlag(flag - tboxConvert));
} else if (byteIndex < validSwitch) {
return membit.isSwitch(eventFlagToAreaFlag(flag - switchConvert));
} else if (byteIndex < validItem) {
return membit.isItem(eventFlagToAreaFlag(flag - itemConvert));
const auto LoadFlag = [&](const EventAreaFlags& flag) -> bool {
switch (flag.flag.type) {
case AreaFlagType::Item: {
return membit.isItem(flag.flag.flagID);
} break;
case AreaFlagType::Switch: {
return membit.isSwitch(flag.flag.flagID);
} break;
case AreaFlagType::Tbox: {
return membit.isTbox(flag.flag.flagID);
} break;
}
return false;
};
const auto SetFlag = [&](uint16_t flag, bool set) -> void {
const auto byteIndex = getByteIndexFromFlag(flag);
const auto SetFlag = [&](const AreaFlagInd& flag, bool set) -> void {
if (set) {
if (byteIndex < validTbox) {
membit.onTbox(eventFlagToAreaFlag(flag - tboxConvert));
} else if (byteIndex < validSwitch) {
membit.onSwitch(eventFlagToAreaFlag(flag - switchConvert));
} else if (byteIndex < validItem) {
membit.onItem(eventFlagToAreaFlag(flag - itemConvert));
switch (flag.type) {
case AreaFlagType::Item: {
membit.onItem(flag.flagID);
} break;
case AreaFlagType::Switch: {
membit.onSwitch(flag.flagID);
} break;
case AreaFlagType::Tbox: {
membit.onTbox(flag.flagID);
} break;
}
} else {
if (byteIndex < validTbox) {
membit.offTbox(eventFlagToAreaFlag(flag - tboxConvert));
} else if (byteIndex < validSwitch) {
membit.offSwitch(eventFlagToAreaFlag(flag - switchConvert));
} else if (byteIndex < validItem) {
membit.offItem(eventFlagToAreaFlag(flag - itemConvert));
switch (flag.type) {
case AreaFlagType::Item: {
membit.offItem(flag.flagID);
} break;
case AreaFlagType::Switch: {
membit.offSwitch(flag.flagID);
} break;
case AreaFlagType::Tbox: {
membit.offTbox(flag.flagID);
} break;
}
}
};
const auto LoadMultiByteFlag = [&](uint16_t flag) -> uint8_t {
const auto bitInds = getBitMaskFromFlag(flag);
const auto byteIndex = getByteIndexFromFlag(flag);
const uint16_t startingMask = std::bit_floor(bitInds);
uint8_t val = 0;
for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
bitIndexMask >>= 1)
{
val <<= 1;
if (LoadFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask))) {
val |= 1;
}
const auto LoadMultiByteFlag = [&](const AreaFlagMultibit& flag) -> uint8_t {
BE(u32)* areaFlags = nullptr;
switch (flag.type) {
case AreaFlagType::Item: {
areaFlags = membit.mItem;
} break;
case AreaFlagType::Switch: {
areaFlags = membit.mSwitch;
} break;
case AreaFlagType::Tbox: {
areaFlags = membit.mTbox;
} break;
}
return val;
assert(areaFlags != nullptr);
return (areaFlags[flag.index] & flag.mask) >> flag.shift;
};
const auto SetMultiByteFlag = [&](uint16_t flag, uint8_t val) -> void {
const auto bitInds = getBitMaskFromFlag(flag);
const auto byteIndex = getByteIndexFromFlag(flag);
const uint16_t startingMask = std::bit_floor(bitInds);
uint16_t valueMask = 1 << (getValueSize(flag) - 1);
for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0;
bitIndexMask >>= 1, valueMask >>= 1)
{
SetFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask), (val & valueMask) != 0);
const auto SetMultiByteFlag = [&](const AreaFlagMultibit& flag, uint8_t val) -> void {
BE(u32)* areaFlags = nullptr;
switch (flag.type) {
case AreaFlagType::Item: {
areaFlags = membit.mItem;
} break;
case AreaFlagType::Switch: {
areaFlags = membit.mSwitch;
} break;
case AreaFlagType::Tbox: {
areaFlags = membit.mTbox;
} break;
}
};
const auto LoadSpreadMultiByte = [&](uint16_t high, uint16_t low) -> uint8_t {
if (low == AREA_FLAG_NONE)
return LoadMultiByteFlag(high);
return (LoadMultiByteFlag(high) << getValueSize(low)) | LoadMultiByteFlag(low);
};
const auto SetSpreadMultiByte = [&](uint16_t high, uint16_t low, uint8_t value) -> void {
if (low == AREA_FLAG_NONE)
return SetMultiByteFlag(high, value);
const auto lowerSize = getValueSize(low);
SetMultiByteFlag(high, value >> lowerSize);
SetMultiByteFlag(low, value & makeMask(lowerSize));
areaFlags[flag.index] &= ~flag.mask;
areaFlags[flag.index] |= (val << flag.shift) & flag.mask;
};
auto iter = imguiAreaFlagLookup.find(areaIndex);
@@ -1569,7 +1520,7 @@ namespace dusk {
case COLUMN_DESC:
return l.description < r.description;
case COLUMN_BIT:
return l.flagID < r.flagID;
return l.GetFlagID() < r.GetFlagID();
}
return false;
};
@@ -1598,9 +1549,9 @@ namespace dusk {
ImGui::TableNextRow();
ImGui::TableNextColumn();
bool flag = LoadFlag(e.flagID);
if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flagID).c_str(), &flag)) {
SetFlag(e.flagID, flag);
bool flag = LoadFlag(e);
if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flag.flagID).c_str(), &flag)) {
SetFlag(e.flag, flag);
}
ImGui::TableNextColumn();
@@ -1612,7 +1563,7 @@ namespace dusk {
}
for (const auto& multiByteFlag : areaFlags.multibyteFlags) {
auto flagValue = LoadSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag);
auto flagValue = LoadMultiByteFlag(multiByteFlag.flag);
const char* currentVal = "UNKNOWN";
@@ -1624,7 +1575,7 @@ namespace dusk {
if (ImGui::BeginCombo(multiByteFlag.name, currentVal)) {
for (const auto& [val, name] : multiByteFlag.enumValues) {
if (ImGui::Selectable(name)) {
SetSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag, val);
SetMultiByteFlag(multiByteFlag.flag, val);
}
}
ImGui::EndCombo();
@@ -1757,7 +1708,9 @@ namespace dusk {
// if we're sorting by flags, do special sort, regular sort is bad for sorting bools
// it can swap values that are the same, and that causes constant reordering
if (column == COLUMN_FLAG) {
const auto testEventFunc = [&event](u16 flag) -> bool { return event.isEventBit(flag); };
const auto testEventFunc = [&event](const duskImguiEventFlagEntry& flag) -> bool {
return event.isEventBit(flag.flagID);
};
if (direction == ImGuiSortDirection_Ascending) {
sortByFlags(std::begin(duskImguiEventFlags),
+3 -1
View File
@@ -417,7 +417,9 @@ void ImGuiStateShare::draw(bool& open) {
}
void ImGuiMenuTools::ShowStateShare() {
if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare)) {
if (!getSettings().backend.enableAdvancedSettings ||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare))
{
return;
}
m_stateShare.draw(m_showStateShare);
+172 -83
View File
@@ -1,49 +1,99 @@
#include "iso_validate.hpp"
#include <SDL3/SDL_iostream.h>
#include <nod.h>
#include <span>
#include <xxhash.h>
#include "SDL3/SDL_iostream.h"
#include <array>
#include <memory>
#include <stdexcept>
#include <string_view>
namespace {
constexpr uint8_t hex_nibble_to_u8(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
throw std::invalid_argument("invalid hex character");
}
constexpr uint64_t parse_u64_hex(std::string_view s) {
if (s.size() != 16)
throw std::invalid_argument("expected 16 hex chars for uint64");
uint64_t value = 0;
for (char c : s) {
value = (value << 4) | hex_nibble_to_u8(c);
}
return value;
}
constexpr XXH128_hash_t parse_xxh128(std::string_view hex) {
if (hex.size() != 32)
throw std::invalid_argument("expected 32 hex chars for XXH128");
return XXH128_hash_t{
.low64 = parse_u64_hex(hex.substr(16, 16)),
.high64 = parse_u64_hex(hex.substr(0, 16)),
};
}
} // namespace
namespace dusk::iso {
constexpr const char* TP_GAME_IDS[] = {
"GZ2E01", // GCN USA
"GZ2P01", // GCN PAL
"GZ2J01", // GCN JPN
"RZDE01", // Wii USA
"RZDP01", // Wii PAL
"RZDJ01", // Wii JPN
"RZDK01", // Wii KOR
enum class Platform : u8 {
GameCube,
Wii,
};
constexpr const char* PAL_GAME_IDS[] = {
"GZ2P01", // GCN PAL
"RZDP01", // Wii PAL
enum class Region : u8 {
NorthAmerica,
Europe,
Japan,
Korea,
};
constexpr const char* SUPPORTED_TP_GAME_IDS[] = {
"GZ2E01", // GCN USA
"GZ2P01", // GCN PAL
struct KnownDisc {
std::string_view id;
Platform platform;
Region region;
bool supported = false;
XXH128_hash_t hash{};
constexpr KnownDisc(std::string_view id, Platform platform, Region region)
: id(id), platform(platform), region(region) {}
constexpr KnownDisc(
std::string_view id, Platform platform, Region region, const std::string_view hash)
: id(id), platform(platform), region(region), supported(true), hash(parse_xxh128(hash)) {}
};
template <size_t N>
constexpr bool matches(const char (&id)[6], const char* const (&valid)[N]) {
for (auto elem : valid) {
if (strncmp(id, elem, 6) == 0) {
return true;
}
constexpr auto KNOWN_DISCS = std::to_array<KnownDisc>({
{"GZ2E01", Platform::GameCube, Region::NorthAmerica, "14e886f08e548a000afde98a3195e788"},
{"GZ2J01", Platform::GameCube, Region::Japan},
{"GZ2P01", Platform::GameCube, Region::Europe, "9ef597588b0035ca9e91b333fa9a8a7e"},
{"RZDE01", Platform::Wii, Region::NorthAmerica},
{"RZDJ01", Platform::Wii, Region::Japan},
{"RZDK01", Platform::Wii, Region::Korea},
{"RZDP01", Platform::Wii, Region::Europe},
});
constexpr const KnownDisc* find_disc(std::string_view id) {
for (const auto& disc : KNOWN_DISCS) {
if (disc.id == id)
return &disc;
}
return false;
return nullptr;
}
struct NodHandleWrapper {
NodHandle* handle;
NodHandleWrapper() : handle(nullptr) {
}
NodHandleWrapper() : handle(nullptr) {}
~NodHandleWrapper() {
if (handle != nullptr) {
nod_free(handle);
@@ -52,7 +102,7 @@ struct NodHandleWrapper {
}
};
static ValidationError convertNodError(NodResult result) {
static ValidationError convert_nod_error(NodResult result) {
switch (result) {
case NOD_RESULT_ERR_IO:
return ValidationError::IOError;
@@ -67,96 +117,135 @@ s64 StreamReadAt(void* user_data, u64 offset, void* out, size_t len) {
if (len == 0) {
return 0;
}
auto io = static_cast<SDL_IOStream*>(user_data);
auto ret = SDL_SeekIO(io, static_cast<s64>(offset), SDL_IO_SEEK_SET);
auto* io = static_cast<SDL_IOStream*>(user_data);
const auto ret = SDL_SeekIO(io, static_cast<s64>(offset), SDL_IO_SEEK_SET);
if (ret < 0) {
return -1;
}
auto read = SDL_ReadIO(io, out, len);
const auto read = SDL_ReadIO(io, out, len);
if (read == 0) {
if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF) {
return 0;
}
return -1;
}
return static_cast<s64>(read);
}
s64 StreamLength(void* user_data) {
auto io = static_cast<SDL_IOStream*>(user_data);
return SDL_GetIOSize(io);
return SDL_GetIOSize(static_cast<SDL_IOStream*>(user_data));
}
void StreamClose(void* user_data) {
auto io = static_cast<SDL_IOStream*>(user_data);
SDL_CloseIO(io);
SDL_CloseIO(static_cast<SDL_IOStream*>(user_data));
}
ValidationError validate(const char* path) {
NodHandleWrapper disc;
ValidationError verify_disc(NodHandle* disc, VerificationStatus& status) {
std::unique_ptr<XXH3_state_t, decltype(&XXH3_freeState)> hashState(
XXH3_createState(), XXH3_freeState);
if (!hashState) {
return ValidationError::Unknown;
}
XXH3_128bits_reset(hashState.get());
while (true) {
if (status.shouldCancel.load(std::memory_order_relaxed)) {
return ValidationError::Canceled;
}
size_t bytesAvail;
const auto buf = nod_buf_read(disc, &bytesAvail);
if (!bytesAvail)
break;
XXH3_128bits_update(hashState.get(), buf, bytesAvail);
status.bytesRead.fetch_add(bytesAvail, std::memory_order_relaxed);
nod_buf_consume(disc, bytesAvail);
}
const auto hash = XXH3_128bits_digest(hashState.get());
if (!XXH128_isEqual(hash, status.knownDisc->hash)) {
return ValidationError::HashMismatch;
}
return ValidationError::Success;
}
ValidationError validate(const char* path, VerificationStatus& status, DiscInfo& info) {
const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) {
return ValidationError::IOError;
}
const NodDiscStream nod_stream {
.user_data = sdlStream,
.read_at = StreamReadAt,
.stream_len = StreamLength,
.close = StreamClose,
};
auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle);
if (disc.handle == nullptr || result != NOD_RESULT_OK) {
return convertNodError(result);
}
NodDiscHeader header{};
result = nod_disc_header(disc.handle, &header);
if (result != NOD_RESULT_OK) {
return convertNodError(result);
}
if (!matches(header.game_id, TP_GAME_IDS)) {
return ValidationError::WrongGame;
}
if (!matches(header.game_id, SUPPORTED_TP_GAME_IDS)) {
return ValidationError::WrongVersion;
}
return ValidationError::Success;
}
bool isPal(const char* path) {
NodHandleWrapper disc;
const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) {
return false;
}
const NodDiscStream nod_stream{
.user_data = sdlStream,
.read_at = StreamReadAt,
.stream_len = StreamLength,
.close = StreamClose,
};
auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle);
if (disc.handle == nullptr || result != NOD_RESULT_OK) {
return convert_nod_error(result);
}
if (nod_disc_open_stream(&nod_stream, nullptr, &disc.handle) != NOD_RESULT_OK || disc.handle == nullptr) {
return false;
status.bytesTotal.store(nod_disc_size(disc.handle), std::memory_order_relaxed);
NodDiscHeader header{};
result = nod_disc_header(disc.handle, &header);
if (result != NOD_RESULT_OK) {
return convert_nod_error(result);
}
const auto knownDisc = find_disc(std::string_view(header.game_id, 6));
if (!knownDisc) {
return ValidationError::WrongGame;
}
status.knownDisc = knownDisc;
info.isPal = knownDisc->region == Region::Europe;
if (!knownDisc->supported) {
return ValidationError::WrongVersion;
}
return verify_disc(disc.handle, status);
}
ValidationError inspect(const char* path, DiscInfo& info) {
const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) {
return ValidationError::IOError;
}
NodHandleWrapper disc;
const NodDiscStream nod_stream{
.user_data = sdlStream,
.read_at = StreamReadAt,
.stream_len = StreamLength,
.close = StreamClose,
};
auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle);
if (disc.handle == nullptr || result != NOD_RESULT_OK) {
return convert_nod_error(result);
}
NodDiscHeader header{};
if (nod_disc_header(disc.handle, &header) != NOD_RESULT_OK) {
return false;
result = nod_disc_header(disc.handle, &header);
if (result != NOD_RESULT_OK) {
return convert_nod_error(result);
}
return matches(header.game_id, PAL_GAME_IDS);
const auto knownDisc = find_disc(std::string_view(header.game_id, 6));
if (!knownDisc) {
return ValidationError::WrongGame;
}
info.isPal = knownDisc->region == Region::Europe;
if (!knownDisc->supported) {
return ValidationError::WrongVersion;
}
return ValidationError::Success;
}
} // namespace dusk::iso
bool isPal(const char* path) {
DiscInfo info{};
return inspect(path, info) == ValidationError::Success && info.isPal;
}
} // namespace dusk::iso
+31 -13
View File
@@ -1,19 +1,37 @@
#ifndef DUSK_ISO_VALIDATE_HPP
#define DUSK_ISO_VALIDATE_HPP
namespace dusk::iso {
enum class ValidationError : u8 {
Success = 0,
IOError,
InvalidImage,
WrongGame,
WrongVersion,
ExecutableMismatch,
Unknown
};
#include <atomic>
ValidationError validate(const char* path);
bool isPal(const char* path);
}
namespace dusk::iso {
struct KnownDisc;
enum class ValidationError : u8 {
Unknown = 0,
IOError,
InvalidImage,
WrongGame,
WrongVersion,
Canceled,
HashMismatch,
Success
};
struct VerificationStatus {
std::atomic_size_t bytesRead = 0;
std::atomic_size_t bytesTotal = 0;
const KnownDisc* knownDisc = nullptr;
std::atomic_bool shouldCancel = false;
};
struct DiscInfo {
bool isPal = false;
};
ValidationError inspect(const char* path, DiscInfo& info);
ValidationError validate(const char* path, VerificationStatus& status, DiscInfo& info);
bool isPal(const char* path);
} // namespace dusk::iso
#endif // DUSK_ISO_VALIDATE_HPP
+110 -7
View File
@@ -5,17 +5,110 @@
#endif
#include <aurora/main.h>
#include "dusk/main.h"
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#if !defined(_WIN32)
#include <unistd.h>
#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif
#endif
int game_main(int argc, char* argv[]);
namespace {
bool RestartProcess(int argc, char* argv[]) {
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
(void)argc;
(void)argv;
return false;
#elif _WIN32
std::wstring commandLine = GetCommandLineW();
STARTUPINFOW startupInfo{};
startupInfo.cb = sizeof(startupInfo);
PROCESS_INFORMATION processInfo{};
if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr,
&startupInfo, &processInfo))
{
fprintf(stderr, "Failed to restart Dusk: CreateProcessW error %lu\n", GetLastError());
return false;
}
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
return true;
#else
std::filesystem::path executablePath;
#if defined(__APPLE__)
uint32_t pathSize = 0;
_NSGetExecutablePath(nullptr, &pathSize);
if (pathSize > 0) {
std::string path(pathSize, '\0');
if (_NSGetExecutablePath(path.data(), &pathSize) == 0) {
path.resize(std::strlen(path.c_str()));
std::error_code ec;
executablePath = std::filesystem::weakly_canonical(path, ec);
if (ec) {
executablePath = path;
}
}
}
#elif defined(__linux__)
std::array<char, 4096> path{};
const ssize_t len = readlink("/proc/self/exe", path.data(), path.size() - 1);
if (len > 0) {
path[static_cast<size_t>(len)] = '\0';
executablePath = path.data();
}
#endif
if (executablePath.empty() && argc > 0 && argv[0] != nullptr && argv[0][0] != '\0') {
std::error_code ec;
executablePath = std::filesystem::absolute(argv[0], ec);
if (ec) {
executablePath = argv[0];
}
}
if (executablePath.empty()) {
fprintf(stderr, "Failed to restart Dusk: unable to resolve executable path\n");
return false;
}
std::vector<std::string> args;
args.reserve(static_cast<size_t>(std::max(argc, 1)));
args.push_back(executablePath.string());
for (int i = 1; i < argc; ++i) {
args.emplace_back(argv[i] != nullptr ? argv[i] : "");
}
std::vector<char*> execArgv;
execArgv.reserve(args.size() + 1);
for (auto& arg : args) {
execArgv.push_back(arg.data());
}
execArgv.push_back(nullptr);
execv(executablePath.c_str(), execArgv.data());
fprintf(stderr, "Failed to restart Dusk: execv failed: %s\n", std::strerror(errno));
return false;
#endif
}
#if _WIN32
bool ShouldShowWindowsConsole(int argc, char* argv[]) {
if (const auto* env = std::getenv("DUSK_CONSOLE")) {
@@ -53,19 +146,25 @@ void WindowsSetupConsole(bool showConsole) {
SetConsoleOutputCP(CP_UTF8);
if (const HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr) {
stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr)
{
DWORD consoleMode = 0;
if (GetConsoleMode(stdoutHandle, &consoleMode)) {
SetConsoleMode(stdoutHandle,
consoleMode | ENABLE_PROCESSED_OUTPUT
| ENABLE_VIRTUAL_TERMINAL_PROCESSING);
consoleMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
}
int DuskMain(int argc, char* argv[]) {
WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv));
return game_main(argc, argv);
const int result = game_main(argc, argv);
if constexpr (dusk::SupportsProcessRestart) {
if (dusk::RestartRequested) {
return RestartProcess(argc, argv) ? 0 : result;
}
}
return result;
}
std::vector<std::string> WideArgsToUtf8(int argc, wchar_t** argv) {
@@ -81,8 +180,8 @@ std::vector<std::string> WideArgsToUtf8(int argc, wchar_t** argv) {
}
std::vector<char> utf8Buffer(static_cast<size_t>(requiredSize));
WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr,
nullptr);
WideCharToMultiByte(
CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr, nullptr);
utf8Args.emplace_back(utf8Buffer.data());
}
@@ -109,7 +208,11 @@ int RunWindowsGuiEntryPoint() {
}
#else
int DuskMain(int argc, char* argv[]) {
return game_main(argc, argv);
const int result = game_main(argc, argv);
if (dusk::RestartRequested && RestartProcess(argc, argv)) {
return 0;
}
return result;
}
#endif
+27 -15
View File
@@ -8,6 +8,8 @@ UserSettings g_userSettings = {
.enableFullscreen {"video.enableFullscreen", false},
.enableVsync {"video.enableVsync", true},
.lockAspectRatio {"video.lockAspectRatio", false},
.enableFpsOverlay {"game.enableFpsOverlay", false},
.fpsOverlayCorner {"game.fpsOverlayCorner", 0},
},
.audio = {
@@ -27,7 +29,6 @@ UserSettings g_userSettings = {
// Quality of Life
.enableQuickTransform {"game.enableQuickTransform", false},
.hideTvSettingsScreen {"game.hideTvSettingsScreen", true},
.skipWarningScreen {"game.skipWarningScreen", true},
.biggerWallets {"game.biggerWallets", false},
.noReturnRupees {"game.noReturnRupees", false},
.disableRupeeCutscenes {"game.disableRupeeCutscenes", false},
@@ -46,9 +47,9 @@ UserSettings g_userSettings = {
// Preferences
.enableMirrorMode {"game.enableMirrorMode", false},
.disableMainHUD {"game.disableMainHUD", false},
.minimalHUD {"game.minimalHUD", false},
.pauseOnFocusLost {"game.pauseOnFocusLost", false},
.enableLinkDollRotation = {"game.enableLinkDollRotation", false},
.enableLinkDollRotation {"game.enableLinkDollRotation", false},
.enableAchievementNotifications {"game.enableAchievementNotifications", true},
// Graphics
@@ -80,18 +81,19 @@ UserSettings g_userSettings = {
.invertCameraYAxis {"game.invertCameraYAxis", false},
.freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f},
.debugFlyCam {"game.debugFlyCam", false},
.debugFlyCamLockEvents {"game.debugFlyCamLockEvents", true},
// Cheats
.infiniteHearts {"game.infiniteHearts", false},
.infiniteArrows{"game.infiniteArrows", false},
.infiniteBombs{"game.infiniteBombs", false},
.infiniteOil{"game.infiniteOil", false},
.infiniteOxygen{"game.infiniteOxygen", false},
.infiniteRupees{"game.infiniteRupees", false},
.infiniteArrows {"game.infiniteArrows", false},
.infiniteBombs {"game.infiniteBombs", false},
.infiniteOil {"game.infiniteOil", false},
.infiniteOxygen {"game.infiniteOxygen", false},
.infiniteRupees {"game.infiniteRupees", false},
.enableIndefiniteItemDrops {"game.enableIndefiniteItemDrops", false},
.moonJump{"game.moonJump", false},
.superClawshot{"game.superClawshot", false},
.alwaysGreatspin{"game.alwaysGreatspin", false},
.moonJump {"game.moonJump", false},
.superClawshot {"game.superClawshot", false},
.alwaysGreatspin {"game.alwaysGreatspin", false},
.enableFastIronBoots {"game.enableFastIronBoots", false},
.canTransformAnywhere {"game.canTransformAnywhere", false},
.fastSpinner {"game.fastSpinner", false},
@@ -105,17 +107,21 @@ UserSettings g_userSettings = {
// Tools
.speedrunMode {"game.speedrunMode", false},
.liveSplitEnabled {"game.liveSplitEnabled", false}
.liveSplitEnabled {"game.liveSplitEnabled", false},
.recordingMode {"game.recordingMode", false}
},
.backend = {
.isoPath {"backend.isoPath", ""},
.isoVerification {"backend.isoVerification", DiscVerificationState::Unknown},
.graphicsBackend {"backend.graphicsBackend", "auto"},
.skipPreLaunchUI {"backend.skipPreLaunchUI", false},
.showPipelineCompilation {"backend.showPipelineCompilation", false},
.wasPresetChosen {"backend.wasPresetChosen", false},
.enableCrashReporting {"backend.enableCrashReporting", true},
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)}
.checkForUpdates {"backend.checkForUpdates", true},
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)},
.enableAdvancedSettings {"backend.enableAdvancedSettings", false},
}
};
@@ -128,6 +134,8 @@ void registerSettings() {
Register(g_userSettings.video.enableFullscreen);
Register(g_userSettings.video.enableVsync);
Register(g_userSettings.video.lockAspectRatio);
Register(g_userSettings.video.enableFpsOverlay);
Register(g_userSettings.video.fpsOverlayCorner);
// Audio
Register(g_userSettings.audio.masterVolume);
@@ -143,7 +151,6 @@ void registerSettings() {
Register(g_userSettings.game.language);
Register(g_userSettings.game.enableQuickTransform);
Register(g_userSettings.game.hideTvSettingsScreen);
Register(g_userSettings.game.skipWarningScreen);
Register(g_userSettings.game.biggerWallets);
Register(g_userSettings.game.noReturnRupees);
Register(g_userSettings.game.disableRupeeCutscenes);
@@ -162,7 +169,7 @@ void registerSettings() {
Register(g_userSettings.game.invertCameraXAxis);
Register(g_userSettings.game.invertCameraYAxis);
Register(g_userSettings.game.freeCameraSensitivity);
Register(g_userSettings.game.disableMainHUD);
Register(g_userSettings.game.minimalHUD);
Register(g_userSettings.game.pauseOnFocusLost);
Register(g_userSettings.game.bloomMode);
Register(g_userSettings.game.bloomMultiplier);
@@ -183,6 +190,7 @@ void registerSettings() {
Register(g_userSettings.game.enableTurboKeybind);
Register(g_userSettings.game.speedrunMode);
Register(g_userSettings.game.liveSplitEnabled);
Register(g_userSettings.game.recordingMode);
Register(g_userSettings.game.fastSpinner);
Register(g_userSettings.game.infiniteHearts);
Register(g_userSettings.game.infiniteArrows);
@@ -206,14 +214,18 @@ void registerSettings() {
Register(g_userSettings.game.gyroInvertYaw);
Register(g_userSettings.game.freeCamera);
Register(g_userSettings.game.debugFlyCam);
Register(g_userSettings.game.debugFlyCamLockEvents);
Register(g_userSettings.backend.isoPath);
Register(g_userSettings.backend.isoVerification);
Register(g_userSettings.backend.graphicsBackend);
Register(g_userSettings.backend.skipPreLaunchUI);
Register(g_userSettings.backend.showPipelineCompilation);
Register(g_userSettings.backend.wasPresetChosen);
Register(g_userSettings.backend.enableCrashReporting);
Register(g_userSettings.backend.checkForUpdates);
Register(g_userSettings.backend.cardFileType);
Register(g_userSettings.backend.enableAdvancedSettings);
}
// Transient settings
+3 -3
View File
@@ -41,7 +41,7 @@ Rml::String build_achievement_info_rml(const Achievement& a) {
if (a.isCounter) {
float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f;
s += fmt::format(
R"(<progressbar value="{:.3f}" class="{}"/>)"
R"(<progress value="{:.3f}" class="{}"/>)"
R"(<span class="achievement-progress">{} / {}</span>)",
fraction,
a.unlocked ? "progress-done" : "progress-ongoing",
@@ -65,7 +65,7 @@ public:
btn.on_nav_command([this, key = std::string(a.key)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (mConfirming) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
mDoAud_seStartMenu(kSoundClick);
AchievementSystem::get().clearOne(key.c_str());
resetConfirm();
} else {
@@ -158,7 +158,7 @@ AchievementsWindow::AchievementsWindow() {
clearAllBtn.on_nav_command([clearAllPtr, confirmingAll](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (*confirmingAll) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
mDoAud_seStartMenu(kSoundClick);
AchievementSystem::get().clearAll();
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
+1 -1
View File
@@ -36,7 +36,7 @@ bool BoolButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) {
const bool newValue = !mGetValue();
mSetValue(newValue);
mDoAud_seStartMenu(newValue ? Z2SE_SY_CURSOR_OK : Z2SE_SY_CURSOR_CANCEL);
mDoAud_seStartMenu(newValue ? kSoundItemEnable : kSoundItemDisable);
return true;
}
return false;
-1
View File
@@ -37,7 +37,6 @@ Button& Button::on_pressed(ButtonCallback callback) {
// TODO: convert this to a FluentComponent method?
on_nav_command([callback = std::move(callback)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
callback();
return true;
}
-10
View File
@@ -59,16 +59,6 @@ void Component::set_disabled(bool value) {
}
}
Rml::Element* Component::append(Rml::Element* parent, const Rml::String& tag) {
if (parent == nullptr) {
return nullptr;
}
auto* doc = parent->GetOwnerDocument();
if (doc == nullptr) {
return nullptr;
}
return parent->AppendChild(doc->CreateElement(tag));
}
void Component::listen(Rml::Element* element, Rml::EventId event,
ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) {
-1
View File
@@ -47,7 +47,6 @@ public:
Rml::Element* root() const { return mRoot; }
protected:
static Rml::Element* append(Rml::Element* parent, const Rml::String& tag);
void clear_children();
Rml::Element* mRoot = nullptr;
+496 -23
View File
@@ -3,10 +3,11 @@
#include "bool_button.hpp"
#include "button.hpp"
#include "pane.hpp"
#include "select_button.hpp"
#include "number_button.hpp"
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_mouse.h>
#include <fmt/format.h>
#include <array>
@@ -17,9 +18,17 @@
namespace dusk::ui {
namespace {
bool keyboard_active(int port) {
u32 count = 0;
return PADGetKeyButtonBindings(static_cast<u32>(port), &count) != nullptr;
}
Rml::String current_controller_name(int port) {
const char* name = PADGetName(port);
return name == nullptr ? "None" : name;
if (name != nullptr) {
return name;
}
return keyboard_active(port) ? "Keyboard" : "None";
}
Rml::String controller_index_name(u32 index) {
@@ -202,6 +211,86 @@ bool keyboard_escape_pressed() {
return keys != nullptr && SDL_SCANCODE_ESCAPE < keyCount && keys[SDL_SCANCODE_ESCAPE];
}
Rml::String keyboard_key_name(s32 scancode) {
if (scancode == PAD_KEY_INVALID) {
return "Not bound";
}
switch (scancode) {
case PAD_KEY_MOUSE_LEFT:
return "Mouse Left";
case PAD_KEY_MOUSE_MIDDLE:
return "Mouse Middle";
case PAD_KEY_MOUSE_RIGHT:
return "Mouse Right";
case PAD_KEY_MOUSE_X1:
return "Mouse X1";
case PAD_KEY_MOUSE_X2:
return "Mouse X2";
default:
break;
}
if (scancode < 0) {
return "Unknown";
}
const char* name = SDL_GetScancodeName(static_cast<SDL_Scancode>(scancode));
if (name == nullptr || name[0] == '\0') {
return "Unknown";
}
return name;
}
bool keyboard_neutral() {
int keyCount = 0;
const bool* keys = SDL_GetKeyboardState(&keyCount);
if (keys != nullptr) {
for (int i = 0; i < keyCount; ++i) {
if (keys[i]) {
return false;
}
}
}
float x, y;
if (SDL_GetMouseState(&x, &y) != 0) {
return false;
}
return true;
}
s32 keyboard_key_pressed() {
int keyCount = 0;
const bool* keys = SDL_GetKeyboardState(&keyCount);
if (keys != nullptr) {
for (int i = 1; i < keyCount; ++i) {
if (i == SDL_SCANCODE_ESCAPE) {
continue;
}
if (keys[i]) {
return static_cast<s32>(i);
}
}
}
float x, y;
const auto mouseButtons = SDL_GetMouseState(&x, &y);
for (int btn = 1; btn <= 5; ++btn) {
if (mouseButtons & (1u << (btn - 1))) {
return -(btn + 1); // maps to PAD_KEY_MOUSE_LEFT (-2), etc.
}
}
return PAD_KEY_INVALID;
}
u16 percent_to_raw(int percent) {
return static_cast<u16>((static_cast<float>(percent) / 100.f) * 32767.f);
}
int deadzone_raw_to_percent(u16 raw) {
return static_cast<int>((static_cast<float>(raw) * 100.f) / 32767.f + 0.5f);
}
int rumble_raw_to_percent(u16 raw) {
return static_cast<int>((static_cast<float>(raw) / 32767.f) * 100.f + 0.5f);
}
} // namespace
ControllerConfigWindow::ControllerConfigWindow() {
@@ -231,6 +320,7 @@ ControllerConfigWindow::ControllerConfigWindow() {
}
void ControllerConfigWindow::hide(bool close) {
stop_rumble_test();
cancel_pending_binding();
Window::hide(close);
}
@@ -241,16 +331,18 @@ void ControllerConfigWindow::update() {
}
void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
stop_rumble_test();
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
mRightPane = &rightPane;
mActivePort = port;
auto addPageButton = [this, &leftPane, &rightPane, port](
Page page, Rml::String key, auto getValue) {
Page page, Rml::String key, auto getValue, auto isDisabled) {
leftPane.register_control(leftPane.add_select_button({
.key = std::move(key),
.getValue = std::move(getValue),
.isDisabled = std::move(isDisabled),
}),
rightPane, [this, port, page](Pane& pane) {
mPage = page;
@@ -258,10 +350,11 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
});
};
addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); });
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); });
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); });
addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); }, [] { return false; });
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Rumble, "Rumble", [] { return Rml::String(">"); }, [port] { return !PADSupportsRumbleIntensity(static_cast<u32>(port)); });
leftPane.add_section("Options");
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
@@ -311,22 +404,38 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
switch (page) {
case Page::Controller: {
pane.add_button(
{
.text = "None",
.isSelected =
[port] { return PADGetIndexForPort(port) < 0 && !keyboard_active(port); },
})
.on_pressed([this, port] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSerializeMappings();
});
pane.add_button({
.text = "Keyboard",
.isSelected = [port] { return keyboard_active(port); },
})
.on_pressed([this, port] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), TRUE);
PADSerializeMappings();
});
const u32 controllerCount = PADCount();
if (controllerCount == 0) {
pane.add_text("No controllers detected");
break;
}
pane.add_button({
.text = "None",
.isSelected = [port] { return PADGetIndexForPort(port) < 0; },
})
.on_pressed([this, port] {
cancel_pending_binding();
PADClearPort(port);
PADSerializeMappings();
});
for (u32 i = 0; i < controllerCount; ++i) {
pane.add_button(
{
@@ -335,7 +444,9 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
[port, i] { return PADGetIndexForPort(port) == static_cast<s32>(i); },
})
.on_pressed([this, port, i] {
mDoAud_seStartMenu(kSoundItemChange);
cancel_pending_binding();
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSetPortForIndex(i, port);
PADSerializeMappings();
});
@@ -343,6 +454,54 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
break;
}
case Page::Buttons: {
if (keyboard_active(port)) {
auto addKeyButton = [&](PADButton button) {
pane.add_select_button(
{
.key = PADGetButtonName(button),
.getValue =
[this, port, button] {
if (mPendingKeyButton == static_cast<int>(button)) {
return pending_key_label();
}
u32 count = 0;
PADKeyButtonBinding* bindings =
PADGetKeyButtonBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_BUTTON_COUNT; ++i) {
if (bindings[i].padButton == button) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, button] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyButton = static_cast<int>(button);
});
};
pane.add_section("Buttons");
addKeyButton(PAD_BUTTON_A);
addKeyButton(PAD_BUTTON_B);
addKeyButton(PAD_BUTTON_X);
addKeyButton(PAD_BUTTON_Y);
addKeyButton(PAD_BUTTON_START);
addKeyButton(PAD_TRIGGER_Z);
pane.add_section("D-Pad");
addKeyButton(PAD_BUTTON_UP);
addKeyButton(PAD_BUTTON_DOWN);
addKeyButton(PAD_BUTTON_LEFT);
addKeyButton(PAD_BUTTON_RIGHT);
break;
}
u32 buttonCount = 0;
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
if (mappings == nullptr) {
@@ -405,6 +564,79 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
break;
}
case Page::Triggers: {
if (keyboard_active(port)) {
auto addKeyButton = [&](PADButton button) {
pane.add_select_button(
{
.key = PADGetButtonName(button),
.getValue =
[this, port, button] {
if (mPendingKeyButton == static_cast<int>(button)) {
return pending_key_label();
}
u32 count = 0;
PADKeyButtonBinding* bindings =
PADGetKeyButtonBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_BUTTON_COUNT; ++i) {
if (bindings[i].padButton == button) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, button] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyButton = static_cast<int>(button);
});
};
auto addKeyAxis = [&](PADAxis axis) {
pane.add_select_button(
{
.key = PADGetAxisName(axis),
.getValue =
[this, port, axis] {
if (mPendingKeyAxis == static_cast<int>(axis)) {
return pending_key_label();
}
u32 count = 0;
PADKeyAxisBinding* bindings =
PADGetKeyAxisBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_AXIS_COUNT; ++i) {
if (bindings[i].padAxis == axis) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, axis] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyAxis = static_cast<int>(axis);
});
};
pane.add_section("Analog");
addKeyAxis(PAD_AXIS_TRIGGER_L);
addKeyAxis(PAD_AXIS_TRIGGER_R);
pane.add_section("Digital");
addKeyButton(PAD_TRIGGER_L);
addKeyButton(PAD_TRIGGER_R);
break;
}
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
u32 buttonCount = 0;
@@ -468,9 +700,87 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
});
}
}
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
pane.add_section("Emulated Trigger Thresholds");
pane.add_child<NumberButton>(NumberButton::Props{
.key = "L Threshold",
.getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->leftTriggerActivationZone); },
.setValue =
[deadZones](int value) {
deadZones->leftTriggerActivationZone = percent_to_raw(value);
PADSerializeMappings();
},
.isDisabled = [deadZones] { return !deadZones->emulateTriggers; },
.min = 0,
.max = 100,
.step = 1,
.suffix = "%",
});
pane.add_child<NumberButton>(NumberButton::Props{
.key = "R Threshold",
.getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->rightTriggerActivationZone); },
.setValue =
[deadZones](int value) {
deadZones->rightTriggerActivationZone = percent_to_raw(value);
PADSerializeMappings();
},
.isDisabled = [deadZones] { return !deadZones->emulateTriggers; },
.min = 0,
.max = 100,
.step = 1,
.suffix = "%",
});
}
break;
}
case Page::Sticks: {
if (keyboard_active(port)) {
auto addKeyAxis = [&](PADAxis axis) {
pane.add_select_button(
{
.key = PADGetAxisDirectionLabel(axis),
.getValue =
[this, port, axis] {
if (mPendingKeyAxis == static_cast<int>(axis)) {
return pending_key_label();
}
u32 count = 0;
PADKeyAxisBinding* bindings =
PADGetKeyAxisBindings(static_cast<u32>(port), &count);
if (bindings == nullptr) {
return Rml::String("Not bound");
}
for (u32 i = 0; i < PAD_AXIS_COUNT; ++i) {
if (bindings[i].padAxis == axis) {
return keyboard_key_name(bindings[i].scancode);
}
}
return Rml::String("Not bound");
},
})
.on_pressed([this, port, axis] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingKeyAxis = static_cast<int>(axis);
});
};
pane.add_section("Control Stick");
addKeyAxis(PAD_AXIS_LEFT_Y_POS);
addKeyAxis(PAD_AXIS_LEFT_Y_NEG);
addKeyAxis(PAD_AXIS_LEFT_X_NEG);
addKeyAxis(PAD_AXIS_LEFT_X_POS);
pane.add_section("C Stick");
addKeyAxis(PAD_AXIS_RIGHT_Y_POS);
addKeyAxis(PAD_AXIS_RIGHT_Y_NEG);
addKeyAxis(PAD_AXIS_RIGHT_X_NEG);
addKeyAxis(PAD_AXIS_RIGHT_X_POS);
break;
}
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes == nullptr) {
@@ -507,12 +817,121 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
addAxis(PAD_AXIS_LEFT_Y_NEG);
addAxis(PAD_AXIS_LEFT_X_NEG);
addAxis(PAD_AXIS_LEFT_X_POS);
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
pane.add_child<NumberButton>(NumberButton::Props{
.key = "Deadzone",
.getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->stickDeadZone); },
.setValue =
[deadZones](int value) {
deadZones->stickDeadZone = percent_to_raw(value);
PADSerializeMappings();
},
.isDisabled = [deadZones] { return !deadZones->useDeadzones; },
.min = 0,
.max = 100,
.step = 1,
.suffix = "%",
});
}
pane.add_section("C Stick");
addAxis(PAD_AXIS_RIGHT_Y_POS);
addAxis(PAD_AXIS_RIGHT_Y_NEG);
addAxis(PAD_AXIS_RIGHT_X_NEG);
addAxis(PAD_AXIS_RIGHT_X_POS);
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
pane.add_child<NumberButton>(NumberButton::Props{
.key = "Deadzone",
.getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->substickDeadZone); },
.setValue =
[deadZones](int value) {
deadZones->substickDeadZone = percent_to_raw(value);
PADSerializeMappings();
},
.isDisabled = [deadZones] { return !deadZones->useDeadzones; },
.min = 0,
.max = 100,
.step = 1,
.suffix = "%",
});
}
break;
}
case Page::Rumble: {
auto& rumbleTest = pane.add_select_button({
.key = "Test Rumble",
.getValue =
[this, port] {
return (mRumbleTestActive && mRumbleTestPort == port) ? Rml::String("Stop")
: Rml::String("Start");
},
});
rumbleTest.on_pressed([this, port] {
if (!PADSupportsRumbleIntensity(static_cast<u32>(port))) {
return;
}
mDoAud_seStartMenu(kSoundItemChange);
if (mRumbleTestActive && mRumbleTestPort == port) {
PADControlMotor(port, PAD_MOTOR_STOP_HARD);
mRumbleTestActive = false;
mRumbleTestPort = -1;
} else {
if (mRumbleTestActive) {
PADControlMotor(mRumbleTestPort, PAD_MOTOR_STOP_HARD);
}
PADControlMotor(port, PAD_MOTOR_RUMBLE);
mRumbleTestActive = true;
mRumbleTestPort = port;
}
});
pane.add_child<NumberButton>(NumberButton::Props{
.key = "Low Rumble Frequency",
.getValue =
[port] {
u16 low = 0;
u16 high = 0;
PADGetRumbleIntensity(static_cast<u32>(port), &low, &high);
return rumble_raw_to_percent(low);
},
.setValue =
[port](int value) {
u16 low = 0;
u16 high = 0;
PADGetRumbleIntensity(static_cast<u32>(port), &low, &high);
PADSetRumbleIntensity(static_cast<u32>(port), percent_to_raw(value), high);
PADSerializeMappings();
},
.isDisabled = [this] { return mRumbleTestActive; },
.min = 0,
.max = 100,
.step = 1,
.suffix = "%",
});
pane.add_child<NumberButton>(NumberButton::Props{
.key = "High Rumble Frequency",
.getValue =
[port] {
u16 low = 0;
u16 high = 0;
PADGetRumbleIntensity(static_cast<u32>(port), &low, &high);
return rumble_raw_to_percent(high);
},
.setValue =
[port](int value) {
u16 low = 0;
u16 high = 0;
PADGetRumbleIntensity(static_cast<u32>(port), &low, &high);
PADSetRumbleIntensity(static_cast<u32>(port), low, percent_to_raw(value));
PADSerializeMappings();
},
.isDisabled = [this] { return mRumbleTestActive; },
.min = 0,
.max = 100,
.step = 1,
.suffix = "%",
});
pane.add_text("Configure your desired rumble intensities, then run a test to check how they feel.");
break;
}
}
@@ -547,6 +966,21 @@ void ControllerConfigWindow::poll_pending_binding() {
return;
}
if (mPendingKeyButton >= 0 || mPendingKeyAxis >= 0) {
const s32 scancode = keyboard_key_pressed();
if (scancode != PAD_KEY_INVALID) {
if (mPendingKeyButton >= 0) {
PADSetKeyButtonBinding(static_cast<u32>(mPendingPort),
{scancode, static_cast<PADButton>(mPendingKeyButton)});
} else {
PADSetKeyAxisBinding(static_cast<u32>(mPendingPort),
{scancode, static_cast<PADAxis>(mPendingKeyAxis), 0});
}
finish_pending_key_binding();
}
return;
}
if (mPendingButtonMapping != nullptr) {
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
if (nativeButton != -1) {
@@ -588,26 +1022,40 @@ void ControllerConfigWindow::finish_pending_binding(int completedPort) {
}
void ControllerConfigWindow::unmap_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr) {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
const int completedPort = mPendingPort;
if (mPendingButtonMapping != nullptr) {
mPendingButtonMapping->nativeButton = PAD_NATIVE_BUTTON_INVALID;
}
if (mPendingAxisMapping != nullptr) {
finish_pending_binding(completedPort);
} else if (mPendingAxisMapping != nullptr) {
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
mPendingAxisMapping->nativeButton = -1;
finish_pending_binding(completedPort);
} else if (mPendingKeyButton >= 0) {
PADSetKeyButtonBinding(static_cast<u32>(completedPort),
{PAD_KEY_INVALID, static_cast<PADButton>(mPendingKeyButton)});
finish_pending_key_binding();
} else if (mPendingKeyAxis >= 0) {
PADSetKeyAxisBinding(static_cast<u32>(completedPort),
{PAD_KEY_INVALID, static_cast<PADAxis>(mPendingKeyAxis), 0});
finish_pending_key_binding();
}
finish_pending_binding(completedPort);
}
bool ControllerConfigWindow::capture_active() const {
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr;
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr ||
mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
}
bool ControllerConfigWindow::pending_input_neutral() const {
if (mPendingKeyButton >= 0 || mPendingKeyAxis >= 0) {
return keyboard_neutral();
}
return input_neutral(mPendingPort);
}
@@ -621,16 +1069,41 @@ Rml::String ControllerConfigWindow::pending_axis_label() const {
void ControllerConfigWindow::cancel_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
!mSuppressNavigationUntilNeutral)
!mSuppressNavigationUntilNeutral && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingKeyButton = -1;
mPendingKeyAxis = -1;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = false;
mSuppressNavigationPort = -1;
}
void ControllerConfigWindow::finish_pending_key_binding() {
mPendingKeyButton = -1;
mPendingKeyAxis = -1;
mPendingPort = -1;
mPendingBindingArmed = false;
PADSerializeMappings();
}
Rml::String ControllerConfigWindow::pending_key_label() const {
return mPendingBindingArmed ? "Press a key or mouse button..." : "Waiting...";
}
void ControllerConfigWindow::stop_rumble_test() {
if (!mRumbleTestActive) {
return;
}
if (mRumbleTestPort >= PAD_CHAN0 && mRumbleTestPort < PAD_CHANMAX) {
PADControlMotor(mRumbleTestPort, PAD_MOTOR_STOP_HARD);
}
mRumbleTestActive = false;
mRumbleTestPort = -1;
}
} // namespace dusk::ui
+8
View File
@@ -19,6 +19,7 @@ private:
Buttons,
Triggers,
Sticks,
Rumble,
};
void build_port_tab(Rml::Element* content, int port);
@@ -32,6 +33,9 @@ private:
Rml::String pending_button_label() const;
Rml::String pending_axis_label() const;
void cancel_pending_binding();
void finish_pending_key_binding();
Rml::String pending_key_label() const;
void stop_rumble_test();
Page mPage = Page::Controller;
Pane* mRightPane = nullptr;
@@ -42,6 +46,10 @@ private:
int mSuppressNavigationPort = -1;
PADButtonMapping* mPendingButtonMapping = nullptr;
PADAxisMapping* mPendingAxisMapping = nullptr;
int mPendingKeyButton = -1;
int mPendingKeyAxis = -1;
bool mRumbleTestActive = false;
int mRumbleTestPort = -1;
};
} // namespace dusk::ui

Some files were not shown because too many files have changed in this diff Show More