diff --git a/CMakeLists.txt b/CMakeLists.txt index 569654b6a1..dfd89a2a50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,10 @@ option(DUSK_BUILD_WARNINGS "Enable compiler warnings (off by default)") option(DUSK_SELECTED_OPT "If on, selected parts of the project will be compiled with optimizations on Debug, intending to make the game run at 30 FPS. Note for MSVC: you will need to remove '/RTC1' from your debug flags in CMake.") option(DUSK_MOVIE_SUPPORT "If on, compile against libjpeg-turbo to enable THP file decoding" ON) +option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF) +set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN") +set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment") + if (DUSK_MOVIE_SUPPORT) find_package(libjpeg-turbo QUIET) if (libjpeg-turbo_FOUND) @@ -148,21 +152,23 @@ elseif (APPLE) set(CMAKE_INSTALL_RPATH "$ORIGIN") set(CMAKE_BUILD_RPATH "$ORIGIN") elseif (MSVC) - add_compile_options(/bigobj) - add_compile_options(/Zc:strictStrings-) - add_compile_options(/MP) - add_compile_options(/FS) + add_compile_options( + $<$:/bigobj> + $<$:/Zc:strictStrings-> + $<$:/MP> + $<$:/FS> + ) if (NOT DUSK_BUILD_WARNINGS) - add_compile_options(/W0) + add_compile_options($<$:/W0>) else () # Disable warnings - add_compile_options(/wd4068) # unknown pragma - add_compile_options(/wd4291) # no matching delete operator, leaks if exception thrown + add_compile_options($<$:/wd4068>) # unknown pragma + add_compile_options($<$:/wd4291>) # no matching delete operator, leaks if exception thrown # Only show warnings once - add_compile_options(/wo4244) # narrowing conversion, possible data loss + add_compile_options($<$:/wo4244>) # narrowing conversion, possible data loss endif () - add_compile_options(/utf-8) + add_compile_options($<$:/utf-8>) endif () @@ -183,6 +189,46 @@ FetchContent_Declare(json ) FetchContent_MakeAvailable(cxxopts json) +if (DUSK_ENABLE_SENTRY_NATIVE) + message(STATUS "dusk: Fetching sentry-native") + set(SENTRY_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(SENTRY_BACKEND crashpad CACHE STRING "" FORCE) + if (WIN32) + set(SENTRY_TRANSPORT winhttp CACHE STRING "" FORCE) + endif () + set(SENTRY_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(SENTRY_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(SENTRY_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE) + FetchContent_Declare(sentry_native + GIT_REPOSITORY https://github.com/getsentry/sentry-native.git + GIT_TAG 0.13.6 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + GIT_SUBMODULES_RECURSE TRUE + ) + if (NOT sentry_native_POPULATED) + FetchContent_Populate(sentry_native) + set(_dusk_skip_install_rules ${CMAKE_SKIP_INSTALL_RULES}) + set(CMAKE_SKIP_INSTALL_RULES ON) + add_subdirectory(${sentry_native_SOURCE_DIR} ${sentry_native_BINARY_DIR} EXCLUDE_FROM_ALL) + set(CMAKE_SKIP_INSTALL_RULES ${_dusk_skip_install_rules}) + endif () +endif () + +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + set(PLATFORM_NAME win32) +elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin) + if (IOS) + set(PLATFORM_NAME ios) + elseif (TVOS) + set(PLATFORM_NAME tvos) + else () + set(PLATFORM_NAME macos) + endif () +else () + string(TOLOWER CMAKE_SYSTEM_NAME PLATFORM_NAME) +endif () + configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h) include(files.cmake) @@ -191,6 +237,10 @@ include(files.cmake) set(DUSK_BUNDLE_NAME Dusk) set(DUSK_BUNDLE_IDENTIFIER dev.decomp.dusk) +set(DUSK_COMPANY_NAME "Twilit Realm") +set(DUSK_FILE_DESCRIPTION "Dusk") +set(DUSK_PRODUCT_NAME "Dusk") +set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors") set(DUSK_GAME_NAME "GZ2E") set(DUSK_GAME_VERSION "01") set(DUSK_TP_VERSION ${DUSK_GAME_NAME}${DUSK_GAME_VERSION}) @@ -216,6 +266,13 @@ set(GAME_INCLUDE_DIRS set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient) +list(APPEND GAME_LIBS libzstd_static) + +if (DUSK_ENABLE_SENTRY_NATIVE) + list(APPEND GAME_LIBS sentry) + list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_SENTRY_NATIVE=1 SENTRY_BUILD_STATIC=1) +endif () + if (DUSK_MOVIE_SUPPORT) if (TARGET libjpeg-turbo::turbojpeg-static) list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) @@ -270,6 +327,9 @@ add_executable(dusk src/dusk/main.cpp) target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) target_link_libraries(dusk PRIVATE game aurora::main) +if (TARGET crashpad_handler) + add_dependencies(dusk crashpad_handler) +endif () add_custom_command(TARGET dusk POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory @@ -278,6 +338,35 @@ add_custom_command(TARGET dusk POST_BUILD COMMENT "Copying resources" ) +if (WIN32) + set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows) + set(DUSK_WINDOWS_ICON_PNG ${CMAKE_CURRENT_SOURCE_DIR}/res/icon.png) + set(DUSK_WINDOWS_ICON_ICO ${CMAKE_CURRENT_BINARY_DIR}/dusk.ico) + set(DUSK_WINDOWS_RC ${CMAKE_CURRENT_BINARY_DIR}/dusk.rc) + set(DUSK_WINDOWS_MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/dusk.manifest) + + add_custom_command( + OUTPUT ${DUSK_WINDOWS_ICON_ICO} + COMMAND powershell -ExecutionPolicy Bypass -File + ${DUSK_WINDOWS_RESOURCE_DIR}/Create-IcoFromPng.ps1 + -InputPng ${DUSK_WINDOWS_ICON_PNG} + -OutputIco ${DUSK_WINDOWS_ICON_ICO} + DEPENDS ${DUSK_WINDOWS_ICON_PNG} ${DUSK_WINDOWS_RESOURCE_DIR}/Create-IcoFromPng.ps1 + VERBATIM + COMMENT "Generating Windows icon" + ) + + configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.manifest.in ${DUSK_WINDOWS_MANIFEST} @ONLY) + configure_file(${DUSK_WINDOWS_RESOURCE_DIR}/dusk.rc.in ${DUSK_WINDOWS_RC} @ONLY) + + target_sources(dusk PRIVATE ${DUSK_WINDOWS_ICON_ICO} ${DUSK_WINDOWS_RC}) + set_target_properties(dusk PROPERTIES WIN32_EXECUTABLE TRUE) + + if (MSVC) + target_link_options(dusk PRIVATE /MANIFEST:NO) + endif () +endif () + if (APPLE) if (IOS) set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios) @@ -366,6 +455,8 @@ if (TARGET crashpad_handler) list(APPEND EXTRA_TARGETS crashpad_handler) endif () install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX}) +aurora_install_runtime_dlls(dusk ${CMAKE_INSTALL_PREFIX}) +install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX}) if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) set(DEBUG_FILES_LIST "") foreach (target IN LISTS BINARY_TARGETS EXTRA_TARGETS) diff --git a/CMakePresets.json b/CMakePresets.json index 73c9915aba..d6203c6d27 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -88,7 +88,7 @@ "name": "windows-msvc", "displayName": "Windows (MSVC)", "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", + "binaryDir": "${sourceDir}/build/${presetName}", "architecture": { "value": "x64", "strategy": "external" @@ -96,7 +96,7 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install" + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install" }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { @@ -126,7 +126,7 @@ "name": "windows-arm64-msvc", "displayName": "Windows ARM64 (MSVC)", "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", + "binaryDir": "${sourceDir}/build/${presetName}", "architecture": { "value": "arm64", "strategy": "external" @@ -134,7 +134,7 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", "AURORA_DAWN_PROVIDER": "vendor" }, "vendor": { diff --git a/extern/aurora b/extern/aurora index 4c0d0feb02..4dd23c74d8 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 4c0d0feb0240546fc2815af709929c9f3bdcce22 +Subproject commit 4dd23c74d8bac5b1ba8be6ccb0270dc1bcb37ef9 diff --git a/files.cmake b/files.cmake index 6fb45e50be..358ede7c81 100644 --- a/files.cmake +++ b/files.cmake @@ -1338,6 +1338,7 @@ set(DUSK_FILES src/d/actor/d_a_alink_dusk.cpp src/dusk/asserts.cpp src/dusk/config.cpp + src/dusk/crash_reporting.cpp src/dusk/endian.cpp src/dusk/extras.c src/dusk/extras.cpp @@ -1373,6 +1374,8 @@ set(DUSK_FILES 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/offset_ptr.cpp src/dusk/OSContext.cpp src/dusk/OSThread.cpp diff --git a/include/d/d_drawlist.h b/include/d/d_drawlist.h index 9eecac2d61..4126c715f3 100644 --- a/include/d/d_drawlist.h +++ b/include/d/d_drawlist.h @@ -308,8 +308,11 @@ private: /* 0x0000C */ dDlst_shadowSimple_c mSimple[128]; /* 0x0340C */ int mNextID; /* 0x03410 */ dDlst_shadowReal_c mReal[8]; - /* 0x15EB0 */ TGXTexObj field_0x15eb0[2]; - /* 0x15EF0 */ void* field_0x15ef0[2]; + /* 0x15EB0 */ TGXTexObj mShadowTexObj[2]; + /* 0x15EF0 */ void* mShadowTexData[2]; + #if TARGET_PC + int mTexResScale; + #endif }; class dDlst_window_c { diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp index 6b78ee2e33..a480887fb2 100644 --- a/include/dusk/config_var.hpp +++ b/include/dusk/config_var.hpp @@ -146,6 +146,12 @@ concept ConfigValue = template const ConfigImplBase* GetConfigImpl(); +template +struct ConfigEnumRange { + static constexpr auto min = std::numeric_limits>::min(); + static constexpr auto max = std::numeric_limits>::max(); +}; + /** * \brief A CVar storing values. * @@ -189,6 +195,11 @@ public: } } + [[nodiscard]] constexpr const T& getDefaultValue() const noexcept { + checkRegistered(); + return defaultValue; + } + /** * \brief Change the value of a CVar. * diff --git a/include/dusk/crash_reporting.h b/include/dusk/crash_reporting.h new file mode 100644 index 0000000000..dfc5bde817 --- /dev/null +++ b/include/dusk/crash_reporting.h @@ -0,0 +1,8 @@ +#pragma once + +namespace dusk { + +void InitializeCrashReporting(); +void ShutdownCrashReporting(); + +} // namespace dusk diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 5c2dc2e1b8..423ac9af37 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -20,6 +20,11 @@ void begin_record(); void end_record(); void interpolate(float step); float get_interpolation_step(); + +void notify_presentation_frame(); +void request_presentation_sync(); +bool presentation_sync_active(); + void notify_sim_tick_complete(); uint32_t begin_presentation_ui_pass(); uint32_t get_presentation_ui_advance_ticks(); diff --git a/include/dusk/hotkeys.h b/include/dusk/hotkeys.h index 6e98d62521..4879b2ddce 100644 --- a/include/dusk/hotkeys.h +++ b/include/dusk/hotkeys.h @@ -17,6 +17,7 @@ constexpr const char* SHOW_HEAP_VIEWER = "F4"; constexpr const char* SHOW_STUB_LOG = "F5"; constexpr const char* SHOW_CAMERA_DEBUG = "F6"; constexpr const char* SHOW_AUDIO_DEBUG = "F7"; +constexpr const char* SHOW_STATE_SHARE = "F8"; constexpr const char* TURBO = "Tab"; diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 68ea49c752..8e57b9db99 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -7,6 +7,20 @@ namespace dusk { using namespace config; +enum class BloomMode : int { + Off = 0, + Classic = 1, + Dusk = 2, +}; + +namespace config { +template <> +struct ConfigEnumRange { + static constexpr auto min = BloomMode::Off; + static constexpr auto max = BloomMode::Dusk; +}; +} + // Persistent user settings struct UserSettings { @@ -53,7 +67,8 @@ struct UserSettings { ConfigVar invertCameraXAxis; // Graphics - ConfigVar enableBloom; + ConfigVar bloomMode; + ConfigVar bloomMultiplier; ConfigVar enableWaterRefraction; ConfigVar enableFrameInterpolation; ConfigVar shadowResolutionMultiplier; @@ -88,6 +103,7 @@ struct UserSettings { ConfigVar skipPreLaunchUI; ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; + ConfigVar enableCrashReporting; } backend; }; diff --git a/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp b/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp index 3b87a6e34a..ee99429ee2 100644 --- a/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp +++ b/libs/JSystem/src/JStudio/JStudio/jstudio-object.cpp @@ -2,7 +2,11 @@ #include "JSystem/JStudio/JStudio/jstudio-object.h" +#if TARGET_PC #include "dusk/audio.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" +#endif namespace JStudio { namespace { @@ -650,10 +654,25 @@ value_or_fun: return; value: +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation && u <= 5 && + (operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12)) + { + dusk::frame_interp::request_presentation_sync(); + } +#endif adaptor->adaptor_setVariableValue(control, u, operation, param_2, param_3); return; value_n: +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation && + (pN == TAdaptor_camera::sauVariableValue_3_POSITION_XYZ || pN == TAdaptor_camera::sauVariableValue_3_TARGET_POSITION_XYZ) && + (operation == data::UNK_0x2 || operation == data::UNK_0x3 || operation == data::UNK_0x12)) + { + dusk::frame_interp::request_presentation_sync(); + } +#endif adaptor->adaptor_setVariableValue_n(control, pN, u, operation, param_2, param_3); return; diff --git a/platforms/windows/Create-IcoFromPng.ps1 b/platforms/windows/Create-IcoFromPng.ps1 new file mode 100644 index 0000000000..6c375ef69c --- /dev/null +++ b/platforms/windows/Create-IcoFromPng.ps1 @@ -0,0 +1,91 @@ +param( + [Parameter(Mandatory = $true)] + [string]$InputPng, + + [Parameter(Mandatory = $true)] + [string]$OutputIco +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +Add-Type -AssemblyName System.Drawing + +$iconSizes = @(16, 24, 32, 48, 64, 128, 256) + +$inputPath = (Resolve-Path $InputPng).Path +$outputPath = [System.IO.Path]::GetFullPath($OutputIco) +$outputDir = Split-Path -Parent $outputPath +if (-not [string]::IsNullOrEmpty($outputDir)) { + [System.IO.Directory]::CreateDirectory($outputDir) | Out-Null +} + +$sourceImage = [System.Drawing.Image]::FromFile($inputPath) + +try { + $entries = New-Object System.Collections.Generic.List[object] + + foreach ($size in $iconSizes) { + $bitmap = New-Object System.Drawing.Bitmap $size, $size + try { + $graphics = [System.Drawing.Graphics]::FromImage($bitmap) + try { + $graphics.Clear([System.Drawing.Color]::Transparent) + $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality + $graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality + $graphics.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality + $graphics.DrawImage($sourceImage, 0, 0, $size, $size) + } finally { + $graphics.Dispose() + } + + $memory = New-Object System.IO.MemoryStream + try { + $bitmap.Save($memory, [System.Drawing.Imaging.ImageFormat]::Png) + $entries.Add([pscustomobject]@{ + Size = $size + Bytes = $memory.ToArray() + }) | Out-Null + } finally { + $memory.Dispose() + } + } finally { + $bitmap.Dispose() + } + } + + $fileStream = [System.IO.File]::Open($outputPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write) + try { + $writer = New-Object System.IO.BinaryWriter $fileStream + try { + $writer.Write([UInt16]0) + $writer.Write([UInt16]1) + $writer.Write([UInt16]$entries.Count) + + $dataOffset = 6 + (16 * $entries.Count) + foreach ($entry in $entries) { + $dimension = if ($entry.Size -ge 256) { 0 } else { [byte]$entry.Size } + $writer.Write([byte]$dimension) + $writer.Write([byte]$dimension) + $writer.Write([byte]0) + $writer.Write([byte]0) + $writer.Write([UInt16]1) + $writer.Write([UInt16]32) + $writer.Write([UInt32]$entry.Bytes.Length) + $writer.Write([UInt32]$dataOffset) + $dataOffset += $entry.Bytes.Length + } + + foreach ($entry in $entries) { + $writer.Write($entry.Bytes) + } + } finally { + $writer.Dispose() + } + } finally { + $fileStream.Dispose() + } +} finally { + $sourceImage.Dispose() +} diff --git a/platforms/windows/dusk.manifest.in b/platforms/windows/dusk.manifest.in new file mode 100644 index 0000000000..ed1c998dde --- /dev/null +++ b/platforms/windows/dusk.manifest.in @@ -0,0 +1,46 @@ + + + + @DUSK_FILE_DESCRIPTION@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + PerMonitorV2 + true + + + diff --git a/platforms/windows/dusk.rc.in b/platforms/windows/dusk.rc.in new file mode 100644 index 0000000000..343d10046a --- /dev/null +++ b/platforms/windows/dusk.rc.in @@ -0,0 +1,38 @@ +#include +#include + +IDI_MAIN_ICON ICON "@DUSK_WINDOWS_ICON_ICO@" +1 RT_MANIFEST "@DUSK_WINDOWS_MANIFEST@" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@ + PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@ + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "@DUSK_COMPANY_NAME@\0" + VALUE "FileDescription", "@DUSK_FILE_DESCRIPTION@\0" + VALUE "FileVersion", "@DUSK_VERSION_STRING@\0" + VALUE "InternalName", "dusk\0" + VALUE "LegalCopyright", "@DUSK_COPYRIGHT@\0" + VALUE "OriginalFilename", "dusk.exe\0" + VALUE "ProductName", "@DUSK_PRODUCT_NAME@\0" + VALUE "ProductVersion", "@DUSK_VERSION_STRING@\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END diff --git a/res/icon.png b/res/icon.png index b7a0614b9b..05e11c9b41 100644 Binary files a/res/icon.png and b/res/icon.png differ diff --git a/res/logo.png b/res/logo.png new file mode 100644 index 0000000000..34f1d59cd0 Binary files /dev/null and b/res/logo.png differ diff --git a/res/org-icon.png b/res/org-icon.png new file mode 100644 index 0000000000..b7a0614b9b Binary files /dev/null and b/res/org-icon.png differ diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index b218a8f1ee..7d102b8554 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -4394,13 +4394,34 @@ void daAlink_c::setSelectEquipItem(BOOL param_0) { if (mClothesChangeWaitTimer == 0) { if (checkZoraWearAbility()) { if (checkZoraWearMaskDraw()) { - field_0x06f0->show(); +#if TARGET_PC + if (field_0x06f0 != NULL) +#endif + { + field_0x06f0->show(); + } + if (!checkEquipHeavyBoots()) { - field_0x06e4->show(); +#if TARGET_PC + if (field_0x06e4 != NULL) +#endif + { + field_0x06e4->show(); + } } } else { - field_0x06f0->hide(); - field_0x06e4->hide(); +#if TARGET_PC + if (field_0x06f0 != NULL) +#endif + { + field_0x06f0->hide(); + } +#if TARGET_PC + if (field_0x06e4 != NULL) +#endif + { + field_0x06e4->hide(); + } } } @@ -19506,7 +19527,12 @@ int daAlink_c::draw() { field_0x06e8->hide(); } - field_0x06f0->hide(); +#if TARGET_PC + if (field_0x06f0 != NULL) +#endif + { + field_0x06f0->hide(); + } #if PLATFORM_SHIELD if (mProcID == PROC_HOOKSHOT_WALL_SHOOT || mProcID == PROC_HOOKSHOT_SUBJECT) { @@ -19536,7 +19562,12 @@ int daAlink_c::draw() { } if (!checkZoraWearMaskDraw() && checkZoraWearAbility()) { - field_0x06f0->hide(); +#if TARGET_PC + if (field_0x06f0 != NULL) +#endif + { + field_0x06f0->hide(); + } } } @@ -19545,7 +19576,12 @@ int daAlink_c::draw() { } if (checkZoraWearMaskDraw() || !checkZoraWearAbility()) { - field_0x06f0->show(); +#if TARGET_PC + if (field_0x06f0 != NULL) +#endif + { + field_0x06f0->show(); + } } } diff --git a/src/d/actor/d_a_obj_onsenTaru.cpp b/src/d/actor/d_a_obj_onsenTaru.cpp index c83aa24b0d..ea2b107ecf 100644 --- a/src/d/actor/d_a_obj_onsenTaru.cpp +++ b/src/d/actor/d_a_obj_onsenTaru.cpp @@ -277,6 +277,11 @@ void daOnsTaru_c::mode_init_drop() { cLib_offBit(attention_info.flags, fopAc_AttnFlag_CARRY_e); gravity = -7.0f; mMode = MODE_DROP_e; + + #if TARGET_PC + // TODO: figure out why this is needed and where exactly the UB happens + mCcStts.ClrCcMove(); + #endif } void daOnsTaru_c::mode_proc_drop() { diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 955683e05b..7b95a0d8ea 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -20,7 +20,6 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_lib.h" -#include "dusk/frame_interpolation.h" #include #include @@ -29,6 +28,11 @@ #include "d/d_debug_camera.h" #endif +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#include "dusk/logging.h" +#endif + namespace { static f32 limitf(f32 value, f32 min, f32 max) { @@ -2048,6 +2052,18 @@ s32 dCamera_c::nextType(s32 i_curType) { bool dCamera_c::onTypeChange(s32 i_curType, s32 i_nextType) { daAlink_c* unusedPlayer = daAlink_getAlinkActorClass(); +#if TARGET_PC + const s32 event_type_id = specialType[CAM_TYPE_EVENT]; + DuskLog.debug( + "frameInterp: onTypeChange {} -> {} (event_type_id={}, leaving_event={}, entering_event={})", + static_cast(i_curType), + static_cast(i_nextType), + static_cast(event_type_id), + i_curType == event_type_id, + i_nextType == event_type_id + ); +#endif + if (i_curType == specialType[CAM_TYPE_EVENT]) { if (mCamSetup.CheckFlag(0x4000)) { mGear = 0; @@ -10161,6 +10177,26 @@ bool dCamera_c::eventCamera(s32 param_0) { ActionNames[var_r29]); #endif +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + switch (var_r29) { + case 3: + case 4: + case 5: + case 12: + dusk::frame_interp::request_presentation_sync(); + break; + default: + DuskLog.debug( + "frameInterp: presentation sync not requested for ZEV event [{}] (staff idx {})", + static_cast(ActionNames[var_r29]), + static_cast(mEventData.mStaffIdx) + ); + break; + } + } +#endif + if (getEvFloatData(&sp28, "KeepDist") != 0 && mViewCache.mDirection.R() < sp28) { mViewCache.mDirection.R(sp28); diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 42ac8075c7..2ef4732455 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1440,16 +1440,17 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa void dDlst_shadowControl_c::init() { #if TARGET_PC - u16 resMult = dusk::getSettings().game.shadowResolutionMultiplier; + mTexResScale = dusk::getSettings().game.shadowResolutionMultiplier; // Increase shadow map resolution u16 l_realImageSize[2] = { - static_cast(192 * resMult), - static_cast(64 * resMult) + static_cast(192 * mTexResScale), + static_cast(64 * mTexResScale) }; #else static u16 l_realImageSize[2] = {192, 64}; #endif + for (int i = 0; i < 2; i++) { u16 size = l_realImageSize[i]; @@ -1458,10 +1459,13 @@ void dDlst_shadowControl_c::init() { #else u32 buffer_size = GXGetTexBufferSize(size, size, 5, GX_DISABLE, 0); #endif - field_0x15ef0[i] = JKR_NEW_ARRAY_ARGS(u8, buffer_size, 0x20); - GXInitTexObj(&field_0x15eb0[i], field_0x15ef0[i], size, size, GX_TF_RGB5A3, GX_CLAMP, + delete mShadowTexData[i]; + mShadowTexData[i] = JKR_NEW_ARRAY_ARGS(u8, buffer_size, 0x20); + + mShadowTexObj[i].reset(); + GXInitTexObj(&mShadowTexObj[i], mShadowTexData[i], size, size, GX_TF_RGB5A3, GX_CLAMP, GX_CLAMP, GX_DISABLE); - GXInitTexObjLOD(&field_0x15eb0[i], GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, + GXInitTexObjLOD(&mShadowTexObj[i], GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); } } @@ -1479,25 +1483,13 @@ void dDlst_shadowControl_c::reset() { mRealNum = 0; field_0x4 = NULL; -#ifdef TARGET_PC - field_0x15eb0[0].reset(); - field_0x15eb0[1].reset(); +#if TARGET_PC + if (mTexResScale != dusk::getSettings().game.shadowResolutionMultiplier) + init(); #endif } -#if TARGET_PC -int lastShadowValue = 0; -#endif - void dDlst_shadowControl_c::imageDraw(Mtx param_0) { - #if TARGET_PC - if (lastShadowValue != dusk::getSettings().game.shadowResolutionMultiplier) { - reset(); - init(); - lastShadowValue = dusk::getSettings().game.shadowResolutionMultiplier; - } - #endif - static u8 l_matDL[] ATTRIBUTE_ALIGN(32) = { 0x10, 0x00, 0x00, 0x10, 0x0E, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x04, 0x00, 0x61, 0x28, 0x38, 0x00, 0x00, 0x61, 0xC0, 0x08, 0xFF, 0xF2, @@ -1530,7 +1522,7 @@ void dDlst_shadowControl_c::imageDraw(Mtx param_0) { j3dSys.setDrawModeOpaTexEdge(); J3DShape::resetVcdVatCache(); dDlst_shadowReal_c* shadowReal = field_0x4; - int r29 = 0; + int chan = 0; int tex = 0; u16 r27; u16 r26; @@ -1539,8 +1531,8 @@ void dDlst_shadowControl_c::imageDraw(Mtx param_0) { #endif for (; shadowReal; shadowReal = shadowReal->getZsortNext()) { if (shadowReal->isUse()) { - if (r29 == 0) { - r27 = GXGetTexObjWidth(field_0x15eb0 + tex); + if (chan == 0) { + r27 = GXGetTexObjWidth(&mShadowTexObj[tex]); r26 = r27 * 2; #ifdef TARGET_PC GXCreateFrameBuffer(r26, r26); @@ -1549,27 +1541,27 @@ void dDlst_shadowControl_c::imageDraw(Mtx param_0) { GXSetViewport(0.0f, 0.0f, r26, r26, 0.0f, 1.0f); GXSetScissor(0, 0, r26, r26); } - GXSetTevColor(GX_TEVREG0, l_imageDrawColor[r29]); - if (r29 == 3) { + GXSetTevColor(GX_TEVREG0, l_imageDrawColor[chan]); + if (chan == 3) { GXSetColorUpdate(GX_DISABLE); GXSetAlphaUpdate(GX_ENABLE); } shadowReal->imageDraw(param_0); - r29 = (r29 + 1) % 4; - if (r29 == 0) { + chan = (chan + 1) % 4; + if (chan == 0) { GXSetTexCopySrc(0, 0, r26, r26); GXSetTexCopyDst(r27, r27, GX_TF_RGB5A3, GX_TRUE); GXSetColorUpdate(GX_ENABLE); - GXCopyTex(field_0x15ef0[tex++], GX_TRUE); + GXCopyTex(mShadowTexData[tex++], GX_TRUE); GXPixModeSync(); GXSetAlphaUpdate(GX_DISABLE); } } } - if (r29) { + if (chan) { GXSetTexCopySrc(0, 0, r26, r26); GXSetTexCopyDst(r27, r27, GX_TF_RGB5A3, GX_TRUE); - GXCopyTex(field_0x15ef0[tex], GX_TRUE); + GXCopyTex(mShadowTexData[tex], GX_TRUE); GXPixModeSync(); GXSetAlphaUpdate(GX_DISABLE); } @@ -1621,7 +1613,7 @@ void dDlst_shadowControl_c::draw(Mtx param_0) { for (int i2 = 0, i3 = 0; real != NULL; real = real->getZsortNext()) { if (real->isUse()) { if (i2 == 0) { - TGXTexObj* obj = &field_0x15eb0[i3]; + TGXTexObj* obj = &mShadowTexObj[i3]; i3++; GXLoadTexObj(obj, GX_TEXMAP0); diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index 7fd5ea31a2..af8a734fe7 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -48,13 +48,20 @@ public: GXPixModeSync(); #if TARGET_PC // init mTexObj at capture time so the gpu ref survives window resizes - GXInitTexObj(&mTexObj, mDoGph_gInf_c::getFrameBufferTex(), mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), + mCaptureWidth = mDoGph_gInf_c::getWidth(); + mCaptureHeight = mDoGph_gInf_c::getHeight(); + GXInitTexObj(&mTexObj, mDoGph_gInf_c::getFrameBufferTex(), mCaptureWidth, mCaptureHeight, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&mTexObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); #endif } else { #if TARGET_PC - // reuse the persistent mTexObj + // If the window was resized since capture, force a re-capture at the new size + if (mCaptureWidth != (u16)mDoGph_gInf_c::getWidth() || + mCaptureHeight != (u16)mDoGph_gInf_c::getHeight()) { + mFlag = 1; + return; + } GXLoadTexObj(&mTexObj, GX_TEXMAP0); #else TGXTexObj tex; @@ -112,6 +119,10 @@ public: mFlag = 0; mAlpha = 255; mTopFlag = 0; +#if TARGET_PC + mCaptureWidth = 0; + mCaptureHeight = 0; +#endif } void setCaptureFlag() { mFlag = 1; } @@ -126,6 +137,8 @@ private: /* 0x5 */ u8 mAlpha; /* 0x6 */ u8 mTopFlag; #if TARGET_PC + u16 mCaptureWidth; + u16 mCaptureHeight; TGXTexObj mTexObj; #endif }; diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 98f32cf8cd..c377d1ba54 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -5,6 +5,7 @@ #include "aurora/lib/logging.hpp" #include "dusk/io.hpp" +#include "dusk/settings.h" #include #include @@ -37,9 +38,24 @@ const ConfigImplBase* ConfigVarBase::getImpl() const noexcept { return impl; } +template +static T sanitizeEnumValue(const ConfigVar& cVar, T value) { + if constexpr (std::is_enum_v) { + using Underlying = std::underlying_type_t; + const Underlying raw = static_cast(value); + const Underlying min = static_cast(ConfigEnumRange::min); + const Underlying max = static_cast(ConfigEnumRange::max); + if (raw < min || raw > max) { + return cVar.getDefaultValue(); + } + } + + return value; +} + template void ConfigImpl::loadFromJson(ConfigVar& cVar, const json& jsonValue) { - cVar.setValue(jsonValue.get(), false); + cVar.setValue(sanitizeEnumValue(cVar, jsonValue.get()), false); } template @@ -85,6 +101,28 @@ static void loadFromArgImpl(ConfigVar& cVar, const std::string_view cVar.setOverrideValue(std::string(stringValue)); } +template requires std::is_enum_v +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + using Underlying = std::underlying_type_t; + const std::string str(stringValue); + + if constexpr (std::is_signed_v) { + const auto result = std::stoll(str); + if (result >= std::numeric_limits::min() && result <= std::numeric_limits::max()) { + cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast(result))); + } else { + throw std::out_of_range("Value is too large"); + } + } else { + const auto result = std::stoull(str); + if (result <= std::numeric_limits::max()) { + cVar.setOverrideValue(sanitizeEnumValue(cVar, static_cast(result))); + } else { + throw std::out_of_range("Value is too large"); + } + } +} + template void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { loadFromArgImpl(cVar, stringValue); @@ -115,6 +153,7 @@ namespace dusk::config { template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; + template class ConfigImpl; } void dusk::config::Register(ConfigVarBase& configVar) { diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp new file mode 100644 index 0000000000..1b1adbf774 --- /dev/null +++ b/src/dusk/crash_reporting.cpp @@ -0,0 +1,173 @@ +#include "dusk/crash_reporting.h" + +#include "dusk/app_info.hpp" +#include "dusk/dusk.h" +#include "dusk/logging.h" +#include "dusk/settings.h" +#include "version.h" + +#include +#include +#include +#include +#include + +#include "SDL3/SDL_filesystem.h" + +#if DUSK_ENABLE_SENTRY_NATIVE +#include +#endif + +namespace dusk { + +namespace { + +#if DUSK_ENABLE_SENTRY_NATIVE +bool g_sentryInitialized = false; + +bool IsTruthy(std::string_view value) { + return value == "1" || value == "true" || value == "TRUE" || value == "yes" + || value == "YES" || value == "on" || value == "ON"; +} + +std::string GetEnvOrEmpty(const char* name) { + if (const char* value = std::getenv(name)) { + return value; + } + return {}; +} + +bool GetEffectiveEnabled() { + const std::string env = GetEnvOrEmpty("DUSK_SENTRY_ENABLED"); + if (!env.empty()) { + return IsTruthy(env); + } + return getSettings().backend.enableCrashReporting; +} + +std::string GetEffectiveDsn() { + const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DSN"); + if (!env.empty()) { + return env; + } + return DUSK_SENTRY_DSN; +} + +bool GetEffectiveDebug() { + const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DEBUG"); + if (!env.empty()) { + return IsTruthy(env); + } + return false; +} + +std::string GetReleaseName() { + return std::string(AppName) + "@" DUSK_WC_DESCRIBE; +} + +std::filesystem::path GetSentryDatabasePath() { + return std::filesystem::path(configPath) / "sentry"; +} + +std::filesystem::path GetCrashpadHandlerPath() { + const char* basePath = SDL_GetBasePath(); + if (!basePath) { + return {}; + } + + const std::filesystem::path handlerDir(basePath); + SDL_free(const_cast(basePath)); + +#if _WIN32 + return handlerDir / "crashpad_handler.exe"; +#else + return handlerDir / "crashpad_handler"; +#endif +} + +void ConfigurePathOptions(sentry_options_t* options) { + const auto databasePath = GetSentryDatabasePath(); + std::error_code ec; + std::filesystem::create_directories(databasePath, ec); + if (ec) { + DuskLog.warn("Unable to create Sentry database path '{}': {}", + databasePath.string(), ec.message()); + } + +#if _WIN32 + const std::wstring databasePathWide = databasePath.wstring(); + sentry_options_set_database_pathw(options, databasePathWide.c_str()); + + const auto handlerPath = GetCrashpadHandlerPath(); + if (!handlerPath.empty()) { + const std::wstring handlerPathWide = handlerPath.wstring(); + sentry_options_set_handler_pathw(options, handlerPathWide.c_str()); + } +#else + const std::string databasePathUtf8 = databasePath.string(); + sentry_options_set_database_path(options, databasePathUtf8.c_str()); + + const auto handlerPath = GetCrashpadHandlerPath(); + if (!handlerPath.empty()) { + const std::string handlerPathUtf8 = handlerPath.string(); + sentry_options_set_handler_path(options, handlerPathUtf8.c_str()); + } +#endif +} +#endif + +} // namespace + +void InitializeCrashReporting() { +#if DUSK_ENABLE_SENTRY_NATIVE + if (g_sentryInitialized) { + return; + } + + if (!GetEffectiveEnabled()) { + return; + } + + const std::string dsn = GetEffectiveDsn(); + if (dsn.empty()) { + DuskLog.warn("Crash reporting is enabled but no Sentry DSN is configured"); + return; + } + + const std::string release = GetReleaseName(); + + sentry_options_t* options = sentry_options_new(); + sentry_options_set_dsn(options, dsn.c_str()); + sentry_options_set_release(options, release.c_str()); + sentry_options_set_environment(options, DUSK_SENTRY_ENVIRONMENT); + sentry_options_set_debug(options, GetEffectiveDebug() ? 1 : 0); + sentry_options_set_cache_keep(options, 1); + sentry_options_set_max_breadcrumbs(options, 100); + ConfigurePathOptions(options); + + if (sentry_init(options) != 0) { + DuskLog.warn("Failed to initialize Sentry crash reporting"); + return; + } + + sentry_set_tag("git_branch", DUSK_WC_BRANCH); + sentry_set_tag("build_type", DUSK_BUILD_TYPE); + sentry_set_tag("tp_version", DUSK_TP_VERSION); + g_sentryInitialized = true; + + DuskLog.info("Initialized Sentry crash reporting"); +#endif +} + +void ShutdownCrashReporting() { +#if DUSK_ENABLE_SENTRY_NATIVE + if (!g_sentryInitialized) { + return; + } + + sentry_close(); + g_sentryInitialized = false; +#endif +} + +} // namespace dusk diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index e9b35da1f0..8c45c034c6 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -63,6 +63,10 @@ bool s_initialized = false; bool g_enabled = false; bool g_recording = false; bool g_interpolating = false; +bool g_sync_presentation = false; +uint32_t g_presentation_counter = 0; +uint32_t g_presentation_sync_end = 0; + float g_step = 0.0f; uint32_t g_pending_presentation_ui_ticks = 0; uint32_t g_current_presentation_ui_ticks = 0; @@ -235,7 +239,7 @@ void interpolate_branch(const Path& old_path, const Path& new_path, float step) } const Mtx* resolve_replacement(const Mtx* source, Mtx* scratch) { - if (!g_interpolating || source == nullptr) { + if (!g_interpolating || source == nullptr || dusk::frame_interp::presentation_sync_active()) { return source; } @@ -268,6 +272,7 @@ void begin_record() { ensure_initialized(); if (!g_enabled) { g_interpolating = false; + g_sync_presentation = false; g_previous_recording = {}; g_current_recording = {}; g_current_path.clear(); @@ -275,6 +280,10 @@ void begin_record() { return; } + if (g_sync_presentation && g_presentation_counter > g_presentation_sync_end) { + g_sync_presentation = false; + } + g_previous_recording = std::move(g_current_recording); g_current_recording = {}; g_current_path.clear(); @@ -292,21 +301,38 @@ void interpolate(float step) { ensure_initialized(); clear_replacements(); g_step = std::clamp(step, 0.0f, 1.0f); - g_interpolating = g_enabled && !g_recording && has_recording_data(g_current_recording); + g_interpolating = g_enabled && !g_recording && !g_sync_presentation && has_recording_data(g_current_recording); if (!g_interpolating) { return; } + const Path& old_root = has_recording_data(g_previous_recording) ? g_previous_recording.root : g_current_recording.root; + interpolate_branch(old_root, g_current_recording.root, g_step); +} - if (!has_recording_data(g_previous_recording)) { - interpolate_branch(g_current_recording.root, g_current_recording.root, g_step); +void notify_presentation_frame() { + ensure_initialized(); + ++g_presentation_counter; +} + +void request_presentation_sync() { + ensure_initialized(); + if (!g_enabled) { return; } + g_sync_presentation = true; + g_presentation_sync_end = g_presentation_counter + 1; +} - interpolate_branch(g_previous_recording.root, g_current_recording.root, g_step); +bool presentation_sync_active() { + if (!s_initialized || !g_enabled) { + return false; + } + return g_sync_presentation; } float get_interpolation_step() { - return g_step; + ensure_initialized(); + return presentation_sync_active() ? 1.0f : g_step; } void notify_sim_tick_complete() { @@ -371,7 +397,7 @@ void record_final_mtx_raw(const Mtx* dest, const Mtx src) { } bool lookup_replacement(const void* source, Mtx out) { - if (!s_initialized || !g_interpolating || source == nullptr) { + if (presentation_sync_active() || !g_interpolating || source == nullptr) { return false; } @@ -385,7 +411,7 @@ bool lookup_replacement(const void* source, Mtx out) { } bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { - if (!s_initialized || !g_interpolating || lhs == nullptr || rhs == nullptr) { + if (presentation_sync_active() || !g_interpolating || lhs == nullptr || rhs == nullptr) { return false; } @@ -393,9 +419,7 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { Mtx rhs_scratch; const Mtx* resolved_lhs = resolve_replacement(reinterpret_cast(lhs), &lhs_scratch); const Mtx* resolved_rhs = resolve_replacement(reinterpret_cast(rhs), &rhs_scratch); - if (resolved_lhs == reinterpret_cast(lhs) && - resolved_rhs == reinterpret_cast(rhs)) - { + if (resolved_lhs == reinterpret_cast(lhs) && resolved_rhs == reinterpret_cast(rhs)) { return false; } diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 1bff5da458..612c95d7f3 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -269,6 +269,7 @@ namespace dusk { m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); } + m_menuTools.ShowStateShare(); DuskDebugPad(); // temporary, remove later // Only show cursor when menu or any windows are open diff --git a/src/dusk/imgui/ImGuiDebugPad.cpp b/src/dusk/imgui/ImGuiDebugPad.cpp index 75dc8d77a6..b1591f1968 100644 --- a/src/dusk/imgui/ImGuiDebugPad.cpp +++ b/src/dusk/imgui/ImGuiDebugPad.cpp @@ -47,4 +47,15 @@ void DuskDebugPad() { pad.mMainStickValue = 1.0f; pad.mMainStickAngle = -0x4000; } + + if (ImGui::IsKeyDown(ImGuiKey_Q)) { + pad.mTriggerLeft = 1.0; + pad.mTrigLockL = 1; + pad.mHoldLockL = 1; + } + if (ImGui::IsKeyDown(ImGuiKey_E)) { + pad.mTriggerRight = 1.0; + pad.mTrigLockR = 1; + pad.mHoldLockR = 1; + } } diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index f8dc048c0f..0c0b5e934d 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -29,13 +29,22 @@ bool AssetExists(const std::string& path) { SDL_PathInfo pathInfo{}; return SDL_GetPathInfo(path.c_str(), &pathInfo) && pathInfo.type == SDL_PATHTYPE_FILE; } + +ImTextureID AddTexture(const char* assetName) { + auto image = GetImage(GetAssetPath(assetName)); + if (image.data == nullptr || image.width == 0 || image.height == 0) { + return 0; + } + return aurora_imgui_add_texture(image.width, image.height, image.data.get()); +} } // namespace ImFont* ImGuiEngine::fontNormal; ImFont* ImGuiEngine::fontLarge; ImFont* ImGuiEngine::fontExtraLarge; ImFont* ImGuiEngine::fontMono; -ImTextureID ImGuiEngine::duskIcon = 0; +ImTextureID ImGuiEngine::orgIcon = 0; +ImTextureID ImGuiEngine::duskLogo = 0; inline ImFont* CreateFont(float size, const std::string& fontPath, std::string_view fontName) { bool fontFileExists = !fontPath.empty() && AssetExists(fontPath); @@ -149,6 +158,7 @@ void ImGuiEngine_Initialize(float scale) { Image GetImage(const std::string& path) { if (!AssetExists(path)) { + DuskLog.warn("Image '{}' does not exist", path); return {}; } @@ -187,13 +197,11 @@ Image GetImage(const std::string& path) { } void ImGuiEngine_AddTextures() { - if (ImGuiEngine::duskIcon == 0) { - auto icon = GetImage(GetAssetPath("icon.png")); - if (icon.data == nullptr || icon.width == 0 || icon.height == 0) { - ImGuiEngine::duskIcon = 0; - return; - } - ImGuiEngine::duskIcon = aurora_imgui_add_texture(icon.width, icon.height, icon.data.get()); + if (ImGuiEngine::orgIcon == 0) { + ImGuiEngine::orgIcon = AddTexture("org-icon.png"); + } + if (ImGuiEngine::duskLogo == 0) { + ImGuiEngine::duskLogo = AddTexture("logo.png"); } } } // namespace dusk diff --git a/src/dusk/imgui/ImGuiEngine.hpp b/src/dusk/imgui/ImGuiEngine.hpp index 4b0baaa3a8..3b3e3e0303 100644 --- a/src/dusk/imgui/ImGuiEngine.hpp +++ b/src/dusk/imgui/ImGuiEngine.hpp @@ -11,7 +11,8 @@ public: static ImFont* fontLarge; static ImFont* fontExtraLarge; static ImFont* fontMono; - static ImTextureID duskIcon; + static ImTextureID orgIcon; + static ImTextureID duskLogo; }; void ImGuiEngine_Initialize(float scale); @@ -23,5 +24,5 @@ struct Image { uint32_t width; uint32_t height; }; -Image GetImage(std::string_view path); +Image GetImage(const std::string& path); } // namespace dusk diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index b73eaff6e6..99c77dac70 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -12,11 +12,13 @@ namespace dusk { static void ApplyPresetClassic() { auto& s = getSettings(); s.video.lockAspectRatio.setValue(true); + s.game.bloomMode.setValue(BloomMode::Classic); VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); } static void ApplyPresetHD() { auto& s = getSettings(); + s.game.bloomMode.setValue(BloomMode::Classic); s.game.hideTvSettingsScreen.setValue(true); s.game.skipWarningScreen.setValue(true); s.game.noReturnRupees.setValue(true); @@ -37,6 +39,7 @@ static void ApplyPresetDusk() { s.game.instantSaves.setValue(true); s.game.midnasLamentNonStop.setValue(true); s.game.enableFrameInterpolation.setValue(true); + s.game.bloomMode.setValue(BloomMode::Dusk); } // ========================================================================= diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index d2b893c884..27e036ee2e 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -62,7 +62,30 @@ namespace dusk { config::Save(); } - config::ImGuiCheckbox("Native Bloom", getSettings().game.enableBloom); + constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"}; + int bloomMode = static_cast(getSettings().game.bloomMode.getValue()); + if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) { + for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) { + const bool selected = bloomMode == i; + if (ImGui::Selectable(bloomModeNames[i], selected)) { + getSettings().game.bloomMode.setValue(static_cast(i)); + config::Save(); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + bool bloomOff = bloomMode == static_cast(BloomMode::Off); + if (bloomOff) ImGui::BeginDisabled(); + float mult = getSettings().game.bloomMultiplier.getValue(); + if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) { + getSettings().game.bloomMultiplier.setValue(mult); + config::Save(); + } + if (bloomOff) ImGui::EndDisabled(); config::ImGuiCheckbox("Enable Water Refraction", getSettings().game.enableWaterRefraction); @@ -110,6 +133,9 @@ namespace dusk { if (ImGui::BeginMenu("Interface")) { config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI); config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation); +#if DUSK_ENABLE_SENTRY_NATIVE + config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting); +#endif ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index f6c29c0a16..3a9935353f 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -53,6 +53,7 @@ namespace dusk { ImGui::MenuItem("Map Loader", nullptr, &m_showMapLoader); ImGui::MenuItem("Player Info", nullptr, &m_showPlayerInfo); ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor); + ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare); ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); if (!dusk::IsGameLaunched) { diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 37409ddb06..05bdd9364b 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -6,6 +6,7 @@ #include "imgui.h" #include "ImGuiSaveEditor.hpp" +#include "ImGuiStateShare.hpp" namespace dusk { class ImGuiMenuTools { @@ -23,6 +24,7 @@ namespace dusk { void ShowPlayerInfo(); void ShowAudioDebug(); void ShowSaveEditor(); + void ShowStateShare(); private: bool m_showDebugOverlay = false; @@ -57,6 +59,9 @@ namespace dusk { bool m_showSaveEditor = false; ImGuiSaveEditor m_saveEditor; + + bool m_showStateShare = false; + ImGuiStateShare m_stateShare; }; } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index 15b229a3cb..adb5952a85 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -80,12 +80,20 @@ void ImGuiPreLaunchWindow::draw() { float iconSize = 150.f; ImGui::SameLine(windowSize.x / 2 - iconSize + (iconSize / 2)); - if (ImGuiEngine::duskIcon != 0) - ImGui::Image(ImGuiEngine::duskIcon, ImVec2{iconSize, iconSize}); + if (ImGuiEngine::orgIcon != 0) { + ImGui::Image(ImGuiEngine::orgIcon, ImVec2{iconSize, iconSize}); + } ImGuiTextCenter("Twilit Realm presents"); - ImGui::PushFont(ImGuiEngine::fontExtraLarge); - ImGuiTextCenter("Dusk"); - ImGui::PopFont(); + 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])(); diff --git a/src/dusk/imgui/ImGuiStateShare.cpp b/src/dusk/imgui/ImGuiStateShare.cpp new file mode 100644 index 0000000000..172aad17a5 --- /dev/null +++ b/src/dusk/imgui/ImGuiStateShare.cpp @@ -0,0 +1,133 @@ +#include "ImGuiStateShare.hpp" +#include "ImGuiMenuTools.hpp" +#include "ImGuiConsole.hpp" + +#include "imgui.h" +#include "fmt/format.h" +#include "absl/strings/escaping.h" + +#include "d/d_com_inf_game.h" +#include "dusk/main.h" + +#include + +namespace dusk { + +#pragma pack(push, 1) +struct StateSharePacket { + char stageName[8]; + int8_t roomNo; + int8_t layer; + int16_t startPoint; + // followed by raw dSv_info_c bytes +}; +#pragma pack(pop) + +static constexpr size_t PACKET_TOTAL = sizeof(StateSharePacket) + sizeof(dSv_info_c); + +void ImGuiStateShare::copyState() { + StateSharePacket pkt = {}; + strncpy(pkt.stageName, dComIfGp_getStartStageName(), 7); + pkt.roomNo = dComIfGp_getStartStageRoomNo(); + pkt.layer = dComIfGp_getStartStageLayer(); + pkt.startPoint = dComIfGp_getStartStagePoint(); + + std::string raw(PACKET_TOTAL, '\0'); + memcpy(raw.data(), &pkt, sizeof(pkt)); + memcpy(raw.data() + sizeof(pkt), &g_dComIfG_gameInfo.info, sizeof(dSv_info_c)); + + size_t bound = ZSTD_compressBound(raw.size()); + std::string compressed(bound, '\0'); + compressed.resize(ZSTD_compress(compressed.data(), bound, raw.data(), raw.size(), 1)); + + std::string encoded = absl::Base64Escape(compressed); + ImGui::SetClipboardText(encoded.c_str()); + m_statusMsg = "Copied to clipboard."; +} + +bool ImGuiStateShare::pasteState() { + const char* clip = ImGui::GetClipboardText(); + if (!clip || clip[0] == '\0') { + m_statusMsg = "Clipboard is empty."; + return false; + } + + std::string decoded; + if (!absl::Base64Unescape(clip, &decoded)) { + m_statusMsg = "Invalid base64."; + return false; + } + + unsigned long long dSize = ZSTD_getFrameContentSize(decoded.data(), decoded.size()); + if (dSize == ZSTD_CONTENTSIZE_ERROR || dSize == ZSTD_CONTENTSIZE_UNKNOWN || dSize < PACKET_TOTAL) { + m_statusMsg = "Not a valid state string."; + return false; + } + + std::string raw(static_cast(dSize), '\0'); + size_t result = ZSTD_decompress(raw.data(), raw.size(), decoded.data(), decoded.size()); + if (ZSTD_isError(result)) { + m_statusMsg = fmt::format("Decompression failed: {}", ZSTD_getErrorName(result)); + return false; + } + + StateSharePacket pkt; + memcpy(&pkt, raw.data(), sizeof(pkt)); + pkt.stageName[7] = '\0'; + + memcpy(&g_dComIfG_gameInfo.info, raw.data() + sizeof(pkt), sizeof(dSv_info_c)); + + s16 spawnPoint = pkt.startPoint == -4 ? -1 : pkt.startPoint; + + if (spawnPoint == -1) { + dComIfGs_setRestartRoomParam(pkt.roomNo & 0x3F); + } + + dComIfGp_setNextStage(pkt.stageName, spawnPoint, pkt.roomNo, pkt.layer); + m_pendingInfo = g_dComIfG_gameInfo.info; + + m_statusMsg = fmt::format("Warping to {} room {} layer {}.", pkt.stageName, (int)pkt.roomNo, (int)pkt.layer); + return true; +} + +void ImGuiStateShare::tickPendingApply() { + if (!m_pendingInfo.has_value() || dComIfGp_isEnableNextStage()) + return; + g_dComIfG_gameInfo.info = *m_pendingInfo; + m_pendingInfo.reset(); +} + +void ImGuiStateShare::draw(bool& open) { + if (dusk::IsGameLaunched) + tickPendingApply(); + + if (!open) + return; + + if (!ImGui::Begin("State Share", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { + ImGui::End(); + return; + } + + if (!dusk::IsGameLaunched) ImGui::BeginDisabled(); + if (ImGui::Button("Copy State")) copyState(); + ImGui::SameLine(); + if (ImGui::Button("Import State")) pasteState(); + if (!dusk::IsGameLaunched) ImGui::EndDisabled(); + + if (!m_statusMsg.empty()) { + ImGui::Spacing(); + ImGui::Separator(); + ImGui::TextWrapped("%s", m_statusMsg.c_str()); + } + + ImGui::End(); +} + +void ImGuiMenuTools::ShowStateShare() { + if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare)) + return; + m_stateShare.draw(m_showStateShare); +} + +} diff --git a/src/dusk/imgui/ImGuiStateShare.hpp b/src/dusk/imgui/ImGuiStateShare.hpp new file mode 100644 index 0000000000..a09cfd5963 --- /dev/null +++ b/src/dusk/imgui/ImGuiStateShare.hpp @@ -0,0 +1,24 @@ +#ifndef DUSK_IMGUI_STATESHARE_HPP +#define DUSK_IMGUI_STATESHARE_HPP + +#include "d/d_save.h" +#include +#include + +namespace dusk { + class ImGuiStateShare { + public: + void draw(bool& open); + + private: + void copyState(); + bool pasteState(); + void tickPendingApply(); + + std::string m_statusMsg; + std::optional m_pendingInfo; + }; +} + +#endif + \ No newline at end of file diff --git a/src/dusk/main.cpp b/src/dusk/main.cpp index 7d19325f8f..22cd5a9fc6 100644 --- a/src/dusk/main.cpp +++ b/src/dusk/main.cpp @@ -1,26 +1,126 @@ #if _WIN32 #define WINDOWS_LEAN_AND_MEAN #include +#include #endif #include +#include +#include +#include +#include +#include + int game_main(int argc, char* argv[]); -void WindowsSetupConsole(); +namespace { -int main(int argc, char* argv[]) { - WindowsSetupConsole(); +#if _WIN32 +bool ShouldShowWindowsConsole(int argc, char* argv[]) { + if (const auto* env = std::getenv("DUSK_CONSOLE")) { + if (env[0] != '\0' && env[0] != '0') { + return true; + } + } + + for (int i = 1; i < argc; ++i) { + if (std::string_view(argv[i]) == "--console") { + return true; + } + } + + return false; +} + +void SetupWindowsConsoleStreams() { + FILE* stream = nullptr; + freopen_s(&stream, "CONIN$", "r", stdin); + freopen_s(&stream, "CONOUT$", "w", stdout); + freopen_s(&stream, "CONOUT$", "w", stderr); +} + +void WindowsSetupConsole(bool showConsole) { + if (!showConsole) { + return; + } + + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + AllocConsole(); + } + + SetupWindowsConsoleStreams(); + SetConsoleOutputCP(CP_UTF8); + + if (const HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr) { + DWORD consoleMode = 0; + if (GetConsoleMode(stdoutHandle, &consoleMode)) { + SetConsoleMode(stdoutHandle, + consoleMode | ENABLE_PROCESSED_OUTPUT + | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + } +} + +int DuskMain(int argc, char* argv[]) { + WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv)); return game_main(argc, argv); } -void WindowsSetupConsole() { -#if _WIN32 - SetConsoleOutputCP(CP_UTF8); +std::vector WideArgsToUtf8(int argc, wchar_t** argv) { + std::vector utf8Args; + utf8Args.reserve(argc); - auto stdout = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD consoleMode; - GetConsoleMode(stdout, &consoleMode); - SetConsoleMode(stdout, consoleMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + for (int i = 0; i < argc; ++i) { + const int requiredSize = + WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr); + if (requiredSize <= 0) { + utf8Args.emplace_back(); + continue; + } + + std::vector utf8Buffer(static_cast(requiredSize)); + WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr, + nullptr); + utf8Args.emplace_back(utf8Buffer.data()); + } + + return utf8Args; +} + +int RunWindowsGuiEntryPoint() { + int argc = 0; + wchar_t** wideArgv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (wideArgv == nullptr) { + return DuskMain(__argc, __argv); + } + + std::vector utf8Args = WideArgsToUtf8(argc, wideArgv); + LocalFree(wideArgv); + + std::vector argv; + argv.reserve(utf8Args.size()); + for (auto& arg : utf8Args) { + argv.push_back(arg.data()); + } + + return DuskMain(argc, argv.data()); +} +#else +int DuskMain(int argc, char* argv[]) { + return game_main(argc, argv); +} +#endif + +} // namespace + +int main(int argc, char* argv[]) { + return DuskMain(argc, argv); +} + +#if _WIN32 +int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { + return RunWindowsGuiEntryPoint(); +} #endif -} \ No newline at end of file diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 9d3d873acc..4a4f6186f0 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -41,7 +41,8 @@ UserSettings g_userSettings = { .invertCameraXAxis {"game.invertCameraXAxis", false}, // Graphics - .enableBloom {"game.enableBloom", true}, + .bloomMode {"game.bloomMode", BloomMode::Classic}, + .bloomMultiplier {"game.bloomMultiplier", 1.0f}, .enableWaterRefraction {"game.enableWaterRefraction", true}, .enableFrameInterpolation = {"game.enableFrameInterpolation", false}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, @@ -75,7 +76,8 @@ UserSettings g_userSettings = { .graphicsBackend {"backend.graphicsBackend", "auto"}, .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .showPipelineCompilation {"backend.showPipelineCompilation", false}, - .wasPresetChosen {"backend.wasPresetChosen", false} + .wasPresetChosen {"backend.wasPresetChosen", false}, + .enableCrashReporting {"backend.enableCrashReporting", true} } }; @@ -113,7 +115,8 @@ void registerSettings() { Register(g_userSettings.game.instantSaves); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); - Register(g_userSettings.game.enableBloom); + Register(g_userSettings.game.bloomMode); + Register(g_userSettings.game.bloomMultiplier); Register(g_userSettings.game.enableWaterRefraction); Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableFastIronBoots); @@ -137,6 +140,7 @@ void registerSettings() { Register(g_userSettings.backend.skipPreLaunchUI); Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); + Register(g_userSettings.backend.enableCrashReporting); } // Transient settings diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 8c6b5e7c45..51733dad8c 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1259,7 +1259,7 @@ void mDoGph_gInf_c::bloom_c::remove() { } #if TARGET_PC -static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, int dstWidth, int dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) { +static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, u16 dstWidth, u16 dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) { GXSetTexCopyDst(dstWidth, dstHeight, dstFmt, FALSE); GXCopyTex((void*)texID, false); GXInitTexObj(pDst, (void*)texID, dstWidth, dstHeight, dstFmt, GX_CLAMP, GX_CLAMP, GX_FALSE); @@ -1311,35 +1311,58 @@ void mDoGph_gInf_c::bloom_c::draw2() { BlurPass0, BlurPassN = BlurPass0 + MaxDivNum, MaxTexNum, }; - scissor_class divPorts[MaxDivNum]; - divPorts[0] = {0.0f, 0.0f, 1.0f, 1.0f}; // full-size texture - divPorts[1] = {0.0f, 0.0f, 0.5f, 0.5f}; // bloom texture (wide enough, half-tall) - divPorts[2] = {0.5f, 0.0f, 0.25f, 0.25f}; - for (int i = 3; i < ARRAY_SIZE(divPorts); i++) { - auto& port = divPorts[i]; - auto const& prev = divPorts[i - 1]; - port.x_orig = prev.x_orig; - port.y_orig = prev.y_orig + prev.height; - port.width = prev.width * 0.5f; - port.height = prev.height * 0.5f; + struct { + u16 x; + u16 y; + u16 w; + u16 h; + } divRects[MaxDivNum]; + divRects[0] = { + 0, + 0, + static_cast(width), + static_cast(height), + }; + divRects[1] = { + 0, + 0, + static_cast(divRects[0].w / 2), + static_cast(divRects[0].h / 2), + }; + divRects[2] = { + divRects[1].w, + 0, + static_cast(divRects[1].w / 2), + static_cast(divRects[1].h / 2), + }; + for (int i = 3; i < ARRAY_SIZE(divRects); i++) { + const auto& prev = divRects[i - 1]; + divRects[i] = { + prev.x, + static_cast(prev.y + prev.h), + static_cast(prev.w / 2), + static_cast(prev.h / 2), + }; } auto divCopySrc = [&](int divNo) { - auto const& port = divPorts[divNo]; - GXSetTexCopySrc(port.x_orig * width, port.y_orig * height, port.width * width, port.height * height); + auto const& rect = divRects[divNo]; + GXSetTexCopySrc(rect.x, rect.y, rect.w, rect.h); }; TGXTexObj tmpTex[MaxTexNum]; auto divCopyTex = [&](uintptr_t texNo, int divNo) -> GXTexObj * { - auto const& port = divPorts[divNo]; - CopyToTexObj(&tmpTex[texNo], texNo, port.width * width, port.height * height); + auto const& rect = divRects[divNo]; + CopyToTexObj(&tmpTex[texNo], texNo, rect.w, rect.h); return &tmpTex[texNo]; }; auto divQuad = [&](int divNo) { - auto const& port = divPorts[divNo]; - f32 x0 = port.x_orig, y0 = port.y_orig; - f32 x1 = x0 + port.width, y1 = y0 + port.height; + auto const& rect = divRects[divNo]; + f32 x0 = rect.x / width; + f32 y0 = rect.y / height; + f32 x1 = (rect.x + rect.w) / width; + f32 y1 = (rect.y + rect.h) / height; GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition3f32(x0, y0, -5); GXTexCoord2s8(0, 0); @@ -1409,18 +1432,16 @@ void mDoGph_gInf_c::bloom_c::draw2() { // Setup blur filter TEV. GXSetNumTexGens(8); - auto SetupBlurMtx = [](float scale) { u32 texMtxID = GX_TEXMTX0; int angle = 0; for (int texCoord = (int)GX_TEXCOORD0; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); - mDoMtx_stack_c::transS((scale * cM_scos(angle)) * getInvScale(), - scale * cM_ssin(angle), 0.0f); + mDoMtx_stack_c::transS((blurScale * cM_scos(angle)) * getInvScale(), + blurScale * cM_ssin(angle), 0.0f); GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); texMtxID += 3; angle += 0x2000; } - }; GXSetNumTevStages(8); for (int stage = 0; stage < 8; stage++) { @@ -1433,19 +1454,20 @@ void mDoGph_gInf_c::bloom_c::draw2() { } // Successively downsample and apply blurs. - int divStart = 2; - int divNum = 6; + static int divStart = 2; + static int divNum = 6; // inclusive - // Distribute the brightness through each pass. - int totalNumPasses = (divNum - divStart) * 2; // down, up - float brightnessF32 = (mBlureRatio / 255.0f); + // The original mBlureRatio is multiplied into each sample, of which there are 8 samples originally. + // This is applied over two passes, the second one with an alpha of 25%; however, the clipping that this introduces is a bit integral to the look, + // so we do the same thing, letting it clip. + float brightnessF32 = (mBlureRatio * 16 / 255.0f); + + // Distribute the brightness through the total number of passes. + f32 totalNumPasses = (divNum - divStart + 1); float brightnessPerPass = 255.0f * powf(brightnessF32, 1.0f / totalNumPasses); GXSetTevColorS10(GX_TEVREG1, {0, 0, 0, s16(brightnessPerPass / 8)}); for (int i = divStart; i < divNum; i++) { - float blurStrength = 1.0f + (i - divStart) * 5.0f; - SetupBlurMtx(blurScale * blurStrength); - // Apply blur filter. divQuad(i); @@ -1457,16 +1479,18 @@ void mDoGph_gInf_c::bloom_c::draw2() { GXLoadTexObj(blurTex, GX_TEXMAP0); } - // All the way down at the bottom. Instead of blurring the bottom layer by itself, we blur when going up to the next layer. - // The remaining upscales are all just normal alpha blending. + // All the way down at the bottom. divQuad(divNum); // Now successively alpha blend back up, don't blur anymore. GXSetNumTevStages(1); - GXSetTevColorS10(GX_TEVREG1, {0, 0, 0, s16(brightnessPerPass)}); + GXSetTevColorS10(GX_TEVREG1, {0, 0, 0, s16(255)}); GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); - GXSetBlendMode(GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_OR); + GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_OR); for (int i = divNum; i > divStart; i--) { + float alpha = 255.0f * powf(0.25f * dusk::getSettings().game.bloomMultiplier.getValue(), 1.0f / (divNum - i + 1)); + GXSetTevColorS10(GX_TEVREG0, {0, 0, 0, s16(alpha)}); + divCopySrc(i); GXTexObj* upTex = divCopyTex(BlurPass0 + i, i); GXLoadTexObj(upTex, GX_TEXMAP0); @@ -1500,13 +1524,11 @@ void mDoGph_gInf_c::bloom_c::draw2() { void mDoGph_gInf_c::bloom_c::draw() { ZoneScoped; - if (!dusk::getSettings().game.enableBloom) { + if (dusk::getSettings().game.bloomMode.getValue() == dusk::BloomMode::Dusk) { + draw2(); return; } - - static bool s_bloom2 = false; - if (s_bloom2) { - draw2(); + if (dusk::getSettings().game.bloomMode.getValue() != dusk::BloomMode::Classic) { return; } @@ -1598,7 +1620,12 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GXSetBlendMode(GX_BM_NONE, GX_BL_ZERO, GX_BL_ZERO, GX_LO_OR); - GXColorS10 tevColor0 = {(s16)-mPoint, (s16)-mPoint, (s16)-mPoint, 0x40}; +#if TARGET_PC + s16 bloomAlpha = s16(0x40 * dusk::getSettings().game.bloomMultiplier.getValue()); +#else + s16 bloomAlpha = 0x40; +#endif + GXColorS10 tevColor0 = {(s16)-mPoint, (s16)-mPoint, (s16)-mPoint, bloomAlpha}; GXSetTevColorS10(GX_TEVREG0, tevColor0); GXColor tevColor1 = {mBlureRatio, mBlureRatio, mBlureRatio, mBlureRatio}; GXSetTevColor(GX_TEVREG1, tevColor1); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 55f3d28a7e..7f5487d6fe 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -46,6 +46,7 @@ #include #include "SSystem/SComponent/c_API.h" #include "dusk/app_info.hpp" +#include "dusk/crash_reporting.h" #include "dusk/dusk.h" #include "dusk/frame_interpolation.h" #include "dusk/gyro_aim.h" @@ -245,6 +246,8 @@ void main01(void) { } if (dusk::getSettings().game.enableFrameInterpolation) { + dusk::frame_interp::notify_presentation_frame(); + while (accumulator >= kSimStepSeconds) { mDoCPd_c::read(); if (dusk::getSettings().game.enableGyroAim) { @@ -434,6 +437,7 @@ int game_main(int argc, char* argv[]) { arg_options.add_options() ("l,log-level", "Log level from " + std::to_string(AuroraLogLevel::LOG_DEBUG) + " to " + std::to_string(AuroraLogLevel::LOG_FATAL), cxxopts::value()->default_value("0")) ("h,help", "Print usage") + ("console", "Show the Windows console window for logs", cxxopts::value()->default_value("false")->implicit_value("true")) ("dvd", "Path to DVD image file", cxxopts::value()) ("backend", "Graphics API backend to use (auto, d3d12, metal, vulkan, null)", cxxopts::value()) ("cvar", "Override configuration variables without modifying config", cxxopts::value>()); @@ -459,6 +463,7 @@ int game_main(int argc, char* argv[]) { dusk::config::LoadFromUserPreferences(); ApplyCVarOverrides(parsed_arg_options["cvar"]); + dusk::InitializeCrashReporting(); AuroraConfig config{}; config.appName = dusk::AppName; @@ -514,6 +519,7 @@ int game_main(int argc, char* argv[]) { if (!dvd_opened) { // pre game launch ui main loop if (!launchUILoop()) { + dusk::ShutdownCrashReporting(); aurora_shutdown(); return 0; } @@ -551,6 +557,7 @@ int game_main(int argc, char* argv[]) { main01(); + dusk::ShutdownCrashReporting(); fflush(stdout); fflush(stderr); diff --git a/version.h.in b/version.h.in index c85a6dc659..51297bcd46 100644 --- a/version.h.in +++ b/version.h.in @@ -10,11 +10,17 @@ #define DUSK_BUILD_TYPE "@CMAKE_BUILD_TYPE@" #if defined(__x86_64__) || defined(_M_AMD64) -#define DUSK_DLPACKAGE "dusk-@DUSK_WC_DESCRIBE@-@PLATFORM_NAME@-x86_64" +#define DUSK_ARCH "x86_64" #elif defined(__i386__) || defined(_M_IX86) -#define DUSK_DLPACKAGE "dusk-@DUSK_WC_DESCRIBE@-@PLATFORM_NAME@-x86" +#define DUSK_ARCH "x86" #elif defined(__aarch64__) || defined(_M_ARM64) -#define DUSK_DLPACKAGE "dusk-@DUSK_WC_DESCRIBE@-@PLATFORM_NAME@-arm64" +#define DUSK_ARCH "arm64" #endif +#define DUSK_PLATFORM_NAME "@PLATFORM_NAME@" +#define DUSK_DLPACKAGE "dusk-@DUSK_WC_DESCRIBE@-" DUSK_PLATFORM_NAME "-" DUSK_ARCH + +#define DUSK_SENTRY_DSN "@DUSK_SENTRY_DSN@" +#define DUSK_SENTRY_ENVIRONMENT "@DUSK_SENTRY_ENVIRONMENT@" + #endif