From 551bfdea50c0961c2de40538365dc87de9acc372 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 6 Mar 2023 18:49:28 +0100 Subject: [PATCH 01/10] Add discord notifications --- .github/workflows/build.yml | 6 +++--- .github/workflows/discord-notify.yml | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/discord-notify.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d0abf350..cc7c7513 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,7 @@ jobs: name: Deploy artifacts needs: build runs-on: ubuntu-latest - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') + if: github.repository_owner == 'momo5502' && github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') steps: - name: Setup environment run: echo "BOIII_MASTER_PATH=${{ secrets.BOIII_MASTER_SSH_PATH }}" >> $GITHUB_ENV @@ -96,7 +96,7 @@ jobs: uses: actions/download-artifact@v3.0.2 with: name: Release Binary - + - name: Download Release data artifacts uses: actions/download-artifact@v3.0.2 with: @@ -107,7 +107,7 @@ jobs: uses: shimataro/ssh-key-action@v2.5.0 with: key: ${{ secrets.BOIII_MASTER_SSH_PRIVATE_KEY }} - known_hosts: 'just-a-placeholder-so-we-dont-get-errors' + known_hosts: "just-a-placeholder-so-we-dont-get-errors" - name: Add known hosts run: ssh-keyscan -H ${{ secrets.BOIII_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml new file mode 100644 index 00000000..1e1cbfe7 --- /dev/null +++ b/.github/workflows/discord-notify.yml @@ -0,0 +1,17 @@ +name: Notify Discord + +on: + push: + branches: + - "*" + issues: + +jobs: + notify: + runs-on: ubuntu-latest + if: github.repository_owner == 'momo5502' + steps: + - name: Send notification to Discord + uses: Ilshidur/action-discord@master + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_CI_BOT_WEBHOOK }} From 39bb77b780019e70ca55dc55cf6e7aae09b62b79 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 6 Mar 2023 18:42:52 +0100 Subject: [PATCH 02/10] Fix reconnect command This fixes #160 --- src/client/component/party.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 365b134b..4d7e1110 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -183,14 +183,18 @@ namespace party void connect_stub(const char* address) { - const auto target = network::address_from_string(address); - if (target.type == game::NA_BAD) + if (address) { - return; + const auto target = network::address_from_string(address); + if (target.type == game::NA_BAD) + { + return; + } + + connect_host = target; } - connect_host = target; - query_server(target, handle_connect_query_response); + query_server(connect_host, handle_connect_query_response); } void send_server_query(server_query& query) From 8bfcfa3f18655b08ef3a6c04dd971b0ba924b1e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 18:15:16 +0000 Subject: [PATCH 03/10] Bump deps/rapidjson from `012be85` to `083f359` Bumps [deps/rapidjson](https://github.com/Tencent/rapidjson) from `012be85` to `083f359`. - [Release notes](https://github.com/Tencent/rapidjson/releases) - [Commits](https://github.com/Tencent/rapidjson/compare/012be8528783cdbf4b7a9e64f78bd8f056b97e24...083f359f5c36198accc2b9360ce1e32a333231d9) --- updated-dependencies: - dependency-name: deps/rapidjson dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/rapidjson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/rapidjson b/deps/rapidjson index 012be852..083f359f 160000 --- a/deps/rapidjson +++ b/deps/rapidjson @@ -1 +1 @@ -Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24 +Subproject commit 083f359f5c36198accc2b9360ce1e32a333231d9 From d6ae9dc752e8707e58b09e74f911457c1a3b672f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 18:15:21 +0000 Subject: [PATCH 04/10] Bump deps/asmjit from `915186f` to `1098b7d` Bumps [deps/asmjit](https://github.com/asmjit/asmjit) from `915186f` to `1098b7d`. - [Release notes](https://github.com/asmjit/asmjit/releases) - [Commits](https://github.com/asmjit/asmjit/compare/915186f6c5c2f5a4638e5cb97ccc23d741521a64...1098b7d8873777f281e54a84a2b422fec30d91d4) --- updated-dependencies: - dependency-name: deps/asmjit dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/asmjit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/asmjit b/deps/asmjit index 915186f6..1098b7d8 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit 915186f6c5c2f5a4638e5cb97ccc23d741521a64 +Subproject commit 1098b7d8873777f281e54a84a2b422fec30d91d4 From 83399572a4107cfb3498084c6638bbbcb588052b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 18:15:37 +0000 Subject: [PATCH 05/10] Bump deps/curl from `ad4997e` to `93eefa6` Bumps [deps/curl](https://github.com/curl/curl) from `ad4997e` to `93eefa6`. - [Release notes](https://github.com/curl/curl/releases) - [Commits](https://github.com/curl/curl/compare/ad4997e5b289c97724fdcbaeb3c8c50222a757c4...93eefa6ba134aceef4f6cd51fc602319b382e629) --- updated-dependencies: - dependency-name: deps/curl dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/curl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/curl b/deps/curl index ad4997e5..93eefa6b 160000 --- a/deps/curl +++ b/deps/curl @@ -1 +1 @@ -Subproject commit ad4997e5b289c97724fdcbaeb3c8c50222a757c4 +Subproject commit 93eefa6ba134aceef4f6cd51fc602319b382e629 From a9614cdd4d1b45ff2a5baaf8e73bff16278792fb Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Tue, 7 Mar 2023 16:59:19 +0100 Subject: [PATCH 06/10] Kill voice chat --- src/client/component/network.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index 8f035ceb..2ae9263a 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -265,6 +265,9 @@ namespace network // Kill lobby system handle_packet_internal_hook.create(game::select(0x141EF7FE0, 0x1404A5B90), &handle_packet_internal_stub); + + // Kill voice chat + utils::hook::nop(game::select(0x14134CDB3, 0x14018EB90), 9); } }; } From 64e0e05e1e2c9366ac99beac032338b030c8dd5e Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Tue, 7 Mar 2023 17:01:51 +0100 Subject: [PATCH 07/10] Properly kill voice chat --- src/client/component/network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index 2ae9263a..d1f3c324 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -267,7 +267,7 @@ namespace network handle_packet_internal_hook.create(game::select(0x141EF7FE0, 0x1404A5B90), &handle_packet_internal_stub); // Kill voice chat - utils::hook::nop(game::select(0x14134CDB3, 0x14018EB90), 9); + utils::hook::set(game::select(0x141359310, 0x14018FE40), 0xC3C03148); } }; } From dbee7d974154082f1d4a3247d74fef2bfc15f08b Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Tue, 7 Mar 2023 18:33:27 +0100 Subject: [PATCH 08/10] Add Dvar_RegisterString --- src/client/game/symbols.hpp | 49 +++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 8365deac..d9b7ac1b 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -10,7 +10,8 @@ namespace game // CL WEAK symbol CL_ConnectFromLobby + int numPrivateSlots, const char* mapname, const char* gametype, + const char* somethingWithUserMaps)> CL_ConnectFromLobby {0x14134C570}; // Game @@ -24,7 +25,9 @@ namespace game WEAK symbol Com_SessionMode_SetNetworkMode{0x1420F75B0, 0x140500B80}; WEAK symbol Com_SessionMode_SetGameMode{0x1420F7570, 0x140500B40}; WEAK symbol Com_SessionMode_SetMode{0x1420F7570}; - WEAK symbol Com_GametypeSettings_SetGametype{0x1420F5980}; + WEAK symbol Com_GametypeSettings_SetGametype{ + 0x1420F5980 + }; WEAK symbol Com_IsRunningUILevel{0x142148350}; WEAK symbol Com_SwitchMode{ 0x14214A4D0 @@ -79,7 +82,7 @@ namespace game WEAK symbol Sys_IsDatabaseReady{0x142183A60}; // Unnamed - WEAK symbol CopyString{0x1422AC220, 0x14056BD70}; + WEAK symbol CopyString{0x1422AC220, 0x14056BD70}; // Dvar WEAK symbol Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890}; @@ -90,9 +93,14 @@ namespace game WEAK symbol Dvar_GetString{0x1422BF590, 0x140575E30}; WEAK symbol Dvar_DisplayableValue{0x1422BC080}; WEAK symbol Dvar_GetBool{0x1422BCED0}; - WEAK symbol Dvar_RegisterBool{ + WEAK symbol Dvar_RegisterBool{ 0x1422D0900 }; + WEAK symbol Dvar_RegisterString{ + 0x1422D0B70 + }; WEAK symbol Dvar_ForEach{0x1422BCD00}; WEAK symbol Dvar_SetFromStringByName{ 0x1422C7500 @@ -103,7 +111,7 @@ namespace game WEAK symbol UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50}; WEAK symbol UI_CoD_Shutdown{0x141F32E10, 0x0}; WEAK symbol UI_AddMenu{0x1427018F0, 0x0}; - WEAK symbol UI_CoD_GetRootNameForController{0x141F28940, 0x0}; + WEAK symbol UI_CoD_GetRootNameForController{0x141F28940, 0x0}; WEAK symbol Lua_CoD_LoadLuaFile{0x141F11A20, 0x0}; WEAK symbol CG_LUIHUDRestart{0x140F7E970}; WEAK symbol CL_CheckKeepDrawingConnectScreen{0x1413CCAE0}; @@ -117,7 +125,8 @@ namespace game }; WEAK symbol Scr_GetNumParam{0x0, 0x140171320}; - WEAK symbol Cinematic_StartPlayback{0x1412BE3A0}; + WEAK symbol Cinematic_StartPlayback{0x1412BE3A0}; WEAK symbol Cinematic_StopPlayback{0x1412BEA70}; // Rendering @@ -131,10 +140,12 @@ namespace game WEAK symbol SV_AddTestClient{0x142248F40, 0x14052E3E0}; WEAK symbol SV_SendServerCommand{0x0, 0x140537F10}; WEAK symbol SV_IsTestClient{0x14224AB60, 0x14052FF40}; - WEAK symbol SV_SpawnServer{0x1422528C0, 0x140535B20}; + WEAK symbol SV_SpawnServer{ + 0x1422528C0, 0x140535B20 + }; // Utils - WEAK symbol I_CleanStr{0x1422E9050, 0x140580E80}; + WEAK symbol I_CleanStr{0x1422E9050, 0x140580E80}; // Variables WEAK symbol cmd_functions{0x15689DF58, 0x14946F860}; @@ -176,18 +187,26 @@ namespace game namespace hks { - WEAK symbol lua_state {0x159C76D88, 0x14858C408}; + WEAK symbol lua_state{0x159C76D88, 0x14858C408}; WEAK symbol hksi_lua_pushlstring{0x140A18430, 0x1401DE6F0}; - WEAK symbol hks_obj_settable{0x141D4B660, 0x1403F41B0}; - WEAK symbol hks_obj_gettable{0x141D4ABF0, 0x1403F3750}; - WEAK symbol vm_call_internal{0x141D70FE0, 0x140418E40}; - WEAK symbol Hashtable_Create{0x141D3B5F0, 0x1403E46D0}; - WEAK symbol cclosure_Create{0x141D3B7E0, 0x1403E48C0}; + WEAK symbol + hks_obj_settable{0x141D4B660, 0x1403F41B0}; + WEAK symbol + hks_obj_gettable{0x141D4ABF0, 0x1403F3750}; + WEAK symbol vm_call_internal{ + 0x141D70FE0, 0x140418E40 + }; + WEAK symbol Hashtable_Create{ + 0x141D3B5F0, 0x1403E46D0 + }; + WEAK symbol cclosure_Create{0x141D3B7E0, 0x1403E48C0}; WEAK symbol hksi_luaL_ref{0x141D4D1A0, 0x1403F5CF0}; WEAK symbol hksi_luaL_unref{0x141D4D320, 0x1403F5E70}; - WEAK symbol hksi_hksL_loadbuffer{0x141D4BD80, 0x1403F48D0}; + WEAK symbol hksi_hksL_loadbuffer{0x141D4BD80, 0x1403F48D0}; WEAK symbol hksi_lua_getinfo{0x141D4D8D0, 0x1403F64B0}; WEAK symbol hksi_lua_getstack{0x141D4DB90, 0x1403F6770}; WEAK symbol hksi_luaL_error{0x141D4D050, 0x1403F5BA0}; From 73f9859eedb27570f34d7e6cfccbc645bd0f0ecb Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Tue, 7 Mar 2023 18:33:39 +0100 Subject: [PATCH 09/10] Support fetching the windows username --- src/common/utils/nt.cpp | 22 +++++++++++++++++++--- src/common/utils/nt.hpp | 2 ++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/common/utils/nt.cpp b/src/common/utils/nt.cpp index 45f3cebc..cf114350 100644 --- a/src/common/utils/nt.cpp +++ b/src/common/utils/nt.cpp @@ -1,4 +1,7 @@ #include "nt.hpp" + +#include + #include "string.hpp" namespace utils::nt @@ -234,14 +237,14 @@ namespace utils::nt { registry_key new_key{}; if (RegOpenKeyExA(current_key, part.data(), 0, - KEY_ALL_ACCESS, &new_key) == ERROR_SUCCESS) + KEY_ALL_ACCESS, &new_key) == ERROR_SUCCESS) { current_key = std::move(new_key); continue; } if (RegCreateKeyExA(current_key, part.data(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, - nullptr, &new_key, nullptr) != ERROR_SUCCESS) + nullptr, &new_key, nullptr) != ERROR_SUCCESS) { return {}; } @@ -310,7 +313,8 @@ namespace utils::nt GetCurrentDirectoryA(sizeof(current_dir), current_dir); auto* const command_line = GetCommandLineA(); - CreateProcessA(self.get_path().generic_string().data(), command_line, nullptr, nullptr, false, NULL, nullptr, current_dir, + CreateProcessA(self.get_path().generic_string().data(), command_line, nullptr, nullptr, false, NULL, nullptr, + current_dir, &startup_info, &process_info); if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread); @@ -322,4 +326,16 @@ namespace utils::nt TerminateProcess(GetCurrentProcess(), code); _Exit(code); } + + std::string get_user_name() + { + char username[UNLEN + 1]; + DWORD username_len = UNLEN + 1; + if (!GetUserNameA(username, &username_len)) + { + return {}; + } + + return std::string(username, username_len - 1); + } } diff --git a/src/common/utils/nt.hpp b/src/common/utils/nt.hpp index 4ca36860..5fff175d 100644 --- a/src/common/utils/nt.hpp +++ b/src/common/utils/nt.hpp @@ -252,4 +252,6 @@ namespace utils::nt void relaunch_self(); __declspec(noreturn) void terminate(uint32_t code = 0); + + std::string get_user_name(); } From 81d4cc47b47d9c2c60f768183c459d6e8179a6bb Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Tue, 7 Mar 2023 19:23:08 +0100 Subject: [PATCH 10/10] Prepare overwriting the player name Step 1 for #47 --- src/client/component/name.cpp | 103 +++++++++++++ src/client/component/name.hpp | 6 + src/client/component/steam_proxy.cpp | 4 +- src/client/component/steam_proxy.hpp | 2 + src/client/loader/component_interface.hpp | 2 + src/client/steam/interfaces/friends.cpp | 4 +- src/client/updater/file_updater.cpp | 4 +- src/common/utils/concurrency.hpp | 6 + src/common/utils/named_mutex.cpp | 44 ++++++ src/common/utils/named_mutex.hpp | 26 ++++ src/common/utils/properties.cpp | 172 ++++++++++++++++++++++ src/common/utils/properties.hpp | 19 +++ 12 files changed, 387 insertions(+), 5 deletions(-) create mode 100644 src/client/component/name.cpp create mode 100644 src/client/component/name.hpp create mode 100644 src/common/utils/named_mutex.cpp create mode 100644 src/common/utils/named_mutex.hpp create mode 100644 src/common/utils/properties.cpp create mode 100644 src/common/utils/properties.hpp 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); +}