From 88ad327c937b2997acf898976f33fb92d0bdac60 Mon Sep 17 00:00:00 2001 From: lightbull Date: Fri, 9 Oct 2026 10:14:09 +0200 Subject: [PATCH 1/3] Add support for syncing haven pieces data --- mods/src/defaultconfig.h | 3 ++- mods/src/patches/parts/sync.cc | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/mods/src/defaultconfig.h b/mods/src/defaultconfig.h index 40b31ae5..7158c8bd 100644 --- a/mods/src/defaultconfig.h +++ b/mods/src/defaultconfig.h @@ -225,9 +225,10 @@ namespace Sync constexpr const char* nextspocksclub_url = "https://next.spocks.club/sync/ingress/"; constexpr bool away_assignments = true; - constexpr bool battlelogs = true; + constexpr bool battlelogs = false; constexpr bool buffs = true; constexpr bool buildings = true; + constexpr bool haven = true; constexpr bool inventory = true; constexpr bool jobs = true; constexpr bool missions = true; diff --git a/mods/src/patches/parts/sync.cc b/mods/src/patches/parts/sync.cc index f54a9036..cb0c6dde 100644 --- a/mods/src/patches/parts/sync.cc +++ b/mods/src/patches/parts/sync.cc @@ -802,6 +802,9 @@ static std::mutex away_assignment_states_mtx; static std::unordered_map structure_states; static std::mutex structure_states_mtx; +static std::unordered_map pieces_states; +static std::mutex pieces_states_mtx; + static eastl::ring_buffer previously_sent_battlelogs; static std::mutex previously_sent_battlelogs_mtx; @@ -1469,6 +1472,8 @@ static void planetary_base_data(std::unique_ptr&& bytes) using json = nlohmann::json; using trackers::structure_states; using trackers::structure_states_mtx; + using trackers::pieces_states; + using trackers::pieces_states_mtx; if (auto response = Digit::PrimeServer::Models::PlanetaryBase(); response.ParseFromString(*bytes)) { http::logging::trace("PROCESS", "planetary base data", STR_FORMAT("Processing {} buildings", response.buildings_size())); @@ -1488,9 +1493,25 @@ static void planetary_base_data(std::unique_ptr&& bytes) } } + auto pieces_array = json::array(); + { + std::scoped_lock lk(pieces_states_mtx); + + for (const auto& [piece, count] : response.pieces()) { + if (const auto& it = pieces_states.find(piece); it == pieces_states.end() || it->second != count) { + pieces_states[piece] = piece; + pieces_array.push_back({{"type", SyncConfig::Type::Haven + "_pieces"}, {"sid", piece}, {"count", piece}}); + } + } + } + if (!structure_array.empty()) { workers::queue_data(SyncConfig::Type::Haven, structure_array); } + + if (!pieces_array.empty()) { + workers::queue_data(SyncConfig::Type::Haven, pieces_array); + } } } From 78d6fad8db331d47c0bb5d51adb60166ff0fdba3 Mon Sep 17 00:00:00 2001 From: lightbull Date: Fri, 9 Oct 2026 10:42:27 +0200 Subject: [PATCH 2/3] Include building ID in Haven map sync data --- mods/src/patches/parts/sync.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/src/patches/parts/sync.cc b/mods/src/patches/parts/sync.cc index cb0c6dde..d08ccfb0 100644 --- a/mods/src/patches/parts/sync.cc +++ b/mods/src/patches/parts/sync.cc @@ -1520,8 +1520,8 @@ static void planetary_map_building_data(const google::protobuf::Map Date: Sat, 10 Oct 2026 20:21:33 +0200 Subject: [PATCH 3/3] Update haven sync type --- mods/src/patches/parts/sync.cc | 116 +++++++++++++++++++--------- mods/src/prime/UserProfile.h | 6 ++ mods/src/prime/UserProfileManager.h | 22 ++++++ mods/src/str_utils.h | 10 +++ 4 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 mods/src/prime/UserProfileManager.h diff --git a/mods/src/patches/parts/sync.cc b/mods/src/patches/parts/sync.cc index d08ccfb0..0fd2ffcf 100644 --- a/mods/src/patches/parts/sync.cc +++ b/mods/src/patches/parts/sync.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -612,6 +613,23 @@ namespace types std::vector components; }; + struct PlanetaryBaseState { + explicit PlanetaryBaseState(const int32_t l = -1, const int32_t s = -1, const int64_t p = -1) + : level(l) + , status(s) + , position(p) + { + } + + bool operator==(const PlanetaryBaseState& other) const + { return this->level == other.level && this->status == other.status && this->position == other.position; } + + private: + int32_t level = -1; + int32_t status = -1; + int64_t position = -1; + }; + struct pairhash { template std::size_t operator()(const std::pair& x) const { return std::hash()(x.first) ^ std::hash()(x.second); } @@ -799,8 +817,8 @@ static std::mutex slot_states_mtx; static std::unordered_map away_assignment_states; static std::mutex away_assignment_states_mtx; -static std::unordered_map structure_states; -static std::mutex structure_states_mtx; +static std::unordered_map structure_states; +static std::mutex structure_states_mtx; static std::unordered_map pieces_states; static std::mutex pieces_states_mtx; @@ -1470,45 +1488,23 @@ static void starbase_modules(std::unique_ptr&& bytes) static void planetary_base_data(std::unique_ptr&& bytes) { using json = nlohmann::json; - using trackers::structure_states; - using trackers::structure_states_mtx; using trackers::pieces_states; using trackers::pieces_states_mtx; if (auto response = Digit::PrimeServer::Models::PlanetaryBase(); response.ParseFromString(*bytes)) { - http::logging::trace("PROCESS", "planetary base data", STR_FORMAT("Processing {} buildings", response.buildings_size())); - - auto structure_array = json::array(); - { - std::scoped_lock lk(structure_states_mtx); - - for (const auto& building : response.buildings() | std::views::values) { - if (const auto& it = structure_states.find(building.id()); it == structure_states.end() || it->second != building.level()) { - structure_states[building.id()] = building.level(); - structure_array.push_back({{"type", SyncConfig::Type::Haven}, - {"sid", building.specid()}, - {"id", building.id()}, - {"level", building.level()}}); - } - } - } - + http::logging::trace("PROCESS", "planetary base data", STR_FORMAT("Processing {} haven building pieces", response.pieces_size())); auto pieces_array = json::array(); { std::scoped_lock lk(pieces_states_mtx); for (const auto& [piece, count] : response.pieces()) { if (const auto& it = pieces_states.find(piece); it == pieces_states.end() || it->second != count) { - pieces_states[piece] = piece; - pieces_array.push_back({{"type", SyncConfig::Type::Haven + "_pieces"}, {"sid", piece}, {"count", piece}}); + pieces_states[piece] = count; + pieces_array.push_back({{"type", SyncConfig::Type::Haven + "_pieces"}, {"sid", piece}, {"count", count}}); } } } - if (!structure_array.empty()) { - workers::queue_data(SyncConfig::Type::Haven, structure_array); - } - if (!pieces_array.empty()) { workers::queue_data(SyncConfig::Type::Haven, pieces_array); } @@ -1519,9 +1515,22 @@ static void planetary_map_building_data(const google::protobuf::Mapsecond != state) { + structure_states[id] = state; + structure_array.push_back({{"type", SyncConfig::Type::Haven}, {"sid", building.specid()}, {"id", id}, {"level", building.level()}, {"status", building.status()}, {"position", building.position()}}); + } + } } if (!structure_array.empty()) { @@ -1547,18 +1556,51 @@ static void planetary_map_prosperity(std::unique_ptr&& bytes) } } -/* static void planetary_map(std::unique_ptr&& bytes) +static void planetary_map(std::unique_ptr&& bytes) { if (auto response = Digit::PrimeServer::Models::PlanetaryMapResponse(); response.ParseFromString(*bytes)) { http::logging::trace("PROCESS", "planetary map", STR_FORMAT("Processing {} haven structures", response.mapdata().buildings_size())); + const auto& owner = response.owneruserid(); + if (!owner.empty()) { + auto profileManager = UserProfileManager::Instance(); + if (profileManager == nullptr) { + spdlog::error("Failed to get UserProfileManager instance"); + return; + } + + static auto& class_helper = profileManager->get_class_helper(); + static auto fn = class_helper.GetMethodInfo("GetLocalUserProfile"); + + if (fn == nullptr ) { + spdlog::error("Failed to get method info for GetLocalUserProfile"); + return; + } + + Il2CppException* exception = nullptr; + auto localProfile = reinterpret_cast(il2cpp_runtime_invoke(fn, profileManager, nullptr, &exception)); + if (exception != nullptr || localProfile == nullptr) { + spdlog::error("Failed to get local user profile"); + return; + } + + const auto userId = localProfile->UserId; + if (userId == nullptr) { + spdlog::error("Local user profile has empty UserId"); + return; + } + + if (owner != userId) { + spdlog::debug("Planetary map data is not for local user, skipping processing"); + return; + } + } - // TODO: If the owner is not the current player, we may want to skip processing the map data, as it may not be relevant to the player's own structures. planetary_map_building_data(response.mapdata().buildings()); } else { spdlog::error("Failed to parse planetary map"); } -} */ +} static void player_inventories(std::unique_ptr&& bytes) { @@ -2539,11 +2581,11 @@ static void HandleEntityGroup(EntityGroup* entity_group) } break; - // case EntityGroup::Type::PlanetaryMapResponse: - // if (sync_options.haven) { - // submit_async(processors::planetary_map); - // } - // break; + case EntityGroup::Type::PlanetaryMapResponse: + if (sync_options.haven) { + submit_async(processors::planetary_map); + } + break; // inventory case EntityGroup::Type::PlayerInventories: diff --git a/mods/src/prime/UserProfile.h b/mods/src/prime/UserProfile.h index d5204ef5..d202a959 100644 --- a/mods/src/prime/UserProfile.h +++ b/mods/src/prime/UserProfile.h @@ -6,6 +6,7 @@ struct UserProfile { public: __declspec(property(get = __get_LocaId)) long LocaId; __declspec(property(get = __get_Name)) Il2CppString* Name; + __declspec(property(get = __get_UserId)) Il2CppString* UserId; private: static IL2CppClassHelper& get_class_helper() @@ -28,4 +29,9 @@ struct UserProfile { return *(Il2CppString**)((char*)this + field); } + Il2CppString* __get_UserId() + { + static auto prop = get_class_helper().GetProperty("UserId"); + return prop.GetRaw(this); + } }; diff --git a/mods/src/prime/UserProfileManager.h b/mods/src/prime/UserProfileManager.h new file mode 100644 index 00000000..90da6b93 --- /dev/null +++ b/mods/src/prime/UserProfileManager.h @@ -0,0 +1,22 @@ +#pragma once + +#include "MonoSingleton.h" +#include "UserProfile.h" + +#include + +struct UserProfileManager : MonoSingleton { + + static IL2CppClassHelper& get_class_helper() + { + static auto class_helper = + il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlayerProfile", "UserProfileManager"); + return class_helper; + } + + [[nodiscard]] UserProfile* GetLocalUserProfile() + { + static auto method = get_class_helper().GetInvokeMethod("GetLocalUserProfile"); + return method.Invoke(this); + } +}; diff --git a/mods/src/str_utils.h b/mods/src/str_utils.h index a9fbfb7d..2c153891 100644 --- a/mods/src/str_utils.h +++ b/mods/src/str_utils.h @@ -145,6 +145,16 @@ inline std::string to_string(const Il2CppString* str) return result; } +inline bool operator==(const Il2CppString* lhs, const std::string& rhs) +{ + return to_string(lhs) == rhs; +} + +inline bool operator==(const std::string& lhs, const Il2CppString* rhs) +{ + return lhs == to_string(rhs); +} + inline std::optional> parse_timestamp(const std::string& timestamp) { #ifdef _WIN32