mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-04-26 20:31:41 +00:00
Implemented achievement overlay (WIP)
This commit is contained in:
parent
91eb12f42c
commit
15e1472684
36 changed files with 587 additions and 125 deletions
|
|
@ -28,11 +28,6 @@ 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"
|
||||
|
|
@ -80,10 +75,17 @@ set(SWA_PATCHES_CXX_SOURCES
|
|||
)
|
||||
|
||||
set(SWA_UI_CXX_SOURCES
|
||||
"ui/achievement_overlay.cpp"
|
||||
"ui/options_menu.cpp"
|
||||
"ui/window.cpp"
|
||||
)
|
||||
|
||||
set(SWA_USER_CXX_SOURCES
|
||||
"user/achievement_data.cpp"
|
||||
"user/config.cpp"
|
||||
"user/config_detail.cpp"
|
||||
)
|
||||
|
||||
set(SWA_CXX_SOURCES
|
||||
"app.cpp"
|
||||
"exports.cpp"
|
||||
|
|
@ -91,7 +93,6 @@ set(SWA_CXX_SOURCES
|
|||
"misc_impl.cpp"
|
||||
"stdafx.cpp"
|
||||
|
||||
${SWA_CFG_CXX_SOURCES}
|
||||
${SWA_KERNEL_CXX_SOURCES}
|
||||
${SWA_CPU_CXX_SOURCES}
|
||||
${SWA_GPU_CXX_SOURCES}
|
||||
|
|
@ -99,6 +100,7 @@ set(SWA_CXX_SOURCES
|
|||
${SWA_HID_CXX_SOURCES}
|
||||
${SWA_PATCHES_CXX_SOURCES}
|
||||
${SWA_UI_CXX_SOURCES}
|
||||
${SWA_USER_CXX_SOURCES}
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "CSD/Manager/csdmSubjectBase.h"
|
||||
#include "CSD/Platform/csdTexList.h"
|
||||
|
||||
#include "SWA/Achievement/AchievementManager.h"
|
||||
#include "SWA/Camera/Camera.h"
|
||||
#include "SWA/CSD/CsdDatabaseWrapper.h"
|
||||
#include "SWA/CSD/CsdProject.h"
|
||||
|
|
@ -40,6 +41,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,3 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <xdbf_wrapper.h>
|
||||
|
||||
extern double g_deltaTime;
|
||||
extern XDBFWrapper g_xdbf;
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@
|
|||
#include <kernel/memory.h>
|
||||
#include <xxHashMap.h>
|
||||
#include <shader/shader_cache.h>
|
||||
#include <ui/achievement_overlay.h>
|
||||
#include <ui/options_menu.h>
|
||||
|
||||
#include "imgui_snapshot.h"
|
||||
#include "imgui_common.h"
|
||||
#include "video.h"
|
||||
#include <ui/window.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
|
||||
#include "shader/copy_vs.hlsl.dxil.h"
|
||||
#include "shader/copy_vs.hlsl.spirv.h"
|
||||
|
|
@ -1011,6 +1012,7 @@ static void CreateImGuiBackend()
|
|||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||||
OptionsMenu::Init();
|
||||
AchievementOverlay::Init();
|
||||
ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
|
||||
|
||||
g_imFontTexture = std::make_unique<GuestTexture>(ResourceType::Texture);
|
||||
|
|
@ -1649,6 +1651,7 @@ static void DrawImGui()
|
|||
{
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
AchievementOverlay::Draw();
|
||||
OptionsMenu::Draw();
|
||||
ImGui::Render();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include <stdafx.h>
|
||||
#include <SDL.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
#include <hid/hid_detail.h>
|
||||
#include <ui/window.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <cfg/config_detail.h>
|
||||
#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 =
|
||||
|
|
@ -89,6 +89,11 @@ 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]" } }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
|
||||
inline static std::string g_localeMissing = "<missing string>";
|
||||
|
||||
|
|
@ -65,6 +65,18 @@ inline static std::unordered_map<std::string, std::unordered_map<ELanguage, std:
|
|||
{
|
||||
{ ELanguage::English, "This option is not supported by your operating system." }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Achievements_Name",
|
||||
{
|
||||
{ ELanguage::English, "Achievements" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"Achievements_Unlock",
|
||||
{
|
||||
{ ELanguage::English, "Achievement Unlocked!" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@
|
|||
#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 <xdbf_wrapper.h>
|
||||
|
||||
#define GAME_XEX_PATH "game:\\default.xex"
|
||||
|
||||
|
|
@ -21,6 +24,7 @@ const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF;
|
|||
Memory g_memory{ reinterpret_cast<void*>(0x100000000), 0x100000000 };
|
||||
Heap g_userHeap;
|
||||
CodeCache g_codeCache;
|
||||
XDBFWrapper g_xdbf;
|
||||
|
||||
// Name inspired from nt's entry point
|
||||
void KiSystemStartup()
|
||||
|
|
@ -40,7 +44,7 @@ void KiSystemStartup()
|
|||
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?
|
||||
|
|
@ -118,12 +122,17 @@ uint32_t LdrLoadModule(const char* path)
|
|||
}
|
||||
}
|
||||
|
||||
auto res = Xex2FindOptionalHeader<XEX_RESOURCE_INFO>(xex, XEX_HEADER_RESOURCE_INFO);
|
||||
|
||||
g_xdbf = XDBFWrapper((uint8_t*)g_memory.Translate(res->Offset.get()), res->SizeOfData);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
Config::Load();
|
||||
AchievementData::Load();
|
||||
|
||||
KiSystemStartup();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include <cpu/guest_code.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
#include <kernel/function.h>
|
||||
#include <kernel/platform.h>
|
||||
#include <patches/audio_patches.h>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
constexpr float m_baseAspectRatio = 16.0f / 9.0f;
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,9 +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);
|
||||
|
|
@ -42,13 +45,38 @@ PPC_FUNC(sub_824EB9B0)
|
|||
__imp__sub_824EB9B0(ctx, base);
|
||||
}
|
||||
|
||||
// CApplicationDocument::LoadArchiveDatabases
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#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;
|
||||
|
|
|
|||
183
UnleashedRecomp/ui/achievement_overlay.cpp
Normal file
183
UnleashedRecomp/ui/achievement_overlay.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#include "achievement_overlay.h"
|
||||
#include "imgui_utils.h"
|
||||
|
||||
#include <user/config.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <gpu/video.h>
|
||||
#include <kernel/memory.h>
|
||||
#include <locale/locale.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
|
||||
constexpr double OVERLAY_CONTAINER_MOTION_START = 0.0;
|
||||
constexpr double OVERLAY_CONTAINER_MOTION_END = 8.0;
|
||||
constexpr double OVERLAY_CONTAINER_FADE_IN_START = 5.0;
|
||||
constexpr double OVERLAY_CONTAINER_FADE_OUT_START = 4.0;
|
||||
constexpr double OVERLAY_ELEMENTS_FADE_START = 10.0;
|
||||
constexpr double OVERLAY_ELEMENTS_FADE_END = 12.0;
|
||||
constexpr double OVERLAY_DURATION = 5.0;
|
||||
|
||||
static bool g_isClosing = false;
|
||||
|
||||
static double g_appearTime = 0.0;
|
||||
|
||||
static Achievement g_achievement;
|
||||
static std::unique_ptr<GuestTexture> g_upAchievementIcon;
|
||||
|
||||
static ImFont* g_fntSeurat;
|
||||
|
||||
void AchievementOverlay::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 30.0f);
|
||||
}
|
||||
|
||||
static double ComputeMotion(double frameOffset, double frames)
|
||||
{
|
||||
double t = std::clamp((ImGui::GetTime() - g_appearTime - frameOffset / 60.0) / frames * 60.0, 0.0, 1.0);
|
||||
return sqrt(t);
|
||||
}
|
||||
|
||||
// TODO: move this somewhere where it can be re-used.
|
||||
void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto containerMotion = ComputeMotion(OVERLAY_CONTAINER_MOTION_START, OVERLAY_CONTAINER_MOTION_END);
|
||||
auto centreX = (min.x + max.x) / 2.0f;
|
||||
auto centreY = (min.y + max.y) / 2.0f;
|
||||
|
||||
if (g_isClosing)
|
||||
{
|
||||
min.x = CubicEase(min.x, centreX, containerMotion);
|
||||
max.x = CubicEase(max.x, centreX, containerMotion);
|
||||
min.y = CubicEase(min.y, centreY, containerMotion);
|
||||
max.y = CubicEase(max.y, centreY, containerMotion);
|
||||
}
|
||||
else
|
||||
{
|
||||
min.x = CubicEase(centreX, min.x, containerMotion);
|
||||
max.x = CubicEase(centreX, max.x, containerMotion);
|
||||
min.y = CubicEase(centreY, min.y, containerMotion);
|
||||
max.y = CubicEase(centreY, max.y, containerMotion);
|
||||
}
|
||||
|
||||
ImVec2 v1 = { min.x, min.y + cornerRadius };
|
||||
ImVec2 v2 = { min.x + cornerRadius, min.y };
|
||||
ImVec2 v3 = { max.x, min.y };
|
||||
ImVec2 v4 = { max.x, min.y + cornerRadius };
|
||||
ImVec2 v5 = { max.x, max.y - cornerRadius };
|
||||
ImVec2 v6 = { max.x - cornerRadius, max.y };
|
||||
ImVec2 v7 = { min.x, max.y };
|
||||
ImVec2 v8 = { min.x, max.y - cornerRadius };
|
||||
|
||||
ImVec2 top[] = { v1, v2, v3, v4 };
|
||||
ImVec2 bottom[] = { v5, v6, v7, v8 };
|
||||
ImVec2 border[] = { v1, v2, v3, v4, v5, v6, v7, v8 };
|
||||
|
||||
auto colourMotion = ComputeMotion(g_isClosing ? OVERLAY_CONTAINER_MOTION_START : OVERLAY_CONTAINER_FADE_IN_START,
|
||||
g_isClosing ? OVERLAY_CONTAINER_FADE_OUT_START : OVERLAY_CONTAINER_MOTION_END);
|
||||
|
||||
auto colShadow = IM_COL32(0, 0, 0, (int)CubicEase(g_isClosing ? 156 : 0, g_isClosing ? 0 : 156, colourMotion));
|
||||
auto colGradientTop = IM_COL32(197, 194, 197, (int)CubicEase(g_isClosing ? 200 : 0, g_isClosing ? 0 : 200, colourMotion));
|
||||
auto colGradientBottom = IM_COL32(121, 120, 121, (int)CubicEase(g_isClosing ? 236 : 0, g_isClosing ? 0 : 236, colourMotion)); // TODO: match gradient used by the game (115, 113, 115, 236).
|
||||
|
||||
// TODO: add a drop shadow.
|
||||
|
||||
drawList->AddConvexPolyFilled(top, IM_ARRAYSIZE(top), colGradientTop);
|
||||
drawList->AddRectFilledMultiColor({ min.x, min.y + cornerRadius }, { max.x, max.y - cornerRadius }, colGradientTop, colGradientTop, colGradientBottom, colGradientBottom);
|
||||
drawList->AddConvexPolyFilled(bottom, IM_ARRAYSIZE(bottom), colGradientBottom);
|
||||
drawList->AddPolyline(border, IM_ARRAYSIZE(border), IM_COL32(247, 247, 247, (int)CubicEase(g_isClosing ? 255 : 0, g_isClosing ? 0 : 255, colourMotion)), true, Scale(2.5f));
|
||||
|
||||
for (int i = 0; i < IM_ARRAYSIZE(border); i++)
|
||||
{
|
||||
border[i].x -= 0.4f;
|
||||
border[i].y -= 0.2f;
|
||||
}
|
||||
|
||||
auto lineAlpha = (int)CubicEase(g_isClosing ? 230 : 0, g_isClosing ? 0 : 230, colourMotion);
|
||||
auto colLineTop = IM_COL32(165, 170, 165, lineAlpha);
|
||||
auto colLineBottom = IM_COL32(190, 190, 190, lineAlpha);
|
||||
auto lineThickness = Scale(1.0f);
|
||||
|
||||
// Top left corner bottom to top left corner top.
|
||||
drawList->AddLine(border[0], border[1], colLineTop, lineThickness * 0.5f);
|
||||
|
||||
// Top left corner bottom to bottom left.
|
||||
drawList->AddRectFilledMultiColor({ border[0].x - 0.2f, border[0].y }, { border[6].x + lineThickness - 0.2f, border[6].y }, colLineTop, colLineTop, colLineBottom, colLineBottom);
|
||||
|
||||
// Top left corner top to top right.
|
||||
drawList->AddLine(border[1], border[2], colLineTop, lineThickness);
|
||||
|
||||
drawList->PushClipRect(min, max);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
auto fontSize = 30.0f;
|
||||
auto headerTextSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, strAchievementUnlocked);
|
||||
auto bodyTextSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, strAchievementName);
|
||||
auto longestTextSize = std::max(headerTextSize.x, bodyTextSize.x);
|
||||
|
||||
constexpr auto imageX = 25.0f;
|
||||
constexpr auto imageY = 20.0f;
|
||||
constexpr auto imageSize = 90.0f;
|
||||
constexpr auto textX = 140.0f;
|
||||
|
||||
auto width = textX + longestTextSize + 30.0f;
|
||||
|
||||
ImVec2 min = { (res.x / 2.0f) - (width / 2.0f), 50.0f };
|
||||
ImVec2 max = { min.x + width, min.y + 125.0f };
|
||||
|
||||
DrawContainer(min, max);
|
||||
|
||||
auto colourMotion = ComputeMotion(g_isClosing ? OVERLAY_CONTAINER_MOTION_START : OVERLAY_ELEMENTS_FADE_START,
|
||||
g_isClosing ? OVERLAY_CONTAINER_FADE_OUT_START : OVERLAY_ELEMENTS_FADE_END);
|
||||
|
||||
auto alpha = (int)CubicEase(g_isClosing ? 255 : 0, g_isClosing ? 0 : 255, colourMotion);
|
||||
|
||||
drawList->AddImage(g_upAchievementIcon.get(), { min.x + imageX, min.y + imageY }, { min.x + imageX + imageSize, min.y + imageY + imageSize }, { 0, 0 }, { 1, 1 }, IM_COL32(255, 255, 255, alpha));
|
||||
|
||||
DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - headerTextSize.x) / 2.0f, min.y + 30.0f}, IM_COL32(252, 243, 5, alpha), strAchievementUnlocked, 2.0f, IM_COL32(0, 0, 0, alpha));
|
||||
DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - bodyTextSize.x) / 2.0f, min.y + 68.0f}, IM_COL32(255, 255, 255, alpha), strAchievementName, 2.0f, IM_COL32(0, 0, 0, alpha));
|
||||
|
||||
// Pop clip rect from DrawContainer
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
void AchievementOverlay::Open(int id)
|
||||
{
|
||||
s_isVisible = true;
|
||||
g_isClosing = false;
|
||||
g_appearTime = ImGui::GetTime();
|
||||
|
||||
g_achievement = g_xdbf.GetAchievement((EXDBFLanguage)Config::Language.Value, id);
|
||||
g_upAchievementIcon = LoadTexture((uint8_t*)g_achievement.pImageBuffer, g_achievement.ImageBufferSize);
|
||||
|
||||
Game_PlaySound("obj_navi_appear");
|
||||
}
|
||||
|
||||
void AchievementOverlay::Close()
|
||||
{
|
||||
if (!g_isClosing)
|
||||
{
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
}
|
||||
|
||||
if (ImGui::GetTime() - g_appearTime >= OVERLAY_ELEMENTS_FADE_END)
|
||||
s_isVisible = false;
|
||||
}
|
||||
12
UnleashedRecomp/ui/achievement_overlay.h
Normal file
12
UnleashedRecomp/ui/achievement_overlay.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
struct AchievementOverlay
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open(int id);
|
||||
static void Close();
|
||||
};
|
||||
95
UnleashedRecomp/ui/imgui_utils.h
Normal file
95
UnleashedRecomp/ui/imgui_utils.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
// 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 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.0f, 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();
|
||||
}
|
||||
|
||||
static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, int32_t outlineSize, ImU32 outlineColor)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// 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.5f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255))
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
drawList->AddText(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text);
|
||||
drawList->AddText(font, fontSize, pos, colour, text);
|
||||
}
|
||||
|
||||
static float Lerp(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
static float CubicEase(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * (t * t * 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);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "options_menu.h"
|
||||
#include "imgui_utils.h"
|
||||
#include "window.h"
|
||||
#include "exports.h"
|
||||
|
||||
|
|
@ -77,73 +78,6 @@ static void SetShaderModifier(uint32_t shaderModifier)
|
|||
callbackData->setShaderModifier.shaderModifier = shaderModifier;
|
||||
}
|
||||
|
||||
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 = g_seuratFont->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, 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();
|
||||
}
|
||||
|
||||
static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, int32_t outlineSize, ImU32 outlineColor)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Not aspect ratio aware. Will stretch.
|
||||
static float ScaleX(float x)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
return x * io.DisplaySize.x / 1280.0f;
|
||||
}
|
||||
|
||||
static float ScaleY(float y)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
return y * io.DisplaySize.y / 720.0f;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
static float Lerp(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * 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 void DrawScanlineBars()
|
||||
{
|
||||
constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0);
|
||||
|
|
@ -854,6 +788,7 @@ static void DrawConfigOptions()
|
|||
DrawConfigOption(rowCount++, yOffset, &Config::Language, !OptionsMenu::s_isPause, cmnReason);
|
||||
DrawConfigOption(rowCount++, yOffset, &Config::Hints, !isStage, cmnReason);
|
||||
DrawConfigOption(rowCount++, yOffset, &Config::ControlTutorial, !isStage, cmnReason);
|
||||
DrawConfigOption(rowCount++, yOffset, &Config::AchievementNotifications, true);
|
||||
DrawConfigOption(rowCount++, yOffset, &Config::SaveScoreAtCheckpoints, true);
|
||||
DrawConfigOption(rowCount++, yOffset, &Config::UnleashGaugeBehaviour, true);
|
||||
DrawConfigOption(rowCount++, yOffset, &Config::TimeOfDayTransition, true);
|
||||
|
|
@ -1100,6 +1035,8 @@ void OptionsMenu::Close()
|
|||
|
||||
*(bool*)g_memory.Translate(0x8328BB26) = true;
|
||||
|
||||
Config::Save();
|
||||
|
||||
// TODO: animate Miles Electric out if we're in a stage.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include "window.h"
|
||||
#include "sdl_listener.h"
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
bool m_isFullscreenKeyReleased = true;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include <res/icon.h>
|
||||
#include <res/icon_night.h>
|
||||
#include <ui/window_events.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
|
||||
#define DEFAULT_WIDTH 1280
|
||||
#define DEFAULT_HEIGHT 720
|
||||
|
|
|
|||
66
UnleashedRecomp/user/achievement_data.cpp
Normal file
66
UnleashedRecomp/user/achievement_data.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "achievement_data.h"
|
||||
#include <fstream>
|
||||
#include <ui/achievement_overlay.h>
|
||||
#include <user/config.h>
|
||||
|
||||
bool AchievementData::IsUnlocked(uint16_t id)
|
||||
{
|
||||
for (int i = 0; i < sizeof(Data.Records); i++)
|
||||
{
|
||||
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 < sizeof(Data.Records); i++)
|
||||
{
|
||||
if (Data.Records[i].ID == 0)
|
||||
{
|
||||
Data.Records[i].ID = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Config::AchievementNotifications)
|
||||
AchievementOverlay::Open(id);
|
||||
}
|
||||
|
||||
void AchievementData::Load()
|
||||
{
|
||||
auto dataPath = GetDataPath();
|
||||
|
||||
if (!std::filesystem::exists(dataPath))
|
||||
return;
|
||||
|
||||
std::ifstream file(dataPath, std::ios::binary);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
printf("[*] Failed to parse achievement data.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file.read((char*)&Data, sizeof(Data));
|
||||
file.close();
|
||||
}
|
||||
|
||||
void AchievementData::Save()
|
||||
{
|
||||
std::ofstream file(GetDataPath(), std::ios::binary);
|
||||
|
||||
if (!file)
|
||||
{
|
||||
printf("[*] Failed to write achievement data.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file.write((const char*)&Data, sizeof(Data));
|
||||
file.close();
|
||||
}
|
||||
40
UnleashedRecomp/user/achievement_data.h
Normal file
40
UnleashedRecomp/user/achievement_data.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <user/paths.h>
|
||||
|
||||
#define ACH_SIGNATURE 'ACH '
|
||||
#define ACH_VERSION 0x01000000
|
||||
|
||||
class AchievementData
|
||||
{
|
||||
public:
|
||||
#pragma pack(push, 1)
|
||||
struct Record
|
||||
{
|
||||
uint16_t ID;
|
||||
uint16_t Reserved[7];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class Data
|
||||
{
|
||||
public:
|
||||
uint32_t Signature{};
|
||||
uint32_t Version{};
|
||||
uint32_t Reserved[2];
|
||||
Record Records[50];
|
||||
};
|
||||
|
||||
inline static Data Data{ ACH_SIGNATURE, ACH_VERSION };
|
||||
|
||||
static std::filesystem::path GetDataPath()
|
||||
{
|
||||
return GetSavePath() / "ACH-DATA";
|
||||
}
|
||||
|
||||
static bool IsUnlocked(uint16_t id);
|
||||
static void Unlock(uint16_t id);
|
||||
static void Load();
|
||||
static void Save();
|
||||
};
|
||||
|
|
@ -24,7 +24,7 @@ void Config::Load()
|
|||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cfg/config_detail.h>
|
||||
#include <user/config_detail.h>
|
||||
#include <locale/config_locale.h>
|
||||
#include <user/paths.h>
|
||||
#include <exports.h>
|
||||
|
||||
class Config
|
||||
|
|
@ -12,6 +13,7 @@ public:
|
|||
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);
|
||||
|
|
@ -65,31 +67,9 @@ public:
|
|||
CONFIG_DEFINE_ENUM_LOCALISED("Video", EMovieScaleMode, MovieScaleMode, EMovieScaleMode::Fit);
|
||||
CONFIG_DEFINE_ENUM_LOCALISED("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";
|
||||
return GetUserPath() / "config.toml";
|
||||
}
|
||||
|
||||
static void Load();
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
#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};
|
||||
|
||||
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";
|
||||
}
|
||||
|
|
@ -506,3 +506,8 @@ return_on_true = true
|
|||
name = "ToggleSubtitlesMidAsmHook"
|
||||
address = 0x82B9BB74
|
||||
registers = ["r27"]
|
||||
|
||||
[[midasm_hook]]
|
||||
name = "AchievementManagerUnlockMidAsmHook"
|
||||
address = 0x82BCFF28
|
||||
registers = ["r31"]
|
||||
|
|
|
|||
2
thirdparty/PowerRecomp
vendored
2
thirdparty/PowerRecomp
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 7dd4f91ac635b001a56cc7a27af48f0436bbad3f
|
||||
Subproject commit 675b482ec4852b873590fb999d24b426bade2b3a
|
||||
Loading…
Add table
Reference in a new issue