Implemented achievement overlay (WIP)

This commit is contained in:
Hyper 2024-11-26 06:47:14 +00:00
parent 91eb12f42c
commit 15e1472684
36 changed files with 587 additions and 125 deletions

View file

@ -28,11 +28,6 @@ set(SWA_PRECOMPILED_HEADERS
"stdafx.h" "stdafx.h"
) )
set(SWA_CFG_CXX_SOURCES
"cfg/config.cpp"
"cfg/config_detail.cpp"
)
set(SWA_KERNEL_CXX_SOURCES set(SWA_KERNEL_CXX_SOURCES
"kernel/imports.cpp" "kernel/imports.cpp"
"kernel/xdm.cpp" "kernel/xdm.cpp"
@ -80,10 +75,17 @@ set(SWA_PATCHES_CXX_SOURCES
) )
set(SWA_UI_CXX_SOURCES set(SWA_UI_CXX_SOURCES
"ui/achievement_overlay.cpp"
"ui/options_menu.cpp" "ui/options_menu.cpp"
"ui/window.cpp" "ui/window.cpp"
) )
set(SWA_USER_CXX_SOURCES
"user/achievement_data.cpp"
"user/config.cpp"
"user/config_detail.cpp"
)
set(SWA_CXX_SOURCES set(SWA_CXX_SOURCES
"app.cpp" "app.cpp"
"exports.cpp" "exports.cpp"
@ -91,7 +93,6 @@ set(SWA_CXX_SOURCES
"misc_impl.cpp" "misc_impl.cpp"
"stdafx.cpp" "stdafx.cpp"
${SWA_CFG_CXX_SOURCES}
${SWA_KERNEL_CXX_SOURCES} ${SWA_KERNEL_CXX_SOURCES}
${SWA_CPU_CXX_SOURCES} ${SWA_CPU_CXX_SOURCES}
${SWA_GPU_CXX_SOURCES} ${SWA_GPU_CXX_SOURCES}
@ -99,6 +100,7 @@ set(SWA_CXX_SOURCES
${SWA_HID_CXX_SOURCES} ${SWA_HID_CXX_SOURCES}
${SWA_PATCHES_CXX_SOURCES} ${SWA_PATCHES_CXX_SOURCES}
${SWA_UI_CXX_SOURCES} ${SWA_UI_CXX_SOURCES}
${SWA_USER_CXX_SOURCES}
) )
if (WIN32) if (WIN32)

View file

@ -32,6 +32,7 @@
#include "CSD/Manager/csdmSubjectBase.h" #include "CSD/Manager/csdmSubjectBase.h"
#include "CSD/Platform/csdTexList.h" #include "CSD/Platform/csdTexList.h"
#include "SWA/Achievement/AchievementManager.h"
#include "SWA/Camera/Camera.h" #include "SWA/Camera/Camera.h"
#include "SWA/CSD/CsdDatabaseWrapper.h" #include "SWA/CSD/CsdDatabaseWrapper.h"
#include "SWA/CSD/CsdProject.h" #include "SWA/CSD/CsdProject.h"
@ -40,6 +41,7 @@
#include "SWA/HUD/GeneralWindow/GeneralWindow.h" #include "SWA/HUD/GeneralWindow/GeneralWindow.h"
#include "SWA/HUD/Loading/Loading.h" #include "SWA/HUD/Loading/Loading.h"
#include "SWA/HUD/Pause/HudPause.h" #include "SWA/HUD/Pause/HudPause.h"
#include "SWA/HUD/SaveIcon/SaveIcon.h"
#include "SWA/HUD/Sonic/HudSonicStage.h" #include "SWA/HUD/Sonic/HudSonicStage.h"
#include "SWA/Movie/MovieDisplayer.h" #include "SWA/Movie/MovieDisplayer.h"
#include "SWA/Movie/MovieManager.h" #include "SWA/Movie/MovieManager.h"

View 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);
};
}

