|
@@ -3,6 +3,7 @@
|
|
|
#include <Windows.h>
|
|
#include <Windows.h>
|
|
|
|
|
|
|
|
#include <array>
|
|
#include <array>
|
|
|
|
|
+#include <algorithm>
|
|
|
#include <cstdint>
|
|
#include <cstdint>
|
|
|
#include <cstring>
|
|
#include <cstring>
|
|
|
#include <limits>
|
|
#include <limits>
|
|
@@ -10,15 +11,22 @@
|
|
|
#include <string_view>
|
|
#include <string_view>
|
|
|
|
|
|
|
|
#include "../../core/filesystem/path.h"
|
|
#include "../../core/filesystem/path.h"
|
|
|
|
|
+#include "season_pass_reward_catalog.h"
|
|
|
|
|
|
|
|
namespace sunrise::state::progression::seasonal_experience {
|
|
namespace sunrise::state::progression::seasonal_experience {
|
|
|
namespace {
|
|
namespace {
|
|
|
|
|
|
|
|
constexpr std::wstring_view kFileSuffix = L"\\cache\\seasonal_experience.bin";
|
|
constexpr std::wstring_view kFileSuffix = L"\\cache\\seasonal_experience.bin";
|
|
|
-constexpr std::array<char, 8> kMagic{'S', 'N', 'R', 'S', 'X', 'P', '0', '1'};
|
|
|
|
|
|
|
+constexpr std::array<char, 8> kLegacyMagic{'S', 'N', 'R', 'S', 'X', 'P', '0', '1'};
|
|
|
|
|
+constexpr std::array<char, 8> kMagic{'S', 'N', 'R', 'S', 'X', 'P', '0', '2'};
|
|
|
|
|
+constexpr std::size_t kRewardCount = 196;
|
|
|
|
|
+constexpr std::size_t kRewardClaimByteCount = (kRewardCount + 7U) / 8U;
|
|
|
|
|
+constexpr std::int32_t kExperiencePerRank = 100'000;
|
|
|
|
|
+constexpr std::uint16_t kMaximumRank = 100;
|
|
|
|
|
|
|
|
std::mutex g_lock;
|
|
std::mutex g_lock;
|
|
|
std::int32_t g_experience{};
|
|
std::int32_t g_experience{};
|
|
|
|
|
+std::array<std::uint8_t, kRewardClaimByteCount> g_rewardClaims{};
|
|
|
core::path::Buffer g_path{};
|
|
core::path::Buffer g_path{};
|
|
|
bool g_pathReady{};
|
|
bool g_pathReady{};
|
|
|
|
|
|
|
@@ -26,9 +34,14 @@ void store_locked() noexcept {
|
|
|
if (!g_pathReady) {
|
|
if (!g_pathReady) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- std::array<std::byte, kMagic.size() + sizeof(g_experience)> document{};
|
|
|
|
|
|
|
+ std::array<std::byte,
|
|
|
|
|
+ kMagic.size() + sizeof(g_experience) + kRewardClaimByteCount>
|
|
|
|
|
+ document{};
|
|
|
std::memcpy(document.data(), kMagic.data(), kMagic.size());
|
|
std::memcpy(document.data(), kMagic.data(), kMagic.size());
|
|
|
std::memcpy(document.data() + kMagic.size(), &g_experience, sizeof g_experience);
|
|
std::memcpy(document.data() + kMagic.size(), &g_experience, sizeof g_experience);
|
|
|
|
|
+ std::memcpy(document.data() + kMagic.size() + sizeof g_experience,
|
|
|
|
|
+ g_rewardClaims.data(),
|
|
|
|
|
+ g_rewardClaims.size());
|
|
|
const HANDLE file = CreateFileW(g_path.chars.data(),
|
|
const HANDLE file = CreateFileW(g_path.chars.data(),
|
|
|
GENERIC_WRITE,
|
|
GENERIC_WRITE,
|
|
|
0,
|
|
0,
|
|
@@ -59,25 +72,35 @@ void load_locked() noexcept {
|
|
|
if (file == INVALID_HANDLE_VALUE) {
|
|
if (file == INVALID_HANDLE_VALUE) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- std::array<std::byte, kMagic.size() + sizeof(g_experience)> document{};
|
|
|
|
|
|
|
+ std::array<std::byte,
|
|
|
|
|
+ kMagic.size() + sizeof(g_experience) + kRewardClaimByteCount>
|
|
|
|
|
+ document{};
|
|
|
DWORD read = 0;
|
|
DWORD read = 0;
|
|
|
- const bool complete =
|
|
|
|
|
- ReadFile(file,
|
|
|
|
|
- document.data(),
|
|
|
|
|
- static_cast<DWORD>(document.size()),
|
|
|
|
|
- &read,
|
|
|
|
|
- nullptr)
|
|
|
|
|
- != FALSE
|
|
|
|
|
- && read == document.size()
|
|
|
|
|
- && std::memcmp(document.data(), kMagic.data(), kMagic.size()) == 0;
|
|
|
|
|
|
|
+ const bool readable = ReadFile(file,
|
|
|
|
|
+ document.data(),
|
|
|
|
|
+ static_cast<DWORD>(document.size()),
|
|
|
|
|
+ &read,
|
|
|
|
|
+ nullptr)
|
|
|
|
|
+ != FALSE;
|
|
|
(void)CloseHandle(file);
|
|
(void)CloseHandle(file);
|
|
|
std::int32_t restored = 0;
|
|
std::int32_t restored = 0;
|
|
|
- if (complete) {
|
|
|
|
|
|
|
+ const std::size_t legacySize = kLegacyMagic.size() + sizeof restored;
|
|
|
|
|
+ const bool current = readable && read == document.size()
|
|
|
|
|
+ && std::memcmp(document.data(), kMagic.data(), kMagic.size()) == 0;
|
|
|
|
|
+ const bool legacy = readable && read == legacySize
|
|
|
|
|
+ && std::memcmp(document.data(), kLegacyMagic.data(), kLegacyMagic.size())
|
|
|
|
|
+ == 0;
|
|
|
|
|
+ if (current || legacy) {
|
|
|
std::memcpy(&restored, document.data() + kMagic.size(), sizeof restored);
|
|
std::memcpy(&restored, document.data() + kMagic.size(), sizeof restored);
|
|
|
}
|
|
}
|
|
|
if (restored >= 0) {
|
|
if (restored >= 0) {
|
|
|
g_experience = restored;
|
|
g_experience = restored;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (current) {
|
|
|
|
|
+ std::memcpy(g_rewardClaims.data(),
|
|
|
|
|
+ document.data() + kMagic.size() + sizeof g_experience,
|
|
|
|
|
+ g_rewardClaims.size());
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
} // namespace
|
|
@@ -85,6 +108,7 @@ void load_locked() noexcept {
|
|
|
bool initialize(void* module) noexcept {
|
|
bool initialize(void* module) noexcept {
|
|
|
const std::lock_guard<std::mutex> guard(g_lock);
|
|
const std::lock_guard<std::mutex> guard(g_lock);
|
|
|
g_experience = 0;
|
|
g_experience = 0;
|
|
|
|
|
+ g_rewardClaims.fill(0);
|
|
|
g_pathReady = core::path::artifact_directory(module, g_path)
|
|
g_pathReady = core::path::artifact_directory(module, g_path)
|
|
|
&& core::path::append(g_path, kFileSuffix);
|
|
&& core::path::append(g_path, kFileSuffix);
|
|
|
if (g_pathReady) {
|
|
if (g_pathReady) {
|
|
@@ -96,6 +120,7 @@ bool initialize(void* module) noexcept {
|
|
|
void shutdown() noexcept {
|
|
void shutdown() noexcept {
|
|
|
const std::lock_guard<std::mutex> guard(g_lock);
|
|
const std::lock_guard<std::mutex> guard(g_lock);
|
|
|
g_experience = 0;
|
|
g_experience = 0;
|
|
|
|
|
+ g_rewardClaims.fill(0);
|
|
|
g_path = {};
|
|
g_path = {};
|
|
|
g_pathReady = false;
|
|
g_pathReady = false;
|
|
|
}
|
|
}
|
|
@@ -118,4 +143,54 @@ std::int32_t earned() noexcept {
|
|
|
return g_experience;
|
|
return g_experience;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+std::uint16_t rank() noexcept {
|
|
|
|
|
+ const std::lock_guard<std::mutex> guard(g_lock);
|
|
|
|
|
+ const std::int32_t earnedRanks = g_experience / kExperiencePerRank;
|
|
|
|
|
+ return static_cast<std::uint16_t>(
|
|
|
|
|
+ (std::min)(static_cast<std::int32_t>(kMaximumRank), earnedRanks + 1));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool reward_claimed(std::uint16_t rewardIndex) noexcept {
|
|
|
|
|
+ if (rewardIndex >= kRewardCount) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const std::lock_guard<std::mutex> guard(g_lock);
|
|
|
|
|
+ const std::uint8_t mask = static_cast<std::uint8_t>(1U << (rewardIndex & 7U));
|
|
|
|
|
+ return (g_rewardClaims[rewardIndex >> 3U] & mask) != 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool claim_reward(std::uint16_t rewardIndex) noexcept {
|
|
|
|
|
+ if (rewardIndex >= kRewardCount) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const std::lock_guard<std::mutex> guard(g_lock);
|
|
|
|
|
+ const std::uint8_t mask = static_cast<std::uint8_t>(1U << (rewardIndex & 7U));
|
|
|
|
|
+ std::uint8_t& byte = g_rewardClaims[rewardIndex >> 3U];
|
|
|
|
|
+ if ((byte & mask) != 0) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ byte = static_cast<std::uint8_t>(byte | mask);
|
|
|
|
|
+ store_locked();
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool apply_reward_claims(std::span<std::uint8_t> acquiredFlags) noexcept {
|
|
|
|
|
+ constexpr std::uint8_t kAcquiredFlagValue = 2;
|
|
|
|
|
+ if (acquiredFlags.size()
|
|
|
|
|
+ <= season_pass::claim_account_flag_index(
|
|
|
|
|
+ static_cast<std::uint16_t>(season_pass::kRewards.size() - 1U))) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const std::lock_guard<std::mutex> guard(g_lock);
|
|
|
|
|
+ for (std::uint16_t rewardIndex = 0; rewardIndex < season_pass::kRewards.size(); ++rewardIndex) {
|
|
|
|
|
+ const std::uint8_t mask = static_cast<std::uint8_t>(1U << (rewardIndex & 7U));
|
|
|
|
|
+ if ((g_rewardClaims[rewardIndex >> 3U] & mask) == 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ acquiredFlags[season_pass::claim_account_flag_index(rewardIndex)] = kAcquiredFlagValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
} // namespace sunrise::state::progression::seasonal_experience
|
|
} // namespace sunrise::state::progression::seasonal_experience
|