diff --git a/src/client/component/name.cpp b/src/client/component/name.cpp new file mode 100644 index 00000000..136ca950 --- /dev/null +++ b/src/client/component/name.cpp @@ -0,0 +1,103 @@ +#include +#include "loader/component_loader.hpp" + +#include "name.hpp" +#include "steam_proxy.hpp" +#include "command.hpp" + +#include +#include +#include +#include + +namespace name +{ + namespace + { + utils::concurrency::container player_name{}; + + void store_player_name(const std::string& name) + { + utils::properties::store("playerName", name); + } + + void activate_player_name(std::string new_name) + { + player_name.access([&](std::string& name) + { + name = std::move(new_name); + }); + } + + void update_player_name(const std::string& new_name) + { + store_player_name(new_name); + activate_player_name(new_name); + } + + void setup_player_name() + { + std::string initial_name = steam_proxy::get_player_name(); + + if (initial_name.empty()) + { + initial_name = utils::nt::get_user_name(); + } + + if (initial_name.empty()) + { + initial_name = "Unknown Soldier"; + } + + update_player_name(initial_name); + } + + void load_player_name() + { + const auto stored_name = utils::properties::load("playerName"); + + if (stored_name) + { + activate_player_name(*stored_name); + } + else + { + setup_player_name(); + } + } + } + + struct component final : client_component + { + void post_load() override + { + load_player_name(); + } + + void post_unpack() override + { + command::add("name", [](const command::params& params) + { + if (params.size() != 2) + { + return; + } + + update_player_name(params[1]); + }); + } + + component_priority priority() const override + { + return component_priority::name; + } + }; + + const char* get_player_name() + { + const auto name = player_name.copy(); + return utils::string::va("%.*s", static_cast(name.size()), name.data()); + } +} + +REGISTER_COMPONENT(name::component) diff --git a/src/client/component/name.hpp b/src/client/component/name.hpp new file mode 100644 index 00000000..b7c252a0 --- /dev/null +++ b/src/client/component/name.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace name +{ + const char* get_player_name(); +} diff --git a/src/client/component/steam_proxy.cpp b/src/client/component/steam_proxy.cpp index 568a9d97..85dc8aca 100644 --- a/src/client/component/steam_proxy.cpp +++ b/src/client/component/steam_proxy.cpp @@ -7,6 +7,8 @@ #include #include +#include "game/utils.hpp" + #include "steam/interface.hpp" #include "steam/steam.hpp" @@ -252,7 +254,7 @@ namespace steam_proxy return client_friends.invoke("GetPersonaName"); } - return "boiii"; + return ""; } void update_subscribed_items() diff --git a/src/client/component/steam_proxy.hpp b/src/client/component/steam_proxy.hpp index 16b45e85..fb1a3f63 100644 --- a/src/client/component/steam_proxy.hpp +++ b/src/client/component/steam_proxy.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace steam_proxy { const utils::nt::library& get_overlay_module(); diff --git a/src/client/loader/component_interface.hpp b/src/client/loader/component_interface.hpp index a17a0c73..2f8f84cf 100644 --- a/src/client/loader/component_interface.hpp +++ b/src/client/loader/component_interface.hpp @@ -3,6 +3,8 @@ enum class component_priority { min = 0, + // must run after the steam_proxy + name, // must run after the updater steam_proxy, updater, diff --git a/src/client/steam/interfaces/friends.cpp b/src/client/steam/interfaces/friends.cpp index 14479d5f..0a9a7240 100644 --- a/src/client/steam/interfaces/friends.cpp +++ b/src/client/steam/interfaces/friends.cpp @@ -3,13 +3,13 @@ #include -#include "component/steam_proxy.hpp" +#include "component/name.hpp" namespace steam { const char* friends::GetPersonaName() { - return steam_proxy::get_player_name(); + return name::get_player_name(); } unsigned long long friends::SetPersonaName(const char* pchPersonaName) diff --git a/src/client/updater/file_updater.cpp b/src/client/updater/file_updater.cpp index fedc43ad..2de1c384 100644 --- a/src/client/updater/file_updater.cpp +++ b/src/client/updater/file_updater.cpp @@ -380,13 +380,13 @@ namespace updater legal_files.reserve(files.size()); for (const auto& file : files) { - if (file.name != UPDATE_HOST_BINARY) + if (file.name.starts_with("data")) { legal_files.emplace_back(std::filesystem::absolute(base / file.name)); } } - const auto existing_files = utils::io::list_files(base.string(), true); + const auto existing_files = utils::io::list_files(base / "data", true); for (auto& file : existing_files) { const auto is_file = std::filesystem::is_regular_file(file); diff --git a/src/common/utils/concurrency.hpp b/src/common/utils/concurrency.hpp index 05c5d3ad..75967525 100644 --- a/src/common/utils/concurrency.hpp +++ b/src/common/utils/concurrency.hpp @@ -39,6 +39,12 @@ namespace utils::concurrency T& get_raw() { return object_; } const T& get_raw() const { return object_; } + T copy() const + { + std::unique_lock lock{mutex_}; + return object_; + } + private: mutable MutexType mutex_{}; T object_{}; diff --git a/src/common/utils/named_mutex.cpp b/src/common/utils/named_mutex.cpp new file mode 100644 index 00000000..388eb826 --- /dev/null +++ b/src/common/utils/named_mutex.cpp @@ -0,0 +1,44 @@ +#include "named_mutex.hpp" +#include "nt.hpp" + +namespace utils +{ + named_mutex::named_mutex(const std::string& name) + { + this->handle_ = CreateMutexA(nullptr, FALSE, name.data()); + } + + named_mutex::~named_mutex() + { + if (this->handle_) + { + CloseHandle(this->handle_); + } + } + + void named_mutex::lock() const + { + if (this->handle_) + { + WaitForSingleObject(this->handle_, INFINITE); + } + } + + bool named_mutex::try_lock(const std::chrono::milliseconds timeout) const + { + if (this->handle_) + { + return WAIT_OBJECT_0 == WaitForSingleObject(this->handle_, static_cast(timeout.count())); + } + + return false; + } + + void named_mutex::unlock() const noexcept + { + if (this->handle_) + { + ReleaseMutex(this->handle_); + } + } +} diff --git a/src/common/utils/named_mutex.hpp b/src/common/utils/named_mutex.hpp new file mode 100644 index 00000000..65db0a16 --- /dev/null +++ b/src/common/utils/named_mutex.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace utils +{ + class named_mutex + { + public: + named_mutex(const std::string& name); + ~named_mutex(); + + named_mutex(named_mutex&&) = delete; + named_mutex(const named_mutex&) = delete; + named_mutex& operator=(named_mutex&&) = delete; + named_mutex& operator=(const named_mutex&) = delete; + + void lock() const; + bool try_lock(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) const; + void unlock() const noexcept; + + private: + void* handle_{}; + }; +} diff --git a/src/common/utils/properties.cpp b/src/common/utils/properties.cpp new file mode 100644 index 00000000..f0ee3775 --- /dev/null +++ b/src/common/utils/properties.cpp @@ -0,0 +1,172 @@ +#include "properties.hpp" + +#include "finally.hpp" + +#include +#include +#include "rapidjson/filereadstream.h" +#include "rapidjson/filewritestream.h" +#include "rapidjson/encodedstream.h" + +#include "io.hpp" +#include "com.hpp" +#include "string.hpp" + +namespace utils::properties +{ + namespace + { + typedef rapidjson::GenericDocument> WDocument; + typedef rapidjson::GenericValue> WValue; + + typedef rapidjson::EncodedOutputStream, rapidjson::FileWriteStream> OutputStream; + typedef rapidjson::EncodedInputStream, rapidjson::FileReadStream> InputStream; + + std::filesystem::path get_properties_folder() + { + static auto props = get_appdata_path() / "user"; + return props; + } + + std::filesystem::path get_properties_file() + { + static auto props = get_properties_folder() / "properties.json"; + return props; + } + + WDocument load_properties() + { + WDocument default_doc{}; + default_doc.SetObject(); + + char read_buffer[256]; // Raw buffer for reading + + const std::wstring& props = get_properties_file(); + + FILE* fp; + auto err = _wfopen_s(&fp, props.data(), L"rb"); + if (err || !fp) + { + return default_doc; + } + + // This will handle the BOM + rapidjson::FileReadStream bis(fp, read_buffer, sizeof(read_buffer)); + InputStream eis(bis); + + WDocument doc{}; + const rapidjson::ParseResult result = doc.ParseStream>(eis); + + fclose(fp); + + if (!result || !doc.IsObject()) + { + return default_doc; + } + + return doc; + } + + void store_properties(const WDocument& doc) + { + char write_buffer[256]; // Raw buffer for writing + + const std::wstring& props = get_properties_file(); + io::create_directory(get_properties_folder()); + + FILE* fp; + auto err = _wfopen_s(&fp, props.data(), L"wb"); + if (err || !fp) + { + return; + } + + rapidjson::FileWriteStream bos(fp, write_buffer, sizeof(write_buffer)); + OutputStream eos(bos, true); // Write BOM + + rapidjson::Writer, rapidjson::UTF16LE<>> writer(eos); + doc.Accept(writer); + + fclose(fp); + } + } + + std::filesystem::path get_appdata_path() + { + PWSTR path; + if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) + { + throw std::runtime_error("Failed to read APPDATA path!"); + } + + auto _ = utils::finally([&path] + { + CoTaskMemFree(path); + }); + + static auto appdata = std::filesystem::path(path) / "boiii"; + return appdata; + } + + std::unique_lock lock() + { + static named_mutex mutex{"boiii-properties-lock"}; + return std::unique_lock{mutex}; + } + + std::optional load(const std::wstring& name) + { + const auto _ = lock(); + const auto doc = load_properties(); + + if (!doc.HasMember(name)) + { + return {}; + } + + const auto& value = doc[name]; + if (!value.IsString()) + { + return {}; + } + + return {std::wstring{value.GetString()}}; + } + + std::optional load(const std::string& name) + { + const auto result = load(string::convert(name)); + if (!result) + { + return {}; + } + + return {string::convert(*result)}; + } + + void store(const std::wstring& name, const std::wstring& value) + { + const auto _ = lock(); + auto doc = load_properties(); + + while (doc.HasMember(name)) + { + doc.RemoveMember(name); + } + + WValue key{}; + key.SetString(name, doc.GetAllocator()); + + WValue member{}; + member.SetString(value, doc.GetAllocator()); + + doc.AddMember(key, member, doc.GetAllocator()); + + store_properties(doc); + } + + void store(const std::string& name, const std::string& value) + { + store(string::convert(name), string::convert(value)); + } +} diff --git a/src/common/utils/properties.hpp b/src/common/utils/properties.hpp new file mode 100644 index 00000000..4fcb0500 --- /dev/null +++ b/src/common/utils/properties.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "named_mutex.hpp" +#include +#include +#include + +namespace utils::properties +{ + std::filesystem::path get_appdata_path(); + + std::unique_lock lock(); + + std::optional load(const std::wstring& name); + std::optional load(const std::string& name); + + void store(const std::wstring& name, const std::wstring& value); + void store(const std::string& name, const std::string& value); +}