diff --git a/README.md b/README.md index 9fd5725..0929fc5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ## 📊 Features +### RustPlusApi + This is a list of the features that the Rust+ API provides: - `GetEntityInfo` Get current state of a Smart Device @@ -16,6 +18,10 @@ This is a list of the features that the Rust+ API provides: Feel free to **explore** the `./RustPlusApi/Examples/` folder to see how to **use** the API. +### RustPlusApi.Fcm + + + ## 🖊️ Versions ![skills](https://skillicons.dev/icons?i=cs,dotnet) @@ -24,14 +30,20 @@ Feel free to **explore** the `./RustPlusApi/Examples/` folder to see how to **us ## 📍 NuGet -Simply use this nuget by running the following command: +Simply use this library in your project by running the following commands: ```dotnet dotnet add package RustPlusApi ``` +```dotnet +dotnet add package RustPlusApi.Fcm +``` + ## ⚙️ Usage +### RustPlusApi + First, instantiate the `RustPlus` class with the necessary parameters: ```csharp @@ -43,9 +55,11 @@ Parameters: - `server`: The IP address of the Rust+ server. - `port`: The port dedicated for the Rust+ companion app (not the one used to connect in-game). - `playerId`: Your Steam ID. -- `playerToken`: Your player token acquired with FCM. +- `playerToken`\*: Your player token acquired with FCM. - `useFacepunchProxy`: Specifies whether to use the Facepunch proxy. Default is false. +\* To aquired the player token, you can use the `FcmListener` and received at least one notification. Go to the next section to see how to use it. + Then, connect to the Rust+ server: ```csharp @@ -68,8 +82,59 @@ Remember to dispose the `RustPlus` instance when you're done: rustPlusApi.Dispose(); ``` +--- + +### RustPlusApi.Fcm + +First, instantiate the `FcmListener` class with the necessary parameters: + +```csharp +var fcmListener = new FcmListener(credentials, persistentIds); +``` + +Parameters: + +- `credentials`: The `Credentials`\* object containing the FCM & GCM credentials + the keys to decrypt the notification. +- `persistentIds`: A list of notification IDs that should be ignored. Default is null. + +\* Go to the [Credentials](#credentials) section to know how to get it. + +Then, connect to the FCM socket: + +```csharp +await fcmListener.ConnectAsync(); +``` + +You can subscribe to events to handle connection, disconnection, errors, and received messages: + +```csharp +fcmListener.Connecting += (sender, e) => { /* handle connecting event */ }; +fcmListener.Connected += (sender, e) => { /* handle connected event */ }; +fcmListener.Disconnected += (sender, e) => { /* handle disconnected event */ }; +fcmListener.ErrorOccurred += (sender, e) => { /* handle error event */ }; +fcmListener.MessageReceived += (sender, e) => { /* handle received message event */ }; +``` + +Remember to dispose the `FcmListener` instance when you're done: + +```csharp +fcmListener.Dispose(); +``` + +### Credentials + +Currenlty, there is not simple way to get the FCM & GCM credentials using **.NET**. +I've planned to implement a solution but it's not ready yet. + +To use this library, you need to get the FCM & GCM credentials manually. +To do so I recommand you to use [this project](https://github.com/liamcottle/rustplus.js) to get the credentials. + +I'm sorry for the inconvenience but since the API is not fully complete it's the easiest way. + ## 🖼️ Credits -This project is grandly inspired by [liamcottle/rustplus.js](https://github.com/liamcottle/rustplus.js). +*This project is grandly inspired by [liamcottle/rustplus.js](https://github.com/liamcottle/rustplus.js).* + +Special thanks to [**Versette**](https://github.com/Versette) for his work on the `RustPlusApi.Fcm` socket. * Author: [**HandyS11**](https://github.com/HandyS11) \ No newline at end of file diff --git a/RustPlusApi.sln b/RustPlusApi.sln index 1da1db5..3616f7c 100644 --- a/RustPlusApi.sln +++ b/RustPlusApi.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RustPlusApi", "RustPlusApi\ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{BC948ADE-1674-4955-B27C-F0E96100978E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetEntityInfo", "RustPlusApi\Examples\GetEntityInfo\GetEntityInfo.csproj", "{A53B0FAC-F59B-4021-9A67-997D1F5727D7}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "__Constants", "RustPlusApi\Examples\__Constants\__Constants.csproj", "{DF3F9213-6F6D-4832-8738-CA106BE49860}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetInfo", "RustPlusApi\Examples\GetInfo\GetInfo.csproj", "{AB17A661-1D1D-496C-A39C-B31F3BAEADA1}" @@ -21,13 +19,67 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetTime", "RustPlusApi\Exam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SendTeamChat", "RustPlusApi\Examples\SendTeamChat\SendTeamChat.csproj", "{D70DD514-081F-4055-8779-6626DA96D18D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetEntityChanges", "RustPlusApi\Examples\GetEntityChanges\GetEntityChanges.csproj", "{C1BA15FE-9F2E-4A9A-BBB3-9161FCFEE344}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SetEntityValue", "RustPlusApi\Examples\SetEntityValue\SetEntityValue.csproj", "{4E5DCE64-A8E5-42C0-8786-EA1FF36CACE4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetMapMarkers", "RustPlusApi\Examples\GetMapMarkers\GetMapMarkers.csproj", "{DD34FAE8-30AA-4787-A6D1-77C330A0928F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RustPlusApi.Fcm", "RustPlusApi\RustPlusApi.Fcm\RustPlusApi.Fcm.csproj", "{CFDFB335-A783-4F82-8094-3ED14E3EBC49}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Fcm", "Fcm", "{A4EF8656-723B-4E2E-9457-57DCB24CB409}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FcmRegister", "RustPlusApi\Examples\Fcm\FcmRegister\FcmRegister.csproj", "{FF9758F9-2500-4CE1-AACE-8C67134046F4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FcmListener", "RustPlusApi\Examples\Fcm\FcmListener\FcmListener.csproj", "{5B175E6B-4118-422D-BF08-F94B7337D229}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetTeamChat", "RustPlusApi\Examples\GetTeamChat\GetTeamChat.csproj", "{1A186504-8461-4F19-BDC5-C3DB90A554D6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PromoteToLeader", "RustPlusApi\Examples\PromoteToLeader\PromoteToLeader.csproj", "{3DE93E0D-DAF5-48E7-A353-BD99ABB942DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetTeamChatChanges", "RustPlusApi\Examples\GetTeamChatChanges\GetTeamChatChanges.csproj", "{D39626D0-079C-40C6-A260-09852E11C70A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetSmartSwitchInfo", "RustPlusApi\Examples\GetSmartSwitchInfo\GetSmartSwitchInfo.csproj", "{CCC95529-EA3A-4FF3-945A-97151848F6BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetAlarmInfo", "RustPlusApi\Examples\GetAlarmInfo\GetAlarmInfo.csproj", "{B9E7E802-867B-40E4-BEEE-807735F091E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetStorageMonitorInfo", "RustPlusApi\Examples\GetStorageMonitorInfo\GetStorageMonitorInfo.csproj", "{51CD85C0-0240-4C7C-BBDC-0DCF5234299E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Legacy", "Legacy", "{EBB661D0-93FF-4742-AF91-9036027CD136}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetEntityInfoLegacy", "RustPlusApi\Examples\Legacy\GetEntityInfoLegacy\GetEntityInfoLegacy.csproj", "{39EC2C27-0351-4099-A07B-237DD8C73FA6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetEntityChangesLegacy", "RustPlusApi\Examples\Legacy\GetEntityChangesLegacy\GetEntityChangesLegacy.csproj", "{443F0C99-2BC7-44CB-9DE8-A292B10B9CE3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetInfoLegacy", "RustPlusApi\Examples\Legacy\GetInfoLegacy\GetInfoLegacy.csproj", "{17F33FF0-AFE2-4DE4-81E9-6804A2B210A7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetMapLegacy", "RustPlusApi\Examples\Legacy\GetMapLegacy\GetMapLegacy.csproj", "{4C4D800C-1DF9-4139-BD7C-2FE0D78FF30C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetSmartSwitchChanges", "RustPlusApi\Examples\GetSmartSwitchChanges\GetSmartSwitchChanges.csproj", "{A7852D6E-9371-4026-B3BE-1BE264718D1D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetStorageMonitorChanges", "RustPlusApi\Examples\GetStorageMonitorChanges\GetStorageMonitorChanges.csproj", "{2E65E71F-1C4E-4813-86C9-C5F5D3E60187}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetMapMarkersLegacy", "RustPlusApi\Examples\Legacy\GetMapMarkersLegacy\GetMapMarkersLegacy.csproj", "{70F7555D-DEC9-45E7-BB64-CB31704A2C0A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetTeamChatLegacy", "RustPlusApi\Examples\Legacy\GetTeamChatLegacy\GetTeamChatLegacy.csproj", "{2223E2FE-9CAA-45DF-9F47-E52371018077}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetTeamInfoLegacy", "RustPlusApi\Examples\Legacy\GetTeamInfoLegacy\GetTeamInfoLegacy.csproj", "{4E63E17B-7FD4-4B58-9F22-265B0EE92672}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetTimeLegacy", "RustPlusApi\Examples\Legacy\GetTimeLegacy\GetTimeLegacy.csproj", "{4255A1A9-C7D8-45F5-81C3-BBCA96886DAA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PromoteToLeaderLegacy", "RustPlusApi\Examples\Legacy\PromoteToLeader\PromoteToLeaderLegacy.csproj", "{80F46159-40F8-4CA3-90A1-EAE62E6BF2F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SendTeamMessageLegacy", "RustPlusApi\Examples\Legacy\SendTeamMessageLegacy\SendTeamMessageLegacy.csproj", "{232323AD-2A44-4EF4-AD71-100563083FAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetTeamChatChangesLegacy", "RustPlusApi\Examples\Legacy\GetTeamChatChangesLegacy\GetTeamChatChangesLegacy.csproj", "{F8EDEF6A-5EF8-4D01-9C6E-FB379ACF9C9E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetClanChatLegacy", "RustPlusApi\Examples\Legacy\GetClanChatLegacy\GetClanChatLegacy.csproj", "{D90102B3-1F28-42C9-AE3D-D19E689C4D4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetClanChatChangesLegacy", "RustPlusApi\Examples\Legacy\GetClanChatChangesLegacy\GetClanChatChangesLegacy.csproj", "{248A7713-161F-4A78-9C33-14E5470DFB08}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetEntityValue", "RustPlusApi\Examples\SetEntityValue\SetEntityValue.csproj", "{4E5DCE64-A8E5-42C0-8786-EA1FF36CACE4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetEntityValueLegacy", "RustPlusApi\Examples\Legacy\SetEntityValueLegacy\SetEntityValueLegacy.csproj", "{F4BB95F4-2171-4087-889D-F65158AFD713}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrobeEntity", "RustPlusApi\Examples\StrobeEntity\StrobeEntity.csproj", "{3C4E8016-7B7F-4C5E-A5CA-A92A3797AA2B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StrobeEntityLegacy", "RustPlusApi\Examples\Legacy\StrobeEntityLegacy\StrobeEntityLegacy.csproj", "{9A41A0A3-9FAD-42E2-BD22-E3B4E2DBA161}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetMapMarkers", "RustPlusApi\Examples\GetMapMarkers\GetMapMarkers.csproj", "{DD34FAE8-30AA-4787-A6D1-77C330A0928F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToggleEntityLegacy", "RustPlusApi\Examples\Legacy\ToggleEntityLegacy\ToggleEntityLegacy.csproj", "{172AAEAA-0F46-457C-9494-8BBEE5377D66}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,10 +91,6 @@ Global {2E744AA2-4D20-4B44-A79E-AC1CC56E848E}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E744AA2-4D20-4B44-A79E-AC1CC56E848E}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E744AA2-4D20-4B44-A79E-AC1CC56E848E}.Release|Any CPU.Build.0 = Release|Any CPU - {A53B0FAC-F59B-4021-9A67-997D1F5727D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A53B0FAC-F59B-4021-9A67-997D1F5727D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A53B0FAC-F59B-4021-9A67-997D1F5727D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A53B0FAC-F59B-4021-9A67-997D1F5727D7}.Release|Any CPU.Build.0 = Release|Any CPU {DF3F9213-6F6D-4832-8738-CA106BE49860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DF3F9213-6F6D-4832-8738-CA106BE49860}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF3F9213-6F6D-4832-8738-CA106BE49860}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -67,38 +115,163 @@ Global {D70DD514-081F-4055-8779-6626DA96D18D}.Debug|Any CPU.Build.0 = Debug|Any CPU {D70DD514-081F-4055-8779-6626DA96D18D}.Release|Any CPU.ActiveCfg = Release|Any CPU {D70DD514-081F-4055-8779-6626DA96D18D}.Release|Any CPU.Build.0 = Release|Any CPU - {C1BA15FE-9F2E-4A9A-BBB3-9161FCFEE344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1BA15FE-9F2E-4A9A-BBB3-9161FCFEE344}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1BA15FE-9F2E-4A9A-BBB3-9161FCFEE344}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1BA15FE-9F2E-4A9A-BBB3-9161FCFEE344}.Release|Any CPU.Build.0 = Release|Any CPU {4E5DCE64-A8E5-42C0-8786-EA1FF36CACE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E5DCE64-A8E5-42C0-8786-EA1FF36CACE4}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E5DCE64-A8E5-42C0-8786-EA1FF36CACE4}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E5DCE64-A8E5-42C0-8786-EA1FF36CACE4}.Release|Any CPU.Build.0 = Release|Any CPU - {3C4E8016-7B7F-4C5E-A5CA-A92A3797AA2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C4E8016-7B7F-4C5E-A5CA-A92A3797AA2B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C4E8016-7B7F-4C5E-A5CA-A92A3797AA2B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C4E8016-7B7F-4C5E-A5CA-A92A3797AA2B}.Release|Any CPU.Build.0 = Release|Any CPU {DD34FAE8-30AA-4787-A6D1-77C330A0928F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DD34FAE8-30AA-4787-A6D1-77C330A0928F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DD34FAE8-30AA-4787-A6D1-77C330A0928F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DD34FAE8-30AA-4787-A6D1-77C330A0928F}.Release|Any CPU.Build.0 = Release|Any CPU + {CFDFB335-A783-4F82-8094-3ED14E3EBC49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFDFB335-A783-4F82-8094-3ED14E3EBC49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFDFB335-A783-4F82-8094-3ED14E3EBC49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFDFB335-A783-4F82-8094-3ED14E3EBC49}.Release|Any CPU.Build.0 = Release|Any CPU + {FF9758F9-2500-4CE1-AACE-8C67134046F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF9758F9-2500-4CE1-AACE-8C67134046F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF9758F9-2500-4CE1-AACE-8C67134046F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF9758F9-2500-4CE1-AACE-8C67134046F4}.Release|Any CPU.Build.0 = Release|Any CPU + {5B175E6B-4118-422D-BF08-F94B7337D229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B175E6B-4118-422D-BF08-F94B7337D229}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B175E6B-4118-422D-BF08-F94B7337D229}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B175E6B-4118-422D-BF08-F94B7337D229}.Release|Any CPU.Build.0 = Release|Any CPU + {1A186504-8461-4F19-BDC5-C3DB90A554D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A186504-8461-4F19-BDC5-C3DB90A554D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A186504-8461-4F19-BDC5-C3DB90A554D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A186504-8461-4F19-BDC5-C3DB90A554D6}.Release|Any CPU.Build.0 = Release|Any CPU + {3DE93E0D-DAF5-48E7-A353-BD99ABB942DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DE93E0D-DAF5-48E7-A353-BD99ABB942DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DE93E0D-DAF5-48E7-A353-BD99ABB942DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DE93E0D-DAF5-48E7-A353-BD99ABB942DE}.Release|Any CPU.Build.0 = Release|Any CPU + {D39626D0-079C-40C6-A260-09852E11C70A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D39626D0-079C-40C6-A260-09852E11C70A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D39626D0-079C-40C6-A260-09852E11C70A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D39626D0-079C-40C6-A260-09852E11C70A}.Release|Any CPU.Build.0 = Release|Any CPU + {CCC95529-EA3A-4FF3-945A-97151848F6BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCC95529-EA3A-4FF3-945A-97151848F6BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCC95529-EA3A-4FF3-945A-97151848F6BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCC95529-EA3A-4FF3-945A-97151848F6BB}.Release|Any CPU.Build.0 = Release|Any CPU + {B9E7E802-867B-40E4-BEEE-807735F091E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9E7E802-867B-40E4-BEEE-807735F091E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9E7E802-867B-40E4-BEEE-807735F091E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9E7E802-867B-40E4-BEEE-807735F091E3}.Release|Any CPU.Build.0 = Release|Any CPU + {51CD85C0-0240-4C7C-BBDC-0DCF5234299E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51CD85C0-0240-4C7C-BBDC-0DCF5234299E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51CD85C0-0240-4C7C-BBDC-0DCF5234299E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51CD85C0-0240-4C7C-BBDC-0DCF5234299E}.Release|Any CPU.Build.0 = Release|Any CPU + {39EC2C27-0351-4099-A07B-237DD8C73FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39EC2C27-0351-4099-A07B-237DD8C73FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39EC2C27-0351-4099-A07B-237DD8C73FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39EC2C27-0351-4099-A07B-237DD8C73FA6}.Release|Any CPU.Build.0 = Release|Any CPU + {443F0C99-2BC7-44CB-9DE8-A292B10B9CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {443F0C99-2BC7-44CB-9DE8-A292B10B9CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {443F0C99-2BC7-44CB-9DE8-A292B10B9CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {443F0C99-2BC7-44CB-9DE8-A292B10B9CE3}.Release|Any CPU.Build.0 = Release|Any CPU + {17F33FF0-AFE2-4DE4-81E9-6804A2B210A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17F33FF0-AFE2-4DE4-81E9-6804A2B210A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17F33FF0-AFE2-4DE4-81E9-6804A2B210A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17F33FF0-AFE2-4DE4-81E9-6804A2B210A7}.Release|Any CPU.Build.0 = Release|Any CPU + {4C4D800C-1DF9-4139-BD7C-2FE0D78FF30C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C4D800C-1DF9-4139-BD7C-2FE0D78FF30C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C4D800C-1DF9-4139-BD7C-2FE0D78FF30C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C4D800C-1DF9-4139-BD7C-2FE0D78FF30C}.Release|Any CPU.Build.0 = Release|Any CPU + {A7852D6E-9371-4026-B3BE-1BE264718D1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7852D6E-9371-4026-B3BE-1BE264718D1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7852D6E-9371-4026-B3BE-1BE264718D1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7852D6E-9371-4026-B3BE-1BE264718D1D}.Release|Any CPU.Build.0 = Release|Any CPU + {2E65E71F-1C4E-4813-86C9-C5F5D3E60187}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E65E71F-1C4E-4813-86C9-C5F5D3E60187}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E65E71F-1C4E-4813-86C9-C5F5D3E60187}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E65E71F-1C4E-4813-86C9-C5F5D3E60187}.Release|Any CPU.Build.0 = Release|Any CPU + {70F7555D-DEC9-45E7-BB64-CB31704A2C0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70F7555D-DEC9-45E7-BB64-CB31704A2C0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70F7555D-DEC9-45E7-BB64-CB31704A2C0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70F7555D-DEC9-45E7-BB64-CB31704A2C0A}.Release|Any CPU.Build.0 = Release|Any CPU + {2223E2FE-9CAA-45DF-9F47-E52371018077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2223E2FE-9CAA-45DF-9F47-E52371018077}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2223E2FE-9CAA-45DF-9F47-E52371018077}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2223E2FE-9CAA-45DF-9F47-E52371018077}.Release|Any CPU.Build.0 = Release|Any CPU + {4E63E17B-7FD4-4B58-9F22-265B0EE92672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E63E17B-7FD4-4B58-9F22-265B0EE92672}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E63E17B-7FD4-4B58-9F22-265B0EE92672}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E63E17B-7FD4-4B58-9F22-265B0EE92672}.Release|Any CPU.Build.0 = Release|Any CPU + {4255A1A9-C7D8-45F5-81C3-BBCA96886DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4255A1A9-C7D8-45F5-81C3-BBCA96886DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4255A1A9-C7D8-45F5-81C3-BBCA96886DAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4255A1A9-C7D8-45F5-81C3-BBCA96886DAA}.Release|Any CPU.Build.0 = Release|Any CPU + {80F46159-40F8-4CA3-90A1-EAE62E6BF2F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80F46159-40F8-4CA3-90A1-EAE62E6BF2F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80F46159-40F8-4CA3-90A1-EAE62E6BF2F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80F46159-40F8-4CA3-90A1-EAE62E6BF2F3}.Release|Any CPU.Build.0 = Release|Any CPU + {232323AD-2A44-4EF4-AD71-100563083FAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {232323AD-2A44-4EF4-AD71-100563083FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {232323AD-2A44-4EF4-AD71-100563083FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {232323AD-2A44-4EF4-AD71-100563083FAD}.Release|Any CPU.Build.0 = Release|Any CPU + {F8EDEF6A-5EF8-4D01-9C6E-FB379ACF9C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8EDEF6A-5EF8-4D01-9C6E-FB379ACF9C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8EDEF6A-5EF8-4D01-9C6E-FB379ACF9C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8EDEF6A-5EF8-4D01-9C6E-FB379ACF9C9E}.Release|Any CPU.Build.0 = Release|Any CPU + {D90102B3-1F28-42C9-AE3D-D19E689C4D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D90102B3-1F28-42C9-AE3D-D19E689C4D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D90102B3-1F28-42C9-AE3D-D19E689C4D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D90102B3-1F28-42C9-AE3D-D19E689C4D4E}.Release|Any CPU.Build.0 = Release|Any CPU + {248A7713-161F-4A78-9C33-14E5470DFB08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {248A7713-161F-4A78-9C33-14E5470DFB08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {248A7713-161F-4A78-9C33-14E5470DFB08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {248A7713-161F-4A78-9C33-14E5470DFB08}.Release|Any CPU.Build.0 = Release|Any CPU + {F4BB95F4-2171-4087-889D-F65158AFD713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4BB95F4-2171-4087-889D-F65158AFD713}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4BB95F4-2171-4087-889D-F65158AFD713}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4BB95F4-2171-4087-889D-F65158AFD713}.Release|Any CPU.Build.0 = Release|Any CPU + {9A41A0A3-9FAD-42E2-BD22-E3B4E2DBA161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A41A0A3-9FAD-42E2-BD22-E3B4E2DBA161}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A41A0A3-9FAD-42E2-BD22-E3B4E2DBA161}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A41A0A3-9FAD-42E2-BD22-E3B4E2DBA161}.Release|Any CPU.Build.0 = Release|Any CPU + {172AAEAA-0F46-457C-9494-8BBEE5377D66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {172AAEAA-0F46-457C-9494-8BBEE5377D66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {172AAEAA-0F46-457C-9494-8BBEE5377D66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {172AAEAA-0F46-457C-9494-8BBEE5377D66}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {A53B0FAC-F59B-4021-9A67-997D1F5727D7} = {BC948ADE-1674-4955-B27C-F0E96100978E} {DF3F9213-6F6D-4832-8738-CA106BE49860} = {BC948ADE-1674-4955-B27C-F0E96100978E} {AB17A661-1D1D-496C-A39C-B31F3BAEADA1} = {BC948ADE-1674-4955-B27C-F0E96100978E} {4E87360D-AC39-4475-957F-D49018DB2DC8} = {BC948ADE-1674-4955-B27C-F0E96100978E} {70453D6E-9C19-41BC-8C96-0E20F919DB11} = {BC948ADE-1674-4955-B27C-F0E96100978E} {E9F90342-34DE-4166-9769-483A3FA1F144} = {BC948ADE-1674-4955-B27C-F0E96100978E} {D70DD514-081F-4055-8779-6626DA96D18D} = {BC948ADE-1674-4955-B27C-F0E96100978E} - {C1BA15FE-9F2E-4A9A-BBB3-9161FCFEE344} = {BC948ADE-1674-4955-B27C-F0E96100978E} {4E5DCE64-A8E5-42C0-8786-EA1FF36CACE4} = {BC948ADE-1674-4955-B27C-F0E96100978E} - {3C4E8016-7B7F-4C5E-A5CA-A92A3797AA2B} = {BC948ADE-1674-4955-B27C-F0E96100978E} {DD34FAE8-30AA-4787-A6D1-77C330A0928F} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {A4EF8656-723B-4E2E-9457-57DCB24CB409} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {FF9758F9-2500-4CE1-AACE-8C67134046F4} = {A4EF8656-723B-4E2E-9457-57DCB24CB409} + {5B175E6B-4118-422D-BF08-F94B7337D229} = {A4EF8656-723B-4E2E-9457-57DCB24CB409} + {1A186504-8461-4F19-BDC5-C3DB90A554D6} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {3DE93E0D-DAF5-48E7-A353-BD99ABB942DE} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {D39626D0-079C-40C6-A260-09852E11C70A} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {CCC95529-EA3A-4FF3-945A-97151848F6BB} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {B9E7E802-867B-40E4-BEEE-807735F091E3} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {51CD85C0-0240-4C7C-BBDC-0DCF5234299E} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {EBB661D0-93FF-4742-AF91-9036027CD136} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {39EC2C27-0351-4099-A07B-237DD8C73FA6} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {443F0C99-2BC7-44CB-9DE8-A292B10B9CE3} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {17F33FF0-AFE2-4DE4-81E9-6804A2B210A7} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {4C4D800C-1DF9-4139-BD7C-2FE0D78FF30C} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {A7852D6E-9371-4026-B3BE-1BE264718D1D} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {2E65E71F-1C4E-4813-86C9-C5F5D3E60187} = {BC948ADE-1674-4955-B27C-F0E96100978E} + {70F7555D-DEC9-45E7-BB64-CB31704A2C0A} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {2223E2FE-9CAA-45DF-9F47-E52371018077} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {4E63E17B-7FD4-4B58-9F22-265B0EE92672} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {4255A1A9-C7D8-45F5-81C3-BBCA96886DAA} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {80F46159-40F8-4CA3-90A1-EAE62E6BF2F3} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {232323AD-2A44-4EF4-AD71-100563083FAD} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {F8EDEF6A-5EF8-4D01-9C6E-FB379ACF9C9E} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {D90102B3-1F28-42C9-AE3D-D19E689C4D4E} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {248A7713-161F-4A78-9C33-14E5470DFB08} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {F4BB95F4-2171-4087-889D-F65158AFD713} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {9A41A0A3-9FAD-42E2-BD22-E3B4E2DBA161} = {EBB661D0-93FF-4742-AF91-9036027CD136} + {172AAEAA-0F46-457C-9494-8BBEE5377D66} = {EBB661D0-93FF-4742-AF91-9036027CD136} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A4B4251F-ADA4-418D-95B5-27BA99A307A3} diff --git a/RustPlusApi/Examples/Fcm/FcmListener/FcmListener.csproj b/RustPlusApi/Examples/Fcm/FcmListener/FcmListener.csproj new file mode 100644 index 0000000..c40f917 --- /dev/null +++ b/RustPlusApi/Examples/Fcm/FcmListener/FcmListener.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/RustPlusApi/Examples/Fcm/FcmListener/Program.cs b/RustPlusApi/Examples/Fcm/FcmListener/Program.cs new file mode 100644 index 0000000..da03dcb --- /dev/null +++ b/RustPlusApi/Examples/Fcm/FcmListener/Program.cs @@ -0,0 +1,38 @@ +using RustPlusApi.Fcm; +using RustPlusApi.Fcm.Data; + +var credentials = new Credentials +{ + Keys = new Keys + { + PrivateKey = "", + PublicKey = "", + AuthSecret = "", + }, + Fcm = new FcmCredentials + { + Token = "", + PushSet = "", + }, + Gcm = new GcmCredentials + { + Token = "", + AndroidId = 0, + SecurityToken = 0, + AppId = "", + } +}; + +var listener = new FcmListener(credentials); + +listener.NotificationReceived += (_, message) => +{ + Console.WriteLine($"[MESSAGE]: {DateTime.Now}:\n{message}"); +}; + +listener.ErrorOccurred += (_, error) => +{ + Console.WriteLine($"[ERROR]: {error}"); +}; + +await listener.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetEntityChanges/GetEntityChanges.csproj b/RustPlusApi/Examples/Fcm/FcmRegister/FcmRegister.csproj similarity index 65% rename from RustPlusApi/Examples/GetEntityChanges/GetEntityChanges.csproj rename to RustPlusApi/Examples/Fcm/FcmRegister/FcmRegister.csproj index 0aa3ca6..65c5f8b 100644 --- a/RustPlusApi/Examples/GetEntityChanges/GetEntityChanges.csproj +++ b/RustPlusApi/Examples/Fcm/FcmRegister/FcmRegister.csproj @@ -8,8 +8,7 @@ - - + diff --git a/RustPlusApi/Examples/Fcm/FcmRegister/Program.cs b/RustPlusApi/Examples/Fcm/FcmRegister/Program.cs new file mode 100644 index 0000000..8266013 --- /dev/null +++ b/RustPlusApi/Examples/Fcm/FcmRegister/Program.cs @@ -0,0 +1,10 @@ +using RustPlusApi.Fcm; +using Newtonsoft.Json; + +// NOT WORKING, NEEDS TO BE FIXED +return; + +/* var senderId = "976529667804"; +var credentials = await FcmRegister.RegisterAsync(senderId); + +Console.WriteLine(JsonConvert.SerializeObject(credentials, Formatting.Indented));*/ \ No newline at end of file diff --git a/RustPlusApi/Examples/GetEntityInfo/GetEntityInfo.csproj b/RustPlusApi/Examples/GetAlarmInfo/GetAlarmInfo.csproj similarity index 58% rename from RustPlusApi/Examples/GetEntityInfo/GetEntityInfo.csproj rename to RustPlusApi/Examples/GetAlarmInfo/GetAlarmInfo.csproj index 0aa3ca6..8587a6a 100644 --- a/RustPlusApi/Examples/GetEntityInfo/GetEntityInfo.csproj +++ b/RustPlusApi/Examples/GetAlarmInfo/GetAlarmInfo.csproj @@ -7,9 +7,9 @@ enable - - - - + + + + diff --git a/RustPlusApi/Examples/GetEntityInfo/Program.cs b/RustPlusApi/Examples/GetAlarmInfo/Program.cs similarity index 51% rename from RustPlusApi/Examples/GetEntityInfo/Program.cs rename to RustPlusApi/Examples/GetAlarmInfo/Program.cs index 265c6c7..48c0dfe 100644 --- a/RustPlusApi/Examples/GetEntityInfo/Program.cs +++ b/RustPlusApi/Examples/GetAlarmInfo/Program.cs @@ -5,15 +5,15 @@ using static __Constants.ExamplesConst; var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; rustPlus.Connected += async (_, _) => { - await rustPlus.GetEntityInfoAsync(EntityId, message => - { - Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); - rustPlus.Dispose(); - return true; - }); + var message = await rustPlus.GetAlarmInfoAsync(entityId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetEntityChanges/Program.cs b/RustPlusApi/Examples/GetEntityChanges/Program.cs deleted file mode 100644 index 8e13bb3..0000000 --- a/RustPlusApi/Examples/GetEntityChanges/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; - -using RustPlusApi; - -using static __Constants.ExamplesConst; - -var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); - -rustPlus.Connected += async (_, _) => -{ - await rustPlus.GetEntityInfoAsync(EntityId, message => - { - Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); - return true; - }); -}; - -rustPlus.MessageReceived += (_, message) => -{ - if (message.Broadcast is not { EntityChanged: not null }) return; - - var entityChanged = message.Broadcast.EntityChanged; - Console.WriteLine($"Message:\n{JsonConvert.SerializeObject(entityChanged, JsonSettings)}"); -}; - -await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetInfo/Program.cs b/RustPlusApi/Examples/GetInfo/Program.cs index 12a0666..d85d435 100644 --- a/RustPlusApi/Examples/GetInfo/Program.cs +++ b/RustPlusApi/Examples/GetInfo/Program.cs @@ -8,12 +8,11 @@ rustPlus.Connected += async (_, _) => { - await rustPlus.GetInfoAsync(message => - { - Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); - rustPlus.Dispose(); - return true; - }); + var message = await rustPlus.GetInfoAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetMap/Program.cs b/RustPlusApi/Examples/GetMap/Program.cs index f0f739d..c9a1094 100644 --- a/RustPlusApi/Examples/GetMap/Program.cs +++ b/RustPlusApi/Examples/GetMap/Program.cs @@ -1,4 +1,6 @@ -using RustPlusApi; +using Newtonsoft.Json; + +using RustPlusApi; using static __Constants.ExamplesConst; @@ -6,15 +8,18 @@ rustPlus.Connected += async (_, _) => { - await rustPlus.GetMapAsync(message => - { - var imageData = message.Response.Map.JpgImage.ToByteArray(); - if (imageData == null) return false; - File.WriteAllBytes("map.jpg", imageData); - Console.WriteLine(@"Saved under .\RustPlusApi\RustPlusApi\Examples\GetMap\bin\Debug\net8.0"); - rustPlus.Dispose(); - return true; - }); + var message = await rustPlus.GetMapAsync(); + + if (!message.IsSuccess) return; + + File.WriteAllBytes("map.jpg", message.Data?.JpgImage!); + + message.Data!.JpgImage = null; + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + Console.WriteLine($"Image saved under: {Directory.GetCurrentDirectory()}"); + + rustPlus.Dispose(); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetMapMarkers/Program.cs b/RustPlusApi/Examples/GetMapMarkers/Program.cs index 99d2eaa..777975e 100644 --- a/RustPlusApi/Examples/GetMapMarkers/Program.cs +++ b/RustPlusApi/Examples/GetMapMarkers/Program.cs @@ -8,12 +8,12 @@ rustPlus.Connected += async (_, _) => { - await rustPlus.GetMapMarkersAsync(message => - { - Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); - rustPlus.Dispose(); - return true; - }); + //await rustPlus.GetMapMarkersAsync(message => + //{ + // Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + // rustPlus.Dispose(); + // return true; + //}); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/StrobeEntity/StrobeEntity.csproj b/RustPlusApi/Examples/GetSmartSwitchChanges/GetSmartSwitchChanges.csproj similarity index 58% rename from RustPlusApi/Examples/StrobeEntity/StrobeEntity.csproj rename to RustPlusApi/Examples/GetSmartSwitchChanges/GetSmartSwitchChanges.csproj index 0aa3ca6..8587a6a 100644 --- a/RustPlusApi/Examples/StrobeEntity/StrobeEntity.csproj +++ b/RustPlusApi/Examples/GetSmartSwitchChanges/GetSmartSwitchChanges.csproj @@ -7,9 +7,9 @@ enable - - - - + + + + diff --git a/RustPlusApi/Examples/GetSmartSwitchChanges/Program.cs b/RustPlusApi/Examples/GetSmartSwitchChanges/Program.cs new file mode 100644 index 0000000..154f681 --- /dev/null +++ b/RustPlusApi/Examples/GetSmartSwitchChanges/Program.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetSmartSwitchInfoAsync(entityId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +rustPlus.OnSmartSwitchTriggered += (_, message) => +{ + Console.WriteLine($"SmartSwitch:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetSmartSwitchInfo/GetSmartSwitchInfo.csproj b/RustPlusApi/Examples/GetSmartSwitchInfo/GetSmartSwitchInfo.csproj new file mode 100644 index 0000000..8587a6a --- /dev/null +++ b/RustPlusApi/Examples/GetSmartSwitchInfo/GetSmartSwitchInfo.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/GetSmartSwitchInfo/Program.cs b/RustPlusApi/Examples/GetSmartSwitchInfo/Program.cs new file mode 100644 index 0000000..a452cc7 --- /dev/null +++ b/RustPlusApi/Examples/GetSmartSwitchInfo/Program.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetSmartSwitchInfoAsync(entityId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetStorageMonitorChanges/GetStorageMonitorChanges.csproj b/RustPlusApi/Examples/GetStorageMonitorChanges/GetStorageMonitorChanges.csproj new file mode 100644 index 0000000..8587a6a --- /dev/null +++ b/RustPlusApi/Examples/GetStorageMonitorChanges/GetStorageMonitorChanges.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/GetStorageMonitorChanges/Program.cs b/RustPlusApi/Examples/GetStorageMonitorChanges/Program.cs new file mode 100644 index 0000000..962434d --- /dev/null +++ b/RustPlusApi/Examples/GetStorageMonitorChanges/Program.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetStorageMonitorInfoAsync(entityId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +rustPlus.OnStorageMonitorTriggered += (_, message) => +{ + Console.WriteLine($"StorageMonitor:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetStorageMonitorInfo/GetStorageMonitorInfo.csproj b/RustPlusApi/Examples/GetStorageMonitorInfo/GetStorageMonitorInfo.csproj new file mode 100644 index 0000000..8587a6a --- /dev/null +++ b/RustPlusApi/Examples/GetStorageMonitorInfo/GetStorageMonitorInfo.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/GetStorageMonitorInfo/Program.cs b/RustPlusApi/Examples/GetStorageMonitorInfo/Program.cs new file mode 100644 index 0000000..c68847d --- /dev/null +++ b/RustPlusApi/Examples/GetStorageMonitorInfo/Program.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetStorageMonitorInfoAsync(entityId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetTeamChat/GetTeamChat.csproj b/RustPlusApi/Examples/GetTeamChat/GetTeamChat.csproj new file mode 100644 index 0000000..1cb0581 --- /dev/null +++ b/RustPlusApi/Examples/GetTeamChat/GetTeamChat.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/GetTeamChat/Program.cs b/RustPlusApi/Examples/GetTeamChat/Program.cs new file mode 100644 index 0000000..0225a31 --- /dev/null +++ b/RustPlusApi/Examples/GetTeamChat/Program.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + //await rustPlus.GetTeamChatAsync(message => + //{ + // Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + // return true; + //}); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetTeamChatChanges/GetTeamChatChanges.csproj b/RustPlusApi/Examples/GetTeamChatChanges/GetTeamChatChanges.csproj new file mode 100644 index 0000000..1cb0581 --- /dev/null +++ b/RustPlusApi/Examples/GetTeamChatChanges/GetTeamChatChanges.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/GetTeamChatChanges/Program.cs b/RustPlusApi/Examples/GetTeamChatChanges/Program.cs new file mode 100644 index 0000000..b562b7e --- /dev/null +++ b/RustPlusApi/Examples/GetTeamChatChanges/Program.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + //await rustPlus.GetTeamChatAsync(message => + //{ + // Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + // return true; + //}); +}; + +rustPlus.MessageReceived += (_, message) => +{ + if (message.Broadcast is not { TeamMessage: not null }) return; + + var teamMessage = message.Broadcast.TeamMessage; + Console.WriteLine($"Message:\n{JsonConvert.SerializeObject(teamMessage, JsonSettings)}"); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetTeamInfo/Program.cs b/RustPlusApi/Examples/GetTeamInfo/Program.cs index 488c8da..0ea502e 100644 --- a/RustPlusApi/Examples/GetTeamInfo/Program.cs +++ b/RustPlusApi/Examples/GetTeamInfo/Program.cs @@ -8,12 +8,12 @@ rustPlus.Connected += async (_, _) => { - await rustPlus.GetTeamInfoAsync(message => - { - Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); - rustPlus.Dispose(); - return true; - }); + //await rustPlus.GetTeamInfoAsync(message => + //{ + // Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + // rustPlus.Dispose(); + // return true; + //}); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/GetTime/Program.cs b/RustPlusApi/Examples/GetTime/Program.cs index 3c300fc..7da28c9 100644 --- a/RustPlusApi/Examples/GetTime/Program.cs +++ b/RustPlusApi/Examples/GetTime/Program.cs @@ -6,12 +6,12 @@ rustPlus.Connected += async (_, _) => { - await rustPlus.GetTimeAsync(message => - { - Console.WriteLine($"Time: {message.Response.Time.Time}"); - rustPlus.Dispose(); - return true; - }); + //await rustPlus.GetTimeAsync(message => + //{ + // Console.WriteLine($"Time: {message.Response.Time.Time}"); + // rustPlus.Dispose(); + // return true; + //}); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetClanChatChangesLegacy/GetClanChatChangesLegacy.csproj b/RustPlusApi/Examples/Legacy/GetClanChatChangesLegacy/GetClanChatChangesLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetClanChatChangesLegacy/GetClanChatChangesLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetClanChatChangesLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetClanChatChangesLegacy/Program.cs new file mode 100644 index 0000000..eba974d --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetClanChatChangesLegacy/Program.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + // This method is not fully integrated in Rust so it will not work until the Clan update is released. + var message = await rustPlus.GetClanChatLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +rustPlus.MessageReceived += (_, message) => +{ + if (message.Broadcast is not { ClanChanged: not null }) return; + + Console.WriteLine($"Message:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetClanChatLegacy/GetClanChatLegacy.csproj b/RustPlusApi/Examples/Legacy/GetClanChatLegacy/GetClanChatLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetClanChatLegacy/GetClanChatLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetClanChatLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetClanChatLegacy/Program.cs new file mode 100644 index 0000000..45ca5cf --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetClanChatLegacy/Program.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + // This method is not fully integrated in Rust so it will not work until the Clan update is released. + var message = await rustPlus.GetClanChatLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetEntityChangesLegacy/GetEntityChangesLegacy.csproj b/RustPlusApi/Examples/Legacy/GetEntityChangesLegacy/GetEntityChangesLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetEntityChangesLegacy/GetEntityChangesLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetEntityChangesLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetEntityChangesLegacy/Program.cs new file mode 100644 index 0000000..dda9a6e --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetEntityChangesLegacy/Program.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 3716008; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetEntityInfoLegacyAsync(entityId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +rustPlus.MessageReceived += (_, message) => +{ + if (message.Broadcast is not { EntityChanged: not null }) return; + + Console.WriteLine($"Message:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetEntityInfoLegacy/GetEntityInfoLegacy.csproj b/RustPlusApi/Examples/Legacy/GetEntityInfoLegacy/GetEntityInfoLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetEntityInfoLegacy/GetEntityInfoLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetEntityInfoLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetEntityInfoLegacy/Program.cs new file mode 100644 index 0000000..c209b77 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetEntityInfoLegacy/Program.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetEntityInfoLegacyAsync(entityId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetInfoLegacy/GetInfoLegacy.csproj b/RustPlusApi/Examples/Legacy/GetInfoLegacy/GetInfoLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetInfoLegacy/GetInfoLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetInfoLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetInfoLegacy/Program.cs new file mode 100644 index 0000000..a229e26 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetInfoLegacy/Program.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetTeamChatLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetMapLegacy/GetMapLegacy.csproj b/RustPlusApi/Examples/Legacy/GetMapLegacy/GetMapLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetMapLegacy/GetMapLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetMapLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetMapLegacy/Program.cs new file mode 100644 index 0000000..ce8a8d5 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetMapLegacy/Program.cs @@ -0,0 +1,20 @@ +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetMapLegacyAsync(); + + if (message.Response.Error is not null) return; + + File.WriteAllBytes("map.jpg", message.Response.Map.JpgImage.ToByteArray()); + + Console.WriteLine($"Image saved under: {Directory.GetCurrentDirectory()}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetMapMarkersLegacy/GetMapMarkersLegacy.csproj b/RustPlusApi/Examples/Legacy/GetMapMarkersLegacy/GetMapMarkersLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetMapMarkersLegacy/GetMapMarkersLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetMapMarkersLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetMapMarkersLegacy/Program.cs new file mode 100644 index 0000000..42f268b --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetMapMarkersLegacy/Program.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetMapMarkersLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetTeamChatChangesLegacy/GetTeamChatChangesLegacy.csproj b/RustPlusApi/Examples/Legacy/GetTeamChatChangesLegacy/GetTeamChatChangesLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTeamChatChangesLegacy/GetTeamChatChangesLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetTeamChatChangesLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetTeamChatChangesLegacy/Program.cs new file mode 100644 index 0000000..eb3eb14 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTeamChatChangesLegacy/Program.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetTeamChatLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +rustPlus.MessageReceived += (_, message) => +{ + if (message.Broadcast is not { TeamMessage: not null }) return; + + Console.WriteLine($"Message:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetTeamChatLegacy/GetTeamChatLegacy.csproj b/RustPlusApi/Examples/Legacy/GetTeamChatLegacy/GetTeamChatLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTeamChatLegacy/GetTeamChatLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetTeamChatLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetTeamChatLegacy/Program.cs new file mode 100644 index 0000000..a229e26 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTeamChatLegacy/Program.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetTeamChatLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetTeamInfoLegacy/GetTeamInfoLegacy.csproj b/RustPlusApi/Examples/Legacy/GetTeamInfoLegacy/GetTeamInfoLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTeamInfoLegacy/GetTeamInfoLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetTeamInfoLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetTeamInfoLegacy/Program.cs new file mode 100644 index 0000000..a120907 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTeamInfoLegacy/Program.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetTeamInfoLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/GetTimeLegacy/GetTimeLegacy.csproj b/RustPlusApi/Examples/Legacy/GetTimeLegacy/GetTimeLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTimeLegacy/GetTimeLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/GetTimeLegacy/Program.cs b/RustPlusApi/Examples/Legacy/GetTimeLegacy/Program.cs new file mode 100644 index 0000000..d788532 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/GetTimeLegacy/Program.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.GetTimeLegacyAsync(); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/PromoteToLeader/Program.cs b/RustPlusApi/Examples/Legacy/PromoteToLeader/Program.cs new file mode 100644 index 0000000..58ba8b1 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/PromoteToLeader/Program.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); +const ulong steamId = 0; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.PromoteToLeaderLegacyAsync(steamId); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/PromoteToLeader/PromoteToLeaderLegacy.csproj b/RustPlusApi/Examples/Legacy/PromoteToLeader/PromoteToLeaderLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/PromoteToLeader/PromoteToLeaderLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/SendTeamMessageLegacy/Program.cs b/RustPlusApi/Examples/Legacy/SendTeamMessageLegacy/Program.cs new file mode 100644 index 0000000..b7e769c --- /dev/null +++ b/RustPlusApi/Examples/Legacy/SendTeamMessageLegacy/Program.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); +const string teamMessage = "Hello world!"; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.SendTeamMessageLegacyAsync(teamMessage); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/SendTeamMessageLegacy/SendTeamMessageLegacy.csproj b/RustPlusApi/Examples/Legacy/SendTeamMessageLegacy/SendTeamMessageLegacy.csproj new file mode 100644 index 0000000..476e35a --- /dev/null +++ b/RustPlusApi/Examples/Legacy/SendTeamMessageLegacy/SendTeamMessageLegacy.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/SetEntityValueLegacy/Program.cs b/RustPlusApi/Examples/Legacy/SetEntityValueLegacy/Program.cs new file mode 100644 index 0000000..99acdbc --- /dev/null +++ b/RustPlusApi/Examples/Legacy/SetEntityValueLegacy/Program.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; +const bool value = true; + +rustPlus.Connected += async (_, _) => +{ + var message = await rustPlus.SetEntityValueLegacyAsync(entityId, value); + + Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/SetEntityValueLegacy/SetEntityValueLegacy.csproj b/RustPlusApi/Examples/Legacy/SetEntityValueLegacy/SetEntityValueLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/SetEntityValueLegacy/SetEntityValueLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/StrobeEntityLegacy/Program.cs b/RustPlusApi/Examples/Legacy/StrobeEntityLegacy/Program.cs new file mode 100644 index 0000000..8b16622 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/StrobeEntityLegacy/Program.cs @@ -0,0 +1,17 @@ +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 0; + +rustPlus.Connected += async (_, _) => +{ + await rustPlus.StrobeEntityLegacyAsync(entityId); + + Console.WriteLine($"Strobed entity: {entityId}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/StrobeEntityLegacy/StrobeEntityLegacy.csproj b/RustPlusApi/Examples/Legacy/StrobeEntityLegacy/StrobeEntityLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/StrobeEntityLegacy/StrobeEntityLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/Legacy/ToggleEntityLegacy/Program.cs b/RustPlusApi/Examples/Legacy/ToggleEntityLegacy/Program.cs new file mode 100644 index 0000000..a104522 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/ToggleEntityLegacy/Program.cs @@ -0,0 +1,17 @@ +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlusLegacy(Ip, Port, PlayerId, PlayerToken); +const uint entityId = 85942; + +rustPlus.Connected += async (_, _) => +{ + await rustPlus.ToogleEntityValueLegacyAsync(entityId); + + Console.WriteLine($"Toggled entity: {entityId}"); + + rustPlus.Dispose(); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/Legacy/ToggleEntityLegacy/ToggleEntityLegacy.csproj b/RustPlusApi/Examples/Legacy/ToggleEntityLegacy/ToggleEntityLegacy.csproj new file mode 100644 index 0000000..cb85ff3 --- /dev/null +++ b/RustPlusApi/Examples/Legacy/ToggleEntityLegacy/ToggleEntityLegacy.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/PromoteToLeader/Program.cs b/RustPlusApi/Examples/PromoteToLeader/Program.cs new file mode 100644 index 0000000..aeedf74 --- /dev/null +++ b/RustPlusApi/Examples/PromoteToLeader/Program.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +using RustPlusApi; + +using static __Constants.ExamplesConst; + +var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); +ulong steamId = 0; + +rustPlus.Connected += async (_, _) => +{ + //await rustPlus.PromoteToLeaderAsync(steamId, message => + //{ + // Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + // rustPlus.Dispose(); + // return true; + //}); +}; + +await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/PromoteToLeader/PromoteToLeader.csproj b/RustPlusApi/Examples/PromoteToLeader/PromoteToLeader.csproj new file mode 100644 index 0000000..1cb0581 --- /dev/null +++ b/RustPlusApi/Examples/PromoteToLeader/PromoteToLeader.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/RustPlusApi/Examples/SendTeamChat/Program.cs b/RustPlusApi/Examples/SendTeamChat/Program.cs index 3c40d47..148aeb5 100644 --- a/RustPlusApi/Examples/SendTeamChat/Program.cs +++ b/RustPlusApi/Examples/SendTeamChat/Program.cs @@ -6,8 +6,8 @@ rustPlus.Connected += async (_, _) => { - await rustPlus.SendTeamMessageAsync("Hello from RustPlusApi!"); - rustPlus.Dispose(); + //await rustPlus.SendTeamMessageAsync("Hello from RustPlusApi!"); + //rustPlus.Dispose(); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/SetEntityValue/Program.cs b/RustPlusApi/Examples/SetEntityValue/Program.cs index c27187c..018ae55 100644 --- a/RustPlusApi/Examples/SetEntityValue/Program.cs +++ b/RustPlusApi/Examples/SetEntityValue/Program.cs @@ -5,15 +5,17 @@ using static __Constants.ExamplesConst; var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); +var entityId = 0; +var entityValue = true; rustPlus.Connected += async (_, _) => { - await rustPlus.SetEntityValueAsync(EntityId, EntityValue, message => - { - Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); - rustPlus.Dispose(); - return true; - }); + //await rustPlus.SetEntityValueAsync(entityId, entityValue, message => + //{ + // Console.WriteLine($"Infos:\n{JsonConvert.SerializeObject(message, JsonSettings)}"); + // rustPlus.Dispose(); + // return true; + //}); }; await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/StrobeEntity/Program.cs b/RustPlusApi/Examples/StrobeEntity/Program.cs deleted file mode 100644 index 1b9933b..0000000 --- a/RustPlusApi/Examples/StrobeEntity/Program.cs +++ /dev/null @@ -1,13 +0,0 @@ -using RustPlusApi; - -using static __Constants.ExamplesConst; - -var rustPlus = new RustPlus(Ip, Port, PlayerId, PlayerToken); - -rustPlus.Connected += async (_, _) => -{ - await rustPlus.StrobeAsync(EntityId); - rustPlus.Dispose(); -}; - -await rustPlus.ConnectAsync(); \ No newline at end of file diff --git a/RustPlusApi/Examples/__Constants/ExamplesConst.cs b/RustPlusApi/Examples/__Constants/ExamplesConst.cs index c5776cb..b74e5f9 100644 --- a/RustPlusApi/Examples/__Constants/ExamplesConst.cs +++ b/RustPlusApi/Examples/__Constants/ExamplesConst.cs @@ -1,4 +1,6 @@ using Newtonsoft.Json; +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable FieldCanBeMadeReadOnly.Global namespace __Constants { @@ -9,10 +11,7 @@ public record ExamplesConst public const ulong PlayerId = 0; public const int PlayerToken = 0; - public const int EntityId = 0; - public const bool EntityValue = true; - - public static JsonSerializerSettings JsonSettings = new() + public static readonly JsonSerializerSettings JsonSettings = new() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented, diff --git a/RustPlusApi/RustPlusApi.Fcm/Converters/BodyConverter.cs b/RustPlusApi/RustPlusApi.Fcm/Converters/BodyConverter.cs new file mode 100644 index 0000000..e0147fc --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Converters/BodyConverter.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using RustPlusApi.Fcm.Data; + +namespace RustPlusApi.Fcm.Converters +{ + public class BodyConverter : JsonConverter + { + public override Body? ReadJson(JsonReader reader, Type objectType, Body? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var bodyString = reader.Value!.ToString(); + return JsonConvert.DeserializeObject(bodyString!); + } + + public override void WriteJson(JsonWriter writer, Body? value, JsonSerializer serializer) + { + JObject jsonObject = JObject.FromObject(value!, serializer); + jsonObject.WriteTo(writer); + } + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/Constants.cs b/RustPlusApi/RustPlusApi.Fcm/Data/Constants.cs new file mode 100644 index 0000000..288543c --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/Constants.cs @@ -0,0 +1,113 @@ +namespace RustPlusApi.Fcm.Data +{ + public static class Constants + { + public enum ProcessingState + { + McsVersionTagAndSize = 0, + McsTagAndSize = 1, + McsSize = 2, + McsProtoBytes = 3 + } + + public const int ReadTimeoutSecs = 60 * 60; + public const int MinResetIntervalSecs = 5 * 60; + public const int MaxSilentIntervalSecs = 60 * 60; + + public const int KVersionPacketLen = 1; + public const int KTagPacketLen = 1; + public const int KSizePacketLenMin = 1; + public const int KSizePacketLenMax = 5; + + public const int KMcsVersion = 41; + + public enum McsProtoTag + { + KHeartbeatPingTag = 0, + KHeartbeatAckTag = 1, + KLoginRequestTag = 2, + KLoginResponseTag = 3, + KCloseTag = 4, + KMessageStanzaTag = 5, + KPresenceStanzaTag = 6, + KIqStanzaTag = 7, + KDataMessageStanzaTag = 8, + KBatchPresenceStanzaTag = 9, + KStreamErrorStanzaTag = 10, + KHttpRequestTag = 11, + KHttpResponseTag = 12, + KBindAccountRequestTag = 13, + KBindAccountResponseTag = 14, + KTalkMetadataTag = 15, + KNumProtoTypes = 16 + } + + public static readonly byte[] ServerKey = [ + 0x04, + 0x33, + 0x94, + 0xf7, + 0xdf, + 0xa1, + 0xeb, + 0xb1, + 0xdc, + 0x03, + 0xa2, + 0x5e, + 0x15, + 0x71, + 0xdb, + 0x48, + 0xd3, + 0x2e, + 0xed, + 0xed, + 0xb2, + 0x34, + 0xdb, + 0xb7, + 0x47, + 0x3a, + 0x0c, + 0x8f, + 0xc4, + 0xcc, + 0xe1, + 0x6f, + 0x3c, + 0x8c, + 0x84, + 0xdf, + 0xab, + 0xb6, + 0x66, + 0x3e, + 0xf2, + 0x0c, + 0xd4, + 0x8b, + 0xfe, + 0xe3, + 0xf9, + 0x76, + 0x2f, + 0x14, + 0x1c, + 0x63, + 0x08, + 0x6a, + 0x6f, + 0x2d, + 0xb1, + 0x1a, + 0x95, + 0xb0, + 0xce, + 0x37, + 0xc0, + 0x9c, + 0x6e + ]; + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/Credentials.cs b/RustPlusApi/RustPlusApi.Fcm/Data/Credentials.cs new file mode 100644 index 0000000..e73a3e2 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/Credentials.cs @@ -0,0 +1,30 @@ +namespace RustPlusApi.Fcm.Data +{ + public sealed class Credentials + { + public Keys Keys { get; set; } = null!; + public FcmCredentials Fcm { get; set; } = null!; + public GcmCredentials Gcm { get; set; } = null!; + } + + public sealed class Keys + { + public string PrivateKey { get; set; } = null!; + public string PublicKey { get; set; } = null!; + public string AuthSecret { get; set; } = null!; + } + + public sealed class FcmCredentials + { + public string Token { get; set; } = null!; + public string PushSet { get; set; } = null!; + } + + public sealed class GcmCredentials + { + public string Token { get; set; } = null!; + public ulong AndroidId { get; set; } + public ulong SecurityToken { get; set; } + public string AppId { get; set; } = null!; + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/Events/AlarmEventArg.cs b/RustPlusApi/RustPlusApi.Fcm/Data/Events/AlarmEventArg.cs new file mode 100644 index 0000000..445ffe7 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/Events/AlarmEventArg.cs @@ -0,0 +1,9 @@ +namespace RustPlusApi.Fcm.Data.Events +{ + public class AlarmEventArg + { + public Guid ServerId { get; set; } + public string Title { get; set; } = null!; + public string Message { get; set; } = null!; + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/Events/EntityEventArg.cs b/RustPlusApi/RustPlusApi.Fcm/Data/Events/EntityEventArg.cs new file mode 100644 index 0000000..509000b --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/Events/EntityEventArg.cs @@ -0,0 +1,9 @@ +namespace RustPlusApi.Fcm.Data.Events +{ + public class EntityEventArg + { + public int EntityType { get; set; } + public int EntityId { get; set; } + public string EntityName { get; set; } = null!; + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/Events/MessageEventArg.cs b/RustPlusApi/RustPlusApi.Fcm/Data/Events/MessageEventArg.cs new file mode 100644 index 0000000..34a64d7 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/Events/MessageEventArg.cs @@ -0,0 +1,10 @@ +using static RustPlusApi.Fcm.Data.Constants; + +namespace RustPlusApi.Fcm.Data.Events +{ + internal class MessageEventArgs : EventArgs + { + public McsProtoTag Tag { get; set; } + public object? Object { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/Events/ServerEventArg.cs b/RustPlusApi/RustPlusApi.Fcm/Data/Events/ServerEventArg.cs new file mode 100644 index 0000000..8e53c3d --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/Events/ServerEventArg.cs @@ -0,0 +1,9 @@ + +namespace RustPlusApi.Fcm.Data.Events +{ + public class ServerEventArg + { + public Guid Id { get; set; } + public string Name { get; set; } = null!; + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/Events/ServerFullEventArg.cs b/RustPlusApi/RustPlusApi.Fcm/Data/Events/ServerFullEventArg.cs new file mode 100644 index 0000000..f6a9cf5 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/Events/ServerFullEventArg.cs @@ -0,0 +1,12 @@ +namespace RustPlusApi.Fcm.Data.Events +{ + public class ServerFullEventArg : ServerEventArg + { + public string Ip { get; set; } = null!; + public int Port { get; set; } + public string Desc { get; set; } = null!; + public int Logo { get; set; } + public int Img { get; set; } + public string Url { get; set; } = null!; + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Data/FcmMessage.cs b/RustPlusApi/RustPlusApi.Fcm/Data/FcmMessage.cs new file mode 100644 index 0000000..aaa2cf0 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Data/FcmMessage.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; + +using RustPlusApi.Fcm.Converters; + +namespace RustPlusApi.Fcm.Data +{ + public class FcmMessage + { + public Guid FcmMessageId { get; set; } + public string Priority { get; set; } = null!; + public long From { get; set; } + public MessageData Data { get; set; } = null!; + } + + public class MessageData + { + public Guid ProjectId { get; set; } + public string ChannelId { get; set; } = null!; + public string Title { get; set; } = null!; + public string Message { get; set; } = null!; + public string ExperienceId { get; set; } = null!; + public string ScopeKey { get; set; } = null!; + [JsonConverter(typeof(BodyConverter))] + public Body Body { get; set; } = null!; + } + + public class Body + { + public Guid Id { get; set; } + public string Ip { get; set; } = null!; + public int Port { get; set; } + public string Name { get; set; } = null!; + public string Desc { get; set; } = null!; + public int Logo { get; set; } + public int Img { get; set; } + public string Url { get; set; } = null!; + public ulong PlayerId { get; set; } + public string PlayerToken { get; set; } = null!; + public string Type { get; set; } = null!; + public int? EntityType { get; set; } + public int? EntityId { get; set; } + public string EntityName { get; set; } = null!; + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/FcmListener.cs b/RustPlusApi/RustPlusApi.Fcm/FcmListener.cs new file mode 100644 index 0000000..ccea4e9 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/FcmListener.cs @@ -0,0 +1,110 @@ +using System.Diagnostics; + +using RustPlusApi.Fcm.Data; +using RustPlusApi.Fcm.Data.Events; + +namespace RustPlusApi.Fcm +{ + public class FcmListener(Credentials credentials, ICollection? persistentIds = null) : FcmListenerBasic(credentials, persistentIds) + { + public event EventHandler? OnParing; + + public event EventHandler<(ServerEventArg, EntityEventArg)>? OnEntityParing; + public event EventHandler? OnServerPairing; + + public event EventHandler<(ServerEventArg, int)>? OnSmartSwitchParing; + public event EventHandler<(ServerEventArg, int)>? OnSmartAlarmParing; + public event EventHandler<(ServerEventArg, int)>? OnStorageMonitorParing; + + public event EventHandler? OnAlarmTriggered; + + protected override void ParseNotification(FcmMessage? message) + { + if (message == null) return; + + switch (message.Data.ChannelId) + { + case "pairing": + OnParing?.Invoke(this, message.Data); + ParsePairing(message.Data.Body); + break; + case "alarm": + var alarm = new AlarmEventArg() + { + ServerId = message.Data.Body.Id, + Title = message.Data.Title, + Message = message.Data.Message + }; + OnAlarmTriggered?.Invoke(this, alarm); + break; + default: + Debug.WriteLine($"Unknown channel: {message.Data.ChannelId}"); + break; + } + } + + private void ParsePairing(Body body) + { + switch (body.Type) + { + case "entity": + var server = new ServerEventArg() + { + Id = body.Id, + Name = body.Name + }; + var entity = new EntityEventArg() + { + EntityType = body.EntityType ?? 0, + EntityId = body.EntityId ?? 0, + EntityName = body.EntityName + }; + OnEntityParing?.Invoke(this, (server, entity)); + ParsePairingEntity(body); + break; + case "server": + var serverFull = new ServerFullEventArg() + { + Id = body.Id, + Name = body.Name, + Ip = body.Ip, + Port = body.Port, + Desc = body.Desc, + Logo = body.Logo, + Img = body.Img, + Url = body.Url + }; + OnServerPairing?.Invoke(this, serverFull); + break; + default: + Debug.WriteLine($"Unknown pairing type: {body.Type}"); + break; + } + } + + private void ParsePairingEntity(Body body) + { + var server = new ServerEventArg() + { + Id = body.Id, + Name = body.Name + }; + + switch (body.EntityType) + { + case 1: + OnSmartSwitchParing?.Invoke(this, (server, body.EntityId ?? 0)); + break; + case 2: + OnSmartAlarmParing?.Invoke(this, (server, body.EntityId ?? 0)); + break; + case 3: + OnStorageMonitorParing?.Invoke(this, (server, body.EntityId ?? 0)); + break; + default: + Debug.WriteLine($"Unknown entity type: {body.EntityType}"); + break; + } + } + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/FcmListenerBasic.cs b/RustPlusApi/RustPlusApi.Fcm/FcmListenerBasic.cs new file mode 100644 index 0000000..9f28df7 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/FcmListenerBasic.cs @@ -0,0 +1,307 @@ +using System.Diagnostics; +using System.Net.Security; +using System.Net.Sockets; +using System.Numerics; + +using McsProto; + +using Newtonsoft.Json; + +using ProtoBuf; + +using RustPlusApi.Fcm.Data; +using RustPlusApi.Fcm.Utils; + +using static RustPlusApi.Fcm.Data.Constants; +using static System.GC; +using RustPlusApi.Fcm.Data.Events; + +namespace RustPlusApi.Fcm +{ + public class FcmListenerBasic(Credentials credentials, ICollection? persistentIds = null) : IDisposable + { + private const string Host = "mtalk.google.com"; + private const int Port = 5228; + + private TcpClient? _tcpClient; + private SslStream? _sslStream; + private DateTime _lastReset; + private DateTime _timeLastMessageReceived; + private Timer? _checkinTimer; + + public event EventHandler? Connecting; + public event EventHandler? Connected; + public event EventHandler? NotificationReceived; + public event EventHandler? Disconnected; + public event EventHandler? ErrorOccurred; + + public async Task ConnectAsync() + { + _tcpClient = new TcpClient(); + await _tcpClient.ConnectAsync(Host, Port); + + _sslStream = new SslStream(_tcpClient.GetStream(), false); + await _sslStream.AuthenticateAsClientAsync(Host); + + Connecting?.Invoke(this, EventArgs.Empty); + + try + { + var loginRequest = new LoginRequest + { + AdaptiveHeartbeat = false, + auth_service = LoginRequest.AuthService.AndroidId, + AuthToken = credentials.Gcm.SecurityToken.ToString(), + Id = "chrome-63.0.3234.0", + Domain = "mcs.android.com", + DeviceId = $"android-{BigInteger.Parse(credentials.Gcm.AndroidId.ToString()):X}", + NetworkType = 1, + Resource = credentials.Gcm.AndroidId.ToString(), + User = credentials.Gcm.AndroidId.ToString(), + UseRmq2 = true, + Settings = { new Setting() { Name = "new_vc", Value = "1" } }, + ClientEvents = { new ClientEvent() }, + ReceivedPersistentIds = { }, + }; + + if (persistentIds != null) loginRequest.ReceivedPersistentIds.AddRange(persistentIds); + + SendPacket(loginRequest); + + _lastReset = DateTime.Now; + _timeLastMessageReceived = DateTime.Now; + + Connected?.Invoke(this, EventArgs.Empty); + + StatusCheck(); + ReceiveMessages(); + } + catch (Exception ex) + { + ErrorOccurred?.Invoke(this, ex); + Dispose(); + } + } + + public void Dispose() + { + _sslStream?.Dispose(); + _tcpClient?.Dispose(); + + Disconnected?.Invoke(this, EventArgs.Empty); + + SuppressFinalize(this); + } + + private void ReceiveMessages() + { + var parser = new RawMessageParser(); + parser.MessageReceived += (_, e) => OnMessage(e); + + // First receival (LoginResponse) + byte[] header = Read(2); + int version = header[0]; + int tag = header[1]; + + if (version < KMcsVersion && version != 38) + throw new InvalidOperationException($"Protocol version {version} unsupported"); + + int size = ReadVarint32(); + Debug.WriteLine($"Got message size: {size} bytes"); + + byte[] payload = Read(size); + Debug.WriteLine($"Successfully read: {payload.Length} bytes"); + + Type type = RawMessageParser.BuildProtobufFromTag(((McsProtoTag)tag)); + Debug.WriteLine($"RECEIVED PROTO OF TYPE {type.Name}"); + + if (type != typeof(LoginResponse)) + throw new Exception($"Got wrong login response. Expected {typeof(LoginResponse).Name}, got {type.Name}"); + + parser.OnGotLoginResponse(); + parser.OnData(payload, type); + + // Start receival of the rest of messages + Debug.WriteLine("Starting receiver loop."); + while (true) + { + tag = _sslStream!.ReadByte(); + size = ReadVarint32(); + payload = Read(size); + type = RawMessageParser.BuildProtobufFromTag((McsProtoTag)tag); + Debug.WriteLine($"RECEIVED PROTO OF TYPE {type.Name}"); + + parser.OnData(payload, type); + } + } + + private byte[] Read(int size) + { + byte[] buffer = new byte[size]; + int bytesRead = 0; + while (bytesRead < size) + { + bytesRead += _sslStream!.Read(buffer, bytesRead, size - bytesRead); + } + return buffer; + } + + private int ReadVarint32() + { + int result = 0; + int shift = 0; + while (true) + { + byte b = (byte)_sslStream!.ReadByte(); + result |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) break; + shift += 7; + } + return result; + } + + private static byte[] EncodeVarint32(int value) + { + List result = []; + while (value != 0) + { + byte b = (byte)(value & 0x7F); + value >>= 7; + if (value != 0) b |= 0x80; + result.Add(b); + } + return [.. result]; + } + + private void SendPacket(object packet) + { + var tagEnum = RawMessageParser.GetTagFromProtobufType(packet.GetType()); + var header = new byte[] { KMcsVersion, (byte)(int)tagEnum }; + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, packet); + + byte[] payload = ms.ToArray(); + _sslStream!.Write([.. header, .. EncodeVarint32(payload.Length), .. payload]); + } + + private void HandlePing(HeartbeatPing? ping) + { + if (ping == null) return; + + Debug.WriteLine($"Responding to ping: Stream ID: {ping.StreamId}, Last: {ping.LastStreamIdReceived}, Status: {ping.Status}"); + var pingResponse = new HeartbeatAck + { + StreamId = ping.StreamId + 1, + LastStreamIdReceived = ping.StreamId, + Status = ping.Status + }; + + SendPacket(pingResponse); + } + + private void Reset(bool noWait = false) + { + if (!noWait) + { + var timeSinceLastReset = DateTime.Now - _lastReset; + + if (timeSinceLastReset < TimeSpan.FromSeconds(MinResetIntervalSecs)) + { + Debug.WriteLine($"{timeSinceLastReset.TotalSeconds}s since last reset attempt."); + + var waitTime = TimeSpan.FromSeconds(MinResetIntervalSecs) - timeSinceLastReset; + + Debug.WriteLine($"Waiting {waitTime.TotalSeconds}seconds"); + Thread.Sleep(waitTime); + } + } + _lastReset = DateTime.Now; + + Debug.WriteLine("Resetting listener."); + Dispose(); + + ConnectAsync().GetAwaiter().GetResult(); + } + + private void StatusCheck(object? state = null) + { + TimeSpan timeSinceLastMessage = DateTime.UtcNow - _timeLastMessageReceived; + if (timeSinceLastMessage > TimeSpan.FromSeconds(MaxSilentIntervalSecs)) + { + Debug.WriteLine($"No communications received in {timeSinceLastMessage.TotalSeconds}s. Resetting connection."); + Reset(true); + } + else + { + int expectedTimeout = 1 + MaxSilentIntervalSecs - (int)timeSinceLastMessage.TotalSeconds; + _checkinTimer = new Timer(StatusCheck, null, expectedTimeout * 1000, Timeout.Infinite); + } + } + + private void OnMessage(MessageEventArgs e) + { + _timeLastMessageReceived = DateTime.Now; + + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + switch (e.Tag) + { + case McsProtoTag.KLoginResponseTag: + persistentIds?.Clear(); + break; + case McsProtoTag.KDataMessageStanzaTag: + OnDataMessage(e.Object as DataMessageStanza); + break; + case McsProtoTag.KHeartbeatPingTag: + HandlePing(e.Object as HeartbeatPing); + break; + case McsProtoTag.KCloseTag: + Reset(true); + break; + case McsProtoTag.KIqStanzaTag: + break; // I'm not sure about what this message does, and it arrives partially empty, so I will just leave it like this for now + default: + throw new ArgumentOutOfRangeException($"Unrecognized tag: {e.Tag}"); + } + } + + private void OnDataMessage(DataMessageStanza? dataMessage) + { + if (dataMessage?.PersistentId != null + && persistentIds != null + && persistentIds!.Contains(dataMessage?.PersistentId!)) + return; + + var message = string.Empty; + try + { + message = DecryptionUtility.Decrypt(dataMessage!, credentials.Keys); + } + catch (Exception ex) + { + if (ex.Message.Contains("Unsupported state or unable to authenticate data") || + ex.Message.Contains("crypto-key is missing") || + ex.Message.Contains("salt is missing")) + { + Debug.WriteLine($"Message dropped as it could not be decrypted: {ex.Message}"); + return; + } + } + finally + { + persistentIds?.Add(dataMessage!.PersistentId); + } + + var fcmMessage = JsonConvert.DeserializeObject(message); + + fcmMessage!.Data.Body.Desc = ""; + + var betterMessage = JsonConvert.SerializeObject(fcmMessage, Formatting.Indented); + + ParseNotification(fcmMessage); + NotificationReceived?.Invoke(this, betterMessage); + } + + protected virtual void ParseNotification(FcmMessage? message) { } + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/FcmRegister.cs b/RustPlusApi/RustPlusApi.Fcm/FcmRegister.cs new file mode 100644 index 0000000..7209aed --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/FcmRegister.cs @@ -0,0 +1,23 @@ +using RustPlusApi.Fcm.Data; +using RustPlusApi.Fcm.Tools; + +namespace RustPlusApi.Fcm +{ + /*public class FcmRegister + { + public static async Task RegisterAsync(string senderId) + { + var appId = $"wp:receiver.push.com#{Guid.NewGuid()}"; + + var gcmSubscription = await GcmTools.RegisterAsync(appId); + var fcmRegistration = await FcmTools.RegisterFcmAsync(senderId, gcmSubscription.Token); + + return new Credentials + { + Keys = fcmRegistration.Item1, + Gcm = gcmSubscription, + Fcm = fcmRegistration.Item2, + }; + } + }*/ +} diff --git a/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/AndroidCheckin.cs b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/AndroidCheckin.cs new file mode 100644 index 0000000..b858339 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/AndroidCheckin.cs @@ -0,0 +1,178 @@ +// +// This file was generated by a tool; you should avoid making direct changes. +// Consider using 'partial classes' to extend these types +// Input: my.proto +// + +#region Designer generated code +#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +namespace AndroidCheckinProto +{ + + [global::ProtoBuf.ProtoContract()] + public partial class ChromeBuildProto : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1)] + [global::System.ComponentModel.DefaultValue(Platform.PlatformWin)] + public Platform platform + { + get => __pbn__platform ?? Platform.PlatformWin; + set => __pbn__platform = value; + } + public bool ShouldSerializeplatform() => __pbn__platform != null; + public void Resetplatform() => __pbn__platform = null; + private Platform? __pbn__platform; + + [global::ProtoBuf.ProtoMember(2, Name = @"chrome_version")] + [global::System.ComponentModel.DefaultValue("")] + public string ChromeVersion + { + get => __pbn__ChromeVersion ?? ""; + set => __pbn__ChromeVersion = value; + } + public bool ShouldSerializeChromeVersion() => __pbn__ChromeVersion != null; + public void ResetChromeVersion() => __pbn__ChromeVersion = null; + private string __pbn__ChromeVersion; + + [global::ProtoBuf.ProtoMember(3)] + [global::System.ComponentModel.DefaultValue(Channel.ChannelStable)] + public Channel channel + { + get => __pbn__channel ?? Channel.ChannelStable; + set => __pbn__channel = value; + } + public bool ShouldSerializechannel() => __pbn__channel != null; + public void Resetchannel() => __pbn__channel = null; + private Channel? __pbn__channel; + + [global::ProtoBuf.ProtoContract()] + public enum Platform + { + [global::ProtoBuf.ProtoEnum(Name = @"PLATFORM_WIN")] + PlatformWin = 1, + [global::ProtoBuf.ProtoEnum(Name = @"PLATFORM_MAC")] + PlatformMac = 2, + [global::ProtoBuf.ProtoEnum(Name = @"PLATFORM_LINUX")] + PlatformLinux = 3, + [global::ProtoBuf.ProtoEnum(Name = @"PLATFORM_CROS")] + PlatformCros = 4, + [global::ProtoBuf.ProtoEnum(Name = @"PLATFORM_IOS")] + PlatformIos = 5, + [global::ProtoBuf.ProtoEnum(Name = @"PLATFORM_ANDROID")] + PlatformAndroid = 6, + } + + [global::ProtoBuf.ProtoContract()] + public enum Channel + { + [global::ProtoBuf.ProtoEnum(Name = @"CHANNEL_STABLE")] + ChannelStable = 1, + [global::ProtoBuf.ProtoEnum(Name = @"CHANNEL_BETA")] + ChannelBeta = 2, + [global::ProtoBuf.ProtoEnum(Name = @"CHANNEL_DEV")] + ChannelDev = 3, + [global::ProtoBuf.ProtoEnum(Name = @"CHANNEL_CANARY")] + ChannelCanary = 4, + [global::ProtoBuf.ProtoEnum(Name = @"CHANNEL_UNKNOWN")] + ChannelUnknown = 5, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class AndroidCheckinProto : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(2, Name = @"last_checkin_msec")] + public long LastCheckinMsec + { + get => __pbn__LastCheckinMsec.GetValueOrDefault(); + set => __pbn__LastCheckinMsec = value; + } + public bool ShouldSerializeLastCheckinMsec() => __pbn__LastCheckinMsec != null; + public void ResetLastCheckinMsec() => __pbn__LastCheckinMsec = null; + private long? __pbn__LastCheckinMsec; + + [global::ProtoBuf.ProtoMember(6, Name = @"cell_operator")] + [global::System.ComponentModel.DefaultValue("")] + public string CellOperator + { + get => __pbn__CellOperator ?? ""; + set => __pbn__CellOperator = value; + } + public bool ShouldSerializeCellOperator() => __pbn__CellOperator != null; + public void ResetCellOperator() => __pbn__CellOperator = null; + private string __pbn__CellOperator; + + [global::ProtoBuf.ProtoMember(7, Name = @"sim_operator")] + [global::System.ComponentModel.DefaultValue("")] + public string SimOperator + { + get => __pbn__SimOperator ?? ""; + set => __pbn__SimOperator = value; + } + public bool ShouldSerializeSimOperator() => __pbn__SimOperator != null; + public void ResetSimOperator() => __pbn__SimOperator = null; + private string __pbn__SimOperator; + + [global::ProtoBuf.ProtoMember(8, Name = @"roaming")] + [global::System.ComponentModel.DefaultValue("")] + public string Roaming + { + get => __pbn__Roaming ?? ""; + set => __pbn__Roaming = value; + } + public bool ShouldSerializeRoaming() => __pbn__Roaming != null; + public void ResetRoaming() => __pbn__Roaming = null; + private string __pbn__Roaming; + + [global::ProtoBuf.ProtoMember(9, Name = @"user_number")] + public int UserNumber + { + get => __pbn__UserNumber.GetValueOrDefault(); + set => __pbn__UserNumber = value; + } + public bool ShouldSerializeUserNumber() => __pbn__UserNumber != null; + public void ResetUserNumber() => __pbn__UserNumber = null; + private int? __pbn__UserNumber; + + [global::ProtoBuf.ProtoMember(12, Name = @"type")] + [global::System.ComponentModel.DefaultValue(DeviceType.DeviceAndroidOs)] + public DeviceType Type + { + get => __pbn__Type ?? DeviceType.DeviceAndroidOs; + set => __pbn__Type = value; + } + public bool ShouldSerializeType() => __pbn__Type != null; + public void ResetType() => __pbn__Type = null; + private DeviceType? __pbn__Type; + + [global::ProtoBuf.ProtoMember(13, Name = @"chrome_build")] + public ChromeBuildProto ChromeBuild { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public enum DeviceType + { + [global::ProtoBuf.ProtoEnum(Name = @"DEVICE_ANDROID_OS")] + DeviceAndroidOs = 1, + [global::ProtoBuf.ProtoEnum(Name = @"DEVICE_IOS_OS")] + DeviceIosOs = 2, + [global::ProtoBuf.ProtoEnum(Name = @"DEVICE_CHROME_BROWSER")] + DeviceChromeBrowser = 3, + [global::ProtoBuf.ProtoEnum(Name = @"DEVICE_CHROME_OS")] + DeviceChromeOs = 4, + } + +} + +#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#endregion diff --git a/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/Checkin.cs b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/Checkin.cs new file mode 100644 index 0000000..2388427 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/Checkin.cs @@ -0,0 +1,315 @@ +// This file needs to be changed +// +// This file was generated by a tool; you should avoid making direct changes. +// Consider using 'partial classes' to extend these types +// Input: my.proto +// + +#region Designer generated code +#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +namespace CheckinProto +{ + + [global::ProtoBuf.ProtoContract()] + public partial class GservicesSetting : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"name", IsRequired = true)] + public byte[] Name { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"value", IsRequired = true)] + public byte[] Value { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class AndroidCheckinRequest : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"imei")] + [global::System.ComponentModel.DefaultValue("")] + public string Imei + { + get => __pbn__Imei ?? ""; + set => __pbn__Imei = value; + } + public bool ShouldSerializeImei() => __pbn__Imei != null; + public void ResetImei() => __pbn__Imei = null; + private string __pbn__Imei; + + [global::ProtoBuf.ProtoMember(10, Name = @"meid")] + [global::System.ComponentModel.DefaultValue("")] + public string Meid + { + get => __pbn__Meid ?? ""; + set => __pbn__Meid = value; + } + public bool ShouldSerializeMeid() => __pbn__Meid != null; + public void ResetMeid() => __pbn__Meid = null; + private string __pbn__Meid; + + [global::ProtoBuf.ProtoMember(9, Name = @"mac_addr")] + public global::System.Collections.Generic.List MacAddrs { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(19, Name = @"mac_addr_type")] + public global::System.Collections.Generic.List MacAddrTypes { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(16, Name = @"serial_number")] + [global::System.ComponentModel.DefaultValue("")] + public string SerialNumber + { + get => __pbn__SerialNumber ?? ""; + set => __pbn__SerialNumber = value; + } + public bool ShouldSerializeSerialNumber() => __pbn__SerialNumber != null; + public void ResetSerialNumber() => __pbn__SerialNumber = null; + private string __pbn__SerialNumber; + + [global::ProtoBuf.ProtoMember(17, Name = @"esn")] + [global::System.ComponentModel.DefaultValue("")] + public string Esn + { + get => __pbn__Esn ?? ""; + set => __pbn__Esn = value; + } + public bool ShouldSerializeEsn() => __pbn__Esn != null; + public void ResetEsn() => __pbn__Esn = null; + private string __pbn__Esn; + + [global::ProtoBuf.ProtoMember(2, Name = @"id")] + public long Id + { + get => __pbn__Id.GetValueOrDefault(); + set => __pbn__Id = value; + } + public bool ShouldSerializeId() => __pbn__Id != null; + public void ResetId() => __pbn__Id = null; + private long? __pbn__Id; + + [global::ProtoBuf.ProtoMember(7, Name = @"logging_id")] + public long LoggingId + { + get => __pbn__LoggingId.GetValueOrDefault(); + set => __pbn__LoggingId = value; + } + public bool ShouldSerializeLoggingId() => __pbn__LoggingId != null; + public void ResetLoggingId() => __pbn__LoggingId = null; + private long? __pbn__LoggingId; + + [global::ProtoBuf.ProtoMember(3, Name = @"digest")] + [global::System.ComponentModel.DefaultValue("")] + public string Digest + { + get => __pbn__Digest ?? ""; + set => __pbn__Digest = value; + } + public bool ShouldSerializeDigest() => __pbn__Digest != null; + public void ResetDigest() => __pbn__Digest = null; + private string __pbn__Digest; + + [global::ProtoBuf.ProtoMember(6, Name = @"locale")] + [global::System.ComponentModel.DefaultValue("")] + public string Locale + { + get => __pbn__Locale ?? ""; + set => __pbn__Locale = value; + } + public bool ShouldSerializeLocale() => __pbn__Locale != null; + public void ResetLocale() => __pbn__Locale = null; + private string __pbn__Locale; + + [global::ProtoBuf.ProtoMember(4, Name = @"checkin", IsRequired = true)] + public AndroidCheckinProto.AndroidCheckinProto Checkin { get; set; } + + [global::ProtoBuf.ProtoMember(5, Name = @"desired_build")] + [global::System.ComponentModel.DefaultValue("")] + public string DesiredBuild + { + get => __pbn__DesiredBuild ?? ""; + set => __pbn__DesiredBuild = value; + } + public bool ShouldSerializeDesiredBuild() => __pbn__DesiredBuild != null; + public void ResetDesiredBuild() => __pbn__DesiredBuild = null; + private string __pbn__DesiredBuild; + + [global::ProtoBuf.ProtoMember(8, Name = @"market_checkin")] + [global::System.ComponentModel.DefaultValue("")] + public string MarketCheckin + { + get => __pbn__MarketCheckin ?? ""; + set => __pbn__MarketCheckin = value; + } + public bool ShouldSerializeMarketCheckin() => __pbn__MarketCheckin != null; + public void ResetMarketCheckin() => __pbn__MarketCheckin = null; + private string __pbn__MarketCheckin; + + [global::ProtoBuf.ProtoMember(11, Name = @"account_cookie")] + public global::System.Collections.Generic.List AccountCookies { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(12, Name = @"time_zone")] + [global::System.ComponentModel.DefaultValue("")] + public string TimeZone + { + get => __pbn__TimeZone ?? ""; + set => __pbn__TimeZone = value; + } + public bool ShouldSerializeTimeZone() => __pbn__TimeZone != null; + public void ResetTimeZone() => __pbn__TimeZone = null; + private string __pbn__TimeZone; + + [global::ProtoBuf.ProtoMember(13, Name = @"security_token", DataFormat = global::ProtoBuf.DataFormat.FixedSize)] + public ulong SecurityToken + { + get => __pbn__SecurityToken.GetValueOrDefault(); + set => __pbn__SecurityToken = value; + } + public bool ShouldSerializeSecurityToken() => __pbn__SecurityToken != null; + public void ResetSecurityToken() => __pbn__SecurityToken = null; + private ulong? __pbn__SecurityToken; + + [global::ProtoBuf.ProtoMember(14, Name = @"version")] + public int Version + { + get => __pbn__Version.GetValueOrDefault(); + set => __pbn__Version = value; + } + public bool ShouldSerializeVersion() => __pbn__Version != null; + public void ResetVersion() => __pbn__Version = null; + private int? __pbn__Version; + + [global::ProtoBuf.ProtoMember(15, Name = @"ota_cert")] + public global::System.Collections.Generic.List OtaCerts { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(20, Name = @"fragment")] + public int Fragment + { + get => __pbn__Fragment.GetValueOrDefault(); + set => __pbn__Fragment = value; + } + public bool ShouldSerializeFragment() => __pbn__Fragment != null; + public void ResetFragment() => __pbn__Fragment = null; + private int? __pbn__Fragment; + + [global::ProtoBuf.ProtoMember(21, Name = @"user_name")] + [global::System.ComponentModel.DefaultValue("")] + public string UserName + { + get => __pbn__UserName ?? ""; + set => __pbn__UserName = value; + } + public bool ShouldSerializeUserName() => __pbn__UserName != null; + public void ResetUserName() => __pbn__UserName = null; + private string __pbn__UserName; + + [global::ProtoBuf.ProtoMember(22, Name = @"user_serial_number")] + public int UserSerialNumber + { + get => __pbn__UserSerialNumber.GetValueOrDefault(); + set => __pbn__UserSerialNumber = value; + } + public bool ShouldSerializeUserSerialNumber() => __pbn__UserSerialNumber != null; + public void ResetUserSerialNumber() => __pbn__UserSerialNumber = null; + private int? __pbn__UserSerialNumber; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class AndroidCheckinResponse : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"stats_ok", IsRequired = true)] + public bool StatsOk { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"time_msec")] + public long TimeMsec + { + get => __pbn__TimeMsec.GetValueOrDefault(); + set => __pbn__TimeMsec = value; + } + public bool ShouldSerializeTimeMsec() => __pbn__TimeMsec != null; + public void ResetTimeMsec() => __pbn__TimeMsec = null; + private long? __pbn__TimeMsec; + + [global::ProtoBuf.ProtoMember(4, Name = @"digest")] + [global::System.ComponentModel.DefaultValue("")] + public string Digest + { + get => __pbn__Digest ?? ""; + set => __pbn__Digest = value; + } + public bool ShouldSerializeDigest() => __pbn__Digest != null; + public void ResetDigest() => __pbn__Digest = null; + private string __pbn__Digest; + + [global::ProtoBuf.ProtoMember(9, Name = @"settings_diff")] + public bool SettingsDiff + { + get => __pbn__SettingsDiff.GetValueOrDefault(); + set => __pbn__SettingsDiff = value; + } + public bool ShouldSerializeSettingsDiff() => __pbn__SettingsDiff != null; + public void ResetSettingsDiff() => __pbn__SettingsDiff = null; + private bool? __pbn__SettingsDiff; + + [global::ProtoBuf.ProtoMember(10, Name = @"delete_setting")] + public global::System.Collections.Generic.List DeleteSettings { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(5, Name = @"setting")] + public global::System.Collections.Generic.List Settings { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(6, Name = @"market_ok")] + public bool MarketOk + { + get => __pbn__MarketOk.GetValueOrDefault(); + set => __pbn__MarketOk = value; + } + public bool ShouldSerializeMarketOk() => __pbn__MarketOk != null; + public void ResetMarketOk() => __pbn__MarketOk = null; + private bool? __pbn__MarketOk; + + [global::ProtoBuf.ProtoMember(7, Name = @"android_id", DataFormat = global::ProtoBuf.DataFormat.FixedSize)] + public ulong AndroidId + { + get => __pbn__AndroidId.GetValueOrDefault(); + set => __pbn__AndroidId = value; + } + public bool ShouldSerializeAndroidId() => __pbn__AndroidId != null; + public void ResetAndroidId() => __pbn__AndroidId = null; + private ulong? __pbn__AndroidId; + + [global::ProtoBuf.ProtoMember(8, Name = @"security_token", DataFormat = global::ProtoBuf.DataFormat.FixedSize)] + public ulong SecurityToken + { + get => __pbn__SecurityToken.GetValueOrDefault(); + set => __pbn__SecurityToken = value; + } + public bool ShouldSerializeSecurityToken() => __pbn__SecurityToken != null; + public void ResetSecurityToken() => __pbn__SecurityToken = null; + private ulong? __pbn__SecurityToken; + + [global::ProtoBuf.ProtoMember(11, Name = @"version_info")] + [global::System.ComponentModel.DefaultValue("")] + public string VersionInfo + { + get => __pbn__VersionInfo ?? ""; + set => __pbn__VersionInfo = value; + } + public bool ShouldSerializeVersionInfo() => __pbn__VersionInfo != null; + public void ResetVersionInfo() => __pbn__VersionInfo = null; + private string __pbn__VersionInfo; + + } + +} + +#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#endregion diff --git a/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/Mcs.cs b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/Mcs.cs new file mode 100644 index 0000000..f0528c9 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/Mcs.cs @@ -0,0 +1,856 @@ +// +// This file was generated by a tool; you should avoid making direct changes. +// Consider using 'partial classes' to extend these types +// Input: my.proto +// + +#region Designer generated code +#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +namespace McsProto +{ + + [global::ProtoBuf.ProtoContract()] + public partial class HeartbeatPing : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"stream_id")] + public int StreamId + { + get => __pbn__StreamId.GetValueOrDefault(); + set => __pbn__StreamId = value; + } + public bool ShouldSerializeStreamId() => __pbn__StreamId != null; + public void ResetStreamId() => __pbn__StreamId = null; + private int? __pbn__StreamId; + + [global::ProtoBuf.ProtoMember(2, Name = @"last_stream_id_received")] + public int LastStreamIdReceived + { + get => __pbn__LastStreamIdReceived.GetValueOrDefault(); + set => __pbn__LastStreamIdReceived = value; + } + public bool ShouldSerializeLastStreamIdReceived() => __pbn__LastStreamIdReceived != null; + public void ResetLastStreamIdReceived() => __pbn__LastStreamIdReceived = null; + private int? __pbn__LastStreamIdReceived; + + [global::ProtoBuf.ProtoMember(3, Name = @"status")] + public long Status + { + get => __pbn__Status.GetValueOrDefault(); + set => __pbn__Status = value; + } + public bool ShouldSerializeStatus() => __pbn__Status != null; + public void ResetStatus() => __pbn__Status = null; + private long? __pbn__Status; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class HeartbeatAck : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"stream_id")] + public int StreamId + { + get => __pbn__StreamId.GetValueOrDefault(); + set => __pbn__StreamId = value; + } + public bool ShouldSerializeStreamId() => __pbn__StreamId != null; + public void ResetStreamId() => __pbn__StreamId = null; + private int? __pbn__StreamId; + + [global::ProtoBuf.ProtoMember(2, Name = @"last_stream_id_received")] + public int LastStreamIdReceived + { + get => __pbn__LastStreamIdReceived.GetValueOrDefault(); + set => __pbn__LastStreamIdReceived = value; + } + public bool ShouldSerializeLastStreamIdReceived() => __pbn__LastStreamIdReceived != null; + public void ResetLastStreamIdReceived() => __pbn__LastStreamIdReceived = null; + private int? __pbn__LastStreamIdReceived; + + [global::ProtoBuf.ProtoMember(3, Name = @"status")] + public long Status + { + get => __pbn__Status.GetValueOrDefault(); + set => __pbn__Status = value; + } + public bool ShouldSerializeStatus() => __pbn__Status != null; + public void ResetStatus() => __pbn__Status = null; + private long? __pbn__Status; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ErrorInfo : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"code", IsRequired = true)] + public int Code { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"message")] + [global::System.ComponentModel.DefaultValue("")] + public string Message + { + get => __pbn__Message ?? ""; + set => __pbn__Message = value; + } + public bool ShouldSerializeMessage() => __pbn__Message != null; + public void ResetMessage() => __pbn__Message = null; + private string __pbn__Message; + + [global::ProtoBuf.ProtoMember(3, Name = @"type")] + [global::System.ComponentModel.DefaultValue("")] + public string Type + { + get => __pbn__Type ?? ""; + set => __pbn__Type = value; + } + public bool ShouldSerializeType() => __pbn__Type != null; + public void ResetType() => __pbn__Type = null; + private string __pbn__Type; + + [global::ProtoBuf.ProtoMember(4, Name = @"extension")] + public Extension Extension { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class Setting : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"name", IsRequired = true)] + public string Name { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"value", IsRequired = true)] + public string Value { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class HeartbeatStat : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"ip", IsRequired = true)] + public string Ip { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"timeout", IsRequired = true)] + public bool Timeout { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"interval_ms", IsRequired = true)] + public int IntervalMs { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class HeartbeatConfig : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"upload_stat")] + public bool UploadStat + { + get => __pbn__UploadStat.GetValueOrDefault(); + set => __pbn__UploadStat = value; + } + public bool ShouldSerializeUploadStat() => __pbn__UploadStat != null; + public void ResetUploadStat() => __pbn__UploadStat = null; + private bool? __pbn__UploadStat; + + [global::ProtoBuf.ProtoMember(2, Name = @"ip")] + [global::System.ComponentModel.DefaultValue("")] + public string Ip + { + get => __pbn__Ip ?? ""; + set => __pbn__Ip = value; + } + public bool ShouldSerializeIp() => __pbn__Ip != null; + public void ResetIp() => __pbn__Ip = null; + private string __pbn__Ip; + + [global::ProtoBuf.ProtoMember(3, Name = @"interval_ms")] + public int IntervalMs + { + get => __pbn__IntervalMs.GetValueOrDefault(); + set => __pbn__IntervalMs = value; + } + public bool ShouldSerializeIntervalMs() => __pbn__IntervalMs != null; + public void ResetIntervalMs() => __pbn__IntervalMs = null; + private int? __pbn__IntervalMs; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class ClientEvent : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1)] + [global::System.ComponentModel.DefaultValue(Type.Unknown)] + public Type type + { + get => __pbn__type ?? Type.Unknown; + set => __pbn__type = value; + } + public bool ShouldSerializetype() => __pbn__type != null; + public void Resettype() => __pbn__type = null; + private Type? __pbn__type; + + [global::ProtoBuf.ProtoMember(100, Name = @"number_discarded_events")] + public uint NumberDiscardedEvents + { + get => __pbn__NumberDiscardedEvents.GetValueOrDefault(); + set => __pbn__NumberDiscardedEvents = value; + } + public bool ShouldSerializeNumberDiscardedEvents() => __pbn__NumberDiscardedEvents != null; + public void ResetNumberDiscardedEvents() => __pbn__NumberDiscardedEvents = null; + private uint? __pbn__NumberDiscardedEvents; + + [global::ProtoBuf.ProtoMember(200, Name = @"network_type")] + public int NetworkType + { + get => __pbn__NetworkType.GetValueOrDefault(); + set => __pbn__NetworkType = value; + } + public bool ShouldSerializeNetworkType() => __pbn__NetworkType != null; + public void ResetNetworkType() => __pbn__NetworkType = null; + private int? __pbn__NetworkType; + + [global::ProtoBuf.ProtoMember(202, Name = @"time_connection_started_ms")] + public ulong TimeConnectionStartedMs + { + get => __pbn__TimeConnectionStartedMs.GetValueOrDefault(); + set => __pbn__TimeConnectionStartedMs = value; + } + public bool ShouldSerializeTimeConnectionStartedMs() => __pbn__TimeConnectionStartedMs != null; + public void ResetTimeConnectionStartedMs() => __pbn__TimeConnectionStartedMs = null; + private ulong? __pbn__TimeConnectionStartedMs; + + [global::ProtoBuf.ProtoMember(203, Name = @"time_connection_ended_ms")] + public ulong TimeConnectionEndedMs + { + get => __pbn__TimeConnectionEndedMs.GetValueOrDefault(); + set => __pbn__TimeConnectionEndedMs = value; + } + public bool ShouldSerializeTimeConnectionEndedMs() => __pbn__TimeConnectionEndedMs != null; + public void ResetTimeConnectionEndedMs() => __pbn__TimeConnectionEndedMs = null; + private ulong? __pbn__TimeConnectionEndedMs; + + [global::ProtoBuf.ProtoMember(204, Name = @"error_code")] + public int ErrorCode + { + get => __pbn__ErrorCode.GetValueOrDefault(); + set => __pbn__ErrorCode = value; + } + public bool ShouldSerializeErrorCode() => __pbn__ErrorCode != null; + public void ResetErrorCode() => __pbn__ErrorCode = null; + private int? __pbn__ErrorCode; + + [global::ProtoBuf.ProtoMember(300, Name = @"time_connection_established_ms")] + public ulong TimeConnectionEstablishedMs + { + get => __pbn__TimeConnectionEstablishedMs.GetValueOrDefault(); + set => __pbn__TimeConnectionEstablishedMs = value; + } + public bool ShouldSerializeTimeConnectionEstablishedMs() => __pbn__TimeConnectionEstablishedMs != null; + public void ResetTimeConnectionEstablishedMs() => __pbn__TimeConnectionEstablishedMs = null; + private ulong? __pbn__TimeConnectionEstablishedMs; + + [global::ProtoBuf.ProtoContract()] + public enum Type + { + [global::ProtoBuf.ProtoEnum(Name = @"UNKNOWN")] + Unknown = 0, + [global::ProtoBuf.ProtoEnum(Name = @"DISCARDED_EVENTS")] + DiscardedEvents = 1, + [global::ProtoBuf.ProtoEnum(Name = @"FAILED_CONNECTION")] + FailedConnection = 2, + [global::ProtoBuf.ProtoEnum(Name = @"SUCCESSFUL_CONNECTION")] + SuccessfulConnection = 3, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class LoginRequest : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"id", IsRequired = true)] + public string Id { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"domain", IsRequired = true)] + public string Domain { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"user", IsRequired = true)] + public string User { get; set; } + + [global::ProtoBuf.ProtoMember(4, Name = @"resource", IsRequired = true)] + public string Resource { get; set; } + + [global::ProtoBuf.ProtoMember(5, Name = @"auth_token", IsRequired = true)] + public string AuthToken { get; set; } + + [global::ProtoBuf.ProtoMember(6, Name = @"device_id")] + [global::System.ComponentModel.DefaultValue("")] + public string DeviceId + { + get => __pbn__DeviceId ?? ""; + set => __pbn__DeviceId = value; + } + public bool ShouldSerializeDeviceId() => __pbn__DeviceId != null; + public void ResetDeviceId() => __pbn__DeviceId = null; + private string __pbn__DeviceId; + + [global::ProtoBuf.ProtoMember(7, Name = @"last_rmq_id")] + public long LastRmqId + { + get => __pbn__LastRmqId.GetValueOrDefault(); + set => __pbn__LastRmqId = value; + } + public bool ShouldSerializeLastRmqId() => __pbn__LastRmqId != null; + public void ResetLastRmqId() => __pbn__LastRmqId = null; + private long? __pbn__LastRmqId; + + [global::ProtoBuf.ProtoMember(8, Name = @"setting")] + public global::System.Collections.Generic.List Settings { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(10, Name = @"received_persistent_id")] + public global::System.Collections.Generic.List ReceivedPersistentIds { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(12, Name = @"adaptive_heartbeat")] + public bool AdaptiveHeartbeat + { + get => __pbn__AdaptiveHeartbeat.GetValueOrDefault(); + set => __pbn__AdaptiveHeartbeat = value; + } + public bool ShouldSerializeAdaptiveHeartbeat() => __pbn__AdaptiveHeartbeat != null; + public void ResetAdaptiveHeartbeat() => __pbn__AdaptiveHeartbeat = null; + private bool? __pbn__AdaptiveHeartbeat; + + [global::ProtoBuf.ProtoMember(13, Name = @"heartbeat_stat")] + public HeartbeatStat HeartbeatStat { get; set; } + + [global::ProtoBuf.ProtoMember(14, Name = @"use_rmq2")] + public bool UseRmq2 + { + get => __pbn__UseRmq2.GetValueOrDefault(); + set => __pbn__UseRmq2 = value; + } + public bool ShouldSerializeUseRmq2() => __pbn__UseRmq2 != null; + public void ResetUseRmq2() => __pbn__UseRmq2 = null; + private bool? __pbn__UseRmq2; + + [global::ProtoBuf.ProtoMember(15, Name = @"account_id")] + public long AccountId + { + get => __pbn__AccountId.GetValueOrDefault(); + set => __pbn__AccountId = value; + } + public bool ShouldSerializeAccountId() => __pbn__AccountId != null; + public void ResetAccountId() => __pbn__AccountId = null; + private long? __pbn__AccountId; + + [global::ProtoBuf.ProtoMember(16)] + [global::System.ComponentModel.DefaultValue(AuthService.AndroidId)] + public AuthService auth_service + { + get => __pbn__auth_service ?? AuthService.AndroidId; + set => __pbn__auth_service = value; + } + public bool ShouldSerializeauth_service() => __pbn__auth_service != null; + public void Resetauth_service() => __pbn__auth_service = null; + private AuthService? __pbn__auth_service; + + [global::ProtoBuf.ProtoMember(17, Name = @"network_type")] + public int NetworkType + { + get => __pbn__NetworkType.GetValueOrDefault(); + set => __pbn__NetworkType = value; + } + public bool ShouldSerializeNetworkType() => __pbn__NetworkType != null; + public void ResetNetworkType() => __pbn__NetworkType = null; + private int? __pbn__NetworkType; + + [global::ProtoBuf.ProtoMember(18, Name = @"status")] + public long Status + { + get => __pbn__Status.GetValueOrDefault(); + set => __pbn__Status = value; + } + public bool ShouldSerializeStatus() => __pbn__Status != null; + public void ResetStatus() => __pbn__Status = null; + private long? __pbn__Status; + + [global::ProtoBuf.ProtoMember(22, Name = @"client_event")] + public global::System.Collections.Generic.List ClientEvents { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoContract()] + public enum AuthService + { + [global::ProtoBuf.ProtoEnum(Name = @"ANDROID_ID")] + AndroidId = 2, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class LoginResponse : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"id", IsRequired = true)] + public string Id { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"jid")] + [global::System.ComponentModel.DefaultValue("")] + public string Jid + { + get => __pbn__Jid ?? ""; + set => __pbn__Jid = value; + } + public bool ShouldSerializeJid() => __pbn__Jid != null; + public void ResetJid() => __pbn__Jid = null; + private string __pbn__Jid; + + [global::ProtoBuf.ProtoMember(3, Name = @"error")] + public ErrorInfo Error { get; set; } + + [global::ProtoBuf.ProtoMember(4, Name = @"setting")] + public global::System.Collections.Generic.List Settings { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(5, Name = @"stream_id")] + public int StreamId + { + get => __pbn__StreamId.GetValueOrDefault(); + set => __pbn__StreamId = value; + } + public bool ShouldSerializeStreamId() => __pbn__StreamId != null; + public void ResetStreamId() => __pbn__StreamId = null; + private int? __pbn__StreamId; + + [global::ProtoBuf.ProtoMember(6, Name = @"last_stream_id_received")] + public int LastStreamIdReceived + { + get => __pbn__LastStreamIdReceived.GetValueOrDefault(); + set => __pbn__LastStreamIdReceived = value; + } + public bool ShouldSerializeLastStreamIdReceived() => __pbn__LastStreamIdReceived != null; + public void ResetLastStreamIdReceived() => __pbn__LastStreamIdReceived = null; + private int? __pbn__LastStreamIdReceived; + + [global::ProtoBuf.ProtoMember(7, Name = @"heartbeat_config")] + public HeartbeatConfig HeartbeatConfig { get; set; } + + [global::ProtoBuf.ProtoMember(8, Name = @"server_timestamp")] + public long ServerTimestamp + { + get => __pbn__ServerTimestamp.GetValueOrDefault(); + set => __pbn__ServerTimestamp = value; + } + public bool ShouldSerializeServerTimestamp() => __pbn__ServerTimestamp != null; + public void ResetServerTimestamp() => __pbn__ServerTimestamp = null; + private long? __pbn__ServerTimestamp; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class StreamErrorStanza : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"type", IsRequired = true)] + public string Type { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"text")] + [global::System.ComponentModel.DefaultValue("")] + public string Text + { + get => __pbn__Text ?? ""; + set => __pbn__Text = value; + } + public bool ShouldSerializeText() => __pbn__Text != null; + public void ResetText() => __pbn__Text = null; + private string __pbn__Text; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class Close : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class Extension : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"id", IsRequired = true)] + public int Id { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"data", IsRequired = true)] + public byte[] Data { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class IqStanza : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"rmq_id")] + public long RmqId + { + get => __pbn__RmqId.GetValueOrDefault(); + set => __pbn__RmqId = value; + } + public bool ShouldSerializeRmqId() => __pbn__RmqId != null; + public void ResetRmqId() => __pbn__RmqId = null; + private long? __pbn__RmqId; + + [global::ProtoBuf.ProtoMember(2, Name = @"type", IsRequired = true)] + public IqType Type { get; set; } + + [global::ProtoBuf.ProtoMember(3, Name = @"id", IsRequired = true)] + public string Id { get; set; } + + [global::ProtoBuf.ProtoMember(4, Name = @"from")] + [global::System.ComponentModel.DefaultValue("")] + public string From + { + get => __pbn__From ?? ""; + set => __pbn__From = value; + } + public bool ShouldSerializeFrom() => __pbn__From != null; + public void ResetFrom() => __pbn__From = null; + private string __pbn__From; + + [global::ProtoBuf.ProtoMember(5, Name = @"to")] + [global::System.ComponentModel.DefaultValue("")] + public string To + { + get => __pbn__To ?? ""; + set => __pbn__To = value; + } + public bool ShouldSerializeTo() => __pbn__To != null; + public void ResetTo() => __pbn__To = null; + private string __pbn__To; + + [global::ProtoBuf.ProtoMember(6, Name = @"error")] + public ErrorInfo Error { get; set; } + + [global::ProtoBuf.ProtoMember(7, Name = @"extension")] + public Extension Extension { get; set; } + + [global::ProtoBuf.ProtoMember(8, Name = @"persistent_id")] + [global::System.ComponentModel.DefaultValue("")] + public string PersistentId + { + get => __pbn__PersistentId ?? ""; + set => __pbn__PersistentId = value; + } + public bool ShouldSerializePersistentId() => __pbn__PersistentId != null; + public void ResetPersistentId() => __pbn__PersistentId = null; + private string __pbn__PersistentId; + + [global::ProtoBuf.ProtoMember(9, Name = @"stream_id")] + public int StreamId + { + get => __pbn__StreamId.GetValueOrDefault(); + set => __pbn__StreamId = value; + } + public bool ShouldSerializeStreamId() => __pbn__StreamId != null; + public void ResetStreamId() => __pbn__StreamId = null; + private int? __pbn__StreamId; + + [global::ProtoBuf.ProtoMember(10, Name = @"last_stream_id_received")] + public int LastStreamIdReceived + { + get => __pbn__LastStreamIdReceived.GetValueOrDefault(); + set => __pbn__LastStreamIdReceived = value; + } + public bool ShouldSerializeLastStreamIdReceived() => __pbn__LastStreamIdReceived != null; + public void ResetLastStreamIdReceived() => __pbn__LastStreamIdReceived = null; + private int? __pbn__LastStreamIdReceived; + + [global::ProtoBuf.ProtoMember(11, Name = @"account_id")] + public long AccountId + { + get => __pbn__AccountId.GetValueOrDefault(); + set => __pbn__AccountId = value; + } + public bool ShouldSerializeAccountId() => __pbn__AccountId != null; + public void ResetAccountId() => __pbn__AccountId = null; + private long? __pbn__AccountId; + + [global::ProtoBuf.ProtoMember(12, Name = @"status")] + public long Status + { + get => __pbn__Status.GetValueOrDefault(); + set => __pbn__Status = value; + } + public bool ShouldSerializeStatus() => __pbn__Status != null; + public void ResetStatus() => __pbn__Status = null; + private long? __pbn__Status; + + [global::ProtoBuf.ProtoContract()] + public enum IqType + { + [global::ProtoBuf.ProtoEnum(Name = @"GET")] + Get = 0, + [global::ProtoBuf.ProtoEnum(Name = @"SET")] + Set = 1, + [global::ProtoBuf.ProtoEnum(Name = @"RESULT")] + Result = 2, + [global::ProtoBuf.ProtoEnum(Name = @"IQ_ERROR")] + IqError = 3, + } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class AppData : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"key", IsRequired = true)] + public string Key { get; set; } + + [global::ProtoBuf.ProtoMember(2, Name = @"value", IsRequired = true)] + public string Value { get; set; } + + } + + [global::ProtoBuf.ProtoContract()] + public partial class DataMessageStanza : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(2, Name = @"id")] + [global::System.ComponentModel.DefaultValue("")] + public string Id + { + get => __pbn__Id ?? ""; + set => __pbn__Id = value; + } + public bool ShouldSerializeId() => __pbn__Id != null; + public void ResetId() => __pbn__Id = null; + private string __pbn__Id; + + [global::ProtoBuf.ProtoMember(3, Name = @"from", IsRequired = true)] + public string From { get; set; } + + [global::ProtoBuf.ProtoMember(4, Name = @"to")] + [global::System.ComponentModel.DefaultValue("")] + public string To + { + get => __pbn__To ?? ""; + set => __pbn__To = value; + } + public bool ShouldSerializeTo() => __pbn__To != null; + public void ResetTo() => __pbn__To = null; + private string __pbn__To; + + [global::ProtoBuf.ProtoMember(5, Name = @"category", IsRequired = true)] + public string Category { get; set; } + + [global::ProtoBuf.ProtoMember(6, Name = @"token")] + [global::System.ComponentModel.DefaultValue("")] + public string Token + { + get => __pbn__Token ?? ""; + set => __pbn__Token = value; + } + public bool ShouldSerializeToken() => __pbn__Token != null; + public void ResetToken() => __pbn__Token = null; + private string __pbn__Token; + + [global::ProtoBuf.ProtoMember(7, Name = @"app_data")] + public global::System.Collections.Generic.List AppDatas { get; } = new global::System.Collections.Generic.List(); + + [global::ProtoBuf.ProtoMember(8, Name = @"from_trusted_server")] + public bool FromTrustedServer + { + get => __pbn__FromTrustedServer.GetValueOrDefault(); + set => __pbn__FromTrustedServer = value; + } + public bool ShouldSerializeFromTrustedServer() => __pbn__FromTrustedServer != null; + public void ResetFromTrustedServer() => __pbn__FromTrustedServer = null; + private bool? __pbn__FromTrustedServer; + + [global::ProtoBuf.ProtoMember(9, Name = @"persistent_id")] + [global::System.ComponentModel.DefaultValue("")] + public string PersistentId + { + get => __pbn__PersistentId ?? ""; + set => __pbn__PersistentId = value; + } + public bool ShouldSerializePersistentId() => __pbn__PersistentId != null; + public void ResetPersistentId() => __pbn__PersistentId = null; + private string __pbn__PersistentId; + + [global::ProtoBuf.ProtoMember(10, Name = @"stream_id")] + public int StreamId + { + get => __pbn__StreamId.GetValueOrDefault(); + set => __pbn__StreamId = value; + } + public bool ShouldSerializeStreamId() => __pbn__StreamId != null; + public void ResetStreamId() => __pbn__StreamId = null; + private int? __pbn__StreamId; + + [global::ProtoBuf.ProtoMember(11, Name = @"last_stream_id_received")] + public int LastStreamIdReceived + { + get => __pbn__LastStreamIdReceived.GetValueOrDefault(); + set => __pbn__LastStreamIdReceived = value; + } + public bool ShouldSerializeLastStreamIdReceived() => __pbn__LastStreamIdReceived != null; + public void ResetLastStreamIdReceived() => __pbn__LastStreamIdReceived = null; + private int? __pbn__LastStreamIdReceived; + + [global::ProtoBuf.ProtoMember(13, Name = @"reg_id")] + [global::System.ComponentModel.DefaultValue("")] + public string RegId + { + get => __pbn__RegId ?? ""; + set => __pbn__RegId = value; + } + public bool ShouldSerializeRegId() => __pbn__RegId != null; + public void ResetRegId() => __pbn__RegId = null; + private string __pbn__RegId; + + [global::ProtoBuf.ProtoMember(16, Name = @"device_user_id")] + public long DeviceUserId + { + get => __pbn__DeviceUserId.GetValueOrDefault(); + set => __pbn__DeviceUserId = value; + } + public bool ShouldSerializeDeviceUserId() => __pbn__DeviceUserId != null; + public void ResetDeviceUserId() => __pbn__DeviceUserId = null; + private long? __pbn__DeviceUserId; + + [global::ProtoBuf.ProtoMember(17, Name = @"ttl")] + public int Ttl + { + get => __pbn__Ttl.GetValueOrDefault(); + set => __pbn__Ttl = value; + } + public bool ShouldSerializeTtl() => __pbn__Ttl != null; + public void ResetTtl() => __pbn__Ttl = null; + private int? __pbn__Ttl; + + [global::ProtoBuf.ProtoMember(18, Name = @"sent")] + public long Sent + { + get => __pbn__Sent.GetValueOrDefault(); + set => __pbn__Sent = value; + } + public bool ShouldSerializeSent() => __pbn__Sent != null; + public void ResetSent() => __pbn__Sent = null; + private long? __pbn__Sent; + + [global::ProtoBuf.ProtoMember(19, Name = @"queued")] + public int Queued + { + get => __pbn__Queued.GetValueOrDefault(); + set => __pbn__Queued = value; + } + public bool ShouldSerializeQueued() => __pbn__Queued != null; + public void ResetQueued() => __pbn__Queued = null; + private int? __pbn__Queued; + + [global::ProtoBuf.ProtoMember(20, Name = @"status")] + public long Status + { + get => __pbn__Status.GetValueOrDefault(); + set => __pbn__Status = value; + } + public bool ShouldSerializeStatus() => __pbn__Status != null; + public void ResetStatus() => __pbn__Status = null; + private long? __pbn__Status; + + [global::ProtoBuf.ProtoMember(21, Name = @"raw_data")] + public byte[] RawData + { + get => __pbn__RawData; + set => __pbn__RawData = value; + } + public bool ShouldSerializeRawData() => __pbn__RawData != null; + public void ResetRawData() => __pbn__RawData = null; + private byte[] __pbn__RawData; + + [global::ProtoBuf.ProtoMember(24, Name = @"immediate_ack")] + public bool ImmediateAck + { + get => __pbn__ImmediateAck.GetValueOrDefault(); + set => __pbn__ImmediateAck = value; + } + public bool ShouldSerializeImmediateAck() => __pbn__ImmediateAck != null; + public void ResetImmediateAck() => __pbn__ImmediateAck = null; + private bool? __pbn__ImmediateAck; + + } + + [global::ProtoBuf.ProtoContract()] + public partial class StreamAck : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + } + + [global::ProtoBuf.ProtoContract()] + public partial class SelectiveAck : global::ProtoBuf.IExtensible + { + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(1, Name = @"id")] + public global::System.Collections.Generic.List Ids { get; } = new global::System.Collections.Generic.List(); + + } + +} + +#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#endregion diff --git a/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/android_checkin.proto b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/android_checkin.proto new file mode 100644 index 0000000..07bacd7 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/android_checkin.proto @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Logging information for Android "checkin" events (automatic, periodic +// requests made by Android devices to the server). + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +package checkin_proto; + +// Build characteristics unique to the Chrome browser, and Chrome OS +message ChromeBuildProto { + enum Platform { + PLATFORM_WIN = 1; + PLATFORM_MAC = 2; + PLATFORM_LINUX = 3; + PLATFORM_CROS = 4; + PLATFORM_IOS = 5; + // Just a placeholder. Likely don't need it due to the presence of the + // Android GCM on phone/tablet devices. + PLATFORM_ANDROID = 6; + } + + enum Channel { + CHANNEL_STABLE = 1; + CHANNEL_BETA = 2; + CHANNEL_DEV = 3; + CHANNEL_CANARY = 4; + CHANNEL_UNKNOWN = 5; // for tip of tree or custom builds + } + + // The platform of the device. + optional Platform platform = 1; + + // The Chrome instance's version. + optional string chrome_version = 2; + + // The Channel (build type) of Chrome. + optional Channel channel = 3; +} + +// Information sent by the device in a "checkin" request. +message AndroidCheckinProto { + // Miliseconds since the Unix epoch of the device's last successful checkin. + optional int64 last_checkin_msec = 2; + + // The current MCC+MNC of the mobile device's current cell. + optional string cell_operator = 6; + + // The MCC+MNC of the SIM card (different from operator if the + // device is roaming, for instance). + optional string sim_operator = 7; + + // The device's current roaming state (reported starting in eclair builds). + // Currently one of "{,not}mobile-{,not}roaming", if it is present at all. + optional string roaming = 8; + + // For devices supporting multiple user profiles (which may be + // supported starting in jellybean), the ordinal number of the + // profile that is checking in. This is 0 for the primary profile + // (which can't be changed without wiping the device), and 1,2,3,... + // for additional profiles (which can be added and deleted freely). + optional int32 user_number = 9; + + // Class of device. Indicates the type of build proto + // (IosBuildProto/ChromeBuildProto/AndroidBuildProto) + // That is included in this proto + optional DeviceType type = 12 [default = DEVICE_ANDROID_OS]; + + // For devices running MCS on Chrome, build-specific characteristics + // of the browser. There are no hardware aspects (except for ChromeOS). + // This will only be populated for Chrome builds/ChromeOS devices + optional checkin_proto.ChromeBuildProto chrome_build = 13; + + // Note: Some of the Android specific optional fields were skipped to limit + // the protobuf definition. + // Next 14 +} + +// enum values correspond to the type of device. +// Used in the AndroidCheckinProto and Device proto. +enum DeviceType { + // Android Device + DEVICE_ANDROID_OS = 1; + + // Apple IOS device + DEVICE_IOS_OS = 2; + + // Chrome browser - Not Chrome OS. No hardware records. + DEVICE_CHROME_BROWSER = 3; + + // Chrome OS + DEVICE_CHROME_OS = 4; +} diff --git a/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/checkin.proto b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/checkin.proto new file mode 100644 index 0000000..b741653 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/checkin.proto @@ -0,0 +1,155 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Request and reply to the "checkin server" devices poll every few hours. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package checkin_proto; + +import "android_checkin.proto"; + +// A concrete name/value pair sent to the device's Gservices database. +message GservicesSetting { + required bytes name = 1; + required bytes value = 2; +} + +// Devices send this every few hours to tell us how they're doing. +message AndroidCheckinRequest { + // IMEI (used by GSM phones) is sent and stored as 15 decimal + // digits; the 15th is a check digit. + optional string imei = 1; // IMEI, reported but not logged. + + // MEID (used by CDMA phones) is sent and stored as 14 hexadecimal + // digits (no check digit). + optional string meid = 10; // MEID, reported but not logged. + + // MAC address (used by non-phone devices). 12 hexadecimal digits; + // no separators (eg "0016E6513AC2", not "00:16:E6:51:3A:C2"). + repeated string mac_addr = 9; // MAC address, reported but not logged. + + // An array parallel to mac_addr, describing the type of interface. + // Currently accepted values: "wifi", "ethernet", "bluetooth". If + // not present, "wifi" is assumed. + repeated string mac_addr_type = 19; + + // Serial number (a manufacturer-defined unique hardware + // identifier). Alphanumeric, case-insensitive. + optional string serial_number = 16; + + // Older CDMA networks use an ESN (8 hex digits) instead of an MEID. + optional string esn = 17; // ESN, reported but not logged + + optional int64 id = 2; // Android device ID, not logged + optional int64 logging_id = 7; // Pseudonymous logging ID for Sawmill + optional string digest = 3; // Digest of device provisioning, not logged. + optional string locale = 6; // Current locale in standard (xx_XX) format + required AndroidCheckinProto checkin = 4; + + // DEPRECATED, see AndroidCheckinProto.requested_group + optional string desired_build = 5; + + // Blob of data from the Market app to be passed to Market API server + optional string market_checkin = 8; + + // SID cookies of any google accounts stored on the phone. Not logged. + repeated string account_cookie = 11; + + // Time zone. Not currently logged. + optional string time_zone = 12; + + // Security token used to validate the checkin request. + // Required for android IDs issued to Froyo+ devices, not for legacy IDs. + optional fixed64 security_token = 13; + + // Version of checkin protocol. + // + // There are currently two versions: + // + // - version field missing: android IDs are assigned based on + // hardware identifiers. unsecured in the sense that you can + // "unregister" someone's phone by sending a registration request + // with their IMEI/MEID/MAC. + // + // - version=2: android IDs are assigned randomly. The device is + // sent a security token that must be included in all future + // checkins for that android id. + // + // - version=3: same as version 2, but the 'fragment' field is + // provided, and the device understands incremental updates to the + // gservices table (ie, only returning the keys whose values have + // changed.) + // + // (version=1 was skipped to avoid confusion with the "missing" + // version field that is effectively version 1.) + optional int32 version = 14; + + // OTA certs accepted by device (base-64 SHA-1 of cert files). Not + // logged. + repeated string ota_cert = 15; + + // Honeycomb and newer devices send configuration data with their checkin. + // optional DeviceConfigurationProto device_configuration = 18; + + // A single CheckinTask on the device may lead to multiple checkin + // requests if there is too much log data to upload in a single + // request. For version 3 and up, this field will be filled in with + // the number of the request, starting with 0. + optional int32 fragment = 20; + + // For devices supporting multiple users, the name of the current + // profile (they all check in independently, just as if they were + // multiple physical devices). This may not be set, even if the + // device is using multiuser. (checkin.user_number should be set to + // the ordinal of the user.) + optional string user_name = 21; + + // For devices supporting multiple user profiles, the serial number + // for the user checking in. Not logged. May not be set, even if + // the device supportes multiuser. checkin.user_number is the + // ordinal of the user (0, 1, 2, ...), which may be reused if users + // are deleted and re-created. user_serial_number is never reused + // (unless the device is wiped). + optional int32 user_serial_number = 22; + + // NEXT TAG: 23 +} + +// The response to the device. +message AndroidCheckinResponse { + required bool stats_ok = 1; // Whether statistics were recorded properly. + optional int64 time_msec = 3; // Time of day from server (Java epoch). + // repeated AndroidIntentProto intent = 2; + + // Provisioning is sent if the request included an obsolete digest. + // + // For version <= 2, 'digest' contains the digest that should be + // sent back to the server on the next checkin, and 'setting' + // contains the entire gservices table (which replaces the entire + // current table on the device). + // + // for version >= 3, 'digest' will be absent. If 'settings_diff' + // is false, then 'setting' contains the entire table, as in version + // 2. If 'settings_diff' is true, then 'delete_setting' contains + // the keys to delete, and 'setting' contains only keys to be added + // or for which the value has changed. All other keys in the + // current table should be left untouched. If 'settings_diff' is + // absent, don't touch the existing gservices table. + // + optional string digest = 4; + optional bool settings_diff = 9; + repeated string delete_setting = 10; + repeated GservicesSetting setting = 5; + + optional bool market_ok = 6; // If Market got the market_checkin data OK. + + optional fixed64 android_id = 7; // From the request, or newly assigned + optional fixed64 security_token = 8; // The associated security token + + optional string version_info = 11; + // NEXT TAG: 12 +} diff --git a/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/mcs.proto b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/mcs.proto new file mode 100644 index 0000000..e9b048f --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/ProtoBuf/mcs.proto @@ -0,0 +1,328 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// MCS protocol for communication between Chrome client and Mobile Connection +// Server . + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package mcs_proto; + +/* + Common fields/comments: + + stream_id: no longer sent by server, each side keeps a counter + last_stream_id_received: sent only if a packet was received since last time + a last_stream was sent + status: new bitmask including the 'idle' as bit 0. + + */ + +/** + TAG: 0 + */ +message HeartbeatPing { + optional int32 stream_id = 1; + optional int32 last_stream_id_received = 2; + optional int64 status = 3; +} + +/** + TAG: 1 + */ +message HeartbeatAck { + optional int32 stream_id = 1; + optional int32 last_stream_id_received = 2; + optional int64 status = 3; +} + +message ErrorInfo { + required int32 code = 1; + optional string message = 2; + optional string type = 3; + optional Extension extension = 4; +} + +// MobileSettings class. +// "u:f", "u:b", "u:s" - multi user devices reporting foreground, background +// and stopped users. +// hbping: heatbeat ping interval +// rmq2v: include explicit stream IDs + +message Setting { + required string name = 1; + required string value = 2; +} + +message HeartbeatStat { + required string ip = 1; + required bool timeout = 2; + required int32 interval_ms = 3; +} + +message HeartbeatConfig { + optional bool upload_stat = 1; + optional string ip = 2; + optional int32 interval_ms = 3; +} + +// ClientEvents are used to inform the server of failed and successful +// connections. +message ClientEvent { + enum Type { + UNKNOWN = 0; + // Count of discarded events if the buffer filled up and was trimmed. + DISCARDED_EVENTS = 1; + // Failed connection event: the connection failed to be established or we + // had a login error. + FAILED_CONNECTION = 2; + // Successful connection event: information about the last successful + // connection, including the time at which it was established. + SUCCESSFUL_CONNECTION = 3; + } + + // Common fields [1-99] + optional Type type = 1; + + // Fields for DISCARDED_EVENTS messages [100-199] + optional uint32 number_discarded_events = 100; + + // Fields for FAILED_CONNECTION and SUCCESSFUL_CONNECTION messages [200-299] + // Network type is a value in net::NetworkChangeNotifier::ConnectionType. + optional int32 network_type = 200; + // Reserved for network_port. + reserved 201; + optional uint64 time_connection_started_ms = 202; + optional uint64 time_connection_ended_ms = 203; + // Error code should be a net::Error value. + optional int32 error_code = 204; + + // Fields for SUCCESSFUL_CONNECTION messages [300-399] + optional uint64 time_connection_established_ms = 300; +} + +/** + TAG: 2 + */ +message LoginRequest { + enum AuthService { + ANDROID_ID = 2; + } + required string id = 1; // Must be present ( proto required ), may be empty + // string. + // mcs.android.com. + required string domain = 2; + // Decimal android ID + required string user = 3; + + required string resource = 4; + + // Secret + required string auth_token = 5; + + // Format is: android-HEX_DEVICE_ID + // The user is the decimal value. + optional string device_id = 6; + + // RMQ1 - no longer used + optional int64 last_rmq_id = 7; + + repeated Setting setting = 8; + //optional int32 compress = 9; + repeated string received_persistent_id = 10; + + // Replaced by "rmq2v" setting + // optional bool include_stream_ids = 11; + + optional bool adaptive_heartbeat = 12; + optional HeartbeatStat heartbeat_stat = 13; + // Must be true. + optional bool use_rmq2 = 14; + optional int64 account_id = 15; + + // ANDROID_ID = 2 + optional AuthService auth_service = 16; + + optional int32 network_type = 17; + optional int64 status = 18; + + // 19, 20, and 21 are not currently populated by Chrome. + reserved 19, 20, 21; + + // Events recorded on the client after the last successful connection. + repeated ClientEvent client_event = 22; +} + +/** + * TAG: 3 + */ +message LoginResponse { + required string id = 1; + // Not used. + optional string jid = 2; + // Null if login was ok. + optional ErrorInfo error = 3; + repeated Setting setting = 4; + optional int32 stream_id = 5; + // Should be "1" + optional int32 last_stream_id_received = 6; + optional HeartbeatConfig heartbeat_config = 7; + // used by the client to synchronize with the server timestamp. + optional int64 server_timestamp = 8; +} + +message StreamErrorStanza { + required string type = 1; + optional string text = 2; +} + +/** + * TAG: 4 + */ +message Close { +} + +message Extension { + // 12: SelectiveAck + // 13: StreamAck + required int32 id = 1; + required bytes data = 2; +} + +/** + * TAG: 7 + * IqRequest must contain a single extension. IqResponse may contain 0 or 1 + * extensions. + */ +message IqStanza { + enum IqType { + GET = 0; + SET = 1; + RESULT = 2; + IQ_ERROR = 3; + } + + optional int64 rmq_id = 1; + required IqType type = 2; + required string id = 3; + optional string from = 4; + optional string to = 5; + optional ErrorInfo error = 6; + + // Only field used in the 38+ protocol (besides common last_stream_id_received, status, rmq_id) + optional Extension extension = 7; + + optional string persistent_id = 8; + optional int32 stream_id = 9; + optional int32 last_stream_id_received = 10; + optional int64 account_id = 11; + optional int64 status = 12; +} + +message AppData { + required string key = 1; + required string value = 2; +} + +/** + * TAG: 8 + */ +message DataMessageStanza { + // Not used. + // optional int64 rmq_id = 1; + + // This is the message ID, set by client, DMP.9 (message_id) + optional string id = 2; + + // Project ID of the sender, DMP.1 + required string from = 3; + + // Part of DMRequest - also the key in DataMessageProto. + optional string to = 4; + + // Package name. DMP.2 + required string category = 5; + + // The collapsed key, DMP.3 + optional string token = 6; + + // User data + GOOGLE. prefixed special entries, DMP.4 + repeated AppData app_data = 7; + + // Not used. + optional bool from_trusted_server = 8; + + // Part of the ACK protocol, returned in DataMessageResponse on server side. + // It's part of the key of DMP. + optional string persistent_id = 9; + + // In-stream ack. Increments on each message sent - a bit redundant + // Not used in DMP/DMR. + optional int32 stream_id = 10; + optional int32 last_stream_id_received = 11; + + // Not used. + // optional string permission = 12; + + // Sent by the device shortly after registration. + optional string reg_id = 13; + + // Not used. + // optional string pkg_signature = 14; + // Not used. + // optional string client_id = 15; + + // serial number of the target user, DMP.8 + // It is the 'serial number' according to user manager. + optional int64 device_user_id = 16; + + // Time to live, in seconds. + optional int32 ttl = 17; + // Timestamp ( according to client ) when message was sent by app, in seconds + optional int64 sent = 18; + + // How long has the message been queued before the flush, in seconds. + // This is needed to account for the time difference between server and + // client: server should adjust 'sent' based on its 'receive' time. + optional int32 queued = 19; + + optional int64 status = 20; + + // Optional field containing the binary payload of the message. + optional bytes raw_data = 21; + + // Not used. + // The maximum delay of the message, in seconds. + // optional int32 max_delay = 22; + + // Not used. + // How long the message was delayed before it was sent, in seconds. + // optional int32 actual_delay = 23; + + // If set the server requests immediate ack. Used for important messages and + // for testing. + optional bool immediate_ack = 24; + + // Not used. + // Enables message receipts from MCS/GCM back to CCS clients + // optional bool delivery_receipt_requested = 25; +} + +/** + Included in IQ with ID 13, sent from client or server after 10 unconfirmed + messages. + */ +message StreamAck { + // No last_streamid_received required. This is included within an IqStanza, + // which includes the last_stream_id_received. +} + +/** + Included in IQ sent after LoginResponse from server with ID 12. +*/ +message SelectiveAck { + repeated string id = 1; +} diff --git a/RustPlusApi/RustPlusApi.Fcm/README.md b/RustPlusApi/RustPlusApi.Fcm/README.md new file mode 100644 index 0000000..c00d795 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/README.md @@ -0,0 +1,54 @@ +# RustPlusApi.Fcm + +This is a C# client for the Rust+ websocket. It allows you to receive notification via FCM. + +## Prerequisites + +- **.NET 8** or later + +## Usage + +First, instantiate the `FcmListener` class with the necessary parameters: + +```csharp +var fcmListener = new FcmListener(credentials, persistentIds); +``` + +Parameters: + +- `credentials`: The `Credentials`* object containing the FCM & GCM credentials + the keys to decrypt the notification. +- `persistentIds`: A list of notification IDs that should be ignored. Default is null. + +\* Go to the next section to see how to create a `Credentials` object. + +Then, connect to the FCM socket: + +```csharp +await fcmListener.ConnectAsync(); +``` + +You can subscribe to events to handle connection, disconnection, errors, and received messages: + +```csharp +fcmListener.Connecting += (sender, e) => { /* handle connecting event */ }; +fcmListener.Connected += (sender, e) => { /* handle connected event */ }; +fcmListener.Disconnected += (sender, e) => { /* handle disconnected event */ }; +fcmListener.ErrorOccurred += (sender, e) => { /* handle error event */ }; +fcmListener.MessageReceived += (sender, e) => { /* handle received message event */ }; +``` + +Remember to dispose the `FcmListener` instance when you're done: + +```csharp +fcmListener.Dispose(); +``` + +## Credentials + +Currenlty, there is not simple way to get the FCM & GCM credentials using .NET. +I've planned to implement a solution but it's not ready yet. + +To use this library, you need to get the FCM & GCM credentials manually. +To do so I recommand you to use [this project](https://github.com/liamcottle/rustplus.js) to get the credentials. + +I'm sorry for the inconvenience but since the API is not fully complete it's the easiest way. \ No newline at end of file diff --git a/RustPlusApi/RustPlusApi.Fcm/RustPlusApi.Fcm.csproj b/RustPlusApi/RustPlusApi.Fcm/RustPlusApi.Fcm.csproj new file mode 100644 index 0000000..9fa298f --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/RustPlusApi.Fcm.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + RustPlusApi.Fcm + 1.0.2 + HandyS11 + HandyS11 + A Rust+ API websocket made in C#. + This is a C# client for the Rust+ websocket. It allows you to receive notification via FCM. + rust rustplus rustplusapi + MIT + https://github.com/HandyS11/RustPlusApi/blob/main/LICENSE + true + https://github.com/HandyS11/RustPlusApi + git + README.md + + + + + + + + + + + + + diff --git a/RustPlusApi/RustPlusApi.Fcm/Tools/FcmTools.cs b/RustPlusApi/RustPlusApi.Fcm/Tools/FcmTools.cs new file mode 100644 index 0000000..cbee472 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Tools/FcmTools.cs @@ -0,0 +1,51 @@ +using System.Security.Cryptography; +using System.Text.Json; + +using RustPlusApi.Fcm.Data; + +namespace RustPlusApi.Fcm.Tools +{ + internal static class FcmTools + { + private const string FcmSubscribeUrl = "https://fcm.googleapis.com/fcm/connect/subscribe"; + private const string FcmEndpoint = "https://fcm.googleapis.com/fcm/send"; + + private static readonly HttpClient HttpClient = new(); + + internal static async Task> RegisterFcmAsync(string senderId, string token) + { + var keys = CreateKeys(); + var response = await HttpClient.PostAsync(FcmSubscribeUrl, new FormUrlEncodedContent(new Dictionary + { + { "authorized_entity", senderId }, + { "endpoint", $"{FcmEndpoint}/{token}" }, + { "encryption_key", keys.PublicKey }, + { "encryption_auth", keys.AuthSecret }, + })); + + var responseText = await response.Content.ReadAsStringAsync(); + var fcmResponse = JsonSerializer.Deserialize(responseText); + + return new Tuple(keys, fcmResponse!); + } + + private static Keys CreateKeys() + { + using var dh = ECDiffieHellman.Create(); + var privateKey = Convert.ToBase64String(dh.ExportECPrivateKey()); + var publicKey = Convert.ToBase64String(dh.PublicKey.ExportSubjectPublicKeyInfo()); + + var authSecretBytes = new byte[16]; + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(authSecretBytes); + var authSecret = Convert.ToBase64String(authSecretBytes); + + return new Keys + { + PrivateKey = privateKey, + PublicKey = publicKey, + AuthSecret = authSecret + }; + } + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Tools/GcmTools.cs b/RustPlusApi/RustPlusApi.Fcm/Tools/GcmTools.cs new file mode 100644 index 0000000..048d5cd --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Tools/GcmTools.cs @@ -0,0 +1,137 @@ +using System.Diagnostics; +using System.Net.Http.Headers; + +using AndroidCheckinProto; + +using CheckinProto; + +using ProtoBuf; + +using RustPlusApi.Fcm.Data; + +namespace RustPlusApi.Fcm.Tools +{ + internal static class GcmTools + { + private static readonly HttpClient HttpClient = new(); + + private const string CheckInUrl = "https://android.clients.google.com/checkin"; + private const string RegisterUrl = "https://android.clients.google.com/c2dm/register3"; + + private static readonly string ServerKey = Convert.ToBase64String(Constants.ServerKey); + + public static async Task RegisterAsync(string appId) + { + var options = await CheckInAsync(); + var credentials = await DoRegisterAsync(options, appId); + return credentials; + } + + internal static async Task CheckInAsync(ulong? androidId = null, ulong? securityToken = null) + { + try + { + var id = (androidId != null) ? (long)androidId : (long?)null; + var requestBody = GetCheckInRequest(id, securityToken); + + var request = new HttpRequestMessage(HttpMethod.Post, CheckInUrl); + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, requestBody); + + var content = new ByteArrayContent(ms.ToArray()); + content.Headers.ContentType = new MediaTypeHeaderValue("application/x-protobuf"); + + request.Content = content; + + var response = await HttpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var data = await response.Content.ReadAsByteArrayAsync(); + + using var stream = new MemoryStream(data); + var message = Serializer.Deserialize(stream); + + return message; + } + catch (Exception ex) + { + throw new ApplicationException("Error during check-in request.", ex); + } + } + + + private static AndroidCheckinRequest GetCheckInRequest(long? androidId = null, ulong? securityToken = null) + { + return new AndroidCheckinRequest + { + UserSerialNumber = 0, + Checkin = new AndroidCheckinProto.AndroidCheckinProto + { + Type = DeviceType.DeviceChromeBrowser, + ChromeBuild = new ChromeBuildProto + { + platform = ChromeBuildProto.Platform.PlatformMac, + ChromeVersion = "63.0.3234.0", + channel = ChromeBuildProto.Channel.ChannelStable + } + }, + Version = 3, + Id = androidId ?? default, + SecurityToken = securityToken ?? default + }; + } + + private static async Task DoRegisterAsync(AndroidCheckinResponse option, string appId) + { + var body = new Dictionary + { + { "app", "org.chromium.linux" }, + { "X-subtype", appId }, + { "device", option.AndroidId.ToString() }, + { "sender", ServerKey } + }; + + var response = await PostRegisterAsync(option.AndroidId, option.SecurityToken, body); + var token = response.Split('=')[1]; + + return new GcmCredentials + { + Token = token, + AndroidId = option.AndroidId, + SecurityToken = option.SecurityToken, + AppId = appId + }; + } + + private static async Task PostRegisterAsync(ulong androidId, ulong securityToken, Dictionary body, int retry = 0) + { + while (true) + { + var request = new HttpRequestMessage(HttpMethod.Post, RegisterUrl) + { + Headers = + { + { "Authorization", $"AidLogin {androidId}:{securityToken}" }, + { "Content-Type", "application/x-www-form-urlencoded" } + }, + Content = new FormUrlEncodedContent(body) + }; + + var response = await HttpClient.SendAsync(request); + var responseText = await response.Content.ReadAsStringAsync(); + + if (!responseText.Contains("Error")) return responseText; + Debug.WriteLine($"Register request has failed with {responseText}"); + if (retry >= 5) + { + throw new Exception("GCM register has failed"); + } + + Debug.WriteLine($"Retry... {retry + 1}"); + await Task.Delay(1000); + retry++; + } + } + } +} diff --git a/RustPlusApi/RustPlusApi.Fcm/Utils/DecryptionUtility.cs b/RustPlusApi/RustPlusApi.Fcm/Utils/DecryptionUtility.cs new file mode 100644 index 0000000..ac66c7b --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Utils/DecryptionUtility.cs @@ -0,0 +1,391 @@ +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; + +using McsProto; + +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +using RustPlusApi.Fcm.Data; + +using ECCurve = Org.BouncyCastle.Math.EC.ECCurve; + +namespace RustPlusApi.Fcm.Utils +{ + internal class DecryptionUtility + { + private const int SHA_256_LENGTH = 32; + private const int KEY_LENGTH = 16; + private const int NONCE_LENGTH = 12; + private const int HEADER_RS = 4096; + private const int TAG_LENGTH = 16; + private const int CHUNK_SIZE = HEADER_RS + TAG_LENGTH; + private const int PADSIZE = 2; + + private static readonly ECDomainParameters ecDomainParameters; + private static readonly ECKeyGenerationParameters eckgparameters; + private static readonly ECCurve ecCurve; + private static readonly ECDomainParameters ecSpec; + private static readonly X9ECParameters ecParams = NistNamedCurves.GetByName("P-256"); + private static readonly SecureRandom secureRandom = new(); + + private static readonly byte[] keyInfoParameter = Encoding.ASCII.GetBytes("Content-Encoding: aesgcm\0"); + private static readonly byte[] _nonceInfoParameter = Encoding.ASCII.GetBytes("Content-Encoding: nonce\0"); + private static readonly byte[] _authInfoParameter = Encoding.ASCII.GetBytes("Content-Encoding: auth\0"); + private static readonly byte[] _keyLabel = Encoding.ASCII.GetBytes("P-256"); + + private readonly ECPrivateKeyParameters _privateKey; + private readonly ECPublicKeyParameters _publicKey; + + private byte[] AuthSecret { get; } + private byte[] PublicKey { get; } + + static DecryptionUtility() + { + ecCurve = ecParams.Curve; + ecSpec = new ECDomainParameters(ecCurve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed()); + + eckgparameters = new ECKeyGenerationParameters(ecSpec, secureRandom); + ecDomainParameters = eckgparameters.DomainParameters; + } + + public DecryptionUtility() + { + (_privateKey, _publicKey) = GenerateKeys(); + PublicKey = _publicKey.Q.GetEncoded(); + + AuthSecret = new byte[16]; + secureRandom.NextBytes(AuthSecret); + } + + public static string Decrypt(DataMessageStanza dataMessage, Keys keys) + { + var decryptor = new DecryptionUtility(DecodeBase64(keys.PublicKey), DecodeBase64(keys.PrivateKey), DecodeBase64(keys.AuthSecret)); + + var cryptoKey = dataMessage.AppDatas.FirstOrDefault(item => item.Key == "crypto-key") + ?? throw new Exception("crypto-key is missing"); + + var salt = dataMessage.AppDatas.FirstOrDefault(item => item.Key == "encryption") + ?? throw new Exception("salt is missing"); + + var decryptedBytes = decryptor.Decrypt(dataMessage.RawData, DecodeBase64(cryptoKey.Value[3..]), DecodeBase64(salt.Value[5..])); + + return Encoding.UTF8.GetString(decryptedBytes); + } + + private byte[] Decrypt(byte[] buffer, byte[] senderPublicKeyBytes, byte[] salt) + { + var ecP = NistNamedCurves.GetByName("P-256"); + var eCDomainParameters = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N); + + var pt = ecP.Curve.DecodePoint(senderPublicKeyBytes); + var senderPublicKey = new ECPublicKeyParameters(pt, eCDomainParameters); + + var (key, nonce) = DeriveKeyAndNonce(salt, AuthSecret, senderPublicKey, _publicKey, _privateKey); + + var result = Array.Empty(); + var start = 0; + + // TODO: this is not tested with more than one iteration + for (uint i = 0; start < buffer.Length; ++i) + { + var end = start + CHUNK_SIZE; + if (end == buffer.Length) throw new InvalidOperationException("Truncated payload"); + + end = Math.Min(end, buffer.Length); + + if (end - start <= TAG_LENGTH) throw new InvalidOperationException("Invalid block: too small at " + i); + + var block = DecryptRecord(key, nonce, i, ByteArray.Slice(buffer, start, end), end >= buffer.Length); + result = ByteArray.Concat(result, block); + start = end; + } + return result; + } + + private DecryptionUtility(byte[] publicKey, byte[] privateKey, byte[] authSecret) + { + var pt = ecCurve.DecodePoint(publicKey); + _publicKey = new ECPublicKeyParameters(pt, ecDomainParameters); + _privateKey = new ECPrivateKeyParameters(new BigInteger(1, privateKey), ecDomainParameters); + + AuthSecret = authSecret; + PublicKey = _publicKey.Q.GetEncoded(); + } + + private static byte[] AddLengthPrefix(byte[] buffer) + { + var newBuffer = new byte[buffer.Length + 2]; + Array.Copy(buffer, 0, newBuffer, 2, buffer.Length); + + var intBytes = BitConverter.GetBytes((short)buffer.Length); + + if (BitConverter.IsLittleEndian) Array.Reverse(intBytes); + + Debug.Assert(intBytes.Length <= 2); + Array.Copy(intBytes, 0, newBuffer, 0, intBytes.Length); + + return newBuffer; + } + + private static byte[] RemovePad(byte[] buffer) + { + var pad = (int)ByteArray.ReadUInt64(buffer, 0, PADSIZE); + + if (pad + PADSIZE > buffer.Length) throw new InvalidOperationException("padding exceeds block size"); + + return ByteArray.Slice(buffer, pad + PADSIZE, buffer.Length); + } + + private static byte[] DecryptRecord(byte[] key, byte[] nonce, uint counter, byte[] buffer, bool last) + { + nonce = GenerateNonce(nonce, counter); + + var blockCipher = new GcmBlockCipher(new AesEngine()); + + blockCipher.Init(false, new AeadParameters(new KeyParameter(key), 128, nonce)); + + var decryptedMessage = new byte[blockCipher.GetOutputSize(buffer.Length)]; + + var decryptedMessageLength = blockCipher.ProcessBytes(buffer, 0, buffer.Length, decryptedMessage, 0); + + decryptedMessageLength += blockCipher.DoFinal(decryptedMessage, decryptedMessageLength); + + return RemovePad(decryptedMessage); + } + + private static (byte[], byte[]) DeriveKeyAndNonce(byte[] salt, byte[] authSecret, ECPublicKeyParameters senderPublicKey, + ECPublicKeyParameters receiverPublicKey, ECPrivateKeyParameters receiverPrivateKey) + { + var (secret, context) = ExtractSecretAndContext(senderPublicKey, receiverPublicKey, receiverPrivateKey); + secret = HKDF.GetBytes(authSecret, secret, _authInfoParameter, SHA_256_LENGTH); + + var keyInfo = ByteArray.Concat(keyInfoParameter, context); + var nonceInfo = ByteArray.Concat(_nonceInfoParameter, context); + + var prk = HKDF.Extract(salt, secret); + + return (HKDF.Expand(prk, keyInfo, KEY_LENGTH), HKDF.Expand(prk, nonceInfo, NONCE_LENGTH)); + } + + private static (byte[], byte[]) ExtractSecretAndContext(ECPublicKeyParameters senderPublicKey, + ECPublicKeyParameters receiverPublicKey, ECPrivateKeyParameters receiverPrivateKey) + { + IBasicAgreement aKeyAgree = new ECDHBasicAgreement(); + + aKeyAgree.Init(receiverPrivateKey); + var sharedSecret = aKeyAgree.CalculateAgreement(senderPublicKey).ToByteArrayUnsigned(); + + var receiverKeyBytes = AddLengthPrefix(receiverPublicKey.Q.GetEncoded()); + var senderPublicKeyBytes = AddLengthPrefix(senderPublicKey.Q.GetEncoded()); + + var context = new byte[_keyLabel.Length + 1 + receiverKeyBytes.Length + senderPublicKeyBytes.Length]; + + var destinationOffset = 0; + Array.Copy(_keyLabel, 0, context, destinationOffset, _keyLabel.Length); + destinationOffset += _keyLabel.Length + 1; + Array.Copy(receiverKeyBytes, 0, context, destinationOffset, receiverKeyBytes.Length); + destinationOffset += receiverKeyBytes.Length; + Array.Copy(senderPublicKeyBytes, 0, context, destinationOffset, senderPublicKeyBytes.Length); + + return (sharedSecret, context); + } + + private static byte[] GenerateNonce(byte[] buffer, uint counter) + { + var nonce = new byte[buffer.Length]; + Buffer.BlockCopy(buffer, 0, nonce, 0, buffer.Length); + var m = ByteArray.ReadUInt64(nonce, nonce.Length - 6, 6); + //var x = ((m ^ counter) & 0xffffff) + (((m / 0x1000000) ^ (counter / 0x1000000)) & 0xffffff) * 0x1000000; + ByteArray.WriteUInt64(nonce, m, nonce.Length - 6, 6); + + return nonce; + } + + private static (ECPrivateKeyParameters, ECPublicKeyParameters) GenerateKeys() + { + var gen = new ECKeyPairGenerator("ECDH"); + gen.Init(eckgparameters); + var eckp = gen.GenerateKeyPair(); + + var ecPub = (ECPublicKeyParameters)eckp.Public; + var ecPri = (ECPrivateKeyParameters)eckp.Private; + + return (ecPri, ecPub); + } + + private static byte[] DecodeBase64(string base64) + { + base64 = base64.Replace('-', '+').Replace('_', '/'); + + while (base64.Length % 4 != 0) base64 += "="; + + return Convert.FromBase64String(base64); + } + + private static class ByteArray + { + // TODO: optimize this method to use pointers or bit shifts + // check if it is working with big endian + internal static ulong ReadUInt64(byte[] bytes, int startIndex, int length) + { + var buffer = new byte[8]; + Buffer.BlockCopy(bytes, startIndex, buffer, 8 - length, length); + + if (BitConverter.IsLittleEndian) Array.Reverse(buffer); + + return BitConverter.ToUInt64(buffer, 0); + } + + // TODO: check if it is working with big endian + internal static void WriteUInt64(byte[] source, ulong value, int startIndex, int length) + { + var buffer = BitConverter.GetBytes(value); + Buffer.BlockCopy(source, startIndex, buffer, 0, length); + } + + internal static byte[] Slice(byte[] source, int startIndex, int endIndex) + { + Debug.Assert(startIndex < endIndex); + + var length = endIndex - startIndex; + var result = new byte[length]; + Buffer.BlockCopy(source, startIndex, result, 0, length); + return result; + } + + internal static byte[] Concat(byte[] first, byte[] second) + { + var ret = new byte[first.Length + second.Length]; + Buffer.BlockCopy(first, 0, ret, 0, first.Length); + Buffer.BlockCopy(second, 0, ret, first.Length, second.Length); + return ret; + } + } + + private class HKDF + { + /// + /// Returns a 32 byte psuedorandom number that can be used with the Expand method if + /// a cryptographically secure pseudorandom number is not already available. + /// + /// + /// (Optional, but you should use it) Non-secret random value. + /// If less than 64 bytes it is padded with zeros. Can be reused but output is + /// stronger if not reused. (And of course output is much stronger with salt than + /// without it) + /// + /// + /// Material that is not necessarily random that + /// will be used with the HMACSHA256 hash function and the salt to produce + /// a 32 byte psuedorandom number. + /// + /// + internal static byte[] Extract(byte[] salt, byte[] inputKeyMaterial) + { + //For algorithm docs, see section 2.2: https://tools.ietf.org/html/rfc5869 + + using var hmac = new HMACSHA256(salt); + return hmac.ComputeHash(inputKeyMaterial, 0, inputKeyMaterial.Length); + } + + /// + /// Returns a secure pseudorandom key of the desired length. Useful as a key derivation + /// function to derive one cryptograpically secure pseudorandom key from another + /// cryptograpically secure pseudorandom key. This can be useful, for example, + /// when needing to create a subKey from a master key. + /// + /// + /// A cryptograpically secure pseudorandom number. Can be obtained + /// via the Extract method or elsewhere. Must be 32 bytes or greater. 64 bytes is + /// the prefered size. Shorter keys are padded to 64 bytes, longer ones are hashed + /// to 64 bytes. + /// + /// + /// (Optional) Context and application specific information. + /// Allows the output to be bound to application context related information. + /// + /// Length of output in bytes. + /// + internal static byte[] Expand(byte[] key, byte[] info, int length) + { + //For algorithm docs, see section 2.3: https://tools.ietf.org/html/rfc5869 + //Also note: + // SHA256 has a block size of 64 bytes + // SHA256 has an output length of 32 bytes (but can be truncated to less) + const int hashLength = 32; + + //Min recommended length for Key is the size of the hash output (32 bytes in this case) + //See section 2: https://tools.ietf.org/html/rfc2104#section-3 + //Also see: http://security.stackexchange.com/questions/95972/what-are-requirements-for-hmac-secret-key + if (key == null || key.Length < 32) + throw new ArgumentOutOfRangeException("Key should be 32 bytes or greater."); + + ArgumentOutOfRangeException.ThrowIfGreaterThan(length, 255 * hashLength); + + var outputIndex = 0; + byte[] buffer; + var hash = Array.Empty(); + var output = new byte[length]; + var count = 1; + int bytesToCopy; + + using (var hmac = new HMACSHA256(key)) + { + while (outputIndex < length) + { + //Setup buffer to hash + buffer = new byte[hash.Length + info.Length + 1]; + Buffer.BlockCopy(hash, 0, buffer, 0, hash.Length); + Buffer.BlockCopy(info, 0, buffer, hash.Length, info.Length); + buffer[^1] = (byte)count++; + + //Hash the buffer and return a 32 byte hash + hash = hmac.ComputeHash(buffer, 0, buffer.Length); + + //Copy as much of the hash as we need to the final output + bytesToCopy = Math.Min(length - outputIndex, hash.Length); + Buffer.BlockCopy(hash, 0, output, outputIndex, bytesToCopy); + outputIndex += bytesToCopy; + } + } + return output; + } + + /// + /// Generates a psuedorandom number of the length specified. This number is suitable + /// for use as an encryption key, HMAC validation key or other uses of a cryptographically + /// secure psuedorandom number. + /// + /// + /// non-secret random value. If less than 64 bytes it is + /// padded with zeros. Can be reused but output is stronger if not reused. + /// + /// + /// Material that is not necessarily random that + /// will be used with the HMACSHA256 hash function and the salt to produce + /// a 32 byte psuedorandom number. + /// + /// + /// (Optional) context and application specific information. + /// Allows the output to be bound to application context related information. Pass 0 length + /// byte array to omit. + /// + /// Length of output in bytes. + internal static byte[] GetBytes(byte[] salt, byte[] inputKeyMaterial, byte[] info, int length) + { + var key = Extract(salt, inputKeyMaterial); + return Expand(key, info, length); + } + } + } +} \ No newline at end of file diff --git a/RustPlusApi/RustPlusApi.Fcm/Utils/RawMessageParser.cs b/RustPlusApi/RustPlusApi.Fcm/Utils/RawMessageParser.cs new file mode 100644 index 0000000..cd7b541 --- /dev/null +++ b/RustPlusApi/RustPlusApi.Fcm/Utils/RawMessageParser.cs @@ -0,0 +1,98 @@ +using System.Diagnostics; + +using McsProto; + +using ProtoBuf; + +using RustPlusApi.Fcm.Data.Events; + +using static RustPlusApi.Fcm.Data.Constants; + +namespace RustPlusApi.Fcm.Utils +{ + internal class RawMessageParser() + { + internal event EventHandler? ErrorOccurred; + internal event EventHandler? MessageReceived; + + private byte[] _data = []; + private bool _handshakeComplete; + + internal void OnData(byte[] buffer, Type type) + { + _data = buffer; + OnGotMessageBytes(type); + } + + internal void OnGotLoginResponse() + { + _handshakeComplete = true; + } + + private void OnGotMessageBytes(Type type) + { + try + { + var messageTag = GetTagFromProtobufType(type); + + if (_data.Length == 0) + { + MessageReceived?.Invoke(this, new MessageEventArgs { Tag = messageTag, Object = Activator.CreateInstance(type) }); + return; + } + + var buffer = _data.Take(_data.Length).ToArray(); + _data = _data.Skip(_data.Length).ToArray(); + + using var stream = new MemoryStream(buffer); + var message = Serializer.NonGeneric.Deserialize(type, stream); + + MessageReceived?.Invoke(this, new MessageEventArgs { Tag = messageTag, Object = message }); + + if (messageTag == McsProtoTag.KLoginResponseTag) + { + if (_handshakeComplete) Debug.WriteLine("Unexpected login response"); + else + { + _handshakeComplete = true; + Debug.WriteLine("GCM Handshake complete."); + } + } + } + catch (Exception ex) + { + ErrorOccurred?.Invoke(this, ex); + } + } + + internal static McsProtoTag GetTagFromProtobufType(Type type) + { + if (type == typeof(HeartbeatPing)) return McsProtoTag.KHeartbeatPingTag; + else if (type == typeof(HeartbeatAck)) return McsProtoTag.KHeartbeatAckTag; + else if (type == typeof(LoginRequest)) return McsProtoTag.KLoginRequestTag; + else if (type == typeof(LoginResponse)) return McsProtoTag.KLoginResponseTag; + else if (type == typeof(Close)) return McsProtoTag.KCloseTag; + else if (type == typeof(IqStanza)) return McsProtoTag.KIqStanzaTag; + else if (type == typeof(DataMessageStanza)) return McsProtoTag.KDataMessageStanzaTag; + else if (type == typeof(StreamErrorStanza)) return McsProtoTag.KStreamErrorStanzaTag; + else throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + + internal static Type BuildProtobufFromTag(McsProtoTag tag) + { + // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault + return tag switch + { + McsProtoTag.KHeartbeatPingTag => typeof(HeartbeatPing), + McsProtoTag.KHeartbeatAckTag => typeof(HeartbeatAck), + McsProtoTag.KLoginRequestTag => typeof(LoginRequest), + McsProtoTag.KLoginResponseTag => typeof(LoginResponse), + McsProtoTag.KCloseTag => typeof(Close), + McsProtoTag.KIqStanzaTag => typeof(IqStanza), + McsProtoTag.KDataMessageStanzaTag => typeof(DataMessageStanza), + McsProtoTag.KStreamErrorStanzaTag => typeof(StreamErrorStanza), + _ => throw new ArgumentOutOfRangeException(nameof(tag), tag, null) + }; + } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/AlarmInfo.cs b/RustPlusApi/RustPlusApi/Data/AlarmInfo.cs new file mode 100644 index 0000000..0b02ee9 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/AlarmInfo.cs @@ -0,0 +1,7 @@ +namespace RustPlusApi.Data +{ + public class AlarmInfo + { + public bool Value { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/Events/SmartSwitchEventArg.cs b/RustPlusApi/RustPlusApi/Data/Events/SmartSwitchEventArg.cs new file mode 100644 index 0000000..4934c5c --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/Events/SmartSwitchEventArg.cs @@ -0,0 +1,7 @@ +namespace RustPlusApi.Data.Events +{ + public class SmartSwitchEventArg : SmartSwitchInfo + { + public uint Id { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/Events/StorageMonitorEventArg.cs b/RustPlusApi/RustPlusApi/Data/Events/StorageMonitorEventArg.cs new file mode 100644 index 0000000..10e47c8 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/Events/StorageMonitorEventArg.cs @@ -0,0 +1,7 @@ +namespace RustPlusApi.Data.Events +{ + public class StorageMonitorEventArg : StorageMonitorInfo + { + public uint Id { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/Response.cs b/RustPlusApi/RustPlusApi/Data/Response.cs new file mode 100644 index 0000000..509a341 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/Response.cs @@ -0,0 +1,14 @@ +namespace RustPlusApi.Data +{ + public class Response + { + public bool IsSuccess { get; set; } + public Error? Error { get; set; } + public T? Data { get; set; } + } + + public class Error + { + public string? Message { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/ServerInfo.cs b/RustPlusApi/RustPlusApi/Data/ServerInfo.cs new file mode 100644 index 0000000..3df4484 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/ServerInfo.cs @@ -0,0 +1,20 @@ +namespace RustPlusApi.Data +{ + public class ServerInfo + { + public string? Name { get; set; } + public string? HeaderImage { get; set; } + public string? Url { get; set; } + public string? Map { get; set; } + public uint? MapSize { get; set; } + public DateTime? WipeTime { get; set; } + public uint? PlayerCount { get; set; } + public uint? MaxPlayerCount { get; set; } + public uint? QueuedPlayerCount { get; set; } + public uint? Seed { get; set; } + public uint? Salt { get; set; } + public string? LogoImage { get; set; } + public string? Nexus { get; set; } + public string? NexusZone { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/ServerMap.cs b/RustPlusApi/RustPlusApi/Data/ServerMap.cs new file mode 100644 index 0000000..d146614 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/ServerMap.cs @@ -0,0 +1,14 @@ +using System.Drawing; + +namespace RustPlusApi.Data +{ + public class ServerMap + { + public uint? Height { get; set; } + public uint? Width { get; set; } + public int? OceanMargin { get; set; } + public Color Background { get; set; } + public List? Monuments { get; set; } + public byte[]? JpgImage { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/ServerMapMonument.cs b/RustPlusApi/RustPlusApi/Data/ServerMapMonument.cs new file mode 100644 index 0000000..bdfac16 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/ServerMapMonument.cs @@ -0,0 +1,9 @@ +namespace RustPlusApi.Data +{ + public class ServerMapMonument + { + public string? Token { get; set; } + public float? X { get; set; } + public float? Y { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/SmartSwitchInfo.cs b/RustPlusApi/RustPlusApi/Data/SmartSwitchInfo.cs new file mode 100644 index 0000000..f7b9385 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/SmartSwitchInfo.cs @@ -0,0 +1,7 @@ +namespace RustPlusApi.Data +{ + public class SmartSwitchInfo + { + public bool Value { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/StorageMonitorInfo.cs b/RustPlusApi/RustPlusApi/Data/StorageMonitorInfo.cs new file mode 100644 index 0000000..78331b4 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/StorageMonitorInfo.cs @@ -0,0 +1,10 @@ +namespace RustPlusApi.Data +{ + public class StorageMonitorInfo + { + public int? Capacity { get; set; } + public bool? HasProtection { get; set; } + public uint? ProtectionExpiry { get; set; } + public List? Items { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Data/StorageMonitorItemInfo.cs b/RustPlusApi/RustPlusApi/Data/StorageMonitorItemInfo.cs new file mode 100644 index 0000000..6a3a75c --- /dev/null +++ b/RustPlusApi/RustPlusApi/Data/StorageMonitorItemInfo.cs @@ -0,0 +1,9 @@ +namespace RustPlusApi.Data +{ + public class StorageMonitorItemInfo + { + public int Id { get; set; } + public int? Quantity { get; set; } + public bool? IsItemBlueprint { get; set; } + } +} diff --git a/RustPlusApi/RustPlusApi/Extensions/AppEntityInfoToModel.cs b/RustPlusApi/RustPlusApi/Extensions/AppEntityInfoToModel.cs new file mode 100644 index 0000000..5d684c4 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Extensions/AppEntityInfoToModel.cs @@ -0,0 +1,63 @@ +using RustPlusApi.Data; + +using RustPlusContracts; +// ReSharper disable MemberCanBePrivate.Global + +namespace RustPlusApi.Extensions +{ + public static class AppEntityInfoToModel + { + public static object ToEntityInfo(this AppEntityInfo entity) + { + return entity.Type switch + { + AppEntityType.Switch => entity.ToSmartSwitchInfo(), + AppEntityType.Alarm => entity.ToAlarmInfo(), + AppEntityType.StorageMonitor => entity.ToStorageMonitorInfo(), + _ => throw new ArgumentException($"The given type is not possible: {entity.Type}") + }; + } + + public static SmartSwitchInfo ToSmartSwitchInfo(this AppEntityInfo entity) + { + return new SmartSwitchInfo + { + Value = entity.Payload.Value + }; + } + + public static AlarmInfo ToAlarmInfo(this AppEntityInfo entity) + { + return new AlarmInfo + { + Value = entity.Payload.Value + }; + } + + public static StorageMonitorInfo ToStorageMonitorInfo(this AppEntityInfo entity) + { + return new StorageMonitorInfo + { + Capacity = entity.Payload.Capacity, + HasProtection = entity.Payload.HasProtection, + ProtectionExpiry = entity.Payload.ProtectionExpiry, + Items = entity.Payload.Items.ToStorageMonitorItemsInfo().ToList() + }; + } + + public static StorageMonitorItemInfo ToStorageMonitorItemInfo(this AppEntityPayload.Types.Item item) + { + return new StorageMonitorItemInfo + { + Id = item.ItemId, + Quantity = item.Quantity, + IsItemBlueprint = item.ItemIsBlueprint, + }; + } + + public static IEnumerable ToStorageMonitorItemsInfo(this IEnumerable items) + { + return items.Select(ToStorageMonitorItemInfo); + } + } +} diff --git a/RustPlusApi/RustPlusApi/Extensions/AppInfoToModel.cs b/RustPlusApi/RustPlusApi/Extensions/AppInfoToModel.cs new file mode 100644 index 0000000..6dcac88 --- /dev/null +++ b/RustPlusApi/RustPlusApi/Extensions/AppInfoToModel.cs @@ -0,0 +1,30 @@ +using RustPlusApi.Data; + +using RustPlusContracts; + +namespace RustPlusApi.Extensions +{ + public static class AppInfoToModel + { + public static ServerInfo ToServerInfo(this AppInfo appInfo) + { + return new ServerInfo + { + Name = appInfo.Name, + HeaderImage = appInfo.HeaderImage, + Url = appInfo.Url, + Map = appInfo.Map, + MapSize = appInfo.MapSize, + WipeTime = DateTimeOffset.FromUnixTimeSeconds(appInfo.WipeTime).UtcDateTime, + PlayerCount = appInfo.Players, + MaxPlayerCount = appInfo.MaxPlayers, + QueuedPlayerCount = appInfo.QueuedPlayers, + Seed = appInfo.Seed, + Salt = appInfo.Salt, + LogoImage = appInfo.LogoImage, + Nexus = appInfo.Nexus, + NexusZone = appInfo.NexusZone + }; + } + } +} diff --git a/RustPlusApi/RustPlusApi/Extensions/AppMapToModel.cs b/RustPlusApi/RustPlusApi/Extensions/AppMapToModel.cs new file mode 100644 index 0000000..d6ec42e --- /dev/null +++ b/RustPlusApi/RustPlusApi/Extensions/AppMapToModel.cs @@ -0,0 +1,41 @@ +using System.Drawing; + +using RustPlusApi.Data; + +using RustPlusContracts; + +using static RustPlusContracts.AppMap.Types; + +namespace RustPlusApi.Extensions +{ + public static class AppMapToModel + { + public static ServerMap ToServerMap(this AppMap appMap) + { + return new ServerMap + { + Height = appMap.Height, + Width = appMap.Width, + OceanMargin = appMap.OceanMargin, + Background = ColorTranslator.FromHtml(appMap.Background), + Monuments = appMap.Monuments.ToServerMapMonuments().ToList(), + JpgImage = appMap.JpgImage.ToByteArray() + }; + } + + public static ServerMapMonument ToServerMapMonument(this Monument appMapMonument) + { + return new ServerMapMonument + { + Token = appMapMonument.Token, + X = appMapMonument.X, + Y = appMapMonument.Y + }; + } + + public static IEnumerable ToServerMapMonuments(this IEnumerable appMapMonuments) + { + return appMapMonuments.Select(ToServerMapMonument); + } + } +} diff --git a/RustPlusApi/RustPlusApi/Extensions/EntityChangedToModel.cs b/RustPlusApi/RustPlusApi/Extensions/EntityChangedToModel.cs new file mode 100644 index 0000000..e0d505d --- /dev/null +++ b/RustPlusApi/RustPlusApi/Extensions/EntityChangedToModel.cs @@ -0,0 +1,30 @@ +using RustPlusApi.Data.Events; + +using RustPlusContracts; + +namespace RustPlusApi.Extensions +{ + public static class EntityChangedToModel + { + public static SmartSwitchEventArg ToSmartSwitchEvent(this AppEntityChanged entityChanged) + { + return new SmartSwitchEventArg + { + Id = entityChanged.EntityId, + Value = entityChanged.Payload.Value + }; + } + + public static StorageMonitorEventArg ToStorageMonitorEvent(this AppEntityChanged entityChanged) + { + return new StorageMonitorEventArg + { + Id = entityChanged.EntityId, + Capacity = entityChanged.Payload.Capacity, + HasProtection = entityChanged.Payload.HasProtection, + ProtectionExpiry = entityChanged.Payload.ProtectionExpiry, + Items = entityChanged.Payload.Items.ToStorageMonitorItemsInfo().ToList() + }; + } + } +} diff --git a/RustPlusApi/RustPlusApi/RustPlusContracts.cs b/RustPlusApi/RustPlusApi/Protobuf/RustPlusContracts.cs similarity index 100% rename from RustPlusApi/RustPlusApi/RustPlusContracts.cs rename to RustPlusApi/RustPlusApi/Protobuf/RustPlusContracts.cs diff --git a/RustPlusContracts.proto b/RustPlusApi/RustPlusApi/Protobuf/RustPlusContracts.proto similarity index 100% rename from RustPlusContracts.proto rename to RustPlusApi/RustPlusApi/Protobuf/RustPlusContracts.proto diff --git a/RustPlusApi/RustPlusApi/README.md b/RustPlusApi/RustPlusApi/README.md index 3fd6263..68222a0 100644 --- a/RustPlusApi/RustPlusApi/README.md +++ b/RustPlusApi/RustPlusApi/README.md @@ -38,4 +38,8 @@ rustPlusApi.ErrorOccurred += (sender, e) => { /* handle error event */ }; rustPlusApi.MessageReceived += (sender, e) => { /* handle received message event */ }; ``` -Remember to dispose the `RustPlus` instance when you're done: \ No newline at end of file +Remember to dispose the `RustPlus` instance when you're done: + +```csharp +rustPlusApi.Dispose(); +``` \ No newline at end of file diff --git a/RustPlusApi/RustPlusApi/RustPlus.cs b/RustPlusApi/RustPlusApi/RustPlus.cs index a7b4ad8..3e22b36 100644 --- a/RustPlusApi/RustPlusApi/RustPlus.cs +++ b/RustPlusApi/RustPlusApi/RustPlus.cs @@ -1,6 +1,9 @@ -using System.Net.WebSockets; +using System.Diagnostics; -using Google.Protobuf; +using RustPlusApi.Data; +using RustPlusApi.Data.Events; +using RustPlusApi.Extensions; +using RustPlusApi.Utils; using RustPlusContracts; @@ -14,278 +17,197 @@ namespace RustPlusApi /// Your Steam ID. /// Your player token acquired with FCM. /// Specifies whether to use the Facepunch proxy. - public class RustPlus(string server, int port, ulong playerId, int playerToken, bool useFacepunchProxy = false) : IDisposable + public class RustPlus(string server, int port, ulong playerId, int playerToken, bool useFacepunchProxy = false) + : RustPlusLegacy(server, port, playerId, playerToken, useFacepunchProxy) { - private ClientWebSocket? _webSocket; - private uint _seq; - private readonly Dictionary> _seqCallbacks = new(); - - public event EventHandler? Connecting; - public event EventHandler? Connected; - public event EventHandler? MessageReceived; - public event EventHandler? RequestSent; - public event EventHandler? Disconnected; - public event EventHandler? ErrorOccurred; + public event EventHandler? OnSmartSwitchTriggered; // Alarm will also be triggered since there is no physical difference between them + public event EventHandler? OnStorageMonitorTriggered; /// - /// Connects to the Rust+ server asynchronously. + /// Parses the notification received from the Rust+ server. /// - public async Task ConnectAsync() + /// The broadcast received from the server. + protected override void ParseNotification(AppBroadcast? broadcast) { - _webSocket = new ClientWebSocket(); - _webSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(20); - var address = useFacepunchProxy - ? new Uri($"wss://companion-rust.facepunch.com/game/{server}/{port}") - : new Uri($"ws://{server}:{port}"); - - Connecting?.Invoke(this, EventArgs.Empty); + if (broadcast is null) return; - try + if (broadcast.EntityChanged is not null) { - await _webSocket.ConnectAsync(address, CancellationToken.None); - Connected?.Invoke(this, EventArgs.Empty); - await ReceiveMessagesAsync(); + // There is no physical difference between a SmartSwitch and an Alarm + // If you check the status of an alarm, it will return the same as a smart switch + if (broadcast.EntityChanged.Payload.Capacity is 0) + OnSmartSwitchTriggered?.Invoke(this, broadcast.EntityChanged.ToSmartSwitchEvent()); + else + OnStorageMonitorTriggered?.Invoke(this, broadcast.EntityChanged.ToStorageMonitorEvent()); } - catch (Exception ex) + else { - ErrorOccurred?.Invoke(this, ex); - Disconnect(); + Debug.WriteLine($"Unknown broadcast:\n{broadcast}"); } } /// - /// Receives messages from the Rust+ server asynchronously. + /// Processes the request asynchronously and returns the result. /// - private async Task ReceiveMessagesAsync() + /// The type of the result. + /// The request to be processed. + /// The function to select the result from the response. + /// A representing the asynchronous operation. The task result contains a with the processed result. + private async Task> ProcessRequestAsync(AppRequest request, Func successSelector) { - const int bufferSize = 1024; - var buffer = new byte[bufferSize]; - - try - { - while (_webSocket!.State == WebSocketState.Open) - { - var receiveBuffer = new List(); - WebSocketReceiveResult result; - - do - { - result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - receiveBuffer.AddRange(buffer.Take(result.Count)); - } while (!result.EndOfMessage); + var response = await SendRequestAsync(request); - var messageData = receiveBuffer.ToArray(); - var message = AppMessage.Parser.ParseFrom(messageData); - HandleResponse(message); - } - } - catch (WebSocketException ex) - { - ErrorOccurred?.Invoke(this, ex); - } - catch (Exception ex) - { - ErrorOccurred?.Invoke(this, ex); - } + return IsError(response) + ? ResponseHelper.BuildGenericOutput(false, default!, response.Response.Error.Error) + : ResponseHelper.BuildGenericOutput(true, successSelector(response)); } /// - /// Handles the response received from the Rust+ server. + /// Retrieves the information of a smart switch asynchronously. /// - /// The AppMessage received from the server. - private void HandleResponse(AppMessage message) + /// The ID of the smart switch entity. + /// A representing the asynchronous operation. The task result contains a with the smart switch information. + public async Task> GetSmartSwitchInfoAsync(uint entityId) { - if (message.Response != null && message.Response.Seq != 0 && _seqCallbacks.ContainsKey((int)message.Response.Seq)) + var request = new AppRequest { - var callback = _seqCallbacks[(int)message.Response.Seq]; - var result = callback.Invoke(message); - _seqCallbacks.Remove((int)message.Response.Seq); - if (result) return; - } - MessageReceived?.Invoke(this, message); - } - - /// - /// Sends a request to the Rust+ server asynchronously. - /// - /// The request to send. - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. - private async Task SendRequestAsync(AppRequest request, Func? callback = null) - { - var seq = ++_seq; - if (callback != null) _seqCallbacks[(int)seq] = callback; - - request.Seq = seq; - request.PlayerId = playerId; - request.PlayerToken = playerToken; - - var requestData = request.ToByteArray(); - var buffer = new ArraySegment(requestData); - await _webSocket!.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None); - RequestSent?.Invoke(this, request); + EntityId = entityId, + GetEntityInfo = new AppEmpty() + }; + return await ProcessRequestAsync(request, r => r.Response.EntityInfo.ToSmartSwitchInfo()); } /// - /// Disconnects from the Rust+ server. + /// Retrieves the information of an alarm asynchronously. /// - private void Disconnect() + /// The ID of the alarm entity. + /// A representing the asynchronous operation. The task result contains a with the alarm information. + public async Task> GetAlarmInfoAsync(uint entityId) { - if (_webSocket is not { State: WebSocketState.Open }) return; - - _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed by client.", CancellationToken.None).Wait(); - _webSocket.Dispose(); - - Disconnected?.Invoke(this, EventArgs.Empty); + var request = new AppRequest + { + EntityId = entityId, + GetEntityInfo = new AppEmpty() + }; + return await ProcessRequestAsync(request, r => r.Response.EntityInfo.ToAlarmInfo()); } /// - /// Disposes the Rust+ API client and disconnects from the Rust+ server. - /// - public void Dispose() => Disconnect(); - - /// - /// Checks if the client is connected to the Rust+ server. + /// Retrieves the information of a storage monitor asynchronously. /// - /// True if the client is connected; otherwise, false. - public bool IsConnected() => _webSocket is { State: WebSocketState.Open }; - - /// - /// Retrieves information about an entity from the Rust+ server asynchronously. - /// - /// The ID of the entity to retrieve information for. - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. - public async Task GetEntityInfoAsync(int entityId, Func? callback = null) + /// The ID of the storage monitor entity. + /// A representing the asynchronous operation. The task result contains a with the storage monitor information. + public async Task> GetStorageMonitorInfoAsync(uint entityId) { var request = new AppRequest { - EntityId = (uint)entityId, + EntityId = entityId, GetEntityInfo = new AppEmpty() }; - await SendRequestAsync(request, callback); + return await ProcessRequestAsync(request, r => r.Response.EntityInfo.ToStorageMonitorInfo()); } /// - /// Retrieves general information from the Rust+ server asynchronously. + /// Retrieves the server information asynchronously. /// - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. - public async Task GetInfoAsync(Func? callback = null) + /// A representing the asynchronous operation. The task result contains a with the server information. + public async Task> GetInfoAsync() { var request = new AppRequest { GetInfo = new AppEmpty() }; - await SendRequestAsync(request, callback); + return await ProcessRequestAsync(request, r => r.Response.Info.ToServerInfo()); } /// - /// Retrieves the map from the Rust+ server asynchronously. + /// Retrieves the server map asynchronously. /// - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. - public async Task GetMapAsync(Func? callback = null) + /// A representing the asynchronous operation. The task result contains a with the server map. + public async Task> GetMapAsync() { var request = new AppRequest { GetMap = new AppEmpty() }; - await SendRequestAsync(request, callback); + return await ProcessRequestAsync(request, r => r.Response.Map.ToServerMap()); } - /// - /// Retrieves the map markers from the Rust+ server asynchronously. - /// - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. + /* + public async Task GetMapMarkersAsync(Func? callback = null) { - var request = new AppRequest - { - GetMapMarkers = new AppEmpty() - }; - await SendRequestAsync(request, callback); + var request = new AppRequest + { + GetMapMarkers = new AppEmpty() + }; + await SendRequestAsync(request, callback); + } + + public async Task GetTeamChatAsync(Func? callback = null) + { + var request = new AppRequest + { + GetTeamChat = new AppEmpty(), + }; + await SendRequestAsync(request, callback); } - /// - /// Retrieves team information from the Rust+ server asynchronously. - /// - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. public async Task GetTeamInfoAsync(Func? callback = null) { - var request = new AppRequest - { - GetTeamInfo = new AppEmpty() - }; - await SendRequestAsync(request, callback); + var request = new AppRequest + { + GetTeamInfo = new AppEmpty() + }; + await SendRequestAsync(request, callback); } - /// - /// Retrieves the current time from the Rust+ server asynchronously. - /// - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. public async Task GetTimeAsync(Func? callback = null) { - var request = new AppRequest - { - GetTime = new AppEmpty() - }; - await SendRequestAsync(request, callback); + var request = new AppRequest + { + GetTime = new AppEmpty() + }; + await SendRequestAsync(request, callback); } - /// - /// Sends a team message to the Rust+ server asynchronously. - /// - /// The message to send. - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. - public async Task SendTeamMessageAsync(string message, Func? callback = null) + public async Task PromoteToLeaderAsync(ulong steamId, Func? callback = null) { - var request = new AppRequest - { - SendTeamMessage = new AppSendMessage - { - Message = message - } - }; - await SendRequestAsync(request, callback); + var request = new AppRequest + { + PromoteToLeader = new AppPromoteToLeader + { + SteamId = steamId + } + }; + await SendRequestAsync(request, callback); } - /// - /// Sets the value of an entity asynchronously. - /// - /// The ID of the entity to set the value for. - /// The value to set. - /// An optional callback function to handle the response. - /// A task representing the asynchronous operation. - public async Task SetEntityValueAsync(int entityId, bool value, Func? callback = null) + public async Task SendTeamMessageAsync(string message, Func? callback = null) { - var request = new AppRequest - { - EntityId = (uint)entityId, - SetEntityValue = new AppSetEntityValue - { - Value = value - } - }; - await SendRequestAsync(request, callback); + var request = new AppRequest + { + SendTeamMessage = new AppSendMessage + { + Message = message + } + }; + await SendRequestAsync(request, callback); } - /// - /// Toggles the value of an entity repeatedly with a specified timeout. - /// - /// The ID of the entity to toggle the value for. - /// The timeout in milliseconds between toggling the value. - /// The initial value to set. - /// A task representing the asynchronous operation. - public async Task StrobeAsync(int entityId, int timeoutMilliseconds = 1000, bool value = true) + public async Task SetEntityValueAsync(int entityId, bool value, Func? callback = null) { - await SetEntityValueAsync(entityId, value); - await Task.Delay(timeoutMilliseconds); - await SetEntityValueAsync(entityId, !value); + var request = new AppRequest + { + EntityId = (uint)entityId, + SetEntityValue = new AppSetEntityValue + { + Value = value + } + }; + await SendRequestAsync(request, callback); } + + */ } -} +} \ No newline at end of file diff --git a/RustPlusApi/RustPlusApi/RustPlusBase.cs b/RustPlusApi/RustPlusApi/RustPlusBase.cs new file mode 100644 index 0000000..983ffbc --- /dev/null +++ b/RustPlusApi/RustPlusApi/RustPlusBase.cs @@ -0,0 +1,180 @@ +using System.Diagnostics; +using System.Net.WebSockets; + +using Google.Protobuf; + +using RustPlusContracts; + +using static System.GC; +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable MemberCanBePrivate.Global + +namespace RustPlusApi +{ + /// + /// A Rust+ API client made in C#. + /// + /// The IP address of the Rust+ server. + /// The port dedicated for the Rust+ companion app (not the one used to connect in-game). + /// Your Steam ID. + /// Your player token acquired with FCM. + /// Specifies whether to use the Facepunch proxy. + public abstract class RustPlusBase(string server, int port, ulong playerId, int playerToken, bool useFacepunchProxy = false) : IDisposable + { + private ClientWebSocket? _webSocket; + private uint _seq; + private readonly Dictionary> _seqCallbacks = []; + + public event EventHandler? Connecting; + public event EventHandler? Connected; + public event EventHandler? MessageReceived; + public event EventHandler? RequestSent; + public event EventHandler? Disconnected; + public event EventHandler? ErrorOccurred; + + /// + /// Connects to the Rust+ server asynchronously. + /// + public async Task ConnectAsync() + { + _webSocket = new ClientWebSocket(); + _webSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(20); + var address = useFacepunchProxy + ? new Uri($"wss://companion-rust.facepunch.com/game/{server}/{port}") + : new Uri($"ws://{server}:{port}"); + + Connecting?.Invoke(this, EventArgs.Empty); + + try + { + await _webSocket.ConnectAsync(address, CancellationToken.None); + Connected?.Invoke(this, EventArgs.Empty); + await ReceiveMessagesAsync(); + } + catch (Exception ex) + { + ErrorOccurred?.Invoke(this, ex); + Dispose(); + } + } + + /// + /// Receives messages from the Rust+ server asynchronously. + /// + /// A task representing the asynchronous operation. + protected async Task ReceiveMessagesAsync() + { + const int bufferSize = 1024; + var buffer = new byte[bufferSize]; + + try + { + while (_webSocket!.State == WebSocketState.Open) + { + var receiveBuffer = new List(); + WebSocketReceiveResult result; + + do + { + result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + receiveBuffer.AddRange(buffer.Take(result.Count)); + } while (!result.EndOfMessage); + + var messageData = receiveBuffer.ToArray(); + var message = AppMessage.Parser.ParseFrom(messageData); + HandleResponse(message); + } + } + catch (WebSocketException ex) + { + Debug.WriteLine($"Disconnected from the Rust+ socket due to a WebSocketException: {ex}"); + ErrorOccurred?.Invoke(this, ex); + } + catch (Exception ex) + { + Debug.WriteLine($"Disconnected from the Rust+ socket due to an Exception: {ex}"); + ErrorOccurred?.Invoke(this, ex); + } + finally + { + Dispose(); + } + } + + /// + /// Handles the response received from the Rust+ server. + /// + /// The AppMessage received from the server. + protected void HandleResponse(AppMessage message) + { + if (message.Response != null + && message.Response.Seq != 0 + && _seqCallbacks.ContainsKey((int)message.Response.Seq)) + { + var tcs = _seqCallbacks[(int)message.Response.Seq]; + tcs.SetResult(message); + _seqCallbacks.Remove((int)message.Response.Seq); + return; + } + MessageReceived?.Invoke(this, message); + ParseNotification(message.Broadcast); + } + + /// + /// Parses the notification received from the Rust+ server. + /// + /// The AppBroadcast received from the server. + protected virtual void ParseNotification(AppBroadcast? broadcast) { } + + /// + /// Sends a request to the Rust+ server asynchronously. + /// + /// The request to send. + /// A task representing the asynchronous operation. + public async Task SendRequestAsync(AppRequest request) + { + var seq = ++_seq; + var tcs = new TaskCompletionSource(); + _seqCallbacks[(int)seq] = tcs; + + request.Seq = seq; + request.PlayerId = playerId; + request.PlayerToken = playerToken; + + var requestData = request.ToByteArray(); + var buffer = new ArraySegment(requestData); + await _webSocket!.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None); + RequestSent?.Invoke(this, request); + + return await tcs.Task; + } + + /// + /// Disposes the Rust+ API client and disconnects from the Rust+ server. + /// + public void Dispose() + { + if (_webSocket is not { State: WebSocketState.Open }) return; + + _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed by client.", CancellationToken.None).Wait(); + _webSocket.Dispose(); + + Disconnected?.Invoke(this, EventArgs.Empty); + + SuppressFinalize(this); + } + + /// + /// Checks if the client is connected to the Rust+ socket. + /// + /// True if the client is connected; otherwise, false. + public bool IsConnected() => _webSocket is { State: WebSocketState.Open }; + + /// + /// Checks if the given response is an error. + /// + /// The AppMessage response to check. + /// True if the response is an error; otherwise, false. + protected static bool IsError(AppMessage response) => response.Response.Error is not null; + } +} diff --git a/RustPlusApi/RustPlusApi/RustPlusLegacy.cs b/RustPlusApi/RustPlusApi/RustPlusLegacy.cs new file mode 100644 index 0000000..6150cb8 --- /dev/null +++ b/RustPlusApi/RustPlusApi/RustPlusLegacy.cs @@ -0,0 +1,201 @@ +using RustPlusContracts; +// ReSharper disable MemberCanBePrivate.Global + +namespace RustPlusApi +{ + /// + /// A Rust+ API client made in C#. + /// + /// The IP address of the Rust+ server. + /// The port dedicated for the Rust+ companion app (not the one used to connect in-game). + /// Your Steam ID. + /// Your player token acquired with FCM. + /// Specifies whether to use the Facepunch proxy. + public class RustPlusLegacy(string server, int port, ulong playerId, int playerToken, bool useFacepunchProxy = false) + : RustPlusBase(server, port, playerId, playerToken, useFacepunchProxy) + { + /// + /// Retrieves the clan chat from the Rust+ server asynchronously. + /// + /// The clan chat. + public async Task GetClanChatLegacyAsync() + { + var request = new AppRequest + { + GetClanChat = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Retrieves information about an entity from the Rust+ server asynchronously. + /// + /// The ID of the entity to retrieve information for. + /// The entity information. + public async Task GetEntityInfoLegacyAsync(uint entityId) + { + var request = new AppRequest + { + EntityId = entityId, + GetEntityInfo = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Retrieves the information from the Rust+ server asynchronously. + /// + /// The information. + public async Task GetInfoLegacyAsync() + { + var request = new AppRequest + { + GetInfo = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Retrieves the map from the Rust+ server asynchronously. + /// + /// The map. + public async Task GetMapLegacyAsync() + { + var request = new AppRequest + { + GetMap = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Retrieves the map markers from the Rust+ server asynchronously. + /// + /// The map markers. + public async Task GetMapMarkersLegacyAsync() + { + var request = new AppRequest + { + GetMapMarkers = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Retrieves the team chat from the Rust+ server asynchronously. + /// + /// The team chat. + public async Task GetTeamChatLegacyAsync() + { + var request = new AppRequest + { + GetTeamChat = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Retrieves the team information from the Rust+ server asynchronously. + /// + /// The team information. + public async Task GetTeamInfoLegacyAsync() + { + var request = new AppRequest + { + GetTeamInfo = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Retrieves the time from the Rust+ server asynchronously. + /// + /// The time. + public async Task GetTimeLegacyAsync() + { + var request = new AppRequest + { + GetTime = new AppEmpty() + }; + return await SendRequestAsync(request); + } + + /// + /// Promotes a player to leader in the Rust+ server asynchronously. + /// + /// The Steam ID of the player to promote. + /// The response from the server. + public async Task PromoteToLeaderLegacyAsync(ulong steamId) + { + var request = new AppRequest + { + PromoteToLeader = new AppPromoteToLeader + { + SteamId = steamId + } + }; + return await SendRequestAsync(request); + } + + /// + /// Sends a team message to the Rust+ server asynchronously. + /// + /// The message to send. + /// The response from the server. + public async Task SendTeamMessageLegacyAsync(string message) + { + var request = new AppRequest + { + SendTeamMessage = new AppSendMessage + { + Message = message + } + }; + return await SendRequestAsync(request); + } + + /// + /// Sets the value of an entity in the Rust+ server asynchronously. + /// + /// The ID of the entity. + /// The value to set. + /// The response from the server. + public async Task SetEntityValueLegacyAsync(uint entityId, bool value) + { + var request = new AppRequest + { + EntityId = entityId, + SetEntityValue = new AppSetEntityValue + { + Value = value + } + }; + return await SendRequestAsync(request); + } + + /// + /// Strobes an entity in the Rust+ server asynchronously. + /// + /// The ID of the entity. + /// The timeout in milliseconds. + /// The value to set. + public async Task StrobeEntityLegacyAsync(uint entityId, int timeoutMilliseconds = 1000, bool value = true) + { + await SetEntityValueLegacyAsync(entityId, value); + await Task.Delay(timeoutMilliseconds); + await SetEntityValueLegacyAsync(entityId, !value); + } + + /// + /// Toggles the value of an entity in the Rust+ server asynchronously. + /// + /// The ID of the entity. + /// A task representing the asynchronous operation. + public async Task ToogleEntityValueLegacyAsync(uint entityId) + { + var entityInfo = await GetEntityInfoLegacyAsync(entityId); + var value = entityInfo.Response.EntityInfo.Payload.Value; + await SetEntityValueLegacyAsync(entityId, !value); + } + } +} diff --git a/RustPlusApi/RustPlusApi/Utils/ResponseHelper.cs b/RustPlusApi/RustPlusApi/Utils/ResponseHelper.cs new file mode 100644 index 0000000..2ae28ef --- /dev/null +++ b/RustPlusApi/RustPlusApi/Utils/ResponseHelper.cs @@ -0,0 +1,17 @@ +using RustPlusApi.Data; + +namespace RustPlusApi.Utils +{ + public static class ResponseHelper + { + public static Response BuildGenericOutput(bool isSuccess, T data, string? message = null) + { + return new Response + { + IsSuccess = isSuccess, + Error = message is null ? null : new Error { Message = message }, + Data = data + }; + } + } +}