Fix DLC info message always being displayed (#1393)

* Fix DLC info message always being displayed

* Use uint32_t for version numbers

* Make AchievementManager follow the same naming convention as PersistentStorageManager

* persistent_data: remove header size field

* Make status success by default or on file not existing
This commit is contained in:
Hyper 2025-03-28 16:08:13 +00:00 committed by GitHub
parent 4b4c439709
commit 1dd5ba7fcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 281 additions and 60 deletions

View file

@ -183,6 +183,8 @@ set(UNLEASHED_RECOMP_USER_CXX_SOURCES
"user/config.cpp" "user/config.cpp"
"user/registry.cpp" "user/registry.cpp"
"user/paths.cpp" "user/paths.cpp"
"user/persistent_data.cpp"
"user/persistent_storage_manager.cpp"
) )
set(UNLEASHED_RECOMP_MOD_CXX_SOURCES set(UNLEASHED_RECOMP_MOD_CXX_SOURCES

View file

@ -80,9 +80,13 @@ namespace SWA
boost::shared_ptr<Hedgehog::Mirage::CRenderScene> m_spRenderScene; boost::shared_ptr<Hedgehog::Mirage::CRenderScene> m_spRenderScene;
SWA_INSERT_PADDING(0x04); SWA_INSERT_PADDING(0x04);
boost::shared_ptr<CGameParameter> m_spGameParameter; boost::shared_ptr<CGameParameter> m_spGameParameter;
SWA_INSERT_PADDING(0x78); SWA_INSERT_PADDING(0x0C);
boost::anonymous_shared_ptr m_spItemParamManager;
SWA_INSERT_PADDING(0x64);
boost::shared_ptr<Hedgehog::Base::CCriticalSection> m_spCriticalSection; boost::shared_ptr<Hedgehog::Base::CCriticalSection> m_spCriticalSection;
SWA_INSERT_PADDING(0x20); SWA_INSERT_PADDING(0x14);
bool m_ShowDLCInfo;
SWA_INSERT_PADDING(0x08);
}; };
// TODO: Hedgehog::Base::TSynchronizedPtr<CApplicationDocument> // TODO: Hedgehog::Base::TSynchronizedPtr<CApplicationDocument>
@ -111,7 +115,9 @@ namespace SWA
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_Field10C, 0x10C); SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_Field10C, 0x10C);
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spRenderScene, 0x12C); SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spRenderScene, 0x12C);
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spGameParameter, 0x138); SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spGameParameter, 0x138);
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spItemParamManager, 0x14C);
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spCriticalSection, 0x1B8); SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spCriticalSection, 0x1B8);
SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_ShowDLCInfo, 0x1D4);
SWA_ASSERT_SIZEOF(CApplicationDocument::CMember, 0x1E0); SWA_ASSERT_SIZEOF(CApplicationDocument::CMember, 0x1E0);
SWA_ASSERT_OFFSETOF(CApplicationDocument, m_pMember, 0x04); SWA_ASSERT_OFFSETOF(CApplicationDocument, m_pMember, 0x04);

View file

@ -7,11 +7,19 @@ namespace SWA
class CGameParameter // : public Hedgehog::Universe::CMessageActor class CGameParameter // : public Hedgehog::Universe::CMessageActor
{ {
public: public:
struct SSaveData; struct SSaveData
{
SWA_INSERT_PADDING(0x8600);
be<uint32_t> DLCFlags[8];
SWA_INSERT_PADDING(0x15C);
};
struct SStageParameter; struct SStageParameter;
SWA_INSERT_PADDING(0x94); SWA_INSERT_PADDING(0x94);
xpointer<SSaveData> m_pSaveData; xpointer<SSaveData> m_pSaveData;
xpointer<SStageParameter> m_pStageParameter; xpointer<SStageParameter> m_pStageParameter;
}; };
SWA_ASSERT_OFFSETOF(CGameParameter::SSaveData, DLCFlags, 0x8600);
} }

View file

