From 5a8367d7d84c20b214df8565d6b1bdf04d316bae Mon Sep 17 00:00:00 2001 From: HardCPP Date: Wed, 6 Aug 2025 22:57:25 +0200 Subject: [PATCH] Version 6.4.1 for BS 1.40.8 --- include/git_info.h | 2 +- mod.json | 28 +- mod.template.json | 5 +- qpm.json | 24 +- qpm.shared.json | 106 ++-- shared/CP_SDK/CPConfig.hpp | 6 +- shared/CP_SDK/ChatPlexService.hpp | 119 ++++ shared/CP_SDK/Network/IWebClient.hpp | 82 +-- shared/CP_SDK/Network/JsonRPCClient.hpp | 90 +++ shared/CP_SDK/Network/JsonRPCResult.hpp | 53 ++ shared/CP_SDK/Network/WebClientCore.hpp | 183 ++++++ shared/CP_SDK/Network/WebClientUnity.hpp | 143 ++--- shared/CP_SDK/Network/WebContent.hpp | 8 +- shared/CP_SDK/Network/WebResponse.hpp | 36 +- shared/CP_SDK/UI/Views/SettingsLeftView.hpp | 28 +- shared/CP_SDK/Utils/Il2cpp.hpp | 6 - shared/CP_SDK/Utils/Json.hpp | 3 +- src/CP_SDK/CPConfig.cpp | 2 + src/CP_SDK/ChatPlexSDK.cpp | 6 +- src/CP_SDK/ChatPlexService.cpp | 318 +++++++++++ src/CP_SDK/Network/JsonRPCClient.cpp | 190 +++++++ src/CP_SDK/Network/JsonRPCResult.cpp | 49 ++ src/CP_SDK/Network/WebClientCore.cpp | 594 ++++++++++++++++++++ src/CP_SDK/Network/WebClientUnity.cpp | 255 +++++---- src/CP_SDK/Network/WebContent.cpp | 8 + src/CP_SDK/Network/WebResponse.cpp | 46 +- src/CP_SDK/UI/Views/SettingsLeftView.cpp | 184 +++++- src/CP_SDK/Utils/Il2cpp.cpp | 2 + src/CP_SDK_BS/Game/LevelSelection.cpp | 12 +- 29 files changed, 2243 insertions(+), 345 deletions(-) create mode 100644 shared/CP_SDK/ChatPlexService.hpp create mode 100644 shared/CP_SDK/Network/JsonRPCClient.hpp create mode 100644 shared/CP_SDK/Network/JsonRPCResult.hpp create mode 100644 shared/CP_SDK/Network/WebClientCore.hpp create mode 100644 src/CP_SDK/ChatPlexService.cpp create mode 100644 src/CP_SDK/Network/JsonRPCClient.cpp create mode 100644 src/CP_SDK/Network/JsonRPCResult.cpp create mode 100644 src/CP_SDK/Network/WebClientCore.cpp diff --git a/include/git_info.h b/include/git_info.h index 5c0b8ca..8001fe1 100644 --- a/include/git_info.h +++ b/include/git_info.h @@ -1,5 +1,5 @@ #pragma once #define GIT_USER "HardCPP" #define GIT_BRANCH "dev" -#define GIT_COMMIT 0xc20158d +#define GIT_COMMIT 0x51e3151 #define GIT_MODIFIED 1 diff --git a/mod.json b/mod.json index 6dc76ac..5b4d3dd 100644 --- a/mod.json +++ b/mod.json @@ -5,42 +5,42 @@ "id": "chatplex-sdk-bs", "modloader": "Scotland2", "author": "HardCPP", - "version": "6.4.0", + "version": "6.4.1", "packageId": "com.beatgames.beatsaber", - "packageVersion": "1.40.4_5283", + "packageVersion": "1.40.8_7379", "description": "ChatPlex BeatSaber modding SDK (Dependence for other mods)", "coverImage": "cover.png", "dependencies": [ { - "version": "^6.4.1", + "version": "^6.4.2", "id": "beatsaber-hook", - "downloadIfMissing": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/beatsaber-hook.qmod" + "downloadIfMissing": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.2/beatsaber-hook.qmod" }, { - "version": "^0.18.2", + "version": "^0.18.3", "id": "custom-types", - "downloadIfMissing": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/CustomTypes.qmod" + "downloadIfMissing": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.3/CustomTypes.qmod" }, { - "version": "^0.4.51", + "version": "^0.4.55", "id": "bsml", - "downloadIfMissing": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.51/BSML.qmod" + "downloadIfMissing": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.55/BSML.qmod" }, { - "version": "^1.1.20", + "version": "^1.1.24", "id": "songcore", - "downloadIfMissing": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/SongCore.qmod" + "downloadIfMissing": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.24/SongCore.qmod" }, { - "version": "^4.6.1", + "version": "^4.6.4", "id": "paper2_scotland2", - "downloadIfMissing": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.2/paper2_scotland2.qmod" + "downloadIfMissing": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.4/paper2_scotland2.qmod" } ], - "modFiles": [ + "modFiles": [], + "lateModFiles": [ "libchatplex-sdk-bs.so" ], - "lateModFiles": [], "libraryFiles": [], "fileCopies": [], "copyExtensions": [] diff --git a/mod.template.json b/mod.template.json index 6eabf18..a4e3f1a 100644 --- a/mod.template.json +++ b/mod.template.json @@ -6,11 +6,12 @@ "author": "HardCPP", "version": "${version}", "packageId": "com.beatgames.beatsaber", - "packageVersion": "1.40.4_5283", + "packageVersion": "1.40.8_7379", "description": "ChatPlex BeatSaber modding SDK (Dependence for other mods)", "coverImage": "cover.png", "dependencies": [], - "modFiles": ["${binary}"], + "modFiles": [], + "lateModFiles": ["${binary}"], "libraryFiles": [], "fileCopies": [], "copyExtensions": [] diff --git a/qpm.json b/qpm.json index 88070e9..4b98143 100644 --- a/qpm.json +++ b/qpm.json @@ -5,7 +5,7 @@ "info": { "name": "ChatPlexSDK-BS", "id": "chatplex-sdk-bs", - "version": "6.4.0", + "version": "6.4.1", "url": "https://github.com/hardcpp/QuestChatPlexSDK-BS", "additionalData": { "overrideSoName": "libchatplex-sdk-bs.so", @@ -47,17 +47,17 @@ "dependencies": [ { "id": "beatsaber-hook", - "versionRange": "^6.4.1", + "versionRange": "^6.4.2", "additionalData": {} }, { "id": "bs-cordl", - "versionRange": "^4004.0.0", + "versionRange": "^4008.*", "additionalData": {} }, { "id": "custom-types", - "versionRange": "^0.18.2", + "versionRange": "^0.18.3", "additionalData": {} }, { @@ -70,19 +70,14 @@ }, { "id": "bsml", - "versionRange": "^0.4.51", + "versionRange": "^0.4.55", "additionalData": { "private": true } }, - { - "id": "libil2cpp", - "versionRange": "^0.4.0", - "additionalData": {} - }, { "id": "songcore", - "versionRange": "^1.1.20", + "versionRange": "^1.1.24", "additionalData": { "private": true } @@ -96,13 +91,18 @@ }, { "id": "paper2_scotland2", - "versionRange": "^4.6.1", + "versionRange": "^4.6.4", "additionalData": {} }, { "id": "kaleb", "versionRange": "^0.1.9", "additionalData": {} + }, + { + "id": "libcurl", + "versionRange": "=8.5.0", + "additionalData": {} } ] } \ No newline at end of file diff --git a/qpm.shared.json b/qpm.shared.json index 321cd6e..81aebe6 100644 --- a/qpm.shared.json +++ b/qpm.shared.json @@ -7,7 +7,7 @@ "info": { "name": "ChatPlexSDK-BS", "id": "chatplex-sdk-bs", - "version": "6.4.0", + "version": "6.4.1", "url": "https://github.com/hardcpp/QuestChatPlexSDK-BS", "additionalData": { "overrideSoName": "libchatplex-sdk-bs.so", @@ -49,17 +49,17 @@ "dependencies": [ { "id": "beatsaber-hook", - "versionRange": "^6.4.1", + "versionRange": "^6.4.2", "additionalData": {} }, { "id": "bs-cordl", - "versionRange": "^4004.0.0", + "versionRange": "4008.*", "additionalData": {} }, { "id": "custom-types", - "versionRange": "^0.18.2", + "versionRange": "^0.18.3", "additionalData": {} }, { @@ -72,19 +72,14 @@ }, { "id": "bsml", - "versionRange": "^0.4.51", + "versionRange": "^0.4.55", "additionalData": { "private": true } }, - { - "id": "libil2cpp", - "versionRange": "^0.4.0", - "additionalData": {} - }, { "id": "songcore", - "versionRange": "^1.1.20", + "versionRange": "^1.1.24", "additionalData": { "private": true } @@ -98,13 +93,18 @@ }, { "id": "paper2_scotland2", - "versionRange": "^4.6.1", + "versionRange": "^4.6.4", "additionalData": {} }, { "id": "kaleb", "versionRange": "^0.1.9", "additionalData": {} + }, + { + "id": "libcurl", + "versionRange": "=8.5.0", + "additionalData": {} } ] }, @@ -112,27 +112,27 @@ { "dependency": { "id": "bsml", - "versionRange": "=0.4.51", + "versionRange": "=0.4.55", "additionalData": { - "soLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.51/libbsml.so", - "debugSoLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.51/debug_libbsml.so", + "soLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.55/libbsml.so", + "debugSoLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.55/debug_libbsml.so", "overrideSoName": "libbsml.so", - "modLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.51/BSML.qmod", - "branchName": "version/v0_4_51", + "modLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.55/BSML.qmod", + "branchName": "version/v0_4_55", "cmake": true } }, - "version": "0.4.51" + "version": "0.4.55" }, { "dependency": { "id": "paper2_scotland2", - "versionRange": "=4.6.2", + "versionRange": "=4.6.4", "additionalData": { - "soLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.2/libpaper2_scotland2.so", + "soLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.4/libpaper2_scotland2.so", "overrideSoName": "libpaper2_scotland2.so", - "modLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.2/paper2_scotland2.qmod", - "branchName": "version/v4_6_2", + "modLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.4/paper2_scotland2.qmod", + "branchName": "version/v4_6_4", "compileOptions": { "systemIncludes": [ "shared/utfcpp/source" @@ -141,7 +141,7 @@ "cmake": false } }, - "version": "4.6.2" + "version": "4.6.4" }, { "dependency": { @@ -162,13 +162,13 @@ { "dependency": { "id": "custom-types", - "versionRange": "=0.18.2", + "versionRange": "=0.18.3", "additionalData": { - "soLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/libcustom-types.so", - "debugSoLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/debug_libcustom-types.so", + "soLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.3/libcustom-types.so", + "debugSoLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.3/debug_libcustom-types.so", "overrideSoName": "libcustom-types.so", - "modLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/CustomTypes.qmod", - "branchName": "version/v0_18_2", + "modLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.3/CustomTypes.qmod", + "branchName": "version/v0_18_3", "compileOptions": { "cppFlags": [ "-Wno-invalid-offsetof" @@ -177,7 +177,7 @@ "cmake": true } }, - "version": "0.18.2" + "version": "0.18.3" }, { "dependency": { @@ -199,10 +199,10 @@ { "dependency": { "id": "bs-cordl", - "versionRange": "=4004.0.0", + "versionRange": "=4008.0.0", "additionalData": { "headersOnly": true, - "branchName": "version/v4004_0_0", + "branchName": "version/v4008_0_0", "compileOptions": { "includePaths": [ "include" @@ -218,7 +218,7 @@ } } }, - "version": "4004.0.0" + "version": "4008.0.0" }, { "dependency": { @@ -232,35 +232,53 @@ }, "version": "0.3.0" }, + { + "dependency": { + "id": "libcurl", + "versionRange": "=8.5.0", + "additionalData": { + "staticLinking": true, + "soLink": "https://github.com/darknight1050/openssl-curl-android/releases/download/v8.5.0/libcurl.a", + "overrideSoName": "libcurl.a", + "branchName": "version-v8.5.0" + } + }, + "version": "8.5.0" + }, { "dependency": { "id": "songcore", - "versionRange": "=1.1.20", + "versionRange": "=1.1.24", "additionalData": { - "soLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/libsongcore.so", - "debugSoLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/debug_libsongcore.so", + "soLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.24/libsongcore.so", + "debugSoLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.24/debug_libsongcore.so", "overrideSoName": "libsongcore.so", - "modLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/SongCore.qmod", - "branchName": "version/v1_1_20", + "modLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.24/SongCore.qmod", + "branchName": "version/v1_1_24", "cmake": true } }, - "version": "1.1.20" + "version": "1.1.24" }, { "dependency": { "id": "beatsaber-hook", - "versionRange": "=6.4.1", + "versionRange": "=6.4.2", "additionalData": { - "soLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/libbeatsaber-hook.so", - "debugSoLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/debug_libbeatsaber-hook.so", + "soLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.2/libbeatsaber-hook.so", + "debugSoLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.2/debug_libbeatsaber-hook.so", "overrideSoName": "libbeatsaber-hook.so", - "modLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/beatsaber-hook.qmod", - "branchName": "version/v6_4_1", + "modLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.2/beatsaber-hook.qmod", + "branchName": "version/v6_4_2", + "compileOptions": { + "cppFlags": [ + "-Wno-extra-qualification" + ] + }, "cmake": true } }, - "version": "6.4.1" + "version": "6.4.2" }, { "dependency": { diff --git a/shared/CP_SDK/CPConfig.hpp b/shared/CP_SDK/CPConfig.hpp index 1cfd370..e1cf727 100644 --- a/shared/CP_SDK/CPConfig.hpp +++ b/shared/CP_SDK/CPConfig.hpp @@ -1,6 +1,7 @@ #pragma once #include "Config/JsonConfig.hpp" +#include namespace CP_SDK { @@ -10,8 +11,9 @@ namespace CP_SDK { CP_SDK_CONFIG_JSONCONFIG_INSTANCE_DECL(CPConfig); public: - bool FirstRun = true; - bool FirstChatServiceRun = true; + bool FirstRun = true; + bool FirstChatServiceRun = true; + std::u16string ChatPlexServiceToken = u""; protected: /// @brief Reset config to default diff --git a/shared/CP_SDK/ChatPlexService.hpp b/shared/CP_SDK/ChatPlexService.hpp new file mode 100644 index 0000000..cb6d11d --- /dev/null +++ b/shared/CP_SDK/ChatPlexService.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include "Network/JsonRPCResult.hpp" +#include "Network/JsonRPCClient.hpp" +#include "Utils/Event.hpp" +#include "Utils/Il2cpp.hpp" +#include "Utils/Delegate.hpp" + +#include +#include +#include +#include + +namespace CP_SDK { + + namespace _u + { + using namespace il2cpp_utils; + using namespace System; + using namespace UnityEngine; + } + + namespace _v + { + using namespace CP_SDK::Network; + using namespace CP_SDK::Utils; + } + + /// @brief ChatPlexService + class CP_SDK_EXPORT ChatPlexService + { + CP_SDK_NO_DEF_CTORS(ChatPlexService); + + public: + enum class EState + { + Disconnected, + LinkRequest, + LinkWait, + Connecting, + + Connected, + + Error + }; + + private: + static bool m_ThreadCondition; + static _u::il2cpp_aware_thread* m_Thread; + static EState m_State; + static _v::WebClientCore::Ptr m_WebClientCore; + static _v::JsonRPCClient::Ptr m_JsonRPCClient; + static std::u16string m_LinkRequestID; + static std::u16string m_LinkCode; + static std::u16string m_LastError; + static std::u16string m_ActiveSubscription; + static std::vector m_UnlockedFeatures; + static std::queue<_v::Action<>> m_OnTokenReadyQueue; + static std::mutex m_OnTokenReadyQueueMutex; + static std::u16string m_DeviceName; + + public: + static const EState State(); + static const std::u16string_view Token(); + static const std::u16string_view LinkCode(); + static const std::u16string_view LastError(); + static const std::u16string_view ActiveSubscription(); + static const std::vector& UnlockedFeatures(); + + static _v::Event StateChanged; + + public: + /// @brief Init the service + static void Init(); + /// @brief Release the service + static void Release(); + + public: + /// @brief Add a callback to be called when the token is ready (call immediatly if ready) + /// @param action Callback to be caled + static void OnTokenReady(_v::CActionRef<> action); + + public: + /// @brief Start linking procedure + static void StartLinking(); + /// @brief Stop linking procedure + static void StopLinking(); + /// @brief Refresh the session + static void Refresh(); + /// @brief Disconnect and erase the saved connected application token + static void Disconnect(); + + private: + /// @brief Change state and notify listenners + static void ChangeState(const EState newState); + /// @brief Fire on token ready actions + static void FireOnTokenReady(); + + private: + /// @brief Thread function + static void ThreadRunner(); + + private: + /// @brief Is RPC call result a success result? + /// @param rpcResult Result of the RPC command + /// @return True if success + static bool IsRPCSuccess(_v::JsonRPCResult::Ptr& rpcResult); + + private: + /// @brief When we are Authed + /// @param rpcResult Result of the RPC command + static void OnAuthed(_v::JsonRPCResult::Ptr& rpcResult); + /// @brief On error received + /// @param rpcResult Result of the RPC command + static void OnError(_v::JsonRPCResult::Ptr& rpcResult); + + }; + +} ///< namespace CP_SDK \ No newline at end of file diff --git a/shared/CP_SDK/Network/IWebClient.hpp b/shared/CP_SDK/Network/IWebClient.hpp index 29a9a28..67f7767 100644 --- a/shared/CP_SDK/Network/IWebClient.hpp +++ b/shared/CP_SDK/Network/IWebClient.hpp @@ -24,57 +24,67 @@ namespace CP_SDK::Network { { CP_SDK_NO_COPYMOVE_CTORS(IWebClient); + public: + using IPtr = std::shared_ptr; + protected: /// @brief Constructor IWebClient() = default; public: /// @brief Get header - /// @param p_Name Header name - virtual std::u16string GetHeader(std::u16string_view p_Name) = 0; + /// @param name Header name + virtual std::u16string GetHeader(std::u16string_view name) = 0; /// @brief Set header - /// @param p_Name Header name - /// @param p_Value Header value - virtual void SetHeader(std::u16string_view p_Name, std::u16string_view p_Value) = 0; + /// @param name Header name + /// @param value Header value + virtual void SetHeader(std::u16string_view name, std::u16string_view value) = 0; /// @brief Remove header - /// @param p_Name Header name - virtual void RemoveHeader(std::u16string_view p_Name) = 0; + /// @param name Header name + virtual void RemoveHeader(std::u16string_view name) = 0; public: /// @brief Do Async GET query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - virtual void GetAsync(std::u16string_view p_URL, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false, _v::CActionRef p_Progress = nullptr) = 0; + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + virtual void GetAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false, _v::CActionRef progress = nullptr) = 0; /// @brief Do Async GET query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - virtual void DownloadAsync(std::u16string_view p_URL, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false, _v::CActionRef p_Progress = nullptr) = 0; + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + virtual void DownloadAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false, _v::CActionRef progress = nullptr) = 0; /// @brief Do Async POST query - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - virtual void PostAsync(std::u16string_view p_URL, const WebContent::Ptr& p_Content, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false) = 0; + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PostAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) = 0; /// @brief Do Async PATCH query - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - virtual void PatchAsync(std::u16string_view p_URL, const WebContent::Ptr& p_Content, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false) = 0; + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PatchAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) = 0; + /// @brief Do Async PUT query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PutAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) = 0; /// @brief Do Async DELETE query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - virtual void DeleteAsync(std::u16string_view p_URL, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false) = 0; + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void DeleteAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) = 0; }; diff --git a/shared/CP_SDK/Network/JsonRPCClient.hpp b/shared/CP_SDK/Network/JsonRPCClient.hpp new file mode 100644 index 0000000..c247a3b --- /dev/null +++ b/shared/CP_SDK/Network/JsonRPCClient.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include "CP_SDK/Utils/Json.hpp" +#include "JsonRPCResult.hpp" +#include "WebClientCore.hpp" +#include + +namespace CP_SDK::Network { + + namespace _v + { + using namespace CP_SDK::Utils; + } + + /// @brief JsonRPCClient + class CP_SDK_EXPORT JsonRPCClient : public std::enable_shared_from_this + { + CP_SDK_NO_COPYMOVE_CTORS(JsonRPCClient); + CP_SDK_PRIV_TAG(); + + private: + WebClientCore::Ptr m_WebClient; + + public: + using Ptr = std::shared_ptr; + + /// @brief Constructor + /// @param webClient Web client instance + JsonRPCClient(CP_SDK_PRIV_TAG_ARG(), WebClientCore::Ptr& webClient); + /// @brief Destructor + ~JsonRPCClient(); + + public: + /// @brief Create + /// @param webClient Raw web response + static Ptr Create(WebClientCore::Ptr& webClient); + + public: + /// @brief Do a RPC request + /// @param method Target method + /// @param parameters Request parameters + /// @param dontRetry Should not retry? + JsonRPCResult::Ptr Request(std::u16string_view method, std::shared_ptr<_v::Json::U16Value> parameters, bool dontRetry = false); + + public: + /// @brief Do a RPC request + /// @param method Target method + /// @param parameters Request parameters + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry? + void RequestAsync( + std::u16string_view method, + std::shared_ptr<_v::Json::U16Value> parameters, + _u::CancellationToken token, + _v::CActionRef callback, + bool dontRetry = false + ); + + private: + /// @brief Do a RPC request + /// @param method Target method + /// @param content Request content + /// @param dontRetry Should not retry? + JsonRPCResult::Ptr DoRequest( + std::u16string method, + std::shared_ptr<_v::Json::U16Document>& content, + bool dontRetry + ); + /// @brief Do a RPC request + /// @param method Target method + /// @param content Request content + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry? + void DoRequestAsync( + std::u16string method, + std::shared_ptr<_v::Json::U16Document>& content, + _u::CancellationToken token, + _v::CActionRef callback, + bool dontRetry + ); + /// @brief Do a RPC request + /// @param method Target method + /// @param rawResponse Web response + JsonRPCResult::Ptr HandleWebResponse(std::u16string method, WebResponse::Ptr& rawResponse); + + }; + +} ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/shared/CP_SDK/Network/JsonRPCResult.hpp b/shared/CP_SDK/Network/JsonRPCResult.hpp new file mode 100644 index 0000000..190b321 --- /dev/null +++ b/shared/CP_SDK/Network/JsonRPCResult.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "../Utils/Json.hpp" +#include "WebResponse.hpp" + +namespace CP_SDK::Network { + + namespace _v + { + using namespace CP_SDK::Utils; + } + + /// @brief JsonRPCResult + class CP_SDK_EXPORT JsonRPCResult + { + CP_SDK_NO_COPYMOVE_CTORS(JsonRPCResult); + CP_SDK_PRIV_TAG(); + + public: + WebResponse::Ptr RawResponse; + std::shared_ptr<_v::Json::U16Document> Result; + std::shared_ptr<_v::Json::U16Document> Error; + + public: + using Ptr = std::shared_ptr; + + /// @brief Constructor + /// @param rawResponse Raw web response + /// @param result Result json + /// @param error Json content + JsonRPCResult( + CP_SDK_PRIV_TAG_ARG(), + WebResponse::Ptr& rawResponse, + std::shared_ptr<_v::Json::U16Document> result, + std::shared_ptr<_v::Json::U16Document> error + ); + /// @brief Destructor + ~JsonRPCResult(); + + public: + /// @brief Create + /// @param rawResponse Raw web response + /// @param result Result json + /// @param error Json content + static Ptr Create( + WebResponse::Ptr& rawResponse, + std::shared_ptr<_v::Json::U16Document> result, + std::shared_ptr<_v::Json::U16Document> error + ); + + }; + +} ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/shared/CP_SDK/Network/WebClientCore.hpp b/shared/CP_SDK/Network/WebClientCore.hpp new file mode 100644 index 0000000..bbc2693 --- /dev/null +++ b/shared/CP_SDK/Network/WebClientCore.hpp @@ -0,0 +1,183 @@ +#pragma once + +#include "IWebClient.hpp" + +#include + +#include +#include + +namespace CP_SDK::Network { + + namespace _u + { + using namespace System; + using namespace System::Threading; + } + namespace _v + { + using namespace CP_SDK::Utils; + } + + /// @brief WebClientCore + class CP_SDK_EXPORT WebClientCore : public IWebClient, public std::enable_shared_from_this + { + CP_SDK_NO_COPYMOVE_CTORS(WebClientCore); + CP_SDK_PRIV_TAG(); + + public: + using Ptr = std::shared_ptr; + + private: + static Ptr m_GlobalClient; + + public: + /// @brief Global client instance + static WebClientCore* GlobalClient(); + + private: + std::u16string m_BaseAddress; + int m_TimeOut; + std::map m_Headers; + std::mutex m_HeadersLock; + + public: + /// @brief Maximum retry attempt + int MaxRetry; + /// @brief Delay between each retry + int RetryInterval; + + public: + /// @brief Constructor + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param keepAlive Should try to keep the connection alive + /// @param forceCacheDiscard Should force server cache discard + WebClientCore(CP_SDK_PRIV_TAG_ARG(), std::u16string_view baseAddress, _u::TimeSpan timeOut, bool keepAlive, bool forceCacheDiscard); + + /// @brief Constructor + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param keepAlive Should try to keep the connection alive + /// @param forceCacheDiscard Should force server cache discard + static Ptr Make(std::u16string_view baseAddress, _u::TimeSpan timeOut, bool keepAlive = true, bool forceCacheDiscard = false); + + public: + /// @brief Get header + /// @param p_Name Header name + virtual std::u16string GetHeader(std::u16string_view p_Name) override final; + /// @brief Set header + /// @param p_Name Header name + /// @param p_Value Header value + virtual void SetHeader(std::u16string_view p_Name, std::u16string_view p_Value) override final; + /// @brief Remove header + /// @param p_Name Header name + virtual void RemoveHeader(std::u16string_view p_Name) override final; + + public: + /// @brief Do GET query + /// @param url Target URL + /// @param dontRetry Should not retry + /// @param progress Progress reporter + WebResponse::Ptr Get(std::u16string_view url, bool dontRetry = false, _v::CActionRef progress = nullptr); + /// @brief Do GET query + /// @param url Target URL + /// @param dontRetry Should not retry + /// @param progress Progress reporter + WebResponse::Ptr Download(std::u16string_view url, bool dontRetry = false, _v::CActionRef progress = nullptr); + /// @brief Do POST query + /// @param url Target URL + /// @param content Optional content to post + /// @param dontRetry Should not retry + WebResponse::Ptr Post(std::u16string_view url, const WebContent::Ptr& content, bool dontRetry = false); + /// @brief Do PATCH query + /// @param url Target URL + /// @param content Optional content to post + /// @param dontRetry Should not retry + WebResponse::Ptr Patch(std::u16string_view url, const WebContent::Ptr& content, bool dontRetry = false); + /// @brief Do PUT query + /// @param url Target URL + /// @param content Optional content to post + /// @param dontRetry Should not retry + WebResponse::Ptr Put(std::u16string_view url, const WebContent::Ptr& content, bool dontRetry = false); + /// @brief Do DELETE query + /// @param url Target URL + /// @param dontRetry Should not retry + WebResponse::Ptr Delete(std::u16string_view url, bool dontRetry = false); + + public: + /// @brief Do Async GET query + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + virtual void GetAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false, _v::CActionRef progress = nullptr) override final; + /// @brief Do Async GET query + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + virtual void DownloadAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false, _v::CActionRef progress = nullptr) override final; + /// @brief Do Async POST query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PostAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; + /// @brief Do Async PATCH query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PatchAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; + /// @brief Do Async PUT query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PutAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; + /// @brief Do Async DELETE query + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void DeleteAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; + + private: + /// @brief Get URL + /// @param url Request URL + std::u16string GetURL(std::u16string_view url); + /// @brief Safe URL parsing + /// @param url Source URL + std::u16string SafeURL(std::u16string_view url); + + private: + /// @brief Do request + /// @param debugName Method name for logs + /// @param httpMethod Http method + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + static void DoRequest( + Ptr self, + std::u16string debugName, + std::u16string httpMethod, + std::u16string url, + WebContent::Ptr content, + _u::CancellationToken token, + _v::Action callback, + bool dontRetry, + _v::Action progress + ); + + }; + +} ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/shared/CP_SDK/Network/WebClientUnity.hpp b/shared/CP_SDK/Network/WebClientUnity.hpp index c06bb1e..fa1f7ab 100644 --- a/shared/CP_SDK/Network/WebClientUnity.hpp +++ b/shared/CP_SDK/Network/WebClientUnity.hpp @@ -53,96 +53,103 @@ namespace CP_SDK::Network { public: /// @brief Constructor - /// @param p_BaseAddress Base address - /// @param p_TimeOut Requests timeout - /// @param p_ForceCacheDiscard Should force server cache discard - WebClientUnity(CP_SDK_PRIV_TAG_ARG(), std::u16string_view p_BaseAddress, _u::TimeSpan p_TimeOut, bool p_ForceCacheDiscard); + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param forceCacheDiscard Should force server cache discard + WebClientUnity(CP_SDK_PRIV_TAG_ARG(), std::u16string_view baseAddress, _u::TimeSpan timeOut, bool forceCacheDiscard); /// @brief Constructor - /// @param p_BaseAddress Base address - /// @param p_TimeOut Requests timeout - /// @param p_ForceCacheDiscard Should force server cache discard - static Ptr Make(std::u16string_view p_BaseAddress, _u::TimeSpan p_TimeOut, bool p_ForceCacheDiscard = false); + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param forceCacheDiscard Should force server cache discard + static Ptr Make(std::u16string_view baseAddress, _u::TimeSpan timeOut, bool forceCacheDiscard = false); public: /// @brief Get header - /// @param p_Name Header name - virtual std::u16string GetHeader(std::u16string_view p_Name) override final; + /// @param name Header name + virtual std::u16string GetHeader(std::u16string_view name) override final; /// @brief Set header - /// @param p_Name Header name - /// @param p_Value Header value - virtual void SetHeader(std::u16string_view p_Name, std::u16string_view p_Value) override final; + /// @param name Header name + /// @param value Header value + virtual void SetHeader(std::u16string_view name, std::u16string_view value) override final; /// @brief Remove header - /// @param p_Name Header name - virtual void RemoveHeader(std::u16string_view p_Name) override final; + /// @param name Header name + virtual void RemoveHeader(std::u16string_view name) override final; public: /// @brief Do Async GET query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - virtual void GetAsync(std::u16string_view p_URL, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false, _v::CActionRef p_Progress = nullptr) override final; + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + virtual void GetAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false, _v::CActionRef progress = nullptr) override final; /// @brief Do Async GET query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - virtual void DownloadAsync(std::u16string_view p_URL, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false, _v::CActionRef p_Progress = nullptr) override final; + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + virtual void DownloadAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false, _v::CActionRef progress = nullptr) override final; /// @brief Do Async POST query - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - virtual void PostAsync(std::u16string_view p_URL, const WebContent::Ptr& p_Content, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false) override final; + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PostAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; /// @brief Do Async PATCH query - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - virtual void PatchAsync(std::u16string_view p_URL, const WebContent::Ptr& p_Content, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false) override final; + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PatchAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; + /// @brief Do Async PUT query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void PutAsync(std::u16string_view url, const WebContent::Ptr& content, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; /// @brief Do Async DELETE query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - virtual void DeleteAsync(std::u16string_view p_URL, _u::CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry = false) override final; + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + virtual void DeleteAsync(std::u16string_view url, _u::CancellationToken token, _v::CActionRef callback, bool dontRetry = false) override final; private: /// @brief Get URL - /// @param p_URL Request URL - std::u16string GetURL(std::u16string_view p_URL); + /// @param url Request URL + std::u16string GetURL(std::u16string_view url); /// @brief Safe URL parsing - /// @param p_URL Source URL - std::u16string SafeURL(std::u16string_view p_URL); + /// @param url Source URL + std::u16string SafeURL(std::u16string_view url); private: /// @brief Prepare request - /// @param p_Request Request to prepare - /// @param p_IsDownload Is a download request? - void PrepareRequest(_u::UnityWebRequest* p_Request, bool p_IsDownload); + /// @param request Request to prepare + /// @param isDownload Is a download request? + void PrepareRequest(_u::UnityWebRequest* request, bool isDownload); /// @brief Do request - /// @param p_DebugName Method name for logs - /// @param p_HttpMethod Http method - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - static custom_types::Helpers::Coroutine Coroutine_DoRequest(Ptr p_Self, - std::u16string p_DebugName, - std::u16string p_HttpMethod, - std::u16string p_URL, - WebContent::Ptr p_Content, - _u::CancellationToken p_Token, - _v::Action p_Callback, - bool p_DontRetry, - _v::Action p_Progress); + /// @param debugName Method name for logs + /// @param httpMethod Http method + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + static custom_types::Helpers::Coroutine Coroutine_DoRequest(Ptr self, + std::u16string debugName, + std::u16string httpMethod, + std::u16string url, + WebContent::Ptr content, + _u::CancellationToken token, + _v::Action callback, + bool dontRetry, + _v::Action progress); }; diff --git a/shared/CP_SDK/Network/WebContent.hpp b/shared/CP_SDK/Network/WebContent.hpp index 750da47..fb4e6ef 100644 --- a/shared/CP_SDK/Network/WebContent.hpp +++ b/shared/CP_SDK/Network/WebContent.hpp @@ -2,6 +2,7 @@ #include "../Utils/Il2cpp.hpp" #include "../Utils/MonoPtr.hpp" +#include "../Utils/Json.hpp" #include #include @@ -37,8 +38,11 @@ namespace CP_SDK::Network { public: /// @brief Constructor from Json - /// @param p_Content Json content - static Ptr FromJson(std::u16string_view p_Content); + /// @param content Json content + static Ptr FromJson(std::u16string_view content); + /// @brief Constructor from Json + /// @param content Json content + static Ptr FromJson(std::shared_ptr<_v::Json::U16Document>& content); }; diff --git a/shared/CP_SDK/Network/WebResponse.hpp b/shared/CP_SDK/Network/WebResponse.hpp index c89ba58..cc3c140 100644 --- a/shared/CP_SDK/Network/WebResponse.hpp +++ b/shared/CP_SDK/Network/WebResponse.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace CP_SDK::Network { @@ -60,16 +61,21 @@ namespace CP_SDK::Network { using Ptr = std::shared_ptr; /// Constructor - /// @p_Request: Reply status - WebResponse(_u::UnityWebRequest* p_Request); + /// @param request: Reply status + WebResponse(_u::UnityWebRequest* request); + /// @brief Constructor + /// @param curlPerformResult CURL perform result + /// @param curlInstance CURL instance + /// @param data Response data + WebResponse(long curlPerformResult, void* curlInstance, std::vector* data); public: /// @brief Get JObject from serialized JSON - /// @param p_Object Result object + /// @param object Result object /// @return True or false - bool TryAsJObject(std::shared_ptr<_v::Json::U16Document>& p_Object) + bool TryAsJObject(std::shared_ptr<_v::Json::U16Document>& object) { - p_Object = nullptr; + object = nullptr; auto l_New = std::make_shared<_v::Json::U16Document>(); try @@ -77,40 +83,40 @@ namespace CP_SDK::Network { if (!_v::Json::TryFromU16String(BodyString(), *l_New.get())) return false; - p_Object = l_New; + object = l_New; } catch (const std::exception& l_Exception) { - p_Object = nullptr; + object = nullptr; return false; } - return p_Object != nullptr; + return object != nullptr; } /// @brief Get Object from serialized JSON /// @tparam t_Type Object type - /// @param p_Object Result object + /// @param object Result object /// @return True or false template - bool TryGetObject(std::shared_ptr& p_Object) + bool TryGetObject(std::shared_ptr& object) { - p_Object = nullptr; + object = nullptr; try { _v::Json::U16Document l_Document; _v::Json::TryFromU16String(BodyString(), l_Document); - p_Object = std::make_shared(); - p_Object->Unserialize(l_Document); + object = std::make_shared(); + object->Unserialize(l_Document); } catch (const std::exception& l_Exception) { - p_Object = nullptr; + object = nullptr; return false; } - return p_Object != nullptr; + return object != nullptr; } }; diff --git a/shared/CP_SDK/UI/Views/SettingsLeftView.hpp b/shared/CP_SDK/UI/Views/SettingsLeftView.hpp index 6eb9c7d..0a16836 100644 --- a/shared/CP_SDK/UI/Views/SettingsLeftView.hpp +++ b/shared/CP_SDK/UI/Views/SettingsLeftView.hpp @@ -1,7 +1,8 @@ #pragma once -#include "../ViewController.hpp" +#include "../../ChatPlexService.hpp" #include "../../XUI/XUI.hpp" +#include "../ViewController.hpp" namespace CP_SDK::UI::Views { @@ -23,9 +24,34 @@ namespace CP_SDK::UI::Views { CP_SDK_IL2CPP_DECLARE_DTOR_MONOBEHAVIOUR_CHILD(SettingsLeftView); CP_SDK_UI_VIEW_CONTROLLER_INSTANCE(); + private: + _v::XUIText::Ptr m_StatusText; + _v::XUIText::Ptr m_SubscriptionText; + _v::XUIPrimaryButton::Ptr m_PrimaryButton; + _v::XUISecondaryButton::Ptr m_SecondaryButton; + + private: + bool m_IsLinking = false; + private: /// @brief On view creation void OnViewCreation_Impl(); + /// @brief On view deactivation + void OnViewDeactivation_Impl(); + + private: + /// @brief On primary button pressed + void OnPrimaryButtonPressed(); + /// @brief On secondary button pressed + void OnSecondaryButtonPressed(); + /// @brief On Loading cancel + void OnLoadingCancel(); + + private: + /// @brief On ChatPlex service state change + /// @param oldState Old state + /// @param newState New state + void ChatPlexService_StateChanged(ChatPlexService::EState oldState, ChatPlexService::EState newState); }; diff --git a/shared/CP_SDK/Utils/Il2cpp.hpp b/shared/CP_SDK/Utils/Il2cpp.hpp index a148ed4..54e5ad6 100644 --- a/shared/CP_SDK/Utils/Il2cpp.hpp +++ b/shared/CP_SDK/Utils/Il2cpp.hpp @@ -3,18 +3,12 @@ #define __CP_SDK_U16STR(__mX) u##__mX #define CP_SDK_U16STR(__mX) __CP_SDK_U16STR(#__mX) -#include "../Logging/PaperLogger.hpp" #include "Internals/Il2cpp_enum.hpp" #include "Internals/Il2cpp_customtype.hpp" #include "Internals/Il2cpp_hook.hpp" #include "Internals/Il2cpp_string.hpp" #include -#include -#include -#include -#include -#include #include #include diff --git a/shared/CP_SDK/Utils/Json.hpp b/shared/CP_SDK/Utils/Json.hpp index 17ba1b1..bbea0d8 100644 --- a/shared/CP_SDK/Utils/Json.hpp +++ b/shared/CP_SDK/Utils/Json.hpp @@ -1,6 +1,8 @@ #pragma once #include "Il2cpp.hpp" +#include +#include "beatsaber-hook/shared/rapidjson/include/rapidjson/document.h" #include #include @@ -8,7 +10,6 @@ #include #include -#include #include #include #include diff --git a/src/CP_SDK/CPConfig.cpp b/src/CP_SDK/CPConfig.cpp index 7b46702..7eb208f 100644 --- a/src/CP_SDK/CPConfig.cpp +++ b/src/CP_SDK/CPConfig.cpp @@ -23,6 +23,7 @@ namespace CP_SDK { { CP_SDK_JSON_SERIALIZE_BOOL(FirstRun); CP_SDK_JSON_SERIALIZE_BOOL(FirstChatServiceRun); + CP_SDK_JSON_SERIALIZE_STRING(ChatPlexServiceToken); } /// @brief Read the document /// @param p_Document Source @@ -30,6 +31,7 @@ namespace CP_SDK { { CP_SDK_JSON_UNSERIALIZE_BOOL(FirstRun); CP_SDK_JSON_UNSERIALIZE_BOOL(FirstChatServiceRun); + CP_SDK_JSON_UNSERIALIZE_STRING(ChatPlexServiceToken); } //////////////////////////////////////////////////////////////////////////// diff --git a/src/CP_SDK/ChatPlexSDK.cpp b/src/CP_SDK/ChatPlexSDK.cpp index 6baed2f..ba55df2 100644 --- a/src/CP_SDK/ChatPlexSDK.cpp +++ b/src/CP_SDK/ChatPlexSDK.cpp @@ -9,6 +9,7 @@ #include "CP_SDK/Unity/MTMainThreadInvoker.hpp" #include "CP_SDK/Unity/MTThreadInvoker.hpp" #include "CP_SDK/ModuleBase.hpp" +#include "CP_SDK/ChatPlexService.hpp" #include @@ -51,7 +52,6 @@ namespace CP_SDK { { InstallWEBPCodecs(); - /// Init config Chat::Service::Init(); } /// @brief On assembly exit @@ -87,6 +87,8 @@ namespace CP_SDK { /// Init UI UI::UISystem::Init(); + + ChatPlexService::Init(); } catch (const std::exception& p_Exception) { @@ -102,6 +104,8 @@ namespace CP_SDK { OnGenericSceneChange.Clear(); OnGenericMenuSceneLoaded.Clear(); + ChatPlexService::Release(); + UI::UISystem::Destroy(); UI::LoadingProgressBar::Destroy(); diff --git a/src/CP_SDK/ChatPlexService.cpp b/src/CP_SDK/ChatPlexService.cpp new file mode 100644 index 0000000..f3191a9 --- /dev/null +++ b/src/CP_SDK/ChatPlexService.cpp @@ -0,0 +1,318 @@ +#include "CP_SDK/Unity/MTThreadInvoker.hpp" +#include "CP_SDK/ChatPlexService.hpp" +#include "CP_SDK/CPConfig.hpp" + +#include +#include +#include +#include +#include + +static std::u16string s_EmptyU16String = u""; + +namespace CP_SDK { + + bool ChatPlexService::m_ThreadCondition = true; + _u::il2cpp_aware_thread* ChatPlexService::m_Thread = nullptr; + ChatPlexService::EState ChatPlexService::m_State = ChatPlexService::EState::Disconnected; + _v::WebClientCore::Ptr ChatPlexService::m_WebClientCore = nullptr; + _v::JsonRPCClient::Ptr ChatPlexService::m_JsonRPCClient = nullptr; + std::u16string ChatPlexService::m_LinkRequestID = u""; + std::u16string ChatPlexService::m_LinkCode = u""; + std::u16string ChatPlexService::m_LastError = u""; + std::u16string ChatPlexService::m_ActiveSubscription = u""; + std::vector ChatPlexService::m_UnlockedFeatures; + std::queue<_v::Action<>> ChatPlexService::m_OnTokenReadyQueue; + std::mutex ChatPlexService::m_OnTokenReadyQueueMutex; + std::u16string ChatPlexService::m_DeviceName = u""; + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + const ChatPlexService::EState ChatPlexService::State() + { + return m_State; + } + const std::u16string_view ChatPlexService::Token() + { + return CPConfig::Instance()->ChatPlexServiceToken; + } + const std::u16string_view ChatPlexService::LinkCode() + { + return m_LinkCode; + } + const std::u16string_view ChatPlexService::LastError() + { + return m_LastError; + } + const std::u16string_view ChatPlexService::ActiveSubscription() + { + return m_State == EState::Connected ? m_ActiveSubscription : s_EmptyU16String; + } + const std::vector& ChatPlexService::UnlockedFeatures() + { + return *reinterpret_cast*>(&m_UnlockedFeatures); + } + + _v::Event ChatPlexService::StateChanged; + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Init the service + void ChatPlexService::Init() + { + m_WebClientCore = _v::WebClientCore::Make(u"https://api.chatplex.org/", _u::TimeSpan::FromSeconds(10), false, true); + m_JsonRPCClient = _v::JsonRPCClient::Create(m_WebClientCore); + + m_Thread = new _u::il2cpp_aware_thread(&ThreadRunner); + + m_DeviceName = _u::SystemInfo::GetDeviceName(); + } + /// @brief Release the service + void ChatPlexService::Release() + { + m_ThreadCondition = false; + m_Thread->join(); + + delete m_Thread; + m_Thread = nullptr; + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Add a callback to be called when the token is ready (call immediatly if ready) + /// @param action Callback to be caled + void ChatPlexService::OnTokenReady(_v::CActionRef<> action) + { + if (m_State == EState::Connected) + action(); + else + { + std::lock_guard l_Lock(m_OnTokenReadyQueueMutex); + m_OnTokenReadyQueue.push(action); + } + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Start linking procedure + void ChatPlexService::StartLinking() + { + if (m_State != EState::Disconnected) + return; + + ChangeState(EState::LinkRequest); + } + /// @brief Stop linking procedure + void ChatPlexService::StopLinking() + { + if (m_State != EState::LinkRequest && m_State != EState::LinkWait) + return; + + ChangeState(EState::Disconnected); + } + /// @brief Refresh the session + void ChatPlexService::Refresh() + { + ChangeState(EState::Disconnected); + } + /// @brief Disconnect and erase the saved connected application token + void ChatPlexService::Disconnect() + { + CPConfig::Instance()->ChatPlexServiceToken = u""; + CPConfig::Instance()->Save(); + + ChangeState(EState::Disconnected); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Change state and notify listenners + void ChatPlexService::ChangeState(const EState newState) + { + auto l_OldState = m_State; + m_State = newState; + + Unity::MTThreadInvoker::EnqueueOnThread([=]() -> void { StateChanged(l_OldState, newState); }); + } + /// @brief Fire on token ready actions + void ChatPlexService::FireOnTokenReady() + { + m_OnTokenReadyQueueMutex.lock(); + while (!m_OnTokenReadyQueue.empty()) + { + auto l_Action = m_OnTokenReadyQueue.front(); + m_OnTokenReadyQueue.pop(); + m_OnTokenReadyQueueMutex.unlock(); + try + { + l_Action(); + } + catch (std::exception l_Exception) + { + ChatPlexSDK::Logger()->Error(u"[CP_SDK][ChatPlexService.FireOnTokenReady] Error:"); + ChatPlexSDK::Logger()->Error(l_Exception); + } + m_OnTokenReadyQueueMutex.lock(); + } + + m_OnTokenReadyQueueMutex.unlock(); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Thread function + void ChatPlexService::ThreadRunner() + { + while (m_ThreadCondition) + { + if (m_State == EState::Disconnected && !CPConfig::Instance()->ChatPlexServiceToken.empty()) + { + m_WebClientCore->RemoveHeader(u"Authorization"); + + ChangeState(EState::Connecting); + + std::shared_ptr<_v::Json::U16Document> l_Content(new _v::Json::U16Document()); + l_Content->SetObject(); + l_Content->AddMember(u"ConnectedApplicationToken", _v::Json::U16Value(CPConfig::Instance()->ChatPlexServiceToken, l_Content->GetAllocator()), l_Content->GetAllocator()); + + auto l_Result = m_JsonRPCClient->Request( + u"Account_AuthByConnectedApplicationToken", + l_Content + ); + + if (IsRPCSuccess(l_Result)) + OnAuthed(l_Result); + else + { + if (l_Result->Result != nullptr && l_Result->Result->HasMember(u"Result") && (*l_Result->Result)[u"Result"].GetBool() == false) + { + CPConfig::Instance()->ChatPlexServiceToken = u""; + CPConfig::Instance()->Save(); + + ChangeState(EState::Disconnected); + } + else + OnError(l_Result); + } + } + else if (m_State == EState::LinkRequest) + { + std::shared_ptr<_v::Json::U16Document> l_Content(new _v::Json::U16Document()); + l_Content->SetObject(); + l_Content->AddMember(u"ApplicationIdentifier", _v::Json::U16Value(ChatPlexSDK::ProductName().data(), l_Content->GetAllocator()), l_Content->GetAllocator()); + l_Content->AddMember(u"ApplicationDeviceName", _v::Json::U16Value(m_DeviceName, l_Content->GetAllocator()), l_Content->GetAllocator()); + + auto l_Result = m_JsonRPCClient->Request( + u"ConnectedApplication_CreateLinkRequest", + l_Content + ); + + if (IsRPCSuccess(l_Result)) + { + auto& l_ResultR = *l_Result->Result.get(); + m_LinkRequestID = l_ResultR[u"RequestID"].GetString(); + m_LinkCode = l_ResultR[u"Code"].GetString(); + + ChangeState(EState::LinkWait); + } + else + OnError(l_Result); + } + else if (m_State == EState::LinkWait) + { + std::shared_ptr<_v::Json::U16Document> l_Content(new _v::Json::U16Document()); + l_Content->SetObject(); + l_Content->AddMember(u"RequestID", _v::Json::U16Value(m_LinkRequestID, l_Content->GetAllocator()), l_Content->GetAllocator()); + + auto l_Result = m_JsonRPCClient->Request( + u"ConnectedApplication_GetLinkRequestStatus", + l_Content + ); + + if (IsRPCSuccess(l_Result)) + { + auto& l_ResultR = *l_Result->Result.get(); + if (l_ResultR[u"ResultToken"].IsString()) + { + CPConfig::Instance()->ChatPlexServiceToken = l_ResultR[u"ResultToken"].GetString(); + CPConfig::Instance()->Save(); + + ChangeState(EState::Disconnected); + } + } + else + OnError(l_Result); + } + + if (m_State == EState::LinkWait) + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + else + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Is RPC call result a success result? + /// @param rpcResult Result of the RPC command + /// @return True if success + bool ChatPlexService::IsRPCSuccess(_v::JsonRPCResult::Ptr& rpcResult) + { + if (rpcResult->Result == nullptr) + return false; + + auto& l_ResultR = *rpcResult->Result.get(); + return l_ResultR.HasMember(u"Result") && l_ResultR[u"Result"].GetBool() == true; + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief When we are Authed + /// @param rpcResult Result of the RPC command + void ChatPlexService::OnAuthed(_v::JsonRPCResult::Ptr& rpcResult) + { + auto& l_ResultR = *rpcResult->Result.get(); + m_ActiveSubscription = l_ResultR.HasMember(u"ActiveSubscription") ? l_ResultR[u"ActiveSubscription"].GetString() : u""; + + m_UnlockedFeatures.clear(); + if (l_ResultR.HasMember(u"UnlockedFeatures") && l_ResultR[u"UnlockedFeatures"].IsArray()) + { + auto l_Array = l_ResultR[u"UnlockedFeatures"].GetArray(); + for (auto& l_CurrentValue : l_Array) + { + if (!l_CurrentValue.IsString()) + continue; + + m_UnlockedFeatures.push_back(l_CurrentValue.GetString()); + } + } + + m_WebClientCore->SetHeader(u"Authorization", std::u16string(u"ConnectedApplicationToken ") + CPConfig::Instance()->ChatPlexServiceToken); + + ChangeState(EState::Connected); + FireOnTokenReady(); + } + /// @brief On error received + /// @param rpcResult Result of the RPC command + void ChatPlexService::OnError(_v::JsonRPCResult::Ptr& rpcResult) + { + std::u16string l_Error = u"Unknow server error!"; + if (rpcResult->Result != nullptr && rpcResult->Result->HasMember(u"Error")) + { + auto& l_ResultR = *rpcResult->Result.get(); + l_Error = l_ResultR[u"Error"].GetString(); + } + + m_LastError = l_Error; + ChangeState(EState::Error); + } + +} ///< namespace CP_SDK \ No newline at end of file diff --git a/src/CP_SDK/Network/JsonRPCClient.cpp b/src/CP_SDK/Network/JsonRPCClient.cpp new file mode 100644 index 0000000..a3b8813 --- /dev/null +++ b/src/CP_SDK/Network/JsonRPCClient.cpp @@ -0,0 +1,190 @@ +#include "CP_SDK/Network/JsonRPCClient.hpp" +#include "CP_SDK/Utils/Json.hpp" +#include "beatsaber-hook/shared/rapidjson/include/rapidjson/rapidjson.h" + +#include +#include + +using namespace System::Text; + +namespace CP_SDK::Network { + + /// @brief Constructor + /// @param webClient Web client instance + JsonRPCClient::JsonRPCClient(CP_SDK_PRIV_TAG_ARG(), WebClientCore::Ptr& webClient) + { + m_WebClient = webClient; + } + /// @brief Destructor + JsonRPCClient::~JsonRPCClient() + { + m_WebClient = nullptr; + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Create + /// @param webClient Web client instance + JsonRPCClient::Ptr JsonRPCClient::Create(WebClientCore::Ptr& webClient) + { + return std::make_shared(CP_SDK_PRIV_TAG_VAL(), webClient); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Do a RPC request + /// @param method Target method + /// @param parameters Request parameters + /// @param dontRetry Should not retry? + JsonRPCResult::Ptr JsonRPCClient::Request(std::u16string_view method, std::shared_ptr<_v::Json::U16Value> parameters, bool dontRetry) + { + auto l_Method = std::u16string(method); + + std::shared_ptr<_v::Json::U16Document> l_Content(new _v::Json::U16Document()); + l_Content->SetObject(); + l_Content->AddMember(u"id", _v::Json::U16Value(1), l_Content->GetAllocator()); + l_Content->AddMember(u"jsonrpc", _v::Json::U16Value(u"2.0", l_Content->GetAllocator()), l_Content->GetAllocator()); + l_Content->AddMember(u"method", _v::Json::U16Value(l_Method.c_str(), l_Method.length(), l_Content->GetAllocator()), l_Content->GetAllocator()); + + if (parameters) + { + _v::Json::U16Value l_Copy; + l_Copy.CopyFrom(*parameters, l_Content->GetAllocator()); + + l_Content->AddMember(u"params", l_Copy, l_Content->GetAllocator()); + } + else + l_Content->AddMember(u"params", _v::Json::U16Value(rapidjson::kNullType), l_Content->GetAllocator()); + + return DoRequest(l_Method, l_Content, dontRetry); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Do a RPC request + /// @param method Target method + /// @param parameters Request parameters + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry? + void JsonRPCClient::RequestAsync( + std::u16string_view method, + std::shared_ptr<_v::Json::U16Value> parameters, + _u::CancellationToken token, + _v::CActionRef callback, + bool dontRetry + ) + { + auto l_Method = std::u16string(method); + + std::shared_ptr<_v::Json::U16Document> l_Content(new _v::Json::U16Document()); + l_Content->SetObject(); + l_Content->AddMember(u"id", _v::Json::U16Value(1), l_Content->GetAllocator()); + l_Content->AddMember(u"jsonrpc", _v::Json::U16Value(u"2.0", l_Content->GetAllocator()), l_Content->GetAllocator()); + l_Content->AddMember(u"method", _v::Json::U16Value(l_Method.c_str(), l_Method.length(), l_Content->GetAllocator()), l_Content->GetAllocator()); + + if (parameters) + { + _v::Json::U16Value l_Copy; + l_Copy.CopyFrom(*parameters, l_Content->GetAllocator()); + + l_Content->AddMember(u"params", l_Copy, l_Content->GetAllocator()); + } + else + l_Content->AddMember(u"params", _v::Json::U16Value(rapidjson::kNullType), l_Content->GetAllocator()); + + DoRequestAsync(l_Method, l_Content, token, callback, dontRetry); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Do a RPC request + /// @param method Target method + /// @param parameters Request content + /// @param dontRetry Should not retry? + JsonRPCResult::Ptr JsonRPCClient::DoRequest( + std::u16string method, + std::shared_ptr<_v::Json::U16Document>& content, + bool dontRetry + ) + { + auto l_Response = m_WebClient->Post( + u"", + WebContent::FromJson(content), + dontRetry + ); + return HandleWebResponse(method, l_Response); + } + /// @brief Do a RPC request + /// @param method Target method + /// @param content Request content + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry? + void JsonRPCClient::DoRequestAsync( + std::u16string method, + std::shared_ptr<_v::Json::U16Document>& content, + _u::CancellationToken token, + _v::CActionRef callback, + bool dontRetry + ) + { + auto l_Self = shared_from_this(); + m_WebClient->PostAsync( + u"", + WebContent::FromJson(content), + token, + [=](CP_SDK::Network::WebResponse::Ptr webResponse) -> void { + callback(l_Self->HandleWebResponse(method, webResponse)); + }, + dontRetry + ); + } + /// @brief Do a RPC request + /// @param method Target method + /// @param rawResponse Web response + JsonRPCResult::Ptr JsonRPCClient::HandleWebResponse(std::u16string method, WebResponse::Ptr& rawResponse) + { + if (!rawResponse) + return JsonRPCResult::Create(rawResponse, nullptr, nullptr); + + try + { + auto l_JsonResult = std::make_shared<_v::Json::U16Document>(); + if (!_v::Json::TryFromU16String(rawResponse->BodyString(), *l_JsonResult.get())) + return nullptr; + + auto l_ResultDocument = std::shared_ptr<_v::Json::U16Document>(nullptr); + if (l_JsonResult->HasMember(u"result")) + { + l_ResultDocument = std::make_shared<_v::Json::U16Document>(); + l_ResultDocument->CopyFrom(l_JsonResult->FindMember(u"result")->value, l_ResultDocument->GetAllocator()); + } + + auto l_ErrorDocument = std::shared_ptr<_v::Json::U16Document>(nullptr); + if (l_JsonResult->HasMember(u"error")) + { + l_ErrorDocument = std::make_shared<_v::Json::U16Document>(); + l_ErrorDocument->CopyFrom(l_JsonResult->FindMember(u"error")->value, l_ErrorDocument->GetAllocator()); + } + + return JsonRPCResult::Create( + rawResponse, + l_ResultDocument, + l_ErrorDocument + ); + } + catch (std::exception& l_Exception) + { + ChatPlexSDK::Logger()->Error(u"[CP_API_SDK.Network][JsonRPCClient.HandleResponse] Request " + method + u" failed parsing response:"); + ChatPlexSDK::Logger()->Error(l_Exception); + } + + return nullptr; + } + +} ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/src/CP_SDK/Network/JsonRPCResult.cpp b/src/CP_SDK/Network/JsonRPCResult.cpp new file mode 100644 index 0000000..16c4656 --- /dev/null +++ b/src/CP_SDK/Network/JsonRPCResult.cpp @@ -0,0 +1,49 @@ +#include "CP_SDK/Network/JsonRPCResult.hpp" + +#include +#include + +using namespace System::Text; + +namespace CP_SDK::Network { + + /// @brief Constructor + /// @param rawResponse Raw web response + /// @param result Result json + /// @param error Json content + JsonRPCResult::JsonRPCResult( + CP_SDK_PRIV_TAG_ARG(), + WebResponse::Ptr& rawResponse, + std::shared_ptr<_v::Json::U16Document> result, + std::shared_ptr<_v::Json::U16Document> error + ) + { + RawResponse = rawResponse; + Result = result; + Error = error; + } + /// @brief Destructor + JsonRPCResult::~JsonRPCResult() + { + RawResponse = nullptr; + Result = nullptr; + Error = nullptr; + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Create + /// @param rawResponse Raw web response + /// @param result Result json + /// @param error Json content + JsonRPCResult::Ptr JsonRPCResult::Create( + WebResponse::Ptr& rawResponse, + std::shared_ptr<_v::Json::U16Document> result, + std::shared_ptr<_v::Json::U16Document> error + ) + { + return std::make_shared(CP_SDK_PRIV_TAG_VAL(), rawResponse, result, error); + } + +} ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/src/CP_SDK/Network/WebClientCore.cpp b/src/CP_SDK/Network/WebClientCore.cpp new file mode 100644 index 0000000..d5eb88b --- /dev/null +++ b/src/CP_SDK/Network/WebClientCore.cpp @@ -0,0 +1,594 @@ +#include "CP_SDK/Network/WebClientCore.hpp" +#include "CP_SDK/Unity/MTThreadInvoker.hpp" +#include "CP_SDK/ChatPlexSDK.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace System; +using namespace System::Net; +using namespace System::Threading; + +struct ScopedCURL +{ + CURL* Instance; + struct curl_slist* Headers = NULL; + std::vector* Data; + + ScopedCURL() + { + Instance = curl_easy_init(); + Data = new std::vector(); + } + ~ScopedCURL() + { + curl_easy_cleanup(Instance); + if (Headers != NULL) + curl_slist_free_all(Headers); + + delete Data; + } +}; + +namespace CP_SDK::Network { + + WebClientCore::Ptr WebClientCore::m_GlobalClient; + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Global client instance + WebClientCore* WebClientCore::GlobalClient() + { + if (!m_GlobalClient) + m_GlobalClient = WebClientCore::Make(u"", TimeSpan::FromSeconds(10), true); + + return m_GlobalClient.get(); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Constructor + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param forceCacheDiscard Should force server cache discard + WebClientCore::WebClientCore(CP_SDK_PRIV_TAG_ARG(), std::u16string_view baseAddress, TimeSpan timeOut, bool keepAlive, bool forceCacheDiscard) + : m_Headers({}) + { + MaxRetry = 2; + RetryInterval = 5; + + m_BaseAddress = baseAddress; + + m_TimeOut = (int)timeOut.get_TotalSeconds(); + + if (forceCacheDiscard) + m_Headers[u"Cache-Control"] = u"no-cache, must-revalidate, proxy-revalidate, max-age=0, s-maxage=0, max-stale=0"; + } + + /// @brief Constructor + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param forceCacheDiscard Should force server cache discard + WebClientCore::Ptr WebClientCore::Make(std::u16string_view baseAddress, TimeSpan timeOut, bool keepAlive, bool forceCacheDiscard) + { + return std::make_shared(CP_SDK_PRIV_TAG_VAL(), baseAddress, timeOut, keepAlive, forceCacheDiscard); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Get header + /// @param name Header name + std::u16string WebClientCore::GetHeader(std::u16string_view name) + { + auto l_Name = std::u16string(name); + + std::lock_guard l_Guard(m_HeadersLock); + if (!m_Headers.contains(l_Name)) + return u""; + + return m_Headers[l_Name]; + } + /// @brief Set header + /// @param name Header name + /// @param value Header value + void WebClientCore::SetHeader(std::u16string_view name, std::u16string_view value) + { + auto l_Name = std::u16string(name); + + std::lock_guard l_Guard(m_HeadersLock); + m_Headers[l_Name] = std::u16string(value); + } + /// @brief Remove header + /// @param name Header name + void WebClientCore::RemoveHeader(std::u16string_view name) + { + auto l_Name = std::u16string(name); + + std::lock_guard l_Guard(m_HeadersLock); + auto l_It = m_Headers.find(l_Name); + if (l_It != m_Headers.end()) + m_Headers.erase(l_It); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Do GET query + /// @param url Target URL + /// @param dontRetry Should not retry + /// @param progress Progress reporter + WebResponse::Ptr WebClientCore::Get(std::u16string_view url, bool dontRetry, _v::CActionRef progress) + { + auto l_IsDone = false; + auto l_Reply = WebResponse::Ptr(nullptr); + + DoRequest( + shared_from_this(), + u"Get", + u"GET", + GetURL(url), + nullptr, + CancellationToken::get_None(), + [&](WebResponse::Ptr p_Result) -> void { l_Reply = p_Result; l_IsDone = true; }, + dontRetry, + progress + ); + + while (!l_IsDone) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + return l_Reply; + } + /// @brief Do GET query + /// @param url Target URL + /// @param dontRetry Should not retry + /// @param progress Progress reporter + WebResponse::Ptr WebClientCore::Download(std::u16string_view url, bool dontRetry, _v::CActionRef progress) + { + auto l_IsDone = false; + auto l_Reply = WebResponse::Ptr(nullptr); + + DoRequest( + shared_from_this(), + u"Download", + u"DOWNLOAD", + GetURL(url), + nullptr, + CancellationToken::get_None(), + [&](WebResponse::Ptr p_Result) -> void { l_Reply = p_Result; l_IsDone = true; }, + dontRetry, + progress + ); + + while (!l_IsDone) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + return l_Reply; + } + /// @brief Do POST query + /// @param url Target URL + /// @param content Optional content to post + /// @param dontRetry Should not retry + WebResponse::Ptr WebClientCore::Post(std::u16string_view url, const WebContent::Ptr& content, bool dontRetry) + { + auto l_IsDone = false; + auto l_Reply = WebResponse::Ptr(nullptr); + + DoRequest( + shared_from_this(), + u"Post", + u"POST", + GetURL(url), + content, + CancellationToken::get_None(), + [&](WebResponse::Ptr p_Result) -> void { l_Reply = p_Result; l_IsDone = true; }, + dontRetry, + nullptr + ); + + while (!l_IsDone) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + return l_Reply; + } + /// @brief Do PATCH query + /// @param url Target URL + /// @param content Optional content to post + /// @param dontRetry Should not retry + WebResponse::Ptr WebClientCore::Patch(std::u16string_view url, const WebContent::Ptr& content, bool dontRetry) + { + auto l_IsDone = false; + auto l_Reply = WebResponse::Ptr(nullptr); + + DoRequest( + shared_from_this(), + u"Patch", + u"PATCH", + GetURL(url), + content, + CancellationToken::get_None(), + [&](WebResponse::Ptr p_Result) -> void { l_Reply = p_Result; l_IsDone = true; }, + dontRetry, + nullptr + ); + + while (!l_IsDone) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + return l_Reply; + } + /// @brief Do PUT query + /// @param url Target URL + /// @param content Optional content to post + /// @param dontRetry Should not retry + WebResponse::Ptr WebClientCore::Put(std::u16string_view url, const WebContent::Ptr& content, bool dontRetry) + { + auto l_IsDone = false; + auto l_Reply = WebResponse::Ptr(nullptr); + + DoRequest( + shared_from_this(), + u"Put", + u"PUT", + GetURL(url), + content, + CancellationToken::get_None(), + [&](WebResponse::Ptr p_Result) -> void { l_Reply = p_Result; l_IsDone = true; }, + dontRetry, + nullptr + ); + + while (!l_IsDone) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + return l_Reply; + } + /// @brief Do DELETE query + /// @param url Target URL + /// @param dontRetry Should not retry + WebResponse::Ptr WebClientCore::Delete(std::u16string_view url, bool dontRetry) + { + auto l_IsDone = false; + auto l_Reply = WebResponse::Ptr(nullptr); + + DoRequest( + shared_from_this(), + u"Delete", + u"DELETE", + GetURL(url), + nullptr, + CancellationToken::get_None(), + [&](WebResponse::Ptr p_Result) -> void { l_Reply = p_Result; l_IsDone = true; }, + dontRetry, + nullptr + ); + + while (!l_IsDone) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + return l_Reply; + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Do Async GET query + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + void WebClientCore::GetAsync(std::u16string_view url, CancellationToken token, _v::CActionRef callback, bool dontRetry, _v::CActionRef progress) + { + il2cpp_utils::il2cpp_aware_thread( + &DoRequest, + shared_from_this(), + u"GetAsync", + u"GET", + GetURL(url), + nullptr, + token, + callback, + dontRetry, + progress + ).detach(); + } + /// @brief Do Async GET query + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + void WebClientCore::DownloadAsync(std::u16string_view url, CancellationToken token, _v::CActionRef callback, bool dontRetry, _v::CActionRef progress) + { + il2cpp_utils::il2cpp_aware_thread( + &DoRequest, + shared_from_this(), + u"DownloadAsync", + u"DOWNLOAD", + GetURL(url), + nullptr, + token, + callback, + dontRetry, + progress + ).detach(); + } + /// @brief Do Async POST query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + void WebClientCore::PostAsync(std::u16string_view url, const WebContent::Ptr& content, CancellationToken token, _v::CActionRef callback, bool dontRetry) + { + il2cpp_utils::il2cpp_aware_thread( + &DoRequest, + shared_from_this(), + u"PostAsync", + u"POST", + GetURL(url), + content, + token, + callback, + dontRetry, + nullptr + ).detach(); + } + /// @brief Do Async PATCH query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + void WebClientCore::PatchAsync(std::u16string_view url, const WebContent::Ptr& content, CancellationToken token, _v::CActionRef callback, bool dontRetry) + { + il2cpp_utils::il2cpp_aware_thread( + &DoRequest, + shared_from_this(), + u"PatchAsync", + u"PATCH", + GetURL(url), + content, + token, + callback, + dontRetry, + nullptr + ).detach(); + } + /// @brief Do Async PATCH query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + void WebClientCore::PutAsync(std::u16string_view url, const WebContent::Ptr& content, CancellationToken token, _v::CActionRef callback, bool dontRetry) + { + il2cpp_utils::il2cpp_aware_thread( + &DoRequest, + shared_from_this(), + u"PutAsync", + u"PUT", + GetURL(url), + content, + token, + callback, + dontRetry, + nullptr + ).detach(); + } + /// @brief Do Async GET query + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + void WebClientCore::DeleteAsync(std::u16string_view url, CancellationToken token, _v::CActionRef callback, bool dontRetry) + { + il2cpp_utils::il2cpp_aware_thread( + &DoRequest, + shared_from_this(), + u"DeleteAsync", + u"DELETE", + GetURL(url), + nullptr, + token, + callback, + dontRetry, + nullptr + ); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Get URL + /// @param url Request URL + std::u16string WebClientCore::GetURL(std::u16string_view url) + { + if (m_BaseAddress.size() == 0) return std::u16string(url); + if (url.find(u"://") != std::u16string::npos) return std::u16string(url); + if (m_BaseAddress[m_BaseAddress.size() - 1] == '/') return m_BaseAddress + std::u16string(url); + + return m_BaseAddress + u"/" + std::u16string(url); + } + /// @brief Safe URL parsing + /// @param url Source URL + std::u16string WebClientCore::SafeURL(std::u16string_view url) + { + auto l_Position = url.find_first_of('?'); + if (l_Position != std::u16string::npos) + return std::u16string(url).substr(0, l_Position); + + return std::u16string(url); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief Do request + /// @param debugName Method name for logs + /// @param httpMethod Http method + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + void WebClientCore::DoRequest( + Ptr self, + std::u16string debugName, + std::u16string httpMethod, + std::u16string url, + WebContent::Ptr content, + CancellationToken token, + _v::Action callback, + bool dontRetry, + _v::Action progress + ) + { +#if DEBUG + ChatPlexSDK::Logger()->Debug(u"[CP_SDK.Network][WebClientCore." + debugName + u"] " + httpMethod + u" " + url); +#endif + + WebResponse::Ptr l_Reply = nullptr; + for (int l_RetryI = 1; l_RetryI <= self->MaxRetry; l_RetryI++) + { + if (token.get_IsCancellationRequested()) + break; + + ScopedCURL l_ScopedCURL; + l_ScopedCURL.Headers = curl_slist_append(l_ScopedCURL.Headers, "Accept: */*"); + + for (auto const& [l_Header, l_Value] : self->m_Headers) + l_ScopedCURL.Headers = curl_slist_append(l_ScopedCURL.Headers, Utils::U16StrToStr(l_Header + ": " + l_Value).c_str()); + + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_URL, Utils::U16StrToStr(url).c_str()); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_TIMEOUT, static_cast(self->m_TimeOut)); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_FOLLOWLOCATION, static_cast(1)); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_USERAGENT, Utils::U16StrToStr(ChatPlexSDK::NetworkUserAgent()).c_str()); + + if (httpMethod == u"GET" || httpMethod == u"DOWNLOAD") + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_CUSTOMREQUEST, "GET"); + else if (httpMethod == u"POST" || httpMethod == u"PATCH" || httpMethod == u"PUT") + { + if (httpMethod == u"POST") + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_CUSTOMREQUEST, "POST"); + else if (httpMethod == u"PATCH") + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_CUSTOMREQUEST, "PATCH"); + else + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_CUSTOMREQUEST, "PUT"); + + if (content) + { + l_ScopedCURL.Headers = curl_slist_append(l_ScopedCURL.Headers, Utils::U16StrToStr(std::u16string(u"Content-Type: ") + content->Type).c_str()); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_POSTFIELDSIZE, static_cast(content->Bytes->get_Length())); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_POSTFIELDS, &content->Bytes->_values[0]); + } + } + else if (httpMethod == u"DELETE") + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_CUSTOMREQUEST, "DELETE"); + + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_HTTPHEADER, l_ScopedCURL.Headers); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_SSL_VERIFYPEER, false); + + if (progress.IsValid()) + { + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_NOPROGRESS, false); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_XFERINFODATA, (void*)&progress); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_XFERINFOFUNCTION, + +[] (_v::Action* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + float percentage = (float)dlnow / (float)dltotal * 100.0f; + if(isnan(percentage)) + percentage = 0.0f; + clientp->Invoke(percentage); + return 0; + } + ); + } + + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_WRITEDATA, l_ScopedCURL.Data); + curl_easy_setopt(l_ScopedCURL.Instance, CURLOPT_WRITEFUNCTION, + +[](void *contents, std::size_t size, std::size_t nmemb, std::vector* clientp) -> size_t + { + size_t l_SizeToWrite = size * nmemb; + + try + { + auto l_WritePos = clientp->size(); + clientp->resize(l_WritePos + l_SizeToWrite); + memcpy(clientp->data() + l_WritePos, contents, l_SizeToWrite); + } + catch(std::bad_alloc &e) + { + ChatPlexSDK::Logger()->Error(u"[CP_SDK.Network][WebClientCore.DoRequest] Failed to read from response stream, allocation error"); + return 0; + } + + return l_SizeToWrite; + }); + + if (token.get_IsCancellationRequested()) + break; + + auto l_CURLResult = curl_easy_perform(l_ScopedCURL.Instance); + + l_Reply = std::make_shared(static_cast(l_CURLResult), l_ScopedCURL.Instance, l_ScopedCURL.Data); + if (!l_Reply->IsSuccessStatusCode() && l_Reply->StatusCode() == (HttpStatusCode)429) + { + /* var l_Limits = RateLimitInfo.Get(l_Request); + if (l_Limits != nullptr) + { + int l_TotalMilliseconds = (int)(l_Limits.Reset - DateTime.Now).TotalMilliseconds; + if (l_TotalMilliseconds > 0) + { + ChatPlexSDK::Logger()->Error(u"[CP_SDK.Network][WebClientCore." + debugName + u"] Request {SafeURL(url)} was rate limited, retrying in {l_TotalMilliseconds}ms..."); + + co_yield WaitForSecondsRealtime::New_ctor(RetryInterval)->i_IEnumerator(); + continue; + } + }*/ + } + + if (!l_Reply->IsSuccessStatusCode()) + { + auto l_LogPrefix = u"[CP_SDK.Network][WebClientCore." + debugName + u"] Request " + self->SafeURL(url) + u" failed with code "; + l_LogPrefix += StringW(std::to_string(l_Reply->StatusCode().value__)); + l_LogPrefix += u":\"" + l_Reply->ReasonPhrase() + "\", "; + + if (!l_Reply->ShouldRetry() || dontRetry) + { + ChatPlexSDK::Logger()->Error(l_LogPrefix + u" not retrying"); + break; + } + + ChatPlexSDK::Logger()->Error(l_LogPrefix + u" next try in " + (std::u16string)StringW(std::to_string(self->RetryInterval)) + u" seconds..."); + + std::this_thread::sleep_for(std::chrono::seconds(self->RetryInterval)); + continue; + } + else + { + if (progress.IsValid()) + try { progress(1.0f); } catch (const std::exception&) { } + + break; + } + } + + if (!token.get_IsCancellationRequested() && callback.IsValid()) + Unity::MTThreadInvoker::EnqueueOnThread([=]() -> void { callback(l_Reply); }); + } + +} ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/src/CP_SDK/Network/WebClientUnity.cpp b/src/CP_SDK/Network/WebClientUnity.cpp index 772c79c..0498722 100644 --- a/src/CP_SDK/Network/WebClientUnity.cpp +++ b/src/CP_SDK/Network/WebClientUnity.cpp @@ -6,7 +6,6 @@ #include #include #include -//#include #include #include @@ -36,41 +35,41 @@ namespace CP_SDK::Network { //////////////////////////////////////////////////////////////////////////// /// @brief Constructor - /// @param p_BaseAddress Base address - /// @param p_TimeOut Requests timeout - /// @param p_ForceCacheDiscard Should force server cache discard - WebClientUnity::WebClientUnity(CP_SDK_PRIV_TAG_ARG(), std::u16string_view p_BaseAddress, TimeSpan p_TimeOut, bool p_ForceCacheDiscard) + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param forceCacheDiscard Should force server cache discard + WebClientUnity::WebClientUnity(CP_SDK_PRIV_TAG_ARG(), std::u16string_view baseAddress, TimeSpan timeOut, bool forceCacheDiscard) : m_Headers({}) { DownloadTimeout = 2 * 60; MaxRetry = 2; RetryInterval = 5; - m_BaseAddress = p_BaseAddress; + m_BaseAddress = baseAddress; - m_Timeout = (int)p_TimeOut.get_TotalSeconds(); + m_Timeout = (int)timeOut.get_TotalSeconds(); - if (p_ForceCacheDiscard) + if (forceCacheDiscard) m_Headers[u"Cache-Control"] = u"no-cache, must-revalidate, proxy-revalidate, max-age=0, s-maxage=0, max-stale=0"; } /// @brief Constructor - /// @param p_BaseAddress Base address - /// @param p_TimeOut Requests timeout - /// @param p_ForceCacheDiscard Should force server cache discard - WebClientUnity::Ptr WebClientUnity::Make(std::u16string_view p_BaseAddress, TimeSpan p_TimeOut, bool p_ForceCacheDiscard) + /// @param baseAddress Base address + /// @param timeOut Requests timeout + /// @param forceCacheDiscard Should force server cache discard + WebClientUnity::Ptr WebClientUnity::Make(std::u16string_view baseAddress, TimeSpan timeOut, bool forceCacheDiscard) { - return std::make_shared(CP_SDK_PRIV_TAG_VAL(), p_BaseAddress, p_TimeOut, p_ForceCacheDiscard); + return std::make_shared(CP_SDK_PRIV_TAG_VAL(), baseAddress, timeOut, forceCacheDiscard); } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /// @brief Get header - /// @param p_Name Header name - std::u16string WebClientUnity::GetHeader(std::u16string_view p_Name) + /// @param name Header name + std::u16string WebClientUnity::GetHeader(std::u16string_view name) { - auto l_Name = std::u16string(p_Name); + auto l_Name = std::u16string(name); std::lock_guard l_Guard(m_HeadersLock); if (!m_Headers.contains(l_Name)) @@ -79,20 +78,20 @@ namespace CP_SDK::Network { return m_Headers[l_Name]; } /// @brief Set header - /// @param p_Name Header name - /// @param p_Value Header value - void WebClientUnity::SetHeader(std::u16string_view p_Name, std::u16string_view p_Value) + /// @param name Header name + /// @param value Header value + void WebClientUnity::SetHeader(std::u16string_view name, std::u16string_view value) { - auto l_Name = std::u16string(p_Name); + auto l_Name = std::u16string(name); std::lock_guard l_Guard(m_HeadersLock); - m_Headers[l_Name] = std::u16string(p_Value); + m_Headers[l_Name] = std::u16string(value); } /// @brief Remove header - /// @param p_Name Header name - void WebClientUnity::RemoveHeader(std::u16string_view p_Name) + /// @param name Header name + void WebClientUnity::RemoveHeader(std::u16string_view name) { - auto l_Name = std::u16string(p_Name); + auto l_Name = std::u16string(name); std::lock_guard l_Guard(m_HeadersLock); auto l_It = m_Headers.find(l_Name); @@ -104,153 +103,165 @@ namespace CP_SDK::Network { //////////////////////////////////////////////////////////////////////////// /// @brief Do Async GET query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - void WebClientUnity::GetAsync(std::u16string_view p_URL, CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry, _v::CActionRef p_Progress) + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + void WebClientUnity::GetAsync(std::u16string_view url, CancellationToken token, _v::CActionRef callback, bool dontRetry, _v::CActionRef progress) { - Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"GetAsync", u"GET", GetURL(p_URL), nullptr, p_Token, p_Callback, p_DontRetry, p_Progress))); + Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"GetAsync", u"GET", GetURL(url), nullptr, token, callback, dontRetry, progress))); } /// @brief Do Async GET query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - void WebClientUnity::DownloadAsync(std::u16string_view p_URL, CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry, _v::CActionRef p_Progress) + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + void WebClientUnity::DownloadAsync(std::u16string_view url, CancellationToken token, _v::CActionRef callback, bool dontRetry, _v::CActionRef progress) { - Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"DownloadAsync", u"DOWNLOAD", GetURL(p_URL), nullptr, p_Token, p_Callback, p_DontRetry, p_Progress))); + Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"DownloadAsync", u"DOWNLOAD", GetURL(url), nullptr, token, callback, dontRetry, progress))); } /// @brief Do Async POST query - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - void WebClientUnity::PostAsync(std::u16string_view p_URL, const WebContent::Ptr& p_Content, CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry) + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + void WebClientUnity::PostAsync(std::u16string_view url, const WebContent::Ptr& content, CancellationToken token, _v::CActionRef callback, bool dontRetry) { - Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"PostAsync", u"POST", GetURL(p_URL), p_Content, p_Token, p_Callback, p_DontRetry, nullptr))); + Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"PostAsync", u"POST", GetURL(url), content, token, callback, dontRetry, nullptr))); } /// @brief Do Async PATCH query - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - void WebClientUnity::PatchAsync(std::u16string_view p_URL, const WebContent::Ptr& p_Content, CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry) + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + void WebClientUnity::PatchAsync(std::u16string_view url, const WebContent::Ptr& content, CancellationToken token, _v::CActionRef callback, bool dontRetry) { - Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"PatchAsync", u"PATCH", GetURL(p_URL), p_Content, p_Token, p_Callback, p_DontRetry, nullptr))); + Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"PatchAsync", u"PATCH", GetURL(url), content, token, callback, dontRetry, nullptr))); + } + /// @brief Do Async PUT query + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + void WebClientUnity::PutAsync(std::u16string_view url, const WebContent::Ptr& content, CancellationToken token, _v::CActionRef callback, bool dontRetry) + { + Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"PutAsync", u"PUT", GetURL(url), content, token, callback, dontRetry, nullptr))); } /// @brief Do Async GET query - /// @param p_URL Target URL - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - void WebClientUnity::DeleteAsync(std::u16string_view p_URL, CancellationToken p_Token, _v::CActionRef p_Callback, bool p_DontRetry) + /// @param url Target URL + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + void WebClientUnity::DeleteAsync(std::u16string_view url, CancellationToken token, _v::CActionRef callback, bool dontRetry) { - Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"DownloadAsync", u"DOWNLOAD", GetURL(p_URL), nullptr, p_Token, p_Callback, p_DontRetry, nullptr))); + Unity::MTCoroutineStarter::EnqueueFromThread(custom_types::Helpers::CoroutineHelper::New(Coroutine_DoRequest(shared_from_this(), u"DownloadAsync", u"DOWNLOAD", GetURL(url), nullptr, token, callback, dontRetry, nullptr))); } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /// @brief Get URL - /// @param p_URL Request URL - std::u16string WebClientUnity::GetURL(std::u16string_view p_URL) + /// @param url Request URL + std::u16string WebClientUnity::GetURL(std::u16string_view url) { - if (m_BaseAddress.size() == 0) return std::u16string(p_URL); - if (p_URL.find(u"://") != std::u16string::npos) return std::u16string(p_URL); - if (m_BaseAddress[m_BaseAddress.size() - 1] == '/') return m_BaseAddress + std::u16string(p_URL); + if (m_BaseAddress.size() == 0) return std::u16string(url); + if (url.find(u"://") != std::u16string::npos) return std::u16string(url); + if (m_BaseAddress[m_BaseAddress.size() - 1] == '/') return m_BaseAddress + std::u16string(url); - return m_BaseAddress + u"/" + std::u16string(p_URL); + return m_BaseAddress + u"/" + std::u16string(url); } /// @brief Safe URL parsing - /// @param p_URL Source URL - std::u16string WebClientUnity::SafeURL(std::u16string_view p_URL) + /// @param url Source URL + std::u16string WebClientUnity::SafeURL(std::u16string_view url) { - auto l_Position = p_URL.find_first_of('?'); + auto l_Position = url.find_first_of('?'); if (l_Position != std::u16string::npos) - return std::u16string(p_URL).substr(0, l_Position); + return std::u16string(url).substr(0, l_Position); - return std::u16string(p_URL); + return std::u16string(url); } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /// @brief Prepare request - /// @param p_Request Request to prepare - /// @param p_IsDownload Is a download request? - void WebClientUnity::PrepareRequest(UnityWebRequest* p_Request, bool p_IsDownload) + /// @param request Request to prepare + /// @param isDownload Is a download request? + void WebClientUnity::PrepareRequest(UnityWebRequest* request, bool isDownload) { - if (p_Request->get_downloadHandler() == nullptr) - p_Request->set_downloadHandler(DownloadHandlerBuffer::New_ctor()); + if (request->get_downloadHandler() == nullptr) + request->set_downloadHandler(DownloadHandlerBuffer::New_ctor()); - p_Request->set_timeout(p_IsDownload ? DownloadTimeout : m_Timeout); + request->set_timeout(isDownload ? DownloadTimeout : m_Timeout); std::lock_guard l_Guard(m_HeadersLock); static auto s_UnityWebRequest_InternalSetRequestHeader = il2cpp_utils::resolve_icall("UnityEngine.Networking.UnityWebRequest::InternalSetRequestHeader"); for (auto const& [l_Header, l_Value] : m_Headers) - s_UnityWebRequest_InternalSetRequestHeader(p_Request, l_Header, l_Value); + s_UnityWebRequest_InternalSetRequestHeader(request, l_Header, l_Value); } /// @brief Do request - /// @param p_DebugName Method name for logs - /// @param p_HttpMethod Http method - /// @param p_URL Target URL - /// @param p_Content Optional content to post - /// @param p_Token Cancellation token - /// @param p_Callback Callback - /// @param p_DontRetry Should not retry - /// @param p_Progress Progress reporter - custom_types::Helpers::Coroutine WebClientUnity::Coroutine_DoRequest(Ptr p_Self, - std::u16string p_DebugName, - std::u16string p_HttpMethod, - std::u16string p_URL, - WebContent::Ptr p_Content, - CancellationToken p_Token, - _v::Action p_Callback, - bool p_DontRetry, - _v::Action p_Progress) + /// @param debugName Method name for logs + /// @param httpMethod Http method + /// @param url Target URL + /// @param content Optional content to post + /// @param token Cancellation token + /// @param callback Callback + /// @param dontRetry Should not retry + /// @param progress Progress reporter + custom_types::Helpers::Coroutine WebClientUnity::Coroutine_DoRequest(Ptr self, + std::u16string debugName, + std::u16string httpMethod, + std::u16string url, + WebContent::Ptr content, + CancellationToken token, + _v::Action callback, + bool dontRetry, + _v::Action progress) { #if DEBUG - ChatPlexSDK::Logger()->Debug(u"[CP_SDK.Network][WebClientUnity." + p_DebugName + u"] " + p_HttpMethod + u" " + p_URL); + ChatPlexSDK::Logger()->Debug(u"[CP_SDK.Network][WebClientUnity." + debugName + u"] " + httpMethod + u" " + url); #endif WebResponse::Ptr l_Reply = nullptr; - for (int l_RetryI = 1; l_RetryI <= p_Self->MaxRetry; l_RetryI++) + for (int l_RetryI = 1; l_RetryI <= self->MaxRetry; l_RetryI++) { - if (p_Token.get_IsCancellationRequested()) + if (token.get_IsCancellationRequested()) break; auto l_Request = _v::MonoPtr(nullptr); - if (p_HttpMethod == u"GET" || p_HttpMethod == u"DOWNLOAD") - l_Request = UnityWebRequest::Get(p_URL); - else if (p_HttpMethod == u"POST" || p_HttpMethod == u"PATCH") + if (httpMethod == u"GET" || httpMethod == u"DOWNLOAD") + l_Request = UnityWebRequest::Get(url); + else if (httpMethod == u"POST" || httpMethod == u"PATCH" || httpMethod == u"PUT") { - ChatPlexSDK::Logger()->Error(u"WebClientUnity POST & PATCH are disabled for now"); + ChatPlexSDK::Logger()->Error(u"WebClientUnity POST & PATCH & PUT are disabled for now"); throw std::runtime_error("WebClientUnity POST & PATCH are disabled for now"); /// TODO Disabled until fixed - /*static auto s_UploadHandler_InternalSetContentType = il2cpp_utils::resolve_icall("UnityEngine.Networking.UploadHandler::InternalSetContentType"); - auto l_UploadHandler = UploadHandlerRaw::New_ctor(p_Content->Bytes.Ptr()); - s_UploadHandler_InternalSetContentType(l_UploadHandler, p_Content->Type); + /*static auto s_UploadHandler_InternalSetContentType + = il2cpp_utils::resolve_icall("UnityEngine.Networking.UploadHandler::InternalSetContentType"); + + auto l_UploadHandler = UploadHandlerRaw::New_ctor(content->Bytes.Ptr()); + s_UploadHandler_InternalSetContentType(l_UploadHandler, content->Type); - l_Request = UnityWebRequest::New_ctor(p_URL, p_HttpMethod, DownloadHandlerBuffer::New_ctor(), l_UploadHandler);*/ + l_Request = UnityWebRequest::New_ctor(url, httpMethod, DownloadHandlerBuffer::New_ctor(), l_UploadHandler);*/ } - else if (p_HttpMethod == u"DELETE") - l_Request = UnityWebRequest::New_ctor(p_URL, p_HttpMethod, nullptr, nullptr); + else if (httpMethod == u"DELETE") + l_Request = UnityWebRequest::New_ctor(url, httpMethod, nullptr, nullptr); - p_Self->PrepareRequest(l_Request.Ptr(), p_HttpMethod == u"DOWNLOAD"); + self->PrepareRequest(l_Request.Ptr(), httpMethod == u"DOWNLOAD"); - if (!p_Progress.IsValid()) + if (!progress.IsValid()) co_yield reinterpret_cast(l_Request->SendWebRequest()); else { - try { p_Progress(0.0f); } catch (const std::exception&) { } + try { progress(0.0f); } catch (const std::exception&) { } l_Request->SendWebRequest(); auto l_Waiter = WaitForSecondsRealtime::New_ctor(0.05f); @@ -262,15 +273,15 @@ namespace CP_SDK::Network { static auto s_UnityWebRequest_GetDownloadProgress = il2cpp_utils::resolve_icall("UnityEngine.Networking.UnityWebRequest::GetDownloadProgress"); auto l_Progress = (!s_UnityWebRequest_IsExecuting(l_Request.Ptr()) && !l_Request->get_isDone()) ? -1.0f : s_UnityWebRequest_GetDownloadProgress(l_Request.Ptr()); - p_Progress(l_Progress); + progress(l_Progress); } catch (const std::exception&) { } - if (p_Token.get_IsCancellationRequested() || l_Request->get_isDone() || l_Request->get_result() == UnityWebRequest::Result::ProtocolError || l_Request->get_result() == UnityWebRequest::Result::ConnectionError) + if (token.get_IsCancellationRequested() || l_Request->get_isDone() || l_Request->get_result() == UnityWebRequest::Result::ProtocolError || l_Request->get_result() == UnityWebRequest::Result::ConnectionError) break; } while (true); } - if (p_Token.get_IsCancellationRequested()) + if (token.get_IsCancellationRequested()) break; l_Reply = std::make_shared(l_Request.Ptr()); @@ -283,7 +294,7 @@ namespace CP_SDK::Network { int l_TotalMilliseconds = (int)(l_Limits.Reset - DateTime.Now).TotalMilliseconds; if (l_TotalMilliseconds > 0) { - ChatPlexSDK::Logger()->Error(u"[CP_SDK.Network][WebClientUnity." + p_DebugName + u"] Request {SafeURL(p_URL)} was rate limited, retrying in {l_TotalMilliseconds}ms..."); + ChatPlexSDK::Logger()->Error(u"[CP_SDK.Network][WebClientUnity." + debugName + u"] Request {SafeURL(url)} was rate limited, retrying in {l_TotalMilliseconds}ms..."); co_yield WaitForSecondsRealtime::New_ctor(RetryInterval)->i_IEnumerator(); continue; @@ -293,32 +304,32 @@ namespace CP_SDK::Network { if (!l_Reply->IsSuccessStatusCode()) { - auto l_LogPrefix = u"[CP_SDK.Network][WebClientUnity." + p_DebugName + u"] Request " + p_Self->SafeURL(p_URL) + u" failed with code "; + auto l_LogPrefix = u"[CP_SDK.Network][WebClientUnity." + debugName + u"] Request " + self->SafeURL(url) + u" failed with code "; l_LogPrefix += StringW(std::to_string(l_Reply->StatusCode().value__)); l_LogPrefix += u":\"" + l_Reply->ReasonPhrase() + "\", "; - if (!l_Reply->ShouldRetry() || p_DontRetry) + if (!l_Reply->ShouldRetry() || dontRetry) { ChatPlexSDK::Logger()->Error(l_LogPrefix + u" not retrying"); break; } - ChatPlexSDK::Logger()->Error(l_LogPrefix + u" next try in " + (std::u16string)StringW(std::to_string(p_Self->RetryInterval)) + u" seconds..."); + ChatPlexSDK::Logger()->Error(l_LogPrefix + u" next try in " + (std::u16string)StringW(std::to_string(self->RetryInterval)) + u" seconds..."); - co_yield WaitForSecondsRealtime::New_ctor(p_Self->RetryInterval)->i___System__Collections__IEnumerator(); + co_yield WaitForSecondsRealtime::New_ctor(self->RetryInterval)->i___System__Collections__IEnumerator(); continue; } else { - if (p_Progress.IsValid()) - try { p_Progress(1.0f); } catch (const std::exception&) { } + if (progress.IsValid()) + try { progress(1.0f); } catch (const std::exception&) { } break; } } - if (!p_Token.get_IsCancellationRequested() && p_Callback.IsValid()) - Unity::MTThreadInvoker::EnqueueOnThread([=]() -> void { p_Callback(l_Reply); }); + if (!token.get_IsCancellationRequested() && callback.IsValid()) + Unity::MTThreadInvoker::EnqueueOnThread([=]() -> void { callback(l_Reply); }); } } ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/src/CP_SDK/Network/WebContent.cpp b/src/CP_SDK/Network/WebContent.cpp index c9ef6b2..f3eb29c 100644 --- a/src/CP_SDK/Network/WebContent.cpp +++ b/src/CP_SDK/Network/WebContent.cpp @@ -1,4 +1,5 @@ #include "CP_SDK/Network/WebContent.hpp" +#include "CP_SDK/Utils/Json.hpp" #include #include @@ -31,5 +32,12 @@ namespace CP_SDK::Network { auto l_Array = Encoding::get_UTF8()->GetBytes(p_Content).operator Array *(); return std::make_shared(CP_SDK_PRIV_TAG_VAL(), l_Array, u"application/json; charset=utf-8"); } + /// @brief Constructor from Json + /// @param content Json content + WebContent::Ptr WebContent::FromJson(std::shared_ptr<_v::Json::U16Document>& content) + { + auto l_Array = Encoding::get_UTF8()->GetBytes(content ? _v::Json::ToU16String(*content, false) : u"").operator Array *(); + return std::make_shared(CP_SDK_PRIV_TAG_VAL(), l_Array, u"application/json; charset=utf-8"); + } } ///< namespace CP_SDK::Network \ No newline at end of file diff --git a/src/CP_SDK/Network/WebResponse.cpp b/src/CP_SDK/Network/WebResponse.cpp index 5ce5aa5..f97e320 100644 --- a/src/CP_SDK/Network/WebResponse.cpp +++ b/src/CP_SDK/Network/WebResponse.cpp @@ -1,6 +1,8 @@ #include "CP_SDK/Network/WebResponse.hpp" -#include "CP_SDK/ChatPlexSDK.hpp" +#include +#include +#include #include #include #include @@ -64,13 +66,10 @@ namespace CP_SDK::Network { //////////////////////////////////////////////////////////////////////////// /// Constructor - /// @p_Request: Reply status + /// @param request: Reply status WebResponse::WebResponse(UnityWebRequest * p_Request) { - m_StatusCode = (HttpStatusCode)p_Request->get_responseCode(); - m_IsSuccessStatusCode = !(p_Request->get_result() == UnityWebRequest::Result::ProtocolError && p_Request->get_result() == UnityWebRequest::Result::ConnectionError); - m_ShouldRetry = IsSuccessStatusCode() ? false : (p_Request->get_responseCode() < 400 || p_Request->get_responseCode() >= 500); - m_BodyBytes = reinterpret_cast<::Array*>(p_Request->get_downloadHandler()->GetData().convert()); + m_StatusCode = (HttpStatusCode)p_Request->get_responseCode(); if (p_Request->get_result() == UnityWebRequest::Result::ConnectionError || p_Request->get_result() == UnityWebRequest::Result::ProtocolError) { @@ -78,7 +77,42 @@ namespace CP_SDK::Network { m_ReasonPhrase = u"HTTP/1.1 " + std::to_string(m_StatusCode.value__) + u" " + p_Request->GetHTTPStatusString(m_StatusCode.value__); else m_ReasonPhrase = p_Request->GetWebErrorString(p_Request->GetError()); + + m_IsSuccessStatusCode = false; } + else + m_IsSuccessStatusCode = ((int)p_Request->get_responseCode() >= 200) && ((int)p_Request->get_responseCode() <= 299); + + m_ShouldRetry = IsSuccessStatusCode() ? false : (p_Request->get_responseCode() < 400 || p_Request->get_responseCode() >= 500); + m_BodyBytes = reinterpret_cast<::Array*>(p_Request->get_downloadHandler()->GetData().convert()); + } + /// @brief Constructor + /// @param curlPerformResult CURL perform result + /// @param curlInstance CURL instance + /// @param data Response data + WebResponse::WebResponse(long curlPerformResult, void* curlInstance, std::vector* data) + { + auto l_CURLInstance = reinterpret_cast(curlInstance); + + if (curlPerformResult != CURLE_OK) + { + m_StatusCode = -curlPerformResult; + m_ReasonPhrase = Utils::StrToU16Str(curl_easy_strerror(static_cast(curlPerformResult))); + m_IsSuccessStatusCode = false; + m_ShouldRetry = false; + } + else + { + long l_HTTPCode(0); + curl_easy_getinfo(l_CURLInstance, CURLINFO_RESPONSE_CODE, &l_HTTPCode); + + m_StatusCode = l_HTTPCode; + m_IsSuccessStatusCode = ((int)l_HTTPCode >= 200) && ((int)l_HTTPCode <= 299); + m_ShouldRetry = IsSuccessStatusCode() ? false : (l_HTTPCode < 400 || l_HTTPCode >= 500); + } + + m_BodyBytes = ::Array::NewLength(data->size()); + memcpy(m_BodyBytes->_values, data->data(), data->size()); } } ///< namespace CP_SDK::Network diff --git a/src/CP_SDK/UI/Views/SettingsLeftView.cpp b/src/CP_SDK/UI/Views/SettingsLeftView.cpp index 3169108..f83b50f 100644 --- a/src/CP_SDK/UI/Views/SettingsLeftView.cpp +++ b/src/CP_SDK/UI/Views/SettingsLeftView.cpp @@ -1,9 +1,14 @@ #include "CP_SDK/UI/Views/SettingsLeftView.hpp" #include "CP_SDK/Unity/SpriteU.hpp" +#include "CP_SDK/Unity/MTMainThreadInvoker.hpp" +#include +#include using namespace CP_SDK::XUI; using namespace UnityEngine; +#include "assets.hpp" + namespace CP_SDK::UI::Views { CP_SDK_IL2CPP_INHERIT_INIT(SettingsLeftView); @@ -14,7 +19,8 @@ namespace CP_SDK::UI::Views { /// @brief Constructor CP_SDK_IL2CPP_DECLARE_CTOR_IMPL(SettingsLeftView) { - OnViewCreation = {this, &SettingsLeftView::OnViewCreation_Impl}; + OnViewCreation = {this, &SettingsLeftView::OnViewCreation_Impl}; + OnViewDeactivation = {this, &SettingsLeftView::OnViewDeactivation_Impl}; } /// @brief Destructor CP_SDK_IL2CPP_DECLARE_DTOR_MONOBEHAVIOUR_IMPL(SettingsLeftView) @@ -28,13 +34,179 @@ namespace CP_SDK::UI::Views { /// @brief On view creation void SettingsLeftView::OnViewCreation_Impl() { + auto l_Sprite = Unity::SpriteU::CreateFromRaw(Assets::ChatPlexLogoTransparent_png); + Templates::FullRectLayout({ - Templates::TitleBar(u"Tools"), + Templates::TitleBar(u"ChatPlex Account"), + + XUIPrimaryButton::Make(u"") + ->SetBackgroundSprite(nullptr) + ->SetIconSprite(l_Sprite) + ->SetWidth(52) + ->SetHeight(52) + ->AsShared(), + + XUIText::Make(u"Not connected") + ->Bind(&m_StatusText) + ->AsShared(), + + XUIText::Make(u" ") + ->Bind(&m_SubscriptionText) + ->AsShared(), + + XUIVLayout::Make({ + XUIPrimaryButton::Make(u"Connect", {this, &SettingsLeftView::OnPrimaryButtonPressed}) + ->Bind(&m_PrimaryButton) + ->AsShared(), + XUISecondaryButton::Make(u"Disconnect", {this, &SettingsLeftView::OnSecondaryButtonPressed}) + ->Bind(&m_SecondaryButton) + ->AsShared() + }) + ->SetWidth(60.0f) + ->SetPadding(0) + ->ForEachDirect([](XUIPrimaryButton* y) -> void + { + y->SetHeight(8.0f); + y->OnReady([](Components::CPrimaryButton* x) -> void + { + x->CSizeFitter()->horizontalFit = ContentSizeFitter::FitMode::Unconstrained; + }); + }) + ->ForEachDirect([](XUISecondaryButton* y ) -> void + { + y->SetHeight(8.0f); + y->OnReady([](Components::CSecondaryButton* x) -> void + { + x->CSizeFitter()->horizontalFit = ContentSizeFitter::FitMode::Unconstrained; + }); + }) + ->AsShared() + }) + ->SetBackground(true, std::nullopt, true) + ->BuildUI(get_transform()); + + ChatPlexService::StateChanged += {this, &SettingsLeftView::ChatPlexService_StateChanged}; + + ChatPlexService_StateChanged(ChatPlexService::State(), ChatPlexService::State()); + } + /// @brief On view deactivation + void SettingsLeftView::OnViewDeactivation_Impl() + { + ChatPlexService::StateChanged -= {this, &SettingsLeftView::ChatPlexService_StateChanged}; + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief On primary button pressed + void SettingsLeftView::OnPrimaryButtonPressed() + { + if (ChatPlexService::State() == ChatPlexService::EState::Disconnected) + { + m_IsLinking = true; + ChatPlexService::StartLinking(); + ShowLoadingModal(u"Loading...", true, {this, &SettingsLeftView::OnLoadingCancel}); + } + else if (ChatPlexService::State() == ChatPlexService::EState::Error || ChatPlexService::State() == ChatPlexService::EState::Connected) + { + ChatPlexService::Refresh(); + } + } + /// @brief On secondary button pressed + void SettingsLeftView::OnSecondaryButtonPressed() + { + ChatPlexService::Disconnect(); + } + /// @brief On Loading cancel + void SettingsLeftView::OnLoadingCancel() + { + if (ChatPlexService::State() == ChatPlexService::EState::LinkRequest || ChatPlexService::State() == ChatPlexService::EState::LinkWait) + { + m_IsLinking = false; + ChatPlexService::StopLinking(); + } + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// @brief On ChatPlex service state change + /// @param oldState Old state + /// @param newState New state + void SettingsLeftView::ChatPlexService_StateChanged(ChatPlexService::EState oldState, ChatPlexService::EState newState) + { + Unity::MTMainThreadInvoker::Enqueue([this, oldState, newState]() -> void + { + if (m_IsLinking) + { + if (newState == ChatPlexService::EState::LinkRequest) + ShowLoadingModal(u"Creating link request...", true, {this, &SettingsLeftView::OnLoadingCancel}); + else if (newState == ChatPlexService::EState::LinkWait) + ShowLoadingModal( + std::u16string(u"Go to https://chatplex.org/link and the input following code\n") + ChatPlexService::LinkCode(), + true, + {this, &SettingsLeftView::OnLoadingCancel} + ); + else if (newState == ChatPlexService::EState::Error) + { + m_IsLinking = false; + + CloseLoadingModal(); + ShowMessageModal(u"Error: " + ChatPlexService::LastError()); + } + else + { + m_IsLinking = false; + CloseLoadingModal(); + } + } + + switch (newState) + { + case ChatPlexService::EState::Disconnected: + m_StatusText->SetColor(Color::get_red()); + m_StatusText->SetText(u"Disconected!"); + m_PrimaryButton->SetInteractable(true); + m_PrimaryButton->SetText(u"Connect"); + m_SecondaryButton->SetInteractable(false); + break; + + case ChatPlexService::EState::Error: + m_StatusText->SetColor(Color::get_red()); + m_StatusText->SetText(u"Disconected, error!"); + m_PrimaryButton->SetInteractable(true); + m_PrimaryButton->SetText(u"Connect"); + m_SecondaryButton->SetInteractable(false); + break; + + case ChatPlexService::EState::Connecting: + m_StatusText->SetColor(Color::get_blue()); + m_StatusText->SetText(u"Connecting..."); + m_PrimaryButton->SetInteractable(false); + m_PrimaryButton->SetText(u"Connect"); + m_SecondaryButton->SetInteractable(false); + break; + + case ChatPlexService::EState::LinkRequest: + case ChatPlexService::EState::LinkWait: + m_StatusText->SetColor(Color::get_blue()); + m_StatusText->SetText(u"Linking account..."); + m_PrimaryButton->SetInteractable(false); + m_PrimaryButton->SetText(u"Connect"); + m_SecondaryButton->SetInteractable(false); + break; + + case ChatPlexService::EState::Connected: + m_StatusText->SetColor(Color::get_green()); + m_StatusText->SetText(u"Connected!"); + m_PrimaryButton->SetInteractable(true); + m_PrimaryButton->SetText(u"Refresh"); + m_SecondaryButton->SetInteractable(true); + break; + } - XUIText::Make(u"No available tools at the moment!")->AsShared() - }) - ->SetBackground(true, std::nullopt, true) - ->BuildUI(get_transform()); + m_SubscriptionText->Element()->SetText(ChatPlexService::ActiveSubscription()); + }); } } ///< namespace CP_SDK::UI::Views \ No newline at end of file diff --git a/src/CP_SDK/Utils/Il2cpp.cpp b/src/CP_SDK/Utils/Il2cpp.cpp index 7af7769..bf5e22a 100644 --- a/src/CP_SDK/Utils/Il2cpp.cpp +++ b/src/CP_SDK/Utils/Il2cpp.cpp @@ -1,6 +1,8 @@ #include "CP_SDK/Utils/Il2cpp.hpp" #include "CP_SDK/ChatPlexSDK.hpp" +#include "CP_SDK/Logging/PaperLogger.hpp" + namespace CP_SDK::Utils { std::vector Hooks::m_InstalledFuncs; diff --git a/src/CP_SDK_BS/Game/LevelSelection.cpp b/src/CP_SDK_BS/Game/LevelSelection.cpp index 8d36517..d925926 100644 --- a/src/CP_SDK_BS/Game/LevelSelection.cpp +++ b/src/CP_SDK_BS/Game/LevelSelection.cpp @@ -195,23 +195,23 @@ namespace CP_SDK_BS::Game { try { m_PreventLevelSearchViewController_didStartLoadingEvent = true; - p_LevelSearchViewController->ResetAllFilterSettings(false); + //p_LevelSearchViewController->ResetAllFilterSettings(false); auto l_Filter = GlobalNamespace::LevelFilter(); - l_Filter.songOwned = false; + l_Filter.songOwned = true; l_Filter.songNotOwned = false; l_Filter.songUnplayed = false; - l_Filter.difficulties = _u::BeatmapDifficultyMask(); - l_Filter.songPacks = _u::SongPackMask(); + l_Filter.difficulties = _u::BeatmapDifficultyMask(0); + l_Filter.songPacks = _u::SongPackMask::get_all(); l_Filter.characteristicSerializedName = nullptr; l_Filter.minBpm = 0.0f; l_Filter.maxBpm = 0.0f; - l_Filter.sensitivity = _u::PlayerSensitivityFlag(); + l_Filter.sensitivity = _u::PlayerSensitivityFlag::Unknown; l_Filter.limitIds = ArrayW({ m_PendingFilterSong->___levelID }); l_Filter.searchText = u""; - p_LevelSearchViewController->ResetAllFilterSettings(false); + //p_LevelSearchViewController->ResetAllFilterSettings(false); p_LevelSearchViewController->Refresh( byref(l_Filter) );