diff --git a/CMakeLists.txt b/CMakeLists.txt index a9cdbdf05b..6248450276 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,12 +48,17 @@ else () message(STATUS "Unable to find git, commit information will not be available") endif () -if (DUSK_WC_DESCRIBE) - string(REGEX REPLACE "v([0-9]+)\.([0-9]+)\.([0-9]+)\-([0-9]+).*" "\\1.\\2.\\3.\\4" DUSK_VERSION_STRING "${DUSK_WC_DESCRIBE}") - string(REGEX REPLACE "v([0-9]+)\.([0-9]+)\.([0-9]+).*" "\\1.\\2.\\3" DUSK_SHORT_VERSION_STRING "${DUSK_WC_DESCRIBE}") +if (DUSK_WC_DESCRIBE MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9]+).*)?$") + set(DUSK_SHORT_VERSION_STRING "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") + if (CMAKE_MATCH_5) + set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.${CMAKE_MATCH_5}") + else () + set(DUSK_VERSION_STRING "${DUSK_SHORT_VERSION_STRING}.0") + endif () else () set(DUSK_WC_DESCRIBE "UNKNOWN-VERSION") - set(DUSK_VERSION_STRING "0.0.0") + set(DUSK_VERSION_STRING "0.0.0.0") + set(DUSK_SHORT_VERSION_STRING "0.0.0") endif () # Add version information to CI environment variables @@ -103,6 +108,10 @@ if(ANDROID) set(NOD_COMPRESS_ZSTD OFF CACHE BOOL "" FORCE) endif () +option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF) +set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN") +set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment") + if (DUSK_MOVIE_SUPPORT) find_package(libjpeg-turbo QUIET) if (libjpeg-turbo_FOUND) @@ -156,21 +165,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 () @@ -191,6 +202,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) @@ -199,6 +250,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}) @@ -224,6 +279,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) @@ -282,6 +344,9 @@ endif () 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 () if (ANDROID) # SDLActivity loads SDL_main via dlsym on Android. Since aurora::main is a static @@ -296,6 +361,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) @@ -384,6 +478,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 3a11d1bb16..c3c7463be3 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 7507fb6742..2178c1066c 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/actor/d_a_npc2.h b/include/d/actor/d_a_npc2.h index d1d06d7764..c940d14a87 100644 --- a/include/d/actor/d_a_npc2.h +++ b/include/d/actor/d_a_npc2.h @@ -119,8 +119,7 @@ public: public: /* 0x56C */ dBgS_ObjAcch mAcch; - /* 0x744 */ char field_0x744; - /* 0x745 */ u8 field_0x745[0x74c - 0x745]; + /* 0x744 */ char field_0x744[8]; /* 0x74C */ request_of_phase_process_class mPhase; /* 0x754 */ mDoExt_McaMorfSO* mpModelMorf; /* 0x758 */ Z2Creature mSound; diff --git a/include/d/actor/d_a_obj_catdoor.h b/include/d/actor/d_a_obj_catdoor.h index 77d285dc07..dd7141d920 100644 --- a/include/d/actor/d_a_obj_catdoor.h +++ b/include/d/actor/d_a_obj_catdoor.h @@ -12,6 +12,27 @@ public: /* 0xf4 */ s16 angle; }; +class daObjCatDoor_Attr_c { +public: + /* 0x0 */ s16 speed; + /* 0x2 */ s16 decay_rate; +}; + +class daObjCatDoor_Hio_c : public mDoHIO_entry_c { +public: + daObjCatDoor_Hio_c(); + ~daObjCatDoor_Hio_c(); + + void genMessage(JORMContext*); + + void ct(); + void dt(); + void default_set(); + + /* 0x8 */ int field_0x8; + /* 0xC */ daObjCatDoor_Attr_c m_attr; +}; + /** * @ingroup actors-objects * @class daObjCatDoor_c @@ -24,7 +45,7 @@ class daObjCatDoor_c : public fopAc_ac_c { public: inline ~daObjCatDoor_c(); - const s16* attr() const; + const daObjCatDoor_Attr_c* attr() const; void initBaseMtx(); void setBaseMtx(); void calcOpen(); @@ -45,7 +66,7 @@ public: } void setDoorOpen() { - mRotSpeed = attr()[1]; + mRotSpeed = attr()->decay_rate; dBgW* bgw1 = &mDoor1.bgw; if (bgw1->ChkUsed()) { dComIfG_Bgsp().Release(bgw1); @@ -67,7 +88,7 @@ private: /* 0x790 */ s16 mRotSpeed; public: - static s16 const M_attr[2]; + static const daObjCatDoor_Attr_c M_attr; }; #endif /* D_A_OBJ_CATDOOR_H */ 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/logging.h b/include/dusk/logging.h index 7f239d8b45..0a9cbf238d 100644 --- a/include/dusk/logging.h +++ b/include/dusk/logging.h @@ -7,6 +7,9 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len); namespace dusk { + void InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel); + void ShutdownFileLogging(); + const char* GetLogFilePath(); void SendToStubLog(AuroraLogLevel level, const char* module, const char* message); } 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/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index 5a97f4a326..c6b22654fa 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -31,6 +31,9 @@ public: void create(); void remove(); void draw(); +#if TARGET_PC + void draw2(); +#endif u8 getEnable() { return mEnable; } void setEnable(u8 i_enable) { mEnable = i_enable; } 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_catdoor.cpp b/src/d/actor/d_a_obj_catdoor.cpp index a2438515f2..1be955b6c9 100644 --- a/src/d/actor/d_a_obj_catdoor.cpp +++ b/src/d/actor/d_a_obj_catdoor.cpp @@ -10,35 +10,38 @@ static const char* l_arcName = "CatDoor"; -s16 const daObjCatDoor_c::M_attr[2] = {0x001E, 0x0578}; +const daObjCatDoor_Attr_c daObjCatDoor_c::M_attr = { + 30, + 1400, +}; daObjCatDoor_c::~daObjCatDoor_c() { if (mDoor1.bgw.ChkUsed()) { dComIfG_Bgsp().Release(&mDoor1.bgw); } + if (mDoor2.bgw.ChkUsed()) { dComIfG_Bgsp().Release(&mDoor2.bgw); } + dComIfG_resDelete(&mPhaseReq, l_arcName); } int daObjCatDoor_c::createHeap() { J3DModelData* modelData = (J3DModelData*)dComIfG_getObjectRes(l_arcName, 4); + JUT_ASSERT(174, modelData != NULL); - ASSERT(modelData != NULL); mDoor1.pmodel = mDoExt_J3DModel__create(modelData, 0x80000, 0x11000084); mDoor2.pmodel = mDoExt_J3DModel__create(modelData, 0x80000, 0x11000084); if (mDoor1.pmodel == NULL || mDoor2.pmodel == NULL) { return 0; } - cBgD_t* cbgd = (cBgD_t*)dComIfG_getObjectRes(l_arcName, 7); - if (mDoor1.bgw.Set(cbgd, 1, &mDoor1.mtx)) { + if (mDoor1.bgw.Set((cBgD_t*)dComIfG_getObjectRes(l_arcName, 7), 1, &mDoor1.mtx)) { return 0; } - cBgD_t* cbgd2 = (cBgD_t*)dComIfG_getObjectRes(l_arcName, 7); - if (mDoor2.bgw.Set(cbgd2, 1, &mDoor2.mtx)) { + if (mDoor2.bgw.Set((cBgD_t*)dComIfG_getObjectRes(l_arcName, 7), 1, &mDoor2.mtx)) { return 0; } @@ -48,9 +51,9 @@ int daObjCatDoor_c::createHeap() { int daObjCatDoor_c::draw() { g_env_light.settingTevStruct(0x10, ¤t.pos, &tevStr); - fopAc_ac_c* p1 = static_cast(this); - g_env_light.setLightTevColorType_MAJI(mDoor1.pmodel, &p1->tevStr); - g_env_light.setLightTevColorType_MAJI(mDoor2.pmodel, &p1->tevStr); + fopAc_ac_c* actor = (fopAc_ac_c*)this; + g_env_light.setLightTevColorType_MAJI(mDoor1.pmodel, &actor->tevStr); + g_env_light.setLightTevColorType_MAJI(mDoor2.pmodel, &actor->tevStr); dComIfGd_setListBG(); mDoExt_modelUpdateDL(mDoor1.pmodel); @@ -60,22 +63,22 @@ int daObjCatDoor_c::draw() { } int daObjCatDoor_c::execute() { - if (dComIfGs_isSwitch(fopAcM_GetParam(this) & 0xFF, fopAcM_GetHomeRoomNo(this)) || - mRotSpeed == 0) - { + if (fopAcM_isSwitch(this, getSwitchNo()) || mRotSpeed == 0) { return 1; } + calcOpen(); setBaseMtx(); return 1; } -const s16* daObjCatDoor_c::attr() const { - return daObjCatDoor_c::M_attr; +const daObjCatDoor_Attr_c* daObjCatDoor_c::attr() const { + return &daObjCatDoor_c::M_attr; } -static int createSolidHeap(fopAc_ac_c* i_this) { - return static_cast(i_this)->createHeap(); +static int createSolidHeap(fopAc_ac_c* actor) { + daObjCatDoor_c* i_this = (daObjCatDoor_c*)actor; + return i_this->createHeap(); } int daObjCatDoor_c::create() { @@ -84,7 +87,7 @@ int daObjCatDoor_c::create() { int phase_state = dComIfG_resLoad(&mPhaseReq, l_arcName); if (phase_state == cPhs_COMPLEATE_e) { if (!fopAcM_entrySolidHeap(this, createSolidHeap, 0x2520)) { - phase_state = cPhs_ERROR_e; + return cPhs_ERROR_e; } else { create_init(); } @@ -93,7 +96,8 @@ int daObjCatDoor_c::create() { } void daObjCatDoor_c::create_init() { - ASSERT(getSwitchNo() != 0xff); + JUT_ASSERT(295, getSwitchNo() != 0xff); + fopAcM_setCullSizeBox(this, -200.0f, 0.0f, -20.0f, 200.0f, 260.0f, 100.0f); if (fopAcM_isSwitch(this, getSwitchNo())) { mDoor1.angle = 0x8800; @@ -106,37 +110,40 @@ void daObjCatDoor_c::create_init() { mDoor2.bgw.SetRoomId(fopAcM_GetRoomNo(this)); dComIfG_Bgsp().Regist(&mDoor2.bgw, this); } + initBaseMtx(); } void daObjCatDoor_c::initBaseMtx() { - cullMtx = mMtx; + fopAcM_SetMtx(this, mMtx); mDoMtx_stack_c::transS(current.pos); - mDoMtx_YrotM(mDoMtx_stack_c::get(), shape_angle.y); - mDoMtx_copy(mDoMtx_stack_c::get(), mMtx); + mDoMtx_stack_c::YrotM(shape_angle.y); + cMtx_copy(mDoMtx_stack_c::get(), mMtx); setBaseMtx(); } void daObjCatDoor_c::setBaseMtx() { mDoMtx_stack_c::transS(current.pos); - mDoMtx_YrotM(mDoMtx_stack_c::get(), shape_angle.y); + mDoMtx_stack_c::YrotM(shape_angle.y); + for (int i = 0; i < 2; i++) { daObjCatDoor_Door_c* door = i == 0 ? &mDoor1 : &mDoor2; f32 xOff = i == 0 ? -97.0f : 97.0f; s16 rot = i == 0 ? door->angle : s16(door->angle + 0x8000); + mDoMtx_stack_c::push(); mDoMtx_stack_c::transM(xOff, 0.0, 0.0); - mDoMtx_YrotM(mDoMtx_stack_c::get(), (s16)rot); - mDoMtx_copy(mDoMtx_stack_c::get(), door->pmodel->mBaseTransformMtx); - mDoMtx_copy(mDoMtx_stack_c::get(), door->mtx); + mDoMtx_stack_c::YrotM((s16)rot); + door->pmodel->setBaseTRMtx(mDoMtx_stack_c::get()); + cMtx_copy(mDoMtx_stack_c::get(), door->mtx); door->bgw.Move(); mDoMtx_stack_c::pop(); } } void daObjCatDoor_c::calcOpen() { - s16 prev = mRotSpeed; - int res = cLib_chaseS(&mRotSpeed, 0, *attr()); + s16 prev = (s16)mRotSpeed; + int res = cLib_chaseS(&mRotSpeed, 0, attr()->speed); for (int i = 0; i < 2; i++) { daObjCatDoor_Door_c* door = i == 0 ? &mDoor1 : &mDoor2; if (i == 0) { @@ -159,20 +166,20 @@ static int daObjCatDoor_Execute(daObjCatDoor_c* i_this) { return static_cast(i_this)->execute(); } -static BOOL daObjCatDoor_IsDelete(daObjCatDoor_c* i_this) { - return TRUE; +static int daObjCatDoor_IsDelete(daObjCatDoor_c* i_this) { + return 1; } static int daObjCatDoor_Delete(daObjCatDoor_c* i_this) { - fopAcM_GetID(i_this); + fpc_ProcID id = fopAcM_GetID(i_this); i_this->~daObjCatDoor_c(); return 1; } -static int daObjCatDoor_Create(fopAc_ac_c* i_this) { - fopAcM_GetID(i_this); - daObjCatDoor_c* a_this = static_cast(i_this); - return a_this->create(); +static int daObjCatDoor_Create(fopAc_ac_c* actor) { + daObjCatDoor_c* i_this = (daObjCatDoor_c*)actor; + fpc_ProcID id = fopAcM_GetID(actor); + return i_this->create(); } static actor_method_class l_daObjCatDoor_Method = { diff --git a/src/d/actor/d_a_obj_gra2.cpp b/src/d/actor/d_a_obj_gra2.cpp index 4eca318eb9..550b680e06 100644 --- a/src/d/actor/d_a_obj_gra2.cpp +++ b/src/d/actor/d_a_obj_gra2.cpp @@ -539,7 +539,7 @@ const char* daObj_GrA_c::getResName() { u8 daObj_GrA_c::getMode() { u32 uVar1 = fopAcM_GetParam(this) >> 28 & 3; - strcpy(&field_0x744, "Obj_grA"); + strcpy(field_0x744, "Obj_grA"); switch (uVar1) { case 1: 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..73f432e418 --- /dev/null +++ b/src/dusk/crash_reporting.cpp @@ -0,0 +1,187 @@ +#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 GetLogAttachmentPath() { + if (const char* logPath = GetLogFilePath()) { + return logPath; + } + return {}; +} + +std::filesystem::path GetCrashpadHandlerPath() { + const char* basePath = SDL_GetBasePath(); + if (!basePath) { + return {}; + } + + const std::filesystem::path handlerDir(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 + + const auto logPath = GetLogAttachmentPath(); + if (!logPath.empty()) { +#if _WIN32 + sentry_options_add_attachmentw(options, logPath.wstring().c_str()); +#else + sentry_options_add_attachment(options, logPath.string().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 eb7bf4c6bc..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); @@ -60,7 +69,7 @@ inline ImFont* CreateFont(float size, const std::string& fontPath, std::string_v void ImGuiEngine_Initialize(float scale) { // Round font scale to nearest integer - scale = std::ceilf(scale); + scale = std::ceil(scale); ImGui::GetCurrentContext(); ImGuiIO& io = ImGui::GetIO(); @@ -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 0f8412187a..c524ed6058 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -84,12 +84,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/logging.cpp b/src/dusk/logging.cpp index ec9b1e9fac..5cfe5d0783 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -1,6 +1,11 @@ #include "dusk/logging.h" +#include +#include #include #include +#include +#include +#include #include "tracy/Tracy.hpp" @@ -26,6 +31,60 @@ static constexpr std::string_view StubFragments[] = { "but selective updates are not implemented"sv, }; +namespace { +std::mutex g_logMutex; +FILE* g_logFile = nullptr; +std::string g_logFilePath; + +const char* LogLevelString(AuroraLogLevel level) { + switch (level) { + case LOG_DEBUG: + return "DEBUG"; + case LOG_INFO: + return "INFO"; + case LOG_WARNING: + return "WARNING"; + case LOG_ERROR: + return "ERROR"; + case LOG_FATAL: + return "FATAL"; + } + + return "??"; +} + +FILE* LogStreamForLevel(AuroraLogLevel level) { + return level >= LOG_ERROR ? stderr : stdout; +} + +std::string MakeTimestampedLogName() { + const auto now = std::chrono::system_clock::now(); + const std::time_t nowTime = std::chrono::system_clock::to_time_t(now); + + std::tm localTime{}; +#if _WIN32 + localtime_s(&localTime, &nowTime); +#else + localtime_r(&nowTime, &localTime); +#endif + + std::array buffer{}; + std::strftime(buffer.data(), buffer.size(), "dusk-%Y%m%d-%H%M%S.log", &localTime); + return buffer.data(); +} + +void WriteLogLine(FILE* out, const char* levelStr, const char* module, const char* message, unsigned int len) { + if (out == nullptr) { + return; + } + + std::fprintf(out, "[%s | %s] ", levelStr, module); + std::fwrite(message, 1, len, out); + std::fputc('\n', out); + std::fflush(out); +} +} // namespace + static bool IsForStubLog(const char* message) { std::string_view msg_view(message); @@ -85,30 +144,22 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m return; } - const char* levelStr = "??"; - FILE* out = stdout; - switch (level) { - case LOG_DEBUG: - levelStr = "DEBUG"; - break; - case LOG_INFO: - levelStr = "INFO"; - break; - case LOG_WARNING: - levelStr = "WARNING"; - break; - case LOG_ERROR: - levelStr = "ERROR"; - out = stderr; - break; - case LOG_FATAL: - levelStr = "FATAL"; - out = stderr; - break; + if (module == nullptr) { + module = ""; } - fprintf(out, "[%s | %s] %s\n", levelStr, module, message); + + const char* levelStr = LogLevelString(level); + FILE* out = LogStreamForLevel(level); + WriteLogLine(out, levelStr, module, message, len); + + { + std::lock_guard lock(g_logMutex); + if (g_logFile != nullptr) { + WriteLogLine(g_logFile, levelStr, module, message, len); + } + } + if (level == LOG_FATAL) { - fflush(out); abort(); } } @@ -116,3 +167,48 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m aurora::Module DuskLog("dusk"); + +void dusk::InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel) { + std::lock_guard lock(g_logMutex); + if (g_logFile != nullptr || configDir == nullptr) { + return; + } + + std::error_code ec; + const std::filesystem::path logsDir = std::filesystem::path(configDir) / "logs"; + std::filesystem::create_directories(logsDir, ec); + if (ec) { + std::fprintf(stderr, "[WARNING | dusk] Failed to create log directory '%s': %s\n", + logsDir.string().c_str(), ec.message().c_str()); + return; + } + + const std::filesystem::path logPath = logsDir / MakeTimestampedLogName(); + g_logFile = std::fopen(logPath.string().c_str(), "wb"); + if (g_logFile == nullptr) { + std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n", + logPath.string().c_str()); + return; + } + + g_logFilePath = logPath.string(); + aurora::g_config.logCallback = &aurora_log_callback; + aurora::g_config.logLevel = logLevel; + WriteLogLine(g_logFile, "INFO", "dusk", "File logging initialized", 24); +} + +void dusk::ShutdownFileLogging() { + std::lock_guard lock(g_logMutex); + if (g_logFile == nullptr) { + return; + } + + std::fflush(g_logFile); + std::fclose(g_logFile); + g_logFile = nullptr; +} + +const char* dusk::GetLogFilePath() { + std::lock_guard lock(g_logMutex); + return g_logFilePath.empty() ? nullptr : g_logFilePath.c_str(); +} 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 dc6f80b63f..51733dad8c 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1258,20 +1258,19 @@ void mDoGph_gInf_c::bloom_c::remove() { mMonoColor.a = 0; } - #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); GXInitTexObjLOD(pDst, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); } -void mDoGph_gInf_c::bloom_c::draw() { +void mDoGph_gInf_c::bloom_c::draw2() { ZoneScoped; - if (!dusk::getSettings().game.enableBloom) { - return; - } + // if (!dusk::getSettings().game.enableBloom) { + // return; + // } bool enabled = mEnable; if (mMonoColor.a == 0 && !enabled) @@ -1279,13 +1278,11 @@ void mDoGph_gInf_c::bloom_c::draw() { f32 width = mDoGph_gInf_c::getWidth(); f32 height = mDoGph_gInf_c::getHeight(); - GXSetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f); - GXSetScissor(0, 0, width, height); GXLoadTexObj(getFrameBufferTexObj(), GX_TEXMAP0); GXSetNumChans(0); GXSetNumTexGens(1); - GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 0x3c); + GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_RED, GX_CH_RED, GX_CH_GREEN); GXSetTevSwapModeTable(GX_TEV_SWAP3, GX_CH_BLUE, GX_CH_BLUE, GX_CH_BLUE, GX_CH_ALPHA); GXSetZCompLoc(1); @@ -1296,7 +1293,7 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetCullMode(GX_CULL_NONE); GXSetDither(1); Mtx44 ortho; - C_MTXOrtho(ortho, 0.0f, height, 0.0f, width, 0.0f, 10.0f); + C_MTXOrtho(ortho, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 10.0f); GXLoadPosMtxImm(cMtx_getIdentity(), 0); GXSetProjection(ortho, GX_ORTHOGRAPHIC); GXSetCurrentMtx(0); @@ -1306,16 +1303,74 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S8, 0); - auto filterQuad = [&](int div) { - f32 x = width / div, y = height / div; + // Set up viewports for our pyramid. + enum { MaxDivNum = 10 }; + enum { + Pass0, + FinalPass, + BlurPass0, BlurPassN = BlurPass0 + MaxDivNum, + MaxTexNum, + }; + 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& 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& rect = divRects[divNo]; + CopyToTexObj(&tmpTex[texNo], texNo, rect.w, rect.h); + return &tmpTex[texNo]; + }; + + auto divQuad = [&](int divNo) { + 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(0, 0, -5); + GXPosition3f32(x0, y0, -5); GXTexCoord2s8(0, 0); - GXPosition3f32(x, 0, -5); + GXPosition3f32(x1, y0, -5); GXTexCoord2s8(1, 0); - GXPosition3f32(x, y, -5); + GXPosition3f32(x1, y1, -5); GXTexCoord2s8(1, 1); - GXPosition3f32(0, y, -5); + GXPosition3f32(x0, y1, -5); GXTexCoord2s8(0, 1); GXEnd(); }; @@ -1330,13 +1385,12 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP1, GX_TEV_SWAP1); GXSetTevColor(GX_TEVREG2, mMonoColor); GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_OR); - filterQuad(1); + divQuad(0); } if (enabled) { - enum PassID { Pass0, Pass1, Pass2 }; - - GXCreateFrameBuffer(width, height); + GXCreateFrameBuffer(width * 0.75f, height * 0.5f); + GXSetViewport(0.0f, 0.0f, width, height, 0.1f, 1.0f); // use oversized viewport to make the math easier GXSetNumTevStages(3); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); @@ -1359,75 +1413,97 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetBlendMode(GX_BM_NONE, GX_BL_ZERO, GX_BL_ZERO, GX_LO_OR); GXColorS10 tevColor0 = {(s16)-mPoint, (s16)-mPoint, (s16)-mPoint, 0x40}; GXSetTevColorS10(GX_TEVREG0, tevColor0); - GXColor tevColor1 = {mBlureRatio, mBlureRatio, mBlureRatio, mBlureRatio}; - GXSetTevColor(GX_TEVREG1, tevColor1); GXPixModeSync(); - filterQuad(2); + + // Create thresholded 1/2 image. + divQuad(1); GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED, GX_CH_RED, GX_CH_RED, GX_CH_ALPHA); GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0); GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0); // Downsample and filter from 1/2 EFB into 1/4. - TGXTexObj tmpTex[3]; - GXSetTexCopySrc(0, 0, width / 2, height / 2); - CopyToTexObj(&tmpTex[0], Pass0, width / 4, height / 4); - GXLoadTexObj(&tmpTex[0], GX_TEXMAP0); + divCopySrc(1); + GXTexObj* texPass0 = divCopyTex(Pass0, 2); + GXLoadTexObj(texPass0, GX_TEXMAP0); + f32 blurScale = mBlureSize * ((448.0f / getHeight()) / 6400.0f); + + // Setup blur filter TEV. GXSetNumTexGens(8); - u32 texMtxID = GX_TEXMTX0; - int angle = 0; - GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); - for (int texCoord = (int)GX_TEXCOORD1; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { - GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); - f32 dVar15 = mBlureSize * ((448.0f / getHeight()) / 6400.0f); - - mDoMtx_stack_c::transS((dVar15 * cM_scos(angle)) * getInvScale(), - dVar15 * cM_ssin(angle), 0.0f); - GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); - - texMtxID += 3; - angle += 0x2492; - } + 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((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); - GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); - GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A1, GX_CC_ZERO); - GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); - GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); - GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); - for (int tevStage = (int)GX_TEVSTAGE1; tevStage < 8; tevStage++) { - GXSetTevOrder((GXTevStageID)tevStage, (GXTexCoordID)tevStage, GX_TEXMAP0, - GX_COLOR_NULL); - GXSetTevColorIn((GXTevStageID)tevStage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A1, GX_CC_CPREV); - GXSetTevColorOp((GXTevStageID)tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, - GX_TEVPREV); - GXSetTevAlphaIn((GXTevStageID)tevStage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0); - GXSetTevAlphaOp((GXTevStageID)tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, - GX_TEVPREV); + for (int stage = 0; stage < 8; stage++) { + GXTevStageID tevStage = (GXTevStageID)stage; + GXSetTevOrder(tevStage, (GXTexCoordID)stage, GX_TEXMAP0, GX_COLOR_NULL); + GXSetTevColorIn(tevStage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A1, stage == 0 ? GX_CC_ZERO : GX_CC_CPREV); + GXSetTevColorOp(tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(tevStage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0); + GXSetTevAlphaOp(tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); } - GXPixModeSync(); - // Blur filter from tmp_tex1 1/4 to EFB 1/4. - filterQuad(4); + // Successively downsample and apply blurs. + static int divStart = 2; + static int divNum = 6; // inclusive - GXSetTexCopySrc(0, 0, width / 4, height / 4); - CopyToTexObj(&tmpTex[1], Pass1, width / 8, height / 8); - GXLoadTexObj(&tmpTex[1], GX_TEXMAP0); + // 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)}); - // Upsample 1/8 buffer back up to 1/4 buffer. - GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_OR); - GXPixModeSync(); - GXInvalidateTexAll(); - filterQuad(4); + for (int i = divStart; i < divNum; i++) { + // Apply blur filter. + divQuad(i); + + // Copy to next layer. + divCopySrc(i); + + // Set up for the next pass down. + GXTexObj * blurTex = divCopyTex(BlurPass0 + i, i + 1); + GXLoadTexObj(blurTex, GX_TEXMAP0); + } + + // 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(255)}); + GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + 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); + divQuad(i - 1); + } // Now that we've upsampled and filtered our final bloom, copy 1/4 buffer. - GXSetTexCopySrc(0, 0, width / 4, height / 4); - CopyToTexObj(&tmpTex[2], Pass2, width / 4, height / 4); - GXLoadTexObj(&tmpTex[2], GX_TEXMAP0); + divCopySrc(2); + GXTexObj* texFinal = divCopyTex(FinalPass, 2); + GXLoadTexObj(texFinal, GX_TEXMAP0); GXRestoreFrameBuffer(); + GXSetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f); // Now blend our bloom into the real FB. GXSetTevColor(GX_TEVREG0, mBlendColor); @@ -1437,14 +1513,25 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0); GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); - GXSetBlendMode(GX_BM_BLEND, mMode == 1 ? GX_BL_INVDSTCLR : GX_BL_ONE, GX_BL_SRCALPHA, GX_LO_OR); + GXSetBlendMode(GX_BM_BLEND, mMode == 1 ? GX_BL_INVDSTCLR : GX_BL_ONE, GX_BL_SRCALPHA, + GX_LO_OR); GXPixModeSync(); GXInvalidateTexAll(); - filterQuad(1); + divQuad(0); } } -#else +#endif + void mDoGph_gInf_c::bloom_c::draw() { + ZoneScoped; + if (dusk::getSettings().game.bloomMode.getValue() == dusk::BloomMode::Dusk) { + draw2(); + return; + } + if (dusk::getSettings().game.bloomMode.getValue() != dusk::BloomMode::Classic) { + return; + } + bool enabled = mEnable && m_buffer != NULL; if (mMonoColor.a != 0 || enabled) { #if TARGET_PC @@ -1533,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); @@ -1563,8 +1655,12 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); for (int texCoord = (int)GX_TEXCOORD1; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); - + + #if TARGET_PC + f32 dVar15 = mBlureSize * ((448.0f / height) / 6400.0f); + #else f32 dVar15 = mBlureSize * (1.0f / 6400.0f); + #endif mDoMtx_stack_c::transS((dVar15 * cM_scos(angle)) * getInvScale(), dVar15 * cM_ssin(angle), 0.0f); @@ -1673,7 +1769,6 @@ void mDoGph_gInf_c::bloom_c::draw() { } } } -#endif static void retry_captue_frame(view_class* param_0, view_port_class* param_1, int param_2) { UNUSED(param_0); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 7aaec4b005..fbe0bce3cc 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -43,9 +43,12 @@ #include #include +#include +#include #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" @@ -240,6 +243,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) { @@ -363,6 +368,48 @@ static const char* CalculateConfigPath() { return result; } +static void EnsureInitialPipelineCache(const char* configDir) { + if (configDir == nullptr) { + return; + } + + const std::filesystem::path configPathFs(configDir); + const std::filesystem::path pipelineCachePath = configPathFs / "pipeline_cache.db"; + if (std::filesystem::exists(pipelineCachePath)) { + return; + } + + const char* basePath = SDL_GetBasePath(); + if (basePath == nullptr) { + DuskLog.warn("Unable to resolve base path while seeding pipeline cache: {}", SDL_GetError()); + return; + } + + const std::filesystem::path initialPipelineCachePath = + std::filesystem::path(basePath) / "initial_pipeline_cache.db"; + if (!std::filesystem::exists(initialPipelineCachePath)) { + DuskLog.info("No bundled initial pipeline cache found at '{}'", initialPipelineCachePath.string()); + return; + } + + std::error_code ec; + std::filesystem::create_directories(configPathFs, ec); + if (ec) { + DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}", + configPathFs.string(), ec.message()); + return; + } + + std::filesystem::copy_file(initialPipelineCachePath, pipelineCachePath, std::filesystem::copy_options::none, ec); + if (ec) { + DuskLog.warn("Failed to seed pipeline cache from '{}' to '{}': {}", + initialPipelineCachePath.string(), pipelineCachePath.string(), ec.message()); + return; + } + + DuskLog.info("Seeded pipeline cache from '{}'", initialPipelineCachePath.string()); +} + static constexpr PADDefaultMapping defaultPadMapping = { .buttons = { {SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A}, @@ -418,6 +465,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>()); @@ -440,9 +488,13 @@ int game_main(int argc, char* argv[]) { } configPath = CalculateConfigPath(); + const auto startupLogLevel = static_cast(parsed_arg_options["log-level"].as()); + dusk::InitializeFileLogging(configPath, startupLogLevel); dusk::config::LoadFromUserPreferences(); ApplyCVarOverrides(parsed_arg_options["cvar"]); + dusk::InitializeCrashReporting(); + EnsureInitialPipelineCache(configPath); AuroraConfig config{}; config.appName = dusk::AppName; @@ -455,7 +507,7 @@ int game_main(int argc, char* argv[]) { config.windowHeight = defaultWindowHeight * 2; config.desiredBackend = ResolveDesiredBackend(parsed_arg_options); config.logCallback = &aurora_log_callback; - config.logLevel = (AuroraLogLevel)parsed_arg_options["log-level"].as(); + config.logLevel = startupLogLevel; config.mem1Size = 256 * 1024 * 1024; config.mem2Size = 24 * 1024 * 1024; config.allowJoystickBackgroundEvents = true; @@ -498,6 +550,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; } @@ -535,6 +588,8 @@ int game_main(int argc, char* argv[]) { main01(); + dusk::ShutdownCrashReporting(); + dusk::ShutdownFileLogging(); 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