@ -13,6 +13,7 @@
#include <hid/hid.h> #include <hid/hid.h>
#include <user/config.h> #include <user/config.h>
#include <user/paths.h> #include <user/paths.h>
#include <user/persistent_storage_manager.h>
#include <user/registry.h> #include <user/registry.h>
#include <kernel/xdbf.h> #include <kernel/xdbf.h>
#include <install/installer.h> #include <install/installer.h>
@ -225,6 +226,9 @@ int main(int argc, char *argv[])
} }
Config::Load(); Config::Load();
if (!PersistentStorageManager::LoadBinary())
LOGFN_ERROR("Failed to load persistent storage binary... (status code {})", (int)PersistentStorageManager::BinStatus);
#if defined(_WIN32) && defined(UNLEASHED_RECOMP_D3D12) #if defined(_WIN32) && defined(UNLEASHED_RECOMP_D3D12)
for (auto& dll : g_D3D12RequiredModules) for (auto& dll : g_D3D12RequiredModules)

View file

@ -2,6 +2,7 @@
#include <api/SWA.h> #include <api/SWA.h>
#include <install/update_checker.h> #include <install/update_checker.h>
#include <locale/locale.h> #include <locale/locale.h>
#include <os/logger.h>
#include <ui/fader.h> #include <ui/fader.h>
#include <ui/message_window.h> #include <ui/message_window.h>
#include <user/achievement_manager.h> #include <user/achievement_manager.h>
@ -64,16 +65,16 @@ static bool ProcessCorruptAchievementsMessage()
if (!g_corruptAchievementsMessageOpen) if (!g_corruptAchievementsMessageOpen)
return false; return false;
auto message = AchievementManager::Status == EAchStatus::IOError auto message = AchievementManager::BinStatus == EAchBinStatus::IOError
? Localise("Title_Message_AchievementDataIOError") ? Localise("Title_Message_AchievementDataIOError")
: Localise("Title_Message_AchievementDataCorrupt"); : Localise("Title_Message_AchievementDataCorrupt");
if (MessageWindow::Open(message, &g_corruptAchievementsMessageResult) == MSG_CLOSED) if (MessageWindow::Open(message, &g_corruptAchievementsMessageResult) == MSG_CLOSED)
{ {
// Allow user to proceed if the achievement data couldn't be loaded. // Create a new save file if the file was successfully loaded and failed validation.
// Restarting may fix this error, so it isn't worth clearing the data for. // If the file couldn't be opened, restarting may fix this error, so it isn't worth clearing the data for.
if (AchievementManager::Status != EAchStatus::IOError) if (AchievementManager::BinStatus != EAchBinStatus::IOError)
AchievementManager::Save(true); AchievementManager::SaveBinary(true);
g_corruptAchievementsMessageOpen = false; g_corruptAchievementsMessageOpen = false;
g_corruptAchievementsMessageOpen.notify_one(); g_corruptAchievementsMessageOpen.notify_one();
@ -135,9 +136,10 @@ void PressStartSaveLoadThreadMidAsmHook()
g_faderBegun.wait(true); g_faderBegun.wait(true);
} }
AchievementManager::Load(); if (!AchievementManager::LoadBinary())
LOGFN_ERROR("Failed to load achievement data... (status code {})", (int)AchievementManager::BinStatus);
if (AchievementManager::Status != EAchStatus::Success) if (AchievementManager::BinStatus != EAchBinStatus::Success)
{ {
g_corruptAchievementsMessageOpen = true; g_corruptAchievementsMessageOpen = true;
g_corruptAchievementsMessageOpen.wait(true); g_corruptAchievementsMessageOpen.wait(true);

View file

@ -1,6 +1,7 @@
#include <api/SWA.h> #include <api/SWA.h>
#include <ui/game_window.h> #include <ui/game_window.h>
#include <user/achievement_manager.h> #include <user/achievement_manager.h>
#include <user/persistent_storage_manager.h>
#include <user/config.h> #include <user/config.h>
void AchievementManagerUnlockMidAsmHook(PPCRegister& id) void AchievementManagerUnlockMidAsmHook(PPCRegister& id)
@ -172,3 +173,23 @@ PPC_FUNC(sub_82B4DB48)
__imp__sub_82B4DB48(ctx, base); __imp__sub_82B4DB48(ctx, base);
} }
// DLC save data flag check.
//
// The DLC checks are fundamentally broken in this game, resulting in this method always
// returning true and displaying the DLC info message when it shouldn't be.
//
// The original intent here seems to have been to display the message every time new DLC
// content is installed, but the flags in the save data never get written to properly,
// causing this function to always pass in some way.
//
// We bypass the save data completely and write to external persistent storage to store
// whether we've seen the DLC info message instead. This way we can retain the original
// broken game behaviour, whilst also providing a fix for this issue that is safe.
PPC_FUNC_IMPL(__imp__sub_824EE620);
PPC_FUNC(sub_824EE620)
{
__imp__sub_824EE620(ctx, base);
ctx.r3.u32 = PersistentStorageManager::ShouldDisplayDLCMessage(true);
}

View file

@ -2,11 +2,10 @@
#include <hid/hid.h> #include <hid/hid.h>
#include <os/logger.h> #include <os/logger.h>
#include <user/achievement_manager.h> #include <user/achievement_manager.h>
#include <user/persistent_storage_manager.h>
#include <user/config.h> #include <user/config.h>
#include <app.h> #include <app.h>
bool m_isSavedAchievementData = false;
// SWA::Message::MsgRequestStartLoading::Impl // SWA::Message::MsgRequestStartLoading::Impl
PPC_FUNC_IMPL(__imp__sub_824DCF38); PPC_FUNC_IMPL(__imp__sub_824DCF38);
PPC_FUNC(sub_824DCF38) PPC_FUNC(sub_824DCF38)
@ -99,20 +98,23 @@ PPC_FUNC(sub_824E5170)
App::s_isSaving = pSaveIcon->m_IsVisible; App::s_isSaving = pSaveIcon->m_IsVisible;
static bool isSavedExtraData = false;
if (pSaveIcon->m_IsVisible) if (pSaveIcon->m_IsVisible)
{ {
App::s_isSaveDataCorrupt = false; App::s_isSaveDataCorrupt = false;
if (!m_isSavedAchievementData) if (!isSavedExtraData)
{ {
AchievementManager::Save(); AchievementManager::SaveBinary();
PersistentStorageManager::SaveBinary();
m_isSavedAchievementData = true; isSavedExtraData = true;
} }
} }
else else
{ {
m_isSavedAchievementData = false; isSavedExtraData = false;
} }
} }

View file

@ -11,7 +11,7 @@ bool AchievementData::VerifySignature() const
bool AchievementData::VerifyVersion() const bool AchievementData::VerifyVersion() const
{ {
return Version == AchVersion ACH_VERSION; return Version <= ACH_VERSION;
} }
bool AchievementData::VerifyChecksum() bool AchievementData::VerifyChecksum()

View file

