├── .gitattributes ├── .github └── workflows │ └── build-publish.yaml ├── .gitignore ├── Directory.Build.props ├── FiveMMsgPack └── MsgPack.dll ├── FxEvents.sln ├── GitVersion.yml ├── LICENSE.txt ├── README.md ├── build └── Version.props ├── dependencies └── RedM │ ├── CitizenFX.Core.dll │ └── CitizenFX.Core.xml ├── scripts └── .gitkeep └── src ├── FxEvents.Client ├── .editorconfig ├── EventDispatcher.cs ├── EventHub.cs ├── EventSystem │ └── ClientGateway.cs └── FxEvents.Client.csproj ├── FxEvents.Server ├── .editorconfig ├── EventDispatcher.cs ├── EventHub.cs ├── EventSystem │ └── ServerGateway.cs └── FxEvents.Server.csproj └── FxEvents.Shared ├── BinaryHelper.cs ├── ContractResolvers.cs ├── Encryption ├── Curve25519.cs ├── Curve25519Inner.cs └── Encryption.cs ├── EventSubsystem ├── Attributes │ ├── ForceAttribute.cs │ ├── FxEventAttribute.cs │ └── IgnoreAttribute.cs ├── BaseGateway.cs ├── BaseGatewayHelpers.cs ├── Diagnostics │ ├── IEventLogger.cs │ ├── Impl │ │ ├── ClientStopwatch.cs │ │ └── ServerStopwatch.cs │ └── StopwatchUtil.cs ├── EventsDictionary.cs ├── Exceptions │ └── EventException.cs ├── ISerializable.cs ├── ISource.cs ├── Message │ ├── EventFlowType.cs │ ├── EventMessage.cs │ ├── EventParameter.cs │ ├── EventRemote.cs │ └── EventResponseMessage.cs ├── Models │ ├── EventObservable.cs │ └── EventValueHolder.cs ├── Serialization │ ├── ISerialization.cs │ ├── Implementations │ │ ├── BinarySerialization.cs │ │ ├── JsonSerialization.cs │ │ ├── MsgPack │ │ │ └── MsgPackResolvers │ │ │ │ ├── DoubleFixer.cs │ │ │ │ ├── EntityResolver.cs │ │ │ │ ├── ISourceResolver.cs │ │ │ │ ├── KeyValuePairResolver.cs │ │ │ │ ├── Matrix3x3Resolver.cs │ │ │ │ ├── MatrixResolver.cs │ │ │ │ ├── PlayerResolver.cs │ │ │ │ ├── QuaternionResolver.cs │ │ │ │ ├── SnowflakeResolver.cs │ │ │ │ ├── TupleResolver.cs │ │ │ │ ├── ValueTupleResolver.cs │ │ │ │ └── VectorResolver.cs │ │ └── MsgPackSerialization.cs │ ├── MsgPackCode.cs │ ├── SerializationContext.cs │ ├── SerializationException.cs │ └── TypeConvert.cs ├── ServerId.cs ├── TamperType.cs └── TypeConverter.cs ├── FxEvents.Shared.projitems ├── FxEvents.Shared.shproj ├── JsonHelper.cs ├── Logger ├── ILogger.cs ├── Logger.cs └── LoggerColors.cs ├── Snowflake ├── Clock.cs ├── Program.cs ├── Serialization │ └── SnowflakeConverter.cs ├── Snowflake.cs ├── SnowflakeConfiguration.cs ├── SnowflakeFragments.cs ├── SnowflakeGenerator.cs └── SnowflakeRepresentation.cs ├── TypeConverter.cs └── TypeExtensions ├── Base64Extensions.cs ├── BooleanExtensions.cs ├── DelegateExtensions.cs ├── EnumerableExtensions.cs ├── KeyValuePairExtensions.cs ├── ObjectExtensions.cs ├── StringExtensions.cs ├── TaskExtensions.cs ├── TypeCache.cs └── ValueTuple.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/build-publish.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v[0-9]+.[0-9]+" 5 | - "v[0-9]+.[0-9]+.[0-9]+" 6 | - "v[0-9]+.[0-9]+.[0-9]+.[0-9]+" 7 | if: startsWith(github.ref, 'refs/tags/') 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref_name }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | is-tag: ${{ startsWith(github.ref, 'refs/tags/v') }} 15 | 16 | jobs: 17 | build: 18 | runs-on: windows-latest 19 | strategy: 20 | matrix: 21 | client: ['FiveM', 'RedM'] 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: Setup - GitVersion 29 | uses: gittools/actions/gitversion/setup@v0 30 | with: 31 | versionSpec: '5.x' 32 | preferLatestVersion: true 33 | 34 | - name: Setup - dotnet framework 4.8 35 | run: | 36 | choco install netfx-4.8-devpack -y 37 | dotnet --version # Verify installation 38 | - name: Setup - dotnet 8 39 | uses: actions/setup-dotnet@v4 40 | with: 41 | source-url: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json 42 | env: 43 | NUGET_AUTH_TOKEN: ${{ github.token }} 44 | 45 | - name: GitVersion 46 | id: git-version 47 | uses: gittools/actions/gitversion/execute@v0 48 | with: 49 | useConfigFile: true 50 | 51 | - name: Install dotnet tool 52 | run: dotnet tool install -g dotnetCampus.TagToVersion 53 | 54 | - name: Set tag to version 55 | run: dotnet TagToVersion -t ${{ github.ref }} 56 | 57 | - name: Dotnet - Restore 58 | run: dotnet restore 59 | 60 | - name: Dotnet - Build 61 | run: dotnet build -c "Release ${{ matrix.client }}" --no-restore 62 | 63 | - name: Dotnet - Test 64 | run: dotnet test -c "Release ${{ matrix.client }}" --no-restore 65 | 66 | - name: Dotnet - Pack 67 | run: dotnet pack -c "Release ${{ matrix.client }}" --no-restore 68 | 69 | - name: Publish - NuGet 70 | if: ${{ env.is-tag == 'true' }} 71 | run: dotnet nuget push **/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate 72 | working-directory: CompiledLibs 73 | 74 | - name: Publish - GitHub 75 | if: ${{ env.is-tag == 'true' }} 76 | run: dotnet nuget push **/*.nupkg --skip-duplicate 77 | working-directory: CompiledLibs 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | obj/ 33 | Obj/ 34 | [Oo]ut/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio LightSwitch build output 300 | **/*.HTMLClient/GeneratedArtifacts 301 | **/*.DesktopClient/GeneratedArtifacts 302 | **/*.DesktopClient/ModelManifest.xml 303 | **/*.Server/GeneratedArtifacts 304 | **/*.Server/ModelManifest.xml 305 | _Pvt_Extensions 306 | 307 | # Paket dependency manager 308 | .paket/paket.exe 309 | paket-files/ 310 | 311 | # FAKE - F# Make 312 | .fake/ 313 | 314 | # CodeRush personal settings 315 | .cr/personal 316 | 317 | # Python Tools for Visual Studio (PTVS) 318 | __pycache__/ 319 | *.pyc 320 | 321 | # Cake - Uncomment if you are using it 322 | # tools/** 323 | # !tools/packages.config 324 | 325 | # Tabs Studio 326 | *.tss 327 | 328 | # Telerik's JustMock configuration file 329 | *.jmconfig 330 | 331 | # BizTalk build output 332 | *.btp.cs 333 | *.btm.cs 334 | *.odx.cs 335 | *.xsd.cs 336 | 337 | # OpenCover UI analysis results 338 | OpenCover/ 339 | 340 | # Azure Stream Analytics local run output 341 | ASALocalRun/ 342 | 343 | # MSBuild Binary and Structured Log 344 | *.binlog 345 | 346 | # NVidia Nsight GPU debugger configuration file 347 | *.nvuser 348 | 349 | # MFractors (Xamarin productivity tool) working folder 350 | .mfractor/ 351 | 352 | # Local History for Visual Studio 353 | .localhistory/ 354 | 355 | # BeatPulse healthcheck temp database 356 | healthchecksdb 357 | 358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 359 | MigrationBackup/ 360 | 361 | # scripts folder 362 | .scripts/ 363 | *.bat 364 | *.log 365 | 366 | *.md 367 | 368 | # Ionide (cross platform F# VS Code tools) working folder 369 | .ionide/ 370 | 371 | # Fody - auto-generated XML schema 372 | FodyWeavers.xsd 373 | 374 | # Ignored folders from New configuration 375 | [Rr]elease [Rr]ed[Mm]/ 376 | [Rr]elease [Ff]ive[Mm]/ 377 | [Dd]ebug [Rr]ed[Mm]/ 378 | [Dd]ebug [Ff]ive[Mm]/ 379 | 380 | # .idea files - Rider / JetBrains IDE's 381 | .idea -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /FiveMMsgPack/MsgPack.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manups4e/FxEvents/27c5d2647b769a16c4a9c38391bae150e79bc757/FiveMMsgPack/MsgPack.dll -------------------------------------------------------------------------------- /FxEvents.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32825.248 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FxEvents.Client", "src\FxEvents.Client\FxEvents.Client.csproj", "{4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FxEvents.Server", "src\FxEvents.Server\FxEvents.Server.csproj", "{5C4FECB5-C970-46AA-8536-E6DD716BDCE0}" 9 | EndProject 10 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "FxEvents.Shared", "src\FxEvents.Shared\FxEvents.Shared.shproj", "{7D81B737-B66A-4961-BFA0-6A69347EED78}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug FiveM|Any CPU = Debug FiveM|Any CPU 15 | Debug RedM|Any CPU = Debug RedM|Any CPU 16 | Release FiveM|Any CPU = Release FiveM|Any CPU 17 | Release RedM|Any CPU = Release RedM|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Debug FiveM|Any CPU.ActiveCfg = Debug FiveM|Any CPU 21 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Debug FiveM|Any CPU.Build.0 = Debug FiveM|Any CPU 22 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Debug RedM|Any CPU.ActiveCfg = Debug RedM|Any CPU 23 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Debug RedM|Any CPU.Build.0 = Debug RedM|Any CPU 24 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Release FiveM|Any CPU.ActiveCfg = Release FiveM|Any CPU 25 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Release FiveM|Any CPU.Build.0 = Release FiveM|Any CPU 26 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Release RedM|Any CPU.ActiveCfg = Release RedM|Any CPU 27 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC}.Release RedM|Any CPU.Build.0 = Release RedM|Any CPU 28 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Debug FiveM|Any CPU.ActiveCfg = Debug|Any CPU 29 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Debug FiveM|Any CPU.Build.0 = Debug|Any CPU 30 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Debug RedM|Any CPU.ActiveCfg = Debug|Any CPU 31 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Debug RedM|Any CPU.Build.0 = Debug|Any CPU 32 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Release FiveM|Any CPU.ActiveCfg = Release|Any CPU 33 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Release FiveM|Any CPU.Build.0 = Release|Any CPU 34 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Release RedM|Any CPU.ActiveCfg = Release|Any CPU 35 | {5C4FECB5-C970-46AA-8536-E6DD716BDCE0}.Release RedM|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {46F9C3B8-35E3-4A06-8775-EF96C3B56EA8} 42 | EndGlobalSection 43 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 44 | src\FxEvents.Shared\FxEvents.Shared.projitems*{4f57e7a2-97b7-499b-8f9a-57f0af74c9ac}*SharedItemsImports = 5 45 | src\FxEvents.Shared\FxEvents.Shared.projitems*{5c4fecb5-c970-46aa-8536-e6dd716bdce0}*SharedItemsImports = 5 46 | src\FxEvents.Shared\FxEvents.Shared.projitems*{7d81b737-b66a-4961-bfa0-6a69347eed78}*SharedItemsImports = 13 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDelivery 2 | branches: 3 | master: 4 | mode: ContinuousDeployment 5 | tag: ci 6 | track-merge-target: true 7 | ignore: 8 | sha: [] 9 | merge-message-formats: {} 10 | tag-prefix: "" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FxEvents: An Advanced Event Subsystem for FiveM using mono v1 2 | 3 | FxEvents provides a robust and secure framework for event handling in FiveM, equipped with powerful serialization, encryption, and anti-tampering features. With its easy integration and extensive functionality, FxEvents is a valuable tool for developers seeking efficient and secure client-server communication in their FiveM projects. It incorporates encrypted signatures and MsgPack binary serialization to safeguard against malicious client actions. To integrate FxEvents into your project, simply include `FxEvents.Client.dll` or `FxEvents.Server.dll` along with `Newtonsoft.Json.dll` (if you opt for JSON serialization). The MsgPack functionality is inherently supported by FiveM, so no additional libraries are required! 4 | 5 | Starting from Version 3.0.0, the library eliminates the need for manual event initialization, as internal events are now SHA-256 generated based on the resource name combined with a random seed. This update streamlines the process, with initialization primarily used to register events marked with the `[FxEvent]` attribute that would otherwise require a mandatory call from the requesting script. 6 | 7 | For more details and support, [join our Discord server](https://discord.gg/KKN7kRT2vM). 8 | 9 | --- 10 | 11 | **Support** 12 | 13 | If you like my work, please consider supporting me via PayPal. You can [buy me a coffee or donut, some banana, a shirt, BMW i4, Taycan, Tesla, the stars, or whatever you want here](https://ko-fi.com/manups4e). 14 | 15 | --- 16 | 17 | ## Usage Examples: 18 | 19 | ### Initialization 20 | 21 | To initialize the FxEvents library, include the following in your script: 22 | 23 | ```csharp 24 | public class Main : BaseScript 25 | { 26 | public Main() 27 | { 28 | // Initialize the FxEvents library. Call this once at script start to enable EventHub usage anywhere. 29 | EventHub.Initialize(); 30 | } 31 | } 32 | ``` 33 | 34 | --- 35 | 36 | ### Mounting an Event 37 | 38 | Events can be mounted similarly to standard events. Below is an example of mounting an event in both lambda style and classic method: 39 | 40 | - The `FromSource` attribute enables you to retrieve the source as in standard events, allowing requests for Player, the ISource inheriting class, source as Int32 or as String. 41 | 42 | ```csharp 43 | EventHub.Mount("eventName", Binding.All, new Action([FromSource] source, val1, val2) => 44 | { 45 | // Code to execute inside the event. 46 | // ISource is an optional class handling clients triggering the event, similar to the "[FromSource] Player player" parameter but customizable. 47 | // On the client-side, the same principle applies without the ClientId parameter. 48 | })); 49 | 50 | EventHub.Mount("eventName", Binding.All, new Action(MyMethod)); 51 | private void MyMethod([FromSource] ISource source, type1 val1, type2 val2) 52 | { 53 | // Code to execute inside the event. 54 | // ISource is an optional class handling clients triggering the event, similar to the "[FromSource] Player player" parameter but customizable. 55 | // On the client-side, the same principle applies without the ClientId parameter. 56 | } 57 | ``` 58 | 59 | - **Q: Must the returning method mounted with `Func` be `async Task`?** 60 | - **A: Not necessarily. If you don't need it to be a task, return the required type. The `Get` method is awaitable because it waits for a response from the other side.** 61 | 62 | - Events can also be mounted using the `[FxEvent("EventName")]` attribute or by `EventHub.Events["EventName"] += new Action / new Func`. 63 | 64 | ⚠️ Note: Only one method per attribute can be registered for callbacks. 65 | 66 | --- 67 | 68 | ### Event Handling Enhancements 69 | 70 | From version 3.0.0, events are managed similarly to Mono V2 (thanks to @Thorium for ongoing support), allowing for type conversions across sides. Events are no longer bound to specific parameters, enabling casting from one type to another. ⚠️ Generic objects (object or dynamic) are not allowed due to MsgPack restrictions. 71 | 72 | Example: 73 | 74 | ```csharp 75 | [FxEvent("myEvent")] 76 | public static async void GimmeAll(int a, string b) 77 | => Logger.Info($"GimmeAll1 {a} {b}"); 78 | 79 | [FxEvent("myEvent")] 80 | public static async void GimmeAll(int a) 81 | => Logger.Info($"GimmeAll1 {a}"); 82 | 83 | [FxEvent("myEvent")] 84 | public static async void GimmeAll(string a, int b) 85 | => Logger.Info($"GimmeAll2 {a} {b}"); 86 | 87 | [FxEvent("myEvent")] 88 | public static async void GimmeAll(string a, int b, string c = "Hey") 89 | => Logger.Info($"GimmeAll3 {a} {b} {c}"); 90 | 91 | [FxEvent("myEvent")] 92 | public static async void GimmeAll(int a, string b, string c = "Oh", int d = 678) 93 | => Logger.Info($"GimmeAll4 {a} {b} {c} {d}"); 94 | 95 | [FxEvent("myEvent")] 96 | public static async void GimmeAll(int a, PlayerClient b, string c = "Oh", int d = 678) 97 | => Logger.Info($"GimmeAll5 {a} {b.Player.Name} {c} {d}"); 98 | 99 | // Trigger the event 100 | EventHub.Send("myEvent", 1234, 1); 101 | ``` 102 | 103 | Outputs: 104 | 105 | ![image](https://github.com/manups4e/fx-events/assets/4005518/4e42a6b8-e3eb-4337-99a0-22be5b5211b6) 106 | 107 | ⚠️ Attributed methods MUST be static. 108 | 109 | ### Triggering an Event 110 | 111 | Events can be triggered from both client and server sides: 112 | 113 | ```csharp 114 | // Client-side 115 | EventHub.Send("eventName", params); 116 | 117 | // Server-side 118 | EventHub.Send(Player, "eventName", params); 119 | EventHub.Send(List, "eventName", params); 120 | EventHub.Send(ISource, "eventName", params); 121 | EventHub.Send(List, "eventName", params); 122 | EventHub.Send("eventName", params); // For all connected players 123 | ``` 124 | 125 | ### Triggering a Callback 126 | 127 | #### Mounting a Callback 128 | 129 | ```csharp 130 | EventHub.Mount("eventName", Binding.All, new Func>(async ([FromSource] source, val1, val2) => 131 | { 132 | // Code to execute inside the event. 133 | // ISource is an optional class handling clients triggering the event, similar to the "[FromSource] Player player" parameter but customizable. 134 | // On the client-side, the same principle applies without the ISource parameter. 135 | return val3; 136 | })); 137 | ``` 138 | 139 | Callbacks can also be mounted using the `[FxEvent("EventName")]` attribute. ⚠️ Only one per endpoint. Example: 140 | 141 | ```csharp 142 | [FxEvent("myEvent")] 143 | public static string GimmeAll(int a, string b) 144 | => "This is a test"; 145 | ``` 146 | 147 | #### Calling a Callback 148 | 149 | ```csharp 150 | // Client-side 151 | type param = await EventHub.Get("eventName", params); 152 | 153 | // Server-side 154 | type param = await EventHub.Get(ClientId, "eventName", params); 155 | type param = await EventHub.Get(Player, "eventName", params); 156 | ``` 157 | 158 | Callbacks can also be triggered server-side when the server needs information from specific clients. 159 | 160 | Both sides have a new `SendLocal` and `GetLocal` (method requires binding as All or Local) to trigger local events. FxEvents is required in both scripts to work. 161 | 162 | --- 163 | 164 | ### Native ValueTuple Support 165 | 166 | Starting from version 3.0.0, FxEvents offers native ValueTuple support for client-side (server-side using .Net Standard 2.0 already supports it natively). This provides an alternative to non-fixable Tuples that can't be included in collections or as members/fields inside classes and structs. ValueTuple is dynamic, easy to use, supports the latest C# versions, and being natively supported means no need for external NuGet packages or imported libraries to use it with FxEvents and FiveM. 167 | 168 | --- 169 | 170 | ### FiveM Types Support 171 | 172 | FxEvents supports FiveM internal types, allowing you to send Vectors, Quaternions, Matrices, and Entities. 173 | 174 | - **Player support:** You can send a Player object (or an int32/string) and receive a Player object on the other side. 175 | - **Entities support:** You can send a Ped, Vehicle, Prop, or Entity type as long as they're networked. FxEvents handles them using their NetworkID. 176 | - **Vectors and Math structs:** These are handled as float arrays, making them lightweight and fast. 177 | 178 | --- 179 | 180 | ### Event Binding 181 | 182 | Starting from 3.0.0, all events are handled like in Mono V2 update (thanks @thorium) and require a binding to be specified when mounted. There are 4 types of binding: 183 | 184 | - **None:** The event will be ignored and never triggered. 185 | - **Local:** The event is triggered ONLY when called using `SendLocal` / `GetLocal`. 186 | - **Remote:** The event will trigger only for Client/Server events (classic FxEvents way). 187 | - **All:** The event will trigger both with same-side or client/server calls. 188 | 189 | This allows better handling of communications between sides. Legacy EventDispatcher automatically mounts as Remote and `[FxEvent]` attribute binds automatically to All if no binding is specified. 190 | 191 | --- 192 | 193 | ### Anti-Tamper 194 | 195 | In version 3.0.0, a basic Anti-Tamper system is introduced, which uses a server event (`EventHandlers[fxevents:tamperingprotection] += anyMethodYouWant`) with parameters [source, endpoint, TamperType]: 196 | 197 | - **source:** The player handle that triggered the Anti-Tamper. 198 | - **endpoint:** The event that has been flagged as tampered. 199 | - **TamperType:** The type of tamper applied. It can be: 200 | - **TamperType:** The type of tamper applied. It can be: 201 | - **REPEATED_MESSAGE_ID:** Indicates someone tried to send the same event or a new event with a used ID. 202 | - **EDITED_ENCRYPTED_DATA:** Indicates someone altered the encrypted data trying to change values, making it impossible to decrypt. 203 | - **REQUESTED_NEW_PUBLIC_KEY:** Indicates a client tried to request a new ID to change the encryption, potentially attempting to edit the encryption. 204 | 205 | --- 206 | 207 | ### Additional Features for Customization and Debugging 208 | 209 | FxEvents includes several additional features for customization and debugging, especially regarding serialization: 210 | 211 | #### ToJson() 212 | 213 | Requires Newtonsoft.Json: 214 | ```csharp 215 | string text = param.ToJson(); 216 | ``` 217 | 218 | #### FromJson() 219 | 220 | Requires Newtonsoft.Json: 221 | ```csharp 222 | type value = jsonText.FromJson(); 223 | ``` 224 | 225 | #### ToBytes() 226 | 227 | ```csharp 228 | byte[] bytes = param.ToBytes(); 229 | ``` 230 | 231 | #### FromBytes() 232 | 233 | ```csharp 234 | type value = bytes.FromBytes(); 235 | ``` 236 | 237 | #### EncryptObject(string passkey) 238 | 239 | Binary serialization is performed internally. ⚠️ Same key for encryption and decryption: 240 | ```csharp 241 | byte[] bytes = param.EncryptObject("passkey"); 242 | ``` 243 | 244 | #### DecryptObject(string passkey) 245 | 246 | Binary deserialization is performed internally. ⚠️ Same key for encryption and decryption: 247 | ```csharp 248 | T object = bytes.DecryptObject("passkey"); 249 | ``` 250 | 251 | #### GenerateHash(string input) 252 | 253 | Generates the SHA-256 hash of the given input string: 254 | ```csharp 255 | byte[] hash = Encryption.GenerateHash(string input) 256 | ``` 257 | 258 | #### BytesToString 259 | 260 | Converts a byte array to a string: 261 | ```csharp 262 | byte[] bytes = param.ToBytes(); 263 | string txt = bytes.BytesToString(); 264 | ``` 265 | 266 | #### StringToBytes 267 | 268 | Converts a string back to a byte array: 269 | ```csharp 270 | byte[] bytes = txt.StringToBytes(); 271 | type value = bytes.FromBytes(); 272 | ``` 273 | -------------------------------------------------------------------------------- /build/Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.8.2 4 | 5 | -------------------------------------------------------------------------------- /dependencies/RedM/CitizenFX.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manups4e/FxEvents/27c5d2647b769a16c4a9c38391bae150e79bc757/dependencies/RedM/CitizenFX.Core.dll -------------------------------------------------------------------------------- /scripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manups4e/FxEvents/27c5d2647b769a16c4a9c38391bae150e79bc757/scripts/.gitkeep -------------------------------------------------------------------------------- /src/FxEvents.Client/.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*.{cs,vb}] 3 | #### Naming styles #### 4 | 5 | # Naming rules 6 | 7 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 8 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 9 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 10 | 11 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 12 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 13 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 14 | 15 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 16 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 17 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 18 | 19 | # Symbol specifications 20 | 21 | dotnet_naming_symbols.interface.applicable_kinds = interface 22 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 23 | dotnet_naming_symbols.interface.required_modifiers = 24 | 25 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 26 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 27 | dotnet_naming_symbols.types.required_modifiers = 28 | 29 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 30 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 31 | dotnet_naming_symbols.non_field_members.required_modifiers = 32 | 33 | # Naming styles 34 | 35 | dotnet_naming_style.begins_with_i.required_prefix = I 36 | dotnet_naming_style.begins_with_i.required_suffix = 37 | dotnet_naming_style.begins_with_i.word_separator = 38 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 39 | 40 | dotnet_naming_style.pascal_case.required_prefix = 41 | dotnet_naming_style.pascal_case.required_suffix = 42 | dotnet_naming_style.pascal_case.word_separator = 43 | dotnet_naming_style.pascal_case.capitalization = pascal_case 44 | 45 | dotnet_naming_style.pascal_case.required_prefix = 46 | dotnet_naming_style.pascal_case.required_suffix = 47 | dotnet_naming_style.pascal_case.word_separator = 48 | dotnet_naming_style.pascal_case.capitalization = pascal_case 49 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 50 | tab_width = 4 51 | indent_size = 4 52 | end_of_line = crlf 53 | dotnet_style_coalesce_expression = true:suggestion 54 | dotnet_style_null_propagation = true:suggestion 55 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 56 | dotnet_style_prefer_auto_properties = true:silent 57 | dotnet_style_object_initializer = true:suggestion 58 | dotnet_style_collection_initializer = true:suggestion 59 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 60 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 61 | dotnet_style_prefer_conditional_expression_over_return = true:silent 62 | dotnet_style_explicit_tuple_names = true:suggestion 63 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 64 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 65 | dotnet_style_prefer_compound_assignment = true:suggestion 66 | dotnet_style_prefer_simplified_interpolation = true:suggestion 67 | dotnet_style_namespace_match_folder = true:suggestion 68 | 69 | [*.cs] 70 | csharp_indent_labels = one_less_than_current 71 | csharp_using_directive_placement = outside_namespace:silent 72 | csharp_prefer_simple_using_statement = true:suggestion 73 | csharp_prefer_braces = true:silent 74 | csharp_style_namespace_declarations = block_scoped:silent 75 | csharp_style_prefer_method_group_conversion = true:silent 76 | csharp_style_prefer_top_level_statements = true:silent 77 | csharp_style_expression_bodied_methods = false:silent 78 | csharp_style_expression_bodied_constructors = false:silent 79 | csharp_style_expression_bodied_operators = false:silent 80 | csharp_style_expression_bodied_properties = true:silent 81 | csharp_style_expression_bodied_indexers = true:silent 82 | csharp_style_expression_bodied_accessors = true:silent 83 | csharp_style_expression_bodied_lambdas = true:silent 84 | csharp_style_expression_bodied_local_functions = false:silent 85 | csharp_style_throw_expression = true:suggestion 86 | csharp_style_prefer_null_check_over_type_check = true:suggestion 87 | csharp_prefer_simple_default_expression = true:suggestion 88 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 89 | csharp_style_prefer_index_operator = true:suggestion 90 | csharp_style_prefer_range_operator = true:suggestion 91 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 92 | csharp_style_prefer_tuple_swap = true:suggestion 93 | csharp_style_prefer_utf8_string_literals = true:suggestion 94 | csharp_style_inlined_variable_declaration = true:suggestion 95 | csharp_style_deconstructed_variable_declaration = true:suggestion 96 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion -------------------------------------------------------------------------------- /src/FxEvents.Client/EventDispatcher.cs: -------------------------------------------------------------------------------- 1 | global using CitizenFX.Core; 2 | global using CitizenFX.Core.Native; 3 | using FxEvents.EventSystem; 4 | using FxEvents.Shared; 5 | using FxEvents.Shared.EventSubsystem; 6 | 7 | using Logger; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Linq.Expressions; 12 | using System.Reflection; 13 | using System.Threading.Tasks; 14 | 15 | namespace FxEvents 16 | { 17 | [Obsolete("Use EventHub instead, this class will be removed soon")] 18 | public class EventDispatcher 19 | { 20 | internal static Log Logger; 21 | internal PlayerList GetPlayers => EventHub.Instance.GetPlayers; 22 | internal static ClientGateway Gateway => EventHub.Gateway; 23 | internal static bool Debug => EventHub.Debug; 24 | internal static bool Initialized => EventHub.Initialized; 25 | public static EventsDictionary Events => Gateway._handlers; 26 | 27 | public static void Initalize(string inboundEvent, string outboundEvent, string signatureEvent) 28 | { 29 | EventHub.Initialize(); 30 | } 31 | 32 | public static void Send(string endpoint, params object[] args) 33 | { 34 | EventHub.Send(endpoint, args); 35 | } 36 | 37 | public static void SendLatent(string endpoint, int bytesPerSeconds, params object[] args) 38 | { 39 | EventHub.SendLatent(endpoint, bytesPerSeconds, args); 40 | } 41 | 42 | public static async Task Get(string endpoint, params object[] args) 43 | { 44 | return await EventHub.Get(endpoint, args); 45 | } 46 | public static void Mount(string endpoint, Delegate @delegate) 47 | { 48 | EventHub.Mount(endpoint, Binding.Remote, @delegate); 49 | } 50 | public static void Unmount(string endpoint) 51 | { 52 | EventHub.Unmount(endpoint); 53 | } 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /src/FxEvents.Client/EventHub.cs: -------------------------------------------------------------------------------- 1 | global using CitizenFX.Core; 2 | global using CitizenFX.Core.Native; 3 | using FxEvents.EventSystem; 4 | using FxEvents.Shared; 5 | using FxEvents.Shared.Encryption; 6 | using FxEvents.Shared.EventSubsystem; 7 | 8 | using Logger; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Linq.Expressions; 13 | using System.Reflection; 14 | using System.Threading.Tasks; 15 | 16 | namespace FxEvents 17 | { 18 | public class EventHub : ClientScript 19 | { 20 | internal static Log Logger; 21 | internal PlayerList GetPlayers => Players; 22 | internal static ClientGateway Gateway; 23 | internal static bool Debug { get; set; } 24 | internal static bool Initialized = false; 25 | internal static EventHub Instance; 26 | public static EventsDictionary Events => Gateway._handlers; 27 | 28 | public EventHub() 29 | { 30 | Logger = new Log(); 31 | Instance = this; 32 | var resName = API.GetCurrentResourceName(); 33 | string debugMode = API.GetResourceMetadata(resName, "fxevents_debug_mode", 0); 34 | Debug = debugMode == "yes" || debugMode == "true" || int.TryParse(debugMode, out int num) && num > 0; 35 | 36 | byte[] inbound = Encryption.GenerateHash(resName + "_inbound"); 37 | byte[] outbound = Encryption.GenerateHash(resName + "_outbound"); 38 | byte[] signature = Encryption.GenerateHash(resName + "_signature"); 39 | Gateway = new ClientGateway(); 40 | Gateway.SignaturePipeline = signature.BytesToString(); 41 | Gateway.InboundPipeline = inbound.BytesToString(); 42 | Gateway.OutboundPipeline = outbound.BytesToString(); 43 | } 44 | 45 | public static void Initialize() 46 | { 47 | Initialized = true; 48 | Gateway.AddEvents(); 49 | 50 | var assembly = Assembly.GetCallingAssembly(); 51 | // we keep it outside because multiple classes with same event callback? no sir no. 52 | List withReturnType = new List(); 53 | 54 | foreach (var type in assembly.GetTypes()) 55 | { 56 | var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) 57 | .Where(m => m.GetCustomAttributes(typeof(FxEventAttribute), false).Length > 0); 58 | 59 | foreach (var method in methods) 60 | { 61 | var parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); 62 | var actionType = Expression.GetDelegateType(parameters.Concat(new[] { method.ReturnType }).ToArray()); 63 | var attribute = method.GetCustomAttribute(); 64 | 65 | if (method.ReturnType != typeof(void)) 66 | { 67 | if (withReturnType.Contains(attribute.Name)) 68 | { 69 | // throw error and break execution for the script sake. 70 | throw new Exception($"FxEvents - Failed registering [{attribute.Name}] delegates. Cannot register more than 1 delegate for [{attribute.Name}] with a return type!"); 71 | } 72 | else 73 | { 74 | withReturnType.Add(attribute.Name); 75 | } 76 | } 77 | 78 | if (method.IsStatic) 79 | Mount(attribute.Name, attribute.Binding, Delegate.CreateDelegate(actionType, method)); 80 | else 81 | Logger.Error($"Error registering method {method.Name} - FxEvents supports only Static methods for its [FxEvent] attribute!"); 82 | } 83 | } 84 | } 85 | 86 | /// 87 | /// registra un evento client (TriggerEvent) 88 | /// 89 | /// Nome evento 90 | /// Azione legata all'evento 91 | internal async void AddEventHandler(string eventName, Delegate action) 92 | { 93 | while (!Initialized) await BaseScript.Delay(0); 94 | EventHandlers[eventName] += action; 95 | } 96 | 97 | public static void Send(string endpoint, params object[] args) 98 | { 99 | if (!Initialized) 100 | { 101 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 102 | return; 103 | } 104 | Gateway.Send(endpoint, Binding.Remote, args); 105 | } 106 | 107 | public static void SendLocal(string endpoint, params object[] args) 108 | { 109 | if (!Initialized) 110 | { 111 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 112 | return; 113 | } 114 | Gateway.Send(endpoint, Binding.Local, args); 115 | } 116 | 117 | public static void SendLatent(string endpoint, int bytesPerSeconds, params object[] args) 118 | { 119 | if (!Initialized) 120 | { 121 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 122 | return; 123 | } 124 | Gateway.SendLatent(endpoint, bytesPerSeconds, args); 125 | } 126 | 127 | public static async Task Get(string endpoint, params object[] args) 128 | { 129 | if (!Initialized) 130 | { 131 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 132 | return default; 133 | } 134 | return await Gateway.Get(endpoint, args); 135 | } 136 | public static void Mount(string endpoint, Binding binding, Delegate @delegate) 137 | { 138 | if (!Initialized) 139 | { 140 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 141 | return; 142 | } 143 | Gateway.Mount(endpoint, binding, @delegate); 144 | } 145 | public static void Unmount(string endpoint) 146 | { 147 | if (!Initialized) 148 | { 149 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 150 | return; 151 | } 152 | Gateway.Unmount(endpoint); 153 | } 154 | 155 | } 156 | } -------------------------------------------------------------------------------- /src/FxEvents.Client/EventSystem/ClientGateway.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared; 2 | using FxEvents.Shared.Diagnostics; 3 | using FxEvents.Shared.Encryption; 4 | using FxEvents.Shared.EventSubsystem; 5 | using FxEvents.Shared.Message; 6 | using FxEvents.Shared.Serialization; 7 | using FxEvents.Shared.Serialization.Implementations; 8 | using FxEvents.Shared.Snowflakes; 9 | using System; 10 | using System.Net.Sockets; 11 | using System.Threading.Tasks; 12 | 13 | namespace FxEvents.EventSystem 14 | { 15 | internal class ClientGateway : BaseGateway 16 | { 17 | protected override ISerialization Serialization { get; } 18 | 19 | private EventHub _hub => EventHub.Instance; 20 | private Curve25519 _curve25519; 21 | private byte[] _secret = []; 22 | 23 | 24 | public ClientGateway() 25 | { 26 | SnowflakeGenerator.Create((short)new Random().Next(1, 199)); 27 | _curve25519 = Curve25519.Create(); 28 | Serialization = new MsgPackSerialization(); 29 | DelayDelegate = async delay => await BaseScript.Delay(delay); 30 | PrepareDelegate = PrepareAsync; 31 | PushDelegate = Push; 32 | PushDelegateLatent = PushLatent; 33 | } 34 | 35 | internal void AddEvents() 36 | { 37 | _hub.AddEventHandler(InboundPipeline, new Action(async (endpoint, binding, encrypted) => 38 | { 39 | try 40 | { 41 | await ProcessInboundAsync(new ServerId().Handle, endpoint, binding, encrypted); 42 | } 43 | catch (Exception ex) 44 | { 45 | EventMessage message = encrypted.DecryptObject(); 46 | Logger.Error($"InboundPipeline [{message.Endpoint}]:" + ex.ToString()); 47 | } 48 | })); 49 | 50 | _hub.AddEventHandler(OutboundPipeline, new Action((endpoint, binding, serialized) => 51 | { 52 | try 53 | { 54 | ProcessReply(serialized); 55 | } 56 | catch (Exception ex) 57 | { 58 | Logger.Error("OutboundPipeline:" + ex.ToString()); 59 | } 60 | })); 61 | 62 | _hub.AddEventHandler(SignaturePipeline, new Action(signature => _secret = _curve25519.GetSharedSecret(signature))); 63 | BaseScript.TriggerServerEvent(SignaturePipeline, _curve25519.GetPublicKey()); 64 | } 65 | 66 | internal async Task PrepareAsync(string pipeline, int source, IMessage message) 67 | { 68 | if (_secret.Length == 0) 69 | { 70 | StopwatchUtil stopwatch = StopwatchUtil.StartNew(); 71 | while (_secret.Length == 0) await BaseScript.Delay(0); 72 | if (EventHub.Debug) 73 | { 74 | Logger.Debug($"[{message}] Halted {stopwatch.Elapsed.TotalMilliseconds}ms due to signature retrieval."); 75 | } 76 | } 77 | } 78 | 79 | internal void Push(string pipeline, int source, string endpoint, Binding binding, byte[] buffer) 80 | { 81 | if(binding == Binding.All || binding == Binding.Remote) 82 | { 83 | if(binding != Binding.Remote) 84 | if (source != -1) throw new Exception($"The client can only target server events. (arg {nameof(source)} is not matching -1)"); 85 | BaseScript.TriggerServerEvent(pipeline, endpoint, binding, buffer); 86 | } 87 | else if (binding == Binding.All || binding == Binding.Local) 88 | { 89 | BaseScript.TriggerEvent(pipeline, endpoint, binding, buffer); 90 | } 91 | } 92 | 93 | internal void PushLatent(string pipeline, int source, int bytePerSecond, string endpoint, byte[] buffer) 94 | { 95 | if (source != -1) throw new Exception($"The client can only target server events. (arg {nameof(source)} is not matching -1)"); 96 | BaseScript.TriggerLatentServerEvent(pipeline, bytePerSecond, endpoint, Binding.Remote, buffer); 97 | } 98 | 99 | public async void Send(string endpoint, Binding binding, params object[] args) 100 | { 101 | await CreateAndSendAsync(EventFlowType.Straight, new ServerId().Handle, endpoint, binding, args); 102 | } 103 | 104 | public async void SendLatent(string endpoint, int bytePerSecond, params object[] args) 105 | { 106 | await CreateAndSendLatentAsync(EventFlowType.Straight, new ServerId().Handle, endpoint, bytePerSecond, args); 107 | } 108 | 109 | public async Task Get(string endpoint, params object[] args) 110 | { 111 | return await GetInternal(new ServerId().Handle, endpoint, Binding.Remote, args); 112 | } 113 | 114 | public async Task GetLocal(string endpoint, params object[] args) 115 | { 116 | return await GetInternal(new ServerId().Handle, endpoint, Binding.Local, args); 117 | } 118 | 119 | internal byte[] GetSecret(int _) 120 | { 121 | if (_secret == null) 122 | throw new Exception("Shared Encryption Secret has not been generated yet"); 123 | return _secret; 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /src/FxEvents.Client/FxEvents.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Release RedM;Release FiveM;Debug RedM;Debug FiveM 4 | FxEvents FiveM Client 5 | FxEvents RedM Client 6 | FxEvents an advanced event subsystem for FiveM C# Resources. 7 | FxEvents an advanced event subsystem for RedM C# Resources. 8 | FiveM FxEvents 9 | RedM FxEvents 10 | FxEvents.FiveM.Client 11 | FxEvents.RedM.Client 12 | Debug FiveM 13 | AnyCPU 14 | {4F57E7A2-97B7-499B-8F9A-57F0AF74C9AC} 15 | Library 16 | Properties 17 | FxEvents.Client 18 | FxEvents.Client 19 | v4.5.2 20 | 512 21 | True 22 | net452 23 | latest 24 | ..\..\CompiledLibs\Client 25 | FxEvents.Client 26 | https://github.com/manups4e/FxEvents 27 | Copyright Leonardo Emanuele 28 | README.md 29 | embedded 30 | FxEvents.Client 31 | $(WarningsNotAsError);8632;8618;8600;8602;8625 32 | annotations 33 | False 34 | False 35 | 36 | 37 | true 38 | embedded 39 | false 40 | CLIENT 41 | prompt 42 | 4 43 | 44 | 45 | embedded 46 | true 47 | CLIENT 48 | prompt 49 | 4 50 | 51 | 52 | 53 | ..\..\FiveMMsgPack\MsgPack.dll 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | $(PkgNewtonsoft_Json)\lib\portable-net40+sl5+win8+wp8+wpa81\Newtonsoft.Json.dll 64 | False 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | True 74 | \ 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/FxEvents.Server/.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*.cs] 3 | #### Stili di denominazione #### 4 | 5 | # Regole di denominazione 6 | 7 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 8 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 9 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 10 | 11 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 12 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 13 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 14 | 15 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 16 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 17 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 18 | 19 | # Specifiche dei simboli 20 | 21 | dotnet_naming_symbols.interface.applicable_kinds = interface 22 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 23 | dotnet_naming_symbols.interface.required_modifiers = 24 | 25 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 26 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 27 | dotnet_naming_symbols.types.required_modifiers = 28 | 29 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 30 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 31 | dotnet_naming_symbols.non_field_members.required_modifiers = 32 | 33 | # Stili di denominazione 34 | 35 | dotnet_naming_style.begins_with_i.required_prefix = I 36 | dotnet_naming_style.begins_with_i.required_suffix = 37 | dotnet_naming_style.begins_with_i.word_separator = 38 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 39 | 40 | dotnet_naming_style.pascal_case.required_prefix = 41 | dotnet_naming_style.pascal_case.required_suffix = 42 | dotnet_naming_style.pascal_case.word_separator = 43 | dotnet_naming_style.pascal_case.capitalization = pascal_case 44 | 45 | dotnet_naming_style.pascal_case.required_prefix = 46 | dotnet_naming_style.pascal_case.required_suffix = 47 | dotnet_naming_style.pascal_case.word_separator = 48 | dotnet_naming_style.pascal_case.capitalization = pascal_case 49 | csharp_indent_labels = one_less_than_current 50 | csharp_style_expression_bodied_methods = false:silent 51 | csharp_style_expression_bodied_constructors = false:silent 52 | csharp_style_expression_bodied_operators = false:silent 53 | csharp_style_expression_bodied_properties = true:silent 54 | csharp_style_expression_bodied_indexers = true:silent 55 | csharp_style_expression_bodied_accessors = true:silent 56 | csharp_style_expression_bodied_lambdas = true:silent 57 | csharp_style_expression_bodied_local_functions = false:silent 58 | csharp_style_conditional_delegate_call = true:suggestion 59 | csharp_space_around_binary_operators = before_and_after 60 | 61 | [*.vb] 62 | #### Stili di denominazione #### 63 | 64 | # Regole di denominazione 65 | 66 | dotnet_naming_rule.interface_should_be_inizia_con_i.severity = suggestion 67 | dotnet_naming_rule.interface_should_be_inizia_con_i.symbols = interface 68 | dotnet_naming_rule.interface_should_be_inizia_con_i.style = inizia_con_i 69 | 70 | dotnet_naming_rule.tipi_should_be_notazione_pascal.severity = suggestion 71 | dotnet_naming_rule.tipi_should_be_notazione_pascal.symbols = tipi 72 | dotnet_naming_rule.tipi_should_be_notazione_pascal.style = notazione_pascal 73 | 74 | dotnet_naming_rule.membri_non_di_campo_should_be_notazione_pascal.severity = suggestion 75 | dotnet_naming_rule.membri_non_di_campo_should_be_notazione_pascal.symbols = membri_non_di_campo 76 | dotnet_naming_rule.membri_non_di_campo_should_be_notazione_pascal.style = notazione_pascal 77 | 78 | # Specifiche dei simboli 79 | 80 | dotnet_naming_symbols.interface.applicable_kinds = interface 81 | dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 82 | dotnet_naming_symbols.interface.required_modifiers = 83 | 84 | dotnet_naming_symbols.tipi.applicable_kinds = class, struct, interface, enum 85 | dotnet_naming_symbols.tipi.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 86 | dotnet_naming_symbols.tipi.required_modifiers = 87 | 88 | dotnet_naming_symbols.membri_non_di_campo.applicable_kinds = property, event, method 89 | dotnet_naming_symbols.membri_non_di_campo.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 90 | dotnet_naming_symbols.membri_non_di_campo.required_modifiers = 91 | 92 | # Stili di denominazione 93 | 94 | dotnet_naming_style.inizia_con_i.required_prefix = I 95 | dotnet_naming_style.inizia_con_i.required_suffix = 96 | dotnet_naming_style.inizia_con_i.word_separator = 97 | dotnet_naming_style.inizia_con_i.capitalization = pascal_case 98 | 99 | dotnet_naming_style.notazione_pascal.required_prefix = 100 | dotnet_naming_style.notazione_pascal.required_suffix = 101 | dotnet_naming_style.notazione_pascal.word_separator = 102 | dotnet_naming_style.notazione_pascal.capitalization = pascal_case 103 | 104 | dotnet_naming_style.notazione_pascal.required_prefix = 105 | dotnet_naming_style.notazione_pascal.required_suffix = 106 | dotnet_naming_style.notazione_pascal.word_separator = 107 | dotnet_naming_style.notazione_pascal.capitalization = pascal_case 108 | 109 | [*.{cs,vb}] 110 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 111 | end_of_line = crlf 112 | dotnet_style_coalesce_expression = true:suggestion 113 | dotnet_style_null_propagation = true:suggestion 114 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 115 | dotnet_style_prefer_auto_properties = true:silent 116 | dotnet_style_object_initializer = true:suggestion 117 | dotnet_style_collection_initializer = true:suggestion 118 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 119 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 120 | dotnet_style_prefer_conditional_expression_over_return = true:silent 121 | dotnet_style_explicit_tuple_names = true:suggestion 122 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 123 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 124 | tab_width = 4 125 | indent_size = 4 -------------------------------------------------------------------------------- /src/FxEvents.Server/EventDispatcher.cs: -------------------------------------------------------------------------------- 1 | global using CitizenFX.Core; 2 | global using CitizenFX.Core.Native; 3 | using FxEvents.EventSystem; 4 | using FxEvents.Shared; 5 | using FxEvents.Shared.Encryption; 6 | using FxEvents.Shared.EventSubsystem; 7 | 8 | using Logger; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Linq.Expressions; 13 | using System.Reflection; 14 | using System.Threading.Tasks; 15 | 16 | namespace FxEvents 17 | { 18 | [Obsolete("Use EventHub instead, this class will be removed soon")] 19 | public class EventDispatcher 20 | { 21 | internal static Log Logger { get; set; } 22 | internal ExportDictionary GetExports => EventHub.Instance.GetExports; 23 | internal PlayerList GetPlayers => EventHub.Instance.GetPlayers; 24 | internal static ServerGateway Gateway => EventHub.Gateway; 25 | internal static bool Debug => EventHub.Debug; 26 | internal static bool Initialized = EventHub.Initialized; 27 | 28 | public static EventsDictionary Events => EventHub.Gateway._handlers; 29 | 30 | public static void Initalize(string inboundEvent, string outboundEvent, string signatureEvent) 31 | { 32 | EventHub.Initialize(); 33 | } 34 | 35 | internal async void RegisterEvent(string eventName, Delegate action) 36 | { 37 | EventHub.Instance.RegisterEvent(eventName, action); 38 | } 39 | 40 | public static void Send(Player player, string endpoint, params object[] args) 41 | { 42 | EventHub.Send(player, endpoint, args); 43 | } 44 | public static void Send(ISource client, string endpoint, params object[] args) 45 | { 46 | EventHub.Send(client, endpoint, args); 47 | } 48 | public static void Send(IEnumerable players, string endpoint, params object[] args) 49 | { 50 | EventHub.Send(players, endpoint, args); 51 | } 52 | 53 | public static void Send(string endpoint, params object[] args) 54 | { 55 | EventHub.Send(endpoint, args); 56 | } 57 | 58 | public static void Send(IEnumerable clients, string endpoint, params object[] args) 59 | { 60 | EventHub.Send(clients, endpoint, args); 61 | } 62 | 63 | public static void SendLatent(Player player, string endpoint, int bytesPerSeconds, params object[] args) 64 | { 65 | EventHub.SendLatent(player, endpoint, bytesPerSeconds, args); 66 | } 67 | 68 | public static void SendLatent(ISource client, string endpoint, int bytesPerSeconds, params object[] args) 69 | { 70 | EventHub.SendLatent(client, endpoint, bytesPerSeconds, args); 71 | } 72 | 73 | public static void SendLatent(IEnumerable players, string endpoint, int bytesPerSeconds, params object[] args) 74 | { 75 | EventHub.SendLatent(players, endpoint, bytesPerSeconds, args); 76 | } 77 | 78 | public static void SendLatent(string endpoint, int bytesPerSeconds, params object[] args) 79 | { 80 | EventHub.SendLatent(endpoint, bytesPerSeconds, args); 81 | } 82 | 83 | public static void SendLatent(IEnumerable clients, string endpoint, int bytesPerSeconds, params object[] args) 84 | { 85 | EventHub.SendLatent(clients, endpoint, bytesPerSeconds, args); 86 | } 87 | 88 | public static async Task Get(Player player, string endpoint, params object[] args) 89 | { 90 | return await EventHub.Get(player, endpoint, args); 91 | } 92 | 93 | public static async Task Get(ISource client, string endpoint, params object[] args) 94 | { 95 | return await EventHub.Get(client, endpoint, args); 96 | } 97 | 98 | public static void Mount(string endpoint, Delegate @delegate) 99 | { 100 | EventHub.Mount(endpoint, Binding.Remote, @delegate); 101 | } 102 | public static void Unmount(string endpoint) 103 | { 104 | EventHub.Unmount(endpoint); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /src/FxEvents.Server/EventHub.cs: -------------------------------------------------------------------------------- 1 | global using CitizenFX.Core; 2 | global using CitizenFX.Core.Native; 3 | using FxEvents.EventSystem; 4 | using FxEvents.Shared; 5 | using FxEvents.Shared.Encryption; 6 | using FxEvents.Shared.EventSubsystem; 7 | 8 | using Logger; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Linq.Expressions; 13 | using System.Reflection; 14 | using System.Threading.Tasks; 15 | 16 | namespace FxEvents 17 | { 18 | public class EventHub : ServerScript 19 | { 20 | internal static Log Logger { get; set; } 21 | internal ExportDictionary GetExports => Exports; 22 | internal PlayerList GetPlayers => Players; 23 | internal static ServerGateway Gateway { get; set; } 24 | internal static bool Debug { get; set; } 25 | internal static bool Initialized = false; 26 | internal static EventHub Instance; 27 | 28 | public static EventsDictionary Events => Gateway._handlers; 29 | 30 | public EventHub() 31 | { 32 | Logger = new Log(); 33 | Instance = this; 34 | var resName = API.GetCurrentResourceName(); 35 | string debugMode = API.GetResourceMetadata(resName, "fxevents_debug_mode", 0); 36 | Debug = debugMode == "yes" || debugMode == "true" || int.TryParse(debugMode, out int num) && num > 0; 37 | API.RegisterCommand("generatekey", new Action, string>(async (a, b, c) => 38 | { 39 | if (a != 0) return; 40 | Logger.Info("Generating random passfrase with a 50 words dictionary..."); 41 | Tuple ret = await Encryption.GenerateKey(); 42 | string print = $"Here is your generated encryption key, save it in a safe place.\nThis key is not saved by FXEvents anywhere, so please store it somewhere safe, if you save encrypted data and loose this key, your data will be lost.\n" + 43 | $"You can always generate new keys by using \"generatekey\" command.\n" + 44 | $"Passfrase: {ret.Item1}\nEncrypted Passfrase: {ret.Item2}"; 45 | Logger.Info(print); 46 | }), false); 47 | byte[] inbound = Encryption.GenerateHash(resName + "_inbound"); 48 | byte[] outbound = Encryption.GenerateHash(resName + "_outbound"); 49 | byte[] signature = Encryption.GenerateHash(resName + "_signature"); 50 | Gateway = new ServerGateway(); 51 | Gateway.SignaturePipeline = signature.BytesToString(); 52 | Gateway.InboundPipeline = inbound.BytesToString(); 53 | Gateway.OutboundPipeline = outbound.BytesToString(); 54 | EventHandlers.Add("playerJoining", new Action(OnPlayerDropped)); 55 | EventHandlers.Add("playerDropped", new Action(OnPlayerDropped)); 56 | } 57 | 58 | public static void Initialize() 59 | { 60 | Initialized = true; 61 | Gateway.AddEvents(); 62 | 63 | var assembly = Assembly.GetCallingAssembly(); 64 | List withReturnType = new List(); 65 | foreach (var type in assembly.GetTypes()) 66 | { 67 | var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Where(m => m.GetCustomAttributes(typeof(FxEventAttribute), false).Length > 0); 68 | 69 | foreach (var method in methods) 70 | { 71 | var parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); 72 | var actionType = Expression.GetDelegateType(parameters.Concat(new[] { method.ReturnType }).ToArray()); 73 | var attribute = method.GetCustomAttribute(); 74 | 75 | if (method.ReturnType != typeof(void)) 76 | { 77 | if (withReturnType.Contains(attribute.Name)) 78 | { 79 | // throw error and break execution for the script sake. 80 | throw new Exception($"FxEvents - Failed registering [{attribute.Name}] delegates. Cannot register more than 1 delegate for [{attribute.Name}] with a return type!"); 81 | } 82 | else 83 | { 84 | withReturnType.Add(attribute.Name); 85 | } 86 | } 87 | 88 | if (method.IsStatic) 89 | { 90 | Mount(attribute.Name, attribute.Binding, Delegate.CreateDelegate(actionType, method)); 91 | } 92 | else 93 | Logger.Error($"Error registering method {method.Name} - FxEvents supports only Static methods for its [FxEvent] attribute!"); 94 | } 95 | } 96 | } 97 | 98 | /// 99 | /// Register an event (TriggerEvent) 100 | /// 101 | /// Event name 102 | /// Action bound to the event 103 | internal async void RegisterEvent(string eventName, Delegate action) 104 | { 105 | while (!Initialized) await BaseScript.Delay(0); 106 | EventHandlers[eventName] += action; 107 | } 108 | 109 | public static void Send(Player player, string endpoint, params object[] args) 110 | { 111 | if (!Initialized) 112 | { 113 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 114 | return; 115 | } 116 | Gateway.Send(player, endpoint, args); 117 | } 118 | public static void Send(ISource client, string endpoint, params object[] args) 119 | { 120 | if (!Initialized) 121 | { 122 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 123 | return; 124 | } 125 | Gateway.Send(client, endpoint, args); 126 | } 127 | public static void Send(IEnumerable players, string endpoint, params object[] args) 128 | { 129 | if (!Initialized) 130 | { 131 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 132 | return; 133 | } 134 | Gateway.Send(players.ToList(), endpoint, args); 135 | } 136 | 137 | public static void Send(string endpoint, params object[] args) 138 | { 139 | if (!Initialized) 140 | { 141 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 142 | return; 143 | } 144 | 145 | PlayerList playerList = Instance.GetPlayers; 146 | Gateway.Send(playerList.ToList(), endpoint, args); 147 | } 148 | 149 | public static void Send(IEnumerable clients, string endpoint, params object[] args) 150 | { 151 | if (!Initialized) 152 | { 153 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 154 | return; 155 | } 156 | Gateway.Send(clients.ToList(), endpoint, args); 157 | } 158 | 159 | public static void SendLocal(string endpoint, params object[] args) 160 | { 161 | if (!Initialized) 162 | { 163 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 164 | return; 165 | } 166 | Gateway.Send(endpoint, args); 167 | } 168 | 169 | public static void SendLatent(Player player, string endpoint, int bytesPerSeconds, params object[] args) 170 | { 171 | if (!Initialized) 172 | { 173 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 174 | return; 175 | } 176 | Gateway.SendLatent(Convert.ToInt32(player.Handle), endpoint, bytesPerSeconds, args); 177 | } 178 | 179 | public static void SendLatent(ISource client, string endpoint, int bytesPerSeconds, params object[] args) 180 | { 181 | if (!Initialized) 182 | { 183 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 184 | return; 185 | } 186 | Gateway.SendLatent(client.Handle, endpoint, bytesPerSeconds, args); 187 | } 188 | 189 | public static void SendLatent(IEnumerable players, string endpoint, int bytesPerSeconds, params object[] args) 190 | { 191 | if (!Initialized) 192 | { 193 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 194 | return; 195 | } 196 | Gateway.SendLatent(players.Select(x => Convert.ToInt32(x.Handle)).ToList(), endpoint, bytesPerSeconds, args); 197 | } 198 | 199 | public static void SendLatent(string endpoint, int bytesPerSeconds, params object[] args) 200 | { 201 | if (!Initialized) 202 | { 203 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 204 | return; 205 | } 206 | PlayerList playerList = Instance.GetPlayers; 207 | Gateway.SendLatent(playerList.Select(x => Convert.ToInt32(x.Handle)).ToList(), endpoint, bytesPerSeconds, args); 208 | } 209 | 210 | public static void SendLatent(IEnumerable clients, string endpoint, int bytesPerSeconds, params object[] args) 211 | { 212 | if (!Initialized) 213 | { 214 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 215 | return; 216 | } 217 | Gateway.SendLatent(clients.Select(x => x.Handle).ToList(), endpoint, bytesPerSeconds, args); 218 | } 219 | 220 | public static async Task Get(Player player, string endpoint, params object[] args) 221 | { 222 | if (!Initialized) 223 | { 224 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 225 | return default; 226 | } 227 | return await Gateway.Get(Convert.ToInt32(player.Handle), endpoint, args); 228 | } 229 | 230 | public static async Task Get(ISource client, string endpoint, params object[] args) 231 | { 232 | if (!Initialized) 233 | { 234 | Logger.Error("Dispatcher not initialized, please initialize it and add the events strings"); 235 | return default; 236 | } 237 | return await Gateway.Get(client.Handle, endpoint, args); 238 | } 239 | 240 | public static void Mount(string endpoint, Binding binding, Delegate @delegate) 241 | { 242 | Gateway.Mount(endpoint, binding, @delegate); 243 | } 244 | public static void Unmount(string endpoint) 245 | { 246 | Gateway.Unmount(endpoint); 247 | } 248 | 249 | private void OnPlayerDropped([FromSource] Player player) 250 | { 251 | if (Gateway._signatures.ContainsKey(int.Parse(player.Handle))) 252 | Gateway._signatures.Remove(int.Parse(player.Handle)); 253 | } 254 | } 255 | } -------------------------------------------------------------------------------- /src/FxEvents.Server/EventSystem/ServerGateway.cs: -------------------------------------------------------------------------------- 1 | using CitizenFX.Core; 2 | using FxEvents.Shared; 3 | using FxEvents.Shared.Diagnostics; 4 | using FxEvents.Shared.Encryption; 5 | using FxEvents.Shared.EventSubsystem; 6 | using FxEvents.Shared.Message; 7 | using FxEvents.Shared.Serialization; 8 | using FxEvents.Shared.Serialization.Implementations; 9 | using FxEvents.Shared.Snowflakes; 10 | using FxEvents.Shared.TypeExtensions; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Net; 15 | using System.Security.Cryptography; 16 | using System.Threading.Tasks; 17 | 18 | namespace FxEvents.EventSystem 19 | { 20 | public class ServerGateway : BaseGateway 21 | { 22 | protected override ISerialization Serialization { get; } 23 | internal Dictionary _signatures; 24 | 25 | private EventHub _hub => EventHub.Instance; 26 | 27 | public ServerGateway() 28 | { 29 | SnowflakeGenerator.Create((short)new Random().Next(200, 399)); 30 | Serialization = new MsgPackSerialization(); 31 | DelayDelegate = async delay => await BaseScript.Delay(delay); 32 | PrepareDelegate = PrepareAsync; 33 | PushDelegate = Push; 34 | PushDelegateLatent = PushLatent; 35 | _signatures = new(); 36 | } 37 | 38 | internal void AddEvents() 39 | { 40 | _hub.RegisterEvent(SignaturePipeline, new Action(GetSignature)); 41 | _hub.RegisterEvent(InboundPipeline, new Action(Inbound)); 42 | _hub.RegisterEvent(OutboundPipeline, new Action(Outbound)); 43 | } 44 | 45 | internal void Push(string pipeline, int source, string endpoint, Binding binding, byte[] buffer) 46 | { 47 | if (binding == Binding.All || binding == Binding.Remote) 48 | { 49 | if (source != new ServerId().Handle) 50 | BaseScript.TriggerClientEvent(_hub.GetPlayers[source], pipeline, endpoint, binding, buffer); 51 | else 52 | BaseScript.TriggerClientEvent(pipeline, endpoint, binding, buffer); 53 | } 54 | else if (binding == Binding.All || binding == Binding.Local) 55 | { 56 | BaseScript.TriggerEvent(pipeline, endpoint, binding, buffer); 57 | } 58 | } 59 | 60 | internal void PushLatent(string pipeline, int source, int bytePerSecond, string endpoint, byte[] buffer) 61 | { 62 | if (source != new ServerId().Handle) 63 | BaseScript.TriggerLatentClientEvent(_hub.GetPlayers[source], pipeline, bytePerSecond, endpoint, Binding.Remote, buffer); 64 | else 65 | BaseScript.TriggerLatentClientEvent(pipeline, bytePerSecond, endpoint, Binding.Remote, buffer); 66 | } 67 | 68 | private void GetSignature([FromSource] string source, byte[] clientPubKey) 69 | { 70 | try 71 | { 72 | int client = int.Parse(source.Replace("net:", string.Empty)); 73 | if (_signatures.ContainsKey(client)) 74 | { 75 | Logger.Warning($"Client {API.GetPlayerName("" + client)}[{client}] tried acquiring event signature more than once."); 76 | BaseScript.TriggerEvent("fxevents:tamperingprotection", source, "signature retrival", TamperType.REQUESTED_NEW_PUBLIC_KEY); 77 | return; 78 | } 79 | 80 | Curve25519 curve25519 = Curve25519.Create(); 81 | byte[] secret = curve25519.GetSharedSecret(clientPubKey); 82 | 83 | _signatures.Add(client, secret); 84 | 85 | BaseScript.TriggerClientEvent(_hub.GetPlayers[client], SignaturePipeline, curve25519.GetPublicKey()); 86 | } 87 | catch (Exception ex) 88 | { 89 | Logger.Error(ex.ToString()); 90 | } 91 | } 92 | 93 | private async void Inbound([FromSource] string source, string endpoint, Binding binding, byte[] encrypted) 94 | { 95 | try 96 | { 97 | int client = -1; 98 | if(source != null) 99 | { 100 | client = int.Parse(source.Replace("net:", string.Empty)); 101 | 102 | if (!_signatures.TryGetValue(client, out byte[] signature)) 103 | return; 104 | } 105 | 106 | try 107 | { 108 | await ProcessInboundAsync(client, endpoint, binding, encrypted); 109 | } 110 | catch (TimeoutException) 111 | { 112 | API.DropPlayer(client.ToString(), $"Operation timed out: {endpoint.ToBase64()}"); 113 | } 114 | } 115 | catch (Exception ex) 116 | { 117 | Logger.Error(ex.ToString()); 118 | } 119 | } 120 | 121 | private void Outbound([FromSource] string source, string endpoint, Binding binding, byte[] encrypted) 122 | { 123 | try 124 | { 125 | int client = int.Parse(source.Replace("net:", string.Empty)); 126 | 127 | if (!_signatures.TryGetValue(client, out byte[] signature)) return; 128 | 129 | EventResponseMessage response = encrypted.DecryptObject(client); 130 | 131 | ProcessReply(response); 132 | } 133 | catch (Exception ex) 134 | { 135 | Logger.Error(ex.ToString()); 136 | } 137 | } 138 | 139 | public void Send(Player player, string endpoint, params object[] args) => Send(Convert.ToInt32(player.Handle), endpoint, Binding.Remote, args); 140 | public void Send(ISource client, string endpoint, params object[] args) => Send(client.Handle, endpoint, Binding.Remote, args); 141 | public void Send(List players, string endpoint, params object[] args) => Send(players.Select(x => int.Parse(x.Handle)).ToList(), endpoint, Binding.Remote, args); 142 | public void Send(List clients, string endpoint, params object[] args) => Send(clients.Select(x => x.Handle).ToList(), endpoint, Binding.Remote, args); 143 | public void Send(string endpoint, params object[] args) => Send([], endpoint, Binding.Local, args); 144 | 145 | public async void Send(List targets, string endpoint, Binding binding, params object[] args) 146 | { 147 | if (binding == Binding.Remote) 148 | { 149 | int i = 0; 150 | while (i < targets.Count) 151 | { 152 | await BaseScript.Delay(0); 153 | Send(targets[i], endpoint, binding, args); 154 | i++; 155 | } 156 | } 157 | else if (binding == Binding.Local) 158 | { 159 | Send(-1, endpoint, binding, args); 160 | } 161 | } 162 | 163 | public async void Send(int target, string endpoint, Binding binding, params object[] args) 164 | { 165 | if (!string.IsNullOrWhiteSpace(EventHub.Instance.GetPlayers[target].Name) || (binding == Binding.Local)) 166 | await CreateAndSendAsync(EventFlowType.Straight, target, endpoint, binding, args); 167 | } 168 | 169 | public void SendLatent(Player player, string endpoint, int bytesxSecond, params object[] args) => SendLatent(Convert.ToInt32(player.Handle), endpoint, bytesxSecond, args); 170 | public void SendLatent(ISource client, string endpoint, int bytesxSecond, params object[] args) => SendLatent(client.Handle, endpoint, bytesxSecond, args); 171 | public void SendLatent(List players, string endpoint, int bytesxSecond, params object[] args) => SendLatent(players.Select(x => Convert.ToInt32(x.Handle)).ToList(), endpoint, bytesxSecond, args); 172 | public void SendLatent(List clients, string endpoint, int bytesxSecond, params object[] args) => SendLatent(clients.Select(x => x.Handle).ToList(), endpoint, bytesxSecond, args); 173 | 174 | public async void SendLatent(List targets, string endpoint, int bytesxSecond, params object[] args) 175 | { 176 | int i = 0; 177 | while (i < targets.Count) 178 | { 179 | await BaseScript.Delay(0); 180 | SendLatent(targets[i], endpoint, bytesxSecond, args); 181 | i++; 182 | } 183 | } 184 | 185 | public async void SendLatent(int target, string endpoint, int bytesxSecond, params object[] args) 186 | { 187 | if (!string.IsNullOrWhiteSpace(EventHub.Instance.GetPlayers[target].Name)) 188 | await CreateAndSendLatentAsync(EventFlowType.Straight, target, endpoint, bytesxSecond, args); 189 | } 190 | 191 | public Task Get(Player player, string endpoint, params object[] args) => 192 | Get(Convert.ToInt32(player.Handle), endpoint, args); 193 | 194 | public Task Get(ISource client, string endpoint, params object[] args) => 195 | Get(client.Handle, endpoint, args); 196 | 197 | public async Task Get(int target, string endpoint, params object[] args) 198 | { 199 | return await GetInternal(target, endpoint, Binding.Remote, args); 200 | } 201 | 202 | public async Task GetLocal(string endpoint, params object[] args) 203 | { 204 | return await GetInternal(-1, endpoint, Binding.Local, args); 205 | } 206 | 207 | internal async Task PrepareAsync(string pipeline, int source, IMessage message) 208 | { 209 | if (GetSecret(source).Length == 0) 210 | { 211 | StopwatchUtil stopwatch = StopwatchUtil.StartNew(); 212 | long time = API.GetGameTimer(); 213 | while (GetSecret(source).Length == 0) 214 | { 215 | if (API.GetGameTimer() - time > 1000) 216 | { 217 | if (EventHub.Debug) 218 | { 219 | Logger.Debug($"[{message}] Took to much time: {stopwatch.Elapsed.TotalMilliseconds}ms due to signature retrieval, client not found, still connecting or disconnected."); 220 | } 221 | return; 222 | } 223 | await BaseScript.Delay(0); 224 | } 225 | if (EventHub.Debug) 226 | { 227 | Logger.Debug($"[{message}] Halted {stopwatch.Elapsed.TotalMilliseconds}ms due to signature retrieval."); 228 | } 229 | } 230 | } 231 | 232 | 233 | internal byte[] GetSecret(int source) 234 | { 235 | if (!_signatures.ContainsKey(source)) { 236 | Curve25519 curve25519 = Curve25519.Create(); 237 | byte[] secret = curve25519.GetSharedSecret(curve25519.GetPublicKey()); 238 | return secret; 239 | } 240 | return _signatures[source]; 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /src/FxEvents.Server/FxEvents.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | FxEvents Server 5 | FxEvents an advanced event subsystem for FiveM C# Resources. 6 | README.md 7 | FxEvents.Server 8 | FxEvents.Server 9 | https://github.com/manups4e/FxEvents 10 | https://github.com/manups4e/FxEvents 11 | Copyright Leonardo Emanuele 12 | netstandard2.0 13 | latest 14 | FxEvents.Server 15 | FxEvents.Server 16 | ..\..\CompiledLibs\Server 17 | annotations 18 | Debug;Release 19 | False 20 | False 21 | 22 | 23 | 24 | x64 25 | SERVER 26 | portable 27 | 28 | 29 | 30 | SERVER 31 | portable 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ..\..\FiveMMsgPack\MsgPack.dll 40 | 41 | 42 | $(PkgNewtonsoft_Json)\lib\portable-net40+sl5+win8+wp8+wpa81\Newtonsoft.Json.dll 43 | False 44 | 45 | 46 | 47 | 48 | True 49 | \ 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/BinaryHelper.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.Serialization; 2 | using FxEvents.Shared.Serialization.Implementations; 3 | using System; 4 | using System.Globalization; 5 | using System.Linq; 6 | 7 | namespace FxEvents.Shared 8 | { 9 | public static class BinaryHelper 10 | { 11 | private static MsgPackSerialization msgpackSerialization = new(); 12 | 13 | public static byte[] ToBytes(this T obj) 14 | { 15 | using SerializationContext context = new("BinaryHelper", "ToBytes", msgpackSerialization); 16 | context.Serialize(typeof(T), obj); 17 | return context.GetData(); 18 | } 19 | public static byte[] StringToBytes(this string str) 20 | { 21 | char[] arr = str.ToCharArray(); 22 | if (arr[2] != '-' && arr[5] != '-') 23 | return Enumerable.Range(0, str.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(str.Substring(x, 2), 16)).ToArray(); 24 | return str.Split('-').Select(x => byte.Parse(x, NumberStyles.HexNumber)).ToArray(); 25 | } 26 | 27 | public static string BytesToString(this byte[] ba, bool separator = false, bool toLower = true) 28 | { 29 | string bytes; 30 | if (separator) 31 | bytes = BitConverter.ToString(ba); 32 | else 33 | bytes = BitConverter.ToString(ba).Replace("-", ""); 34 | 35 | if (toLower) 36 | bytes = bytes.ToLower(); 37 | return bytes; 38 | } 39 | 40 | public static T FromBytes(this byte[] data) 41 | { 42 | using SerializationContext context = new(data.ToString(), "FromBytes", msgpackSerialization, data); 43 | return context.Deserialize(); 44 | } 45 | 46 | public static ulong ToUInt64(this byte[] bytes) 47 | { 48 | if (bytes == null) 49 | throw new ArgumentNullException(nameof(bytes)); 50 | if (bytes.Length > sizeof(ulong)) 51 | throw new ArgumentException("Must be 8 elements or fewer", nameof(bytes)); 52 | 53 | ulong result = 0; 54 | for (int i = 0; i < bytes.Length; i++) 55 | { 56 | result |= (ulong)bytes[i] << (i * 8); 57 | } 58 | return result; 59 | } 60 | 61 | public static byte[] FromUInt64(this ulong num) 62 | { 63 | byte[] buffer = new byte[sizeof(ulong)]; 64 | for (int i = 0; i < sizeof(ulong); i++) 65 | { 66 | buffer[i] = (byte)(num >> (i * 8)); 67 | } 68 | return buffer; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/ContractResolvers.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Serialization; 2 | using System; 3 | 4 | namespace FxEvents.Shared 5 | { 6 | public class ContractResolver : DefaultContractResolver 7 | { 8 | protected override JsonContract CreateContract(Type objectType) 9 | { 10 | JsonContract contract = base.CreateContract(objectType); 11 | 12 | if (objectType.IsAbstract || objectType.IsInterface) 13 | { 14 | Type substitute = JsonHelper.Substitutes.TryGetValue(objectType, out Type result) ? result : null; 15 | 16 | if (substitute != null) 17 | { 18 | contract.Converter = new TypeConverter(substitute); 19 | } 20 | } 21 | 22 | return contract; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Encryption/Curve25519.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Encryption 4 | { 5 | /// 6 | /// Elliptic curve Curve25519 with Diffie Hellman key exchange scheme. 7 | /// 8 | public class Curve25519 9 | { 10 | /// 11 | /// Elliptic curve Curve25519 with Diffie Hellman key exchange scheme. 12 | /// 13 | public const string Curve25519Sha256 = "curve25519-sha256"; 14 | 15 | /// 16 | /// Creates a new instance of class. 17 | /// 18 | /// Algorithm name. Only is supported. 19 | public static Curve25519 Create(string algorithmName = Curve25519Sha256) 20 | { 21 | if (Curve25519Sha256 == algorithmName) 22 | return new Curve25519(); 23 | 24 | return null; 25 | } 26 | 27 | private byte[] _privateKey; 28 | 29 | /// 30 | /// Gets algorithm name. 31 | /// 32 | public string Name 33 | { 34 | get { return Curve25519Sha256; } 35 | } 36 | 37 | private void EnsurePrivateKey() 38 | { 39 | if (_privateKey == null) 40 | _privateKey = Curve25519Inner.CreateRandomPrivateKey(); 41 | } 42 | 43 | /// 44 | /// Returns public key. 45 | /// 46 | public byte[] GetPublicKey() 47 | { 48 | EnsurePrivateKey(); 49 | return Curve25519Inner.GetPublicKey(_privateKey); 50 | } 51 | 52 | /// 53 | /// Returns private key. 54 | /// 55 | public byte[] GetPrivateKey() 56 | { 57 | EnsurePrivateKey(); 58 | return (byte[])_privateKey.Clone(); 59 | } 60 | 61 | /// 62 | /// Initializes the algorithm from public key. 63 | /// 64 | public void FromPublicKey(byte[] publicKey) 65 | { 66 | throw new NotSupportedException(); 67 | } 68 | 69 | /// 70 | /// Initializes the algorithm from private key. 71 | /// 72 | public void FromPrivateKey(byte[] privateKey) 73 | { 74 | if (privateKey == null) 75 | throw new ArgumentNullException("privateKey"); 76 | 77 | _privateKey = (byte[])privateKey.Clone(); 78 | } 79 | 80 | /// 81 | /// Returns shared secret for other party's public key and own private key. 82 | /// 83 | public byte[] GetSharedSecret(byte[] otherPublicKey) 84 | { 85 | if (otherPublicKey == null) 86 | throw new ArgumentNullException("otherPublicKey"); 87 | 88 | EnsurePrivateKey(); 89 | return Curve25519Inner.GetSharedSecret(_privateKey, otherPublicKey); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/Encryption/Encryption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FxEvents.Shared.Encryption 9 | { 10 | public static class Encryption 11 | { 12 | static readonly Random random = new Random(DateTime.Now.Millisecond); 13 | #region Byte encryption 14 | private static byte[] GenerateIV() 15 | { 16 | byte[] rgbIV = new byte[16]; 17 | using (RNGCryptoServiceProvider rng = new()) 18 | rng.GetBytes(rgbIV); 19 | return rgbIV; 20 | } 21 | 22 | private static byte[] EncryptBytes(byte[] data, object input) 23 | { 24 | byte[] rgbIV = GenerateIV(); 25 | byte[] keyBytes = input switch 26 | { 27 | int sourceId => EventHub.Gateway.GetSecret(sourceId), 28 | string strKey => GenerateHash(strKey), 29 | _ => throw new ArgumentException("Input must be an int or a string.", nameof(input)), 30 | }; 31 | using AesManaged aesAlg = new AesManaged 32 | { 33 | Key = keyBytes, 34 | IV = rgbIV 35 | }; 36 | 37 | ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); 38 | 39 | using MemoryStream msEncrypt = new MemoryStream(); 40 | msEncrypt.Write(rgbIV, 0, rgbIV.Length); 41 | using CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write); 42 | csEncrypt.Write(data, 0, data.Length); 43 | csEncrypt.FlushFinalBlock(); 44 | 45 | return msEncrypt.ToArray(); 46 | } 47 | 48 | private static byte[] DecryptBytes(byte[] data, object input) 49 | { 50 | byte[] rgbIV = data.Take(16).ToArray(); // Extract the IV from the beginning of the data 51 | byte[] keyBytes = input switch 52 | { 53 | int sourceId => EventHub.Gateway.GetSecret(sourceId), 54 | string strKey => GenerateHash(strKey), 55 | _ => throw new ArgumentException("Input must be an int or a string.", nameof(input)), 56 | }; 57 | using AesManaged aesAlg = new AesManaged 58 | { 59 | Key = keyBytes, 60 | IV = rgbIV 61 | }; 62 | 63 | ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); 64 | 65 | using MemoryStream msDecrypt = new MemoryStream(data.Skip(16).ToArray()); // Skip the IV part 66 | using CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read); 67 | using MemoryStream msDecrypted = new MemoryStream(); 68 | csDecrypt.CopyTo(msDecrypted); 69 | 70 | return msDecrypted.ToArray(); 71 | } 72 | #endregion 73 | 74 | 75 | internal static byte[] EncryptObject(this T obj, int plySource = -1) 76 | { 77 | return EncryptBytes(obj.ToBytes(), plySource); 78 | } 79 | 80 | internal static T DecryptObject(this byte[] data, int plySource = -1) 81 | { 82 | return DecryptBytes(data, plySource).FromBytes(); 83 | } 84 | 85 | 86 | /// 87 | /// Encrypt the object. 88 | /// 89 | /// 90 | /// The object to encrypt. 91 | /// The string key to encrypt it. 92 | /// 93 | /// An encrypted array of byte 94 | public static byte[] EncryptObject(this T obj, string key) 95 | { 96 | if (string.IsNullOrWhiteSpace(key)) 97 | throw new Exception("FXEvents: Encryption key cannot be empty!"); 98 | return EncryptBytes(obj.ToBytes(), key); 99 | } 100 | 101 | /// 102 | /// Decrypt the object. 103 | /// 104 | /// 105 | /// The data to decrypt. 106 | /// The key to decrypt it (MUST BE THE SAME AS THE ENCRYPTION KEY). 107 | /// 108 | /// A 109 | public static T DecryptObject(this byte[] data, string key) 110 | { 111 | if (string.IsNullOrWhiteSpace(key)) 112 | throw new Exception("FXEvents: Encryption key cannot be empty!"); 113 | return EncryptBytes(data, key).FromBytes(); 114 | } 115 | 116 | /// 117 | /// Generate the Sha-256 hash of the given input string. 118 | /// 119 | /// The input string. 120 | /// The generated hash in byte[] 121 | public static byte[] GenerateHash(string input) 122 | { 123 | using SHA256Managed sha256 = new SHA256Managed(); 124 | return sha256.ComputeHash(Encoding.UTF8.GetBytes(input)); 125 | } 126 | 127 | internal static async Task> GenerateKey() 128 | { 129 | string[] words = ["Scalder", "Suscipient", "Sodalite", "Maharanis", "Mussier", "Abouts", "Geologized", "Antivenins", "Volcanized", "Heliskier", "Bedclothes", "Streamier", "Postulant", "Grizzle", "Folkies", "Poplars", "Stalls", "Chiefess", "Trip", "Untarred", "Cadillacs", "Fixings", "Overage", "Upbraider", "Phocas", "Galton", "Pests", "Saxifraga", "Erodes", "Bracketing", "Rugs", "Deprecate", "Monomials", "Subtracts", "Kettledrum", "Cometic", "Wrvs", "Phalangids", "Vareuse", "Pinchbecks", "Moony", "Scissoring", "Sarks", "Victresses", "Thorned", "Bowled", "Bakeries", "Printable", "Beethoven", "Sacher"]; 130 | int i = 0; 131 | int length = random.Next(5, 10); 132 | string passfrase = ""; 133 | while (i <= length) 134 | { 135 | await BaseScript.Delay(5); 136 | string symbol = ""; 137 | if (i > 0) 138 | symbol = "-"; 139 | passfrase += symbol + words[random.Next(words.Length - 1)]; 140 | i++; 141 | } 142 | return new(passfrase, passfrase.EncryptObject(GetRandomString(random.Next(30, 50))).BytesToString()); 143 | } 144 | 145 | private static string GetRandomString(int size, bool lowerCase = false) 146 | { 147 | StringBuilder builder = new StringBuilder(size); 148 | // Unicode/ASCII Letters are divided into two blocks 149 | // (Letters 65�90 / 97�122): 150 | // The first group containing the uppercase letters and 151 | // the second group containing the lowercase. 152 | 153 | // char is a single Unicode character 154 | char offset = lowerCase ? 'a' : 'A'; 155 | const int lettersOffset = 26; // A...Z or a..z: length=26 156 | 157 | for (int i = 0; i < size; i++) 158 | { 159 | char @char = (char)random.Next(offset, offset + lettersOffset); 160 | builder.Append(@char); 161 | } 162 | 163 | return lowerCase ? builder.ToString().ToLower() : builder.ToString(); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Attributes/ForceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Attributes 4 | { 5 | /// 6 | /// Indicates that this property should be forcefully added to serialization. 7 | /// 8 | [Obsolete("the current MsgPack version does not consent to serialize non public members/fields")] 9 | public class ForceAttribute : Attribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Attributes/FxEventAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FxEvents.Shared 6 | { 7 | [Flags] 8 | public enum Binding 9 | { 10 | /// 11 | /// No one can call this 12 | /// 13 | None = 0x0, 14 | 15 | /// 16 | /// Server only accepts server calls, client only client calls 17 | /// 18 | Local = 0x1, 19 | 20 | /// 21 | /// Server only accepts client calls, client only server calls 22 | /// 23 | Remote = 0x2, 24 | 25 | /// 26 | /// Accept all incoming calls 27 | /// 28 | All = Local | Remote 29 | } 30 | 31 | /// 32 | /// The fxevent attribute. 33 | /// 34 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 35 | public class FxEventAttribute : Attribute 36 | { 37 | public string Name { get; } 38 | public Binding Binding { get; } 39 | public FxEventAttribute(string name, Binding binding = Binding.All) 40 | { 41 | Name = name; 42 | Binding = binding; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Attributes/IgnoreAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Attributes 4 | { 5 | /// 6 | /// Indicates that this property should be disregarded from serialization. 7 | /// 8 | [Obsolete("the current MsgPack version does not consent to ignore non public members/fields")] 9 | public class IgnoreAttribute : Attribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/BaseGatewayHelpers.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using System; 4 | using System.Globalization; 5 | 6 | internal static class BaseGatewayHelpers 7 | { 8 | 9 | internal static object GetHolder(MessagePackObject msgpkObj, Type type) 10 | { 11 | object obj = msgpkObj.ToObject(); 12 | TypeCode typeCode = Type.GetTypeCode(type); 13 | switch (typeCode) 14 | { 15 | case TypeCode.String: 16 | if (msgpkObj.IsNil) 17 | return string.Empty; 18 | return obj as string ?? (type.IsSimpleType() ? obj.ToString() : string.Empty); 19 | case TypeCode.Byte: 20 | case TypeCode.SByte: 21 | case TypeCode.Int16: 22 | case TypeCode.Int32: 23 | case TypeCode.Int64: 24 | case TypeCode.UInt16: 25 | case TypeCode.UInt32: 26 | case TypeCode.UInt64: 27 | if (obj is IConvertible convertible) 28 | { 29 | try 30 | { 31 | return Convert.ChangeType(convertible, type); 32 | } 33 | catch (InvalidCastException) 34 | { 35 | return GetDefaultForType(type); 36 | } 37 | } 38 | else 39 | return GetDefaultForType(type); 40 | case TypeCode.Boolean: 41 | bool booleanValue; 42 | if (bool.TryParse(obj.ToString(), out booleanValue)) 43 | return booleanValue; 44 | else 45 | return false; 46 | case TypeCode.Char: 47 | char charValue; 48 | if (char.TryParse(obj.ToString(), out charValue)) 49 | return charValue; 50 | else 51 | return '\0'; 52 | case TypeCode.Decimal: 53 | decimal decimalValue; 54 | if (decimal.TryParse(obj.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out decimalValue)) 55 | return decimalValue; 56 | else 57 | return 0M; 58 | case TypeCode.Single: 59 | float floatValue; 60 | if (float.TryParse(obj.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out floatValue)) 61 | return floatValue; 62 | else 63 | return 0F; 64 | case TypeCode.Double: 65 | double doubleValue; 66 | if (double.TryParse(obj.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) 67 | return doubleValue; 68 | else 69 | return 0D; 70 | case TypeCode.DBNull: 71 | case TypeCode.DateTime: 72 | return obj; 73 | default: 74 | return GetDefaultForType(type); 75 | } 76 | } 77 | private static object GetDefaultForType(Type type) 78 | { 79 | // Determine the default value for the given type 80 | if (type == typeof(string)) return string.Empty; 81 | if (type == typeof(byte)) return 0; 82 | if (type == typeof(sbyte)) return 0; 83 | if (type == typeof(bool)) return false; 84 | if (type == typeof(char)) return '\0'; 85 | if (type == typeof(DateTime)) return DateTime.MinValue; 86 | if (type == typeof(decimal)) return 0M; 87 | if (type == typeof(float)) return 0F; 88 | if (type == typeof(double)) return 0D; 89 | if (type == typeof(short)) return 0; 90 | if (type == typeof(int)) return 0; 91 | if (type == typeof(long)) return 0L; 92 | if (type == typeof(ushort)) return 0U; 93 | if (type == typeof(uint)) return 0U; 94 | if (type == typeof(ulong)) return 0UL; 95 | return null; // Fallback to null for unsupported types 96 | } 97 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Diagnostics/IEventLogger.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.Diagnostics 2 | { 3 | public interface IEventLogger 4 | { 5 | void Debug(params object[] values); 6 | void Info(params object[] values); 7 | } 8 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Diagnostics/Impl/ClientStopwatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Diagnostics.Impl 4 | { 5 | internal class ClientStopwatch : StopwatchUtil 6 | { 7 | private long _startTicks; 8 | private long _totalPauseTicks; 9 | 10 | public override TimeSpan Elapsed 11 | { 12 | get 13 | { 14 | long currentTicks = GetTimestamp(); 15 | long elapsedTicks = currentTicks - _startTicks - _totalPauseTicks; 16 | return TimeSpan.FromTicks(elapsedTicks); 17 | } 18 | } 19 | 20 | public ClientStopwatch() 21 | { 22 | _startTicks = GetTimestamp(); 23 | } 24 | 25 | public override void Stop() 26 | { 27 | _totalPauseTicks += GetTimestamp() - _startTicks; 28 | } 29 | 30 | public override void Start() 31 | { 32 | _startTicks = GetTimestamp() - _totalPauseTicks; 33 | } 34 | 35 | internal static long GetTimestamp() 36 | { 37 | return DateTime.UtcNow.Ticks; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Diagnostics/Impl/ServerStopwatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace FxEvents.Shared.Diagnostics.Impl 5 | { 6 | internal class ServerStopwatch : StopwatchUtil 7 | { 8 | private readonly Stopwatch _stopwatch; 9 | public override TimeSpan Elapsed => _stopwatch.Elapsed; 10 | 11 | public ServerStopwatch() 12 | { 13 | _stopwatch = Stopwatch.StartNew(); 14 | } 15 | 16 | public override void Stop() 17 | { 18 | _stopwatch.Stop(); 19 | } 20 | 21 | public override void Start() 22 | { 23 | _stopwatch.Start(); 24 | } 25 | 26 | internal static long GetTimestamp() 27 | { 28 | return Stopwatch.GetTimestamp() / 10000; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Diagnostics/StopwatchUtil.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.Diagnostics.Impl; 2 | using System; 3 | 4 | namespace FxEvents.Shared.Diagnostics 5 | { 6 | public abstract class StopwatchUtil 7 | { 8 | private static bool IsServer = API.IsDuplicityVersion(); 9 | 10 | public abstract TimeSpan Elapsed { get; } 11 | public abstract void Stop(); 12 | public abstract void Start(); 13 | 14 | 15 | public static long Timestamp 16 | { 17 | get 18 | { 19 | if (IsServer) 20 | { 21 | return ServerStopwatch.GetTimestamp(); 22 | } 23 | else 24 | { 25 | return ClientStopwatch.GetTimestamp(); 26 | } 27 | } 28 | } 29 | 30 | public static StopwatchUtil StartNew() 31 | { 32 | if (IsServer) 33 | { 34 | return new ServerStopwatch(); 35 | } 36 | 37 | return new ClientStopwatch(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/EventsDictionary.cs: -------------------------------------------------------------------------------- 1 |  2 | using FxEvents.Shared.Exceptions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace FxEvents.Shared.EventSubsystem 11 | { 12 | public class EventsDictionary : Dictionary 13 | { 14 | public new EventEntry this[string key] 15 | { 16 | get 17 | { 18 | var lookupKey = key.ToLower(); 19 | 20 | if (this.ContainsKey(lookupKey)) 21 | { 22 | return base[lookupKey]; 23 | } 24 | 25 | var entry = new EventEntry(key); 26 | base.Add(lookupKey, entry); 27 | 28 | return entry; 29 | } 30 | set { } 31 | } 32 | 33 | public void Add(string endpoint, Binding binding, Delegate callback) 34 | { 35 | this[endpoint] += new Tuple(callback,binding); 36 | } 37 | } 38 | 39 | public class EventEntry 40 | { 41 | internal readonly string m_eventName; 42 | internal readonly List> m_callbacks = new(); 43 | internal string name => m_eventName; 44 | 45 | public EventEntry(string eventName) 46 | { 47 | m_eventName = eventName; 48 | } 49 | 50 | public static EventEntry operator +(EventEntry entry, Tuple deleg) 51 | { 52 | entry.m_callbacks.Add(deleg); 53 | 54 | return entry; 55 | } 56 | 57 | public static EventEntry operator -(EventEntry entry, Tuple deleg) 58 | { 59 | entry.m_callbacks.Remove(deleg); 60 | 61 | return entry; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Exceptions/EventException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Exceptions 4 | { 5 | public class EventTimeoutException : Exception 6 | { 7 | public EventTimeoutException(string message) : base(message) 8 | { 9 | } 10 | 11 | public EventTimeoutException(string message, Exception innerException) : base(message, innerException) 12 | { 13 | } 14 | } 15 | 16 | public class EventException : Exception 17 | { 18 | public EventException() { } 19 | public EventException(string message) : base(message) { } 20 | public EventException(string message, Exception innerException) : base(message, innerException) { } 21 | } 22 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/ISerializable.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.Snowflakes; 2 | 3 | namespace FxEvents.Shared.EventSubsystem 4 | { 5 | 6 | public interface IMessage 7 | { 8 | Snowflake Id { get; set; } 9 | string Endpoint { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/ISource.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.EventSubsystem 2 | { 3 | public interface ISource 4 | { 5 | int Handle { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Message/EventFlowType.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.Message 2 | { 3 | public enum EventFlowType 4 | { 5 | Straight, 6 | Circular 7 | } 8 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Message/EventMessage.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.EventSubsystem; 2 | 3 | using FxEvents.Shared.Payload; 4 | using FxEvents.Shared.Snowflakes; 5 | using System.Collections.Generic; 6 | 7 | namespace FxEvents.Shared.Message 8 | { 9 | internal class EventMessage : IMessage 10 | { 11 | public Snowflake Id { get; set; } 12 | public string? Endpoint { get; set; } 13 | public EventFlowType Flow { get; set; } 14 | public EventRemote Sender { get; set; } 15 | 16 | public IEnumerable Parameters { get; set; } 17 | public EventMessage() { } 18 | public EventMessage(string endpoint, EventFlowType flow, IEnumerable parameters, EventRemote sender) 19 | { 20 | Id = Snowflake.Next(); 21 | Endpoint = endpoint; 22 | Flow = flow; 23 | Parameters = parameters; 24 | Sender = sender; 25 | } 26 | 27 | public override string ToString() => Endpoint; 28 | } 29 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Message/EventParameter.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.Payload 2 | { 3 | public class EventParameter 4 | { 5 | public byte[] Data { get; set; } 6 | 7 | public EventParameter() { } 8 | public EventParameter(byte[] data) 9 | { 10 | Data = data; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Message/EventRemote.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FxEvents.Shared.EventSubsystem 6 | { 7 | internal enum EventRemote 8 | { 9 | Client, 10 | Server 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Message/EventResponseMessage.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.EventSubsystem; 2 | using FxEvents.Shared.Snowflakes; 3 | using System; 4 | using System.IO; 5 | 6 | namespace FxEvents.Shared.Message 7 | { 8 | public class EventResponseMessage : IMessage 9 | { 10 | public Snowflake Id { get; set; } 11 | public string Endpoint { get; set; } 12 | public byte[]? Data { get; set; } 13 | 14 | public EventResponseMessage() { } 15 | public EventResponseMessage(Snowflake id, string endpoint, byte[]? data) 16 | { 17 | Id = id; 18 | Endpoint = endpoint; 19 | Data = data; 20 | } 21 | 22 | public override string ToString() => Id.ToString(); 23 | } 24 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Models/EventObservable.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.EventSubsystem; 2 | using System; 3 | 4 | namespace FxEvents.Shared.Models 5 | { 6 | public class EventObservable 7 | { 8 | public IMessage Message { get; set; } 9 | public Action Callback { get; set; } 10 | 11 | public EventObservable(IMessage message, Action callback) 12 | { 13 | Message = message; 14 | Callback = callback; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Models/EventValueHolder.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.Models 2 | { 3 | public class EventValueHolder 4 | { 5 | public byte[] Data { get; set; } 6 | public T Value { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/ISerialization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Serialization 4 | { 5 | public interface ISerialization 6 | { 7 | void Serialize(Type type, object value, SerializationContext context); 8 | void Serialize(T value, SerializationContext context); 9 | object Deserialize(Type type, SerializationContext context); 10 | T Deserialize(SerializationContext context); 11 | } 12 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/JsonSerialization.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Text; 4 | 5 | namespace FxEvents.Shared.Serialization.Implementations 6 | { 7 | public class JsonSerialization : ISerialization 8 | { 9 | public JsonSerialization() 10 | { 11 | } 12 | 13 | public void Serialize(Type type, object value, SerializationContext context) 14 | { 15 | context.Writer.Write(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value))); 16 | } 17 | 18 | public void Serialize(T value, SerializationContext context) 19 | { 20 | Serialize(typeof(T), value, context); 21 | } 22 | 23 | public object Deserialize(Type type, SerializationContext context) 24 | { 25 | return JsonConvert.DeserializeObject( 26 | Encoding.UTF8.GetString(context.Reader.ReadBytes(context.Original!.Length)), type); 27 | } 28 | 29 | public T Deserialize(SerializationContext context) 30 | { 31 | return (T)Deserialize(typeof(T), context); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/DoubleFixer.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using MsgPack.Serialization; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Globalization; 7 | 8 | namespace FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPack.MsgPackResolvers 9 | { 10 | public class DoubleFixer : MessagePackSerializer 11 | { 12 | public DoubleFixer(SerializationContext ownerContext) : base(ownerContext) 13 | { 14 | } 15 | 16 | protected override void PackToCore(Packer packer, double objectTree) 17 | { 18 | packer.Pack(objectTree.ToString("G17", CultureInfo.InvariantCulture)); 19 | } 20 | 21 | protected override double UnpackFromCore(Unpacker unpacker) 22 | { 23 | var data = unpacker.LastReadData; 24 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 25 | throw new Exception($"FxEvents double - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(int).FullName}"); 26 | if (unpacker.IsArrayHeader) 27 | throw new Exception($"FxEvents double - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(int).FullName}"); 28 | 29 | return double.Parse(data.AsString(), CultureInfo.InvariantCulture); 30 | 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/EntityResolver.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using MsgPack.Serialization; 4 | using System; 5 | 6 | namespace FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPackResolvers 7 | { 8 | /// 9 | /// For this one and all its derivates.. we use NetworkID to keep consistency between each side. 10 | /// 11 | public class EntityResolver : MessagePackSerializer 12 | { 13 | public EntityResolver(SerializationContext ownerContext) : base(ownerContext) 14 | { 15 | } 16 | 17 | protected override void PackToCore(Packer packer, Entity objectTree) 18 | { 19 | packer.Pack(objectTree.NetworkId); 20 | } 21 | 22 | protected override Entity UnpackFromCore(Unpacker unpacker) 23 | { 24 | var data = unpacker.LastReadData; 25 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 26 | throw new Exception($"FxEvents Entity - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(int).FullName}"); 27 | if (unpacker.IsArrayHeader) 28 | throw new Exception($"FxEvents Entity - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(int).FullName}"); 29 | 30 | int.TryParse(data.ToObject().ToString(), out int item); 31 | return Entity.FromNetworkId(item); 32 | } 33 | } 34 | 35 | public class PedResolver : MessagePackSerializer 36 | { 37 | public PedResolver(SerializationContext ownerContext) : base(ownerContext) 38 | { 39 | } 40 | 41 | protected override void PackToCore(Packer packer, Ped objectTree) 42 | { 43 | packer.Pack(objectTree.NetworkId); 44 | } 45 | 46 | protected override Ped UnpackFromCore(Unpacker unpacker) 47 | { 48 | var data = unpacker.LastReadData; 49 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 50 | throw new Exception($"FxEvents Ped - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(int).FullName}"); 51 | if (unpacker.IsArrayHeader) 52 | throw new Exception($"FxEvents Ped - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(int).FullName}"); 53 | 54 | int.TryParse(data.ToObject().ToString(), out int item); 55 | return (Ped)Entity.FromNetworkId(item); 56 | } 57 | } 58 | 59 | public class VehicleResolver : MessagePackSerializer 60 | { 61 | public VehicleResolver(SerializationContext ownerContext) : base(ownerContext) 62 | { 63 | } 64 | 65 | protected override void PackToCore(Packer packer, Vehicle objectTree) 66 | { 67 | packer.Pack(objectTree.NetworkId); 68 | } 69 | 70 | protected override Vehicle UnpackFromCore(Unpacker unpacker) 71 | { 72 | var data = unpacker.LastReadData; 73 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 74 | throw new Exception($"FxEvents Vehicle - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(int).FullName}"); 75 | if (unpacker.IsArrayHeader) 76 | throw new Exception($"FxEvents Vehicle - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(int).FullName}"); 77 | 78 | int.TryParse(data.ToObject().ToString(), out int item); 79 | return (Vehicle)Entity.FromNetworkId(item); 80 | } 81 | } 82 | 83 | public class PropResolver : MessagePackSerializer 84 | { 85 | public PropResolver(SerializationContext ownerContext) : base(ownerContext) 86 | { 87 | } 88 | 89 | protected override void PackToCore(Packer packer, Prop objectTree) 90 | { 91 | packer.Pack(objectTree.NetworkId); 92 | } 93 | 94 | protected override Prop UnpackFromCore(Unpacker unpacker) 95 | { 96 | var data = unpacker.LastReadData; 97 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 98 | throw new Exception($"FxEvents Prop - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(int).FullName}"); 99 | if (unpacker.IsArrayHeader) 100 | throw new Exception($"FxEvents Prop - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(int).FullName}"); 101 | 102 | int.TryParse(data.ToObject().ToString(), out int item); 103 | return (Prop)Entity.FromNetworkId(item); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/ISourceResolver.cs: -------------------------------------------------------------------------------- 1 | //using MsgPack; 2 | //using MsgPack.Serialization; 3 | //using System; 4 | //using System.Collections.Generic; 5 | //using System.Text; 6 | 7 | //namespace FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPackResolvers 8 | //{ 9 | // internal class ISourceResolver : MessagePackSerializer 10 | // { 11 | // public ISourceResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 12 | // { 13 | // } 14 | 15 | // protected override void PackToCore(Packer packer, ISource objectTree) 16 | // { 17 | // packer.Pack(objectTree.Handle); 18 | // } 19 | 20 | // protected override ISource UnpackFromCore(Unpacker unpacker) 21 | // { 22 | // return (ISource)Activator.CreateInstance(typeof(ISource), unpacker.LastReadData.AsInt32()); 23 | // } 24 | // } 25 | //} 26 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/KeyValuePairResolver.cs: -------------------------------------------------------------------------------- 1 | //using MsgPack; 2 | //using MsgPack.Serialization; 3 | //using System.Collections.Generic; 4 | 5 | //namespace FxEvents.Shared.Serialization.Implementations.MsgPackResolvers 6 | //{ 7 | // public class KeyValuePairResolver : MessagePackSerializer> 8 | // { 9 | // private readonly MessagePackSerializer _keySerializer; 10 | // private readonly MessagePackSerializer _valueSerializer; 11 | // public KeyValuePairResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 12 | // { 13 | // } 14 | 15 | // protected override void PackToCore(Packer packer, KeyValuePair objectTree) 16 | // { 17 | // packer.PackArrayHeader(2); 18 | // this._keySerializer.PackTo(packer, objectTree.Key); 19 | // this._valueSerializer.PackTo(packer, objectTree.Value); 20 | // } 21 | 22 | // protected override KeyValuePair UnpackFromCore(Unpacker unpacker) 23 | // { 24 | // if (!unpacker.Read()) 25 | // { 26 | // return default; 27 | // } 28 | 29 | // TKey key = unpacker.LastReadData.IsNil ? default(TKey) : this._keySerializer.UnpackFrom(unpacker); 30 | 31 | // if (!unpacker.Read()) 32 | // { 33 | // return default; 34 | // } 35 | 36 | // TValue value = unpacker.LastReadData.IsNil ? default(TValue) : this._valueSerializer.UnpackFrom(unpacker); 37 | 38 | // return new KeyValuePair(key, value); 39 | // } 40 | 41 | // } 42 | //} -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/Matrix3x3Resolver.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using MsgPack.Serialization; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPackResolvers 9 | { 10 | internal class Matrix3x3Resolver : MessagePackSerializer 11 | { 12 | public Matrix3x3Resolver(SerializationContext ownerContext) : base(ownerContext) 13 | { 14 | } 15 | 16 | protected override void PackToCore(Packer packer, Matrix3x3 objectTree) 17 | { 18 | packer.PackArray(objectTree.ToArray()); 19 | } 20 | 21 | protected override Matrix3x3 UnpackFromCore(Unpacker unpacker) 22 | { 23 | float[] values = new float[9]; 24 | for (int i = 0; i < 9; i++) 25 | { 26 | float item; 27 | if (!unpacker.Read()) 28 | { 29 | item = 0; 30 | } 31 | else 32 | { 33 | var data = unpacker.LastReadData; 34 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 35 | throw new Exception($"FxEvents Matrix3x3 - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(float).FullName}"); 36 | if (unpacker.IsArrayHeader) 37 | throw new Exception($"FxEvents Matrix3x3 - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(float).FullName}"); 38 | 39 | float.TryParse(data.ToObject().ToString(), out item); 40 | } 41 | values[i] = item; 42 | } 43 | return new Matrix3x3(values); 44 | 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/MatrixResolver.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using MsgPack.Serialization; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPackResolvers 9 | { 10 | internal class MatrixResolver : MessagePackSerializer 11 | { 12 | public MatrixResolver(SerializationContext ownerContext) : base(ownerContext) 13 | { 14 | } 15 | 16 | protected override void PackToCore(Packer packer, Matrix objectTree) 17 | { 18 | packer.PackArray(objectTree.ToArray()); 19 | } 20 | 21 | protected override Matrix UnpackFromCore(Unpacker unpacker) 22 | { 23 | float[] values = new float[16]; 24 | for (int i = 0; i < 16; i++) 25 | { 26 | float item; 27 | if (!unpacker.Read()) 28 | { 29 | item = 0; 30 | } 31 | else 32 | { 33 | var data = unpacker.LastReadData; 34 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 35 | throw new Exception($"FxEvents Matrix - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(float).FullName}"); 36 | if (unpacker.IsArrayHeader) 37 | throw new Exception($"FxEvents Matrix - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(float).FullName}"); 38 | 39 | float.TryParse(data.ToObject().ToString(), out item); 40 | } 41 | values[i] = item; 42 | } 43 | return new Matrix(values); 44 | 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/PlayerResolver.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using MsgPack.Serialization; 4 | using System; 5 | 6 | namespace FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPackResolvers 7 | { 8 | public class PlayerResolver : MessagePackSerializer 9 | { 10 | public PlayerResolver(SerializationContext ownerContext) : base(ownerContext) 11 | { 12 | } 13 | 14 | protected override void PackToCore(Packer packer, Player objectTree) 15 | { 16 | #if CLIENT 17 | packer.Pack(objectTree.ServerId); 18 | #elif SERVER 19 | packer.Pack(int.Parse(objectTree.Handle)); 20 | #endif 21 | } 22 | 23 | protected override Player UnpackFromCore(Unpacker unpacker) 24 | { 25 | var data = unpacker.LastReadData; 26 | if (!TypeCache.IsSimpleType(data.UnderlyingType)) 27 | throw new Exception($"Cannot deserialize type {data.UnderlyingType.Name} into Player type"); 28 | string last = data.ToObject().ToString(); 29 | int.TryParse(last, out int handle); 30 | return EventHub.Instance.GetPlayers[handle]; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/QuaternionResolver.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using MsgPack.Serialization; 4 | using System; 5 | 6 | namespace FxEvents.Shared.Serialization.Implementations.MsgPackResolvers 7 | { 8 | public class QuaternionResolver : MessagePackSerializer 9 | { 10 | private readonly MessagePackSerializer _itemSerializer; 11 | public QuaternionResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 12 | { 13 | this._itemSerializer = ownerContext.GetSerializer(); 14 | } 15 | 16 | protected override void PackToCore(Packer packer, Quaternion objectTree) 17 | { 18 | packer.PackArray(objectTree.ToArray()); 19 | } 20 | 21 | protected override Quaternion UnpackFromCore(Unpacker unpacker) 22 | { 23 | float[] values = new float[4]; 24 | for (int i = 0; i < 4; i++) 25 | { 26 | float item; 27 | if (!unpacker.Read()) 28 | { 29 | item = 0; 30 | } 31 | else 32 | { 33 | var data = unpacker.LastReadData; 34 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 35 | throw new Exception($"Cannot deserialize type {data.UnderlyingType.Name} into Quaternion float parameter type"); 36 | if(unpacker.IsArrayHeader) 37 | throw new Exception($"Cannot deserialize type array {data.UnderlyingType.Name}[] into Quaternion float parameter type"); 38 | 39 | float.TryParse(data.ToObject().ToString(), out item); 40 | } 41 | values[i] = item; 42 | } 43 | return new Quaternion(values); 44 | } 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/SnowflakeResolver.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.Snowflakes; 2 | using FxEvents.Shared.TypeExtensions; 3 | using MsgPack; 4 | using MsgPack.Serialization; 5 | using System; 6 | 7 | namespace FxEvents.Shared.Serialization.Implementations.MsgPackResolvers 8 | { 9 | public class SnowflakeResolver : MessagePackSerializer 10 | { 11 | public SnowflakeResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 12 | { 13 | } 14 | 15 | protected override void PackToCore(Packer packer, Snowflake objectTree) 16 | { 17 | packer.Pack(objectTree.ToInt64()); 18 | } 19 | 20 | protected override Snowflake UnpackFromCore(Unpacker unpacker) 21 | { 22 | var data = unpacker.LastReadData; 23 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 24 | throw new Exception($"FxEvents Snowflake - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(ulong).FullName}"); 25 | if (unpacker.IsArrayHeader) 26 | throw new Exception($"FxEvents Snowflake - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(ulong).FullName}"); 27 | 28 | ulong.TryParse(data.ToObject().ToString(), out ulong item); 29 | 30 | return new Snowflake(item); 31 | } 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/ValueTupleResolver.cs: -------------------------------------------------------------------------------- 1 | //using Logger; 2 | //using MsgPack; 3 | //using MsgPack.Serialization; 4 | //using System; 5 | 6 | //namespace FxEvents.Shared.Serialization.Implementations.MsgPackResolvers 7 | //{ 8 | // [Obsolete("Ignored by messagepack apparently due to its non generic behaviour, kept for reference and other uses")] 9 | // public class ValueTupleResolver : MessagePackSerializer> 10 | // { 11 | // public ValueTupleResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 12 | // { 13 | // } 14 | 15 | // protected override void PackToCore(Packer packer, ValueTuple objectTree) 16 | // { 17 | // packer.Pack(new object[] { objectTree.Item1 }); 18 | // } 19 | 20 | // protected override ValueTuple UnpackFromCore(Unpacker unpacker) 21 | // { 22 | // object[] obj = (object[])unpacker.LastReadData.ToObject(); 23 | // return new ValueTuple((A)obj[0]); 24 | // } 25 | // } 26 | 27 | // [Obsolete("Ignored by messagepack apparently due to its non generic behaviour, kept for reference and other uses")] 28 | // public class ValueTupleResolver : MessagePackSerializer> 29 | // { 30 | // Log logger = new Log(); 31 | // public ValueTupleResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 32 | // { 33 | 34 | // } 35 | 36 | // protected override void PackToCore(Packer packer, ValueTuple objectTree) 37 | // { 38 | // logger.Info("sono chiamato"); 39 | // packer.Pack(new object[] { objectTree.Item1, objectTree.Item2 }); 40 | // } 41 | 42 | // protected override ValueTuple UnpackFromCore(Unpacker unpacker) 43 | // { 44 | // object[] obj = (object[])unpacker.LastReadData.ToObject(); 45 | // return new ValueTuple((A)obj[0], (B)obj[1]); 46 | // } 47 | // } 48 | 49 | // [Obsolete("Ignored by messagepack apparently due to its non generic behaviour, kept for reference and other uses")] 50 | // public class ValueTupleResolver : MessagePackSerializer> 51 | // { 52 | // public ValueTupleResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 53 | // { 54 | // } 55 | 56 | // protected override void PackToCore(Packer packer, ValueTuple objectTree) 57 | // { 58 | // packer.Pack(new object[] { objectTree.Item1, objectTree.Item2, objectTree.Item3 }); 59 | // } 60 | 61 | // protected override ValueTuple UnpackFromCore(Unpacker unpacker) 62 | // { 63 | // object[] obj = (object[])unpacker.LastReadData.ToObject(); 64 | // return new ValueTuple((A)obj[0], (B)obj[1], (C)obj[2]); 65 | // } 66 | // } 67 | 68 | // [Obsolete("Ignored by messagepack apparently due to its non generic behaviour, kept for reference and other uses")] 69 | // public class ValueTupleResolver : MessagePackSerializer> 70 | // { 71 | // public ValueTupleResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 72 | // { 73 | // } 74 | 75 | // protected override void PackToCore(Packer packer, ValueTuple objectTree) 76 | // { 77 | // packer.Pack(new object[] { objectTree.Item1, objectTree.Item2, objectTree.Item3, objectTree.Item4 }); 78 | // } 79 | 80 | // protected override ValueTuple UnpackFromCore(Unpacker unpacker) 81 | // { 82 | // object[] obj = (object[])unpacker.LastReadData.ToObject(); 83 | // return new ValueTuple((A)obj[0], (B)obj[1], (C)obj[2], (D)obj[3]); 84 | // } 85 | // } 86 | 87 | // [Obsolete("Ignored by messagepack apparently due to its non generic behaviour, kept for reference and other uses")] 88 | // public class ValueTupleResolver : MessagePackSerializer> 89 | // { 90 | // public ValueTupleResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 91 | // { 92 | // } 93 | 94 | // protected override void PackToCore(Packer packer, ValueTuple objectTree) 95 | // { 96 | // packer.Pack(new object[] { objectTree.Item1, objectTree.Item2, objectTree.Item3, objectTree.Item4, objectTree.Item5 }); 97 | // } 98 | 99 | // protected override ValueTuple UnpackFromCore(Unpacker unpacker) 100 | // { 101 | // object[] obj = (object[])unpacker.LastReadData.ToObject(); 102 | // return new ValueTuple((A)obj[0], (B)obj[1], (C)obj[2], (D)obj[3], (E)obj[4]); 103 | // } 104 | // } 105 | 106 | // [Obsolete("Ignored by messagepack apparently due to its non generic behaviour, kept for reference and other uses")] 107 | // public class ValueTupleResolver : MessagePackSerializer> 108 | // { 109 | // public ValueTupleResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 110 | // { 111 | // } 112 | 113 | // protected override void PackToCore(Packer packer, ValueTuple objectTree) 114 | // { 115 | // packer.Pack(new object[] { objectTree.Item1, objectTree.Item2, objectTree.Item3, objectTree.Item4, objectTree.Item5, objectTree.Item6 }); 116 | // } 117 | 118 | // protected override ValueTuple UnpackFromCore(Unpacker unpacker) 119 | // { 120 | // object[] obj = (object[])unpacker.LastReadData.ToObject(); 121 | // return new ValueTuple((A)obj[0], (B)obj[1], (C)obj[2], (D)obj[3], (E)obj[4], (F)obj[5]); 122 | // } 123 | // } 124 | 125 | // [Obsolete("Ignored by messagepack apparently due to its non generic behaviour, kept for reference and other uses")] 126 | // public class ValueTupleResolver : MessagePackSerializer> 127 | // { 128 | // public ValueTupleResolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 129 | // { 130 | // } 131 | 132 | // protected override void PackToCore(Packer packer, ValueTuple objectTree) 133 | // { 134 | // packer.Pack(new object[] { objectTree.Item1, objectTree.Item2, objectTree.Item3, objectTree.Item4, objectTree.Item5, objectTree.Item6, objectTree.Item7 }); 135 | // } 136 | 137 | // protected override ValueTuple UnpackFromCore(Unpacker unpacker) 138 | // { 139 | // object[] obj = (object[])unpacker.LastReadData.ToObject(); 140 | // return new ValueTuple((A)obj[0], (B)obj[1], (C)obj[2], (D)obj[3], (E)obj[4], (F)obj[5], (G)obj[6]); 141 | // } 142 | // } 143 | //} -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPack/MsgPackResolvers/VectorResolver.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using Logger; 3 | using MsgPack; 4 | using MsgPack.Serialization; 5 | using System; 6 | using System.Linq; 7 | 8 | namespace FxEvents.Shared.Serialization.Implementations.MsgPackResolvers 9 | { 10 | public class Vector2Resolver : MessagePackSerializer 11 | { 12 | public Vector2Resolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 13 | { 14 | } 15 | 16 | protected override void PackToCore(Packer packer, Vector2 objectTree) 17 | { 18 | packer.PackArray(objectTree.ToArray()); 19 | } 20 | 21 | 22 | protected override Vector2 UnpackFromCore(Unpacker unpacker) 23 | { 24 | float[] values = new float[2]; 25 | for (int i = 0; i < 2; i++) 26 | { 27 | float item; 28 | if (!unpacker.Read()) 29 | { 30 | item = 0; 31 | } 32 | else 33 | { 34 | var data = unpacker.LastReadData; 35 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 36 | throw new Exception($"FxEvents Vector2 - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(float).FullName}"); 37 | if (unpacker.IsArrayHeader) 38 | throw new Exception($"FxEvents Vector2 - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(float).FullName}"); 39 | 40 | float.TryParse(data.ToObject().ToString(), out item); 41 | } 42 | values[i] = item; 43 | } 44 | return new Vector2(values); 45 | } 46 | 47 | } 48 | 49 | public class Vector3Resolver : MessagePackSerializer 50 | { 51 | public Vector3Resolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 52 | { 53 | } 54 | 55 | protected override void PackToCore(Packer packer, Vector3 objectTree) 56 | { 57 | packer.PackArray(objectTree.ToArray()); 58 | } 59 | 60 | 61 | protected override Vector3 UnpackFromCore(Unpacker unpacker) 62 | { 63 | float[] values = new float[3]; 64 | for (int i = 0; i < 3; i++) 65 | { 66 | float item; 67 | if (!unpacker.Read()) 68 | { 69 | item = 0; 70 | } 71 | else 72 | { 73 | var data = unpacker.LastReadData; 74 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 75 | throw new Exception($"FxEvents Vector3 - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(float).FullName}"); 76 | if (unpacker.IsArrayHeader) 77 | throw new Exception($"FxEvents Vector3 - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(float).FullName}"); 78 | 79 | float.TryParse(data.ToObject().ToString(), out item); 80 | } 81 | values[i] = item; 82 | } 83 | return new Vector3(values); 84 | } 85 | 86 | } 87 | public class Vector4Resolver : MessagePackSerializer 88 | { 89 | public Vector4Resolver(MsgPack.Serialization.SerializationContext ownerContext) : base(ownerContext) 90 | { 91 | } 92 | 93 | protected override void PackToCore(Packer packer, Vector4 objectTree) 94 | { 95 | packer.PackArray(objectTree.ToArray()); 96 | } 97 | 98 | 99 | protected override Vector4 UnpackFromCore(Unpacker unpacker) 100 | { 101 | float[] values = new float[4]; 102 | for (int i = 0; i < 4; i++) 103 | { 104 | float item; 105 | if (!unpacker.Read()) 106 | { 107 | item = 0; 108 | } 109 | else 110 | { 111 | var data = unpacker.LastReadData; 112 | if (!TypeCache.IsSimpleType(data.UnderlyingType) || unpacker.IsMapHeader) 113 | throw new Exception($"FxEvents Vector4 - Cannot deserialize {data.UnderlyingType.FullName} into {typeof(float).FullName}"); 114 | if (unpacker.IsArrayHeader) 115 | throw new Exception($"FxEvents Vector4 - Cannot deserialize {data.UnderlyingType.FullName}[] array into {typeof(float).FullName}"); 116 | 117 | float.TryParse(data.ToObject().ToString(), out item); 118 | } 119 | values[i] = item; 120 | } 121 | return new Vector4(values); 122 | } 123 | 124 | } 125 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/Implementations/MsgPackSerialization.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.EventSubsystem.Serialization; 2 | using FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPack.MsgPackResolvers; 3 | using FxEvents.Shared.EventSubsystem.Serialization.Implementations.MsgPackResolvers; 4 | using FxEvents.Shared.Exceptions; 5 | using FxEvents.Shared.Serialization.Implementations.MsgPackResolvers; 6 | using FxEvents.Shared.TypeExtensions; 7 | using Logger; 8 | using MsgPack; 9 | using MsgPack.Serialization; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Reflection; 14 | using System.Text; 15 | 16 | namespace FxEvents.Shared.Serialization.Implementations 17 | { 18 | public class MsgPackSerialization : ISerialization 19 | { 20 | private Log logger = new(); 21 | private MsgPack.Serialization.SerializationContext _context = new(MsgPack.PackerCompatibilityOptions.None) { SerializationMethod = SerializationMethod.Map, GeneratorOption = SerializationMethodGeneratorOption.Fast }; 22 | public MsgPackSerialization() 23 | { 24 | Vector2Resolver vector2 = new(_context); 25 | Vector3Resolver vector3 = new(_context); 26 | Vector4Resolver vector4 = new(_context); 27 | QuaternionResolver quaternion = new(_context); 28 | MatrixResolver matrix = new(_context); 29 | Matrix3x3Resolver matrix3x3 = new(_context); 30 | SnowflakeResolver snowflake = new(_context); 31 | PlayerResolver player = new(_context); 32 | EntityResolver entity = new(_context); 33 | PedResolver ped = new(_context); 34 | PropResolver prop = new(_context); 35 | VehicleResolver vehicle = new(_context); 36 | DoubleFixer doubleFixer = new(_context); 37 | 38 | _context.Serializers.RegisterOverride(vector2); 39 | _context.Serializers.RegisterOverride(vector3); 40 | _context.Serializers.RegisterOverride(vector4); 41 | _context.Serializers.RegisterOverride(quaternion); 42 | _context.Serializers.RegisterOverride(matrix); 43 | _context.Serializers.RegisterOverride(matrix3x3); 44 | _context.Serializers.RegisterOverride(snowflake); 45 | _context.Serializers.RegisterOverride(player); 46 | _context.Serializers.RegisterOverride(entity); 47 | _context.Serializers.RegisterOverride(ped); 48 | _context.Serializers.RegisterOverride(prop); 49 | _context.Serializers.RegisterOverride(vehicle); 50 | _context.Serializers.RegisterOverride(doubleFixer); 51 | } 52 | 53 | private bool CanCreateInstanceUsingDefaultConstructor(Type t) => t.IsValueType || !t.IsAbstract && t.GetConstructor(Type.EmptyTypes) != null; 54 | private bool IsTuple(Type t) => t.Name.StartsWith("Tuple"); 55 | private bool ContainsTuple(Type t) => t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Any(x=>x.Name.StartsWith("Tuple"))|| 56 | t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Any(x => x.Name.StartsWith("Tuple")); 57 | private Type[] GetTupleTypes(Type t) 58 | { 59 | List types = []; 60 | var fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Where(x => x.Name.StartsWith("Tuple")); 61 | var properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Where(x => x.Name.StartsWith("Tuple")); 62 | 63 | 64 | return types.ToArray(); 65 | } 66 | private static string GetTypeIdentifier(Type type) 67 | { 68 | StringBuilder builder = new StringBuilder(); 69 | Type declaring = type; 70 | 71 | builder.Append(type.Namespace); 72 | builder.Append("."); 73 | 74 | int idx = builder.Length; 75 | 76 | while ((declaring = declaring.DeclaringType) != null) 77 | { 78 | builder.Insert(idx, declaring.Name + "."); 79 | } 80 | 81 | builder.Append(type.Name); 82 | 83 | return builder.ToString(); 84 | } 85 | 86 | 87 | #region Serialization 88 | public void Serialize(Type type, object value, SerializationContext context) 89 | { 90 | if (IsTuple(type)) 91 | { 92 | logger.Warning("Using Tuple is not advised due to differences between client and server environments and the unavailability of resolvers. Consider using ValueTuple instead."); 93 | SerializeTuple(type, value, context); 94 | } 95 | SerializeObject(type, value, context); 96 | } 97 | 98 | private void SerializeTuple(Type type, object value, SerializationContext context) 99 | { 100 | PropertyInfo[] properties = value.GetType().GetProperties(); 101 | 102 | foreach (PropertyInfo property in properties) 103 | { 104 | object propertyValue = property.GetValue(value, null); 105 | Serialize(propertyValue, context); 106 | } 107 | } 108 | 109 | private void SerializeObject(Type type, object value, SerializationContext context) 110 | { 111 | IMessagePackSingleObjectSerializer ser = MessagePackSerializer.Get(type, _context); 112 | ser.Pack(context.Writer.BaseStream, value); 113 | } 114 | 115 | public void Serialize(T value, SerializationContext context) 116 | { 117 | Serialize(typeof(T), value, context); 118 | } 119 | #endregion 120 | 121 | #region Deserialization 122 | public object Deserialize(Type type, SerializationContext context) 123 | { 124 | IMessagePackSingleObjectSerializer ser = MessagePackSerializer.Get(type, _context); 125 | object @return = ser.Unpack(context.Reader.BaseStream); 126 | return @return; 127 | } 128 | 129 | public T Deserialize(SerializationContext context) => Deserialize(typeof(T), context); 130 | 131 | public T Deserialize(Type type, SerializationContext context) 132 | { 133 | if (IsTuple(type)) 134 | { 135 | logger.Warning("Using Tuple is not advised due to differences between client and server environments and the unavailability of resolvers. Consider using ValueTuple instead."); 136 | return DeserializeTuple(type, context); 137 | } 138 | return DeserializeObject(type, context); 139 | } 140 | 141 | private T DeserializeTuple(Type type, SerializationContext context) 142 | { 143 | 144 | Type[] generics = type.GetGenericArguments(); 145 | System.Reflection.ConstructorInfo constructor = type.GetConstructor(generics) ?? 146 | throw new SerializationException(context, type, 147 | $"Could not find suitable constructor for type: {type.Name}"); 148 | List parameters = new List(); 149 | 150 | foreach (Type generic in generics) 151 | { 152 | object entry = Deserialize(generic, context); 153 | parameters.Add(entry); 154 | } 155 | 156 | object tuple = Activator.CreateInstance(type, parameters.ToArray()); 157 | return (T)tuple; 158 | } 159 | 160 | private T DeserializeObject(Type type, SerializationContext context) 161 | { 162 | if(TypeCache.IsSimpleType) 163 | return (T)TypeConvert.GetNewHolder(context, type); 164 | else 165 | { 166 | MessagePackSerializer ser = MessagePackSerializer.Get(_context); 167 | T @return = ser.Unpack(context.Reader.BaseStream); 168 | return @return; 169 | } 170 | } 171 | #endregion 172 | } 173 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/MsgPackCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FxEvents.Shared.EventSubsystem.Serialization 6 | { 7 | internal enum MsgPackCode : byte 8 | { 9 | FixIntPositiveMin = 0, 10 | FixIntPositiveMax = 127, 11 | NilValue = 192, 12 | TrueValue = 195, 13 | FalseValue = 194, 14 | SignedInt8 = 208, 15 | UnsignedInt8 = 204, 16 | SignedInt16 = 209, 17 | UnsignedInt16 = 205, 18 | SignedInt32 = 210, 19 | UnsignedInt32 = 206, 20 | SignedInt64 = 211, 21 | UnsignedInt64 = 207, 22 | Real32 = 202, 23 | Real64 = 203, 24 | MinimumFixedArray = 144, 25 | MaximumFixedArray = 159, 26 | Array16 = 220, 27 | Array32 = 221, 28 | MinimumFixedMap = 128, 29 | MaximumFixedMap = 143, 30 | Map16 = 222, 31 | Map32 = 223, 32 | MinimumFixedRaw = 160, 33 | MaximumFixedRaw = 191, 34 | MaximumFixedString = 175, 35 | Str8 = 217, 36 | Raw16 = 218, 37 | Raw32 = 219, 38 | Bin8 = 196, 39 | Bin16 = 197, 40 | Bin32 = 198, 41 | Ext8 = 199, 42 | Ext16 = 200, 43 | Ext32 = 201, 44 | FixExt1 = 212, 45 | FixExt2 = 213, 46 | FixExt4 = 214, 47 | FixExt8 = 215, 48 | FixExt16 = 216, 49 | FixIntNegativeMin = 0xE0, 50 | FixIntNegativeMax = 0xFF 51 | } 52 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/SerializationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace FxEvents.Shared.Serialization 5 | { 6 | public class SerializationContext : IDisposable 7 | { 8 | internal string Source { get; set; } 9 | internal string Details { get; set; } 10 | internal BinaryWriter? Writer 11 | { 12 | get => _writer; 13 | set 14 | { 15 | _writer?.Dispose(); 16 | _writer = value; 17 | } 18 | } 19 | 20 | internal BinaryReader? Reader 21 | { 22 | get => _reader; 23 | set 24 | { 25 | _reader?.Dispose(); 26 | _reader = value; 27 | } 28 | } 29 | 30 | internal byte[]? Original { get; set; } 31 | 32 | private ISerialization _serialization; 33 | private MemoryStream _memory; 34 | private BinaryReader? _reader; 35 | private BinaryWriter? _writer; 36 | 37 | internal byte[] GetData() 38 | { 39 | return _memory.ToArray(); 40 | } 41 | 42 | internal MemoryStream GetMemory() 43 | { 44 | return _memory; 45 | } 46 | 47 | internal SerializationContext(string source, string details, ISerialization serialization, byte[]? data = null) 48 | { 49 | Source = source; 50 | Details = details; 51 | _serialization = serialization; 52 | _memory = data != null ? new MemoryStream(data) : new MemoryStream(); 53 | _writer = new BinaryWriter(_memory); 54 | _reader = new BinaryReader(_memory); 55 | 56 | if (data != null) 57 | { 58 | Original = data; 59 | } 60 | } 61 | 62 | public void Dispose() 63 | { 64 | Writer?.Dispose(); 65 | Reader?.Dispose(); 66 | } 67 | 68 | internal void Serialize(Type type, object value) => _serialization.Serialize(type, value, this); 69 | internal void Serialize(T value) => _serialization.Serialize(value, this); 70 | internal object Deserialize(Type type) => _serialization.Deserialize(type, this); 71 | internal T Deserialize() => _serialization.Deserialize(this); 72 | } 73 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/Serialization/SerializationException.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.Serialization; 2 | using System; 3 | 4 | namespace FxEvents.Shared.Exceptions 5 | { 6 | public class SerializationException : Exception 7 | { 8 | public SerializationContext Context { get; set; } 9 | public Type InvolvedType { get; set; } 10 | 11 | public override string Message 12 | { 13 | get 14 | { 15 | if (Context != null && InvolvedType != null && _message != null) 16 | { 17 | return 18 | $"{Context.Source}: {(Context.Details != null ? $"[{Context.Details}] " : string.Empty)}({InvolvedType.FullName}) {_message}:"; 19 | } 20 | 21 | return null; 22 | } 23 | } 24 | 25 | public override string StackTrace => null; 26 | 27 | private string _message; 28 | 29 | public SerializationException(SerializationContext context, Type type, string message) 30 | { 31 | Context = context; 32 | InvolvedType = type; 33 | _message = message; 34 | } 35 | 36 | public SerializationException(SerializationContext context, Type type, string message, Exception innerException) 37 | : base(null, innerException) 38 | { 39 | Context = context; 40 | InvolvedType = type; 41 | _message = message; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/ServerId.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.EventSubsystem 2 | { 3 | public class ServerId : ISource 4 | { 5 | public int Handle => -1; 6 | } 7 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/TamperType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FxEvents.Shared.EventSubsystem 6 | { 7 | public enum TamperType 8 | { 9 | REPEATED_MESSAGE_ID, 10 | EDITED_ENCRYPTED_DATA, 11 | REQUESTED_NEW_PUBLIC_KEY 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/EventSubsystem/TypeConverter.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.TypeExtensions; 2 | using MsgPack; 3 | using System; 4 | using System.Globalization; 5 | 6 | namespace FxEvents.Shared.EventSubsystem 7 | { 8 | internal static class TypeConverter 9 | { 10 | 11 | internal static object GetHolder(MessagePackObject msgpkObj, Type type) 12 | { 13 | object obj = msgpkObj.ToObject(); 14 | TypeCode typeCode = Type.GetTypeCode(type); 15 | switch (typeCode) 16 | { 17 | case TypeCode.String: 18 | if (msgpkObj.IsNil) 19 | return string.Empty; 20 | return obj as string ?? (type.IsSimpleType() ? obj.ToString() : string.Empty); 21 | case TypeCode.Byte: 22 | case TypeCode.SByte: 23 | case TypeCode.Int16: 24 | case TypeCode.Int32: 25 | case TypeCode.Int64: 26 | case TypeCode.UInt16: 27 | case TypeCode.UInt32: 28 | case TypeCode.UInt64: 29 | if (obj is IConvertible convertible) 30 | { 31 | try 32 | { 33 | return Convert.ChangeType(convertible, type); 34 | } 35 | catch (InvalidCastException) 36 | { 37 | return GetDefaultForType(type); 38 | } 39 | } 40 | else 41 | return GetDefaultForType(type); 42 | case TypeCode.Boolean: 43 | bool booleanValue; 44 | if (bool.TryParse(obj.ToString(), out booleanValue)) 45 | return booleanValue; 46 | else 47 | return false; 48 | case TypeCode.Char: 49 | char charValue; 50 | if (char.TryParse(obj.ToString(), out charValue)) 51 | return charValue; 52 | else 53 | return '\0'; 54 | case TypeCode.Decimal: 55 | decimal decimalValue; 56 | if (decimal.TryParse(obj.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out decimalValue)) 57 | return decimalValue; 58 | else 59 | return 0M; 60 | case TypeCode.Single: 61 | float floatValue; 62 | if (float.TryParse(obj.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out floatValue)) 63 | return floatValue; 64 | else 65 | return 0F; 66 | case TypeCode.Double: 67 | double doubleValue; 68 | if (double.TryParse(obj.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue)) 69 | return doubleValue; 70 | else 71 | return 0D; 72 | case TypeCode.DBNull: 73 | case TypeCode.DateTime: 74 | return obj; 75 | default: 76 | return GetDefaultForType(type); 77 | } 78 | } 79 | private static object GetDefaultForType(Type type) 80 | { 81 | // Determine the default value for the given type 82 | if (type == typeof(string)) return string.Empty; 83 | if (type == typeof(byte)) return 0; 84 | if (type == typeof(sbyte)) return 0; 85 | if (type == typeof(bool)) return false; 86 | if (type == typeof(char)) return '\0'; 87 | if (type == typeof(DateTime)) return DateTime.MinValue; 88 | if (type == typeof(decimal)) return 0M; 89 | if (type == typeof(float)) return 0F; 90 | if (type == typeof(double)) return 0D; 91 | if (type == typeof(short)) return 0; 92 | if (type == typeof(int)) return 0; 93 | if (type == typeof(long)) return 0L; 94 | if (type == typeof(ushort)) return 0U; 95 | if (type == typeof(uint)) return 0U; 96 | if (type == typeof(ulong)) return 0UL; 97 | return null; // Fallback to null for unsupported types 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/FxEvents.Shared.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 7d81b737-b66a-4961-bfa0-6a69347eed78 7 | 8 | 9 | FxEvents.Shared 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/FxEvents.Shared.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7d81b737-b66a-4961-bfa0-6a69347eed78 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/JsonHelper.cs: -------------------------------------------------------------------------------- 1 | using FxEvents.Shared.Snowflakes; 2 | using FxEvents.Shared.Snowflakes.Serialization; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Serialization; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace FxEvents.Shared 9 | { 10 | public static class JsonHelper 11 | { 12 | public static readonly Dictionary Substitutes = new Dictionary(); 13 | private static SnowflakeConverter _snowflakeConverter = new SnowflakeConverter(); 14 | 15 | public static readonly List Converters = new List 16 | { 17 | _snowflakeConverter 18 | }; 19 | 20 | public static readonly JsonSerializerSettings Empty = new() 21 | { 22 | Converters = Converters, 23 | ContractResolver = new ContractResolver() 24 | }; 25 | 26 | public static readonly JsonSerializerSettings IgnoreJsonIgnoreAttributes = new() 27 | { 28 | ContractResolver = new IgnoreJsonAttributesResolver() 29 | }; 30 | 31 | public static readonly JsonSerializerSettings LowerCaseSettings = new() 32 | { 33 | Converters = Converters, 34 | ContractResolver = new ContractResolver 35 | { 36 | NamingStrategy = new SnakeCaseNamingStrategy() 37 | }, 38 | NullValueHandling = NullValueHandling.Ignore 39 | }; 40 | 41 | public static string ToJson(this object value, bool pretty = false, SnowflakeRepresentation representation = SnowflakeRepresentation.String, 42 | JsonSerializerSettings settings = null) 43 | { 44 | return (string)InvokeWithRepresentation( 45 | () => JsonConvert.SerializeObject(value, pretty ? Formatting.Indented : Formatting.None, settings ?? Empty), representation); 46 | } 47 | 48 | public static T FromJson(this string serialized, SnowflakeRepresentation representation = SnowflakeRepresentation.String, 49 | JsonSerializerSettings settings = null) => (T)FromJsonInternal(serialized, typeof(T), out _, representation, settings); 50 | 51 | public static object FromJson(this string serialized, Type type, SnowflakeRepresentation representation = SnowflakeRepresentation.String, 52 | JsonSerializerSettings settings = null) => FromJsonInternal(serialized, type, out _, representation, settings); 53 | 54 | public static T FromJson(this string serialized, out bool result, SnowflakeRepresentation representation = SnowflakeRepresentation.String, 55 | JsonSerializerSettings settings = null) 56 | { 57 | object value = FromJsonInternal(serialized, typeof(T), out bool transient, representation, settings); 58 | 59 | result = transient; 60 | return (T)value; 61 | } 62 | 63 | private static object FromJsonInternal(string serialized, Type type, out bool result, SnowflakeRepresentation representation, 64 | JsonSerializerSettings settings) 65 | { 66 | try 67 | { 68 | object deserialized = InvokeWithRepresentation(() => JsonConvert.DeserializeObject(serialized, type, settings ?? Empty), 69 | representation, false); 70 | 71 | result = true; 72 | 73 | return deserialized; 74 | } 75 | catch (Exception) 76 | { 77 | result = false; 78 | 79 | throw; 80 | } 81 | } 82 | 83 | private static object InvokeWithRepresentation(Func func, SnowflakeRepresentation representation, bool suppressErrors = true) 84 | { 85 | SnowflakeRepresentation transient = _snowflakeConverter.Representation; 86 | 87 | _snowflakeConverter.Representation = representation; 88 | 89 | try 90 | { 91 | return func.Invoke(); 92 | } 93 | catch (Exception) 94 | { 95 | if (!suppressErrors) 96 | throw; 97 | } 98 | 99 | _snowflakeConverter.Representation = transient; 100 | 101 | return null; 102 | } 103 | } 104 | 105 | internal class IgnoreJsonAttributesResolver : DefaultContractResolver 106 | { 107 | protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) 108 | { 109 | IList props = base.CreateProperties(type, memberSerialization); 110 | foreach (JsonProperty prop in props) 111 | { 112 | prop.Ignored = false; // Ignore [JsonIgnore] 113 | //prop.Converter = null; // Ignore [JsonConverter] 114 | //prop.PropertyName = prop.UnderlyingName; // Use original property name instead of [JsonProperty] name 115 | } 116 | return props; 117 | } 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Logger/ILogger.cs: -------------------------------------------------------------------------------- 1 | namespace Logger 2 | { 3 | public interface ILogger 4 | { 5 | 6 | /// 7 | /// Sends an green info message in console 8 | /// 9 | /// Text of the message 10 | void Info(string text); 11 | 12 | /// 13 | /// Sends a purple debug message in console. (it checks for 'fxevents_debug_mode' in the fxmanifest) 14 | /// 15 | /// Text of the message 16 | void Debug(string text); 17 | 18 | /// 19 | /// Sends a yellow Warning message 20 | /// 21 | /// Text of the message 22 | void Warning(string text); 23 | 24 | /// 25 | /// Sends a red Error message 26 | /// 27 | /// Text of the message 28 | void Error(string text); 29 | 30 | /// 31 | /// Sends a dark red Fatal Error message 32 | /// 33 | /// Text of the message 34 | void Fatal(string text); 35 | } 36 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Logger/Logger.cs: -------------------------------------------------------------------------------- 1 | using FxEvents; 2 | using System; 3 | 4 | namespace Logger 5 | { 6 | public class Log : ILogger 7 | { 8 | public Log() { } 9 | 10 | /// 11 | /// Sends an green info message in console 12 | /// 13 | /// Text of the message 14 | public void Info(string text) 15 | { 16 | string timestamp = $"{DateTime.Now:dd/MM/yyyy, HH:mm}"; 17 | string errorPrefix = "-- [INFO] -- "; 18 | string color = LoggerColors.LIGHT_GREEN; 19 | CitizenFX.Core.Debug.WriteLine($"{color}{timestamp} {errorPrefix} {text}.^7"); 20 | } 21 | 22 | /// 23 | /// Sends a purple debug message in console. (it checks for 'fxevents_debug_mode' in the fxmanifest) 24 | /// 25 | /// Text of the message 26 | public void Debug(string text) 27 | { 28 | if (!EventHub.Debug) return; 29 | string timestamp = $"{DateTime.Now:dd/MM/yyyy, HH:mm}"; 30 | string errorPrefix = "-- [DEBUG] -- "; 31 | string color = LoggerColors.LIGHT_BLUE; 32 | CitizenFX.Core.Debug.WriteLine($"{color}{timestamp} {errorPrefix} {text}.^7"); 33 | } 34 | 35 | /// 36 | /// Sends a yellow Warning message 37 | /// 38 | /// Text of the message 39 | public void Warning(string text) 40 | { 41 | string timestamp = $"{DateTime.Now:dd/MM/yyyy, HH:mm}"; 42 | string errorPrefix = "-- [WARNING] --"; 43 | string color = LoggerColors.YELLOW; 44 | CitizenFX.Core.Debug.WriteLine($"{color}{timestamp} {errorPrefix} {text}.^7"); 45 | } 46 | 47 | /// 48 | /// Sends a red Error message 49 | /// 50 | /// Text of the message 51 | public void Error(string text) 52 | { 53 | string timestamp = $"{DateTime.Now:dd/MM/yyyy, HH:mm}"; 54 | string errorPrefix = "-- [ERROR] -- "; 55 | string color = LoggerColors.LIGHT_RED; 56 | CitizenFX.Core.Debug.WriteLine($"{color}{timestamp} {errorPrefix} {text}.^7"); 57 | } 58 | 59 | /// 60 | /// Sends a dark red Fatal Error message 61 | /// 62 | /// Text of the message 63 | public void Fatal(string text) 64 | { 65 | string timestamp = $"{DateTime.Now:dd/MM/yyyy, HH:mm}"; 66 | string errorPrefix = "-- [FATAL] -- "; 67 | string color = LoggerColors.DARK_RED; 68 | CitizenFX.Core.Debug.WriteLine($"{color}{timestamp} {errorPrefix} {text}.^7"); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Logger/LoggerColors.cs: -------------------------------------------------------------------------------- 1 | namespace Logger 2 | { 3 | public static class LoggerColors 4 | { 5 | // Colors 6 | public const string LIGHT_RED = "^1"; 7 | public const string LIGHT_GREEN = "^2"; 8 | public const string YELLOW = "^3"; 9 | public const string DARK_BLUE = "^4"; 10 | public const string LIGHT_BLUE = "^5"; 11 | public const string PURPLE = "^6"; 12 | public const string WHITE = "^7"; 13 | public const string DARK_RED = "^8"; 14 | public const string PINK = "^9"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/Clock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Snowflakes 4 | { 5 | 6 | public static class Clock 7 | { 8 | public static long GetMilliseconds() => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; 9 | } 10 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/Program.cs: -------------------------------------------------------------------------------- 1 | /*using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace FxEvents.Shared.Snowflake 5 | { 6 | internal static class Program 7 | { 8 | public static void Main() 9 | { 10 | var timestamp = Clock.GetMilliseconds(); 11 | 12 | // Create the singleton instance generator with the specified instance id, 13 | // this id represents which machine or project generated this id, used to minify collisions and can be handy for identifiers. 14 | SnowflakeGenerator.Create(1); 15 | 16 | GenerateWave(); 17 | 18 | Console.WriteLine("..."); 19 | Task.Delay(100).GetAwaiter().GetResult(); 20 | 21 | GenerateWave(); 22 | Console.WriteLine($"Took {Clock.GetMilliseconds() - timestamp - 100}ms"); 23 | } 24 | 25 | private static void GenerateWave() 26 | { 27 | for (var index = 0; index < 10; index++) 28 | { 29 | // var snowflake = Snowflake.Parse( snowflake id ) 30 | var snowflake = Snowflake.Next(); 31 | 32 | // ... then you can deconstruct this id to extract the time, instance and sequence bits 33 | var fragments = snowflake.Deconstruct(); 34 | 35 | Console.WriteLine( 36 | $"#{index + 1}: {snowflake} (time = {fragments.Timestamp}, instance_id = {fragments.Instance}, sequence = {fragments.Sequence})"); 37 | } 38 | } 39 | } 40 | } 41 | */ -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/Serialization/SnowflakeConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace FxEvents.Shared.Snowflakes.Serialization 5 | { 6 | 7 | public class SnowflakeConverter : JsonConverter 8 | { 9 | public SnowflakeRepresentation Representation { get; set; } 10 | 11 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 12 | { 13 | if (value is Snowflake snowflake) 14 | { 15 | if (Representation == SnowflakeRepresentation.UInt) 16 | writer.WriteValue(snowflake.ToInt64()); 17 | else 18 | writer.WriteValue(snowflake.ToString()); 19 | } 20 | } 21 | 22 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 23 | { 24 | return Representation == SnowflakeRepresentation.UInt 25 | ? new Snowflake((long)(reader.Value ?? 0)) 26 | : new Snowflake(ulong.Parse((string)reader.Value ?? "0")); 27 | } 28 | 29 | public override bool CanConvert(Type objectType) => objectType == typeof(Snowflake); 30 | } 31 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/Snowflake.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace FxEvents.Shared.Snowflakes 5 | { 6 | 7 | public struct Snowflake : IEquatable 8 | { 9 | public static readonly Snowflake Empty = new Snowflake(0); 10 | 11 | private ulong _value; 12 | public ulong Value { get => _value; set => _value = value; } 13 | 14 | public Snowflake() { _value = 0; } 15 | public static Snowflake Next() 16 | { 17 | return SnowflakeGenerator.Instance.Next(); 18 | } 19 | 20 | public ulong ToInt64() => _value; 21 | 22 | public static Snowflake Parse(string id) => Parse(ulong.Parse(id)); 23 | 24 | public static Snowflake Parse(ulong id) 25 | { 26 | return new Snowflake(id); 27 | } 28 | 29 | public SnowflakeFragments Deconstruct() 30 | { 31 | SnowflakeGenerator instance = SnowflakeGenerator.Instance; 32 | 33 | return instance.Deconstruct((long)_value); 34 | } 35 | 36 | public Snowflake(ulong value) 37 | { 38 | _value = value; 39 | } 40 | 41 | public Snowflake(long value) : this((ulong)value) 42 | { 43 | } 44 | 45 | public Snowflake(string value) 46 | { 47 | ulong.TryParse(value, out _value); 48 | } 49 | 50 | public override string ToString() 51 | { 52 | return _value.ToString(); 53 | } 54 | 55 | public bool Equals(Snowflake other) 56 | { 57 | return _value == other._value; 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | return obj switch 63 | { 64 | ulong unsigned => _value == unsigned, 65 | long signed => _value == (ulong)signed, 66 | string serialized => ToString() == serialized, 67 | _ => obj is Snowflake other && Equals(other) 68 | }; 69 | } 70 | 71 | public override int GetHashCode() 72 | { 73 | return _value.GetHashCode(); 74 | } 75 | 76 | public static bool operator ==(Snowflake first, Snowflake second) 77 | { 78 | return first.Equals(Empty) ? second.Equals(Empty) : first.Equals(second); 79 | } 80 | 81 | public static bool operator !=(Snowflake first, Snowflake second) 82 | { 83 | return !(first == second); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/SnowflakeConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.Snowflakes 2 | { 3 | public class SnowflakeConfiguration 4 | { 5 | public byte TimestampBits { get; set; } 6 | public byte InstanceBits { get; set; } 7 | public byte SequenceBits { get; set; } 8 | 9 | public SnowflakeConfiguration(byte timestampBits, byte instanceBits, byte sequenceBits) 10 | { 11 | TimestampBits = timestampBits; 12 | InstanceBits = instanceBits; 13 | SequenceBits = sequenceBits; 14 | } 15 | 16 | public SnowflakeConfiguration() : this(42, 10, 12) 17 | { 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/SnowflakeFragments.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.Snowflakes 2 | { 3 | 4 | public class SnowflakeFragments 5 | { 6 | public long Timestamp { get; set; } 7 | public long Instance { get; set; } 8 | public long Sequence { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/SnowflakeGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.Snowflakes 4 | { 5 | 6 | public partial class SnowflakeGenerator 7 | { 8 | private readonly long _maskSequence; 9 | private readonly long _maskTime; 10 | private readonly long _maskInstance; 11 | private readonly int _shiftTime; 12 | private readonly int _shiftInstance; 13 | private readonly object _lock = new object(); 14 | private SnowflakeConfiguration _configuration; 15 | private long _instanceId; 16 | private long _sequence; 17 | private long _lastTimeslot; 18 | 19 | public static SnowflakeGenerator Create(short instance) 20 | { 21 | SnowflakeGenerator value = new SnowflakeGenerator(instance, new SnowflakeConfiguration()); 22 | 23 | _singletonInstance = value; 24 | 25 | return value; 26 | } 27 | 28 | public SnowflakeGenerator(short instance, SnowflakeConfiguration configuration) 29 | { 30 | _configuration = configuration; 31 | _instanceId = instance; 32 | _maskTime = GetMask(configuration.TimestampBits); 33 | _maskInstance = GetMask(configuration.InstanceBits); 34 | _maskSequence = GetMask(configuration.SequenceBits); 35 | _shiftTime = configuration.InstanceBits + configuration.SequenceBits; 36 | _shiftInstance = configuration.SequenceBits; 37 | } 38 | 39 | public Snowflake Next(long time) 40 | { 41 | lock (_lock) 42 | { 43 | long timestamp = time & _maskTime; 44 | 45 | if (_lastTimeslot == timestamp) 46 | { 47 | if (_sequence >= _maskSequence) 48 | { 49 | while (_lastTimeslot == Clock.GetMilliseconds()) 50 | { 51 | } 52 | } 53 | 54 | _sequence++; 55 | } 56 | else 57 | { 58 | _lastTimeslot = timestamp; 59 | _sequence = 0; 60 | } 61 | 62 | return new Snowflake((timestamp << _shiftTime) + (_instanceId << _shiftInstance) + _sequence); 63 | } 64 | } 65 | 66 | public Snowflake Next() => Next(Clock.GetMilliseconds()); 67 | 68 | public SnowflakeFragments Deconstruct(long value) 69 | { 70 | SnowflakeFragments fragments = new SnowflakeFragments 71 | { 72 | Sequence = value & _maskSequence, 73 | Instance = (value >> _shiftInstance) & _maskInstance, 74 | Timestamp = (value >> _shiftTime) & _maskTime 75 | }; 76 | 77 | return fragments; 78 | } 79 | 80 | private long GetMask(byte bits) => (1L << bits) - 1; 81 | 82 | public static SnowflakeGenerator Instance 83 | { 84 | get 85 | { 86 | if (_singletonInstance == null) throw new Exception("SnowflakeGenerator.Instance property was null"); 87 | 88 | return _singletonInstance; 89 | } 90 | } 91 | 92 | private static SnowflakeGenerator _singletonInstance; 93 | } 94 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/Snowflake/SnowflakeRepresentation.cs: -------------------------------------------------------------------------------- 1 | namespace FxEvents.Shared.Snowflakes 2 | { 3 | 4 | public enum SnowflakeRepresentation 5 | { 6 | UInt, 7 | String 8 | } 9 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace FxEvents.Shared 5 | { 6 | public class TypeConverter : JsonConverter 7 | { 8 | public Type DesiredType { get; set; } 9 | 10 | public TypeConverter(Type desiredType) 11 | { 12 | DesiredType = desiredType; 13 | } 14 | 15 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 16 | { 17 | return serializer.Deserialize(reader, DesiredType); 18 | } 19 | 20 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public override bool CanConvert(Type objectType) => objectType == DesiredType; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/Base64Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace FxEvents.Shared.TypeExtensions 5 | { 6 | 7 | public static class Base64Extensions 8 | { 9 | public static string ToBase64(this string value) 10 | { 11 | return Convert.ToBase64String(Encoding.UTF8.GetBytes(value)); 12 | } 13 | 14 | public static string FromBase64(this string serialized) 15 | { 16 | return Encoding.UTF8.GetString(Convert.FromBase64String(serialized)); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/BooleanExtensions.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace FxEvents.Shared.TypeExtensions 4 | { 5 | 6 | public static class BooleanExtensions 7 | { 8 | public static void Toggle(this ref bool value) 9 | { 10 | value = !value; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/DelegateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | 7 | namespace FxEvents.Shared.TypeExtensions 8 | { 9 | 10 | public static class DelegateExtensions 11 | { 12 | public static Delegate CreateDelegate(this MethodInfo method, object target) 13 | { 14 | bool action = method.ReturnType == typeof(void); 15 | System.Collections.Generic.IEnumerable types = method.GetParameters().Select(self => self.ParameterType); 16 | 17 | Func functionType; 18 | 19 | if (action) 20 | { 21 | functionType = Expression.GetActionType; 22 | } 23 | else 24 | { 25 | functionType = Expression.GetFuncType; 26 | types = types.Concat(new[] { method.ReturnType }); 27 | } 28 | 29 | return method.IsStatic 30 | ? Delegate.CreateDelegate(functionType(types.ToArray()), method) 31 | : Delegate.CreateDelegate(functionType(types.ToArray()), target, method.Name); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | 6 | namespace FxEvents.Shared.TypeExtensions 7 | { 8 | 9 | public static class EnumerableExtensions 10 | { 11 | public static string GetString(this List list, int index) 12 | { 13 | return list[index].ToString(); 14 | } 15 | 16 | public static void ForEach(this IEnumerable enumerable, Action action) 17 | { 18 | foreach (T value in enumerable) 19 | { 20 | action?.Invoke(value); 21 | } 22 | } 23 | 24 | public static IEnumerable Aggregate(this IEnumerable enumerable, Action aggregator) 25 | { 26 | List aggregate = enumerable.ToList(); 27 | 28 | aggregate.ForEach(self => aggregator?.Invoke(self)); 29 | 30 | return aggregate; 31 | } 32 | 33 | public static IEnumerable> WithIndex(this IEnumerable enumerable) 34 | { 35 | List> collection = new List>(); 36 | int index = -1; 37 | 38 | foreach (T entry in enumerable) 39 | { 40 | index++; 41 | collection.Add(Tuple.Create(entry, index)); 42 | } 43 | 44 | return collection; 45 | } 46 | 47 | public static IEnumerable Replace(this IEnumerable enumerable, Func filter, T replacement) 48 | { 49 | foreach (T value in enumerable) 50 | { 51 | bool condition = filter.Invoke(value); 52 | 53 | yield return condition ? replacement : value; 54 | } 55 | } 56 | 57 | public static void Replace(this List list, Func filter, T replacement) 58 | { 59 | int index = -1; 60 | 61 | for (int i = 0; i < list.Count; i++) 62 | { 63 | T entry = list[i]; 64 | 65 | if (filter.Invoke(entry)) 66 | { 67 | index = i; 68 | } 69 | } 70 | 71 | if (index != -1) 72 | list[index] = replacement; 73 | } 74 | 75 | public static string Collect(this IEnumerable enumerable, string separator = null) 76 | { 77 | return string.Join(separator ?? string.Empty, enumerable); 78 | } 79 | 80 | public static void AddOrUpdate(this IDictionary dictionary, TKey key, TValue def, Func mutation) 81 | { 82 | if (dictionary.TryGetValue(key, out TValue? value)) 83 | { 84 | dictionary[key] = mutation.Invoke(value); 85 | } 86 | else 87 | { 88 | dictionary.Add(key, def); 89 | } 90 | } 91 | public static IEnumerable> GetCombinations(this IEnumerable elements, int k) 92 | { 93 | if (k == 0) return new[] { new Type[0] }; 94 | 95 | return elements.SelectMany((e, i) => 96 | elements.Skip(i + 1).GetCombinations(k - 1).Select(c => new Type[] { e }.Concat(c))); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/KeyValuePairExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace FxEvents.Shared.TypeExtensions 5 | { 6 | public static class KeyValuePairExtensions 7 | { 8 | public static IDictionary ToDictionary(this IEnumerable> list) 9 | { 10 | return list.ToDictionary(x => x.Key, x => x.Value); 11 | } 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FxEvents.Shared.TypeExtensions 4 | { 5 | 6 | public static class ObjectExtensions 7 | { 8 | public static void Clone(this object source, object destination, bool defaults = true) 9 | { 10 | Type type = destination.GetType(); 11 | System.Reflection.PropertyInfo[] properties = source.GetType().GetProperties(); 12 | 13 | foreach (System.Reflection.PropertyInfo property in properties) 14 | { 15 | if (!property.CanRead) continue; 16 | 17 | System.Reflection.PropertyInfo target = type.GetProperty(property.Name); 18 | 19 | if ((target?.CanWrite ?? false) && target.PropertyType.IsAssignableFrom(property.PropertyType)) 20 | { 21 | bool primitive = property.PropertyType.IsPrimitive; 22 | object value = property.GetValue(source, null); 23 | 24 | if (!primitive) 25 | { 26 | value = value.ToJson(); 27 | } 28 | 29 | if (!defaults && value == null) continue; 30 | 31 | target.SetValue(destination, !primitive ? value.ToString().FromJson(property.PropertyType) : value, null); 32 | } 33 | } 34 | } 35 | 36 | public static object Clone(this object source, bool defaults = true) 37 | { 38 | object holder = Activator.CreateInstance(source.GetType()); 39 | 40 | Clone(source, holder, defaults); 41 | 42 | return holder; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | 4 | namespace FxEvents.Shared.TypeExtensions 5 | { 6 | 7 | public static class StringExtensions 8 | { 9 | public static bool Contains(this string source, string target, StringComparison comparison) 10 | { 11 | return source?.IndexOf(target, comparison) >= 0; 12 | } 13 | 14 | public static string Remove(this string source, string pattern) 15 | { 16 | return source.Replace(pattern, string.Empty); 17 | } 18 | 19 | public static string Surround(this string source, string value) 20 | { 21 | return !string.IsNullOrWhiteSpace(source) ? value + source + value : string.Empty; 22 | } 23 | 24 | public static int ToInt(this string source) 25 | { 26 | return int.Parse(source); 27 | } 28 | 29 | public static float ToFloat(this string source) 30 | { 31 | return float.Parse(source); 32 | } 33 | 34 | public static bool ToBool(this string source) 35 | { 36 | if (int.TryParse(source, out int number)) 37 | { 38 | return number >= 1; 39 | } 40 | 41 | return bool.Parse(source); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | 4 | namespace FxEvents.Shared.TypeExtensions 5 | { 6 | 7 | public static class TaskExtensions 8 | { 9 | public static async void InvokeAndForget(this Task task) 10 | { 11 | await task; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/FxEvents.Shared/TypeExtensions/TypeCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | /// 3 | /// A simple type cache to alleviate the reflection lookups. Checking for simple types is done once per type 4 | /// encountered. Rather than every time. (Saves many CPU cycles. Reflection is slow) 5 | /// 6 | /// 7 | 8 | namespace FxEvents.Shared.TypeExtensions 9 | { 10 | public static class TypeCache 11 | { 12 | static TypeCache() 13 | { 14 | Type = typeof(T); 15 | IsSimpleType = true; 16 | switch (Type.GetTypeCode(Type)) 17 | { 18 | case TypeCode.Object: 19 | case TypeCode.DBNull: 20 | case TypeCode.Empty: 21 | case TypeCode.DateTime: 22 | IsSimpleType = false; 23 | break; 24 | } 25 | } 26 | 27 | // ReSharper disable StaticMemberInGenericType 28 | public static bool IsSimpleType { get; } 29 | public static Type Type { get; } 30 | // ReSharper restore StaticMemberInGenericType 31 | } 32 | 33 | public static class TypeCache 34 | { 35 | public static bool IsSimpleType(this Type type) => Type.GetTypeCode(type) switch 36 | { 37 | TypeCode.Object or TypeCode.DBNull or TypeCode.Empty or TypeCode.DateTime => false, 38 | _ => true, 39 | }; 40 | } 41 | } --------------------------------------------------------------------------------