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
+ };
+ }
+ }
+}