Files
UnleashedRecomp-hedge-dev/UnleashedRecomp/user/achievement_data.cpp
Skyth (Asilkan) a397a90551 Mod loader implementation. (#66)
* Initial mod loader implementation.

* Allow iterating in mod directories.

* Initial append archive implementation.

* Avoid calling function wrappers when loading append ARs.

For some reason they cause issues. Should investigate later.

* UMM merge archive support.

* Load merge archives without archive lists.

* Less thread locals.

I shouldn't worry about string allocations this much when the game itself spams them...

* Check for read-only UMM archives.

TODO: Skip merging as it's currently just doing duplicate loads.

* Skip loading merge archives if they are read-only.

* Merge only archives.

* Implement decompression.

* Fix append ARLs not loading.

* Initial save file redirection implementation.

* Slightly refactor resolved path usage.

* Implement save file redirection fallback.

* Set a default save file path if none is provided.

* Check for enabled option & replace backward slashes with forward ones in mod save file paths.

* Convert back slashes to forward ones when iterating directories.

* Make CSB limit dynamic.

* Cache append/merge archive lookups.

* Close stream after reading compressed ARL.

* Fix UMM/HMM ARL file path inconsistency.
2024-12-31 20:20:07 +03:00

181 lines
3.6 KiB
C++

#include "achievement_data.h"
#include <ui/achievement_overlay.h>
#include <user/config.h>
#include <os/logger.h>
#define NUM_RECORDS sizeof(Data.Records) / sizeof(Record)
time_t AchievementData::GetTimestamp(uint16_t id)
{
for (int i = 0; i < NUM_RECORDS; i++)
{
if (!Data.Records[i].ID)
break;
if (Data.Records[i].ID == id)
return Data.Records[i].Timestamp;
}
return 0;
}
int AchievementData::GetTotalRecords()
{
auto result = 0;
for (int i = 0; i < NUM_RECORDS; i++)
{
if (!Data.Records[i].ID)
break;
result++;
}
return result;
}
bool AchievementData::IsUnlocked(uint16_t id)
{
for (int i = 0; i < NUM_RECORDS; i++)
{
if (!Data.Records[i].ID)
break;
if (Data.Records[i].ID == id)
return true;
}
return false;
}
void AchievementData::Unlock(uint16_t id)
{
if (IsUnlocked(id))
return;
for (int i = 0; i < NUM_RECORDS; i++)
{
if (Data.Records[i].ID == 0)
{
Data.Records[i].ID = id;
Data.Records[i].Timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
break;
}
}
if (Config::AchievementNotifications)
AchievementOverlay::Open(id);
}
uint32_t AchievementData::CalculateChecksum()
{
auto result = 0;
for (int i = 0; i < NUM_RECORDS; i++)
{
auto& record = Data.Records[i];
for (size_t j = 0; j < sizeof(Record); j++)
result ^= ((uint8_t*)(&record))[j];
}
return result;
}
bool AchievementData::VerifySignature()
{
char sig[4] = ACH_SIGNATURE;
return Data.Signature[0] == sig[0] &&
Data.Signature[1] == sig[1] &&
Data.Signature[2] == sig[2] &&
Data.Signature[3] == sig[3];
}
bool AchievementData::VerifyVersion()
{
return Data.Version == Version ACH_VERSION;
}
bool AchievementData::VerifyChecksum()
{
return Data.Checksum == CalculateChecksum();
}
void AchievementData::Load()
{
auto dataPath = GetDataPath(true);
if (!std::filesystem::exists(dataPath))
{
// Try loading base achievement data as fallback.
dataPath = GetDataPath(false);
if (!std::filesystem::exists(dataPath))
return;
}
std::ifstream file(dataPath, std::ios::binary);
if (!file)
{
LOGN_ERROR("Failed to read achievement data.");
return;
}
file.read((char*)&Data.Signature, sizeof(Data.Signature));
if (!VerifySignature())
{
LOGN_ERROR("Invalid achievement data signature.");
char sig[4] = ACH_SIGNATURE;
Data.Signature[0] = sig[0];
Data.Signature[1] = sig[1];
Data.Signature[2] = sig[2];
Data.Signature[3] = sig[3];
file.close();
return;
}
file.read((char*)&Data.Version, sizeof(Data.Version));
if (!VerifyVersion())
{
LOGN_ERROR("Unsupported achievement data version.");
Data.Version = ACH_VERSION;
file.close();
return;
}
file.seekg(0);
file.read((char*)&Data, sizeof(Data));
// TODO: display error message to user before wiping data?
if (!VerifyChecksum())
{
LOGN_ERROR("Achievement data checksum mismatch.");
memset(&Data.Records, 0, sizeof(Data.Records));
}
file.close();
}
void AchievementData::Save()
{
std::ofstream file(GetDataPath(true), std::ios::binary);
if (!file)
{
LOGN_ERROR("Failed to write achievement data.");
return;
}
Data.Checksum = CalculateChecksum();
file.write((const char*)&Data, sizeof(Data));
file.close();
}