diff --git a/UnleashedRecomp/user/achievement_data.cpp b/UnleashedRecomp/user/achievement_data.cpp index 0325ab27..f08d5164 100644 --- a/UnleashedRecomp/user/achievement_data.cpp +++ b/UnleashedRecomp/user/achievement_data.cpp @@ -3,10 +3,29 @@ #include #include +#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; +} + bool AchievementData::IsUnlocked(uint16_t id) { - for (int i = 0; i < sizeof(Data.Records) / sizeof(Record); i++) + for (int i = 0; i < NUM_RECORDS; i++) { + if (!Data.Records[i].ID) + break; + if (Data.Records[i].ID == id) return true; } @@ -19,7 +38,7 @@ void AchievementData::Unlock(uint16_t id) if (IsUnlocked(id)) return; - for (int i = 0; i < sizeof(Data.Records) / sizeof(Record); i++) + for (int i = 0; i < NUM_RECORDS; i++) { if (Data.Records[i].ID == 0) { @@ -33,15 +52,39 @@ void AchievementData::Unlock(uint16_t id) AchievementOverlay::Open(id); } -time_t AchievementData::GetTimestamp(uint16_t id) +uint32_t AchievementData::CalculateChecksum() { - for (int i = 0; i < sizeof(Data.Records) / sizeof(Record); i++) + auto result = 0; + + for (int i = 0; i < NUM_RECORDS; i++) { - if (Data.Records[i].ID == id) - return Data.Records[i].Timestamp; + auto& record = Data.Records[i]; + + for (size_t j = 0; j < sizeof(Record); j++) + result ^= ((uint8_t*)(&record))[j]; } - return 0; + 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() @@ -55,11 +98,48 @@ void AchievementData::Load() if (!file) { - printf("[*] Failed to parse achievement data.\n"); + printf("[*] ERROR: failed to read achievement data.\n"); return; } + file.read((char*)&Data.Signature, sizeof(Data.Signature)); + + if (!VerifySignature()) + { + printf("[*] ERROR: invalid achievement data signature.\n"); + + 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()) + { + printf("[*] ERROR: unsupported achievement data version.\n"); + 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()) + { + printf("[*] ERROR: achievement data checksum mismatch.\n"); + memset(&Data.Records, 0, sizeof(Data.Records)); + } + file.close(); } @@ -69,10 +149,12 @@ void AchievementData::Save() if (!file) { - printf("[*] Failed to write achievement data.\n"); + printf("[*] ERROR: failed to write achievement data.\n"); return; } + Data.Checksum = CalculateChecksum(); + file.write((const char*)&Data, sizeof(Data)); file.close(); } diff --git a/UnleashedRecomp/user/achievement_data.h b/UnleashedRecomp/user/achievement_data.h index df06e7bd..942c9841 100644 --- a/UnleashedRecomp/user/achievement_data.h +++ b/UnleashedRecomp/user/achievement_data.h @@ -1,10 +1,9 @@ #pragma once -#include #include -#define ACH_SIGNATURE 'ACH ' -#define ACH_VERSION 0x01000000 +#define ACH_SIGNATURE { 'A', 'C', 'H', ' ' } +#define ACH_VERSION { 1, 0, 0 } class AchievementData { @@ -18,12 +17,28 @@ public: }; #pragma pack(pop) + struct Version + { + uint8_t Major; + uint8_t Minor; + uint8_t Revision; + uint8_t Reserved; + + bool operator==(const Version& other) const + { + return Major == other.Major && + Minor == other.Minor && + Revision == other.Revision; + } + }; + class Data { public: - uint32_t Signature{}; - uint32_t Version{}; - uint32_t Reserved[2]; + char Signature[4]; + Version Version{}; + uint32_t Checksum; + uint32_t Reserved; Record Records[50]; }; @@ -34,9 +49,13 @@ public: return GetSavePath() / "ACH-DATA"; } + static time_t GetTimestamp(uint16_t id); static bool IsUnlocked(uint16_t id); static void Unlock(uint16_t id); - static time_t GetTimestamp(uint16_t id); + static uint32_t CalculateChecksum(); + static bool VerifySignature(); + static bool VerifyVersion(); + static bool VerifyChecksum(); static void Load(); static void Save(); };