Fix achievement sound not playing in certain conditions. (#373)

* Fix achievement sound not playing in cutscenes.

* Prevent achievement overlay from showing if sounds cannot be played.
This commit is contained in:
Skyth (Asilkan) 2025-02-11 23:50:24 +03:00 committed by GitHub
parent 99d6cebf92
commit dd294a30d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 73 additions and 30 deletions

View file

@ -5,6 +5,7 @@
#include <kernel/heap.h> #include <kernel/heap.h>
#include <kernel/memory.h> #include <kernel/memory.h>
#include <ui/game_window.h> #include <ui/game_window.h>
#include <patches/inspire_patches.h>
void Game_PlaySound(const char* pName) void Game_PlaySound(const char* pName)
{ {
@ -14,8 +15,11 @@ void Game_PlaySound(const char* pName)
} }
else else
{ {
// Use EVENT category in cutscenes since SYSTEM gets muted by the game.
uint32_t category = !InspirePatches::s_sceneName.empty() ? 10 : 7;
guest_stack_var<boost::anonymous_shared_ptr> soundPlayer; 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); GuestToHostFunction<void>(sub_82B4DF50, soundPlayer.get(), ((be<uint32_t>*)g_memory.Translate(0x83367900))->get(), category, 0, 0);
auto soundPlayerVtable = (be<uint32_t>*)g_memory.Translate(*(be<uint32_t>*)soundPlayer->get()); auto soundPlayerVtable = (be<uint32_t>*)g_memory.Translate(*(be<uint32_t>*)soundPlayer->get());
uint32_t virtualFunction = *(soundPlayerVtable + 1); uint32_t virtualFunction = *(soundPlayerVtable + 1);

View file

@ -10,12 +10,12 @@ namespace xdbf
{ {
inline std::string& FixInvalidSequences(std::string& str) inline std::string& FixInvalidSequences(std::string& str)
{ {
static std::vector<std::string> invalidSequences = static std::array<std::string_view, 1> invalidSequences =
{ {
"\xE2\x80\x99" "\xE2\x80\x99"
}; };
static std::vector<std::string> replaceSequences = static std::array<std::string_view, 1> replaceSequences =
{ {
"'" "'"
}; };

View file

@ -5,8 +5,9 @@
#include <app.h> #include <app.h>
#include <sdl_events.h> #include <sdl_events.h>
std::string InspirePatches::s_sceneName;
static SWA::Inspire::CScene* g_pScene; static SWA::Inspire::CScene* g_pScene;
static std::string g_sceneName;
static bool g_isFirstFrameChecked; static bool g_isFirstFrameChecked;
static uint32_t g_eventDispatchCount; static uint32_t g_eventDispatchCount;
@ -49,7 +50,7 @@ PPC_FUNC(sub_82B98D30)
__imp__sub_82B98D30(ctx, base); __imp__sub_82B98D30(ctx, base);
g_pScene = nullptr; g_pScene = nullptr;
g_sceneName.clear(); InspirePatches::s_sceneName.clear();
SDL_User_EvilSonic(App::s_isWerehog); SDL_User_EvilSonic(App::s_isWerehog);
} }
@ -59,7 +60,7 @@ PPC_FUNC(sub_82B9BA98)
{ {
auto sceneName = (Hedgehog::Base::CSharedString*)g_memory.Translate(ctx.r5.u32); auto sceneName = (Hedgehog::Base::CSharedString*)g_memory.Translate(ctx.r5.u32);
g_sceneName = sceneName->c_str(); InspirePatches::s_sceneName = sceneName->c_str();
__imp__sub_82B9BA98(ctx, base); __imp__sub_82B9BA98(ctx, base);
} }
@ -72,7 +73,7 @@ void InspirePatches::DrawDebug()
return; return;
} }
ImGui::Text("Name: %s", g_sceneName.c_str()); ImGui::Text("Name: %s", InspirePatches::s_sceneName.c_str());
ImGui::Text("Frame: %f", g_pScene->m_pData->Frame.get()); ImGui::Text("Frame: %f", g_pScene->m_pData->Frame.get());
ImGui::Text("Cut: %d", g_pScene->m_pData->Cut.get()); ImGui::Text("Cut: %d", g_pScene->m_pData->Cut.get());
@ -97,17 +98,17 @@ void InspirePatches::DrawDebug()
void InspirePatches::Update() void InspirePatches::Update()
{ {
if (!g_pScene || !g_sceneName.size()) if (!g_pScene || !InspirePatches::s_sceneName.size())
return; return;
if (!g_isFirstFrameChecked && std::find(g_alwaysEvilSonic.begin(), g_alwaysEvilSonic.end(), g_sceneName) != g_alwaysEvilSonic.end()) if (!g_isFirstFrameChecked && std::find(g_alwaysEvilSonic.begin(), g_alwaysEvilSonic.end(), InspirePatches::s_sceneName) != g_alwaysEvilSonic.end())
{ {
SDL_User_EvilSonic(true); SDL_User_EvilSonic(true);
g_isFirstFrameChecked = true; g_isFirstFrameChecked = true;
return; return;
} }
auto findResult = g_evilSonicTimings.find(g_sceneName); auto findResult = g_evilSonicTimings.find(InspirePatches::s_sceneName);
if (findResult != g_evilSonicTimings.end()) if (findResult != g_evilSonicTimings.end())
{ {

View file

@ -3,6 +3,8 @@
class InspirePatches class InspirePatches
{ {
public: public:
static std::string s_sceneName;
static void DrawDebug(); static void DrawDebug();
static void Update(); static void Update();
}; };

View file

@ -10,6 +10,7 @@
#include <app.h> #include <app.h>
#include <exports.h> #include <exports.h>
#include <decompressor.h> #include <decompressor.h>
#include <patches/inspire_patches.h>
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0; constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0;
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11; constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11;
@ -80,14 +81,67 @@ void AchievementOverlay::Init()
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf"); g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf");
} }
// Dequeue achievements only when we can actually play sounds.
// Loading thread does not update this object.
static bool g_soundAdministratorUpdated;
PPC_FUNC_IMPL(__imp__sub_82B43480);
PPC_FUNC(sub_82B43480)
{
g_soundAdministratorUpdated = true;
__imp__sub_82B43480(ctx, base);
}
// Dequeue achievements only in the main thread. This is also extra thread safety.
static std::thread::id g_mainThreadId = std::this_thread::get_id();
static bool CanDequeueAchievement()
{
if (g_soundAdministratorUpdated && std::this_thread::get_id() == g_mainThreadId && !AchievementOverlay::s_queue.empty())
{
// Check if we can actually play any audio right now. If not, we'll wait until we can.
uint32_t audioCenter = *reinterpret_cast<be<uint32_t>*>(g_memory.Translate(0x83362FFC));
if (audioCenter != NULL)
{
uint32_t member = *reinterpret_cast<be<uint32_t>*>(g_memory.Translate(audioCenter + 0x4));
uint32_t category = !InspirePatches::s_sceneName.empty() ? 10 : 7; // EVENT category is used during Inspire cutscenes.
// Check if the volume is non zero.
return *reinterpret_cast<uint32_t*>(g_memory.Translate(member + 0x7C + category * 0x10 + 0x8)) != 0;
}
}
return false;
}
void AchievementOverlay::Draw() void AchievementOverlay::Draw()
{ {
if (!AchievementOverlay::s_isVisible && CanDequeueAchievement())
{
s_isVisible = true;
g_isClosing = false;
g_appearTime = ImGui::GetTime();
g_achievement = g_xdbfWrapper.GetAchievement((EXDBFLanguage)Config::Language.Value, s_queue.front());
s_queue.pop();
if (Config::Language == ELanguage::English)
g_achievement.Name = xdbf::FixInvalidSequences(g_achievement.Name);
Game_PlaySound("obj_navi_appear");
}
if (!s_isVisible) if (!s_isVisible)
{
g_soundAdministratorUpdated = false;
return; return;
}
if (ImGui::GetTime() - g_appearTime >= OVERLAY_DURATION) if (ImGui::GetTime() - g_appearTime >= OVERLAY_DURATION)
AchievementOverlay::Close(); AchievementOverlay::Close();
// Close function can use this bool so reset it after.
g_soundAdministratorUpdated = false;
auto drawList = ImGui::GetBackgroundDrawList(); auto drawList = ImGui::GetBackgroundDrawList();
auto& res = ImGui::GetIO().DisplaySize; auto& res = ImGui::GetIO().DisplaySize;
@ -167,21 +221,7 @@ void AchievementOverlay::Draw()
void AchievementOverlay::Open(int id) void AchievementOverlay::Open(int id)
{ {
if (s_isVisible && !g_isClosing) s_queue.push(id);
{
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);
if (Config::Language == ELanguage::English)
g_achievement.Name = xdbf::FixInvalidSequences(g_achievement.Name);
Game_PlaySound("obj_navi_appear");
} }
void AchievementOverlay::Close() void AchievementOverlay::Close()
@ -192,10 +232,6 @@ void AchievementOverlay::Close()
g_isClosing = true; g_isClosing = true;
} }
if (s_queue.size()) if (CanDequeueAchievement())
{
s_isVisible = false; s_isVisible = false;
AchievementOverlay::Open(s_queue.front());
s_queue.pop();
}
} }