View 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;
};
}

View 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;
};
}

View file

@ -35,7 +35,9 @@ namespace SWA
public: public:
SWA_INSERT_PADDING(0x20); SWA_INSERT_PADDING(0x20);
boost::shared_ptr<CGame> m_pGame; 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; xpointer<void> m_spGameParameter;
}; };

View file

@ -1,3 +1,6 @@
#pragma once #pragma once
#include <xdbf_wrapper.h>
extern double g_deltaTime; extern double g_deltaTime;
extern XDBFWrapper g_xdbf;

View file

@ -7,13 +7,14 @@
#include <kernel/memory.h> #include <kernel/memory.h>
#include <xxHashMap.h> #include <xxHashMap.h>
#include <shader/shader_cache.h> #include <shader/shader_cache.h>
#include <ui/achievement_overlay.h>
#include <ui/options_menu.h> #include <ui/options_menu.h>
#include "imgui_snapshot.h" #include "imgui_snapshot.h"
#include "imgui_common.h" #include "imgui_common.h"
#include "video.h" #include "video.h"
#include <ui/window.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.dxil.h"
#include "shader/copy_vs.hlsl.spirv.h" #include "shader/copy_vs.hlsl.spirv.h"
@ -1011,6 +1012,7 @@ static void CreateImGuiBackend()
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
OptionsMenu::Init(); OptionsMenu::Init();
AchievementOverlay::Init();
ImGui_ImplSDL2_InitForOther(Window::s_pWindow); ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
g_imFontTexture = std::make_unique<GuestTexture>(ResourceType::Texture); g_imFontTexture = std::make_unique<GuestTexture>(ResourceType::Texture);
@ -1649,6 +1651,7 @@ static void DrawImGui()
{ {
ImGui_ImplSDL2_NewFrame(); ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
AchievementOverlay::Draw();
OptionsMenu::Draw(); OptionsMenu::Draw();
ImGui::Render(); ImGui::Render();

View file

@ -1,6 +1,6 @@
#include <stdafx.h> #include <stdafx.h>
#include <SDL.h> #include <SDL.h>
#include <cfg/config.h> #include <user/config.h>
#include <hid/hid_detail.h> #include <hid/hid_detail.h>
#include <ui/window.h> #include <ui/window.h>

View file

@ -11,7 +11,7 @@
#include "xam.h" #include "xam.h"
#include "xdm.h" #include "xdm.h"
#include <timeapi.h> #include <timeapi.h>
#include <cfg/config.h> #include <user/config.h>
#include <ntstatus.h> #include <ntstatus.h>

View file

@ -8,6 +8,7 @@
#include <unordered_set> #include <unordered_set>
#include <CommCtrl.h> #include <CommCtrl.h>
#include "xxHashMap.h" #include "xxHashMap.h"
#include <user/paths.h>
// Needed for commctrl // Needed for commctrl
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") #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) if (pContentData->dwContentType == XCONTENTTYPE_SAVEDATA)
{ {
root = Config::GetSavePath().string(); root = GetSavePath().string();
} }
else if (pContentData->dwContentType == XCONTENTTYPE_DLC) else if (pContentData->dwContentType == XCONTENTTYPE_DLC)
{ {

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <cfg/config_detail.h> #include <user/config_detail.h>
#define CONFIG_DEFINE_LOCALE(name) \ #define CONFIG_DEFINE_LOCALE(name) \
inline static std::unordered_map<ELanguage, std::tuple<std::string, std::string>> g_##name##_locale = 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." } } { 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) CONFIG_DEFINE_LOCALE(SaveScoreAtCheckpoints)
{ {
{ ELanguage::English, { "Save Score at Checkpoints", "Keep your score from the last checkpoint upon respawning.\n\n[TO BE REMOVED]" } } { ELanguage::English, { "Save Score at Checkpoints", "Keep your score from the last checkpoint upon respawning.\n\n[TO BE REMOVED]" } }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include <cfg/config.h> #include <user/config.h>
inline static std::string g_localeMissing = "<missing string>"; 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." } { ELanguage::English, "This option is not supported by your operating system." }
} }
},
{
"Achievements_Name",
{
{ ELanguage::English, "Achievements" }
}
},
{
"Achievements_Unlock",
{
{ ELanguage::English, "Achievement Unlocked!" }
}
} }
}; };

View file

@ -11,7 +11,10 @@
#include <xex.h> #include <xex.h>
#include <apu/audio.h> #include <apu/audio.h>
#include <hid/hid.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" #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 }; Memory g_memory{ reinterpret_cast<void*>(0x100000000), 0x100000000 };
Heap g_userHeap; Heap g_userHeap;
CodeCache g_codeCache; CodeCache g_codeCache;
XDBFWrapper g_xdbf;
// Name inspired from nt's entry point // Name inspired from nt's entry point
void KiSystemStartup() void KiSystemStartup()
@ -40,7 +44,7 @@ void KiSystemStartup()
XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : "."); XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : ".");
XamRegisterContent(updateContent, ".\\update"); XamRegisterContent(updateContent, ".\\update");
const auto savePath = Config::GetSavePath(); const auto savePath = GetSavePath();
const auto saveName = "SYS-DATA"; const auto saveName = "SYS-DATA";
// TODO: implement save slots? // 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; return entry;
} }
int main() int main()
{ {
Config::Load(); Config::Load();
AchievementData::Load();
KiSystemStartup(); KiSystemStartup();

View file

@ -1,5 +1,5 @@
#include <cpu/guest_code.h> #include <cpu/guest_code.h>
#include <cfg/config.h> #include <user/config.h>
#include <kernel/function.h> #include <kernel/function.h>
#include <kernel/platform.h> #include <kernel/platform.h>
#include <patches/audio_patches.h> #include <patches/audio_patches.h>

View file

@ -1,7 +1,7 @@
#include <cpu/guest_code.h> #include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/window.h> #include <ui/window.h>
#include <cfg/config.h> #include <user/config.h>
constexpr float m_baseAspectRatio = 16.0f / 9.0f; constexpr float m_baseAspectRatio = 16.0f / 9.0f;

View file

@ -1,7 +1,7 @@
#include <cpu/guest_code.h> #include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/window.h> #include <ui/window.h>
#include <cfg/config.h> #include <user/config.h>
#include <app.h> #include <app.h>
float m_lastLoadingFrameDelta = 0.0f; float m_lastLoadingFrameDelta = 0.0f;

View file

@ -1,7 +1,13 @@
#include <cpu/guest_code.h> #include <cpu/guest_code.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/window.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() bool DisableHintsMidAsmHook()
{ {

View file

@ -2,7 +2,7 @@
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/window.h> #include <ui/window.h>
#include <ui/window_events.h> #include <ui/window_events.h>
#include <cfg/config.h> #include <user/config.h>
uint32_t m_lastCheckpointScore = 0; uint32_t m_lastCheckpointScore = 0;
float m_lastDarkGaiaEnergy = 0.0f; float m_lastDarkGaiaEnergy = 0.0f;

View file

@ -1,9 +1,12 @@
#include <cpu/guest_code.h> #include <cpu/guest_code.h>
#include <cfg/config.h> #include <user/achievement_data.h>
#include <user/config.h>
#include <api/SWA.h> #include <api/SWA.h>
const char* m_pStageID; const char* m_pStageID;
bool m_isSavedAchievementData = false;
void GetStageIDMidAsmHook(PPCRegister& r5) void GetStageIDMidAsmHook(PPCRegister& r5)
{ {
m_pStageID = *(xpointer<const char>*)g_memory.Translate(r5.u32); m_pStageID = *(xpointer<const char>*)g_memory.Translate(r5.u32);
@ -42,13 +45,38 @@ PPC_FUNC(sub_824EB9B0)
__imp__sub_824EB9B0(ctx, base); __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_IMPL(__imp__sub_824EFD28);
PPC_FUNC(sub_824EFD28) PPC_FUNC(sub_824EFD28)
{ {
auto r3 = ctx.r3; auto r3 = ctx.r3;
// CSigninXenon::InitializeDLC // SWA::CSigninXenon::InitializeDLC
ctx.r3.u64 = PPC_LOAD_U32(r3.u32 + 4) + 200; ctx.r3.u64 = PPC_LOAD_U32(r3.u32 + 4) + 200;
ctx.r4.u64 = 0; ctx.r4.u64 = 0;
sub_822C57D8(ctx, base); sub_822C57D8(ctx, base);
@ -57,7 +85,7 @@ PPC_FUNC(sub_824EFD28)
__imp__sub_824EFD28(ctx, base); __imp__sub_824EFD28(ctx, base);
} }
// CFileReaderXenon_DLC::InitializeParallel // SWA::CFileReaderXenon_DLC::InitializeParallel
PPC_FUNC(sub_822C3778) PPC_FUNC(sub_822C3778)
{ {
if (!PPC_LOAD_U8(0x83361F10)) // ms_DLCInitialized if (!PPC_LOAD_U8(0x83361F10)) // ms_DLCInitialized

View file

@ -1,7 +1,7 @@
#include <cpu/guest_code.h> #include <cpu/guest_code.h>
#include <user/config.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/window.h> #include <ui/window.h>
#include <cfg/config.h>
// TODO: to be removed. // TODO: to be removed.
constexpr float m_baseAspectRatio = 16.0f / 9.0f; constexpr float m_baseAspectRatio = 16.0f / 9.0f;

View 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;
}

View 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();
};

View 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);
}

View file

@ -1,4 +1,5 @@
#include "options_menu.h" #include "options_menu.h"
#include "imgui_utils.h"
#include "window.h" #include "window.h"
#include "exports.h" #include "exports.h"
@ -77,73 +78,6 @@ static void SetShaderModifier(uint32_t shaderModifier)
callbackData->setShaderModifier.shaderModifier = 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() static void DrawScanlineBars()
{ {
constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0); 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::Language, !OptionsMenu::s_isPause, cmnReason);
DrawConfigOption(rowCount++, yOffset, &Config::Hints, !isStage, cmnReason); DrawConfigOption(rowCount++, yOffset, &Config::Hints, !isStage, cmnReason);
DrawConfigOption(rowCount++, yOffset, &Config::ControlTutorial, !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::SaveScoreAtCheckpoints, true);
DrawConfigOption(rowCount++, yOffset, &Config::UnleashGaugeBehaviour, true); DrawConfigOption(rowCount++, yOffset, &Config::UnleashGaugeBehaviour, true);
DrawConfigOption(rowCount++, yOffset, &Config::TimeOfDayTransition, true); DrawConfigOption(rowCount++, yOffset, &Config::TimeOfDayTransition, true);
@ -1100,6 +1035,8 @@ void OptionsMenu::Close()
*(bool*)g_memory.Translate(0x8328BB26) = true; *(bool*)g_memory.Translate(0x8328BB26) = true;
Config::Save();
// TODO: animate Miles Electric out if we're in a stage. // TODO: animate Miles Electric out if we're in a stage.
} }

View file

@ -1,6 +1,6 @@
#include "window.h" #include "window.h"
#include "sdl_listener.h" #include "sdl_listener.h"
#include <cfg/config.h> #include <user/config.h>
#include <SDL_syswm.h> #include <SDL_syswm.h>
bool m_isFullscreenKeyReleased = true; bool m_isFullscreenKeyReleased = true;

View file

@ -3,7 +3,7 @@
#include <res/icon.h> #include <res/icon.h>
#include <res/icon_night.h> #include <res/icon_night.h>
#include <ui/window_events.h> #include <ui/window_events.h>
#include <cfg/config.h> #include <user/config.h>
#define DEFAULT_WIDTH 1280 #define DEFAULT_WIDTH 1280
#define DEFAULT_HEIGHT 720 #define DEFAULT_HEIGHT 720

View 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();
}

View 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();
};

View file

@ -24,7 +24,7 @@ void Config::Load()
} }
catch (toml::parse_error& err) 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 else
{ {
printf("Failed to write configuration.\n"); printf("[*] Failed to write configuration.\n");
} }
} }

View file

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <cfg/config_detail.h> #include <user/config_detail.h>
#include <locale/config_locale.h> #include <locale/config_locale.h>
#include <user/paths.h>
#include <exports.h> #include <exports.h>
class Config class Config
@ -12,6 +13,7 @@ public:
CONFIG_DEFINE_ENUM_LOCALISED("System", ELanguage, Language, ELanguage::English); CONFIG_DEFINE_ENUM_LOCALISED("System", ELanguage, Language, ELanguage::English);
CONFIG_DEFINE_LOCALISED("System", bool, Hints, true); CONFIG_DEFINE_LOCALISED("System", bool, Hints, true);
CONFIG_DEFINE_LOCALISED("System", bool, ControlTutorial, true); CONFIG_DEFINE_LOCALISED("System", bool, ControlTutorial, true);
CONFIG_DEFINE_LOCALISED("System", bool, AchievementNotifications, true);
CONFIG_DEFINE_LOCALISED("System", bool, SaveScoreAtCheckpoints, false); CONFIG_DEFINE_LOCALISED("System", bool, SaveScoreAtCheckpoints, false);
CONFIG_DEFINE_ENUM_LOCALISED("System", EUnleashGaugeBehaviour, UnleashGaugeBehaviour, EUnleashGaugeBehaviour::Original); CONFIG_DEFINE_ENUM_LOCALISED("System", EUnleashGaugeBehaviour, UnleashGaugeBehaviour, EUnleashGaugeBehaviour::Original);
CONFIG_DEFINE_ENUM_LOCALISED("System", ETimeOfDayTransition, TimeOfDayTransition, ETimeOfDayTransition::Xbox); 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", EMovieScaleMode, MovieScaleMode, EMovieScaleMode::Fit);
CONFIG_DEFINE_ENUM_LOCALISED("Video", EUIScaleMode, UIScaleMode, EUIScaleMode::Centre); 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() static std::filesystem::path GetConfigPath()
{ {
return GetUserPath() / TOML_FILE; return GetUserPath() / "config.toml";
}
static std::filesystem::path GetSavePath()
{
return GetUserPath() / "save";
} }
static void Load(); static void Load();

View file

@ -1,9 +1,5 @@
#pragma once #pragma once
#define USER_DIRECTORY "SWA"
#define TOML_FILE "config.toml"
#define CONFIG_DEFINE(section, type, name, defaultValue) \ #define CONFIG_DEFINE(section, type, name, defaultValue) \
inline static ConfigDef<type> name{section, #name, defaultValue}; inline static ConfigDef<type> name{section, #name, defaultValue};

View 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";
}

View file

@ -506,3 +506,8 @@ return_on_true = true
name = "ToggleSubtitlesMidAsmHook" name = "ToggleSubtitlesMidAsmHook"
address = 0x82B9BB74 address = 0x82B9BB74
registers = ["r27"] registers = ["r27"]
[[midasm_hook]]
name = "AchievementManagerUnlockMidAsmHook"
address = 0x82BCFF28
registers = ["r31"]

@ -1 +1 @@
Subproject commit 7dd4f91ac635b001a56cc7a27af48f0436bbad3f Subproject commit 675b482ec4852b873590fb999d24b426bade2b3a