@ -4,27 +4,12 @@
#define ACH_FILENAME "ACH-DATA" #define ACH_FILENAME "ACH-DATA"
#define ACH_SIGNATURE { 'A', 'C', 'H', ' ' } #define ACH_SIGNATURE { 'A', 'C', 'H', ' ' }
#define ACH_VERSION { 1, 0, 0 } #define ACH_VERSION 1
#define ACH_RECORDS 50 #define ACH_RECORDS 50
class AchievementData class AchievementData
{ {
public: public:
struct AchVersion
{
uint8_t Major;
uint8_t Minor;
uint8_t Revision;
uint8_t Reserved;
bool operator==(const AchVersion& other) const
{
return Major == other.Major &&
Minor == other.Minor &&
Revision == other.Revision;
}
};
#pragma pack(push, 1) #pragma pack(push, 1)
struct AchRecord struct AchRecord
{ {
@ -35,10 +20,10 @@ public:
#pragma pack(pop) #pragma pack(pop)
char Signature[4] ACH_SIGNATURE; char Signature[4] ACH_SIGNATURE;
AchVersion Version ACH_VERSION; uint32_t Version{ ACH_VERSION };
uint32_t Checksum; uint32_t Checksum{};
uint32_t Reserved; uint32_t Reserved{};
AchRecord Records[ACH_RECORDS]; AchRecord Records[ACH_RECORDS]{};
bool VerifySignature() const; bool VerifySignature() const;
bool VerifyVersion() const; bool VerifyVersion() const;

View file

@ -105,11 +105,11 @@ void AchievementManager::Reset()
*(bool*)g_memory.Translate(0x833647C4) = false; *(bool*)g_memory.Translate(0x833647C4) = false;
} }
void AchievementManager::Load() bool AchievementManager::LoadBinary()
{ {
AchievementManager::Reset(); AchievementManager::Reset();
Status = EAchStatus::Success; BinStatus = EAchBinStatus::Success;
auto dataPath = GetDataPath(true); auto dataPath = GetDataPath(true);
@ -119,7 +119,7 @@ void AchievementManager::Load()
dataPath = GetDataPath(false); dataPath = GetDataPath(false);
if (!std::filesystem::exists(dataPath)) if (!std::filesystem::exists(dataPath))
return; return true;
} }
std::error_code ec; std::error_code ec;
@ -128,16 +128,16 @@ void AchievementManager::Load()
if (fileSize != dataSize) if (fileSize != dataSize)
{ {
Status = EAchStatus::BadFileSize; BinStatus = EAchBinStatus::BadFileSize;
return; return false;
} }
std::ifstream file(dataPath, std::ios::binary); std::ifstream file(dataPath, std::ios::binary);
if (!file) if (!file)
{ {
Status = EAchStatus::IOError; BinStatus = EAchBinStatus::IOError;
return; return false;
} }
AchievementData data{}; AchievementData data{};
@ -146,19 +146,18 @@ void AchievementManager::Load()
if (!data.VerifySignature()) if (!data.VerifySignature())
{ {
Status = EAchStatus::BadSignature; BinStatus = EAchBinStatus::BadSignature;
file.close(); file.close();
return; return false;
} }
file.read((char*)&data.Version, sizeof(data.Version)); file.read((char*)&data.Version, sizeof(data.Version));
// TODO: upgrade in future if the version changes.
if (!data.VerifyVersion()) if (!data.VerifyVersion())
{ {
Status = EAchStatus::BadVersion; BinStatus = EAchBinStatus::BadVersion;
file.close(); file.close();
return; return false;
} }
file.seekg(0); file.seekg(0);
@ -166,22 +165,24 @@ void AchievementManager::Load()
if (!data.VerifyChecksum()) if (!data.VerifyChecksum())
{ {
Status = EAchStatus::BadChecksum; BinStatus = EAchBinStatus::BadChecksum;
file.close(); file.close();
return; return false;
} }
file.close(); file.close();
memcpy(&Data, &data, dataSize); memcpy(&Data, &data, dataSize);
return true;
} }
void AchievementManager::Save(bool ignoreStatus) bool AchievementManager::SaveBinary(bool ignoreStatus)
{ {
if (!ignoreStatus && Status != EAchStatus::Success) if (!ignoreStatus && BinStatus != EAchBinStatus::Success)
{ {
LOGN_WARNING("Achievement data will not be saved in this session!"); LOGN_WARNING("Achievement data will not be saved in this session!");
return; return false;
} }
LOGN("Saving achievements..."); LOGN("Saving achievements...");
@ -191,7 +192,7 @@ void AchievementManager::Save(bool ignoreStatus)
if (!file) if (!file)
{ {
LOGN_ERROR("Failed to write achievement data."); LOGN_ERROR("Failed to write achievement data.");
return; return false;
} }
Data.Checksum = Data.CalculateChecksum(); Data.Checksum = Data.CalculateChecksum();
@ -199,5 +200,7 @@ void AchievementManager::Save(bool ignoreStatus)
file.write((const char*)&Data, sizeof(AchievementData)); file.write((const char*)&Data, sizeof(AchievementData));
file.close(); file.close();
Status = EAchStatus::Success; BinStatus = EAchBinStatus::Success;
return true;
} }

View file

@ -2,7 +2,7 @@
#include <user/achievement_data.h> #include <user/achievement_data.h>
enum class EAchStatus enum class EAchBinStatus
{ {
Success, Success,
IOError, IOError,
@ -16,7 +16,7 @@ class AchievementManager
{ {
public: public:
static inline AchievementData Data{}; static inline AchievementData Data{};
static inline EAchStatus Status{}; static inline EAchBinStatus BinStatus{ EAchBinStatus::Success };
static std::filesystem::path GetDataPath(bool checkForMods) static std::filesystem::path GetDataPath(bool checkForMods)
{ {
@ -29,6 +29,6 @@ public:
static void Unlock(uint16_t id); static void Unlock(uint16_t id);
static void UnlockAll(); static void UnlockAll();
static void Reset(); static void Reset();
static void Load(); static bool LoadBinary();
static void Save(bool ignoreStatus = false); static bool SaveBinary(bool ignoreStatus = false);
}; };

View file

@ -0,0 +1,13 @@
#include "persistent_data.h"
bool PersistentData::VerifySignature() const
{
char sig[4] = EXT_SIGNATURE;
return memcmp(Signature, sig, sizeof(Signature)) == 0;
}
bool PersistentData::VerifyVersion() const
{
return Version <= EXT_VERSION;
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <user/paths.h>
#define EXT_FILENAME "EXT-DATA"
#define EXT_SIGNATURE { 'E', 'X', 'T', ' ' }
#define EXT_VERSION 1
enum class EDLCFlag
{
ApotosAndShamar,
Spagonia,
Chunnan,
Mazuri,
Holoska,
EmpireCityAndAdabat,
Count
};
class PersistentData
{
public:
char Signature[4] EXT_SIGNATURE;
uint32_t Version{ EXT_VERSION };
uint64_t Reserved{};
bool DLCFlags[6]{};
bool VerifySignature() const;
bool VerifyVersion() const;
};

View file

@ -0,0 +1,117 @@
#include "persistent_storage_manager.h"
#include <install/installer.h>
#include <os/logger.h>
#include <user/paths.h>
bool PersistentStorageManager::ShouldDisplayDLCMessage(bool setOffendingDLCFlag)
{
if (BinStatus != EExtBinStatus::Success)
return true;
static std::unordered_map<EDLCFlag, DLC> flags =
{
{ EDLCFlag::ApotosAndShamar, DLC::ApotosShamar },
{ EDLCFlag::Spagonia, DLC::Spagonia },
{ EDLCFlag::Chunnan, DLC::Chunnan },
{ EDLCFlag::Mazuri, DLC::Mazuri },
{ EDLCFlag::Holoska, DLC::Holoska },
{ EDLCFlag::EmpireCityAndAdabat, DLC::EmpireCityAdabat }
};
auto result = false;
for (auto& pair : flags)
{
if (!Data.DLCFlags[(int)pair.first] && Installer::checkDLCInstall(GetGamePath(), pair.second))
{
if (setOffendingDLCFlag)
Data.DLCFlags[(int)pair.first] = true;
result = true;
}
}
return result;
}
bool PersistentStorageManager::LoadBinary()
{
BinStatus = EExtBinStatus::Success;
auto dataPath = GetDataPath(true);
if (!std::filesystem::exists(dataPath))
{
// Try loading base persistent data as fallback.
dataPath = GetDataPath(false);
if (!std::filesystem::exists(dataPath))
return true;
}
std::error_code ec;
auto fileSize = std::filesystem::file_size(dataPath, ec);
auto dataSize = sizeof(PersistentData);
if (fileSize != dataSize)
{
BinStatus = EExtBinStatus::BadFileSize;
return false;
}
std::ifstream file(dataPath, std::ios::binary);
if (!file)
{
BinStatus = EExtBinStatus::IOError;
return false;
}
PersistentData data{};
file.read((char*)&data.Signature, sizeof(data.Signature));
if (!data.VerifySignature())
{
BinStatus = EExtBinStatus::BadSignature;
file.close();
return false;
}
file.read((char*)&data.Version, sizeof(data.Version));
if (!data.VerifyVersion())
{
BinStatus = EExtBinStatus::BadVersion;
file.close();
return false;
}
file.seekg(0);
file.read((char*)&data, sizeof(data));
file.close();
memcpy(&Data, &data, dataSize);
return true;
}
bool PersistentStorageManager::SaveBinary()
{
LOGN("Saving persistent storage binary...");
std::ofstream file(GetDataPath(true), std::ios::binary);
if (!file)
{
LOGN_ERROR("Failed to write persistent storage binary.");
return false;
}
file.write((const char*)&Data, sizeof(PersistentData));
file.close();
BinStatus = EExtBinStatus::Success;
return true;
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <user/persistent_data.h>
enum class EExtBinStatus
{
Success,
IOError,
BadFileSize,
BadSignature,
BadVersion
};
class PersistentStorageManager
{
public:
static inline PersistentData Data{};
static inline EExtBinStatus BinStatus{ EExtBinStatus::Success };
static std::filesystem::path GetDataPath(bool checkForMods)
{
return GetSavePath(checkForMods) / EXT_FILENAME;
}
static bool ShouldDisplayDLCMessage(bool setOffendingDLCFlag);
static bool LoadBinary();
static bool SaveBinary();
};