mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2025-12-16 13:02:20 +00:00
Options menu, achievements, and installer wizard. (#19)
* Implemented guest-to-host function pointers (WIP) Co-Authored-By: Skyth (Asilkan) <19259897+blueskythlikesclouds@users.noreply.github.com> * function: support more types for function pointers * Initial options menu implementation. * Improve options menu visuals. * Draw fade on borders, center tabs better. * Adjust line sizes, fix tab text centering. * Adjust padding & text sizes. * Fix bar dark gradient effect. * api: ported BlueBlur headers and misc. research * Fix config name padding not getting scaled at different resolutions. * config: use string_view, added method to get value pointer * config: use std::map for reverse enum template * Draw config options manually instead of looping through them. * config: implemented name and enum localisation * config_detail: move implementation to cpp, relocate sources * Implemented accessing options menu via pause and title screen * config: replace MSAA with AntiAliasing enum * options_menu: implemented info panel and text marquee (see TODOs) * Draw selection triangles. * Supersample fonts to 2K. * Implement options menu navigation. * Fix duplicate triangles when selecting options. * Draw scroll bar. * Adjust scroll bar padding. * Further scroll bar padding adjustments. * Draw outer container as an outline. * Improve marquee text scrolling. * CTitleMenu: fix options menu re-entering on A press whilst visible * Make procedural grid pattern more accurate. * Add enum & bool editing. * Update English localisation * Fix input state mapping. * options_menu: hide menu on Y hold * CHudPause: fix crash when opening options menu from village/lab * Implement float slider. * options_menu: round res scale description resolution * options_menu: use config callbacks after setting items * api: fix GameObject layout * camera_patches: implemented camera X/Y invert * options_menu: fix buffered A press selecting first option upon entry * config_locale: update description for Battle Music * config: added Allow Background Input option * options_menu: move ATOC option below Anti-Aliasing * options_menu: only draw header/footer fade in stages * Handle real-time modifications of some video config values. * Converge increments only when holding the left/right button. * Add sound effects to options menu. * Change some sounds used in options menu. * Give the final decide sound to bool toggling. * Add option select animation. * options_menu: only play slider sound between min/max range * Apply category select animation. * config: rename Controls category to Input * Implement intro transition animation for options menu. * audio_patches: implemented music volume * Implement FPS slider. * Prevent ImGui from displaying OS cursor. * Fade container brackets during intro transition. * player_patches: added penalty to Unleash Cancel * config_locale: update English localisation * player_patches: ensure Unleash gauge penalty doesn't dip into negatives * options_menu: fix being unable to press A at least once after opening the menu * CTitleMenu: added open/close sounds to the options menu * audio_patches: implemented Music and SE volume * api: update research * Implemented music volume attenuation for Windows 10+ * api: fix score offset * Add an interval between consecutive playbacks of the slider sound effect in fastIncrement mode * config: implemented enum descriptions * options_menu: fit thumbnail rect to grid, remove menu hide input * options_menu: fix description wrap width * camera_patches: fix FOV at narrow aspect ratios mobile gaming is back on the menu * options_menu: implemented greyed out options and localisation * options_menu: allow providing reasons for greyed out options * audio_patches: check if Windows version is supported * Update PowerRecomp submodule * api: more research * options_menu: forget selected item upon opening * options_menu: restrict XButtonHoming to title and world map * window: always hide mouse cursor The options menu doesn't accept mouse input, so there's not really any point to showing the cursor at all. * Animate category tab background from the center. * Fix clip rect in info panel not getting popped at all times. * Expose texture loader in "video.h". * config: use final names and descriptions, label options to be moved to exports * options_menu: implemented Voice Language (and some misc. clean-up) * Move Voice Language patch to resident_patches * config: added Aspect Ratio option (to be implemented) * options_menu: implemented Subtitles * Remove triple buffering from options menu, turn it to an enum. * window: hide mouse cursor on controller input for windowed mode * window: show window dimensions on title bar when resizing window * api: update research * Accept functions directly in GuestToHostFunction & add memory range asserts. * Add guest_stack_var, improve shared_ptr implementation. * Handle float/double arguments properly in GuestToHostFunction. * CHudPause_patches: allocate options strings on stack * api: update research * guest_stack_var: allow creation without constructing underlying type * memory: make assertions lenient towards nullptr * api: include guest_stack_var in SWA.inl * audio_patches: don't worry about it * Implemented achievement overlay (WIP) * Implemented achievements menu (WIP) * Clean-up, improved animation and layouts * options_menu: fix naming convention * achievements_overlay: implemented queue and hermite interpolation * achievements_menu: implemented animations and improved navigation * achievements_menu: improve animation accuracy * achievements_menu: added timestamps * achievement_data: added checksum and format verification * achievement_menu: improved outro animation * achievement_menu: added total unlocked achievements * achievement_menu: update sprite animation * Update resources submodule * Add installer wizard. * Skip drawing scanlines when height is 0. * Tweak install screen to better match the original * Added arrow circle to installer's header * Move icon header generation to resources submodule * Added missing animations and tweaked other ones for installer * Improve detection for DLC only mode. Add template for message prompts. * Add language picker. * window: update icon resources * Added file_to_c * Fixes to conversion. * Implemented message window * achievement_menu: use selection cursor texture * Update embedded resources * Implemented message window * Merge branch 'bin2c' into options-menu * Update embedded resources * Framework for max width for buttons. * Update embedded resources * Use textures for pause menu containers * audio_patches: check if Windows major version is >=10 Just in case. * installer_wizard: use integer outline for button text * Added arrow circle spinning animation during installation screen * achievement_menu: fix timestamp and scroll bar padding * achievement_overlay: fix achievement name padding * installer_wizard: fix arrow circle spinning animation misaligning * Add Scale and Origin to ImGui shaders. Change text to be squashed. * message_window: implemented mouse input * installer_wizard: implemented message windows * achievement_menu: start marquee before timestamp margin * Fix message box flow. * message_window: use pause container texture * Add extra condition for starting the installer. * message_window: only accept mouse click if option is selected * Implemented safer way to check if the game is loaded * Add queued update when using files pickers. * installer_wizard: implement localisation * installer_wizard: use enum for localisation * message_window: fix visibility persisting after window closes * Fix arrow circle animation and added pulse animation * Come back check space. * Implement ZSTD compression in file_to_c. * Add fade-in/out to installation icons and sleep after hitting 100% * Implement ImGui font atlas caching. * Controller navigation. * Implemented button guide * CTitleStateMenu: fix start button opening old options menu * Update resources submodule * imgui_snapshot: check if game is loaded before accessing XDBF * message_window: added button guide * options_menu: increase button guide side margins * video: disable imgui.ini creation * Use IM_DELETE for deleting the existing font atlas. * Remove redundant FlushViewport call. * Fix ImGui callbacks leaking memory. * Replace unique_ptr reference arguments with raw pointers. * Specialize description for resolution scale by reference. --------- Co-authored-by: Hyper <34012267+hyperbx@users.noreply.github.com> Co-authored-by: PTKay <jp_moura99@outlook.com> Co-authored-by: Dario <dariosamo@gmail.com>
This commit is contained in:
parent
b0562b4360
commit
c0897dd507
78 changed files with 8069 additions and 827 deletions
|
|
@ -2,6 +2,7 @@ cmake_minimum_required (VERSION 3.20)
|
|||
|
||||
include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
|
||||
set(SWA_THIRDPARTY_ROOT ${CMAKE_SOURCE_DIR}/thirdparty)
|
||||
set(SWA_TOOLS_ROOT ${CMAKE_SOURCE_DIR}/tools)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
|
|
@ -14,9 +15,9 @@ endif()
|
|||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
|
||||
add_subdirectory(${SWA_THIRDPARTY_ROOT})
|
||||
project("UnleashedRecomp-ALL")
|
||||
add_subdirectory(${SWA_TOOLS_ROOT})
|
||||
|
||||
include("thirdparty/PowerRecomp/cmake/bin2h.cmake")
|
||||
project("UnleashedRecomp-ALL")
|
||||
|
||||
# Include sub-projects.
|
||||
add_subdirectory("UnleashedRecompLib")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,36 @@ set(TARGET_NAME "SWA")
|
|||
|
||||
option(SWA_XAUDIO2 "Use XAudio2 for audio playback" OFF)
|
||||
|
||||
function(BIN2C)
|
||||
cmake_parse_arguments(BIN2C_ARGS "" "TARGET_OBJ;SOURCE_FILE;DEST_FILE;ARRAY_NAME;COMPRESSION_TYPE" "" ${ARGN})
|
||||
|
||||
if(NOT BIN2C_ARGS_TARGET_OBJ)
|
||||
message(FATAL_ERROR "TARGET_OBJ not specified.")
|
||||
endif()
|
||||
|
||||
if(NOT BIN2C_ARGS_SOURCE_FILE)
|
||||
message(FATAL_ERROR "SOURCE_FILE not specified.")
|
||||
endif()
|
||||
|
||||
if(NOT BIN2C_ARGS_DEST_FILE)
|
||||
set(BIN2C_ARGS_DEST_FILE "${BIN2C_ARGS_SOURCE_FILE}")
|
||||
endif()
|
||||
|
||||
if(NOT BIN2C_ARGS_COMPRESSION_TYPE)
|
||||
set(BIN2C_ARGS_COMPRESSION_TYPE "none")
|
||||
endif()
|
||||
|
||||
add_custom_command(OUTPUT "${BIN2C_ARGS_DEST_FILE}.c"
|
||||
COMMAND file_to_c "${BIN2C_ARGS_SOURCE_FILE}" "${BIN2C_ARGS_ARRAY_NAME}" "${BIN2C_ARGS_COMPRESSION_TYPE}" "${BIN2C_ARGS_DEST_FILE}.c" "${BIN2C_ARGS_DEST_FILE}.h"
|
||||
DEPENDS "${BIN2C_ARGS_SOURCE_FILE}" file_to_c
|
||||
BYPRODUCTS "${BIN2C_ARGS_DEST_FILE}.h"
|
||||
COMMENT "Generating binary header for ${BIN2C_ARGS_SOURCE_FILE}..."
|
||||
)
|
||||
|
||||
set_source_files_properties(${BIN2C_ARGS_DEST_FILE}.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
|
||||
target_sources(${BIN2C_ARGS_TARGET_OBJ} PRIVATE ${BIN2C_ARGS_DEST_FILE}.c)
|
||||
endfunction()
|
||||
|
||||
add_compile_options(
|
||||
/fp:strict
|
||||
-march=sandybridge
|
||||
|
|
@ -23,19 +53,10 @@ add_compile_definitions(
|
|||
_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR # Microsoft wtf?
|
||||
_CRT_SECURE_NO_WARNINGS)
|
||||
|
||||
# Generate icon bitmap header for SDL surface.
|
||||
BIN2H(SOURCE_FILE "../UnleashedRecompResources/images/game_icon.bmp" HEADER_FILE "res/icon.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_icon")
|
||||
BIN2H(SOURCE_FILE "../UnleashedRecompResources/images/game_icon_night.bmp" HEADER_FILE "res/icon_night.h" ARRAY_TYPE "unsigned char" VARIABLE_NAME "g_iconNight")
|
||||
|
||||
set(SWA_PRECOMPILED_HEADERS
|
||||
"stdafx.h"
|
||||
)
|
||||
|
||||
set(SWA_CFG_CXX_SOURCES
|
||||
"cfg/config.cpp"
|
||||
"cfg/config_detail.cpp"
|
||||
)
|
||||
|
||||
set(SWA_KERNEL_CXX_SOURCES
|
||||
"kernel/imports.cpp"
|
||||
"kernel/xdm.cpp"
|
||||
|
|
@ -53,6 +74,7 @@ set(SWA_CPU_CXX_SOURCES
|
|||
|
||||
set(SWA_GPU_CXX_SOURCES
|
||||
"gpu/video.cpp"
|
||||
"gpu/imgui_common.cpp"
|
||||
"gpu/imgui_snapshot.cpp"
|
||||
"gpu/rhi/plume_d3d12.cpp"
|
||||
"gpu/rhi/plume_vulkan.cpp"
|
||||
|
|
@ -74,7 +96,11 @@ set(SWA_HID_CXX_SOURCES
|
|||
)
|
||||
|
||||
set(SWA_PATCHES_CXX_SOURCES
|
||||
"patches/ui/CHudPause_patches.cpp"
|
||||
"patches/ui/CTitleStateMenu_patches.cpp"
|
||||
"patches/ui/frontend_listener.cpp"
|
||||
"patches/audio_patches.cpp"
|
||||
"patches/camera_patches.cpp"
|
||||
"patches/fps_patches.cpp"
|
||||
"patches/misc_patches.cpp"
|
||||
"patches/object_patches.cpp"
|
||||
|
|
@ -84,6 +110,13 @@ set(SWA_PATCHES_CXX_SOURCES
|
|||
)
|
||||
|
||||
set(SWA_UI_CXX_SOURCES
|
||||
"ui/achievement_menu.cpp"
|
||||
"ui/achievement_overlay.cpp"
|
||||
"ui/installer_wizard.cpp"
|
||||
"ui/button_guide.cpp"
|
||||
"ui/message_window.cpp"
|
||||
"ui/options_menu.cpp"
|
||||
"ui/sdl_listener.cpp"
|
||||
"ui/window.cpp"
|
||||
)
|
||||
|
||||
|
|
@ -113,13 +146,19 @@ set_source_files_properties(${LIBMSPACK_C_SOURCES} PROPERTIES SKIP_PRECOMPILE_HE
|
|||
|
||||
set(SMOLV_SOURCE_DIR "${SWA_THIRDPARTY_ROOT}/ShaderRecomp/thirdparty/smol-v/source")
|
||||
|
||||
set(SWA_USER_CXX_SOURCES
|
||||
"user/achievement_data.cpp"
|
||||
"user/config.cpp"
|
||||
"user/config_detail.cpp"
|
||||
)
|
||||
|
||||
set(SWA_CXX_SOURCES
|
||||
"app.cpp"
|
||||
"exports.cpp"
|
||||
"main.cpp"
|
||||
"misc_impl.cpp"
|
||||
"stdafx.cpp"
|
||||
|
||||
${SWA_CFG_CXX_SOURCES}
|
||||
${SWA_KERNEL_CXX_SOURCES}
|
||||
${SWA_CPU_CXX_SOURCES}
|
||||
${SWA_GPU_CXX_SOURCES}
|
||||
|
|
@ -130,6 +169,7 @@ set(SWA_CXX_SOURCES
|
|||
${SWA_INSTALL_CXX_SOURCES}
|
||||
${LIBMSPACK_C_SOURCES}
|
||||
"${SMOLV_SOURCE_DIR}/smolv.cpp"
|
||||
${SWA_USER_CXX_SOURCES}
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
@ -161,6 +201,7 @@ find_package(unofficial-concurrentqueue REQUIRED)
|
|||
find_package(imgui CONFIG REQUIRED)
|
||||
find_package(magic_enum CONFIG REQUIRED)
|
||||
find_package(unofficial-tiny-aes-c CONFIG REQUIRED)
|
||||
find_package(nfd CONFIG REQUIRED)
|
||||
find_path(MINIAUDIO_INCLUDE_DIRS "miniaudio.h")
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
|
||||
|
|
@ -199,6 +240,7 @@ target_link_libraries(UnleashedRecomp PRIVATE
|
|||
imgui::imgui
|
||||
magic_enum::magic_enum
|
||||
unofficial::tiny-aes-c::tiny-aes-c
|
||||
nfd::nfd
|
||||
)
|
||||
|
||||
target_include_directories(UnleashedRecomp PRIVATE
|
||||
|
|
@ -276,3 +318,29 @@ generate_aggregate_header(
|
|||
"${CMAKE_CURRENT_SOURCE_DIR}/api"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/api/SWA.h"
|
||||
)
|
||||
|
||||
set(RESOURCES_SOURCE_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources")
|
||||
set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res")
|
||||
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/general_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/general_window.dds" ARRAY_NAME "g_general_window" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/left_mouse_button.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/left_mouse_button.dds" ARRAY_NAME "g_left_mouse_button" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/mat_comon_x360_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/mat_comon_x360_001.dds" ARRAY_NAME "g_mat_comon_x360_001" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_fade.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_fade.dds" ARRAY_NAME "g_select_fade" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_fill.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_fill.dds" ARRAY_NAME "g_select_fill" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/start_back.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/start_back.dds" ARRAY_NAME "g_start_back" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/arrow_circle.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/arrow_circle.dds" ARRAY_NAME "g_arrow_circle" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_002.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_002.dds" ARRAY_NAME "g_install_002" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_003.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_003.dds" ARRAY_NAME "g_install_003" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_004.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_004.dds" ARRAY_NAME "g_install_004" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_005.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_005.dds" ARRAY_NAME "g_install_005" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_006.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_006.dds" ARRAY_NAME "g_install_006" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds" ARRAY_NAME "g_install_007" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds" ARRAY_NAME "g_install_008" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/miles_electric_icon.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/miles_electric_icon.dds" ARRAY_NAME "g_miles_electric_icon" COMPRESSION_TYPE "zstd")
|
||||
BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd")
|
||||
|
|
|
|||
|
|
@ -212,11 +212,6 @@ namespace Hedgehog::Base
|
|||
return find(str.c_str(), pos);
|
||||
}
|
||||
|
||||
inline size_t CSharedString::rfind(const CSharedString& str, size_t pos) const
|
||||
{
|
||||
return rfind(str.c_str(), pos);
|
||||
}
|
||||
|
||||
inline size_t CSharedString::find_first_of(const CSharedString& str, size_t pos) const
|
||||
{
|
||||
return find_first_of(str.c_str(), pos);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@
|
|||
#include "Hedgehog/Universe/Engine/hhUpdateInfo.h"
|
||||
#include "Hedgehog/Universe/Engine/hhUpdateUnit.h"
|
||||
#include "Hedgehog/Universe/Thread/hhParallelJob.h"
|
||||
#include "SWA/Achievement/AchievementManager.h"
|
||||
#include "SWA/Achievement/AchievementTest.h"
|
||||
#include "SWA/CSD/CsdDatabaseWrapper.h"
|
||||
#include "SWA/CSD/CsdProject.h"
|
||||
#include "SWA/CSD/CsdTexListMirage.h"
|
||||
|
|
@ -56,6 +58,7 @@
|
|||
#include "SWA/HUD/GeneralWindow/GeneralWindow.h"
|
||||
#include "SWA/HUD/Loading/Loading.h"
|
||||
#include "SWA/HUD/Pause/HudPause.h"
|
||||
#include "SWA/HUD/SaveIcon/SaveIcon.h"
|
||||
#include "SWA/HUD/Sonic/HudSonicStage.h"
|
||||
#include "SWA/Movie/MovieDisplayer.h"
|
||||
#include "SWA/Movie/MovieManager.h"
|
||||
|
|
|
|||
22
UnleashedRecomp/api/SWA/Achievement/AchievementManager.h
Normal file
22
UnleashedRecomp/api/SWA/Achievement/AchievementManager.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <SWA.inl>
|
||||
|
||||
namespace SWA::Achievement
|
||||
{
|
||||
class CManager : public Hedgehog::Universe::CUpdateUnit
|
||||
{
|
||||
public:
|
||||
class CMember
|
||||
{
|
||||
public:
|
||||
SWA_INSERT_PADDING(0x08);
|
||||
be<uint32_t> m_AchievementID;
|
||||
};
|
||||
|
||||
SWA_INSERT_PADDING(0x98);
|
||||
xpointer<CMember> m_pMember;
|
||||
be<uint32_t> m_IsUnlocked;
|
||||
SWA_INSERT_PADDING(0x10);
|
||||
};
|
||||
}
|
||||
15
UnleashedRecomp/api/SWA/Achievement/AchievementTest.h
Normal file
15
UnleashedRecomp/api/SWA/Achievement/AchievementTest.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <SWA.inl>
|
||||
|
||||
namespace SWA
|
||||
{
|
||||
class CAchievementTest
|
||||
{
|
||||
public:
|
||||
SWA_INSERT_PADDING(0x38);
|
||||
be<uint32_t> m_Unk1;
|
||||
be<uint32_t> m_AchievementID;
|
||||
uint8_t m_Unk2;
|
||||
};
|
||||
}
|
||||
13
UnleashedRecomp/api/SWA/HUD/SaveIcon/SaveIcon.h
Normal file
13
UnleashedRecomp/api/SWA/HUD/SaveIcon/SaveIcon.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <SWA.inl>
|
||||
|
||||
namespace SWA
|
||||
{
|
||||
class CSaveIcon : Hedgehog::Universe::CUpdateUnit
|
||||
{
|
||||
public:
|
||||
SWA_INSERT_PADDING(0xD8);
|
||||
bool m_IsVisible;
|
||||
};
|
||||
}
|
||||
|
|
@ -35,7 +35,9 @@ namespace SWA
|
|||
public:
|
||||
SWA_INSERT_PADDING(0x20);
|
||||
boost::shared_ptr<CGame> m_pGame;
|
||||
SWA_INSERT_PADDING(0x114);
|
||||
SWA_INSERT_PADDING(0xD4);
|
||||
xpointer<Achievement::CManager> m_pAchievementManager;
|
||||
SWA_INSERT_PADDING(0x3C);
|
||||
xpointer<void> m_spGameParameter;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
#include <app.h>
|
||||
#include <kernel/function.h>
|
||||
#include <ui/window.h>
|
||||
#include <app.h>
|
||||
#include <patches/audio_patches.h>
|
||||
|
||||
bool g_isGameLoaded = false;
|
||||
double g_deltaTime;
|
||||
|
||||
// CApplication::Update
|
||||
PPC_FUNC_IMPL(__imp__sub_822C1130);
|
||||
PPC_FUNC(sub_822C1130)
|
||||
{
|
||||
g_isGameLoaded = true;
|
||||
g_deltaTime = ctx.f1.f64;
|
||||
|
||||
SDL_PumpEvents();
|
||||
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
|
||||
|
||||
Window::Update();
|
||||
AudioPatches::Update(g_deltaTime);
|
||||
|
||||
__imp__sub_822C1130(ctx, base);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
extern bool g_isGameLoaded;
|
||||
extern double g_deltaTime;
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "config_detail.h"
|
||||
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
inline static std::vector<IConfigDef*> Definitions{};
|
||||
|
||||
CONFIG_DEFINE_ENUM("System", ELanguage, Language, ELanguage::English);
|
||||
CONFIG_DEFINE("System", bool, Hints, true);
|
||||
CONFIG_DEFINE("System", bool, ControlTutorial, true);
|
||||
CONFIG_DEFINE_ENUM("System", EScoreBehaviour, ScoreBehaviour, EScoreBehaviour::CheckpointReset);
|
||||
CONFIG_DEFINE("System", bool, UnleashOutOfControlDrain, true);
|
||||
CONFIG_DEFINE("System", bool, WerehogHubTransformVideo, true);
|
||||
CONFIG_DEFINE("System", bool, LogoSkip, false);
|
||||
|
||||
CONFIG_DEFINE("Controls", bool, CameraXInvert, false);
|
||||
CONFIG_DEFINE("Controls", bool, CameraYInvert, false);
|
||||
CONFIG_DEFINE("Controls", bool, XButtonHoming, true);
|
||||
CONFIG_DEFINE("Controls", bool, UnleashCancel, false);
|
||||
|
||||
CONFIG_DEFINE("Audio", float, MusicVolume, 1.0f);
|
||||
CONFIG_DEFINE("Audio", float, SEVolume, 1.0f);
|
||||
CONFIG_DEFINE_ENUM("Audio", EVoiceLanguage, VoiceLanguage, EVoiceLanguage::English);
|
||||
CONFIG_DEFINE("Audio", bool, Subtitles, true);
|
||||
CONFIG_DEFINE("Audio", bool, WerehogBattleMusic, true);
|
||||
|
||||
CONFIG_DEFINE_ENUM("Video", EGraphicsAPI, GraphicsAPI, EGraphicsAPI::D3D12);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowX, WINDOWPOS_CENTRED);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowY, WINDOWPOS_CENTRED);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowWidth, 1280);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowHeight, 720);
|
||||
CONFIG_DEFINE_ENUM("Video", EWindowState, WindowState, EWindowState::Normal);
|
||||
|
||||
CONFIG_DEFINE_CALLBACK("Video", float, ResolutionScale, 1.0f,
|
||||
{
|
||||
def->Value = std::clamp(def->Value, 0.25f, 2.0f);
|
||||
});
|
||||
|
||||
CONFIG_DEFINE("Video", bool, Fullscreen, false);
|
||||
CONFIG_DEFINE("Video", bool, VSync, true);
|
||||
CONFIG_DEFINE("Video", bool, TripleBuffering, true);
|
||||
CONFIG_DEFINE("Video", int32_t, FPS, 60);
|
||||
CONFIG_DEFINE("Video", float, Brightness, 0.5f);
|
||||
CONFIG_DEFINE("Video", size_t, MSAA, 4);
|
||||
CONFIG_DEFINE("Video", size_t, AnisotropicFiltering, 16);
|
||||
CONFIG_DEFINE_ENUM("Video", EShadowResolution, ShadowResolution, EShadowResolution::x4096);
|
||||
CONFIG_DEFINE_ENUM("Video", EGITextureFiltering, GITextureFiltering, EGITextureFiltering::Bicubic);
|
||||
CONFIG_DEFINE("Video", bool, AlphaToCoverage, true);
|
||||
CONFIG_DEFINE("Video", bool, MotionBlur, true);
|
||||
CONFIG_DEFINE("Video", bool, Xbox360ColourCorrection, false);
|
||||
CONFIG_DEFINE_ENUM("Video", EMovieScaleMode, MovieScaleMode, EMovieScaleMode::Fit);
|
||||
CONFIG_DEFINE_ENUM("Video", EUIScaleMode, UIScaleMode, EUIScaleMode::Centre);
|
||||
|
||||
static std::filesystem::path GetUserPath()
|
||||
{
|
||||
if (std::filesystem::exists("portable.txt"))
|
||||
return std::filesystem::current_path();
|
||||
|
||||
std::filesystem::path userPath{};
|
||||
|
||||
// TODO: handle platform-specific paths.
|
||||
PWSTR knownPath = NULL;
|
||||
if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &knownPath) == S_OK)
|
||||
userPath = std::filesystem::path{ knownPath } / USER_DIRECTORY;
|
||||
|
||||
CoTaskMemFree(knownPath);
|
||||
|
||||
return userPath;
|
||||
}
|
||||
|
||||
static std::filesystem::path GetConfigPath()
|
||||
{
|
||||
return GetUserPath() / TOML_FILE;
|
||||
}
|
||||
|
||||
static std::filesystem::path GetSavePath()
|
||||
{
|
||||
return GetUserPath() / "save";
|
||||
}
|
||||
|
||||
static void Load();
|
||||
static void Save();
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#include "config.h"
|
||||
#include "config_detail.h"
|
||||
|
||||
// CONFIG_DEFINE
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue)
|
||||
{
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
||||
// CONFIG_DEFINE_ENUM
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map<std::string, T> enumTemplate)
|
||||
: Section(section), Name(name), DefaultValue(defaultValue), EnumTemplate(enumTemplate)
|
||||
{
|
||||
for (const auto& pair : EnumTemplate)
|
||||
EnumTemplateReverse[pair.second] = pair.first;
|
||||
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
||||
// CONFIG_DEFINE_CALLBACK
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue, std::function<void(ConfigDef<T>*)> readCallback)
|
||||
: Section(section), Name(name), DefaultValue(defaultValue), ReadCallback(readCallback)
|
||||
{
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
|
@ -1,282 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define USER_DIRECTORY "SWA"
|
||||
|
||||
#define TOML_FILE "config.toml"
|
||||
|
||||
#define CONFIG_DEFINE(section, type, name, defaultValue) \
|
||||
inline static ConfigDef<type> name{section, #name, defaultValue};
|
||||
|
||||
#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \
|
||||
inline static std::unordered_map<std::string, type> g_##type##_template =
|
||||
|
||||
#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \
|
||||
inline static ConfigDef<type> name{section, #name, defaultValue, g_##type##_template};
|
||||
|
||||
#define CONFIG_DEFINE_CALLBACK(section, type, name, defaultValue, readCallback) \
|
||||
inline static ConfigDef<type> name{section, #name, defaultValue, [](ConfigDef<type>* def) readCallback};
|
||||
|
||||
#define CONFIG_GET_DEFAULT(name) Config::name.DefaultValue
|
||||
#define CONFIG_SET_DEFAULT(name) Config::name.MakeDefault();
|
||||
|
||||
#define WINDOWPOS_CENTRED 0x2FFF0000
|
||||
|
||||
class IConfigDef
|
||||
{
|
||||
public:
|
||||
virtual ~IConfigDef() = default;
|
||||
virtual void ReadValue(toml::v3::ex::parse_result& toml) = 0;
|
||||
virtual void MakeDefault() = 0;
|
||||
virtual std::string GetSection() const = 0;
|
||||
virtual std::string GetName() const = 0;
|
||||
virtual std::string GetDefinition(bool withSection = false) const = 0;
|
||||
virtual std::string ToString() const = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ConfigDef : public IConfigDef
|
||||
{
|
||||
public:
|
||||
std::string Section{};
|
||||
std::string Name{};
|
||||
T DefaultValue{};
|
||||
T Value{ DefaultValue };
|
||||
std::unordered_map<std::string, T> EnumTemplate{};
|
||||
std::unordered_map<T, std::string> EnumTemplateReverse{};
|
||||
std::function<void(ConfigDef<T>*)> ReadCallback;
|
||||
|
||||
// CONFIG_DEFINE
|
||||
ConfigDef(std::string section, std::string name, T defaultValue);
|
||||
|
||||
// CONFIG_DEFINE_ENUM
|
||||
ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map<std::string, T> enumTemplate);
|
||||
|
||||
// CONFIG_DEFINE_CALLBACK
|
||||
ConfigDef(std::string section, std::string name, T defaultValue, std::function<void(ConfigDef<T>*)> readCallback);
|
||||
|
||||
void ReadValue(toml::v3::ex::parse_result& toml) override
|
||||
{
|
||||
if (auto pSection = toml[Section].as_table())
|
||||
{
|
||||
const auto& section = *pSection;
|
||||
|
||||
if constexpr (std::is_same<T, std::string>::value)
|
||||
{
|
||||
Value = section[Name].value_or<std::string>(DefaultValue);
|
||||
}
|
||||
else if constexpr (std::is_enum_v<T>)
|
||||
{
|
||||
auto it = EnumTemplate.begin();
|
||||
|
||||
Value = EnumTemplate[section[Name].value_or<std::string>(static_cast<std::string>(it->first))];
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = section[Name].value_or(DefaultValue);
|
||||
}
|
||||
|
||||
if (ReadCallback)
|
||||
ReadCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
void MakeDefault() override
|
||||
{
|
||||
Value = DefaultValue;
|
||||
}
|
||||
|
||||
std::string GetSection() const override
|
||||
{
|
||||
return Section;
|
||||
}
|
||||
|
||||
std::string GetName() const override
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
std::string GetDefinition(bool withSection = false) const override
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (withSection)
|
||||
result += "[" + Section + "]\n";
|
||||
|
||||
result += Name + " = " + ToString();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ToString() const override
|
||||
{
|
||||
if constexpr (std::is_same<T, std::string>::value)
|
||||
{
|
||||
return std::format("\"{}\"", Value);
|
||||
}
|
||||
else if constexpr (std::is_enum_v<T>)
|
||||
{
|
||||
auto it = EnumTemplateReverse.find(Value);
|
||||
|
||||
if (it != EnumTemplateReverse.end())
|
||||
{
|
||||
return std::format("\"{}\"", it->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "\"N/A\"";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::format("{}", Value);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigDef& operator=(const ConfigDef& other)
|
||||
{
|
||||
if (this != &other)
|
||||
Value = other.Value;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
void operator=(const T& other)
|
||||
{
|
||||
Value = other;
|
||||
}
|
||||
};
|
||||
|
||||
enum class ELanguage : uint32_t
|
||||
{
|
||||
English = 1,
|
||||
Japanese,
|
||||
German,
|
||||
French,
|
||||
Spanish,
|
||||
Italian
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(ELanguage)
|
||||
{
|
||||
{ "English", ELanguage::English },
|
||||
{ "Japanese", ELanguage::Japanese },
|
||||
{ "German", ELanguage::German },
|
||||
{ "French", ELanguage::French },
|
||||
{ "Spanish", ELanguage::Spanish },
|
||||
{ "Italian", ELanguage::Italian }
|
||||
};
|
||||
|
||||
enum class EScoreBehaviour : uint32_t
|
||||
{
|
||||
CheckpointReset,
|
||||
CheckpointRetain
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EScoreBehaviour)
|
||||
{
|
||||
{ "CheckpointReset", EScoreBehaviour::CheckpointReset },
|
||||
{ "CheckpointRetain", EScoreBehaviour::CheckpointRetain }
|
||||
};
|
||||
|
||||
enum class EVoiceLanguage : uint32_t
|
||||
{
|
||||
English,
|
||||
Japanese
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EVoiceLanguage)
|
||||
{
|
||||
{ "English", EVoiceLanguage::English },
|
||||
{ "Japanese", EVoiceLanguage::Japanese }
|
||||
};
|
||||
|
||||
enum class EGraphicsAPI : uint32_t
|
||||
{
|
||||
D3D12,
|
||||
Vulkan
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EGraphicsAPI)
|
||||
{
|
||||
{ "D3D12", EGraphicsAPI::D3D12 },
|
||||
{ "Vulkan", EGraphicsAPI::Vulkan }
|
||||
};
|
||||
|
||||
enum class EWindowState : uint32_t
|
||||
{
|
||||
Normal,
|
||||
Maximised
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EWindowState)
|
||||
{
|
||||
{ "Normal", EWindowState::Normal },
|
||||
{ "Maximised", EWindowState::Maximised },
|
||||
{ "Maximized", EWindowState::Maximised }
|
||||
};
|
||||
|
||||
enum class EShadowResolution : int32_t
|
||||
{
|
||||
Original = -1,
|
||||
x512 = 512,
|
||||
x1024 = 1024,
|
||||
x2048 = 2048,
|
||||
x4096 = 4096,
|
||||
x8192 = 8192
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EShadowResolution)
|
||||
{
|
||||
{ "Original", EShadowResolution::Original },
|
||||
{ "512", EShadowResolution::x512 },
|
||||
{ "1024", EShadowResolution::x1024 },
|
||||
{ "2048", EShadowResolution::x2048 },
|
||||
{ "4096", EShadowResolution::x4096 },
|
||||
{ "8192", EShadowResolution::x8192 },
|
||||
};
|
||||
|
||||
enum class EGITextureFiltering : uint32_t
|
||||
{
|
||||
Linear,
|
||||
Bicubic
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EGITextureFiltering)
|
||||
{
|
||||
{ "Linear", EGITextureFiltering::Linear },
|
||||
{ "Bicubic", EGITextureFiltering::Bicubic }
|
||||
};
|
||||
|
||||
enum class EMovieScaleMode : uint32_t
|
||||
{
|
||||
Stretch,
|
||||
Fit,
|
||||
Fill
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EMovieScaleMode)
|
||||
{
|
||||
{ "Stretch", EMovieScaleMode::Stretch },
|
||||
{ "Fit", EMovieScaleMode::Fit },
|
||||
{ "Fill", EMovieScaleMode::Fill }
|
||||
};
|
||||
|
||||
enum class EUIScaleMode : uint32_t
|
||||
{
|
||||
Stretch,
|
||||
Edge,
|
||||
Centre
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EUIScaleMode)
|
||||
{
|
||||
{ "Stretch", EUIScaleMode::Stretch },
|
||||
{ "Edge", EUIScaleMode::Edge },
|
||||
{ "Centre", EUIScaleMode::Centre },
|
||||
{ "Center", EUIScaleMode::Centre }
|
||||
};
|
||||
9
UnleashedRecomp/decompressor.h
Normal file
9
UnleashedRecomp/decompressor.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
template<size_t N>
|
||||
static std::unique_ptr<uint8_t[]> decompressZstd(const uint8_t(&data)[N], size_t decompressedSize)
|
||||
{
|
||||
auto decompressedData = std::make_unique<uint8_t[]>(decompressedSize);
|
||||
ZSTD_decompress(decompressedData.get(), decompressedSize, data, N);
|
||||
return decompressedData;
|
||||
}
|
||||
31
UnleashedRecomp/exports.cpp
Normal file
31
UnleashedRecomp/exports.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#include <kernel/function.h>
|
||||
#include <kernel/heap.h>
|
||||
#include <kernel/memory.h>
|
||||
#include <cpu/guest_stack_var.h>
|
||||
#include <ui/installer_wizard.h>
|
||||
#include <ui/window.h>
|
||||
#include <api/boost/smart_ptr/shared_ptr.h>
|
||||
|
||||
SWA_API void Game_PlaySound(const char* pName)
|
||||
{
|
||||
// TODO: use own sound player.
|
||||
if (InstallerWizard::s_isVisible)
|
||||
return;
|
||||
|
||||
guest_stack_var<boost::anonymous_shared_ptr> soundPlayer;
|
||||
GuestToHostFunction<void>(sub_82B4DF50, soundPlayer.get(), ((be<uint32_t>*)g_memory.Translate(0x83367900))->get(), 7, 0, 0);
|
||||
|
||||
auto soundPlayerVtable = (be<uint32_t>*)g_memory.Translate(*(be<uint32_t>*)soundPlayer->get());
|
||||
uint32_t virtualFunction = *(soundPlayerVtable + 1);
|
||||
|
||||
size_t strLen = strlen(pName);
|
||||
void* strAllocation = g_userHeap.Alloc(strLen + 1);
|
||||
memcpy(strAllocation, pName, strLen + 1);
|
||||
GuestToHostFunction<void>(virtualFunction, soundPlayer->get(), strAllocation, 0);
|
||||
g_userHeap.Free(strAllocation);
|
||||
}
|
||||
|
||||
SWA_API void Window_SetFullscreen(bool isEnabled)
|
||||
{
|
||||
Window::SetFullscreen(isEnabled);
|
||||
}
|
||||
4
UnleashedRecomp/exports.h
Normal file
4
UnleashedRecomp/exports.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
SWA_API void Game_PlaySound(const char* pName);
|
||||
SWA_API void Window_SetFullscreen(bool isEnabled);
|
||||
|
|
@ -74,3 +74,23 @@ constexpr size_t FirstBitLow(TValue value)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::unique_ptr<uint8_t[]> ReadAllBytes(const char* filePath, size_t& fileSize)
|
||||
{
|
||||
FILE* file = fopen(filePath, "rb");
|
||||
|
||||
if (!file)
|
||||
return std::make_unique<uint8_t[]>(0);
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
|
||||
fileSize = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
auto data = std::make_unique<uint8_t[]>(fileSize);
|
||||
fread(data.get(), 1, fileSize, file);
|
||||
|
||||
fclose(file);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
22
UnleashedRecomp/gpu/imgui_common.cpp
Normal file
22
UnleashedRecomp/gpu/imgui_common.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include "imgui_common.h"
|
||||
|
||||
static std::vector<std::unique_ptr<ImGuiCallbackData>> g_callbackData;
|
||||
static uint32_t g_callbackDataIndex = 0;
|
||||
|
||||
ImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback)
|
||||
{
|
||||
if (g_callbackDataIndex >= g_callbackData.size())
|
||||
g_callbackData.emplace_back(std::make_unique<ImGuiCallbackData>());
|
||||
|
||||
auto& callbackData = g_callbackData[g_callbackDataIndex];
|
||||
++g_callbackDataIndex;
|
||||
|
||||
ImGui::GetForegroundDrawList()->AddCallback(reinterpret_cast<ImDrawCallback>(callback), callbackData.get());
|
||||
|
||||
return callbackData.get();
|
||||
}
|
||||
|
||||
void ResetImGuiCallbacks()
|
||||
{
|
||||
g_callbackDataIndex = 0;
|
||||
}
|
||||
48
UnleashedRecomp/gpu/imgui_common.h
Normal file
48
UnleashedRecomp/gpu/imgui_common.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#define IMGUI_SHADER_MODIFIER_NONE 0
|
||||
#define IMGUI_SHADER_MODIFIER_SCANLINE 1
|
||||
#define IMGUI_SHADER_MODIFIER_CHECKERBOARD 2
|
||||
#define IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON 3
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
enum class ImGuiCallback : int32_t
|
||||
{
|
||||
SetGradient = -1,
|
||||
SetShaderModifier = -2,
|
||||
SetOrigin = -3,
|
||||
SetScale = -4,
|
||||
};
|
||||
|
||||
union ImGuiCallbackData
|
||||
{
|
||||
struct
|
||||
{
|
||||
float gradientMin[2];
|
||||
float gradientMax[2];
|
||||
uint32_t gradientTop;
|
||||
uint32_t gradientBottom;
|
||||
} setGradient;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t shaderModifier;
|
||||
} setShaderModifier;
|
||||
|
||||
struct
|
||||
{
|
||||
float origin[2];
|
||||
} setOrigin;
|
||||
|
||||
struct
|
||||
{
|
||||
float scale[2];
|
||||
} setScale;
|
||||
};
|
||||
|
||||
extern ImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback);
|
||||
|
||||
extern void ResetImGuiCallbacks();
|
||||
|
||||
#endif
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
#include "imgui_snapshot.h"
|
||||
|
||||
#include <locale/locale.h>
|
||||
#include <res/font/im_font_atlas.bin.h>
|
||||
#include <user/config.h>
|
||||
#include <decompressor.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <app.h>
|
||||
|
||||
void ImDrawDataSnapshot::Clear()
|
||||
{
|
||||
for (int n = 0; n < Cache.GetMapSize(); n++)
|
||||
|
|
@ -52,3 +59,214 @@ void ImDrawDataSnapshot::SnapUsingSwap(ImDrawData* src, double current_time)
|
|||
Cache.Remove(GetDrawListID(entry->SrcCopy), entry);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T1, typename T2>
|
||||
void ImFontAtlasSnapshot::SnapPointer(size_t offset, const T1& value, const T2& ptr, size_t count)
|
||||
{
|
||||
if (ptr != nullptr && count != 0)
|
||||
{
|
||||
if (!objects.contains(ptr))
|
||||
{
|
||||
constexpr size_t ALIGN = alignof(std::remove_pointer_t<T2>);
|
||||
constexpr size_t SIZE = sizeof(std::remove_pointer_t<T2>);
|
||||
|
||||
size_t ptrOffset = (data.size() + ALIGN - 1) & ~(ALIGN - 1);
|
||||
data.resize(ptrOffset + SIZE * count);
|
||||
memcpy(&data[ptrOffset], ptr, SIZE * count);
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
size_t curPtrOffset = ptrOffset + SIZE * i;
|
||||
objects[&ptr[i]] = curPtrOffset;
|
||||
Traverse(curPtrOffset, ptr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
size_t fieldOffset = offset + (reinterpret_cast<ptrdiff_t>(&ptr) - reinterpret_cast<ptrdiff_t>(&value));
|
||||
*reinterpret_cast<size_t*>(&data[fieldOffset]) = objects[ptr];
|
||||
offsets.push_back(fieldOffset);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ImFontAtlasSnapshot::Traverse(size_t offset, const T& value)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<T>)
|
||||
{
|
||||
SnapPointer(offset, value, value, 1);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ImFontAtlas>)
|
||||
{
|
||||
SnapPointer(offset, value, value.ConfigData.Data, value.ConfigData.Size);
|
||||
SnapPointer(offset, value, value.CustomRects.Data, value.CustomRects.Size);
|
||||
SnapPointer(offset, value, value.Fonts.Data, value.Fonts.Size);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ImFont>)
|
||||
{
|
||||
SnapPointer(offset, value, value.IndexAdvanceX.Data, value.IndexAdvanceX.Size);
|
||||
SnapPointer(offset, value, value.IndexLookup.Data, value.IndexLookup.Size);
|
||||
SnapPointer(offset, value, value.Glyphs.Data, value.Glyphs.Size);
|
||||
SnapPointer(offset, value, value.FallbackGlyph, 1);
|
||||
SnapPointer(offset, value, value.ContainerAtlas, 1);
|
||||
SnapPointer(offset, value, value.ConfigData, value.ConfigDataCount);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ImFontAtlasCustomRect>)
|
||||
{
|
||||
SnapPointer(offset, value, value.Font, 1);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, ImFontConfig>)
|
||||
{
|
||||
SnapPointer(offset, value, value.GlyphRanges, value.GlyphRanges != nullptr ? wcslen(reinterpret_cast<const wchar_t*>(value.GlyphRanges)) + 1 : 0);
|
||||
SnapPointer(offset, value, value.DstFont, 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
size_t ImFontAtlasSnapshot::Snap(const T& value)
|
||||
{
|
||||
size_t offset = (data.size() + alignof(T) - 1) & ~(alignof(T) - 1);
|
||||
data.resize(offset + sizeof(T));
|
||||
memcpy(&data[offset], &value, sizeof(T));
|
||||
objects[&value] = offset;
|
||||
Traverse(offset, value);
|
||||
return offset;
|
||||
}
|
||||
|
||||
struct ImFontAtlasSnapshotHeader
|
||||
{
|
||||
uint32_t imguiVersion;
|
||||
uint32_t dataOffset;
|
||||
uint32_t offsetCount;
|
||||
uint32_t offsetsOffset;
|
||||
};
|
||||
|
||||
void ImFontAtlasSnapshot::Snap()
|
||||
{
|
||||
data.resize(sizeof(ImFontAtlasSnapshotHeader));
|
||||
size_t dataOffset = Snap(*ImGui::GetIO().Fonts);
|
||||
|
||||
size_t offsetsOffset = data.size();
|
||||
std::sort(offsets.begin(), offsets.end());
|
||||
|
||||
data.insert(data.end(),
|
||||
reinterpret_cast<uint8_t*>(offsets.data()),
|
||||
reinterpret_cast<uint8_t*>(offsets.data() + offsets.size()));
|
||||
|
||||
auto header = reinterpret_cast<ImFontAtlasSnapshotHeader*>(data.data());
|
||||
header->imguiVersion = IMGUI_VERSION_NUM;
|
||||
header->dataOffset = dataOffset;
|
||||
header->offsetCount = offsets.size();
|
||||
header->offsetsOffset = offsetsOffset;
|
||||
}
|
||||
|
||||
static std::unique_ptr<uint8_t[]> g_imFontAtlas;
|
||||
|
||||
ImFontAtlas* ImFontAtlasSnapshot::Load()
|
||||
{
|
||||
g_imFontAtlas = decompressZstd(g_im_font_atlas, g_im_font_atlas_uncompressed_size);
|
||||
|
||||
auto header = reinterpret_cast<ImFontAtlasSnapshotHeader*>(g_imFontAtlas.get());
|
||||
assert(header->imguiVersion == IMGUI_VERSION_NUM && "ImGui version mismatch, the font atlas needs to be regenerated!");
|
||||
|
||||
auto offsetTable = reinterpret_cast<uint32_t*>(g_imFontAtlas.get() + header->offsetsOffset);
|
||||
for (size_t i = 0; i < header->offsetCount; i++)
|
||||
{
|
||||
*reinterpret_cast<size_t*>(g_imFontAtlas.get() + (*offsetTable)) += reinterpret_cast<size_t>(g_imFontAtlas.get());
|
||||
++offsetTable;
|
||||
}
|
||||
|
||||
return reinterpret_cast<ImFontAtlas*>(g_imFontAtlas.get() + header->dataOffset);
|
||||
}
|
||||
|
||||
|
||||
static void GetGlyphs(std::set<ImWchar>& glyphs, const std::string_view& value)
|
||||
{
|
||||
const char* cur = value.data();
|
||||
while (cur < value.data() + value.size())
|
||||
{
|
||||
unsigned int c;
|
||||
cur += ImTextCharFromUtf8(&c, cur, value.data() + value.size());
|
||||
glyphs.emplace(c);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<ImWchar> g_glyphRanges;
|
||||
|
||||
void ImFontAtlasSnapshot::GenerateGlyphRanges()
|
||||
{
|
||||
std::vector<std::string_view> localeStrings;
|
||||
|
||||
for (auto& config : Config::Definitions)
|
||||
config->GetLocaleStrings(localeStrings);
|
||||
|
||||
std::set<ImWchar> glyphs;
|
||||
|
||||
for (size_t i = 0x20; i <= 0xFF; i++)
|
||||
glyphs.emplace(i);
|
||||
|
||||
for (auto& localeString : localeStrings)
|
||||
GetGlyphs(glyphs, localeString);
|
||||
|
||||
for (auto& [name, locale] : g_locale)
|
||||
{
|
||||
for (auto& [language, value] : locale)
|
||||
GetGlyphs(glyphs, value);
|
||||
}
|
||||
|
||||
for (auto& [language, locale] : g_bool_locale)
|
||||
{
|
||||
for (auto& [value, nameAndDesc] : locale)
|
||||
{
|
||||
GetGlyphs(glyphs, std::get<0>(nameAndDesc));
|
||||
GetGlyphs(glyphs, std::get<1>(nameAndDesc));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_isGameLoaded)
|
||||
{
|
||||
for (size_t i = XDBF_LANGUAGE_ENGLISH; i <= XDBF_LANGUAGE_ITALIAN; i++)
|
||||
{
|
||||
auto achievements = g_xdbfWrapper.GetAchievements(static_cast<EXDBFLanguage>(i));
|
||||
for (auto& achievement : achievements)
|
||||
{
|
||||
GetGlyphs(glyphs, achievement.Name);
|
||||
GetGlyphs(glyphs, achievement.UnlockedDesc);
|
||||
GetGlyphs(glyphs, achievement.LockedDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto glyph : glyphs)
|
||||
{
|
||||
if (g_glyphRanges.empty() || (g_glyphRanges.back() + 1) != glyph)
|
||||
{
|
||||
g_glyphRanges.push_back(glyph);
|
||||
g_glyphRanges.push_back(glyph);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_glyphRanges.back() = glyph;
|
||||
}
|
||||
}
|
||||
|
||||
g_glyphRanges.push_back(0);
|
||||
}
|
||||
|
||||
ImFont* ImFontAtlasSnapshot::GetFont(const char* name, float size)
|
||||
{
|
||||
auto fontAtlas = ImGui::GetIO().Fonts;
|
||||
for (auto& configData : fontAtlas->ConfigData)
|
||||
{
|
||||
if (strstr(configData.Name, name) != nullptr && abs(configData.SizePixels - size) < 0.001f)
|
||||
{
|
||||
assert(configData.DstFont != nullptr);
|
||||
return configData.DstFont;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT
|
||||
assert(false && "Unable to locate equivalent font in the atlas file.");
|
||||
#endif
|
||||
|
||||
return fontAtlas->AddFontFromFileTTF(name, size, nullptr, g_glyphRanges.data());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,3 +30,34 @@ struct ImDrawDataSnapshot
|
|||
ImGuiID GetDrawListID(ImDrawList* src_list) { return ImHashData(&src_list, sizeof(src_list)); } // Hash pointer
|
||||
ImDrawDataSnapshotEntry* GetOrAddEntry(ImDrawList* src_list) { return Cache.GetOrAddByKey(GetDrawListID(src_list)); }
|
||||
};
|
||||
|
||||
// Undefine this to generate a font atlas file in working directory.
|
||||
// You also need to do this if you are testing localization, as only
|
||||
// characters in the locale get added to the atlas.
|
||||
// Don't forget to compress the generated atlas texture to BC4 with no mips!
|
||||
#define ENABLE_IM_FONT_ATLAS_SNAPSHOT
|
||||
|
||||
struct ImFontAtlasSnapshot
|
||||
{
|
||||
std::vector<uint8_t> data;
|
||||
ankerl::unordered_dense::map<const void*, size_t> objects;
|
||||
std::vector<uint32_t> offsets;
|
||||
|
||||
template<typename T1, typename T2>
|
||||
void SnapPointer(size_t offset, const T1& value, const T2& ptr, size_t count);
|
||||
|
||||
template<typename T>
|
||||
void Traverse(size_t offset, const T& value);
|
||||
|
||||
template<typename T>
|
||||
size_t Snap(const T& value);
|
||||
|
||||
void Snap();
|
||||
|
||||
static ImFontAtlas* Load();
|
||||
|
||||
static void GenerateGlyphRanges();
|
||||
|
||||
// When ENABLE_IM_FONT_ATLAS_SNAPSHOT is undefined, this creates the font runtime instead.
|
||||
static ImFont* GetFont(const char* name, float size);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,8 +2,15 @@
|
|||
|
||||
struct PushConstants
|
||||
{
|
||||
float2 GradientMin;
|
||||
float2 GradientMax;
|
||||
uint GradientTop;
|
||||
uint GradientBottom;
|
||||
uint ShaderModifier;
|
||||
uint Texture2DDescriptorIndex;
|
||||
float2 InverseDisplaySize;
|
||||
float2 Origin;
|
||||
float2 Scale;
|
||||
};
|
||||
|
||||
Texture2D<float4> g_Texture2DDescriptorHeap[] : register(t0, space0);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,92 @@
|
|||
#include "imgui_common.hlsli"
|
||||
#include "../imgui_common.h"
|
||||
|
||||
float4 DecodeColor(uint color)
|
||||
{
|
||||
return float4(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF, (color >> 24) & 0xFF) / 255.0;
|
||||
}
|
||||
|
||||
float4 SamplePoint(int2 position)
|
||||
{
|
||||
switch (g_PushConstants.ShaderModifier)
|
||||
{
|
||||
case IMGUI_SHADER_MODIFIER_SCANLINE:
|
||||
{
|
||||
if (int(position.y) % 2 == 0)
|
||||
return float4(1.0, 1.0, 1.0, 0.0);
|
||||
|
||||
break;
|
||||
}
|
||||
case IMGUI_SHADER_MODIFIER_CHECKERBOARD:
|
||||
{
|
||||
int remnantX = int(position.x) % 9;
|
||||
int remnantY = int(position.y) % 9;
|
||||
|
||||
float4 color = 1.0;
|
||||
|
||||
if (remnantX == 0 || remnantY == 0)
|
||||
color.a = 0.0;
|
||||
|
||||
if ((remnantY % 2) == 0)
|
||||
color.rgb = 0.5;
|
||||
|
||||
return color;
|
||||
}
|
||||
case IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON:
|
||||
{
|
||||
if (int(position.y) % 2 == 0)
|
||||
return float4(1.0, 1.0, 1.0, 0.5);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float4 SampleLinear(float2 uvTexspace)
|
||||
{
|
||||
int2 integerPart = floor(uvTexspace);
|
||||
float2 fracPart = frac(uvTexspace);
|
||||
|
||||
float4 topLeft = SamplePoint(integerPart + float2(0, 0));
|
||||
float4 topRight = SamplePoint(integerPart + float2(1, 0));
|
||||
float4 bottomLeft = SamplePoint(integerPart + float2(0, 1));
|
||||
float4 bottomRight = SamplePoint(integerPart + float2(1, 1));
|
||||
|
||||
float4 top = lerp(topLeft, topRight, fracPart.x);
|
||||
float4 bottom = lerp(bottomLeft, bottomRight, fracPart.x);
|
||||
|
||||
return lerp(top, bottom, fracPart.y);
|
||||
}
|
||||
|
||||
float4 PixelAntialiasing(float2 uvTexspace)
|
||||
{
|
||||
float2 seam = floor(uvTexspace + 0.5);
|
||||
uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam;
|
||||
uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5);
|
||||
|
||||
if (g_PushConstants.InverseDisplaySize.x < g_PushConstants.InverseDisplaySize.y)
|
||||
uvTexspace *= min(1.0, g_PushConstants.InverseDisplaySize.y * 720.0f);
|
||||
else
|
||||
uvTexspace *= min(1.0, g_PushConstants.InverseDisplaySize.x * 1280.0f);
|
||||
|
||||
return SampleLinear(uvTexspace);
|
||||
}
|
||||
|
||||
float4 main(in Interpolators interpolators) : SV_Target
|
||||
{
|
||||
float4 color = interpolators.Color;
|
||||
color *= PixelAntialiasing(interpolators.Position.xy - 0.5);
|
||||
|
||||
if (g_PushConstants.Texture2DDescriptorIndex != 0)
|
||||
color *= g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex].Sample(g_SamplerDescriptorHeap[0], interpolators.UV);
|
||||
|
||||
if (any(g_PushConstants.GradientMin != g_PushConstants.GradientMax))
|
||||
{
|
||||
float2 factor = saturate((interpolators.Position.xy - g_PushConstants.GradientMin) / (g_PushConstants.GradientMax - g_PushConstants.GradientMin));
|
||||
color *= lerp(DecodeColor(g_PushConstants.GradientTop), DecodeColor(g_PushConstants.GradientBottom), factor.y);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
void main(in float2 position : POSITION, in float2 uv : TEXCOORD, in float4 color : COLOR, out Interpolators interpolators)
|
||||
{
|
||||
interpolators.Position = float4(position * g_PushConstants.InverseDisplaySize * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
float2 correctedPosition = g_PushConstants.Origin + (position - g_PushConstants.Origin) * g_PushConstants.Scale;
|
||||
interpolators.Position = float4(correctedPosition * g_PushConstants.InverseDisplaySize * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
interpolators.UV = uv;
|
||||
interpolators.Color = color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,25 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <cpu/guest_thread.h>
|
||||
#include <kernel/memory.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <xxHashMap.h>
|
||||
#include <shader/shader_cache.h>
|
||||
#include <ui/achievement_menu.h>
|
||||
#include <ui/achievement_overlay.h>
|
||||
#include <ui/button_guide.h>
|
||||
#include <ui/message_window.h>
|
||||
#include <ui/options_menu.h>
|
||||
#include <ui/installer_wizard.h>
|
||||
|
||||
#include "imgui_snapshot.h"
|
||||
#include "imgui_common.h"
|
||||
#include "video.h"
|
||||
#include <ui/sdl_listener.h>
|
||||
#include <cfg/config.h>
|
||||
#include <ui/window.h>
|
||||
#include <user/config.h>
|
||||
|
||||
#include <res/font/im_font_atlas.dds.h>
|
||||
#include <decompressor.h>
|
||||
|
||||
#include <SWA.h>
|
||||
|
||||
|
|
@ -768,7 +780,7 @@ static void SetAlphaTestMode(bool enable)
|
|||
|
||||
if (enable)
|
||||
{
|
||||
enableAlphaToCoverage = Config::AlphaToCoverage && g_renderTarget != nullptr && g_renderTarget->sampleCount != RenderSampleCount::COUNT_1;
|
||||
enableAlphaToCoverage = Config::TransparencyAntiAliasing && g_renderTarget != nullptr && g_renderTarget->sampleCount != RenderSampleCount::COUNT_1;
|
||||
|
||||
if (enableAlphaToCoverage)
|
||||
specConstants = SPEC_CONSTANT_ALPHA_TO_COVERAGE;
|
||||
|
|
@ -1020,10 +1032,7 @@ static bool DetectWine()
|
|||
static constexpr size_t TEXTURE_DESCRIPTOR_SIZE = 65536;
|
||||
static constexpr size_t SAMPLER_DESCRIPTOR_SIZE = 1024;
|
||||
|
||||
static std::unique_ptr<RenderTexture> g_imFontTexture;
|
||||
static std::unique_ptr<RenderTextureView> g_imFontTextureView;
|
||||
static uint32_t g_imFontTextureDescriptorIndex;
|
||||
static bool g_imPendingBarrier = true;
|
||||
static std::unique_ptr<GuestTexture> g_imFontTexture;
|
||||
static std::unique_ptr<RenderPipelineLayout> g_imPipelineLayout;
|
||||
static std::unique_ptr<RenderPipeline> g_imPipeline;
|
||||
static ImDrawDataSnapshot g_imSnapshot;
|
||||
|
|
@ -1043,13 +1052,52 @@ static void ExecuteCopyCommandList(const T& function)
|
|||
static constexpr uint32_t PITCH_ALIGNMENT = 0x100;
|
||||
static constexpr uint32_t PLACEMENT_ALIGNMENT = 0x200;
|
||||
|
||||
struct ImGuiPushConstants
|
||||
{
|
||||
ImVec2 gradientMin{};
|
||||
ImVec2 gradientMax{};
|
||||
ImU32 gradientTop{};
|
||||
ImU32 gradientBottom{};
|
||||
uint32_t shaderModifier{};
|
||||
uint32_t texture2DDescriptorIndex{};
|
||||
ImVec2 inverseDisplaySize{};
|
||||
ImVec2 origin{ 0.0f, 0.0f };
|
||||
ImVec2 scale{ 1.0f, 1.0f };
|
||||
};
|
||||
|
||||
static void CreateImGuiBackend()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.IniFilename = nullptr;
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||||
|
||||
#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT
|
||||
IM_DELETE(io.Fonts);
|
||||
io.Fonts = ImFontAtlasSnapshot::Load();
|
||||
#else
|
||||
io.Fonts->TexDesiredWidth = 4096;
|
||||
io.Fonts->AddFontDefault();
|
||||
ImFontAtlasSnapshot::GenerateGlyphRanges();
|
||||
#endif
|
||||
|
||||
AchievementMenu::Init();
|
||||
AchievementOverlay::Init();
|
||||
ButtonGuide::Init();
|
||||
MessageWindow::Init();
|
||||
OptionsMenu::Init();
|
||||
InstallerWizard::Init();
|
||||
|
||||
ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
|
||||
|
||||
RenderComponentMapping componentMapping(RenderSwizzle::ONE, RenderSwizzle::ONE, RenderSwizzle::ONE, RenderSwizzle::R);
|
||||
|
||||
#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT
|
||||
g_imFontTexture = LoadTexture(decompressZstd(g_im_font_atlas_texture, g_im_font_atlas_texture_uncompressed_size).get(),
|
||||
g_im_font_atlas_texture_uncompressed_size, componentMapping);
|
||||
#else
|
||||
g_imFontTexture = std::make_unique<GuestTexture>(ResourceType::Texture);
|
||||
|
||||
uint8_t* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height);
|
||||
|
|
@ -1062,7 +1110,9 @@ static void CreateImGuiBackend()
|
|||
textureDesc.mipLevels = 1;
|
||||
textureDesc.arraySize = 1;
|
||||
textureDesc.format = RenderFormat::R8_UNORM;
|
||||
g_imFontTexture = g_device->createTexture(textureDesc);
|
||||
|
||||
g_imFontTexture->textureHolder = g_device->createTexture(textureDesc);
|
||||
g_imFontTexture->texture = g_imFontTexture->textureHolder.get();
|
||||
|
||||
uint32_t rowPitch = (width + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1);
|
||||
uint32_t slicePitch = (rowPitch * height + PLACEMENT_ALIGNMENT - 1) & ~(PLACEMENT_ALIGNMENT - 1);
|
||||
|
|
@ -1087,24 +1137,27 @@ static void CreateImGuiBackend()
|
|||
|
||||
ExecuteCopyCommandList([&]
|
||||
{
|
||||
g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(g_imFontTexture.get(), RenderTextureLayout::COPY_DEST));
|
||||
g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(g_imFontTexture->texture, RenderTextureLayout::COPY_DEST));
|
||||
|
||||
g_copyCommandList->copyTextureRegion(
|
||||
RenderTextureCopyLocation::Subresource(g_imFontTexture.get(), 0),
|
||||
RenderTextureCopyLocation::Subresource(g_imFontTexture->texture, 0),
|
||||
RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8_UNORM, width, height, 1, rowPitch, 0));
|
||||
});
|
||||
|
||||
g_imFontTexture->layout = RenderTextureLayout::COPY_DEST;
|
||||
|
||||
RenderTextureViewDesc textureViewDesc;
|
||||
textureViewDesc.format = textureDesc.format;
|
||||
textureViewDesc.dimension = RenderTextureViewDimension::TEXTURE_2D;
|
||||
textureViewDesc.mipLevels = 1;
|
||||
textureViewDesc.componentMapping = RenderComponentMapping(RenderSwizzle::ONE, RenderSwizzle::ONE, RenderSwizzle::ONE, RenderSwizzle::R);
|
||||
g_imFontTextureView = g_imFontTexture->createTextureView(textureViewDesc);
|
||||
textureViewDesc.componentMapping = componentMapping;
|
||||
g_imFontTexture->textureView = g_imFontTexture->texture->createTextureView(textureViewDesc);
|
||||
|
||||
g_imFontTextureDescriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
g_textureDescriptorSet->setTexture(g_imFontTextureDescriptorIndex, g_imFontTexture.get(), RenderTextureLayout::SHADER_READ, g_imFontTextureView.get());
|
||||
g_imFontTexture->descriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
g_textureDescriptorSet->setTexture(g_imFontTexture->descriptorIndex, g_imFontTexture->texture, RenderTextureLayout::SHADER_READ, g_imFontTexture->textureView.get());
|
||||
#endif
|
||||
|
||||
io.Fonts->SetTexID(ImTextureID(g_imFontTextureDescriptorIndex));
|
||||
io.Fonts->SetTexID(g_imFontTexture.get());
|
||||
|
||||
RenderPipelineLayoutBuilder pipelineLayoutBuilder;
|
||||
pipelineLayoutBuilder.begin(false, true);
|
||||
|
|
@ -1120,7 +1173,7 @@ static void CreateImGuiBackend()
|
|||
descriptorSetBuilder.end(true, SAMPLER_DESCRIPTOR_SIZE);
|
||||
pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder);
|
||||
|
||||
pipelineLayoutBuilder.addPushConstant(0, 2, 12, RenderShaderStageFlag::VERTEX | RenderShaderStageFlag::PIXEL);
|
||||
pipelineLayoutBuilder.addPushConstant(0, 2, sizeof(ImGuiPushConstants), RenderShaderStageFlag::VERTEX | RenderShaderStageFlag::PIXEL);
|
||||
|
||||
pipelineLayoutBuilder.end();
|
||||
g_imPipelineLayout = pipelineLayoutBuilder.create(g_device.get());
|
||||
|
|
@ -1147,9 +1200,37 @@ static void CreateImGuiBackend()
|
|||
pipelineDesc.inputSlots = &inputSlot;
|
||||
pipelineDesc.inputSlotsCount = 1;
|
||||
g_imPipeline = g_device->createGraphicsPipeline(pipelineDesc);
|
||||
|
||||
#ifndef ENABLE_IM_FONT_ATLAS_SNAPSHOT
|
||||
ImFontAtlasSnapshot snapshot;
|
||||
snapshot.Snap();
|
||||
|
||||
FILE* file = fopen("im_font_atlas.bin", "wb");
|
||||
if (file)
|
||||
{
|
||||
fwrite(snapshot.data.data(), 1, snapshot.data.size(), file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
static void CreateHostDevice()
|
||||
ddspp::Header header;
|
||||
ddspp::HeaderDXT10 headerDX10;
|
||||
ddspp::encode_header(ddspp::R8_UNORM, width, height, 1, ddspp::Texture2D, 1, 1, header, headerDX10);
|
||||
|
||||
file = fopen("im_font_atlas.dds", "wb");
|
||||
if (file)
|
||||
{
|
||||
fwrite(&ddspp::DDS_MAGIC, 4, 1, file);
|
||||
fwrite(&header, sizeof(header), 1, file);
|
||||
fwrite(&headerDX10, sizeof(headerDX10), 1, file);
|
||||
fwrite(pixels, 1, width * height, file);
|
||||
fclose(file);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void BeginCommandList();
|
||||
|
||||
void Video::CreateHostDevice()
|
||||
{
|
||||
for (uint32_t i = 0; i < 16; i++)
|
||||
g_inputSlots[i].index = i;
|
||||
|
|
@ -1180,7 +1261,22 @@ static void CreateHostDevice()
|
|||
g_copyCommandList = g_device->createCommandList(RenderCommandListType::COPY);
|
||||
g_copyCommandFence = g_device->createCommandFence();
|
||||
|
||||
g_swapChain = g_queue->createSwapChain(Window::s_handle, Config::TripleBuffering ? 3 : 2, BACKBUFFER_FORMAT);
|
||||
uint32_t bufferCount = 2;
|
||||
|
||||
switch (Config::TripleBuffering)
|
||||
{
|
||||
case ETripleBuffering::Auto:
|
||||
bufferCount = g_vulkan ? 2 : 3; // Defaulting to 3 is fine on D3D12 thanks to flip discard model.
|
||||
break;
|
||||
case ETripleBuffering::On:
|
||||
bufferCount = 3;
|
||||
break;
|
||||
case ETripleBuffering::Off:
|
||||
bufferCount = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
g_swapChain = g_queue->createSwapChain(Window::s_handle, bufferCount, BACKBUFFER_FORMAT);
|
||||
g_swapChain->setVsyncEnabled(Config::VSync);
|
||||
g_swapChainValid = !g_swapChain->needsResize();
|
||||
|
||||
|
|
@ -1323,9 +1419,23 @@ static void CreateHostDevice()
|
|||
desc.renderTargetBlend[0] = RenderBlendDesc::Copy();
|
||||
desc.renderTargetCount = 1;
|
||||
g_gammaCorrectionPipeline = g_device->createGraphicsPipeline(desc);
|
||||
|
||||
g_backBuffer = g_userHeap.AllocPhysical<GuestSurface>(ResourceType::RenderTarget);
|
||||
g_backBuffer->width = 1280;
|
||||
g_backBuffer->height = 720;
|
||||
g_backBuffer->format = BACKBUFFER_FORMAT;
|
||||
g_backBuffer->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(1, 1, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));
|
||||
|
||||
BeginCommandList();
|
||||
|
||||
RenderTextureBarrier blankTextureBarriers[TEXTURE_DESCRIPTOR_NULL_COUNT];
|
||||
for (size_t i = 0; i < TEXTURE_DESCRIPTOR_NULL_COUNT; i++)
|
||||
blankTextureBarriers[i] = RenderTextureBarrier(g_blankTextures[i].get(), RenderTextureLayout::SHADER_READ);
|
||||
|
||||
g_commandLists[g_frame]->barriers(RenderBarrierStage::NONE, blankTextureBarriers, std::size(blankTextureBarriers));
|
||||
}
|
||||
|
||||
static void WaitForGPU()
|
||||
void Video::WaitForGPU()
|
||||
{
|
||||
if (g_vulkan)
|
||||
{
|
||||
|
|
@ -1346,12 +1456,11 @@ static void WaitForGPU()
|
|||
}
|
||||
}
|
||||
|
||||
static bool g_pendingRenderThread;
|
||||
static std::atomic<bool> g_pendingRenderThread;
|
||||
|
||||
static void WaitForRenderThread()
|
||||
{
|
||||
while (g_pendingRenderThread)
|
||||
Sleep(0);
|
||||
g_pendingRenderThread.wait(true);
|
||||
}
|
||||
|
||||
static void BeginCommandList()
|
||||
|
|
@ -1363,11 +1472,12 @@ static void BeginCommandList()
|
|||
g_pipelineState.renderTargetFormat = BACKBUFFER_FORMAT;
|
||||
g_pipelineState.depthStencilFormat = RenderFormat::UNKNOWN;
|
||||
|
||||
g_swapChain->setVsyncEnabled(Config::VSync);
|
||||
g_swapChainValid &= !g_swapChain->needsResize();
|
||||
|
||||
if (!g_swapChainValid)
|
||||
{
|
||||
WaitForGPU();
|
||||
Video::WaitForGPU();
|
||||
g_backBuffer->framebuffers.clear();
|
||||
g_swapChainValid = g_swapChain->resize();
|
||||
g_needsResize = g_swapChainValid;
|
||||
|
|
@ -1378,7 +1488,7 @@ static void BeginCommandList()
|
|||
|
||||
if (g_swapChainValid)
|
||||
{
|
||||
bool applyingGammaCorrection = Config::Xbox360ColourCorrection || abs(Config::Brightness - 0.5f) > 0.001f;
|
||||
bool applyingGammaCorrection = Config::XboxColourCorrection || abs(Config::Brightness - 0.5f) > 0.001f;
|
||||
|
||||
if (applyingGammaCorrection)
|
||||
{
|
||||
|
|
@ -1391,7 +1501,7 @@ static void BeginCommandList()
|
|||
if (g_intermediaryBackBufferTextureDescriptorIndex == NULL)
|
||||
g_intermediaryBackBufferTextureDescriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
|
||||
WaitForGPU(); // Fine to wait for GPU, this'll only happen during resize.
|
||||
Video::WaitForGPU(); // Fine to wait for GPU, this'll only happen during resize.
|
||||
|
||||
g_intermediaryBackBufferTexture = g_device->createTexture(RenderTextureDesc::Texture2D(width, height, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));
|
||||
g_textureDescriptorSet->setTexture(g_intermediaryBackBufferTextureDescriptorIndex, g_intermediaryBackBufferTexture.get(), RenderTextureLayout::SHADER_READ);
|
||||
|
|
@ -1438,21 +1548,17 @@ static void BeginCommandList()
|
|||
|
||||
static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, be<uint32_t>* a6)
|
||||
{
|
||||
CreateHostDevice();
|
||||
g_xdbfTextureCache = std::unordered_map<uint16_t, GuestTexture *>();
|
||||
|
||||
g_backBuffer = g_userHeap.AllocPhysical<GuestSurface>(ResourceType::RenderTarget);
|
||||
g_backBuffer->width = 1280;
|
||||
g_backBuffer->height = 720;
|
||||
g_backBuffer->format = BACKBUFFER_FORMAT;
|
||||
g_backBuffer->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(1, 1, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));
|
||||
for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH))
|
||||
{
|
||||
// huh?
|
||||
if (!achievement.pImageBuffer || !achievement.ImageBufferSize)
|
||||
continue;
|
||||
|
||||
BeginCommandList();
|
||||
|
||||
RenderTextureBarrier blankTextureBarriers[TEXTURE_DESCRIPTOR_NULL_COUNT];
|
||||
for (size_t i = 0; i < TEXTURE_DESCRIPTOR_NULL_COUNT; i++)
|
||||
blankTextureBarriers[i] = RenderTextureBarrier(g_blankTextures[i].get(), RenderTextureLayout::SHADER_READ);
|
||||
|
||||
g_commandLists[g_frame]->barriers(RenderBarrierStage::NONE, blankTextureBarriers, std::size(blankTextureBarriers));
|
||||
g_xdbfTextureCache[achievement.ID] =
|
||||
LoadTexture((uint8_t *)achievement.pImageBuffer, achievement.ImageBufferSize).release();
|
||||
}
|
||||
|
||||
auto device = g_userHeap.AllocPhysical<GuestDevice>();
|
||||
memset(device, 0, sizeof(*device));
|
||||
|
|
@ -1668,6 +1774,8 @@ static void DrawImGui()
|
|||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
ResetImGuiCallbacks();
|
||||
|
||||
#ifdef ASYNC_PSO_DEBUG
|
||||
if (ImGui::Begin("Async PSO Stats"))
|
||||
{
|
||||
|
|
@ -1684,6 +1792,13 @@ static void DrawImGui()
|
|||
ImGui::End();
|
||||
#endif
|
||||
|
||||
AchievementMenu::Draw();
|
||||
OptionsMenu::Draw();
|
||||
AchievementOverlay::Draw();
|
||||
InstallerWizard::Draw();
|
||||
MessageWindow::Draw();
|
||||
ButtonGuide::Draw();
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
auto drawData = ImGui::GetDrawData();
|
||||
|
|
@ -1697,15 +1812,16 @@ static void DrawImGui()
|
|||
}
|
||||
}
|
||||
|
||||
static void SetFramebuffer(GuestSurface *renderTarget, GuestSurface *depthStencil, bool settingForClear);
|
||||
|
||||
static void ProcDrawImGui(const RenderCommand& cmd)
|
||||
{
|
||||
auto& commandList = g_commandLists[g_frame];
|
||||
// Make sure the backbuffer is the current target.
|
||||
AddBarrier(g_backBuffer, RenderTextureLayout::COLOR_WRITE);
|
||||
FlushBarriers();
|
||||
SetFramebuffer(g_backBuffer, nullptr, false);
|
||||
|
||||
if (g_imPendingBarrier)
|
||||
{
|
||||
commandList->barriers(RenderBarrierStage::GRAPHICS, RenderTextureBarrier(g_imFontTexture.get(), RenderTextureLayout::SHADER_READ));
|
||||
g_imPendingBarrier = false;
|
||||
}
|
||||
auto& commandList = g_commandLists[g_frame];
|
||||
|
||||
commandList->setGraphicsPipelineLayout(g_imPipelineLayout.get());
|
||||
commandList->setPipeline(g_imPipeline.get());
|
||||
|
|
@ -1715,8 +1831,9 @@ static void ProcDrawImGui(const RenderCommand& cmd)
|
|||
auto& drawData = g_imSnapshot.DrawData;
|
||||
commandList->setViewports(RenderViewport(drawData.DisplayPos.x, drawData.DisplayPos.y, drawData.DisplaySize.x, drawData.DisplaySize.y));
|
||||
|
||||
float inverseDisplaySize[] = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y };
|
||||
commandList->setGraphicsPushConstants(0, inverseDisplaySize, 4, 8);
|
||||
ImGuiPushConstants pushConstants{};
|
||||
pushConstants.inverseDisplaySize = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y };
|
||||
commandList->setGraphicsPushConstants(0, &pushConstants);
|
||||
|
||||
for (int i = 0; i < drawData.CmdListsCount; i++)
|
||||
{
|
||||
|
|
@ -1735,33 +1852,69 @@ static void ProcDrawImGui(const RenderCommand& cmd)
|
|||
for (int j = 0; j < drawList->CmdBuffer.Size; j++)
|
||||
{
|
||||
auto& drawCmd = drawList->CmdBuffer[j];
|
||||
if (drawCmd.UserCallback != nullptr)
|
||||
{
|
||||
auto callbackData = reinterpret_cast<const ImGuiCallbackData*>(drawCmd.UserCallbackData);
|
||||
|
||||
switch (static_cast<ImGuiCallback>(reinterpret_cast<size_t>(drawCmd.UserCallback)))
|
||||
{
|
||||
case ImGuiCallback::SetGradient:
|
||||
commandList->setGraphicsPushConstants(0, &callbackData->setGradient, offsetof(ImGuiPushConstants, gradientMin), sizeof(callbackData->setGradient));
|
||||
break;
|
||||
case ImGuiCallback::SetShaderModifier:
|
||||
commandList->setGraphicsPushConstants(0, &callbackData->setShaderModifier, offsetof(ImGuiPushConstants, shaderModifier), sizeof(callbackData->setShaderModifier));
|
||||
break;
|
||||
case ImGuiCallback::SetOrigin:
|
||||
commandList->setGraphicsPushConstants(0, &callbackData->setOrigin, offsetof(ImGuiPushConstants, origin), sizeof(callbackData->setOrigin));
|
||||
break;
|
||||
case ImGuiCallback::SetScale:
|
||||
commandList->setGraphicsPushConstants(0, &callbackData->setScale, offsetof(ImGuiPushConstants, scale), sizeof(callbackData->setScale));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (drawCmd.ClipRect.z <= drawCmd.ClipRect.x || drawCmd.ClipRect.w <= drawCmd.ClipRect.y)
|
||||
continue;
|
||||
|
||||
uint32_t descriptorIndex = uint32_t(drawCmd.GetTexID());
|
||||
commandList->setGraphicsPushConstants(0, &descriptorIndex, 0, 4);
|
||||
auto texture = reinterpret_cast<GuestTexture*>(drawCmd.TextureId);
|
||||
uint32_t descriptorIndex = TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D;
|
||||
if (texture != nullptr)
|
||||
{
|
||||
if (texture->layout != RenderTextureLayout::SHADER_READ)
|
||||
{
|
||||
commandList->barriers(RenderBarrierStage::GRAPHICS | RenderBarrierStage::COPY,
|
||||
RenderTextureBarrier(texture->texture, RenderTextureLayout::SHADER_READ));
|
||||
|
||||
texture->layout = RenderTextureLayout::SHADER_READ;
|
||||
}
|
||||
|
||||
descriptorIndex = texture->descriptorIndex;
|
||||
}
|
||||
|
||||
commandList->setGraphicsPushConstants(0, &descriptorIndex, offsetof(ImGuiPushConstants, texture2DDescriptorIndex), sizeof(descriptorIndex));
|
||||
commandList->setScissors(RenderRect(int32_t(drawCmd.ClipRect.x), int32_t(drawCmd.ClipRect.y), int32_t(drawCmd.ClipRect.z), int32_t(drawCmd.ClipRect.w)));
|
||||
commandList->drawIndexedInstanced(drawCmd.ElemCount, 1, drawCmd.IdxOffset, drawCmd.VtxOffset, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool g_precompiledPipelineStateCache = false;
|
||||
static bool g_shouldPrecompilePipelines = false;
|
||||
|
||||
static void Present()
|
||||
void Video::HostPresent()
|
||||
{
|
||||
DrawImGui();
|
||||
WaitForRenderThread();
|
||||
DrawImGui();
|
||||
|
||||
g_pendingRenderThread = true;
|
||||
g_pendingRenderThread.store(true);
|
||||
|
||||
RenderCommand cmd;
|
||||
cmd.type = RenderCommandType::Present;
|
||||
g_renderQueue.enqueue(cmd);
|
||||
|
||||
// All the shaders are available at this point. We can precompile embedded PSOs then.
|
||||
if (!g_precompiledPipelineStateCache)
|
||||
if (g_shouldPrecompilePipelines)
|
||||
{
|
||||
// This is all the model consumer thread needs to see.
|
||||
++g_compilingDataCount;
|
||||
|
|
@ -1769,10 +1922,20 @@ static void Present()
|
|||
if ((++g_pendingDataCount) == 1)
|
||||
g_pendingDataCount.notify_all();
|
||||
|
||||
g_precompiledPipelineStateCache = true;
|
||||
g_shouldPrecompilePipelines = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Video::StartPipelinePrecompilation()
|
||||
{
|
||||
g_shouldPrecompilePipelines = true;
|
||||
}
|
||||
|
||||
static void GuestPresent()
|
||||
{
|
||||
Video::HostPresent();
|
||||
}
|
||||
|
||||
static void SetRootDescriptor(const UploadAllocation& allocation, size_t index)
|
||||
{
|
||||
auto& commandList = g_commandLists[g_frame];
|
||||
|
|
@ -1798,7 +1961,7 @@ static void ProcPresent(const RenderCommand& cmd)
|
|||
uint32_t textureDescriptorIndex;
|
||||
} constants;
|
||||
|
||||
if (Config::Xbox360ColourCorrection)
|
||||
if (Config::XboxColourCorrection)
|
||||
{
|
||||
constants.gammaR = 1.2f;
|
||||
constants.gammaG = 1.17f;
|
||||
|
|
@ -1893,7 +2056,8 @@ static void ProcPresent(const RenderCommand& cmd)
|
|||
|
||||
BeginCommandList();
|
||||
|
||||
g_pendingRenderThread = false;
|
||||
g_pendingRenderThread.store(false);
|
||||
g_pendingRenderThread.notify_all();
|
||||
}
|
||||
|
||||
static GuestSurface* GetBackBuffer()
|
||||
|
|
@ -2019,7 +2183,7 @@ static GuestSurface* CreateSurface(uint32_t width, uint32_t height, uint32_t for
|
|||
desc.depth = 1;
|
||||
desc.mipLevels = 1;
|
||||
desc.arraySize = 1;
|
||||
desc.multisampling.sampleCount = multiSample != 0 && Config::MSAA > 1 ? Config::MSAA : RenderSampleCount::COUNT_1;
|
||||
desc.multisampling.sampleCount = multiSample != 0 && Config::AntiAliasing != EAntiAliasing::None ? int32_t(Config::AntiAliasing.Value) : RenderSampleCount::COUNT_1;
|
||||
desc.format = ConvertFormat(format);
|
||||
desc.flags = desc.format == RenderFormat::D32_FLOAT ? RenderTextureFlag::DEPTH_TARGET : RenderTextureFlag::RENDER_TARGET;
|
||||
|
||||
|
|
@ -4039,15 +4203,11 @@ static RenderFormat ConvertDXGIFormat(ddspp::DXGIFormat format)
|
|||
}
|
||||
}
|
||||
|
||||
static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32_t dataSize)
|
||||
{
|
||||
if ((pictureData->flags & 0x1) == 0 && data != nullptr)
|
||||
static bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)
|
||||
{
|
||||
ddspp::Descriptor ddsDesc;
|
||||
if (ddspp::decode_header(data, ddsDesc) != ddspp::Error)
|
||||
if (ddspp::decode_header((unsigned char *)(data), ddsDesc) != ddspp::Error)
|
||||
{
|
||||
const auto texture = g_userHeap.AllocPhysical<GuestTexture>(ResourceType::Texture);
|
||||
|
||||
RenderTextureDesc desc;
|
||||
desc.dimension = ConvertTextureDimension(ddsDesc.type);
|
||||
desc.width = ddsDesc.width;
|
||||
|
|
@ -4058,23 +4218,20 @@ static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32
|
|||
desc.format = ConvertDXGIFormat(ddsDesc.format);
|
||||
desc.flags = ddsDesc.type == ddspp::TextureType::Cubemap ? RenderTextureFlag::CUBE : RenderTextureFlag::NONE;
|
||||
|
||||
texture->textureHolder = g_device->createTexture(desc);
|
||||
texture->texture = texture->textureHolder.get();
|
||||
texture->layout = RenderTextureLayout::COPY_DEST;
|
||||
|
||||
#ifdef _DEBUG
|
||||
texture->texture->setName(reinterpret_cast<char*>(g_memory.Translate(pictureData->name + 2)));
|
||||
#endif
|
||||
texture.textureHolder = g_device->createTexture(desc);
|
||||
texture.texture = texture.textureHolder.get();
|
||||
texture.layout = RenderTextureLayout::COPY_DEST;
|
||||
|
||||
RenderTextureViewDesc viewDesc;
|
||||
viewDesc.format = desc.format;
|
||||
viewDesc.dimension = ConvertTextureViewDimension(ddsDesc.type);
|
||||
viewDesc.mipLevels = ddsDesc.numMips;
|
||||
texture->textureView = texture->texture->createTextureView(viewDesc);
|
||||
texture->descriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
g_textureDescriptorSet->setTexture(texture->descriptorIndex, texture->texture, RenderTextureLayout::SHADER_READ, texture->textureView.get());
|
||||
viewDesc.componentMapping = componentMapping;
|
||||
texture.textureView = texture.texture->createTextureView(viewDesc);
|
||||
texture.descriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
g_textureDescriptorSet->setTexture(texture.descriptorIndex, texture.texture, RenderTextureLayout::SHADER_READ, texture.textureView.get());
|
||||
|
||||
texture->viewDimension = viewDesc.dimension;
|
||||
texture.viewDimension = viewDesc.dimension;
|
||||
|
||||
struct Slice
|
||||
{
|
||||
|
|
@ -4118,7 +4275,7 @@ static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32
|
|||
|
||||
for (auto& slice : slices)
|
||||
{
|
||||
uint8_t* srcData = data + ddsDesc.headerSize + slice.srcOffset;
|
||||
const uint8_t* srcData = data + ddsDesc.headerSize + slice.srcOffset;
|
||||
uint8_t* dstData = mappedMemory + slice.dstOffset;
|
||||
|
||||
if (slice.srcRowPitch == slice.dstRowPitch)
|
||||
|
|
@ -4140,20 +4297,19 @@ static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32
|
|||
|
||||
ExecuteCopyCommandList([&]
|
||||
{
|
||||
g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture->texture, RenderTextureLayout::COPY_DEST));
|
||||
g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST));
|
||||
|
||||
for (size_t i = 0; i < slices.size(); i++)
|
||||
{
|
||||
auto& slice = slices[i];
|
||||
|
||||
g_copyCommandList->copyTextureRegion(
|
||||
RenderTextureCopyLocation::Subresource(texture->texture, i),
|
||||
RenderTextureCopyLocation::Subresource(texture.texture, i),
|
||||
RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), desc.format, slice.width, slice.height, slice.depth, (slice.dstRowPitch * 8) / ddsDesc.bitsPerPixelOrBlock * ddsDesc.blockWidth, slice.dstOffset));
|
||||
}
|
||||
});
|
||||
|
||||
pictureData->texture = g_memory.MapVirtual(texture);
|
||||
pictureData->type = 0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -4162,14 +4318,13 @@ static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32
|
|||
|
||||
if (stbImage != nullptr)
|
||||
{
|
||||
const auto texture = g_userHeap.AllocPhysical<GuestTexture>(ResourceType::Texture);
|
||||
texture->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(width, height, 1, RenderFormat::R8G8B8A8_UNORM));
|
||||
texture->texture = texture->textureHolder.get();
|
||||
texture->viewDimension = RenderTextureViewDimension::TEXTURE_2D;
|
||||
texture->layout = RenderTextureLayout::COPY_DEST;
|
||||
texture.textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(width, height, 1, RenderFormat::R8G8B8A8_UNORM));
|
||||
texture.texture = texture.textureHolder.get();
|
||||
texture.viewDimension = RenderTextureViewDimension::TEXTURE_2D;
|
||||
texture.layout = RenderTextureLayout::COPY_DEST;
|
||||
|
||||
texture->descriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
g_textureDescriptorSet->setTexture(texture->descriptorIndex, texture->texture, RenderTextureLayout::SHADER_READ);
|
||||
texture.descriptorIndex = g_textureDescriptorAllocator.allocate();
|
||||
g_textureDescriptorSet->setTexture(texture.descriptorIndex, texture.texture, RenderTextureLayout::SHADER_READ);
|
||||
|
||||
uint32_t rowPitch = (width * 4 + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1);
|
||||
uint32_t slicePitch = rowPitch * height;
|
||||
|
|
@ -4199,17 +4354,44 @@ static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32
|
|||
|
||||
ExecuteCopyCommandList([&]
|
||||
{
|
||||
g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture->texture, RenderTextureLayout::COPY_DEST));
|
||||
g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST));
|
||||
|
||||
g_copyCommandList->copyTextureRegion(
|
||||
RenderTextureCopyLocation::Subresource(texture->texture, 0),
|
||||
RenderTextureCopyLocation::Subresource(texture.texture, 0),
|
||||
RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8G8B8A8_UNORM, width, height, 1, rowPitch / 4, 0));
|
||||
});
|
||||
|
||||
pictureData->texture = g_memory.MapVirtual(texture);
|
||||
pictureData->type = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)
|
||||
{
|
||||
GuestTexture texture(ResourceType::Texture);
|
||||
|
||||
if (LoadTexture(texture, data, dataSize, componentMapping))
|
||||
return std::make_unique<GuestTexture>(std::move(texture));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void MakePictureData(GuestPictureData* pictureData, uint8_t* data, uint32_t dataSize)
|
||||
{
|
||||
if ((pictureData->flags & 0x1) == 0 && data != nullptr)
|
||||
{
|
||||
GuestTexture texture(ResourceType::Texture);
|
||||
|
||||
if (LoadTexture(texture, data, dataSize, {}))
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
texture.texture->setName(reinterpret_cast<char*>(g_memory.Translate(pictureData->name + 2)));
|
||||
#endif
|
||||
pictureData->texture = g_memory.MapVirtual(g_userHeap.AllocPhysical<GuestTexture>(std::move(texture)));
|
||||
pictureData->type = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4700,7 +4882,7 @@ static void CompileMeshPipeline(Hedgehog::Mirage::CMeshData* mesh, MeshLayer lay
|
|||
pipelineState.vertexStrides[2] = isFur ? 4 : 0;
|
||||
pipelineState.renderTargetFormat = RenderFormat::R16G16B16A16_FLOAT;
|
||||
pipelineState.depthStencilFormat = RenderFormat::D32_FLOAT;
|
||||
pipelineState.sampleCount = Config::MSAA > 1 ? Config::MSAA : 1;
|
||||
pipelineState.sampleCount = Config::AntiAliasing != EAntiAliasing::None ? int32_t(Config::AntiAliasing.Value) : 1;
|
||||
|
||||
if (pipelineState.vertexDeclaration->hasR11G11B10Normal)
|
||||
pipelineState.specConstants |= SPEC_CONSTANT_R11G11B10_NORMAL;
|
||||
|
|
@ -4710,7 +4892,7 @@ static void CompileMeshPipeline(Hedgehog::Mirage::CMeshData* mesh, MeshLayer lay
|
|||
|
||||
if (layer == MeshLayer::PunchThrough)
|
||||
{
|
||||
if (Config::MSAA > 1 && Config::AlphaToCoverage)
|
||||
if (Config::AntiAliasing != EAntiAliasing::None && Config::TransparencyAntiAliasing)
|
||||
{
|
||||
pipelineState.enableAlphaToCoverage = true;
|
||||
pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TO_COVERAGE;
|
||||
|
|
@ -4870,7 +5052,7 @@ static void CompileParticleMaterialPipeline(const Hedgehog::Sparkle::CParticleMa
|
|||
pipelineState.vertexStrides[0] = isMeshShader ? 104 : 28;
|
||||
pipelineState.renderTargetFormat = RenderFormat::R16G16B16A16_FLOAT;
|
||||
pipelineState.depthStencilFormat = RenderFormat::D32_FLOAT;
|
||||
pipelineState.sampleCount = Config::MSAA > 1 ? Config::MSAA : 1;
|
||||
pipelineState.sampleCount = Config::AntiAliasing != EAntiAliasing::None ? int32_t(Config::AntiAliasing.Value) : 1;
|
||||
pipelineState.specConstants = SPEC_CONSTANT_REVERSE_Z;
|
||||
|
||||
if (pipelineState.vertexDeclaration->hasR11G11B10Normal)
|
||||
|
|
@ -5147,14 +5329,14 @@ static void ModelConsumerThread()
|
|||
pipelineState.specConstants |= SPEC_CONSTANT_BICUBIC_GI_FILTER;
|
||||
|
||||
// Compile both MSAA and non MSAA variants to work with reflection maps. The render formats are an assumption but it should hold true.
|
||||
if (Config::MSAA > 1 &&
|
||||
if (Config::AntiAliasing != EAntiAliasing::None &&
|
||||
pipelineState.renderTargetFormat == RenderFormat::R16G16B16A16_FLOAT &&
|
||||
pipelineState.depthStencilFormat == RenderFormat::D32_FLOAT)
|
||||
{
|
||||
auto msaaPipelineState = pipelineState;
|
||||
msaaPipelineState.sampleCount = Config::MSAA;
|
||||
msaaPipelineState.sampleCount = int32_t(Config::AntiAliasing.Value);
|
||||
|
||||
if (Config::AlphaToCoverage && (msaaPipelineState.specConstants & SPEC_CONSTANT_ALPHA_TEST) != 0)
|
||||
if (Config::TransparencyAntiAliasing && (msaaPipelineState.specConstants & SPEC_CONSTANT_ALPHA_TEST) != 0)
|
||||
{
|
||||
msaaPipelineState.enableAlphaToCoverage = true;
|
||||
msaaPipelineState.specConstants &= ~SPEC_CONSTANT_ALPHA_TEST;
|
||||
|
|
@ -5404,6 +5586,15 @@ public:
|
|||
SDLEventListenerForPSOCaching g_sdlEventListenerForPSOCaching;
|
||||
#endif
|
||||
|
||||
void VideoConfigValueChangedCallback(IConfigDef* config)
|
||||
{
|
||||
// Config options that require internal resolution resize
|
||||
g_needsResize |=
|
||||
config == &Config::ResolutionScale ||
|
||||
config == &Config::AntiAliasing ||
|
||||
config == &Config::ShadowResolution;
|
||||
}
|
||||
|
||||
GUEST_FUNCTION_HOOK(sub_82BD99B0, CreateDevice);
|
||||
|
||||
GUEST_FUNCTION_HOOK(sub_82BE6230, DestructResource);
|
||||
|
|
@ -5424,7 +5615,7 @@ GUEST_FUNCTION_HOOK(sub_82BE96F0, GetSurfaceDesc);
|
|||
GUEST_FUNCTION_HOOK(sub_82BE04B0, GetVertexDeclaration);
|
||||
GUEST_FUNCTION_HOOK(sub_82BE0530, HashVertexDeclaration);
|
||||
|
||||
GUEST_FUNCTION_HOOK(sub_82BDA8C0, Present);
|
||||
GUEST_FUNCTION_HOOK(sub_82BDA8C0, GuestPresent);
|
||||
GUEST_FUNCTION_HOOK(sub_82BDD330, GetBackBuffer);
|
||||
|
||||
GUEST_FUNCTION_HOOK(sub_82BE9498, CreateTexture);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@
|
|||
|
||||
using namespace plume;
|
||||
|
||||
struct Video
|
||||
{
|
||||
static void CreateHostDevice();
|
||||
static void HostPresent();
|
||||
static void StartPipelinePrecompilation();
|
||||
static void WaitForGPU();
|
||||
};
|
||||
|
||||
struct GuestSamplerState
|
||||
{
|
||||
be<uint32_t> data[6];
|
||||
|
|
@ -378,3 +386,7 @@ enum GuestTextureAddress
|
|||
D3DTADDRESS_MIRRORONCE = 3,
|
||||
D3DTADDRESS_BORDER = 6
|
||||
};
|
||||
|
||||
extern std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping = RenderComponentMapping());
|
||||
|
||||
extern void VideoConfigValueChangedCallback(class IConfigDef* config);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
#include <stdafx.h>
|
||||
#include <SDL.h>
|
||||
#include <user/config.h>
|
||||
#include <hid/hid_detail.h>
|
||||
#include <ui/window.h>
|
||||
|
||||
#define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X)
|
||||
#define VIBRATION_TIMEOUT_MS 5000
|
||||
|
||||
class Controller
|
||||
|
|
@ -13,17 +17,12 @@ public:
|
|||
XAMINPUT_VIBRATION vibration{ 0, 0 };
|
||||
|
||||
Controller() = default;
|
||||
explicit Controller(int index) : Controller(SDL_GameControllerOpen(index))
|
||||
{
|
||||
|
||||
}
|
||||
explicit Controller(int index) : Controller(SDL_GameControllerOpen(index)) {}
|
||||
|
||||
Controller(SDL_GameController* controller) : controller(controller)
|
||||
{
|
||||
if (!controller)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
joystick = SDL_GameControllerGetJoystick(controller);
|
||||
id = SDL_JoystickInstanceID(joystick);
|
||||
|
|
@ -31,20 +30,28 @@ public:
|
|||
|
||||
void Close()
|
||||
{
|
||||
if (controller == nullptr)
|
||||
{
|
||||
if (!controller)
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GameControllerClose(controller);
|
||||
|
||||
controller = nullptr;
|
||||
joystick = nullptr;
|
||||
id = -1;
|
||||
}
|
||||
|
||||
bool CanPoll()
|
||||
{
|
||||
return controller && (Window::s_isFocused || Config::AllowBackgroundInput);
|
||||
}
|
||||
|
||||
void PollAxis()
|
||||
{
|
||||
if (!CanPoll())
|
||||
return;
|
||||
|
||||
auto& pad = state;
|
||||
|
||||
pad.sThumbLX = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
|
||||
pad.sThumbLY = ~SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
|
||||
|
||||
|
|
@ -55,14 +62,13 @@ public:
|
|||
pad.bRightTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 7;
|
||||
}
|
||||
|
||||
#define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X)
|
||||
void Poll()
|
||||
{
|
||||
if (controller == nullptr)
|
||||
{
|
||||
if (!CanPoll())
|
||||
return;
|
||||
}
|
||||
|
||||
auto& pad = state;
|
||||
|
||||
pad.wButtons = 0;
|
||||
|
||||
pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_UP, XAMINPUT_GAMEPAD_DPAD_UP);
|
||||
|
|
@ -87,12 +93,11 @@ public:
|
|||
|
||||
void SetVibration(const XAMINPUT_VIBRATION& vibration)
|
||||
{
|
||||
if (controller == nullptr)
|
||||
{
|
||||
if (!CanPoll())
|
||||
return;
|
||||
}
|
||||
|
||||
this->vibration = vibration;
|
||||
|
||||
SDL_GameControllerRumble(controller, vibration.wLeftMotorSpeed * 256, vibration.wRightMotorSpeed * 256, VIBRATION_TIMEOUT_MS);
|
||||
}
|
||||
};
|
||||
|
|
@ -102,9 +107,7 @@ std::array<Controller, 4> g_controllers;
|
|||
inline Controller* EnsureController(DWORD dwUserIndex)
|
||||
{
|
||||
if (!g_controllers[dwUserIndex].controller)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &g_controllers[dwUserIndex];
|
||||
}
|
||||
|
|
@ -114,10 +117,8 @@ inline size_t FindFreeController()
|
|||
for (size_t i = 0; i < g_controllers.size(); i++)
|
||||
{
|
||||
if (!g_controllers[i].controller)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -127,10 +128,8 @@ inline Controller* FindController(int which)
|
|||
for (auto& controller : g_controllers)
|
||||
{
|
||||
if (controller.id == which)
|
||||
{
|
||||
return &controller;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -142,22 +141,21 @@ int HID_OnSDLEvent(void*, SDL_Event* event)
|
|||
if (event->type == SDL_CONTROLLERDEVICEADDED)
|
||||
{
|
||||
const auto freeIndex = FindFreeController();
|
||||
|
||||
if (freeIndex != -1)
|
||||
{
|
||||
g_controllers[freeIndex] = Controller(event->cdevice.which);
|
||||
}
|
||||
}
|
||||
if (event->type == SDL_CONTROLLERDEVICEREMOVED)
|
||||
{
|
||||
auto* controller = FindController(event->cdevice.which);
|
||||
|
||||
if (controller)
|
||||
{
|
||||
controller->Close();
|
||||
}
|
||||
}
|
||||
else if (event->type == SDL_CONTROLLERBUTTONDOWN || event->type == SDL_CONTROLLERBUTTONUP || event->type == SDL_CONTROLLERAXISMOTION)
|
||||
{
|
||||
auto* controller = FindController(event->cdevice.which);
|
||||
|
||||
if (controller)
|
||||
{
|
||||
if (event->type == SDL_CONTROLLERAXISMOTION)
|
||||
|
|
@ -181,6 +179,7 @@ void hid::detail::Init()
|
|||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1");
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_EVENTS);
|
||||
|
|
@ -193,62 +192,58 @@ void hid::detail::Init()
|
|||
uint32_t hid::detail::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState)
|
||||
{
|
||||
static DWORD packet;
|
||||
|
||||
if (!pState)
|
||||
{
|
||||
return ERROR_BAD_ARGUMENTS;
|
||||
}
|
||||
|
||||
memset(pState, 0, sizeof(*pState));
|
||||
|
||||
pState->dwPacketNumber = packet++;
|
||||
|
||||
SDL_JoystickUpdate();
|
||||
auto* controller = EnsureController(dwUserIndex);
|
||||
if (controller == nullptr)
|
||||
{
|
||||
|
||||
if (!EnsureController(dwUserIndex))
|
||||
return ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
pState->Gamepad = g_controllers[dwUserIndex].state;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
uint32_t hid::detail::SetState(uint32_t dwUserIndex, XAMINPUT_VIBRATION* pVibration)
|
||||
{
|
||||
if (!pVibration)
|
||||
{
|
||||
return ERROR_BAD_ARGUMENTS;
|
||||
}
|
||||
|
||||
SDL_JoystickUpdate();
|
||||
|
||||
auto* controller = EnsureController(dwUserIndex);
|
||||
if (controller == nullptr)
|
||||
{
|
||||
|
||||
if (!controller)
|
||||
return ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
controller->SetVibration(*pVibration);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
uint32_t hid::detail::GetCapabilities(uint32_t dwUserIndex, XAMINPUT_CAPABILITIES* pCaps)
|
||||
{
|
||||
if (!pCaps)
|
||||
{
|
||||
return ERROR_BAD_ARGUMENTS;
|
||||
}
|
||||
|
||||
SDL_JoystickUpdate();
|
||||
|
||||
auto* controller = EnsureController(dwUserIndex);
|
||||
if (controller == nullptr)
|
||||
{
|
||||
|
||||
if (!controller)
|
||||
return ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
memset(pCaps, 0, sizeof(*pCaps));
|
||||
|
||||
pCaps->Type = XAMINPUT_DEVTYPE_GAMEPAD;
|
||||
pCaps->SubType = XAMINPUT_DEVSUBTYPE_GAMEPAD; // TODO: other types?
|
||||
pCaps->Flags = 0;
|
||||
|
||||
pCaps->Gamepad = controller->state;
|
||||
pCaps->Vibration = controller->vibration;
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ static std::unique_ptr<VirtualFileSystem> createFileSystemFromPath(const std::fi
|
|||
}
|
||||
}
|
||||
|
||||
static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<void(uint32_t)> &progressCallback) {
|
||||
static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<void()> &progressCallback) {
|
||||
const std::string filename(pair.first);
|
||||
const uint32_t hashCount = pair.second;
|
||||
if (!sourceVfs.exists(filename))
|
||||
|
|
@ -136,7 +136,8 @@ static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFi
|
|||
return false;
|
||||
}
|
||||
|
||||
progressCallback(++journal.progressCounter);
|
||||
journal.progressCounter += fileData.size();
|
||||
progressCallback();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -200,7 +201,46 @@ bool Installer::checkGameInstall(const std::filesystem::path &baseDirectory)
|
|||
return std::filesystem::exists(baseDirectory / GameDirectory / GameExecutableFile);
|
||||
}
|
||||
|
||||
bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void(uint32_t)> &progressCallback)
|
||||
bool Installer::checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc)
|
||||
{
|
||||
switch (dlc)
|
||||
{
|
||||
case DLC::Spagonia:
|
||||
return std::filesystem::exists(baseDirectory / SpagoniaDirectory / DLCValidationFile);
|
||||
case DLC::Chunnan:
|
||||
return std::filesystem::exists(baseDirectory / ChunnanDirectory / DLCValidationFile);
|
||||
case DLC::Mazuri:
|
||||
return std::filesystem::exists(baseDirectory / MazuriDirectory / DLCValidationFile);
|
||||
case DLC::Holoska:
|
||||
return std::filesystem::exists(baseDirectory / HoloskaDirectory / DLCValidationFile);
|
||||
case DLC::ApotosShamar:
|
||||
return std::filesystem::exists(baseDirectory / ApotosShamarDirectory / DLCValidationFile);
|
||||
case DLC::EmpireCityAdabat:
|
||||
return std::filesystem::exists(baseDirectory / EmpireCityAdabatDirectory / DLCValidationFile);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize)
|
||||
{
|
||||
for (FilePair pair : filePairs)
|
||||
{
|
||||
const std::string filename(pair.first);
|
||||
if (!sourceVfs.exists(filename))
|
||||
{
|
||||
journal.lastResult = Journal::Result::FileMissing;
|
||||
journal.lastErrorMessage = std::format("File {} does not exist in the file system.", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
totalSize += sourceVfs.getSize(filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback)
|
||||
{
|
||||
if (!std::filesystem::exists(targetDirectory) && !std::filesystem::create_directories(targetDirectory))
|
||||
{
|
||||
|
|
@ -265,45 +305,49 @@ bool Installer::parseContent(const std::filesystem::path &sourcePath, std::uniqu
|
|||
}
|
||||
}
|
||||
|
||||
bool Installer::install(const Input &input, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<void(uint32_t)> &progressCallback)
|
||||
constexpr uint32_t PatcherContribution = 512 * 1024 * 1024;
|
||||
|
||||
bool Installer::parseSources(const Input &input, Journal &journal, Sources &sources)
|
||||
{
|
||||
journal = Journal();
|
||||
sources = Sources();
|
||||
|
||||
// Parse the contents of the base game.
|
||||
std::unique_ptr<VirtualFileSystem> gameSource;
|
||||
if (!input.gameSource.empty())
|
||||
{
|
||||
if (!parseContent(input.gameSource, gameSource, journal))
|
||||
if (!parseContent(input.gameSource, sources.game, journal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
journal.progressTotal += GameFilesSize;
|
||||
if (!computeTotalSize({ GameFiles, GameFilesSize }, GameHashes, *sources.game, journal, sources.totalSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the contents of Update.
|
||||
std::unique_ptr<VirtualFileSystem> updateSource;
|
||||
if (!input.updateSource.empty())
|
||||
{
|
||||
if (!parseContent(input.updateSource, updateSource, journal))
|
||||
// Add an arbitrary progress size for the patching process.
|
||||
journal.progressTotal += PatcherContribution;
|
||||
|
||||
if (!parseContent(input.updateSource, sources.update, journal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
journal.progressTotal += UpdateFilesSize;
|
||||
if (!computeTotalSize({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *sources.update, journal, sources.totalSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the contents of the DLC Packs.
|
||||
struct DLCSource {
|
||||
std::unique_ptr<VirtualFileSystem> sourceVfs;
|
||||
std::span<const FilePair> filePairs;
|
||||
const uint64_t *fileHashes = nullptr;
|
||||
std::string targetSubDirectory;
|
||||
};
|
||||
|
||||
std::vector<DLCSource> dlcSources;
|
||||
for (const auto &path : input.dlcSources)
|
||||
{
|
||||
dlcSources.emplace_back();
|
||||
DLCSource &dlcSource = dlcSources.back();
|
||||
sources.dlc.emplace_back();
|
||||
DLCSource &dlcSource = sources.dlc.back();
|
||||
if (!parseContent(path, dlcSource.sourceVfs, journal))
|
||||
{
|
||||
return false;
|
||||
|
|
@ -346,17 +390,51 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
|||
return false;
|
||||
}
|
||||
|
||||
journal.progressTotal += dlcSource.filePairs.size();
|
||||
if (!computeTotalSize(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, journal, sources.totalSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Install the base game.
|
||||
if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *gameSource, targetDirectory / GameDirectory, GameExecutableFile, input.skipHashChecks, journal, progressCallback))
|
||||
// Add the total size in bytes as the journal progress.
|
||||
journal.progressTotal += sources.totalSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Installer::install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback)
|
||||
{
|
||||
// Install files in reverse order of importance. In case of a process crash or power outage, this will increase the likelihood of the installation
|
||||
// missing critical files required for the game to run. These files are used as the way to detect if the game is installed.
|
||||
|
||||
// Install the DLC.
|
||||
if (!sources.dlc.empty())
|
||||
{
|
||||
journal.createdDirectories.insert(targetDirectory / DLCDirectory);
|
||||
}
|
||||
|
||||
for (const DLCSource &dlcSource : sources.dlc)
|
||||
{
|
||||
if (!copyFiles(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, targetDirectory / dlcSource.targetSubDirectory, DLCValidationFile, skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If no game or update was specified, we're finished. This means the user was only installing the DLC.
|
||||
if ((sources.game == nullptr) && (sources.update == nullptr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Install the update.
|
||||
if (!copyFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *sources.update, targetDirectory / UpdateDirectory, UpdateExecutablePatchFile, skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the update.
|
||||
if (!copyFiles({ UpdateFiles, UpdateFilesSize }, UpdateHashes, *updateSource, targetDirectory / UpdateDirectory, UpdateExecutablePatchFile, input.skipHashChecks, journal, progressCallback))
|
||||
// Install the base game.
|
||||
if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *sources.game, targetDirectory / GameDirectory, GameExecutableFile, skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -374,6 +452,10 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
|||
return false;
|
||||
}
|
||||
|
||||
// Update the progress with the artificial amount attributed to the patching.
|
||||
journal.progressCounter += PatcherContribution;
|
||||
progressCallback();
|
||||
|
||||
// Replace the executable by renaming and deleting in a safe way.
|
||||
std::error_code ec;
|
||||
std::filesystem::path oldXexPath = targetDirectory / GameDirectory / (GameExecutableFile + OldExtension);
|
||||
|
|
@ -396,20 +478,6 @@ bool Installer::install(const Input &input, const std::filesystem::path &targetD
|
|||
|
||||
std::filesystem::remove(oldXexPath);
|
||||
|
||||
// Install the DLC.
|
||||
if (!dlcSources.empty())
|
||||
{
|
||||
journal.createdDirectories.insert(targetDirectory / DLCDirectory);
|
||||
}
|
||||
|
||||
for (const DLCSource &dlcSource : dlcSources)
|
||||
{
|
||||
if (!copyFiles(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, targetDirectory / dlcSource.targetSubDirectory, DLCValidationFile, input.skipHashChecks, journal, progressCallback))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ enum class DLC {
|
|||
Mazuri,
|
||||
Holoska,
|
||||
ApotosShamar,
|
||||
EmpireCityAdabat
|
||||
EmpireCityAdabat,
|
||||
Count = EmpireCityAdabat
|
||||
};
|
||||
|
||||
struct Journal
|
||||
|
|
@ -35,8 +36,8 @@ struct Journal
|
|||
UnknownDLCType
|
||||
};
|
||||
|
||||
uint32_t progressCounter = 0;
|
||||
uint32_t progressTotal = 0;
|
||||
uint64_t progressCounter = 0;
|
||||
uint64_t progressTotal = 0;
|
||||
std::list<std::filesystem::path> createdFiles;
|
||||
std::set<std::filesystem::path> createdDirectories;
|
||||
Result lastResult = Result::Success;
|
||||
|
|
@ -53,13 +54,30 @@ struct Installer
|
|||
std::filesystem::path gameSource;
|
||||
std::filesystem::path updateSource;
|
||||
std::list<std::filesystem::path> dlcSources;
|
||||
bool skipHashChecks = false;
|
||||
};
|
||||
|
||||
struct DLCSource {
|
||||
std::unique_ptr<VirtualFileSystem> sourceVfs;
|
||||
std::span<const FilePair> filePairs;
|
||||
const uint64_t *fileHashes = nullptr;
|
||||
std::string targetSubDirectory;
|
||||
};
|
||||
|
||||
struct Sources
|
||||
{
|
||||
std::unique_ptr<VirtualFileSystem> game;
|
||||
std::unique_ptr<VirtualFileSystem> update;
|
||||
std::vector<DLCSource> dlc;
|
||||
uint64_t totalSize = 0;
|
||||
};
|
||||
|
||||
static bool checkGameInstall(const std::filesystem::path &baseDirectory);
|
||||
static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void(uint32_t)> &progressCallback);
|
||||
static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc);
|
||||
static bool computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize);
|
||||
static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback);
|
||||
static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal);
|
||||
static bool install(const Input &input, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<void(uint32_t)> &progressCallback);
|
||||
static bool parseSources(const Input &input, Journal &journal, Sources &sources);
|
||||
static bool install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback);
|
||||
static void rollback(Journal &journal);
|
||||
|
||||
// Convenience method for checking if the specified file contains the game. This should be used when the user selects the file.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include "xam.h"
|
||||
#include "xdm.h"
|
||||
#include <timeapi.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
|
||||
#include <ntstatus.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <unordered_set>
|
||||
#include <CommCtrl.h>
|
||||
#include "xxHashMap.h"
|
||||
#include <user/paths.h>
|
||||
|
||||
// Needed for commctrl
|
||||
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
||||
|
|
@ -245,7 +246,7 @@ SWA_API uint32_t XamContentCreateEx(DWORD dwUserIndex, LPCSTR szRootName, const
|
|||
|
||||
if (pContentData->dwContentType == XCONTENTTYPE_SAVEDATA)
|
||||
{
|
||||
root = Config::GetSavePath().string();
|
||||
root = GetSavePath().string();
|
||||
}
|
||||
else if (pContentData->dwContentType == XCONTENTTYPE_DLC)
|
||||
{
|
||||
|
|
@ -333,9 +334,6 @@ SWA_API uint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_S
|
|||
{
|
||||
//printf("!!! STUB !!! XamInputGetState\n");
|
||||
|
||||
if (!Window::s_isFocused)
|
||||
return 0;
|
||||
|
||||
uint32_t result = hid::GetState(userIndex, state);
|
||||
|
||||
if (result == ERROR_SUCCESS)
|
||||
|
|
@ -349,6 +347,9 @@ SWA_API uint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_S
|
|||
}
|
||||
else if (userIndex == 0)
|
||||
{
|
||||
if (!Window::s_isFocused)
|
||||
return ERROR_SUCCESS;
|
||||
|
||||
memset(state, 0, sizeof(*state));
|
||||
if (GetAsyncKeyState('W') & 0x8000)
|
||||
state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_Y;
|
||||
|
|
|
|||
7
UnleashedRecomp/kernel/xdbf.h
Normal file
7
UnleashedRecomp/kernel/xdbf.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <gpu/video.h>
|
||||
#include <xdbf_wrapper.h>
|
||||
|
||||
extern XDBFWrapper g_xdbfWrapper;
|
||||
extern std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;
|
||||
338
UnleashedRecomp/locale/config_locale.h
Normal file
338
UnleashedRecomp/locale/config_locale.h
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
#pragma once
|
||||
|
||||
#include <user/config_detail.h>
|
||||
|
||||
#define CONFIG_DEFINE_LOCALE(name) \
|
||||
inline static std::unordered_map<ELanguage, std::tuple<std::string, std::string>> g_##name##_locale =
|
||||
|
||||
#define CONFIG_DEFINE_ENUM_LOCALE(type) \
|
||||
inline static std::unordered_map<ELanguage, std::unordered_map<type, std::tuple<std::string, std::string>>> g_##type##_locale =
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(bool)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ true, { "ON", "" } },
|
||||
{ false, { "OFF", "" } }
|
||||
}
|
||||
},
|
||||
{
|
||||
ELanguage::Japanese,
|
||||
{
|
||||
{ true, { "オン", "" } },
|
||||
{ false, { "オフ", "" } }
|
||||
}
|
||||
},
|
||||
{
|
||||
ELanguage::German,
|
||||
{
|
||||
{ true, { "EIN", "" } },
|
||||
{ false, { "AUS", "" } }
|
||||
}
|
||||
},
|
||||
{
|
||||
ELanguage::French,
|
||||
{
|
||||
{ true, { "OUI", "" } },
|
||||
{ false, { "NON", "" } }
|
||||
}
|
||||
},
|
||||
{
|
||||
ELanguage::Spanish,
|
||||
{
|
||||
{ true, { "SÍ", "" } },
|
||||
{ false, { "NO", "" } }
|
||||
}
|
||||
},
|
||||
{
|
||||
ELanguage::Italian,
|
||||
{
|
||||
{ true, { "SÌ", "" } },
|
||||
{ false, { "NO", "" } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(Language)
|
||||
{
|
||||
{ ELanguage::English, { "Language", "Change the language used for text and logos." } },
|
||||
{ ELanguage::Japanese, { "言語", "[PLACEHOLDER]" } },
|
||||
{ ELanguage::German, { "Sprache", "[PLACEHOLDER]" } },
|
||||
{ ELanguage::French, { "Langue", "[PLACEHOLDER]" } },
|
||||
{ ELanguage::Spanish, { "Idioma", "[PLACEHOLDER]" } },
|
||||
{ ELanguage::Italian, { "Lingua", "[PLACEHOLDER]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(ELanguage)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ ELanguage::English, { "ENGLISH", "" } },
|
||||
{ ELanguage::Japanese, { "日本語", "" } },
|
||||
{ ELanguage::German, { "DEUTSCH", "" } },
|
||||
{ ELanguage::French, { "FRANÇAIS", "" } },
|
||||
{ ELanguage::Spanish, { "ESPAÑOL", "" } },
|
||||
{ ELanguage::Italian, { "ITALIANO", "" } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(Hints)
|
||||
{
|
||||
{ ELanguage::English, { "Hints", "Show hint rings in stages." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(ControlTutorial)
|
||||
{
|
||||
{ ELanguage::English, { "Control Tutorial", "Show controller hints in stages." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(AchievementNotifications)
|
||||
{
|
||||
{ ELanguage::English, { "Achievement Notifications", "Show notifications for unlocking achievements.\n\nAchievements will still be rewarded with notifications disabled." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(SaveScoreAtCheckpoints)
|
||||
{
|
||||
{ ELanguage::English, { "Save Score at Checkpoints", "Keep your score from the last checkpoint upon respawning.\n\n[TO BE REMOVED]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(UnleashGaugeBehaviour)
|
||||
{
|
||||
{ ELanguage::English, { "Unleash Gauge Behavior", "Change how the Unleash gauge behaves.\n\n[TO BE REMOVED]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EUnleashGaugeBehaviour)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EUnleashGaugeBehaviour::Original, { "ORIGINAL", "Original: the gauge will drain at all times regardless." } },
|
||||
{ EUnleashGaugeBehaviour::Revised, { "REVISED", "Revised: the gauge will only drain when the player can move." } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(TimeOfDayTransition)
|
||||
{
|
||||
{ ELanguage::English, { "Time of Day Transition", "Change how the loading screen appears when switching time of day in the hub areas." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(ETimeOfDayTransition)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ ETimeOfDayTransition::Xbox, { "XBOX", "Xbox: the transformation cutscene will play with artificial loading times." } },
|
||||
{ ETimeOfDayTransition::PlayStation, { "PLAYSTATION", "PlayStation: a spinning medal loading screen will be used instead." } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(SkipIntroLogos)
|
||||
{
|
||||
{ ELanguage::English, { "Skip Intro Logos", "Skip the logos during the game's boot sequence.\n\n[TO BE REMOVED]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(InvertCameraX)
|
||||
{
|
||||
{ ELanguage::English, { "Invert Camera X", "Toggle between inverted left and right camera movement." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(InvertCameraY)
|
||||
{
|
||||
{ ELanguage::English, { "Invert Camera Y", "Toggle between inverted up and down camera movement." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(XButtonHoming)
|
||||
{
|
||||
{ ELanguage::English, { "Homing Attack on Boost", "Toggle between using the boost button or the jump button for the homing attack.\n\n[TO BE REMOVED]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(AllowCancellingUnleash)
|
||||
{
|
||||
{ ELanguage::English, { "Allow Cancelling Unleash", "Allow Unleash to be cancelled at the cost of some energy by pressing the input again.\n\n[TO BE REMOVED]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(AllowBackgroundInput)
|
||||
{
|
||||
{ ELanguage::English, { "Allow Background Input", "Accept controller input whilst the game window is unfocused." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(MusicVolume)
|
||||
{
|
||||
{ ELanguage::English, { "Music Volume", "Adjust the volume for the music." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(EffectsVolume)
|
||||
{
|
||||
{ ELanguage::English, { "Effects Volume", "Adjust the volume for sound effects." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(MusicAttenuation)
|
||||
{
|
||||
{ ELanguage::English, { "Music Attenuation", "Fade out the game's music when external media is playing." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(VoiceLanguage)
|
||||
{
|
||||
{ ELanguage::English, { "Voice Language", "Change the language used for character voices." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EVoiceLanguage)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EVoiceLanguage::English, { "ENGLISH", "" } },
|
||||
{ EVoiceLanguage::Japanese, { "日本語", "" } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(Subtitles)
|
||||
{
|
||||
{ ELanguage::English, { "Subtitles", "Show subtitles during dialogue." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(BattleTheme)
|
||||
{
|
||||
{ ELanguage::English, { "Battle Theme", "Play the Werehog battle theme during combat.\n\nThis option will apply the next time you're in combat." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(AspectRatio)
|
||||
{
|
||||
{ ELanguage::English, { "Aspect Ratio", "Change the aspect ratio." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EAspectRatio)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EAspectRatio::Auto, { "AUTO", "Auto: the aspect ratio will dynamically adjust to the window size." } }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(ResolutionScale)
|
||||
{
|
||||
{ ELanguage::English, { "Resolution Scale", "Adjust the internal resolution of the game.\n\n%dx%d" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(Fullscreen)
|
||||
{
|
||||
{ ELanguage::English, { "Fullscreen", "Toggle between borderless fullscreen or windowed mode." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(VSync)
|
||||
{
|
||||
{ ELanguage::English, { "V-Sync", "[PLACEHOLDER]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(FPS)
|
||||
{
|
||||
{ ELanguage::English, { "FPS", "[PLACEHOLDER]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(Brightness)
|
||||
{
|
||||
{ ELanguage::English, { "Brightness", "Adjust the brightness level of the game." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(AntiAliasing)
|
||||
{
|
||||
{ ELanguage::English, { "Anti-Aliasing", "Adjust the amount of smoothing applied to jagged edges." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EAntiAliasing)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EAntiAliasing::None, { "NONE", "" } },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(TransparencyAntiAliasing)
|
||||
{
|
||||
{ ELanguage::English, { "Transparency Anti-Aliasing", "Apply anti-aliasing to alpha transparent textures." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(ShadowResolution)
|
||||
{
|
||||
{ ELanguage::English, { "Shadow Resolution", "[PLACEHOLDER]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EShadowResolution)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EShadowResolution::Original, { "ORIGINAL", "Original: the game will automatically determine the resolution of the shadows." } },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(GITextureFiltering)
|
||||
{
|
||||
{ ELanguage::English, { "GI Texture Filtering", "[PLACEHOLDER]" } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EGITextureFiltering)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EGITextureFiltering::Bilinear, { "BILINEAR", "" } },
|
||||
{ EGITextureFiltering::Bicubic, { "BICUBIC", "" } },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(MotionBlur)
|
||||
{
|
||||
{ ELanguage::English, { "Motion Blur", "Use per-object motion blur and radial blur." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(XboxColourCorrection)
|
||||
{
|
||||
{ ELanguage::English, { "Xbox Color Correction", "Use the warm tint from the Xbox version of the game." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(MovieScaleMode)
|
||||
{
|
||||
{ ELanguage::English, { "Movie Scale Mode", "Change how the movie player scales to the display." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EMovieScaleMode)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EMovieScaleMode::Stretch, { "STRETCH", "Stretch: the movie will stretch to the display." } },
|
||||
{ EMovieScaleMode::Fit, { "FIT", "Fit: the movie will maintain its aspect ratio and fit to the display." } },
|
||||
{ EMovieScaleMode::Fill, { "FILL", "Fill: the movie will scale past the bounds of the display if it doesn't match the aspect ratio." } },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_LOCALE(UIScaleMode)
|
||||
{
|
||||
{ ELanguage::English, { "UI Scale Mode", "Change how the UI scales to the display." } }
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALE(EUIScaleMode)
|
||||
{
|
||||
{
|
||||
ELanguage::English,
|
||||
{
|
||||
{ EUIScaleMode::Stretch, { "STRETCH", "Stretch: the UI will stretch to the display." } },
|
||||
{ EUIScaleMode::Edge, { "EDGE", "Edge: the UI will anchor to the edges of the display." } },
|
||||
{ EUIScaleMode::Centre, { "CENTER", "Center: the UI will anchor to the center of the display." } },
|
||||
}
|
||||
}
|
||||
};
|
||||
279
UnleashedRecomp/locale/locale.h
Normal file
279
UnleashedRecomp/locale/locale.h
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
#pragma once
|
||||
|
||||
#include <user/config.h>
|
||||
|
||||
inline static std::string g_localeMissing = "<missing string>";
|
||||
|
||||
inline static std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_locale
|
||||
{
|
||||
{
|
||||
"Options_Category_System",
|
||||
{
|
||||
{ ELanguage::English, "SYSTEM" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Category_Input",
|
||||
{
|
||||
{ ELanguage::English, "INPUT" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Category_Audio",
|
||||
{
|
||||
{ ELanguage::English, "AUDIO" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Category_Video",
|
||||
{
|
||||
{ ELanguage::English, "VIDEO" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Value_Max",
|
||||
{
|
||||
{ ELanguage::English, "MAX" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Name_WindowSize",
|
||||
{
|
||||
{ ELanguage::English, "Window Size" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Desc_WindowSize",
|
||||
{
|
||||
{ ELanguage::English, "Adjust the size of the game window in windowed mode." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Desc_NotAvailable",
|
||||
{
|
||||
{ ELanguage::English, "This option is not available at this location." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Desc_NotAvailableMSAA",
|
||||
{
|
||||
{ ELanguage::English, "This option is not available without MSAA." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Options_Desc_OSNotSupported",
|
||||
{
|
||||
{ ELanguage::English, "This option is not supported by your operating system." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Achievements_Name",
|
||||
{
|
||||
{ ELanguage::English, "Achievements" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Achievements_Name_Uppercase",
|
||||
{
|
||||
{ ELanguage::English, "ACHIEVEMENTS" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Achievements_Unlock",
|
||||
{
|
||||
{ ELanguage::English, "Achievement Unlocked!" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Header_Installer",
|
||||
{
|
||||
{ ELanguage::English, "INSTALLER" },
|
||||
{ ELanguage::Spanish, "INSTALADOR" },
|
||||
},
|
||||
},
|
||||
{
|
||||
"Installer_Header_Installing",
|
||||
{
|
||||
{ ELanguage::English, "INSTALLING" },
|
||||
{ ELanguage::Spanish, "INSTALANDO" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_SelectLanguage",
|
||||
{
|
||||
{ ELanguage::English, "Please select a language." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_Introduction",
|
||||
{
|
||||
{ ELanguage::English, "Welcome to Unleashed Recompiled!\n\nYou'll need an Xbox 360 copy\nof Sonic Unleashed in order to proceed with the installation." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_SelectGameAndUpdate",
|
||||
{
|
||||
{ ELanguage::English, "Add the files for the game and its title update. You can use digital dumps or pre-extracted folders containing the necessary files." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_SelectDLC",
|
||||
{
|
||||
{ ELanguage::English, "Add the files for the DLC. You can use digital dumps or pre-extracted folders containing the necessary files." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_CheckSpace",
|
||||
{
|
||||
{ ELanguage::English, "The content will be installed to the program's folder. Please confirm you have enough free space.\n\n" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_Installing",
|
||||
{
|
||||
{ ELanguage::English, "Please wait while the content is being installed..." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_InstallSucceeded",
|
||||
{
|
||||
{ ELanguage::English, "Installation complete.\n\nThis project is brought to you by:\n\n" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Page_InstallFailed",
|
||||
{
|
||||
{ ELanguage::English, "Installation failed.\n\nError:\n\n" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_Game",
|
||||
{
|
||||
{ ELanguage::English, "GAME" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_Update",
|
||||
{
|
||||
{ ELanguage::English, "UPDATE" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_RequiredSpace",
|
||||
{
|
||||
{ ELanguage::English, "Required space:" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Step_AvailableSpace",
|
||||
{
|
||||
{ ELanguage::English, "Available space:" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_Next",
|
||||
{
|
||||
{ ELanguage::English, "NEXT" },
|
||||
{ ELanguage::Spanish, "SIGUIENTE" },
|
||||
{ ELanguage::German, "WEITER" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_Skip",
|
||||
{
|
||||
{ ELanguage::English, "SKIP" },
|
||||
{ ELanguage::Spanish, "SALTAR" },
|
||||
{ ELanguage::German, "ÜBERSPRINGEN" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_AddFiles",
|
||||
{
|
||||
{ ELanguage::English, "ADD FILES" },
|
||||
{ ELanguage::Spanish, "AÑADIR ARCHIVOS" },
|
||||
{ ELanguage::German, "DATEIEN HINZUFÜGEN" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Button_AddFolder",
|
||||
{
|
||||
{ ELanguage::English, "ADD FOLDER" },
|
||||
{ ELanguage::Spanish, "AÑADIR CARPETA" },
|
||||
{ ELanguage::German, "ORDNER HINZUFÜGEN" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_InvalidFilesList",
|
||||
{
|
||||
{ ELanguage::English, "The following selected files are invalid:" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_InvalidFiles",
|
||||
{
|
||||
{ ELanguage::English, "Some of the files that have\nbeen provided are not valid.\n\nPlease make sure all the\nspecified files are correct\nand try again." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_IncompatibleGameData",
|
||||
{
|
||||
{ ELanguage::English, "The specified game and\nupdate file are incompatible.\n\nPlease ensure the files are\nfor the same version and\nregion and try again." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Installer_Message_DLCWarning",
|
||||
{
|
||||
{ ELanguage::English, "It is highly recommended\nthat you install all of the\nDLC, as it includes high\nquality lighting textures\nfor the base game.\n\nAre you sure you want to\nskip this step?" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Common_Next",
|
||||
{
|
||||
{ ELanguage::English, "Next" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Common_Select",
|
||||
{
|
||||
{ ELanguage::English, "Select" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Common_Back",
|
||||
{
|
||||
{ ELanguage::English, "Back" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Common_Reset",
|
||||
{
|
||||
{ ELanguage::English, "Reset" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Common_Switch",
|
||||
{
|
||||
{ ELanguage::English, "Switch" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static std::string& Localise(const char* key)
|
||||
{
|
||||
if (!g_locale.count(key))
|
||||
return g_localeMissing;
|
||||
|
||||
if (!g_locale[key].count(Config::Language))
|
||||
{
|
||||
if (g_locale[key].count(ELanguage::English))
|
||||
{
|
||||
return g_locale[key][ELanguage::English];
|
||||
}
|
||||
else
|
||||
{
|
||||
return g_localeMissing;
|
||||
}
|
||||
}
|
||||
|
||||
return g_locale[key][Config::Language];
|
||||
}
|
||||
|
|
@ -11,8 +11,12 @@
|
|||
#include <xex.h>
|
||||
#include <apu/audio.h>
|
||||
#include <hid/hid.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <user/config.h>
|
||||
#include <user/paths.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <install/installer.h>
|
||||
#include <ui/installer_wizard.h>
|
||||
|
||||
#define GAME_XEX_PATH "game:\\default.xex"
|
||||
|
||||
|
|
@ -22,9 +26,10 @@ const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF;
|
|||
Memory g_memory{ reinterpret_cast<void*>(0x100000000), 0x100000000 };
|
||||
Heap g_userHeap;
|
||||
CodeCache g_codeCache;
|
||||
XDBFWrapper g_xdbfWrapper;
|
||||
std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;
|
||||
|
||||
// Name inspired from nt's entry point
|
||||
void KiSystemStartup()
|
||||
void HostStartup()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
|
@ -36,12 +41,18 @@ void KiSystemStartup()
|
|||
|
||||
g_memory.Alloc(XMAIOBegin, 0xFFFF, MEM_COMMIT);
|
||||
|
||||
hid::Init();
|
||||
}
|
||||
|
||||
// Name inspired from nt's entry point
|
||||
void KiSystemStartup()
|
||||
{
|
||||
const auto gameContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Game");
|
||||
const auto updateContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Update");
|
||||
XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : ".");
|
||||
XamRegisterContent(updateContent, ".\\update");
|
||||
|
||||
const auto savePath = Config::GetSavePath();
|
||||
const auto savePath = GetSavePath();
|
||||
const auto saveName = "SYS-DATA";
|
||||
|
||||
// TODO: implement save slots?
|
||||
|
|
@ -77,7 +88,6 @@ void KiSystemStartup()
|
|||
}
|
||||
|
||||
XAudioInitializeSystem();
|
||||
hid::Init();
|
||||
}
|
||||
|
||||
uint32_t LdrLoadModule(const char* path)
|
||||
|
|
@ -125,17 +135,46 @@ uint32_t LdrLoadModule(const char* path)
|
|||
assert(false && "Unknown compression type.");
|
||||
}
|
||||
|
||||
auto res = Xex2FindOptionalHeader<XEX_RESOURCE_INFO>(xex, XEX_HEADER_RESOURCE_INFO);
|
||||
|
||||
g_xdbfWrapper = XDBFWrapper((uint8_t*)g_memory.Translate(res->Offset.get()), res->SizeOfData);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
int main()
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
bool forceInstaller = false;
|
||||
bool forceDLCInstaller = false;
|
||||
for (uint32_t i = 1; i < argc; i++)
|
||||
{
|
||||
forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0);
|
||||
forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0);
|
||||
}
|
||||
|
||||
Config::Load();
|
||||
|
||||
HostStartup();
|
||||
|
||||
Video::CreateHostDevice();
|
||||
|
||||
bool isGameInstalled = Installer::checkGameInstall(".");
|
||||
if (forceInstaller || forceDLCInstaller || !isGameInstalled)
|
||||
{
|
||||
if (!InstallerWizard::Run(isGameInstalled && forceDLCInstaller))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
AchievementData::Load();
|
||||
|
||||
KiSystemStartup();
|
||||
|
||||
uint32_t entry = LdrLoadModule(FileSystem::TransformPath(GAME_XEX_PATH));
|
||||
|
||||
Video::StartPipelinePrecompilation();
|
||||
|
||||
GuestThread::Start(entry);
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
105
UnleashedRecomp/patches/audio_patches.cpp
Normal file
105
UnleashedRecomp/patches/audio_patches.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <user/config.h>
|
||||
#include <kernel/function.h>
|
||||
#include <kernel/platform.h>
|
||||
#include <patches/audio_patches.h>
|
||||
#include <api/SWA.h>
|
||||
|
||||
#if _WIN32
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Media.Control.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Media::Control;
|
||||
|
||||
GlobalSystemMediaTransportControlsSessionManager m_sessionManager = nullptr;
|
||||
|
||||
GlobalSystemMediaTransportControlsSessionManager GetSessionManager()
|
||||
{
|
||||
if (m_sessionManager)
|
||||
return m_sessionManager;
|
||||
|
||||
init_apartment();
|
||||
|
||||
return m_sessionManager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();
|
||||
}
|
||||
|
||||
GlobalSystemMediaTransportControlsSession GetCurrentSession()
|
||||
{
|
||||
return GetSessionManager().GetCurrentSession();
|
||||
}
|
||||
|
||||
bool IsExternalAudioPlaying()
|
||||
{
|
||||
auto session = GetCurrentSession();
|
||||
|
||||
if (!session)
|
||||
return false;
|
||||
|
||||
return session.GetPlaybackInfo().PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing;
|
||||
}
|
||||
|
||||
int AudioPatches::m_isAttenuationSupported = -1;
|
||||
#endif
|
||||
|
||||
be<float>* GetVolume(bool isMusic = true)
|
||||
{
|
||||
auto ppUnkClass = (be<uint32_t>*)g_memory.Translate(0x83362FFC);
|
||||
|
||||
if (!ppUnkClass->get())
|
||||
return nullptr;
|
||||
|
||||
// NOTE (Hyper): This is fine, trust me. See 0x82E58728.
|
||||
return (be<float>*)g_memory.Translate(4 * ((int)isMusic + 0x1C) + ((be<uint32_t>*)g_memory.Translate(ppUnkClass->get() + 4))->get());
|
||||
}
|
||||
|
||||
bool AudioPatches::CanAttenuate()
|
||||
{
|
||||
#if _WIN32
|
||||
if (m_isAttenuationSupported >= 0)
|
||||
return m_isAttenuationSupported;
|
||||
|
||||
auto version = GetPlatformVersion();
|
||||
|
||||
m_isAttenuationSupported = version.Major >= 10 && version.Build >= 17763;
|
||||
|
||||
return m_isAttenuationSupported;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioPatches::Update(float deltaTime)
|
||||
{
|
||||
auto pMusicVolume = GetVolume();
|
||||
auto pEffectsVolume = GetVolume(false);
|
||||
|
||||
if (!pMusicVolume || !pEffectsVolume)
|
||||
return;
|
||||
|
||||
#if _WIN32
|
||||
if (Config::MusicAttenuation && CanAttenuate())
|
||||
{
|
||||
auto time = 1.0f - expf(2.5f * -deltaTime);
|
||||
|
||||
if (IsExternalAudioPlaying())
|
||||
{
|
||||
*pMusicVolume = std::lerp(*pMusicVolume, 0.0f, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
*pMusicVolume = std::lerp(*pMusicVolume, Config::MusicVolume, time);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
*pMusicVolume = Config::MusicVolume;
|
||||
}
|
||||
|
||||
*pEffectsVolume = Config::EffectsVolume;
|
||||
}
|
||||
|
||||
// Stub volume setter.
|
||||
GUEST_FUNCTION_STUB(sub_82E58728);
|
||||
11
UnleashedRecomp/patches/audio_patches.h
Normal file
11
UnleashedRecomp/patches/audio_patches.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
class AudioPatches
|
||||
{
|
||||
protected:
|
||||
static int m_isAttenuationSupported;
|
||||
|
||||
public:
|
||||
static bool CanAttenuate();
|
||||
static void Update(float deltaTime);
|
||||
};
|
||||
51
UnleashedRecomp/patches/camera_patches.cpp
Normal file
51
UnleashedRecomp/patches/camera_patches.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <api/SWA.h>
|
||||
#include <ui/window.h>
|
||||
#include <user/config.h>
|
||||
|
||||
constexpr float m_baseAspectRatio = 16.0f / 9.0f;
|
||||
|
||||
bool CameraAspectRatioMidAsmHook(PPCRegister& r31)
|
||||
{
|
||||
auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32);
|
||||
auto newAspectRatio = (float)Window::s_width / (float)Window::s_height;
|
||||
|
||||
// Dynamically adjust horizontal aspect ratio to window dimensions.
|
||||
pCamera->m_HorzAspectRatio = newAspectRatio;
|
||||
|
||||
if (auto s_pVertAspectRatio = (be<float>*)g_memory.Translate(0x82028FE0))
|
||||
{
|
||||
// Dynamically adjust vertical aspect ratio for VERT+.
|
||||
*s_pVertAspectRatio = 2.0f * atan(tan(45.0f / 2.0f) * (m_baseAspectRatio / newAspectRatio));
|
||||
}
|
||||
|
||||
// Jump to 4:3 code for VERT+ adjustments if using a narrow aspect ratio.
|
||||
return newAspectRatio < m_baseAspectRatio;
|
||||
}
|
||||
|
||||
bool CameraBoostAspectRatioMidAsmHook(PPCRegister& r31, PPCRegister& f0, PPCRegister& f10, PPCRegister& f12)
|
||||
{
|
||||
auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32);
|
||||
|
||||
if (Window::s_width < Window::s_height)
|
||||
{
|
||||
pCamera->m_VertFieldOfView = pCamera->m_HorzFieldOfView + f10.f64;
|
||||
}
|
||||
else
|
||||
{
|
||||
pCamera->m_VertFieldOfView = (f12.f64 / f0.f64) + f10.f64;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PPC_FUNC_IMPL(__imp__sub_824697B0);
|
||||
PPC_FUNC(sub_824697B0)
|
||||
{
|
||||
auto pCamera = (SWA::CCamera*)g_memory.Translate(ctx.r3.u32);
|
||||
|
||||
pCamera->m_InvertX = Config::InvertCameraX;
|
||||
pCamera->m_InvertY = Config::InvertCameraY;
|
||||
|
||||
__imp__sub_824697B0(ctx, base);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <api/SWA.h>
|
||||
#include <ui/window.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
#include <app.h>
|
||||
|
||||
float m_lastLoadingFrameDelta = 0.0f;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <api/SWA.h>
|
||||
#include <ui/window.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <user/config.h>
|
||||
|
||||
void AchievementManagerUnlockMidAsmHook(PPCRegister& id)
|
||||
{
|
||||
AchievementData::Unlock(id.u32);
|
||||
}
|
||||
|
||||
bool DisableHintsMidAsmHook()
|
||||
{
|
||||
|
|
@ -22,9 +28,16 @@ bool DisableEvilControlTutorialMidAsmHook(PPCRegister& r4, PPCRegister& r5)
|
|||
return r4.u32 == 1 && r5.u32 == 1;
|
||||
}
|
||||
|
||||
void ToggleSubtitlesMidAsmHook(PPCRegister& r27)
|
||||
{
|
||||
auto pApplicationDocument = (SWA::CApplicationDocument*)g_memory.Translate(r27.u32);
|
||||
|
||||
pApplicationDocument->m_InspireSubtitles = Config::Subtitles;
|
||||
}
|
||||
|
||||
void WerehogBattleMusicMidAsmHook(PPCRegister& r11)
|
||||
{
|
||||
if (Config::WerehogBattleMusic)
|
||||
if (Config::BattleTheme)
|
||||
return;
|
||||
|
||||
// Swap CStateBattle for CStateNormal.
|
||||
|
|
@ -53,7 +66,7 @@ PPC_FUNC(sub_825197C0)
|
|||
PPC_FUNC_IMPL(__imp__sub_82547DF0);
|
||||
PPC_FUNC(sub_82547DF0)
|
||||
{
|
||||
if (Config::LogoSkip)
|
||||
if (Config::SkipIntroLogos)
|
||||
{
|
||||
ctx.r4.u64 = 0;
|
||||
ctx.r5.u64 = 0;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#include <api/SWA.h>
|
||||
#include <ui/window.h>
|
||||
#include <ui/window_events.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
|
||||
uint32_t m_lastCheckpointScore = 0;
|
||||
float m_lastDarkGaiaEnergy = 0.0f;
|
||||
|
|
@ -15,7 +15,7 @@ PPC_FUNC(sub_82624308)
|
|||
{
|
||||
__imp__sub_82624308(ctx, base);
|
||||
|
||||
if (Config::ScoreBehaviour != EScoreBehaviour::CheckpointRetain)
|
||||
if (!Config::SaveScoreAtCheckpoints)
|
||||
return;
|
||||
|
||||
auto pGameDocument = SWA::CGameDocument::GetInstance();
|
||||
|
|
@ -24,6 +24,8 @@ PPC_FUNC(sub_82624308)
|
|||
return;
|
||||
|
||||
m_lastCheckpointScore = pGameDocument->m_pMember->m_Score;
|
||||
|
||||
printf("[*] Score: %d\n", m_lastCheckpointScore);
|
||||
}
|
||||
|
||||
/* Hook function that resets the score
|
||||
|
|
@ -33,7 +35,7 @@ PPC_FUNC(sub_8245F048)
|
|||
{
|
||||
__imp__sub_8245F048(ctx, base);
|
||||
|
||||
if (Config::ScoreBehaviour != EScoreBehaviour::CheckpointRetain)
|
||||
if (!Config::SaveScoreAtCheckpoints)
|
||||
return;
|
||||
|
||||
auto pGameDocument = SWA::CGameDocument::GetInstance();
|
||||
|
|
@ -41,7 +43,7 @@ PPC_FUNC(sub_8245F048)
|
|||
if (!pGameDocument)
|
||||
return;
|
||||
|
||||
printf("[*] Resetting score to %d\n", m_lastCheckpointScore);
|
||||
printf("[*] Score: %d\n", m_lastCheckpointScore);
|
||||
|
||||
pGameDocument->m_pMember->m_Score = m_lastCheckpointScore;
|
||||
}
|
||||
|
|
@ -60,12 +62,12 @@ PPC_FUNC(sub_823AF7A8)
|
|||
m_lastDarkGaiaEnergy = pEvilSonicContext->m_DarkGaiaEnergy;
|
||||
|
||||
// Don't drain energy if out of control.
|
||||
if (!Config::UnleashOutOfControlDrain && pEvilSonicContext->m_OutOfControlCount && ctx.f1.f64 < 0.0)
|
||||
if (Config::UnleashGaugeBehaviour == EUnleashGaugeBehaviour::Revised && pEvilSonicContext->m_OutOfControlCount && ctx.f1.f64 < 0.0)
|
||||
return;
|
||||
|
||||
__imp__sub_823AF7A8(ctx, base);
|
||||
|
||||
if (!Config::UnleashCancel)
|
||||
if (!Config::AllowCancellingUnleash)
|
||||
return;
|
||||
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
|
|
@ -86,7 +88,7 @@ void PostUnleashMidAsmHook(PPCRegister& r30)
|
|||
if (m_isUnleashCancelled)
|
||||
{
|
||||
if (auto pEvilSonicContext = (SWA::Player::CEvilSonicContext*)g_memory.Translate(r30.u32))
|
||||
pEvilSonicContext->m_DarkGaiaEnergy = m_lastDarkGaiaEnergy;
|
||||
pEvilSonicContext->m_DarkGaiaEnergy = std::max(0.0f, m_lastDarkGaiaEnergy - 35.0f);
|
||||
|
||||
m_isUnleashCancelled = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <user/config.h>
|
||||
#include <api/SWA.h>
|
||||
|
||||
const char* m_pStageID;
|
||||
|
||||
bool m_isSavedAchievementData = false;
|
||||
|
||||
void GetStageIDMidAsmHook(PPCRegister& r5)
|
||||
{
|
||||
m_pStageID = *(xpointer<const char>*)g_memory.Translate(r5.u32);
|
||||
|
|
@ -12,43 +16,67 @@ void GetStageIDMidAsmHook(PPCRegister& r5)
|
|||
PPC_FUNC_IMPL(__imp__sub_824DCF38);
|
||||
PPC_FUNC(sub_824DCF38)
|
||||
{
|
||||
/* Force the Werehog transition ID
|
||||
to use a different transition. */
|
||||
if (!Config::WerehogHubTransformVideo)
|
||||
// TODO: use the actual PS3 loading screen ("n_2_d").
|
||||
if (Config::TimeOfDayTransition == ETimeOfDayTransition::PlayStation)
|
||||
{
|
||||
/*
|
||||
0 - Tails Electric NOW LOADING
|
||||
1 - No Transition
|
||||
2 - Werehog Transition
|
||||
3 - Tails Electric NOW LOADING w/ Info (requires context)
|
||||
4 - Arrows In/Out
|
||||
5 - NOW LOADING
|
||||
6 - Event Gallery
|
||||
7 - NOW LOADING
|
||||
8 - Black Screen
|
||||
*/
|
||||
if (ctx.r4.u32 == 2)
|
||||
ctx.r4.u32 = 4;
|
||||
if (ctx.r4.u32 == SWA::eLoadingDisplayType_WerehogMovie)
|
||||
ctx.r4.u32 = SWA::eLoadingDisplayType_Arrows;
|
||||
}
|
||||
|
||||
if (m_pStageID)
|
||||
{
|
||||
/* Fix restarting Eggmanland as the Werehog
|
||||
erroneously using the Event Gallery transition. */
|
||||
if (ctx.r4.u32 == 6 && !strcmp(m_pStageID, "Act_EggmanLand"))
|
||||
ctx.r4.u32 = 5;
|
||||
if (ctx.r4.u32 == SWA::eLoadingDisplayType_EventGallery && !strcmp(m_pStageID, "Act_EggmanLand"))
|
||||
ctx.r4.u32 = SWA::eLoadingDisplayType_NowLoading;
|
||||
}
|
||||
|
||||
__imp__sub_824DCF38(ctx, base);
|
||||
}
|
||||
|
||||
// CApplicationDocument::LoadArchiveDatabases
|
||||
// Load voice language files.
|
||||
PPC_FUNC_IMPL(__imp__sub_824EB9B0);
|
||||
PPC_FUNC(sub_824EB9B0)
|
||||
{
|
||||
auto pApplicationDocument = (SWA::CApplicationDocument*)g_memory.Translate(ctx.r4.u32);
|
||||
|
||||
pApplicationDocument->m_VoiceLanguage = (SWA::EVoiceLanguage)Config::VoiceLanguage.Value;
|
||||
|
||||
__imp__sub_824EB9B0(ctx, base);
|
||||
}
|
||||
|
||||
// SWA::CSaveIcon::Update
|
||||
PPC_FUNC_IMPL(__imp__sub_824E5170);
|
||||
PPC_FUNC(sub_824E5170)
|
||||
{
|
||||
auto pSaveIcon = (SWA::CSaveIcon*)g_memory.Translate(ctx.r3.u32);
|
||||
|
||||
__imp__sub_824E5170(ctx, base);
|
||||
|
||||
if (pSaveIcon->m_IsVisible)
|
||||
{
|
||||
if (!m_isSavedAchievementData)
|
||||
{
|
||||
printf("[*] Saving achievements...\n");
|
||||
|
||||
AchievementData::Save();
|
||||
|
||||
m_isSavedAchievementData = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_isSavedAchievementData = false;
|
||||
}
|
||||
}
|
||||
|
||||
// SWA::CApplicationDocument::LoadArchiveDatabases
|
||||
PPC_FUNC_IMPL(__imp__sub_824EFD28);
|
||||
PPC_FUNC(sub_824EFD28)
|
||||
{
|
||||
auto r3 = ctx.r3;
|
||||
|
||||
// CSigninXenon::InitializeDLC
|
||||
// SWA::CSigninXenon::InitializeDLC
|
||||
ctx.r3.u64 = PPC_LOAD_U32(r3.u32 + 4) + 200;
|
||||
ctx.r4.u64 = 0;
|
||||
sub_822C57D8(ctx, base);
|
||||
|
|
@ -57,7 +85,7 @@ PPC_FUNC(sub_824EFD28)
|
|||
__imp__sub_824EFD28(ctx, base);
|
||||
}
|
||||
|
||||
// CFileReaderXenon_DLC::InitializeParallel
|
||||
// SWA::CFileReaderXenon_DLC::InitializeParallel
|
||||
PPC_FUNC(sub_822C3778)
|
||||
{
|
||||
if (!PPC_LOAD_U8(0x83361F10)) // ms_DLCInitialized
|
||||
|
|
|
|||
161
UnleashedRecomp/patches/ui/CHudPause_patches.cpp
Normal file
161
UnleashedRecomp/patches/ui/CHudPause_patches.cpp
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <kernel/function.h>
|
||||
#include <api/SWA.h>
|
||||
#include <ui/achievement_menu.h>
|
||||
#include <ui/options_menu.h>
|
||||
#include <app.h>
|
||||
|
||||
float g_achievementMenuIntroTime = 0.0f;
|
||||
constexpr float g_achievementMenuIntroThreshold = 3.0f;
|
||||
float g_achievementMenuOutroTime = 0.0f;
|
||||
constexpr float g_achievementMenuOutroThreshold = 0.32f;
|
||||
bool g_isAchievementMenuOutro = false;
|
||||
|
||||
void CHudPauseAddOptionsItemMidAsmHook(PPCRegister& pThis)
|
||||
{
|
||||
guest_stack_var<Hedgehog::Base::CSharedString> menu("TopMenu");
|
||||
guest_stack_var<Hedgehog::Base::CSharedString> name("option");
|
||||
|
||||
GuestToHostFunction<int>(0x824AE690, pThis.u32, menu.get(), name.get());
|
||||
}
|
||||
|
||||
bool InjectMenuBehaviour(uint32_t pThis, uint32_t count)
|
||||
{
|
||||
auto pHudPause = (SWA::CHudPause*)g_memory.Translate(pThis);
|
||||
auto cursorIndex = *(be<uint32_t>*)g_memory.Translate(4 * (*(be<uint32_t>*)g_memory.Translate(pThis + 0x19C) + 0x68) + pThis);
|
||||
|
||||
auto actionType = SWA::eActionType_Undefined;
|
||||
auto transitionType = SWA::eTransitionType_Undefined;
|
||||
|
||||
switch (pHudPause->m_Menu)
|
||||
{
|
||||
case SWA::eMenuType_WorldMap:
|
||||
case SWA::eMenuType_Stage:
|
||||
case SWA::eMenuType_Misc:
|
||||
actionType = SWA::eActionType_Return;
|
||||
transitionType = SWA::eTransitionType_Quit;
|
||||
break;
|
||||
|
||||
case SWA::eMenuType_Village:
|
||||
case SWA::eMenuType_Hub:
|
||||
actionType = SWA::eActionType_Return;
|
||||
transitionType = SWA::eTransitionType_Hide;
|
||||
break;
|
||||
}
|
||||
|
||||
if (auto pInputState = SWA::CInputState::GetInstance())
|
||||
{
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_Select))
|
||||
{
|
||||
AchievementMenu::Open();
|
||||
|
||||
pHudPause->m_Action = SWA::eActionType_Undefined;
|
||||
pHudPause->m_Transition = SWA::eTransitionType_SubMenu;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pHudPause->m_Status == SWA::eStatusType_Accept)
|
||||
{
|
||||
if (cursorIndex == count - 2)
|
||||
{
|
||||
OptionsMenu::Open(true, pHudPause->m_Menu);
|
||||
|
||||
pHudPause->m_Action = SWA::eActionType_Undefined;
|
||||
pHudPause->m_Transition = SWA::eTransitionType_Hide;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (cursorIndex == count - 1)
|
||||
{
|
||||
pHudPause->m_Action = actionType;
|
||||
pHudPause->m_Transition = transitionType;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHudPauseItemCountMidAsmHook(PPCRegister& pThis, PPCRegister& count)
|
||||
{
|
||||
count.u32 += 1;
|
||||
|
||||
return InjectMenuBehaviour(pThis.u32, count.u32);
|
||||
}
|
||||
|
||||
void CHudPauseVillageItemCountMidAsmHook(PPCRegister& pThis, PPCRegister& count)
|
||||
{
|
||||
count.u32 += 1;
|
||||
|
||||
InjectMenuBehaviour(pThis.u32, count.u32);
|
||||
}
|
||||
|
||||
bool CHudPauseMiscItemCountMidAsmHook(PPCRegister& count)
|
||||
{
|
||||
if (count.u32 < 3)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHudPauseMiscInjectOptionsMidAsmHook(PPCRegister& pThis)
|
||||
{
|
||||
return InjectMenuBehaviour(pThis.u32, 3);
|
||||
}
|
||||
|
||||
// SWA::CHudPause::Update
|
||||
PPC_FUNC_IMPL(__imp__sub_824B0930);
|
||||
PPC_FUNC(sub_824B0930)
|
||||
{
|
||||
auto pHudPause = (SWA::CHudPause*)g_memory.Translate(ctx.r3.u32);
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
|
||||
g_achievementMenuIntroTime += g_deltaTime;
|
||||
|
||||
if (g_isAchievementMenuOutro)
|
||||
{
|
||||
g_achievementMenuOutroTime += g_deltaTime;
|
||||
|
||||
// Re-open pause menu after achievement menu closes with delay.
|
||||
if (g_achievementMenuOutroTime >= g_achievementMenuOutroThreshold)
|
||||
{
|
||||
GuestToHostFunction<int>(0x824AFD28, pHudPause, 0, 1, 0, 0);
|
||||
|
||||
g_achievementMenuOutroTime = 0;
|
||||
g_isAchievementMenuOutro = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: disable Start button closing menu.
|
||||
if (AchievementMenu::s_isVisible)
|
||||
{
|
||||
// HACK: wait for transition to finish before restoring control.
|
||||
if (g_achievementMenuIntroThreshold >= g_achievementMenuIntroTime)
|
||||
__imp__sub_824B0930(ctx, base);
|
||||
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_B) && !g_isAchievementMenuOutro)
|
||||
{
|
||||
AchievementMenu::Close();
|
||||
|
||||
g_isAchievementMenuOutro = true;
|
||||
}
|
||||
}
|
||||
else if (OptionsMenu::s_isVisible && OptionsMenu::s_isPause)
|
||||
{
|
||||
if (OptionsMenu::CanClose() && pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||
{
|
||||
OptionsMenu::Close();
|
||||
|
||||
GuestToHostFunction<int>(0x824AFD28, pHudPause, 0, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_achievementMenuIntroTime = 0;
|
||||
|
||||
__imp__sub_824B0930(ctx, base);
|
||||
}
|
||||
}
|
||||
38
UnleashedRecomp/patches/ui/CTitleStateMenu_patches.cpp
Normal file
38
UnleashedRecomp/patches/ui/CTitleStateMenu_patches.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <api/SWA.h>
|
||||
#include <ui/options_menu.h>
|
||||
#include <exports.h>
|
||||
|
||||
// SWA::CTitleStateMenu::Update
|
||||
PPC_FUNC_IMPL(__imp__sub_825882B8);
|
||||
PPC_FUNC(sub_825882B8)
|
||||
{
|
||||
auto pTitleState = (SWA::CTitleStateBase*)g_memory.Translate(ctx.r3.u32);
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
auto& pPadState = pInputState->GetPadState();
|
||||
auto isOptionsIndex = pTitleState->m_pMember->m_pTitleMenu->m_CursorIndex == 2;
|
||||
|
||||
if (!OptionsMenu::s_isVisible && pInputState && isOptionsIndex)
|
||||
{
|
||||
if (pPadState.IsTapped(SWA::eKeyState_A) || pPadState.IsTapped(SWA::eKeyState_Start))
|
||||
{
|
||||
Game_PlaySound("sys_worldmap_window");
|
||||
Game_PlaySound("sys_worldmap_decide");
|
||||
|
||||
OptionsMenu::Open();
|
||||
}
|
||||
}
|
||||
|
||||
if (!OptionsMenu::s_isVisible)
|
||||
__imp__sub_825882B8(ctx, base);
|
||||
|
||||
if (pInputState && isOptionsIndex)
|
||||
{
|
||||
if (OptionsMenu::CanClose() && pPadState.IsTapped(SWA::eKeyState_B))
|
||||
{
|
||||
Game_PlaySound("sys_worldmap_cansel");
|
||||
|
||||
OptionsMenu::Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "kernel/memory.h"
|
||||
#include "ui/sdl_listener.h"
|
||||
#include "ui/options_menu.h"
|
||||
|
||||
class FrontendListener : public SDLEventListener
|
||||
{
|
||||
|
|
@ -10,6 +11,9 @@ class FrontendListener : public SDLEventListener
|
|||
public:
|
||||
void OnSDLEvent(SDL_Event* event) override
|
||||
{
|
||||
if (OptionsMenu::s_isVisible)
|
||||
return;
|
||||
|
||||
switch (event->type)
|
||||
{
|
||||
case SDL_KEYDOWN:
|
||||
|
|
|
|||
|
|
@ -1,44 +1,12 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <user/config.h>
|
||||
#include <api/SWA.h>
|
||||
#include <ui/window.h>
|
||||
#include <cfg/config.h>
|
||||
|
||||
// TODO: to be removed.
|
||||
constexpr float m_baseAspectRatio = 16.0f / 9.0f;
|
||||
|
||||
bool CameraAspectRatioMidAsmHook(PPCRegister& r31)
|
||||
{
|
||||
auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32);
|
||||
auto newAspectRatio = (float)Window::s_width / (float)Window::s_height;
|
||||
|
||||
// Dynamically adjust horizontal aspect ratio to window dimensions.
|
||||
pCamera->m_HorzAspectRatio = newAspectRatio;
|
||||
|
||||
if (auto s_pVertAspectRatio = (be<float>*)g_memory.Translate(0x82028FE0))
|
||||
{
|
||||
// Dynamically adjust vertical aspect ratio for VERT+.
|
||||
*s_pVertAspectRatio = 2.0f * atan(tan(45.0f / 2.0f) * (m_baseAspectRatio / newAspectRatio));
|
||||
}
|
||||
|
||||
// Jump to 4:3 code for VERT+ adjustments if using a narrow aspect ratio.
|
||||
return newAspectRatio < m_baseAspectRatio;
|
||||
}
|
||||
|
||||
void CameraBoostAspectRatioMidAsmHook(PPCRegister& r31, PPCRegister& f0)
|
||||
{
|
||||
auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32);
|
||||
|
||||
if (Window::s_width < Window::s_height)
|
||||
{
|
||||
// Use horizontal FOV for narrow aspect ratios.
|
||||
f0.f32 = pCamera->m_HorzFieldOfView;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use vertical FOV for wide aspect ratios.
|
||||
f0.f32 = pCamera->m_VertFieldOfView;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: to be removed.
|
||||
void CSDAspectRatioMidAsmHook(PPCRegister& f1, PPCRegister& f2)
|
||||
{
|
||||
if (Config::UIScaleMode == EUIScaleMode::Stretch)
|
||||
|
|
|
|||
1
UnleashedRecomp/res/.gitignore
vendored
1
UnleashedRecomp/res/.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
![Ww][Ii][Nn]32/
|
||||
*.c
|
||||
*.h
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
#include <string>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <span>
|
||||
#include <xbox.h>
|
||||
#include <xxhash.h>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
#include <wrl/client.h>
|
||||
#include <smolv.h>
|
||||
#include <print>
|
||||
#include <set>
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
|
|
|
|||
678
UnleashedRecomp/ui/achievement_menu.cpp
Normal file
678
UnleashedRecomp/ui/achievement_menu.cpp
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
#include "achievement_menu.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <api/SWA.h>
|
||||
#include <gpu/video.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <locale/locale.h>
|
||||
#include <ui/button_guide.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <user/config.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/achievements_menu/trophy.dds.h>
|
||||
#include <res/images/common/general_window.dds.h>
|
||||
#include <res/images/common/select_fill.dds.h>
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
|
||||
constexpr double HEADER_CONTAINER_INTRO_MOTION_START = 0;
|
||||
constexpr double HEADER_CONTAINER_INTRO_MOTION_END = 15;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_MOTION_START = 0;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_MOTION_END = 40;
|
||||
constexpr double HEADER_CONTAINER_INTRO_FADE_START = 5;
|
||||
constexpr double HEADER_CONTAINER_INTRO_FADE_END = 14;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_FADE_START = 0;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_FADE_END = 7;
|
||||
|
||||
constexpr double CONTENT_CONTAINER_COMMON_MOTION_START = 11;
|
||||
constexpr double CONTENT_CONTAINER_COMMON_MOTION_END = 12;
|
||||
|
||||
constexpr double COUNTER_INTRO_FADE_START = 15;
|
||||
constexpr double COUNTER_INTRO_FADE_END = 16;
|
||||
|
||||
constexpr double SELECTION_CONTAINER_BREATHE = 30;
|
||||
|
||||
static bool g_isClosing = false;
|
||||
|
||||
static double g_appearTime;
|
||||
|
||||
static std::vector<std::tuple<Achievement, time_t>> g_achievements;
|
||||
|
||||
static ImFont* g_fntSeurat;
|
||||
static ImFont* g_fntNewRodinDB;
|
||||
static ImFont* g_fntNewRodinUB;
|
||||
|
||||
static std::unique_ptr<GuestTexture> g_upTrophyIcon;
|
||||
static std::unique_ptr<GuestTexture> g_upSelectionCursor;
|
||||
static std::unique_ptr<GuestTexture> g_upWindow;
|
||||
|
||||
static int g_firstVisibleRowIndex;
|
||||
static int g_selectedRowIndex;
|
||||
static double g_rowSelectionTime;
|
||||
|
||||
static bool g_upWasHeld;
|
||||
static bool g_downWasHeld;
|
||||
static bool g_leftWasHeld;
|
||||
static bool g_rightWasHeld;
|
||||
static bool g_upRSWasHeld;
|
||||
static bool g_downRSWasHeld;
|
||||
|
||||
static void ResetSelection()
|
||||
{
|
||||
g_firstVisibleRowIndex = 0;
|
||||
g_selectedRowIndex = 0;
|
||||
g_rowSelectionTime = ImGui::GetTime();
|
||||
g_upWasHeld = false;
|
||||
g_downWasHeld = false;
|
||||
}
|
||||
|
||||
static void DrawContainer(ImVec2 min, ImVec2 max, ImU32 gradientTop, ImU32 gradientBottom, float alpha = 1, float cornerRadius = 25)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
DrawPauseContainer(g_upWindow.get(), min, max, alpha);
|
||||
|
||||
drawList->PushClipRect({ min.x, min.y + Scale(20) }, { max.x, max.y - Scale(5) });
|
||||
}
|
||||
|
||||
static void DrawSelectionContainer(ImVec2 min, ImVec2 max)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
static auto breatheStart = ImGui::GetTime();
|
||||
auto alpha = Lerp(1.0f, 0.75f, (sin((ImGui::GetTime() - breatheStart) * (2.0f * M_PI / (55.0f / 60.0f))) + 1.0f) / 2.0f);
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
auto commonWidth = Scale(11);
|
||||
auto commonHeight = Scale(24);
|
||||
|
||||
auto tl = PIXELS_TO_UV_COORDS(64, 64, 0, 0, 11, 24);
|
||||
auto tc = PIXELS_TO_UV_COORDS(64, 64, 11, 0, 8, 24);
|
||||
auto tr = PIXELS_TO_UV_COORDS(64, 64, 19, 0, 11, 24);
|
||||
auto cl = PIXELS_TO_UV_COORDS(64, 64, 0, 24, 11, 2);
|
||||
auto cc = PIXELS_TO_UV_COORDS(64, 64, 11, 24, 8, 2);
|
||||
auto cr = PIXELS_TO_UV_COORDS(64, 64, 19, 24, 11, 2);
|
||||
auto bl = PIXELS_TO_UV_COORDS(64, 64, 0, 26, 11, 24);
|
||||
auto bc = PIXELS_TO_UV_COORDS(64, 64, 11, 26, 8, 24);
|
||||
auto br = PIXELS_TO_UV_COORDS(64, 64, 19, 26, 11, 24);
|
||||
|
||||
drawList->AddImage(g_upSelectionCursor.get(), min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + commonWidth, min.y }, { max.x - commonWidth, min.y + commonHeight }, GET_UV_COORDS(tc), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tr), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + commonWidth, min.y + commonHeight }, { max.x - commonWidth, max.y - commonHeight }, GET_UV_COORDS(cc), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cr), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y }, GET_UV_COORDS(bl), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + commonWidth, max.y - commonHeight }, { max.x - commonWidth, max.y }, GET_UV_COORDS(bc), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - commonWidth, max.y - commonHeight }, { max.x, max.y }, GET_UV_COORDS(br), colour);
|
||||
}
|
||||
|
||||
static void DrawHeaderContainer(const char* text)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto fontSize = Scale(24);
|
||||
auto textSize = g_fntNewRodinUB->CalcTextSizeA(fontSize, FLT_MAX, 0, text);
|
||||
auto cornerRadius = 23;
|
||||
auto textMarginX = Scale(16) + (Scale(cornerRadius) / 2);
|
||||
|
||||
auto containerMotion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, HEADER_CONTAINER_OUTRO_MOTION_START, HEADER_CONTAINER_OUTRO_MOTION_END)
|
||||
: ComputeMotion(g_appearTime, HEADER_CONTAINER_INTRO_MOTION_START, HEADER_CONTAINER_INTRO_MOTION_END);
|
||||
|
||||
auto colourMotion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, HEADER_CONTAINER_OUTRO_FADE_START, HEADER_CONTAINER_OUTRO_FADE_END)
|
||||
: ComputeMotion(g_appearTime, HEADER_CONTAINER_INTRO_FADE_START, HEADER_CONTAINER_INTRO_FADE_END);
|
||||
|
||||
// Slide animation.
|
||||
auto containerMarginX = g_isClosing
|
||||
? Hermite(251, 151, containerMotion)
|
||||
: Hermite(151, 251, containerMotion);
|
||||
|
||||
// Transparency fade animation.
|
||||
auto alpha = g_isClosing
|
||||
? Lerp(1, 0, colourMotion)
|
||||
: Lerp(0, 1, colourMotion);
|
||||
|
||||
ImVec2 min = { Scale(containerMarginX), Scale(136) };
|
||||
ImVec2 max = { min.x + textMarginX * 2 + textSize.x + Scale(5), Scale(196) };
|
||||
|
||||
DrawPauseHeaderContainer(g_upWindow.get(), min, max, alpha);
|
||||
|
||||
// TODO: skew this text and apply bevel.
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
g_fntNewRodinUB,
|
||||
fontSize,
|
||||
{ /* X */ min.x + textMarginX, /* Y */ CENTRE_TEXT_VERT(min, max, textSize) - Scale(5) },
|
||||
IM_COL32(255, 255, 255, 255 * alpha),
|
||||
text,
|
||||
3,
|
||||
IM_COL32(0, 0, 0, 255 * alpha)
|
||||
);
|
||||
}
|
||||
|
||||
static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievement, bool isUnlocked)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
auto itemWidth = Scale(700);
|
||||
auto itemHeight = Scale(94);
|
||||
auto itemMarginX = Scale(18);
|
||||
auto imageMarginX = Scale(25);
|
||||
auto imageMarginY = Scale(18);
|
||||
auto imageSize = Scale(60);
|
||||
|
||||
ImVec2 min = { itemMarginX + clipRectMin.x, clipRectMin.y + itemHeight * rowIndex + yOffset };
|
||||
ImVec2 max = { itemMarginX + min.x + itemWidth, min.y + itemHeight };
|
||||
|
||||
auto icon = g_xdbfTextureCache[achievement.ID];
|
||||
auto isSelected = rowIndex == g_selectedRowIndex;
|
||||
|
||||
if (isSelected)
|
||||
DrawSelectionContainer(min, max);
|
||||
|
||||
auto desc = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str();
|
||||
auto fontSize = Scale(24);
|
||||
auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, desc);
|
||||
auto textX = min.x + imageMarginX + imageSize + itemMarginX * 2;
|
||||
auto textMarqueeX = min.x + imageMarginX + imageSize;
|
||||
auto titleTextY = Scale(20);
|
||||
auto descTextY = Scale(52);
|
||||
|
||||
// Draw achievement icon.
|
||||
// TODO: make icon greyscale if locked?
|
||||
drawList->AddImage
|
||||
(
|
||||
icon,
|
||||
{ /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY },
|
||||
{ /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize },
|
||||
{ /* U */ 0, /* V */ 0 },
|
||||
{ /* U */ 1, /* V */ 1 },
|
||||
IM_COL32(255, 255, 255, 255 * (isUnlocked ? 1 : 0.5f))
|
||||
);
|
||||
|
||||
drawList->PushClipRect(min, max, true);
|
||||
|
||||
auto colLockedText = IM_COL32(60, 60, 60, 29);
|
||||
|
||||
auto colTextShadow = isUnlocked
|
||||
? IM_COL32(0, 0, 0, 255)
|
||||
: IM_COL32(60, 60, 60, 28);
|
||||
|
||||
auto shadowOffset = isUnlocked ? 2 : 1;
|
||||
|
||||
// Draw achievement name.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ textX, min.y + titleTextY },
|
||||
isUnlocked ? IM_COL32(252, 243, 5, 255) : colLockedText,
|
||||
achievement.Name.c_str(),
|
||||
shadowOffset,
|
||||
0.4f,
|
||||
colTextShadow
|
||||
);
|
||||
|
||||
if (isSelected && textX + textSize.x >= max.x - Scale(10))
|
||||
{
|
||||
// Draw achievement description with marquee.
|
||||
DrawTextWithMarqueeShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ textX, min.y + descTextY },
|
||||
{ textMarqueeX, min.y },
|
||||
max,
|
||||
isUnlocked ? IM_COL32(255, 255, 255, 255) : colLockedText,
|
||||
desc,
|
||||
g_rowSelectionTime,
|
||||
0.9,
|
||||
250.0,
|
||||
shadowOffset,
|
||||
0.4f,
|
||||
colTextShadow
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw achievement description.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ textX, min.y + descTextY },
|
||||
isUnlocked ? IM_COL32(255, 255, 255, 255) : colLockedText,
|
||||
desc,
|
||||
shadowOffset,
|
||||
0.4f,
|
||||
colTextShadow
|
||||
);
|
||||
}
|
||||
|
||||
drawList->PopClipRect();
|
||||
|
||||
if (!isUnlocked)
|
||||
return;
|
||||
|
||||
auto timestamp = AchievementData::GetTimestamp(achievement.ID);
|
||||
|
||||
if (!timestamp)
|
||||
return;
|
||||
|
||||
char buffer[32];
|
||||
struct tm time;
|
||||
localtime_s(&time, ×tamp);
|
||||
strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", &time);
|
||||
|
||||
fontSize = Scale(12);
|
||||
textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer);
|
||||
|
||||
auto containerMarginX = Scale(10);
|
||||
auto textMarginX = Scale(8);
|
||||
|
||||
ImVec2 timestampMin = { max.x - containerMarginX - textSize.x - (textMarginX * 2), min.y + titleTextY };
|
||||
ImVec2 timestampMax = { max.x - containerMarginX, min.y + Scale(46) };
|
||||
|
||||
drawList->PushClipRect(min, max, true);
|
||||
|
||||
auto bevelOffset = Scale(6);
|
||||
|
||||
// Left
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
timestampMin,
|
||||
{ timestampMin.x + bevelOffset, timestampMax.y },
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
IM_COL32(149, 149, 149, 40),
|
||||
IM_COL32(149, 149, 149, 40),
|
||||
IM_COL32(255, 255, 255, 255)
|
||||
);
|
||||
|
||||
// Right
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ timestampMax.x - bevelOffset, timestampMin.y },
|
||||
{ timestampMax.x, timestampMax.y },
|
||||
IM_COL32(149, 149, 149, 40),
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
IM_COL32(149, 149, 149, 40)
|
||||
);
|
||||
|
||||
// Centre
|
||||
drawList->AddRectFilled
|
||||
(
|
||||
{ timestampMin.x, timestampMin.y + bevelOffset },
|
||||
{ timestampMax.x, timestampMax.y - bevelOffset },
|
||||
IM_COL32(38, 38, 38, 172)
|
||||
);
|
||||
|
||||
// Top
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
timestampMin,
|
||||
{ timestampMax.x, timestampMin.y + bevelOffset },
|
||||
IM_COL32(16, 16, 16, 192),
|
||||
IM_COL32(16, 16, 16, 192),
|
||||
IM_COL32(38, 38, 38, 172),
|
||||
IM_COL32(38, 38, 38, 172)
|
||||
);
|
||||
|
||||
// Bottom
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ timestampMin.x, timestampMax.y - bevelOffset },
|
||||
{ timestampMax.x, timestampMax.y },
|
||||
IM_COL32(38, 40, 38, 169),
|
||||
IM_COL32(38, 40, 38, 169),
|
||||
IM_COL32(16, 16, 16, 192),
|
||||
IM_COL32(16, 16, 16, 192)
|
||||
);
|
||||
|
||||
// Draw timestamp text.
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
g_fntNewRodinDB,
|
||||
fontSize,
|
||||
{ /* X */ CENTRE_TEXT_HORZ(timestampMin, timestampMax, textSize), /* Y */ CENTRE_TEXT_VERT(timestampMin, timestampMax, textSize) },
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
buffer,
|
||||
2,
|
||||
IM_COL32(8, 8, 8, 255)
|
||||
);
|
||||
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
static void DrawAchievementTotal(ImVec2 min, ImVec2 max)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// Transparency fade animation.
|
||||
auto alpha = Cubic(0, 1, ComputeMotion(g_appearTime, COUNTER_INTRO_FADE_START, COUNTER_INTRO_FADE_END));
|
||||
|
||||
auto imageMarginX = Scale(5);
|
||||
auto imageMarginY = Scale(5);
|
||||
auto imageSize = Scale(45);
|
||||
|
||||
ImVec2 imageMin = { max.x - imageSize - imageMarginX, min.y - imageSize - imageMarginY };
|
||||
ImVec2 imageMax = { imageMin.x + imageSize, imageMin.y + imageSize };
|
||||
|
||||
constexpr auto columns = 8;
|
||||
constexpr auto rows = 4;
|
||||
constexpr auto spriteSize = 256.0f;
|
||||
constexpr auto textureWidth = 2048.0f;
|
||||
constexpr auto textureHeight = 1024.0f;
|
||||
auto frameIndex = int32_t(floor(ImGui::GetTime() * 30.0f)) % 30;
|
||||
auto columnIndex = frameIndex % columns;
|
||||
auto rowIndex = frameIndex / columns;
|
||||
auto uv0 = ImVec2(columnIndex * spriteSize / textureWidth, rowIndex * spriteSize / textureHeight);
|
||||
auto uv1 = ImVec2((columnIndex + 1) * spriteSize / textureWidth, (rowIndex + 1) * spriteSize / textureHeight);
|
||||
|
||||
drawList->AddImage(g_upTrophyIcon.get(), imageMin, imageMax, uv0, uv1, IM_COL32(255, 255, 255, 255 * alpha));
|
||||
|
||||
auto str = std::format("{} / {}", AchievementData::GetTotalRecords(), ACH_RECORDS);
|
||||
auto fontSize = Scale(20);
|
||||
auto textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
g_fntNewRodinDB,
|
||||
fontSize,
|
||||
{ /* X */ imageMin.x - textSize.x - Scale(6), /* Y */ CENTRE_TEXT_VERT(imageMin, imageMax, textSize) },
|
||||
IM_COL32(255, 255, 255, 255 * alpha),
|
||||
str.c_str(),
|
||||
2,
|
||||
IM_COL32(0, 0, 0, 255 * alpha)
|
||||
);
|
||||
}
|
||||
|
||||
static void DrawContentContainer()
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// Expand/retract animation.
|
||||
auto motion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, 0, CONTENT_CONTAINER_COMMON_MOTION_START)
|
||||
: ComputeMotion(g_appearTime, CONTENT_CONTAINER_COMMON_MOTION_START, CONTENT_CONTAINER_COMMON_MOTION_END);
|
||||
|
||||
auto minX = g_isClosing
|
||||
? Hermite(251, 301, motion)
|
||||
: Hermite(301, 251, motion);
|
||||
|
||||
auto minY = g_isClosing
|
||||
? Hermite(189, 206, motion)
|
||||
: Hermite(206, 189, motion);
|
||||
|
||||
auto maxX = g_isClosing
|
||||
? Hermite(1031, 978, motion)
|
||||
: Hermite(978, 1031, motion);
|
||||
|
||||
auto maxY = g_isClosing
|
||||
? Hermite(604, 573, motion)
|
||||
: Hermite(573, 604, motion);
|
||||
|
||||
ImVec2 min = { Scale(minX), Scale(minY) };
|
||||
ImVec2 max = { Scale(maxX), Scale(maxY) };
|
||||
|
||||
// Transparency fade animation.
|
||||
auto alpha = g_isClosing
|
||||
? Hermite(1, 0, motion)
|
||||
: Hermite(0, 1, motion);
|
||||
|
||||
DrawContainer(min, max, IM_COL32(197, 194, 197, 200), IM_COL32(115, 113, 115, 236), alpha);
|
||||
|
||||
if (motion < 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (g_isClosing)
|
||||
{
|
||||
AchievementMenu::s_isVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
auto itemHeight = Scale(94);
|
||||
auto yOffset = -g_firstVisibleRowIndex * itemHeight + Scale(2);
|
||||
auto rowCount = 0;
|
||||
|
||||
// Draw separators.
|
||||
for (int i = 1; i <= 3; i++)
|
||||
{
|
||||
auto lineMarginLeft = Scale(35);
|
||||
auto lineMarginRight = Scale(55);
|
||||
auto lineMarginY = Scale(2);
|
||||
|
||||
ImVec2 lineMin = { clipRectMin.x + lineMarginLeft, clipRectMin.y + itemHeight * i + lineMarginY };
|
||||
ImVec2 lineMax = { clipRectMax.x - lineMarginRight, clipRectMin.y + itemHeight * i + lineMarginY };
|
||||
|
||||
drawList->AddLine(lineMin, lineMax, IM_COL32(163, 163, 163, 255));
|
||||
drawList->AddLine({ lineMin.x, lineMin.y + Scale(1) }, { lineMax.x, lineMax.y + Scale(1) }, IM_COL32(143, 148, 143, 255));
|
||||
}
|
||||
|
||||
for (auto& tpl : g_achievements)
|
||||
{
|
||||
auto achievement = std::get<0>(tpl);
|
||||
|
||||
if (AchievementData::IsUnlocked(achievement.ID))
|
||||
DrawAchievement(rowCount++, yOffset, achievement, true);
|
||||
}
|
||||
|
||||
for (auto& tpl : g_achievements)
|
||||
{
|
||||
auto achievement = std::get<0>(tpl);
|
||||
|
||||
if (!AchievementData::IsUnlocked(achievement.ID))
|
||||
DrawAchievement(rowCount++, yOffset, achievement, false);
|
||||
}
|
||||
|
||||
auto inputState = SWA::CInputState::GetInstance();
|
||||
|
||||
bool upIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) ||
|
||||
inputState->GetPadState().LeftStickVertical > 0.5f;
|
||||
|
||||
bool downIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) ||
|
||||
inputState->GetPadState().LeftStickVertical < -0.5f;
|
||||
|
||||
bool leftIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadLeft) ||
|
||||
inputState->GetPadState().LeftStickHorizontal < -0.5f;
|
||||
|
||||
bool rightIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadRight) ||
|
||||
inputState->GetPadState().LeftStickHorizontal > 0.5f;
|
||||
|
||||
bool upRSIsHeld = inputState->GetPadState().RightStickVertical > 0.5f;
|
||||
bool downRSIsHeld = inputState->GetPadState().RightStickVertical < -0.5f;
|
||||
|
||||
bool isReachedTop = g_selectedRowIndex == 0;
|
||||
bool isReachedBottom = g_selectedRowIndex == rowCount - 1;
|
||||
|
||||
bool scrollUp = !g_upWasHeld && upIsHeld;
|
||||
bool scrollDown = !g_downWasHeld && downIsHeld;
|
||||
bool scrollPageUp = !g_leftWasHeld && leftIsHeld && !isReachedTop;
|
||||
bool scrollPageDown = !g_rightWasHeld && rightIsHeld && !isReachedBottom;
|
||||
bool jumpToTop = !g_upRSWasHeld && upRSIsHeld && !isReachedTop;
|
||||
bool jumpToBottom = !g_downRSWasHeld && downRSIsHeld && !isReachedBottom;
|
||||
|
||||
int prevSelectedRowIndex = g_selectedRowIndex;
|
||||
|
||||
if (scrollUp)
|
||||
{
|
||||
--g_selectedRowIndex;
|
||||
if (g_selectedRowIndex < 0)
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
}
|
||||
else if (scrollDown)
|
||||
{
|
||||
++g_selectedRowIndex;
|
||||
if (g_selectedRowIndex >= rowCount)
|
||||
g_selectedRowIndex = 0;
|
||||
}
|
||||
else if (scrollPageUp)
|
||||
{
|
||||
g_selectedRowIndex -= 3;
|
||||
if (g_selectedRowIndex < 0)
|
||||
g_selectedRowIndex = 0;
|
||||
}
|
||||
else if (scrollPageDown)
|
||||
{
|
||||
g_selectedRowIndex += 3;
|
||||
if (g_selectedRowIndex >= rowCount)
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
}
|
||||
else if (jumpToTop)
|
||||
{
|
||||
g_selectedRowIndex = 0;
|
||||
}
|
||||
else if (jumpToBottom)
|
||||
{
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
}
|
||||
|
||||
// lol
|
||||
if (scrollUp || scrollDown || scrollPageUp || scrollPageDown || jumpToTop || jumpToBottom)
|
||||
{
|
||||
g_rowSelectionTime = ImGui::GetTime();
|
||||
Game_PlaySound("sys_actstg_pausecursor");
|
||||
}
|
||||
|
||||
g_upWasHeld = upIsHeld;
|
||||
g_downWasHeld = downIsHeld;
|
||||
g_leftWasHeld = leftIsHeld;
|
||||
g_rightWasHeld = rightIsHeld;
|
||||
g_upRSWasHeld = upRSIsHeld;
|
||||
g_downRSWasHeld = downRSIsHeld;
|
||||
|
||||
int visibleRowCount = int(floor((clipRectMax.y - clipRectMin.y) / itemHeight));
|
||||
|
||||
if (g_firstVisibleRowIndex > g_selectedRowIndex)
|
||||
g_firstVisibleRowIndex = g_selectedRowIndex;
|
||||
|
||||
if (g_firstVisibleRowIndex + visibleRowCount - 1 < g_selectedRowIndex)
|
||||
g_firstVisibleRowIndex = std::max(0, g_selectedRowIndex - visibleRowCount + 1);
|
||||
|
||||
// Pop clip rect from DrawContentContainer
|
||||
drawList->PopClipRect();
|
||||
|
||||
DrawAchievementTotal(min, max);
|
||||
|
||||
// Draw scroll bar
|
||||
if (rowCount > visibleRowCount)
|
||||
{
|
||||
float cornerRadius = Scale(25);
|
||||
float totalHeight = (clipRectMax.y - clipRectMin.y - cornerRadius) - Scale(5);
|
||||
float heightRatio = float(visibleRowCount) / float(rowCount);
|
||||
float offsetRatio = float(g_firstVisibleRowIndex) / float(rowCount);
|
||||
float offsetX = clipRectMax.x - Scale(39);
|
||||
float offsetY = offsetRatio * totalHeight + clipRectMin.y + Scale(4);
|
||||
float maxY = max.y - cornerRadius - Scale(3);
|
||||
float lineThickness = Scale(1);
|
||||
float innerMarginX = Scale(2);
|
||||
float outerMarginX = Scale(24);
|
||||
|
||||
// Outline
|
||||
drawList->AddRect
|
||||
(
|
||||
{ /* X */ offsetX - lineThickness, /* Y */ clipRectMin.y - lineThickness },
|
||||
{ /* X */ clipRectMax.x - outerMarginX + lineThickness, /* Y */ maxY + lineThickness },
|
||||
IM_COL32(255, 255, 255, 155),
|
||||
Scale(1)
|
||||
);
|
||||
|
||||
// Background
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ /* X */ offsetX, /* Y */ clipRectMin.y },
|
||||
{ /* X */ clipRectMax.x - outerMarginX, /* Y */ maxY },
|
||||
IM_COL32(123, 125, 123, 255),
|
||||
IM_COL32(123, 125, 123, 255),
|
||||
IM_COL32(97, 99, 97, 255),
|
||||
IM_COL32(97, 99, 97, 255)
|
||||
);
|
||||
|
||||
// Scroll Bar Outline
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ /* X */ offsetX + innerMarginX, /* Y */ offsetY - lineThickness },
|
||||
{ /* X */ clipRectMax.x - outerMarginX - innerMarginX, /* Y */ offsetY + lineThickness + totalHeight * heightRatio },
|
||||
IM_COL32(185, 185, 185, 255),
|
||||
IM_COL32(185, 185, 185, 255),
|
||||
IM_COL32(172, 172, 172, 255),
|
||||
IM_COL32(172, 172, 172, 255)
|
||||
);
|
||||
|
||||
// Scroll Bar
|
||||
drawList->AddRectFilled
|
||||
(
|
||||
{ /* X */ offsetX + innerMarginX + lineThickness, /* Y */ offsetY },
|
||||
{ /* X */ clipRectMax.x - outerMarginX - innerMarginX - lineThickness, /* Y */ offsetY + totalHeight * heightRatio },
|
||||
IM_COL32(255, 255, 255, 255)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementMenu::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||
g_fntNewRodinDB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE);
|
||||
g_fntNewRodinUB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE);
|
||||
|
||||
g_upTrophyIcon = LoadTexture(decompressZstd(g_trophy, g_trophy_uncompressed_size).get(), g_trophy_uncompressed_size);
|
||||
g_upSelectionCursor = LoadTexture(decompressZstd(g_select_fill, g_select_fill_uncompressed_size).get(), g_select_fill_uncompressed_size);
|
||||
g_upWindow = LoadTexture(decompressZstd(g_general_window, g_general_window_uncompressed_size).get(), g_general_window_uncompressed_size);
|
||||
}
|
||||
|
||||
void AchievementMenu::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
DrawHeaderContainer(Localise("Achievements_Name_Uppercase").c_str());
|
||||
DrawContentContainer();
|
||||
}
|
||||
|
||||
void AchievementMenu::Open()
|
||||
{
|
||||
s_isVisible = true;
|
||||
g_isClosing = false;
|
||||
g_appearTime = ImGui::GetTime();
|
||||
|
||||
g_achievements.clear();
|
||||
|
||||
for (auto& achievement : g_xdbfWrapper.GetAchievements((EXDBFLanguage)Config::Language.Value))
|
||||
g_achievements.push_back(std::make_tuple(achievement, AchievementData::GetTimestamp(achievement.ID)));
|
||||
|
||||
std::sort(g_achievements.begin(), g_achievements.end(), [](const auto& a, const auto& b)
|
||||
{
|
||||
return std::get<1>(a) > std::get<1>(b);
|
||||
});
|
||||
|
||||
ButtonGuide::Open({ Button(Localise("Common_Back"), EButtonIcon::B) });
|
||||
|
||||
ResetSelection();
|
||||
Game_PlaySound("sys_actstg_pausewinopen");
|
||||
}
|
||||
|
||||
void AchievementMenu::Close()
|
||||
{
|
||||
if (!g_isClosing)
|
||||
{
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
}
|
||||
|
||||
ButtonGuide::Close();
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinclose");
|
||||
Game_PlaySound("sys_actstg_pausecansel");
|
||||
}
|
||||
12
UnleashedRecomp/ui/achievement_menu.h
Normal file
12
UnleashedRecomp/ui/achievement_menu.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
class AchievementMenu
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open();
|
||||
static void Close();
|
||||
};
|
||||
200
UnleashedRecomp/ui/achievement_overlay.cpp
Normal file
200
UnleashedRecomp/ui/achievement_overlay.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#include "achievement_overlay.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <gpu/video.h>
|
||||
#include <kernel/memory.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <locale/locale.h>
|
||||
#include <user/config.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/common/general_window.dds.h>
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_START = 5;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_END = 9;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_END = 4;
|
||||
|
||||
constexpr double OVERLAY_DURATION = 3;
|
||||
|
||||
static bool g_isClosing = false;
|
||||
|
||||
static double g_appearTime = 0;
|
||||
|
||||
static Achievement g_achievement;
|
||||
|
||||
static ImFont* g_fntSeurat;
|
||||
|
||||
static std::unique_ptr<GuestTexture> g_upWindow;
|
||||
|
||||
static bool DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// Expand/retract animation.
|
||||
auto containerMotion = ComputeMotion(g_appearTime, OVERLAY_CONTAINER_COMMON_MOTION_START, OVERLAY_CONTAINER_COMMON_MOTION_END);
|
||||
|
||||
auto centreX = (min.x + max.x) / 2;
|
||||
auto centreY = (min.y + max.y) / 2;
|
||||
|
||||
if (g_isClosing)
|
||||
{
|
||||
min.x = Hermite(min.x, centreX, containerMotion);
|
||||
max.x = Hermite(max.x, centreX, containerMotion);
|
||||
min.y = Hermite(min.y, centreY, containerMotion);
|
||||
max.y = Hermite(max.y, centreY, containerMotion);
|
||||
}
|
||||
else
|
||||
{
|
||||
min.x = Hermite(centreX, min.x, containerMotion);
|
||||
max.x = Hermite(centreX, max.x, containerMotion);
|
||||
min.y = Hermite(centreY, min.y, containerMotion);
|
||||
max.y = Hermite(centreY, max.y, containerMotion);
|
||||
}
|
||||
|
||||
// Transparency fade animation.
|
||||
auto colourMotion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END)
|
||||
: ComputeMotion(g_appearTime, OVERLAY_CONTAINER_INTRO_FADE_START, OVERLAY_CONTAINER_INTRO_FADE_END);
|
||||
|
||||
auto alpha = g_isClosing
|
||||
? Hermite(1, 0, colourMotion)
|
||||
: Hermite(0, 1, colourMotion);
|
||||
|
||||
DrawPauseContainer(g_upWindow.get(), min, max, alpha);
|
||||
|
||||
drawList->PushClipRect(min, max);
|
||||
|
||||
return containerMotion >= 1.0f;
|
||||
}
|
||||
|
||||
void AchievementOverlay::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||
|
||||
g_upWindow = LoadTexture(decompressZstd(g_general_window, g_general_window_uncompressed_size).get(), g_general_window_uncompressed_size);
|
||||
}
|
||||
|
||||
void AchievementOverlay::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
if (ImGui::GetTime() - g_appearTime >= OVERLAY_DURATION)
|
||||
AchievementOverlay::Close();
|
||||
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto& res = ImGui::GetIO().DisplaySize;
|
||||
|
||||
auto strAchievementUnlocked = Localise("Achievements_Unlock").c_str();
|
||||
auto strAchievementName = g_achievement.Name.c_str();
|
||||
|
||||
// Calculate text sizes.
|
||||
auto fontSize = Scale(24);
|
||||
auto headerSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked);
|
||||
auto bodySize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName);
|
||||
auto maxSize = std::max(headerSize.x, bodySize.x) + Scale(5);
|
||||
|
||||
// Calculate image margins.
|
||||
auto imageMarginX = Scale(25);
|
||||
auto imageMarginY = Scale(22.5f);
|
||||
auto imageSize = Scale(60);
|
||||
|
||||
// Calculate text margins.
|
||||
auto textMarginX = imageMarginX * 2 + imageSize - Scale(5);
|
||||
auto textMarginY = imageMarginY + Scale(2);
|
||||
|
||||
auto containerWidth = imageMarginX + textMarginX + maxSize;
|
||||
|
||||
ImVec2 min = { (res.x / 2) - (containerWidth / 2), Scale(55) };
|
||||
ImVec2 max = { min.x + containerWidth, min.y + Scale(105) };
|
||||
|
||||
if (DrawContainer(min, max))
|
||||
{
|
||||
if (g_isClosing)
|
||||
{
|
||||
s_isVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw achievement icon.
|
||||
drawList->AddImage
|
||||
(
|
||||
g_xdbfTextureCache[g_achievement.ID], // user_texture_id
|
||||
{ /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY }, // p_min
|
||||
{ /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize }, // p_max
|
||||
{ 0, 0 }, // uv_min
|
||||
{ 1, 1 }, // uv_max
|
||||
IM_COL32(255, 255, 255, 255) // col
|
||||
);
|
||||
|
||||
// Draw header text.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat, // font
|
||||
fontSize, // fontSize
|
||||
{ /* X */ min.x + textMarginX + (maxSize - headerSize.x) / 2, /* Y */ min.y + textMarginY }, // pos
|
||||
IM_COL32(252, 243, 5, 255), // colour
|
||||
strAchievementUnlocked, // text
|
||||
2, // offset
|
||||
0.4f, // radius
|
||||
IM_COL32(0, 0, 0, 255) // shadowColour
|
||||
);
|
||||
|
||||
// Draw achievement name.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat, // font
|
||||
fontSize, // fontSize
|
||||
{ /* X */ min.x + textMarginX + (maxSize - bodySize.x) / 2, /* Y */ min.y + textMarginY + bodySize.y + Scale(6) }, // pos
|
||||
IM_COL32(255, 255, 255, 255), // colour
|
||||
strAchievementName, // text
|
||||
2, // offset
|
||||
0.4f, // radius
|
||||
IM_COL32(0, 0, 0, 255) // shadowColour
|
||||
);
|
||||
|
||||
// Pop clip rect from DrawContainer.
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementOverlay::Open(int id)
|
||||
{
|
||||
if (s_isVisible)
|
||||
{
|
||||
s_queue.emplace(id);
|
||||
return;
|
||||
}
|
||||
|
||||
s_isVisible = true;
|
||||
g_isClosing = false;
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_achievement = g_xdbfWrapper.GetAchievement((EXDBFLanguage)Config::Language.Value, id);
|
||||
|
||||
Game_PlaySound("obj_navi_appear");
|
||||
}
|
||||
|
||||
void AchievementOverlay::Close()
|
||||
{
|
||||
if (!g_isClosing)
|
||||
{
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
}
|
||||
|
||||
if (s_queue.size())
|
||||
{
|
||||
s_isVisible = false;
|
||||
AchievementOverlay::Open(s_queue.front());
|
||||
s_queue.pop();
|
||||
}
|
||||
}
|
||||
16
UnleashedRecomp/ui/achievement_overlay.h
Normal file
16
UnleashedRecomp/ui/achievement_overlay.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
|
||||
class AchievementOverlay
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
inline static std::queue<uint16_t> s_queue{};
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open(int id);
|
||||
static void Close();
|
||||
};
|
||||
305
UnleashedRecomp/ui/button_guide.cpp
Normal file
305
UnleashedRecomp/ui/button_guide.cpp
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
#include "button_guide.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
#include <gpu/video.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/common/left_mouse_button.dds.h>
|
||||
#include <res/images/common/mat_comon_x360_001.dds.h>
|
||||
#include <res/images/common/start_back.dds.h>
|
||||
|
||||
constexpr float DEFAULT_SIDE_MARGINS = 379;
|
||||
|
||||
ImFont* g_fntNewRodin;
|
||||
ImFont* g_fntNewRodinLQ;
|
||||
|
||||
std::unique_ptr<GuestTexture> g_upIcons;
|
||||
std::unique_ptr<GuestTexture> g_upLMBIcon;
|
||||
std::unique_ptr<GuestTexture> g_upStartBackIcons;
|
||||
|
||||
float g_sideMargins = DEFAULT_SIDE_MARGINS;
|
||||
|
||||
std::vector<Button> g_buttons;
|
||||
|
||||
std::unordered_map<EButtonIcon, float> g_iconWidths =
|
||||
{
|
||||
{ EButtonIcon::A, 40 },
|
||||
{ EButtonIcon::B, 40 },
|
||||
{ EButtonIcon::X, 40 },
|
||||
{ EButtonIcon::Y, 40 },
|
||||
{ EButtonIcon::LB, 70 },
|
||||
{ EButtonIcon::RB, 70 },
|
||||
{ EButtonIcon::LBRB, 70 },
|
||||
{ EButtonIcon::LT, 42 },
|
||||
{ EButtonIcon::RT, 42 },
|
||||
{ EButtonIcon::LTRT, 42 },
|
||||
{ EButtonIcon::Start, 40 },
|
||||
{ EButtonIcon::Back, 40 },
|
||||
{ EButtonIcon::LMB, 40 }
|
||||
};
|
||||
|
||||
std::unordered_map<EButtonIcon, float> g_iconHeights =
|
||||
{
|
||||
{ EButtonIcon::A, 40 },
|
||||
{ EButtonIcon::B, 40 },
|
||||
{ EButtonIcon::X, 40 },
|
||||
{ EButtonIcon::Y, 40 },
|
||||
{ EButtonIcon::LB, 40 },
|
||||
{ EButtonIcon::RB, 40 },
|
||||
{ EButtonIcon::LBRB, 40 },
|
||||
{ EButtonIcon::LT, 42 },
|
||||
{ EButtonIcon::RT, 42 },
|
||||
{ EButtonIcon::LTRT, 42 },
|
||||
{ EButtonIcon::Start, 40 },
|
||||
{ EButtonIcon::Back, 40 },
|
||||
{ EButtonIcon::LMB, 40 }
|
||||
};
|
||||
|
||||
std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon icon)
|
||||
{
|
||||
std::tuple<ImVec2, ImVec2> btn;
|
||||
GuestTexture* texture;
|
||||
|
||||
switch (icon)
|
||||
{
|
||||
case EButtonIcon::A:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 0, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::B:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 40, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::X:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 80, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::Y:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 120, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::LB:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 166, 0, 70, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::RB:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 246, 0, 70, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::LT:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 319, 0, 42, 42);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::RT:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 359, 0, 42, 42);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::Start:
|
||||
btn = PIXELS_TO_UV_COORDS(256, 256, 0, 0, 128, 128);
|
||||
texture = g_upStartBackIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::Back:
|
||||
btn = PIXELS_TO_UV_COORDS(256, 256, 0, 128, 128, 128);
|
||||
texture = g_upStartBackIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::LMB:
|
||||
btn = PIXELS_TO_UV_COORDS(128, 128, 0, 0, 128, 128);
|
||||
texture = g_upLMBIcon.get();
|
||||
break;
|
||||
}
|
||||
|
||||
return std::make_tuple(btn, texture);
|
||||
}
|
||||
|
||||
ImFont* GetFont(EFontQuality fontQuality)
|
||||
{
|
||||
return fontQuality == EFontQuality::Low
|
||||
? g_fntNewRodinLQ
|
||||
: g_fntNewRodin;
|
||||
}
|
||||
|
||||
static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButtonIcon icon,
|
||||
EButtonAlignment alignment, ImVec2 iconMin, ImVec2 iconMax, EFontQuality fontQuality,
|
||||
ImVec2 textSize, float fontSize, const char* text)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto _icon = icon;
|
||||
auto iconWidth = Scale(g_iconWidths[icon]);
|
||||
auto dualIconMarginX = Scale(25);
|
||||
|
||||
if (icon == EButtonIcon::LBRB)
|
||||
{
|
||||
_icon = EButtonIcon::LB;
|
||||
}
|
||||
else if (icon == EButtonIcon::LTRT)
|
||||
{
|
||||
_icon = EButtonIcon::LT;
|
||||
}
|
||||
else
|
||||
{
|
||||
dualIconMarginX = 0;
|
||||
}
|
||||
|
||||
if (icon == EButtonIcon::LBRB || icon == EButtonIcon::LTRT)
|
||||
{
|
||||
iconMin = alignment == EButtonAlignment::Left
|
||||
? ImVec2(/* X */ regionMin.x + *offset - dualIconMarginX * 3, /* Y */ iconMin.y)
|
||||
: ImVec2(/* X */ regionMax.x - *offset - textSize.x - iconWidth, /* Y */ iconMin.y);
|
||||
|
||||
iconMax = alignment == EButtonAlignment::Left
|
||||
? ImVec2(iconMin.x + iconWidth, iconMax.y)
|
||||
: ImVec2(iconMin.x, iconMax.y);
|
||||
}
|
||||
|
||||
auto btnIcon = GetButtonIcon(_icon);
|
||||
drawList->AddImage(std::get<1>(btnIcon), iconMin, iconMax, GET_UV_COORDS(std::get<0>(btnIcon)));
|
||||
|
||||
auto textMarginX = alignment == EButtonAlignment::Left
|
||||
? regionMin.x + *offset + dualIconMarginX
|
||||
: regionMax.x - *offset - dualIconMarginX * 2;
|
||||
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
GetFont(fontQuality),
|
||||
fontSize,
|
||||
{ /* X */ textMarginX, /* Y */ regionMin.y + Scale(8) },
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
text,
|
||||
2,
|
||||
IM_COL32(0, 0, 0, 255)
|
||||
);
|
||||
|
||||
if (icon == EButtonIcon::LBRB || icon == EButtonIcon::LTRT)
|
||||
{
|
||||
auto btnIcon = GetButtonIcon(icon == EButtonIcon::LBRB ? EButtonIcon::RB : EButtonIcon::RT);
|
||||
|
||||
auto dualIconMin = alignment == EButtonAlignment::Left
|
||||
? ImVec2(/* X */ regionMin.x + *offset + textSize.x + dualIconMarginX * 2, /* Y */ iconMin.y)
|
||||
: ImVec2(/* X */ regionMax.x - *offset + textSize.x - dualIconMarginX, /* Y */ iconMin.y);
|
||||
|
||||
auto dualIconMax = ImVec2(dualIconMin.x + iconWidth, iconMax.y);
|
||||
|
||||
drawList->AddImage(std::get<1>(btnIcon), dualIconMin, dualIconMax, GET_UV_COORDS(std::get<0>(btnIcon)));
|
||||
|
||||
*offset += dualIconMarginX + iconWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonGuide::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf", 24.0f * FONT_SCALE);
|
||||
g_fntNewRodinLQ = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf", 19.0f);
|
||||
|
||||
g_upIcons = LoadTexture(
|
||||
decompressZstd(g_mat_comon_x360_001, g_mat_comon_x360_001_uncompressed_size).get(),
|
||||
g_mat_comon_x360_001_uncompressed_size);
|
||||
|
||||
g_upLMBIcon = LoadTexture(
|
||||
decompressZstd(g_left_mouse_button, g_left_mouse_button_uncompressed_size).get(),
|
||||
g_left_mouse_button_uncompressed_size);
|
||||
|
||||
g_upStartBackIcons = LoadTexture(
|
||||
decompressZstd(g_start_back, g_start_back_uncompressed_size).get(),
|
||||
g_start_back_uncompressed_size);
|
||||
}
|
||||
|
||||
void ButtonGuide::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto& res = ImGui::GetIO().DisplaySize;
|
||||
|
||||
auto regionMarginX = Scale(g_sideMargins);
|
||||
auto regionHeight = Scale(102);
|
||||
|
||||
ImVec2 regionMin = { regionMarginX, res.y - regionHeight };
|
||||
ImVec2 regionMax = { res.x - regionMarginX, res.y };
|
||||
|
||||
auto textMarginX = Scale(57);
|
||||
auto textMarginY = Scale(8);
|
||||
auto iconMarginX = Scale(4);
|
||||
auto fontSize = Scale(22.5f);
|
||||
|
||||
auto offsetLeft = 0.0f;
|
||||
auto offsetRight = 0.0f;
|
||||
|
||||
// Draw left aligned icons.
|
||||
for (int i = 0; i < g_buttons.size(); i++)
|
||||
{
|
||||
auto& btn = g_buttons[i];
|
||||
|
||||
if (btn.Alignment != EButtonAlignment::Left)
|
||||
continue;
|
||||
|
||||
if (btn.Visibility && !*btn.Visibility)
|
||||
continue;
|
||||
|
||||
auto iconWidth = Scale(g_iconWidths[btn.Icon]);
|
||||
auto iconHeight = Scale(g_iconHeights[btn.Icon]);
|
||||
auto textSize = g_fntNewRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, btn.Name.c_str());
|
||||
|
||||
if (i > 0)
|
||||
offsetLeft += textSize.x + iconWidth + textMarginX;
|
||||
|
||||
ImVec2 iconMin = { regionMin.x + offsetLeft - iconWidth - iconMarginX, regionMin.y };
|
||||
ImVec2 iconMax = { regionMin.x + offsetLeft - iconMarginX, regionMin.y + iconHeight };
|
||||
|
||||
DrawGuide(&offsetLeft, regionMin, regionMax, btn.Icon, btn.Alignment, iconMin, iconMax, btn.FontQuality, textSize, fontSize, btn.Name.c_str());
|
||||
}
|
||||
|
||||
// Draw right aligned icons.
|
||||
for (int i = g_buttons.size() - 1; i >= 0; i--)
|
||||
{
|
||||
auto& btn = g_buttons[i];
|
||||
|
||||
if (btn.Alignment != EButtonAlignment::Right)
|
||||
continue;
|
||||
|
||||
if (btn.Visibility && !*btn.Visibility)
|
||||
continue;
|
||||
|
||||
auto iconWidth = Scale(g_iconWidths[btn.Icon]);
|
||||
auto iconHeight = Scale(g_iconHeights[btn.Icon]);
|
||||
auto textSize = g_fntNewRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, btn.Name.c_str());
|
||||
|
||||
if (i < g_buttons.size() - 1)
|
||||
offsetRight += textSize.x + iconWidth + textMarginX;
|
||||
|
||||
ImVec2 iconMin = { regionMax.x - offsetRight - iconWidth - iconMarginX, regionMin.y };
|
||||
ImVec2 iconMax = { regionMax.x - offsetRight - iconMarginX, regionMin.y + iconHeight };
|
||||
|
||||
DrawGuide(&offsetRight, regionMin, regionMax, btn.Icon, btn.Alignment, iconMin, iconMax, btn.FontQuality, textSize, fontSize, btn.Name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonGuide::Open(const std::vector<Button> buttons)
|
||||
{
|
||||
s_isVisible = true;
|
||||
g_sideMargins = DEFAULT_SIDE_MARGINS;
|
||||
g_buttons = buttons;
|
||||
}
|
||||
|
||||
void ButtonGuide::SetSideMargins(float width = DEFAULT_SIDE_MARGINS)
|
||||
{
|
||||
g_sideMargins = width;
|
||||
}
|
||||
|
||||
void ButtonGuide::Close()
|
||||
{
|
||||
s_isVisible = false;
|
||||
}
|
||||
61
UnleashedRecomp/ui/button_guide.h
Normal file
61
UnleashedRecomp/ui/button_guide.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <gpu/video.h>
|
||||
|
||||
enum class EButtonIcon
|
||||
{
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
LB,
|
||||
RB,
|
||||
LBRB,
|
||||
LT,
|
||||
RT,
|
||||
LTRT,
|
||||
Start,
|
||||
Back,
|
||||
LMB
|
||||
};
|
||||
|
||||
enum class EButtonAlignment
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
|
||||
enum class EFontQuality
|
||||
{
|
||||
Low,
|
||||
High
|
||||
};
|
||||
|
||||
class Button
|
||||
{
|
||||
public:
|
||||
std::string Name{};
|
||||
EButtonIcon Icon{};
|
||||
EButtonAlignment Alignment{ EButtonAlignment::Right };
|
||||
EFontQuality FontQuality{ EFontQuality::High };
|
||||
bool* Visibility{ nullptr };
|
||||
|
||||
Button(std::string name, EButtonIcon icon, EButtonAlignment alignment, EFontQuality fontQuality = EFontQuality::High, bool* visibility = nullptr)
|
||||
: Name(name), Icon(icon), Alignment(alignment), FontQuality(fontQuality), Visibility(visibility) {}
|
||||
|
||||
Button(std::string name, EButtonIcon icon, bool* visibility) : Name(name), Icon(icon), Visibility(visibility) {}
|
||||
|
||||
Button(std::string name, EButtonIcon icon) : Name(name), Icon(icon) {}
|
||||
};
|
||||
|
||||
class ButtonGuide
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open(const std::vector<Button> buttons);
|
||||
static void SetSideMargins(float width);
|
||||
static void Close();
|
||||
};
|
||||
362
UnleashedRecomp/ui/imgui_utils.h
Normal file
362
UnleashedRecomp/ui/imgui_utils.h
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
#pragma once
|
||||
|
||||
#include <gpu/imgui_common.h>
|
||||
#include <gpu/video.h>
|
||||
#include <app.h>
|
||||
|
||||
#define PIXELS_TO_UV_COORDS(textureWidth, textureHeight, x, y, width, height) \
|
||||
std::make_tuple(ImVec2((float)x / (float)textureWidth, (float)y / (float)textureHeight), \
|
||||
ImVec2(((float)x + (float)width) / (float)textureWidth, ((float)y + (float)height) / (float)textureHeight))
|
||||
|
||||
#define GET_UV_COORDS(tuple) std::get<0>(tuple), std::get<1>(tuple)
|
||||
|
||||
#define CENTRE_TEXT_HORZ(min, max, textSize) min.x + ((max.x - min.x) - textSize.x) / 2
|
||||
#define CENTRE_TEXT_VERT(min, max, textSize) min.y + ((max.y - min.y) - textSize.y) / 2
|
||||
|
||||
static void SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetGradient);
|
||||
callbackData->setGradient.gradientMin[0] = min.x;
|
||||
callbackData->setGradient.gradientMin[1] = min.y;
|
||||
callbackData->setGradient.gradientMax[0] = max.x;
|
||||
callbackData->setGradient.gradientMax[1] = max.y;
|
||||
callbackData->setGradient.gradientTop = top;
|
||||
callbackData->setGradient.gradientBottom = bottom;
|
||||
}
|
||||
|
||||
static void ResetGradient()
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetGradient);
|
||||
memset(&callbackData->setGradient, 0, sizeof(callbackData->setGradient));
|
||||
}
|
||||
|
||||
static void SetShaderModifier(uint32_t shaderModifier)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetShaderModifier);
|
||||
callbackData->setShaderModifier.shaderModifier = shaderModifier;
|
||||
}
|
||||
|
||||
static void SetOrigin(ImVec2 origin)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetOrigin);
|
||||
callbackData->setOrigin.origin[0] = origin.x;
|
||||
callbackData->setOrigin.origin[1] = origin.y;
|
||||
}
|
||||
|
||||
static void SetScale(ImVec2 scale)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetScale);
|
||||
callbackData->setScale.scale[0] = scale.x;
|
||||
callbackData->setScale.scale[1] = scale.y;
|
||||
}
|
||||
|
||||
// Aspect ratio aware.
|
||||
static float Scale(float size)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
if (io.DisplaySize.x > io.DisplaySize.y)
|
||||
return size * std::max(1.0f, io.DisplaySize.y / 720.0f);
|
||||
else
|
||||
return size * std::max(1.0f, io.DisplaySize.x / 1280.0f);
|
||||
}
|
||||
|
||||
// Not aspect ratio aware. Will stretch.
|
||||
static float ScaleX(float x)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
return x * io.DisplaySize.x / 1280.0f;
|
||||
}
|
||||
|
||||
// Not aspect ratio aware. Will stretch.
|
||||
static float ScaleY(float y)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
return y * io.DisplaySize.y / 720.0f;
|
||||
}
|
||||
|
||||
static double ComputeMotion(double duration, double offset, double total)
|
||||
{
|
||||
return sqrt(std::clamp((ImGui::GetTime() - duration - offset / 60.0) / total * 60.0, 0.0, 1.0));
|
||||
}
|
||||
|
||||
static void DrawPauseContainer(GuestTexture* texture, ImVec2 min, ImVec2 max, float alpha = 1)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto commonWidth = Scale(35);
|
||||
auto commonHeight = Scale(35);
|
||||
auto bottomHeight = Scale(5);
|
||||
|
||||
auto tl = PIXELS_TO_UV_COORDS(512, 512, 0, 0, 35, 35);
|
||||
auto tc = PIXELS_TO_UV_COORDS(512, 512, 51, 0, 5, 35);
|
||||
auto tr = PIXELS_TO_UV_COORDS(512, 512, 70, 0, 35, 35);
|
||||
auto cl = PIXELS_TO_UV_COORDS(512, 512, 0, 35, 35, 235);
|
||||
auto cc = PIXELS_TO_UV_COORDS(512, 512, 51, 35, 5, 235);
|
||||
auto cr = PIXELS_TO_UV_COORDS(512, 512, 70, 35, 35, 235);
|
||||
auto bl = PIXELS_TO_UV_COORDS(512, 512, 0, 270, 35, 40);
|
||||
auto bc = PIXELS_TO_UV_COORDS(512, 512, 51, 270, 5, 40);
|
||||
auto br = PIXELS_TO_UV_COORDS(512, 512, 70, 270, 35, 40);
|
||||
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
drawList->AddImage(texture, min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, min.y }, { max.x - commonWidth, min.y + commonHeight }, GET_UV_COORDS(tc), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tr), colour);
|
||||
drawList->AddImage(texture, { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, min.y + commonHeight }, { max.x - commonWidth, max.y - commonHeight }, GET_UV_COORDS(cc), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cr), colour);
|
||||
drawList->AddImage(texture, { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bl), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, max.y - commonHeight }, { max.x - commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bc), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, max.y - commonHeight }, { max.x, max.y + bottomHeight }, GET_UV_COORDS(br), colour);
|
||||
}
|
||||
|
||||
static void DrawPauseHeaderContainer(GuestTexture* texture, ImVec2 min, ImVec2 max, float alpha = 1)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto commonWidth = Scale(35);
|
||||
|
||||
auto left = PIXELS_TO_UV_COORDS(512, 512, 0, 314, 35, 60);
|
||||
auto centre = PIXELS_TO_UV_COORDS(512, 512, 51, 314, 5, 60);
|
||||
auto right = PIXELS_TO_UV_COORDS(512, 512, 70, 314, 35, 60);
|
||||
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
drawList->AddImage(texture, min, { min.x + commonWidth, max.y }, GET_UV_COORDS(left), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, min.y }, { max.x - commonWidth, max.y }, GET_UV_COORDS(centre), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, min.y }, max, GET_UV_COORDS(right), colour);
|
||||
}
|
||||
|
||||
static void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 color, const char* text, double time, double delay, double speed)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto rectWidth = max.x - min.x;
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, text);
|
||||
auto textX = pos.x - fmodf(std::max(0.0, ImGui::GetTime() - (time + delay)) * speed, textSize.x + rectWidth);
|
||||
|
||||
drawList->PushClipRect(min, max, true);
|
||||
|
||||
if (textX <= pos.x)
|
||||
drawList->AddText(font, fontSize, { textX, pos.y }, color, text);
|
||||
|
||||
if (textX + textSize.x < pos.x)
|
||||
drawList->AddText(font, fontSize, { textX + textSize.x + rectWidth, pos.y }, color, text);
|
||||
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, T outlineSize, ImU32 outlineColor)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
outlineSize = Scale(outlineSize);
|
||||
|
||||
if constexpr (std::is_same_v<float, T> || std::is_same_v<double, T>)
|
||||
{
|
||||
// TODO: This is still very inefficient!
|
||||
for (float i = -outlineSize; i <= outlineSize; i += 0.5f)
|
||||
{
|
||||
for (float j = -outlineSize; j <= outlineSize; j += 0.5f)
|
||||
{
|
||||
if (i == 0.0f && j == 0.0f)
|
||||
continue;
|
||||
|
||||
drawList->AddText(font, fontSize, { pos.x + i, pos.y + j }, outlineColor, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_integral_v<T>)
|
||||
{
|
||||
// TODO: This is very inefficient!
|
||||
for (int32_t i = -outlineSize + 1; i < outlineSize; i++)
|
||||
{
|
||||
for (int32_t j = -outlineSize + 1; j < outlineSize; j++)
|
||||
drawList->AddText(font, fontSize, { pos.x + i, pos.y + j }, outlineColor, text);
|
||||
}
|
||||
}
|
||||
|
||||
drawList->AddText(font, fontSize, pos, color, text);
|
||||
}
|
||||
|
||||
static void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text, float offset = 2.0f, float radius = 0.4f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255))
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
offset = Scale(offset);
|
||||
radius = Scale(radius);
|
||||
|
||||
DrawTextWithOutline<float>(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text, radius, shadowColour);
|
||||
|
||||
drawList->AddText(font, fontSize, pos, colour, text, nullptr);
|
||||
}
|
||||
|
||||
static float CalcWidestTextSize(const ImFont* font, float fontSize, std::span<std::string> strs)
|
||||
{
|
||||
auto result = 0.0f;
|
||||
|
||||
for (auto& str : strs)
|
||||
result = std::max(result, font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str()).x);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string Truncate(const std::string& input, size_t maxLength, bool useEllipsis = true, bool usePrefixEllipsis = false)
|
||||
{
|
||||
const std::string ellipsis = "...";
|
||||
|
||||
if (input.length() > maxLength)
|
||||
{
|
||||
if (useEllipsis && maxLength > ellipsis.length())
|
||||
{
|
||||
if (usePrefixEllipsis)
|
||||
{
|
||||
return ellipsis + input.substr(0, maxLength - ellipsis.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.substr(0, maxLength - ellipsis.length()) + ellipsis;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.substr(0, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static std::vector<std::string> Split(const char* str, char delimiter)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
if (!str)
|
||||
return result;
|
||||
|
||||
const char* start = str;
|
||||
const char* current = str;
|
||||
|
||||
while (*current)
|
||||
{
|
||||
if (*current == delimiter)
|
||||
{
|
||||
result.emplace_back(start, current - start);
|
||||
start = current + 1;
|
||||
}
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
result.emplace_back(start);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, std::vector<std::string> lines)
|
||||
{
|
||||
auto x = 0.0f;
|
||||
auto y = 0.0f;
|
||||
|
||||
for (auto& str : lines)
|
||||
{
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
x = std::max(x, textSize.x);
|
||||
y += textSize.y + Scale(lineMargin);
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
static ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, const char* text)
|
||||
{
|
||||
return MeasureCentredParagraph(font, fontSize, lineMargin, Split(text, '\n'));
|
||||
}
|
||||
|
||||
static void DrawCentredParagraph(const ImFont* font, float fontSize, const ImVec2& centre, float lineMargin, const char* text, std::function<void(const char*, ImVec2)> drawMethod)
|
||||
{
|
||||
auto lines = Split(text, '\n');
|
||||
auto paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines);
|
||||
auto offsetY = 0.0f;
|
||||
|
||||
auto hasList = std::strstr(text, "- ");
|
||||
auto isList = false;
|
||||
auto listOffsetX = 0.0f;
|
||||
|
||||
for (int i = 0; i < lines.size(); i++)
|
||||
{
|
||||
auto& str = lines[i];
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
if (hasList)
|
||||
{
|
||||
if (!isList && str.starts_with("- ") && lines.size() > i + 1 && lines[i + 1].starts_with("- "))
|
||||
{
|
||||
isList = true;
|
||||
listOffsetX = centre.x - textSize.x / 2;
|
||||
}
|
||||
else if (isList && !str.starts_with("- "))
|
||||
{
|
||||
isList = false;
|
||||
}
|
||||
}
|
||||
|
||||
drawMethod(str.c_str(), ImVec2(/* X */ isList ? listOffsetX : centre.x - textSize.x / 2, /* Y */ centre.y - paragraphSize.y / 2 + offsetY));
|
||||
|
||||
offsetY += textSize.y + Scale(lineMargin);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawTextWithMarqueeShadow(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 colour, const char* text, double time, double delay, double speed, float offset = 2.0f, float radius = 0.4f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255))
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto rectWidth = max.x - min.x;
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, text);
|
||||
auto textX = pos.x - fmodf(std::max(0.0, ImGui::GetTime() - (time + delay)) * speed, textSize.x + rectWidth);
|
||||
|
||||
drawList->PushClipRect(min, max, true);
|
||||
|
||||
if (textX <= pos.x)
|
||||
DrawTextWithShadow(font, fontSize, { textX, pos.y }, colour, text, offset, radius, shadowColour);
|
||||
|
||||
if (textX + textSize.x < pos.x)
|
||||
DrawTextWithShadow(font, fontSize, { textX + textSize.x + rectWidth, pos.y }, colour, text, offset, radius, shadowColour);
|
||||
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
static float Lerp(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
static float Cubic(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * (t * t * t);
|
||||
}
|
||||
|
||||
static float Hermite(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * (t * t * (3 - 2 * t));
|
||||
}
|
||||
|
||||
static ImVec2 Lerp(const ImVec2& a, const ImVec2& b, float t)
|
||||
{
|
||||
return { a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t };
|
||||
}
|
||||
|
||||
static ImU32 ColourLerp(ImU32 c0, ImU32 c1, float t)
|
||||
{
|
||||
auto a = ImGui::ColorConvertU32ToFloat4(c0);
|
||||
auto b = ImGui::ColorConvertU32ToFloat4(c1);
|
||||
|
||||
ImVec4 result;
|
||||
result.x = a.x + (b.x - a.x) * t;
|
||||
result.y = a.y + (b.y - a.y) * t;
|
||||
result.z = a.z + (b.z - a.z) * t;
|
||||
result.w = a.w + (b.w - a.w) * t;
|
||||
|
||||
return ImGui::ColorConvertFloat4ToU32(result);
|
||||
}
|
||||
1436
UnleashedRecomp/ui/installer_wizard.cpp
Normal file
1436
UnleashedRecomp/ui/installer_wizard.cpp
Normal file
File diff suppressed because it is too large
Load diff
13
UnleashedRecomp/ui/installer_wizard.h
Normal file
13
UnleashedRecomp/ui/installer_wizard.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <api/SWA.h>
|
||||
|
||||
struct InstallerWizard
|
||||
{
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Shutdown();
|
||||
static bool Run(bool skipGame);
|
||||
};
|
||||
351
UnleashedRecomp/ui/message_window.cpp
Normal file
351
UnleashedRecomp/ui/message_window.cpp
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
#include "message_window.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <api/SWA.h>
|
||||
#include <gpu/video.h>
|
||||
#include <locale/locale.h>
|
||||
#include <ui/button_guide.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
#include <res/images/common/general_window.dds.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/common/select_fade.dds.h>
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_START = 5;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_END = 9;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_END = 4;
|
||||
|
||||
static bool g_isAwaitingResult = false;
|
||||
static bool g_isClosing = false;
|
||||
static bool g_isControlsVisible = false;
|
||||
|
||||
static int g_selectedRowIndex;
|
||||
static int g_foregroundCount;
|
||||
|
||||
static bool g_upWasHeld;
|
||||
static bool g_downWasHeld;
|
||||
|
||||
static double g_appearTime;
|
||||
static double g_controlsAppearTime;
|
||||
|
||||
static ImFont* g_fntSeurat;
|
||||
|
||||
static std::unique_ptr<GuestTexture> g_upSelectionCursor;
|
||||
static std::unique_ptr<GuestTexture> g_upWindow;
|
||||
|
||||
std::string g_text;
|
||||
int g_result;
|
||||
std::vector<std::string> g_buttons;
|
||||
int g_defaultButtonIndex;
|
||||
|
||||
bool DrawContainer(float appearTime, ImVec2 centre, ImVec2 max, bool isForeground = true)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
ImVec2 _min = { centre.x - max.x, centre.y - max.y };
|
||||
ImVec2 _max = { centre.x + max.x, centre.y + max.y };
|
||||
|
||||
// Expand/retract animation.
|
||||
auto containerMotion = ComputeMotion(appearTime, OVERLAY_CONTAINER_COMMON_MOTION_START, OVERLAY_CONTAINER_COMMON_MOTION_END);
|
||||
|
||||
if (g_isClosing)
|
||||
{
|
||||
_min.x = Hermite(_min.x, centre.x, containerMotion);
|
||||
_max.x = Hermite(_max.x, centre.x, containerMotion);
|
||||
_min.y = Hermite(_min.y, centre.y, containerMotion);
|
||||
_max.y = Hermite(_max.y, centre.y, containerMotion);
|
||||
}
|
||||
else
|
||||
{
|
||||
_min.x = Hermite(centre.x, _min.x, containerMotion);
|
||||
_max.x = Hermite(centre.x, _max.x, containerMotion);
|
||||
_min.y = Hermite(centre.y, _min.y, containerMotion);
|
||||
_max.y = Hermite(centre.y, _max.y, containerMotion);
|
||||
}
|
||||
|
||||
// Transparency fade animation.
|
||||
auto colourMotion = g_isClosing
|
||||
? ComputeMotion(appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END)
|
||||
: ComputeMotion(appearTime, OVERLAY_CONTAINER_INTRO_FADE_START, OVERLAY_CONTAINER_INTRO_FADE_END);
|
||||
|
||||
auto alpha = g_isClosing
|
||||
? Lerp(1, 0, colourMotion)
|
||||
: Lerp(0, 1, colourMotion);
|
||||
|
||||
if (!isForeground)
|
||||
g_foregroundCount++;
|
||||
|
||||
if (isForeground)
|
||||
drawList->AddRectFilled({ 0.0f, 0.0f }, ImGui::GetIO().DisplaySize, IM_COL32(0, 0, 0, 223 * (g_foregroundCount ? 1 : alpha)));
|
||||
|
||||
DrawPauseContainer(g_upWindow.get(), _min, _max, alpha);
|
||||
|
||||
drawList->PushClipRect(_min, _max);
|
||||
|
||||
return containerMotion >= 1.0f && !g_isClosing;
|
||||
}
|
||||
|
||||
void DrawButton(int rowIndex, float yOffset, float width, float height, std::string& text)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
ImVec2 min = { clipRectMin.x + ((clipRectMax.x - clipRectMin.x) - width) / 2, clipRectMin.y + height * rowIndex + yOffset };
|
||||
ImVec2 max = { min.x + width, min.y + height };
|
||||
|
||||
bool isSelected = rowIndex == g_selectedRowIndex;
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
static auto breatheStart = ImGui::GetTime();
|
||||
auto alpha = Lerp(1.0f, 0.75f, (sin((ImGui::GetTime() - breatheStart) * (2.0f * M_PI / (55.0f / 60.0f))) + 1.0f) / 2.0f);
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
auto width = Scale(11);
|
||||
auto left = PIXELS_TO_UV_COORDS(64, 64, 0, 0, 11, 50);
|
||||
auto centre = PIXELS_TO_UV_COORDS(64, 64, 11, 0, 8, 50);
|
||||
auto right = PIXELS_TO_UV_COORDS(64, 64, 19, 0, 11, 50);
|
||||
|
||||
drawList->AddImage(g_upSelectionCursor.get(), min, { min.x + width, max.y }, GET_UV_COORDS(left), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + width, min.y }, { max.x - width, max.y }, GET_UV_COORDS(centre), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - width, min.y }, max, GET_UV_COORDS(right), colour);
|
||||
}
|
||||
|
||||
auto fontSize = Scale(28);
|
||||
auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, text.c_str());
|
||||
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ /* X */ min.x + ((max.x - min.x) - textSize.x) / 2, /* Y */ min.y + ((max.y - min.y) - textSize.y) / 2 },
|
||||
isSelected ? IM_COL32(255, 128, 0, 255) : IM_COL32(255, 255, 255, 255),
|
||||
text.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
static void ResetSelection()
|
||||
{
|
||||
g_selectedRowIndex = g_defaultButtonIndex;
|
||||
g_upWasHeld = false;
|
||||
g_downWasHeld = false;
|
||||
}
|
||||
|
||||
void MessageWindow::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||
|
||||
g_upSelectionCursor = LoadTexture(decompressZstd(g_select_fade, g_select_fade_uncompressed_size).get(), g_select_fade_uncompressed_size);
|
||||
g_upWindow = LoadTexture(decompressZstd(g_general_window, g_general_window_uncompressed_size).get(), g_general_window_uncompressed_size);
|
||||
}
|
||||
|
||||
void MessageWindow::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
auto pInputState = g_isGameLoaded ? SWA::CInputState::GetInstance() : nullptr;
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto& res = ImGui::GetIO().DisplaySize;
|
||||
|
||||
ImVec2 centre = { res.x / 2, res.y / 2 };
|
||||
|
||||
auto fontSize = Scale(28);
|
||||
auto textSize = MeasureCentredParagraph(g_fntSeurat, fontSize, 5, g_text.c_str());
|
||||
auto textMarginX = Scale(37);
|
||||
auto textMarginY = Scale(45);
|
||||
|
||||
if (DrawContainer(g_appearTime, centre, { textSize.x / 2 + textMarginX, textSize.y / 2 + textMarginY }, !g_isControlsVisible))
|
||||
{
|
||||
DrawCentredParagraph
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ centre.x, centre.y + Scale(3) },
|
||||
5,
|
||||
g_text.c_str(),
|
||||
|
||||
[=](const char* str, ImVec2 pos)
|
||||
{
|
||||
DrawTextWithShadow(g_fntSeurat, fontSize, pos, IM_COL32(255, 255, 255, 255), str);
|
||||
}
|
||||
);
|
||||
|
||||
drawList->PopClipRect();
|
||||
|
||||
bool isAccepted = pInputState
|
||||
? pInputState->GetPadState().IsTapped(SWA::eKeyState_A)
|
||||
: ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
|
||||
if (g_buttons.size())
|
||||
{
|
||||
auto itemWidth = std::max(Scale(162), Scale(CalcWidestTextSize(g_fntSeurat, fontSize, g_buttons)));
|
||||
auto itemHeight = Scale(57);
|
||||
auto windowMarginX = Scale(23);
|
||||
auto windowMarginY = Scale(30);
|
||||
|
||||
ImVec2 controlsMax = { /* X */ itemWidth / 2 + windowMarginX, /* Y */ itemHeight / 2 * g_buttons.size() + windowMarginY };
|
||||
|
||||
if (g_isControlsVisible && DrawContainer(g_controlsAppearTime, centre, controlsMax))
|
||||
{
|
||||
auto rowCount = 0;
|
||||
|
||||
for (auto& button : g_buttons)
|
||||
DrawButton(rowCount++, windowMarginY, itemWidth, itemHeight, button);
|
||||
|
||||
if (pInputState)
|
||||
{
|
||||
bool upIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) ||
|
||||
pInputState->GetPadState().LeftStickVertical > 0.5f;
|
||||
|
||||
bool downIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) ||
|
||||
pInputState->GetPadState().LeftStickVertical < -0.5f;
|
||||
|
||||
bool scrollUp = !g_upWasHeld && upIsHeld;
|
||||
bool scrollDown = !g_downWasHeld && downIsHeld;
|
||||
|
||||
if (scrollUp)
|
||||
{
|
||||
--g_selectedRowIndex;
|
||||
if (g_selectedRowIndex < 0)
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
}
|
||||
else if (scrollDown)
|
||||
{
|
||||
++g_selectedRowIndex;
|
||||
if (g_selectedRowIndex >= rowCount)
|
||||
g_selectedRowIndex = 0;
|
||||
}
|
||||
|
||||
if (scrollUp || scrollDown)
|
||||
Game_PlaySound("sys_actstg_pausecursor");
|
||||
|
||||
g_upWasHeld = upIsHeld;
|
||||
g_downWasHeld = downIsHeld;
|
||||
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||
{
|
||||
g_result = -1;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausecansel");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
|
||||
ButtonGuide::Open
|
||||
(
|
||||
{
|
||||
Button(Localise("Common_Select"), EButtonIcon::A),
|
||||
Button(Localise("Common_Back"),EButtonIcon::B),
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
g_selectedRowIndex = -1;
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
ImVec2 itemMin = { clipRectMin.x + windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i };
|
||||
ImVec2 itemMax = { clipRectMax.x - windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i + itemHeight };
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(itemMin, itemMax, false))
|
||||
g_selectedRowIndex = i;
|
||||
}
|
||||
|
||||
ButtonGuide::Open({ Button(Localise("Common_Select"), EButtonIcon::LMB) });
|
||||
}
|
||||
|
||||
if (g_selectedRowIndex != -1 && isAccepted)
|
||||
{
|
||||
g_result = g_selectedRowIndex;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausedecide");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!g_isControlsVisible && isAccepted)
|
||||
{
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
g_isControlsVisible = true;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinopen");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isAccepted)
|
||||
{
|
||||
g_result = 0;
|
||||
|
||||
MessageWindow::Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g_isClosing)
|
||||
{
|
||||
s_isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageWindow::Open(std::string text, int* result, std::span<std::string> buttons, int defaultButtonIndex)
|
||||
{
|
||||
if (!g_isAwaitingResult && *result == -1)
|
||||
{
|
||||
s_isVisible = true;
|
||||
g_isClosing = false;
|
||||
g_isControlsVisible = false;
|
||||
g_foregroundCount = 0;
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
|
||||
g_text = text;
|
||||
g_buttons = std::vector(buttons.begin(), buttons.end());
|
||||
g_defaultButtonIndex = g_isGameLoaded ? defaultButtonIndex : -1;
|
||||
|
||||
ResetSelection();
|
||||
|
||||
ButtonGuide::Open({ Button(Localise("Common_Next"), g_isGameLoaded ? EButtonIcon::A : EButtonIcon::LMB) });
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinopen");
|
||||
|
||||
g_isAwaitingResult = true;
|
||||
}
|
||||
|
||||
*result = g_result;
|
||||
|
||||
return !g_isAwaitingResult;
|
||||
}
|
||||
|
||||
void MessageWindow::Close()
|
||||
{
|
||||
if (!g_isClosing)
|
||||
{
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
g_isControlsVisible = false;
|
||||
g_foregroundCount = 0;
|
||||
g_isAwaitingResult = false;
|
||||
|
||||
ButtonGuide::Close();
|
||||
}
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinclose");
|
||||
}
|
||||
12
UnleashedRecomp/ui/message_window.h
Normal file
12
UnleashedRecomp/ui/message_window.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
class MessageWindow
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static bool Open(std::string text, int* result, std::span<std::string> buttons = {}, int defaultButtonIndex = 0);
|
||||
static void Close();
|
||||
};
|
||||
1063
UnleashedRecomp/ui/options_menu.cpp
Normal file
1063
UnleashedRecomp/ui/options_menu.cpp
Normal file
File diff suppressed because it is too large
Load diff
19
UnleashedRecomp/ui/options_menu.h
Normal file
19
UnleashedRecomp/ui/options_menu.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <api/SWA.h>
|
||||
|
||||
class OptionsMenu
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
inline static bool s_isPause = false;
|
||||
|
||||
inline static SWA::EMenuType s_pauseMenuType;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open(bool isPause = false, SWA::EMenuType pauseMenuType = SWA::eMenuType_WorldMap);
|
||||
static void Close();
|
||||
|
||||
static bool CanClose();
|
||||
};
|
||||
7
UnleashedRecomp/ui/sdl_listener.cpp
Normal file
7
UnleashedRecomp/ui/sdl_listener.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include "sdl_listener.h"
|
||||
|
||||
std::vector<ISDLEventListener*>& GetEventListeners()
|
||||
{
|
||||
static std::vector<ISDLEventListener*> g_eventListeners;
|
||||
return g_eventListeners;
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "ui/window.h"
|
||||
|
||||
class ISDLEventListener
|
||||
{
|
||||
public:
|
||||
|
|
@ -9,12 +7,14 @@ public:
|
|||
virtual void OnSDLEvent(SDL_Event* event) = 0;
|
||||
};
|
||||
|
||||
std::vector<ISDLEventListener*>& GetEventListeners();
|
||||
|
||||
class SDLEventListener : public ISDLEventListener
|
||||
{
|
||||
public:
|
||||
SDLEventListener()
|
||||
{
|
||||
Window::s_eventListeners.emplace_back(this);
|
||||
GetEventListeners().emplace_back(this);
|
||||
}
|
||||
|
||||
void OnSDLEvent(SDL_Event* event) override {}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
#include "window.h"
|
||||
#include "sdl_listener.h"
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
bool m_isFullscreenKeyReleased = true;
|
||||
bool m_isResizing = false;
|
||||
|
||||
int Window_OnSDLEvent(void*, SDL_Event* event)
|
||||
{
|
||||
if (ImGui::GetIO().BackendPlatformUserData != nullptr)
|
||||
ImGui_ImplSDL2_ProcessEvent(event);
|
||||
|
||||
for (auto listener : Window::s_eventListeners)
|
||||
for (auto listener : GetEventListeners())
|
||||
listener->OnSDLEvent(event);
|
||||
|
||||
switch (event->type)
|
||||
|
|
@ -84,7 +85,7 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
|||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
Window::s_isFocused = true;
|
||||
SDL_ShowCursor(Window::IsFullscreen() ? SDL_DISABLE : SDL_ENABLE);
|
||||
SDL_ShowCursor(Window::IsFullscreen() && !Window::s_cursorAllowed ? SDL_DISABLE : SDL_ENABLE);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
|
|
@ -96,8 +97,10 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
|||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
m_isResizing = true;
|
||||
Window::s_width = event->window.data1;
|
||||
Window::s_height = event->window.data2;
|
||||
Window::SetTitle(std::format("{} - [{}x{}]", Window::GetTitle(), Window::s_width, Window::s_height).c_str());
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
|
|
@ -117,6 +120,20 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
|||
}
|
||||
}
|
||||
|
||||
if (!Window::IsFullscreen())
|
||||
{
|
||||
if (event->type == SDL_CONTROLLERBUTTONDOWN || event->type == SDL_CONTROLLERBUTTONUP || event->type == SDL_CONTROLLERAXISMOTION)
|
||||
{
|
||||
// Hide mouse cursor when controller input is detected.
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
else if (event->type == SDL_MOUSEMOTION)
|
||||
{
|
||||
// Restore mouse cursor when mouse input is detected.
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -177,4 +194,10 @@ void Window::Update()
|
|||
Config::WindowWidth = Window::s_width;
|
||||
Config::WindowHeight = Window::s_height;
|
||||
}
|
||||
|
||||
if (m_isResizing)
|
||||
{
|
||||
SetTitle();
|
||||
m_isResizing = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <res/icon.h>
|
||||
#include <res/icon_night.h>
|
||||
#include <res/images/game_icon.bmp.h>
|
||||
#include <res/images/game_icon_night.bmp.h>
|
||||
#include <ui/window_events.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
|
||||
#if _WIN32
|
||||
#include <dwmapi.h>
|
||||
|
|
@ -14,8 +14,6 @@
|
|||
#define DEFAULT_WIDTH 1280
|
||||
#define DEFAULT_HEIGHT 720
|
||||
|
||||
class SDLEventListener;
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
|
|
@ -29,8 +27,7 @@ public:
|
|||
|
||||
inline static bool s_isFocused;
|
||||
inline static bool s_isIconNight;
|
||||
|
||||
inline static std::vector<SDLEventListener*> s_eventListeners;
|
||||
inline static bool s_cursorAllowed = false;
|
||||
|
||||
static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize)
|
||||
{
|
||||
|
|
@ -56,24 +53,24 @@ public:
|
|||
{
|
||||
if (isNight)
|
||||
{
|
||||
SetIcon((void*)g_iconNight, g_iconNight_size);
|
||||
SetIcon(g_game_icon_night, sizeof(g_game_icon_night));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIcon((void*)g_icon, g_icon_size);
|
||||
SetIcon(g_game_icon, sizeof(g_game_icon));
|
||||
}
|
||||
}
|
||||
|
||||
static void SetTitle(const char* title = nullptr)
|
||||
static const char* GetTitle()
|
||||
{
|
||||
if (!title)
|
||||
{
|
||||
title = Config::Language == ELanguage::Japanese
|
||||
return Config::Language == ELanguage::Japanese
|
||||
? "SONIC WORLD ADVENTURE"
|
||||
: "SONIC UNLEASHED";
|
||||
}
|
||||
|
||||
SDL_SetWindowTitle(s_pWindow, title);
|
||||
static void SetTitle(const char* title = nullptr)
|
||||
{
|
||||
SDL_SetWindowTitle(s_pWindow, title ? title : GetTitle());
|
||||
}
|
||||
|
||||
static void SetDarkTitleBar(bool isEnabled)
|
||||
|
|
@ -103,7 +100,7 @@ public:
|
|||
if (isEnabled)
|
||||
{
|
||||
SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
SDL_ShowCursor(s_cursorAllowed ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -115,6 +112,14 @@ public:
|
|||
return isEnabled;
|
||||
}
|
||||
|
||||
static void SetCursorAllowed(bool isCursorAllowed)
|
||||
{
|
||||
s_cursorAllowed = isCursorAllowed;
|
||||
|
||||
// Refresh fullscreen state to enable the right cursor behavior.
|
||||
SetFullscreen(IsFullscreen());
|
||||
}
|
||||
|
||||
static bool IsMaximised()
|
||||
{
|
||||
return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_MAXIMIZED;
|
||||
|
|
|
|||
174
UnleashedRecomp/user/achievement_data.cpp
Normal file
174
UnleashedRecomp/user/achievement_data.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#include "achievement_data.h"
|
||||
#include <ui/achievement_overlay.h>
|
||||
#include <user/config.h>
|
||||
|
||||
#define NUM_RECORDS sizeof(Data.Records) / sizeof(Record)
|
||||
|
||||
time_t AchievementData::GetTimestamp(uint16_t id)
|
||||
{
|
||||
for (int i = 0; i < NUM_RECORDS; i++)
|
||||
{
|
||||
if (!Data.Records[i].ID)
|
||||
break;
|
||||
|
||||
if (Data.Records[i].ID == id)
|
||||
return Data.Records[i].Timestamp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AchievementData::GetTotalRecords()
|
||||
{
|
||||
auto result = 0;
|
||||
|
||||
for (int i = 0; i < NUM_RECORDS; i++)
|
||||
{
|
||||
if (!Data.Records[i].ID)
|
||||
break;
|
||||
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AchievementData::IsUnlocked(uint16_t id)
|
||||
{
|
||||
for (int i = 0; i < NUM_RECORDS; i++)
|
||||
{
|
||||
if (!Data.Records[i].ID)
|
||||
break;
|
||||
|
||||
if (Data.Records[i].ID == id)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AchievementData::Unlock(uint16_t id)
|
||||
{
|
||||
if (IsUnlocked(id))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < NUM_RECORDS; i++)
|
||||
{
|
||||
if (Data.Records[i].ID == 0)
|
||||
{
|
||||
Data.Records[i].ID = id;
|
||||
Data.Records[i].Timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Config::AchievementNotifications)
|
||||
AchievementOverlay::Open(id);
|
||||
}
|
||||
|
||||
uint32_t AchievementData::CalculateChecksum()
|
||||
{
|
||||
auto result = 0;
|
||||
|
||||
for (int i = 0; i < NUM_RECORDS; i++)
|
||||
{
|
||||
auto& record = Data.Records[i];
|
||||
|
||||
for (size_t j = 0; j < sizeof(Record); j++)
|
||||
result ^= ((uint8_t*)(&record))[j];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AchievementData::VerifySignature()
|
||||
{
|
||||
char sig[4] = ACH_SIGNATURE;
|
||||
|
||||
return Data.Signature[0] == sig[0] &&
|
||||
Data.Signature[1] == sig[1] &&
|
||||
Data.Signature[2] == sig[2] &&
|
||||
Data.Signature[3] == sig[3];
|
||||
}
|
||||
|
||||
bool AchievementData::VerifyVersion()
|
||||
{
|
||||
return Data.Version == Version ACH_VERSION;
|
||||
}
|
||||
|
||||
bool AchievementData::VerifyChecksum()
|
||||
{
|
||||
return Data.Checksum == CalculateChecksum();
|
||||
}
|
||||
|
||||
void AchievementData::Load()
|
||||
{
|
||||
auto dataPath = GetDataPath();
|
||||
|
||||
if (!std::filesystem::exists(dataPath))
|
||||
return;
|
||||
|
||||
std::ifstream file(dataPath, std::ios::binary);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
printf("[*] ERROR: failed to read achievement data.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file.read((char*)&Data.Signature, sizeof(Data.Signature));
|
||||
|
||||
if (!VerifySignature())
|
||||
{
|
||||
printf("[*] ERROR: invalid achievement data signature.\n");
|
||||
|
||||
char sig[4] = ACH_SIGNATURE;
|
||||
|
||||
Data.Signature[0] = sig[0];
|
||||
Data.Signature[1] = sig[1];
|
||||
Data.Signature[2] = sig[2];
|
||||
Data.Signature[3] = sig[3];
|
||||
|
||||
file.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
file.read((char*)&Data.Version, sizeof(Data.Version));
|
||||
|
||||
if (!VerifyVersion())
|
||||
{
|
||||
printf("[*] ERROR: unsupported achievement data version.\n");
|
||||
Data.Version = ACH_VERSION;
|
||||
file.close();
|
||||
return;
|
||||
}
|
||||
|
||||
file.seekg(0);
|
||||
file.read((char*)&Data, sizeof(Data));
|
||||
|
||||
// TODO: display error message to user before wiping data?
|
||||
if (!VerifyChecksum())
|
||||
{
|
||||
printf("[*] ERROR: achievement data checksum mismatch.\n");
|
||||
memset(&Data.Records, 0, sizeof(Data.Records));
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void AchievementData::Save()
|
||||
{
|
||||
std::ofstream file(GetDataPath(), std::ios::binary);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
printf("[*] ERROR: failed to write achievement data.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Data.Checksum = CalculateChecksum();
|
||||
|
||||
file.write((const char*)&Data, sizeof(Data));
|
||||
file.close();
|
||||
}
|
||||
63
UnleashedRecomp/user/achievement_data.h
Normal file
63
UnleashedRecomp/user/achievement_data.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <user/paths.h>
|
||||
|
||||
#define ACH_SIGNATURE { 'A', 'C', 'H', ' ' }
|
||||
#define ACH_VERSION { 1, 0, 0 }
|
||||
#define ACH_RECORDS 50
|
||||
|
||||
class AchievementData
|
||||
{
|
||||
public:
|
||||
#pragma pack(push, 1)
|
||||
struct Record
|
||||
{
|
||||
uint16_t ID;
|
||||
time_t Timestamp;
|
||||
uint16_t Reserved[3];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct Version
|
||||
{
|
||||
uint8_t Major;
|
||||
uint8_t Minor;
|
||||
uint8_t Revision;
|
||||
uint8_t Reserved;
|
||||
|
||||
bool operator==(const Version& other) const
|
||||
{
|
||||
return Major == other.Major &&
|
||||
Minor == other.Minor &&
|
||||
Revision == other.Revision;
|
||||
}
|
||||
};
|
||||
|
||||
class Data
|
||||
{
|
||||
public:
|
||||
char Signature[4];
|
||||
Version Version{};
|
||||
uint32_t Checksum;
|
||||
uint32_t Reserved;
|
||||
Record Records[ACH_RECORDS];
|
||||
};
|
||||
|
||||
inline static Data Data{ ACH_SIGNATURE, ACH_VERSION };
|
||||
|
||||
static std::filesystem::path GetDataPath()
|
||||
{
|
||||
return GetSavePath() / "ACH-DATA";
|
||||
}
|
||||
|
||||
static time_t GetTimestamp(uint16_t id);
|
||||
static int GetTotalRecords();
|
||||
static bool IsUnlocked(uint16_t id);
|
||||
static void Unlock(uint16_t id);
|
||||
static uint32_t CalculateChecksum();
|
||||
static bool VerifySignature();
|
||||
static bool VerifyVersion();
|
||||
static bool VerifyChecksum();
|
||||
static void Load();
|
||||
static void Save();
|
||||
};
|
||||
|
|
@ -18,13 +18,13 @@ void Config::Load()
|
|||
{
|
||||
def->ReadValue(toml);
|
||||
#if _DEBUG
|
||||
printf("%s\n", def->GetDefinition().c_str());
|
||||
printf("%s (0x%p)\n", def->GetDefinition().c_str(), def->GetValue());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
catch (toml::parse_error& err)
|
||||
{
|
||||
printf("Failed to parse configuration: %s\n", err.what());
|
||||
printf("[*] Failed to parse configuration: %s\n", err.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +62,6 @@ void Config::Save()
|
|||
}
|
||||
else
|
||||
{
|
||||
printf("Failed to write configuration.\n");
|
||||
printf("[*] Failed to write configuration.\n");
|
||||
}
|
||||
}
|
||||
77
UnleashedRecomp/user/config.h
Normal file
77
UnleashedRecomp/user/config.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <user/config_detail.h>
|
||||
#include <locale/config_locale.h>
|
||||
#include <user/paths.h>
|
||||
#include <exports.h>
|
||||
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
inline static std::vector<IConfigDef*> Definitions{};
|
||||
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("System", ELanguage, Language, ELanguage::English);
|
||||
CONFIG_DEFINE_LOCALISED("System", bool, Hints, true);
|
||||
CONFIG_DEFINE_LOCALISED("System", bool, ControlTutorial, true);
|
||||
CONFIG_DEFINE_LOCALISED("System", bool, AchievementNotifications, true);
|
||||
CONFIG_DEFINE_LOCALISED("System", bool, SaveScoreAtCheckpoints, false);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("System", EUnleashGaugeBehaviour, UnleashGaugeBehaviour, EUnleashGaugeBehaviour::Original);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("System", ETimeOfDayTransition, TimeOfDayTransition, ETimeOfDayTransition::Xbox);
|
||||
CONFIG_DEFINE_LOCALISED("System", bool, SkipIntroLogos, false);
|
||||
|
||||
CONFIG_DEFINE_LOCALISED("Input", bool, InvertCameraX, false);
|
||||
CONFIG_DEFINE_LOCALISED("Input", bool, InvertCameraY, false);
|
||||
CONFIG_DEFINE_LOCALISED("Input", bool, XButtonHoming, true);
|
||||
CONFIG_DEFINE_LOCALISED("Input", bool, AllowCancellingUnleash, false);
|
||||
CONFIG_DEFINE_LOCALISED("Input", bool, AllowBackgroundInput, false);
|
||||
|
||||
CONFIG_DEFINE_LOCALISED("Audio", float, MusicVolume, 1.0f);
|
||||
CONFIG_DEFINE_LOCALISED("Audio", float, EffectsVolume, 1.0f);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("Audio", EVoiceLanguage, VoiceLanguage, EVoiceLanguage::English);
|
||||
CONFIG_DEFINE_LOCALISED("Audio", bool, Subtitles, true);
|
||||
CONFIG_DEFINE_LOCALISED("Audio", bool, MusicAttenuation, false);
|
||||
CONFIG_DEFINE_LOCALISED("Audio", bool, BattleTheme, true);
|
||||
|
||||
CONFIG_DEFINE_ENUM("Video", EGraphicsAPI, GraphicsAPI, EGraphicsAPI::D3D12);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowX, WINDOWPOS_CENTRED);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowY, WINDOWPOS_CENTRED);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowWidth, 1280);
|
||||
CONFIG_DEFINE("Video", int32_t, WindowHeight, 720);
|
||||
CONFIG_DEFINE_ENUM("Video", EWindowState, WindowState, EWindowState::Normal);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("Video", EAspectRatio, AspectRatio, EAspectRatio::Auto);
|
||||
|
||||
CONFIG_DEFINE_CALLBACK("Video", float, ResolutionScale, 1.0f,
|
||||
{
|
||||
def->Locale = &g_ResolutionScale_locale;
|
||||
def->Value = std::clamp(def->Value, 0.25f, 2.0f);
|
||||
});
|
||||
|
||||
CONFIG_DEFINE_CALLBACK("Video", bool, Fullscreen, false,
|
||||
{
|
||||
def->Locale = &g_Fullscreen_locale;
|
||||
|
||||
Window_SetFullscreen(def->Value);
|
||||
});
|
||||
|
||||
CONFIG_DEFINE_LOCALISED("Video", bool, VSync, true);
|
||||
CONFIG_DEFINE_ENUM("Video", ETripleBuffering, TripleBuffering, ETripleBuffering::Auto);
|
||||
CONFIG_DEFINE_LOCALISED("Video", int32_t, FPS, 60);
|
||||
CONFIG_DEFINE_LOCALISED("Video", float, Brightness, 0.5f);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("Video", EAntiAliasing, AntiAliasing, EAntiAliasing::MSAA4x);
|
||||
CONFIG_DEFINE_LOCALISED("Video", bool, TransparencyAntiAliasing, true);
|
||||
CONFIG_DEFINE("Video", size_t, AnisotropicFiltering, 16);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("Video", EShadowResolution, ShadowResolution, EShadowResolution::x4096);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("Video", EGITextureFiltering, GITextureFiltering, EGITextureFiltering::Bicubic);
|
||||
CONFIG_DEFINE_LOCALISED("Video", bool, MotionBlur, true);
|
||||
CONFIG_DEFINE_LOCALISED("Video", bool, XboxColourCorrection, false);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("Video", EMovieScaleMode, MovieScaleMode, EMovieScaleMode::Fit);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("Video", EUIScaleMode, UIScaleMode, EUIScaleMode::Centre);
|
||||
|
||||
static std::filesystem::path GetConfigPath()
|
||||
{
|
||||
return GetUserPath() / "config.toml";
|
||||
}
|
||||
|
||||
static void Load();
|
||||
static void Save();
|
||||
};
|
||||
190
UnleashedRecomp/user/config_detail.cpp
Normal file
190
UnleashedRecomp/user/config_detail.cpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#include "config.h"
|
||||
#include "config_detail.h"
|
||||
|
||||
// CONFIG_DEFINE
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue)
|
||||
{
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
||||
// CONFIG_DEFINE_LOCALISED
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* locale, T defaultValue)
|
||||
: Section(section), Name(name), Locale(locale), DefaultValue(defaultValue)
|
||||
{
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
||||
// CONFIG_DEFINE_ENUM
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map<std::string, T>* enumTemplate)
|
||||
: Section(section), Name(name), DefaultValue(defaultValue), EnumTemplate(enumTemplate)
|
||||
{
|
||||
for (const auto& pair : *EnumTemplate)
|
||||
EnumTemplateReverse[pair.second] = pair.first;
|
||||
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
||||
// CONFIG_DEFINE_ENUM_LOCALISED
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* locale, T defaultValue, std::unordered_map<std::string, T>* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale)
|
||||
: Section(section), Name(name), Locale(locale), DefaultValue(defaultValue), EnumTemplate(enumTemplate), EnumLocale(enumLocale)
|
||||
{
|
||||
for (const auto& pair : *EnumTemplate)
|
||||
EnumTemplateReverse[pair.second] = pair.first;
|
||||
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
||||
// CONFIG_DEFINE_CALLBACK
|
||||
template<typename T>
|
||||
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue, std::function<void(ConfigDef<T>*)> callback)
|
||||
: Section(section), Name(name), DefaultValue(defaultValue), Callback(callback)
|
||||
{
|
||||
Config::Definitions.emplace_back(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string ConfigDef<T>::GetNameLocalised() const
|
||||
{
|
||||
if (!Locale)
|
||||
return Name;
|
||||
|
||||
if (!Locale->count(Config::Language))
|
||||
{
|
||||
if (Locale->count(ELanguage::English))
|
||||
{
|
||||
return std::get<0>(Locale->at(ELanguage::English));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
return std::get<0>(Locale->at(Config::Language));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string ConfigDef<T>::GetDescription() const
|
||||
{
|
||||
if (!Locale)
|
||||
return "";
|
||||
|
||||
if (!Locale->count(Config::Language))
|
||||
{
|
||||
if (Locale->count(ELanguage::English))
|
||||
{
|
||||
return std::get<1>(Locale->at(ELanguage::English));
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return std::get<1>(Locale->at(Config::Language));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string ConfigDef<T>::GetValueLocalised() const
|
||||
{
|
||||
auto language = Config::Language;
|
||||
CONFIG_ENUM_LOCALE(T)* locale = nullptr;
|
||||
|
||||
if constexpr (std::is_enum_v<T>)
|
||||
{
|
||||
locale = EnumLocale;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
locale = &g_bool_locale;
|
||||
}
|
||||
|
||||
if (!locale)
|
||||
return ToString(false);
|
||||
|
||||
if (!locale->count(language))
|
||||
{
|
||||
if (locale->count(ELanguage::English))
|
||||
{
|
||||
language = ELanguage::English;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ToString(false);
|
||||
}
|
||||
}
|
||||
|
||||
auto strings = locale->at(language);
|
||||
|
||||
if (!strings.count(Value))
|
||||
return ToString(false);
|
||||
|
||||
return std::get<0>(strings.at(Value));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string ConfigDef<T>::GetValueDescription() const
|
||||
{
|
||||
auto language = Config::Language;
|
||||
CONFIG_ENUM_LOCALE(T)* locale = nullptr;
|
||||
|
||||
if constexpr (std::is_enum_v<T>)
|
||||
{
|
||||
locale = EnumLocale;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
locale = &g_bool_locale;
|
||||
}
|
||||
|
||||
if (!locale)
|
||||
return "";
|
||||
|
||||
if (!locale->count(language))
|
||||
{
|
||||
if (locale->count(ELanguage::English))
|
||||
{
|
||||
language = ELanguage::English;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
auto strings = locale->at(language);
|
||||
|
||||
if (!strings.count(Value))
|
||||
return "";
|
||||
|
||||
return std::get<1>(strings.at(Value));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void ConfigDef<T>::GetLocaleStrings(std::vector<std::string_view>& localeStrings) const
|
||||
{
|
||||
if (Locale != nullptr)
|
||||
{
|
||||
for (auto& [language, nameAndDesc] : *Locale)
|
||||
{
|
||||
localeStrings.push_back(std::get<0>(nameAndDesc));
|
||||
localeStrings.push_back(std::get<1>(nameAndDesc));
|
||||
}
|
||||
}
|
||||
|
||||
if (EnumLocale != nullptr)
|
||||
{
|
||||
for (auto& [language, locale] : *EnumLocale)
|
||||
{
|
||||
for (auto& [value, nameAndDesc] : locale)
|
||||
{
|
||||
localeStrings.push_back(std::get<0>(nameAndDesc));
|
||||
localeStrings.push_back(std::get<1>(nameAndDesc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
379
UnleashedRecomp/user/config_detail.h
Normal file
379
UnleashedRecomp/user/config_detail.h
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
#pragma once
|
||||
|
||||
#define CONFIG_DEFINE(section, type, name, defaultValue) \
|
||||
inline static ConfigDef<type> name{section, #name, defaultValue};
|
||||
|
||||
#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue) \
|
||||
inline static ConfigDef<type> name{section, #name, &g_##name##_locale, defaultValue};
|
||||
|
||||
#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \
|
||||
inline static ConfigDef<type> name{section, #name, defaultValue, &g_##type##_template};
|
||||
|
||||
#define CONFIG_DEFINE_ENUM_LOCALISED(section, type, name, defaultValue) \
|
||||
inline static ConfigDef<type> name{section, #name, &g_##name##_locale, defaultValue, &g_##type##_template, &g_##type##_locale};
|
||||
|
||||
#define CONFIG_DEFINE_CALLBACK(section, type, name, defaultValue, readCallback) \
|
||||
inline static ConfigDef<type> name{section, #name, defaultValue, [](ConfigDef<type>* def) readCallback};
|
||||
|
||||
#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \
|
||||
inline static std::unordered_map<std::string, type> g_##type##_template =
|
||||
|
||||
#define CONFIG_LOCALE std::unordered_map<ELanguage, std::tuple<std::string, std::string>>
|
||||
#define CONFIG_ENUM_LOCALE(type) std::unordered_map<ELanguage, std::unordered_map<type, std::tuple<std::string, std::string>>>
|
||||
|
||||
#define WINDOWPOS_CENTRED 0x2FFF0000
|
||||
|
||||
enum class ELanguage : uint32_t
|
||||
{
|
||||
English = 1,
|
||||
Japanese,
|
||||
German,
|
||||
French,
|
||||
Spanish,
|
||||
Italian
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(ELanguage)
|
||||
{
|
||||
{ "English", ELanguage::English },
|
||||
{ "Japanese", ELanguage::Japanese },
|
||||
{ "German", ELanguage::German },
|
||||
{ "French", ELanguage::French },
|
||||
{ "Spanish", ELanguage::Spanish },
|
||||
{ "Italian", ELanguage::Italian }
|
||||
};
|
||||
|
||||
enum class EUnleashGaugeBehaviour : uint32_t
|
||||
{
|
||||
Original,
|
||||
Revised
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EUnleashGaugeBehaviour)
|
||||
{
|
||||
{ "Original", EUnleashGaugeBehaviour::Original },
|
||||
{ "Revised", EUnleashGaugeBehaviour::Revised }
|
||||
};
|
||||
|
||||
enum class ETimeOfDayTransition : uint32_t
|
||||
{
|
||||
Xbox,
|
||||
PlayStation
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(ETimeOfDayTransition)
|
||||
{
|
||||
{ "Xbox", ETimeOfDayTransition::Xbox },
|
||||
{ "PlayStation", ETimeOfDayTransition::PlayStation }
|
||||
};
|
||||
|
||||
enum class EVoiceLanguage : uint32_t
|
||||
{
|
||||
English,
|
||||
Japanese
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EVoiceLanguage)
|
||||
{
|
||||
{ "English", EVoiceLanguage::English },
|
||||
{ "Japanese", EVoiceLanguage::Japanese }
|
||||
};
|
||||
|
||||
enum class EGraphicsAPI : uint32_t
|
||||
{
|
||||
D3D12,
|
||||
Vulkan
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EGraphicsAPI)
|
||||
{
|
||||
{ "D3D12", EGraphicsAPI::D3D12 },
|
||||
{ "Vulkan", EGraphicsAPI::Vulkan }
|
||||
};
|
||||
|
||||
enum class EWindowState : uint32_t
|
||||
{
|
||||
Normal,
|
||||
Maximised
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EWindowState)
|
||||
{
|
||||
{ "Normal", EWindowState::Normal },
|
||||
{ "Maximised", EWindowState::Maximised },
|
||||
{ "Maximized", EWindowState::Maximised }
|
||||
};
|
||||
|
||||
enum class EAspectRatio : uint32_t
|
||||
{
|
||||
Auto,
|
||||
Square,
|
||||
Widescreen
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EAspectRatio)
|
||||
{
|
||||
{ "Auto", EAspectRatio::Auto },
|
||||
{ "4:3", EAspectRatio::Square },
|
||||
{ "16:9", EAspectRatio::Widescreen }
|
||||
};
|
||||
|
||||
enum class ETripleBuffering : uint32_t
|
||||
{
|
||||
Auto,
|
||||
On,
|
||||
Off
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering)
|
||||
{
|
||||
{ "Auto", ETripleBuffering::Auto },
|
||||
{ "On", ETripleBuffering::On },
|
||||
{ "Off", ETripleBuffering::Off }
|
||||
};
|
||||
|
||||
enum class EAntiAliasing : uint32_t
|
||||
{
|
||||
None = 0,
|
||||
MSAA2x = 2,
|
||||
MSAA4x = 4,
|
||||
MSAA8x = 8
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EAntiAliasing)
|
||||
{
|
||||
{ "None", EAntiAliasing::None },
|
||||
{ "2x MSAA", EAntiAliasing::MSAA2x },
|
||||
{ "4x MSAA", EAntiAliasing::MSAA4x },
|
||||
{ "8x MSAA", EAntiAliasing::MSAA8x }
|
||||
};
|
||||
|
||||
enum class EShadowResolution : int32_t
|
||||
{
|
||||
Original = -1,
|
||||
x512 = 512,
|
||||
x1024 = 1024,
|
||||
x2048 = 2048,
|
||||
x4096 = 4096,
|
||||
x8192 = 8192
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EShadowResolution)
|
||||
{
|
||||
{ "Original", EShadowResolution::Original },
|
||||
{ "512", EShadowResolution::x512 },
|
||||
{ "1024", EShadowResolution::x1024 },
|
||||
{ "2048", EShadowResolution::x2048 },
|
||||
{ "4096", EShadowResolution::x4096 },
|
||||
{ "8192", EShadowResolution::x8192 },
|
||||
};
|
||||
|
||||
enum class EGITextureFiltering : uint32_t
|
||||
{
|
||||
Bilinear,
|
||||
Bicubic
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EGITextureFiltering)
|
||||
{
|
||||
{ "Bilinear", EGITextureFiltering::Bilinear },
|
||||
{ "Bicubic", EGITextureFiltering::Bicubic }
|
||||
};
|
||||
|
||||
enum class EMovieScaleMode : uint32_t
|
||||
{
|
||||
Stretch,
|
||||
Fit,
|
||||
Fill
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EMovieScaleMode)
|
||||
{
|
||||
{ "Stretch", EMovieScaleMode::Stretch },
|
||||
{ "Fit", EMovieScaleMode::Fit },
|
||||
{ "Fill", EMovieScaleMode::Fill }
|
||||
};
|
||||
|
||||
enum class EUIScaleMode : uint32_t
|
||||
{
|
||||
Stretch,
|
||||
Edge,
|
||||
Centre
|
||||
};
|
||||
|
||||
CONFIG_DEFINE_ENUM_TEMPLATE(EUIScaleMode)
|
||||
{
|
||||
{ "Stretch", EUIScaleMode::Stretch },
|
||||
{ "Edge", EUIScaleMode::Edge },
|
||||
{ "Centre", EUIScaleMode::Centre },
|
||||
{ "Center", EUIScaleMode::Centre }
|
||||
};
|
||||
|
||||
class IConfigDef
|
||||
{
|
||||
public:
|
||||
virtual ~IConfigDef() = default;
|
||||
virtual void ReadValue(toml::v3::ex::parse_result& toml) = 0;
|
||||
virtual void MakeDefault() = 0;
|
||||
virtual std::string_view GetSection() const = 0;
|
||||
virtual std::string_view GetName() const = 0;
|
||||
virtual std::string GetNameLocalised() const = 0;
|
||||
virtual std::string GetDescription() const = 0;
|
||||
virtual bool IsDefaultValue() const = 0;
|
||||
virtual const void* GetValue() const = 0;
|
||||
virtual std::string GetValueLocalised() const = 0;
|
||||
virtual std::string GetValueDescription() const = 0;
|
||||
virtual std::string GetDefinition(bool withSection = false) const = 0;
|
||||
virtual std::string ToString(bool strWithQuotes = true) const = 0;
|
||||
virtual void GetLocaleStrings(std::vector<std::string_view>& localeStrings) const = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ConfigDef : public IConfigDef
|
||||
{
|
||||
public:
|
||||
std::string Section{};
|
||||
std::string Name{};
|
||||
CONFIG_LOCALE* Locale{};
|
||||
T DefaultValue{};
|
||||
T Value{ DefaultValue };
|
||||
std::unordered_map<std::string, T>* EnumTemplate;
|
||||
std::map<T, std::string> EnumTemplateReverse{};
|
||||
CONFIG_ENUM_LOCALE(T)* EnumLocale{};
|
||||
std::function<void(ConfigDef<T>*)> Callback;
|
||||
|
||||
// CONFIG_DEFINE
|
||||
ConfigDef(std::string section, std::string name, T defaultValue);
|
||||
|
||||
// CONFIG_DEFINE_LOCALISED
|
||||
ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue);
|
||||
|
||||
// CONFIG_DEFINE_ENUM
|
||||
ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map<std::string, T>* enumTemplate);
|
||||
|
||||
// CONFIG_DEFINE_ENUM_LOCALISED
|
||||
ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, std::unordered_map<std::string, T>* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale);
|
||||
|
||||
// CONFIG_DEFINE_CALLBACK
|
||||
ConfigDef(std::string section, std::string name, T defaultValue, std::function<void(ConfigDef<T>*)> callback);
|
||||
|
||||
void ReadValue(toml::v3::ex::parse_result& toml) override
|
||||
{
|
||||
if (auto pSection = toml[Section].as_table())
|
||||
{
|
||||
const auto& section = *pSection;
|
||||
|
||||
if constexpr (std::is_same<T, std::string>::value)
|
||||
{
|
||||
Value = section[Name].value_or<std::string>(DefaultValue);
|
||||
}
|
||||
else if constexpr (std::is_enum_v<T>)
|
||||
{
|
||||
auto it = EnumTemplate->begin();
|
||||
|
||||
Value = EnumTemplate->at(section[Name].value_or<std::string>(static_cast<std::string>(it->first)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = section[Name].value_or(DefaultValue);
|
||||
}
|
||||
|
||||
if (Callback)
|
||||
Callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
void MakeDefault() override
|
||||
{
|
||||
Value = DefaultValue;
|
||||
}
|
||||
|
||||
std::string_view GetSection() const override
|
||||
{
|
||||
return Section;
|
||||
}
|
||||
|
||||
std::string_view GetName() const override
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
std::string GetNameLocalised() const override;
|
||||
|
||||
std::string GetDescription() const override;
|
||||
|
||||
bool IsDefaultValue() const override
|
||||
{
|
||||
return Value == DefaultValue;
|
||||
}
|
||||
|
||||
const void* GetValue() const override
|
||||
{
|
||||
return &Value;
|
||||
}
|
||||
|
||||
std::string GetValueLocalised() const override;
|
||||
|
||||
std::string GetValueDescription() const override;
|
||||
|
||||
std::string GetDefinition(bool withSection = false) const override
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if (withSection)
|
||||
result += "[" + Section + "]\n";
|
||||
|
||||
result += Name + " = " + ToString();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ToString(bool strWithQuotes = true) const override
|
||||
{
|
||||
std::string result = "N/A";
|
||||
|
||||
if constexpr (std::is_same_v<T, std::string>)
|
||||
{
|
||||
result = std::format("{}", Value);
|
||||
|
||||
if (strWithQuotes)
|
||||
result = std::format("\"{}\"", result);
|
||||
}
|
||||
else if constexpr (std::is_enum_v<T>)
|
||||
{
|
||||
auto it = EnumTemplateReverse.find(Value);
|
||||
|
||||
if (it != EnumTemplateReverse.end())
|
||||
result = std::format("{}", it->second);
|
||||
|
||||
if (strWithQuotes)
|
||||
result = std::format("\"{}\"", result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = std::format("{}", Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void GetLocaleStrings(std::vector<std::string_view>& localeStrings) const override;
|
||||
|
||||
ConfigDef& operator=(const ConfigDef& other)
|
||||
{
|
||||
if (this != &other)
|
||||
Value = other.Value;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
void operator=(const T& other)
|
||||
{
|
||||
Value = other;
|
||||
}
|
||||
};
|
||||
25
UnleashedRecomp/user/paths.h
Normal file
25
UnleashedRecomp/user/paths.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#define USER_DIRECTORY "SWA"
|
||||
|
||||
static std::filesystem::path GetUserPath()
|
||||
{
|
||||
if (std::filesystem::exists("portable.txt"))
|
||||
return std::filesystem::current_path();
|
||||
|
||||
std::filesystem::path userPath{};
|
||||
|
||||
// TODO: handle platform-specific paths.
|
||||
PWSTR knownPath = NULL;
|
||||
if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &knownPath) == S_OK)
|
||||
userPath = std::filesystem::path{ knownPath } / USER_DIRECTORY;
|
||||
|
||||
CoTaskMemFree(knownPath);
|
||||
|
||||
return userPath;
|
||||
}
|
||||
|
||||
static std::filesystem::path GetSavePath()
|
||||
{
|
||||
return GetUserPath() / "save";
|
||||
}
|
||||
|
|
@ -98,8 +98,9 @@ jump_address_on_false = 0x82468EE0
|
|||
|
||||
[[midasm_hook]]
|
||||
name = "CameraBoostAspectRatioMidAsmHook"
|
||||
address = 0x8246BDA8
|
||||
registers = ["r31", "f0"]
|
||||
address = 0x8246BDA0
|
||||
registers = ["r31", "f0", "f10", "f12"]
|
||||
jump_address_on_true = 0x8246BDAC
|
||||
|
||||
[[midasm_hook]]
|
||||
name = "CSDAspectRatioMidAsmHook"
|
||||
|
|
@ -453,3 +454,91 @@ registers = ["r1", "r30"]
|
|||
name = "StorageDevicePromptMidAsmHook"
|
||||
address = 0x822C53CC
|
||||
jump_address = 0x822C53F8
|
||||
|
||||
# World Map Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseAddOptionsItemMidAsmHook"
|
||||
address = 0x824AF140
|
||||
registers = ["r31"]
|
||||
|
||||
# Village Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseAddOptionsItemMidAsmHook"
|
||||
address = 0x824AF5BC
|
||||
registers = ["r31"]
|
||||
|
||||
# Stage Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseAddOptionsItemMidAsmHook"
|
||||
address = 0x824AF988
|
||||
registers = ["r31"]
|
||||
|
||||
# Hub Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseAddOptionsItemMidAsmHook"
|
||||
address = 0x824AFB20
|
||||
registers = ["r31"]
|
||||
|
||||
# Misc Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseAddOptionsItemMidAsmHook"
|
||||
address = 0x824AFCC8
|
||||
registers = ["r31"]
|
||||
|
||||
# World Map Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseItemCountMidAsmHook"
|
||||
address = 0x824B02F8
|
||||
registers = ["r3", "r11"]
|
||||
return_on_true = true
|
||||
|
||||
# Village Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseVillageItemCountMidAsmHook"
|
||||
address = 0x824B04AC
|
||||
registers = ["r31", "r10"]
|
||||
|
||||
# Stage Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseItemCountMidAsmHook"
|
||||
address = 0x824B061C
|
||||
registers = ["r3", "r11"]
|
||||
return_on_true = true
|
||||
|
||||
# Hub Pause Menu
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseItemCountMidAsmHook"
|
||||
address = 0x824B07C4
|
||||
registers = ["r3", "r10"]
|
||||
return_on_true = true
|
||||
|
||||
# Misc Pause Menu Index Wrap-around
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseItemCountMidAsmHook"
|
||||
address = 0x824B08A8
|
||||
registers = ["r3", "r11"]
|
||||
return_on_true = true
|
||||
|
||||
# Misc Pause Menu Index Comparison
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseMiscItemCountMidAsmHook"
|
||||
address = 0x824B08B0
|
||||
registers = ["r11"]
|
||||
jump_address_on_true = 0x824B08C0
|
||||
|
||||
# Misc Pause Menu Inject Options Behaviour
|
||||
[[midasm_hook]]
|
||||
name = "CHudPauseMiscInjectOptionsMidAsmHook"
|
||||
address = 0x824B08C0
|
||||
registers = ["r3"]
|
||||
return_on_true = true
|
||||
|
||||
[[midasm_hook]]
|
||||
name = "ToggleSubtitlesMidAsmHook"
|
||||
address = 0x82B9BB74
|
||||
registers = ["r27"]
|
||||
|
||||
[[midasm_hook]]
|
||||
name = "AchievementManagerUnlockMidAsmHook"
|
||||
address = 0x82BCFF28
|
||||
registers = ["r31"]
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 7183c6e0e90480fe0f2c270c87cea5cd5df7d5c3
|
||||
Subproject commit ccb516047d697f9e4009a0b4b534adcef758b64a
|
||||
1
tools/CMakeLists.txt
Normal file
1
tools/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
add_subdirectory(${SWA_TOOLS_ROOT}/file_to_c)
|
||||
11
tools/file_to_c/CMakeLists.txt
Normal file
11
tools/file_to_c/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
project("file_to_c")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_executable(file_to_c "file_to_c.cpp")
|
||||
|
||||
find_package(zstd CONFIG REQUIRED)
|
||||
target_link_libraries(file_to_c PRIVATE $<IF:$<TARGET_EXISTS:zstd::libzstd_static>,zstd::libzstd_static,zstd::libzstd>)
|
||||
136
tools/file_to_c/file_to_c.cpp
Normal file
136
tools/file_to_c/file_to_c.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 RT64 Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <zstd.h>
|
||||
|
||||
std::vector<char> read_file(const char* path) {
|
||||
std::ifstream input_file{path, std::ios::binary};
|
||||
std::vector<char> ret{};
|
||||
|
||||
if (!input_file.good()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Get the length of the file
|
||||
input_file.seekg(0, std::ios::end);
|
||||
ret.resize(input_file.tellg());
|
||||
|
||||
// Read the file contents into the vector
|
||||
input_file.seekg(0, std::ios::beg);
|
||||
input_file.read(ret.data(), ret.size());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void create_parent_if_needed(const char* path) {
|
||||
std::filesystem::path parent_path = std::filesystem::path{path}.parent_path();
|
||||
if (!parent_path.empty()) {
|
||||
std::filesystem::create_directories(parent_path);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
if (argc != 6) {
|
||||
printf("Usage: %s [input file] [array name] [compression type] [output C file] [output C header]\n", argv[0]);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
const char* input_path = argv[1];
|
||||
const char* array_name = argv[2];
|
||||
std::string compression_type = argv[3];
|
||||
const char* output_c_path = argv[4];
|
||||
const char* output_h_path = argv[5];
|
||||
|
||||
// Read the input file's contents
|
||||
std::vector<char> contents = read_file(input_path);
|
||||
|
||||
if (contents.empty()) {
|
||||
fprintf(stderr, "Failed to open file %s! (Or it's empty)\n", input_path);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Compress if requested.
|
||||
std::vector<char> compressed_contents;
|
||||
std::transform(compression_type.begin(), compression_type.end(), compression_type.begin(), tolower);
|
||||
|
||||
if (compression_type == "zstd") {
|
||||
size_t bound_size = ZSTD_compressBound(contents.size());
|
||||
compressed_contents.resize(bound_size);
|
||||
|
||||
size_t compressed_size = ZSTD_compress(compressed_contents.data(), bound_size, contents.data(), contents.size(), ZSTD_maxCLevel());
|
||||
compressed_contents.resize(compressed_size);
|
||||
}
|
||||
else if (compression_type != "none") {
|
||||
fprintf(stderr, "Unknown compression type %s!", compression_type.c_str());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Create the output directories if they don't exist
|
||||
create_parent_if_needed(output_c_path);
|
||||
create_parent_if_needed(output_h_path);
|
||||
|
||||
// Write the C file with the array
|
||||
std::vector<char>& contents_to_write = !compressed_contents.empty() ? compressed_contents : contents;
|
||||
{
|
||||
std::ofstream output_c_file{output_c_path};
|
||||
output_c_file << "extern unsigned char " << array_name << "[" << contents_to_write.size() << "];\n";
|
||||
output_c_file << "unsigned char " << array_name << "[" << contents_to_write.size() << "] = {";
|
||||
|
||||
for (char x : contents_to_write) {
|
||||
output_c_file << (int)(unsigned char)x << ", ";
|
||||
}
|
||||
|
||||
output_c_file << "};\n";
|
||||
|
||||
// Write decompressed size.
|
||||
if (!compressed_contents.empty()) {
|
||||
output_c_file << "extern size_t " << array_name << "_uncompressed_size;\n";
|
||||
output_c_file << "size_t " << array_name << "_uncompressed_size = " << contents.size() << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Write the header file with the extern array
|
||||
{
|
||||
std::ofstream output_h_file{output_h_path};
|
||||
output_h_file <<
|
||||
"#ifdef __cplusplus\n"
|
||||
" extern \"C\" {\n"
|
||||
"#endif\n"
|
||||
"extern unsigned char " << array_name << "[" << contents_to_write.size() << "];\n";
|
||||
|
||||
// Write decompressed size.
|
||||
if (!compressed_contents.empty()) {
|
||||
output_h_file << "extern size_t " << array_name << "_uncompressed_size;\n";
|
||||
}
|
||||
|
||||
output_h_file <<
|
||||
"#ifdef __cplusplus\n"
|
||||
" }\n"
|
||||
"#endif\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
"features": [ "sdl2-binding" ]
|
||||
},
|
||||
"magic-enum",
|
||||
"nativefiledialog-extended",
|
||||
"miniaudio"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue