Compare commits

...

27 Commits

Author SHA1 Message Date
Ray
914acc2e1b Zero-terminate Data to prevent client-side bugs.
- Fixes chat messages displaying stale memory as the client discards the data length.
2024-05-16 21:30:35 +02:00
Ray
b4e6433545 Fix deprecation and nullability warnings. 2024-05-16 12:02:48 +02:00
Ray
63691f897b Update app target framework to .NET 8. 2024-05-16 12:02:01 +02:00
Ray
b509d7bdaf Update test dependencies. 2024-05-16 12:01:56 +02:00
Ray
046033bba7 Update platform toolset. 2024-05-16 12:01:14 +02:00
Ray
6dc2de2445 Update dependencies (same major version only). 2024-05-16 12:00:51 +02:00
Ray
48f1f91be3 Update copyright. 2024-05-16 11:58:06 +02:00
Ray
cbe1388fb0 Fix typo. 2021-09-12 16:56:41 +02:00
Ray
b68503e609 Release 4.0.4 2021-09-12 16:54:04 +02:00
Ray
5fdd97c5ad Add missing StrikeClusterDamage field. 2021-09-12 16:53:19 +02:00
Ray
6a418e84ae Update release notes. 2021-09-12 13:18:07 +02:00
Ray
8eb0a971e8 Release 4.0.3: Fix StackOverflowException when using legacy .NET versions.
Sadly requires all packages to update due to fixed version ProjectReferences.
2021-09-12 13:14:23 +02:00
Ray
6cba058138 Fix StreamShims Read method to not call itself. 2021-09-12 12:30:17 +02:00
Ray
03095916f1 MSBuild: Change common target paths in project files to fix building for DocFX. 2021-03-24 19:27:22 +01:00
Ray Koopa
9a218549e7 Mention new module folder. 2020-07-21 21:08:41 +02:00
Ray Koopa
a99a883f6f Move WormKit loaders, modules, and libraries to module sub folder. Add signature scanning and import replacing. 2020-07-21 21:06:03 +02:00
Ray Koopa
518207b7e6 Add AllowMultiInstance. 2020-07-20 20:17:20 +02:00
Ray Koopa
2b94e09e39 Move FrontendKitLib to WormKitTools to reuse it for other modules. 2020-07-20 18:38:10 +02:00
Ray Koopa
a314377c13 Move reusable FrontendKitWS code into own library. Fix small issue when determining fkNetcode INI path. 2020-07-16 16:01:27 +02:00
Ray Koopa
4e251959b3 Prevent trailing 0-bytes at the end of some sent Packet.Data. 2020-07-14 21:21:49 +02:00
Ray Koopa
2ea6af0324 Remove semaphores. 2020-07-14 21:11:05 +02:00
Ray Koopa
730b14cdfa Change version to 1.1.0 due to new features. 2020-07-14 21:00:28 +02:00
Ray Koopa
bb2b7e72b5 Rewrite server netcode to be asynchronous and support timeouts. Allow multiple proxy connections. 2020-07-14 20:53:11 +02:00
Ray Koopa
65522b6af5 Merge branch 'master' of https://gitlab.com/Syroot/Worms 2020-07-12 21:27:43 +02:00
Ray Koopa
4c27eb415c Read packets more strictly to prevent crashes from bogus data. 2020-07-12 21:27:32 +02:00
Ray Koopa
e9ba6895b8 Add FrontendKitWS and fkNetcode. 2020-07-12 18:37:31 +00:00
Ray Koopa
48e6988083 Use original logic if there's no fallback and / or webservice failed. 2020-07-12 20:20:32 +02:00
76 changed files with 2015 additions and 983 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2020 Syroot
Copyright (c) 2017-2024 Syroot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -28,12 +28,17 @@ This repository hosts .NET libraries which can import, modify, and export the fo
Implementation of formats listed above as unsupported is planned for a later date.
The libraries are available on [NuGet](https://www.nuget.org/packages?q=Syroot.Worms).
## Modules
* `fkNetcode`: Patches Worms 2's outdated external IP detection and resolves it properly via a web service.
* `fkDesPatch`: Unfinished module meant to replace functionality found in Sn*tch patcher.
* `FrontendKitWS`: WormKit-like module loader for patching the Worms 2 Frontend.
* `WormKitLib`: Utilities like signature scanning or import replacement to help in patching Worms executables.
## Tools
* `Syroot.Worms.Mgame.Launcher`: Creates a fake launch configuration to start OW or WWPA clients with.
* `Syroot.Worms.Mgame.GameServer`: Simulates OW or WWPA networking to allow playing games.
* `Syroot.Worms.Worms2.GameServer`: Simulates a Worms 2 server.
## Availability
The libraries are available on [NuGet](https://www.nuget.org/packages?q=Syroot.Worms).

View File

@ -49,240 +49,112 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = " Solution Items", " Solut
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Worms2.GameServer", "tool\Syroot.Worms.Worms2.GameServer\Syroot.Worms.Worms2.GameServer.csproj", "{13ABF717-5809-441D-A5D8-66E1EE75A390}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fkNetcode", "tool\fkNetcode\fkNetcode.vcxproj", "{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module Projects", "Module Projects", "{2A8C6AAA-BA1F-4FB6-A598-114930217B6C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FrontendKitWS", "tool\FrontendKitWS\FrontendKitWS.vcxproj", "{E391EA12-B929-466C-932F-DEF72B8CEB5D}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WormKitTools", "module\WormKitTools\WormKitTools.vcxproj", "{068A8647-0A66-4E39-983B-43ACEAC5C937}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FrontendKitWS", "module\FrontendKitWS\FrontendKitWS.vcxproj", "{E391EA12-B929-466C-932F-DEF72B8CEB5D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fkDesPatch", "module\fkDesPatch\fkDesPatch.vcxproj", "{6B4D57EA-4642-440A-AB62-2E011D7B64E1}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fkNetcode", "module\fkNetcode\fkNetcode.vcxproj", "{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wkUnlimiter", "module\wkUnlimiter\wkUnlimiter.vcxproj", "{CDED4B7C-91DF-45D3-9704-DB8750BDAF5B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|x64.ActiveCfg = Debug|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|x64.Build.0 = Debug|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|x86.ActiveCfg = Debug|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|x86.Build.0 = Debug|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.Build.0 = Release|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|x64.ActiveCfg = Release|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|x64.Build.0 = Release|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|x86.ActiveCfg = Release|Any CPU
{DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|x86.Build.0 = Release|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|x64.ActiveCfg = Debug|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|x64.Build.0 = Debug|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|x86.ActiveCfg = Debug|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|x86.Build.0 = Debug|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.Build.0 = Release|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|x64.ActiveCfg = Release|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|x64.Build.0 = Release|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|x86.ActiveCfg = Release|Any CPU
{0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|x86.Build.0 = Release|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.Build.0 = Debug|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|x64.ActiveCfg = Debug|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|x64.Build.0 = Debug|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|x86.ActiveCfg = Debug|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|x86.Build.0 = Debug|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.ActiveCfg = Release|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.Build.0 = Release|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|x64.ActiveCfg = Release|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|x64.Build.0 = Release|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|x86.ActiveCfg = Release|Any CPU
{510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|x86.Build.0 = Release|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|x64.ActiveCfg = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|x64.Build.0 = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|x86.ActiveCfg = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Debug|x86.Build.0 = Debug|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Release|Any CPU.Build.0 = Release|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Release|x64.ActiveCfg = Release|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Release|x64.Build.0 = Release|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Release|x86.ActiveCfg = Release|Any CPU
{D9694108-539C-4DEE-B5DD-284208D48B46}.Release|x86.Build.0 = Release|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Debug|x64.ActiveCfg = Debug|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Debug|x64.Build.0 = Debug|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Debug|x86.ActiveCfg = Debug|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Debug|x86.Build.0 = Debug|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Release|Any CPU.Build.0 = Release|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Release|x64.ActiveCfg = Release|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Release|x64.Build.0 = Release|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Release|x86.ActiveCfg = Release|Any CPU
{AC9F52FA-F552-49E0-83AE-79759BF44FED}.Release|x86.Build.0 = Release|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Debug|x64.ActiveCfg = Debug|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Debug|x64.Build.0 = Debug|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Debug|x86.ActiveCfg = Debug|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Debug|x86.Build.0 = Debug|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Release|Any CPU.Build.0 = Release|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Release|x64.ActiveCfg = Release|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Release|x64.Build.0 = Release|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Release|x86.ActiveCfg = Release|Any CPU
{4E124BBA-15B5-422E-93D5-96EA7D4180F3}.Release|x86.Build.0 = Release|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Debug|x64.ActiveCfg = Debug|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Debug|x64.Build.0 = Debug|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Debug|x86.ActiveCfg = Debug|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Debug|x86.Build.0 = Debug|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Release|Any CPU.Build.0 = Release|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Release|x64.ActiveCfg = Release|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Release|x64.Build.0 = Release|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Release|x86.ActiveCfg = Release|Any CPU
{BBCECFCC-F2FD-4C4D-A6E9-CC7AA5F10FDD}.Release|x86.Build.0 = Release|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Debug|x64.ActiveCfg = Debug|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Debug|x64.Build.0 = Debug|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Debug|x86.ActiveCfg = Debug|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Debug|x86.Build.0 = Debug|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Release|Any CPU.Build.0 = Release|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Release|x64.ActiveCfg = Release|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Release|x64.Build.0 = Release|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Release|x86.ActiveCfg = Release|Any CPU
{BD8079D9-FDB5-4C7D-8AD4-C80ADC4A4B26}.Release|x86.Build.0 = Release|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Debug|x64.ActiveCfg = Debug|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Debug|x64.Build.0 = Debug|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Debug|x86.ActiveCfg = Debug|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Debug|x86.Build.0 = Debug|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Release|Any CPU.Build.0 = Release|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Release|x64.ActiveCfg = Release|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Release|x64.Build.0 = Release|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Release|x86.ActiveCfg = Release|Any CPU
{239763CD-B7C9-4E8B-A84B-8F24DECF6D7B}.Release|x86.Build.0 = Release|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Debug|Any CPU.Build.0 = Debug|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Debug|x64.ActiveCfg = Debug|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Debug|x64.Build.0 = Debug|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Debug|x86.ActiveCfg = Debug|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Debug|x86.Build.0 = Debug|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Release|Any CPU.ActiveCfg = Release|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Release|Any CPU.Build.0 = Release|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Release|x64.ActiveCfg = Release|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Release|x64.Build.0 = Release|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Release|x86.ActiveCfg = Release|Any CPU
{748D16FF-35C6-4249-A1E8-04B2D239C954}.Release|x86.Build.0 = Release|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Debug|x64.ActiveCfg = Debug|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Debug|x64.Build.0 = Debug|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Debug|x86.ActiveCfg = Debug|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Debug|x86.Build.0 = Debug|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Release|Any CPU.Build.0 = Release|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Release|x64.ActiveCfg = Release|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Release|x64.Build.0 = Release|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Release|x86.ActiveCfg = Release|Any CPU
{3AAC4992-DDB9-4175-82B9-C57F22E12FF6}.Release|x86.Build.0 = Release|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Debug|x64.ActiveCfg = Debug|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Debug|x64.Build.0 = Debug|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Debug|x86.ActiveCfg = Debug|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Debug|x86.Build.0 = Debug|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Release|Any CPU.Build.0 = Release|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Release|x64.ActiveCfg = Release|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Release|x64.Build.0 = Release|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Release|x86.ActiveCfg = Release|Any CPU
{EF308D4E-26A0-471C-B764-9C4EB713BEAE}.Release|x86.Build.0 = Release|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Debug|x64.ActiveCfg = Debug|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Debug|x64.Build.0 = Debug|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Debug|x86.ActiveCfg = Debug|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Debug|x86.Build.0 = Debug|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|Any CPU.Build.0 = Release|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|x64.ActiveCfg = Release|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|x64.Build.0 = Release|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|x86.ActiveCfg = Release|Any CPU
{351B93B0-301F-42E1-82A0-7FA217154F5D}.Release|x86.Build.0 = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|x64.Build.0 = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Debug|x86.Build.0 = Debug|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|Any CPU.Build.0 = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|x64.ActiveCfg = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|x64.Build.0 = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|x86.ActiveCfg = Release|Any CPU
{392E4CA2-61D9-4BE1-B065-8801A9F102B8}.Release|x86.Build.0 = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|Any CPU.Build.0 = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|x64.ActiveCfg = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|x64.Build.0 = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|x86.ActiveCfg = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Debug|x86.Build.0 = Debug|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|Any CPU.ActiveCfg = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|Any CPU.Build.0 = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|x64.ActiveCfg = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|x64.Build.0 = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|x86.ActiveCfg = Release|Any CPU
{212F8090-9775-4098-BD44-9ABC01FBE553}.Release|x86.Build.0 = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|x64.ActiveCfg = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|x64.Build.0 = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|x86.ActiveCfg = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Debug|x86.Build.0 = Debug|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|Any CPU.Build.0 = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|x64.ActiveCfg = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|x64.Build.0 = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|x86.ActiveCfg = Release|Any CPU
{1FAB6B9F-2585-46DC-81C0-579DC808C389}.Release|x86.Build.0 = Release|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|x64.ActiveCfg = Debug|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|x64.Build.0 = Debug|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|x86.ActiveCfg = Debug|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Debug|x86.Build.0 = Debug|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|Any CPU.Build.0 = Release|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|x64.ActiveCfg = Release|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|x64.Build.0 = Release|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|x86.ActiveCfg = Release|Any CPU
{13ABF717-5809-441D-A5D8-66E1EE75A390}.Release|x86.Build.0 = Release|Any CPU
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Debug|Any CPU.ActiveCfg = Debug|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Debug|x64.ActiveCfg = Debug|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Debug|x86.ActiveCfg = Debug|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Debug|x86.Build.0 = Debug|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Release|Any CPU.ActiveCfg = Release|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Release|x64.ActiveCfg = Release|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Release|x86.ActiveCfg = Release|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Release|x86.Build.0 = Release|Win32
{068A8647-0A66-4E39-983B-43ACEAC5C937}.Debug|Any CPU.ActiveCfg = Debug|Win32
{068A8647-0A66-4E39-983B-43ACEAC5C937}.Debug|Any CPU.Build.0 = Debug|Win32
{068A8647-0A66-4E39-983B-43ACEAC5C937}.Release|Any CPU.ActiveCfg = Release|Win32
{068A8647-0A66-4E39-983B-43ACEAC5C937}.Release|Any CPU.Build.0 = Release|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Debug|Any CPU.ActiveCfg = Debug|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Debug|x64.ActiveCfg = Debug|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Debug|x86.ActiveCfg = Debug|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Debug|x86.Build.0 = Debug|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Debug|Any CPU.Build.0 = Debug|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Release|Any CPU.ActiveCfg = Release|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Release|x64.ActiveCfg = Release|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Release|x86.ActiveCfg = Release|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Release|x86.Build.0 = Release|Win32
{E391EA12-B929-466C-932F-DEF72B8CEB5D}.Release|Any CPU.Build.0 = Release|Win32
{6B4D57EA-4642-440A-AB62-2E011D7B64E1}.Debug|Any CPU.ActiveCfg = Debug|Win32
{6B4D57EA-4642-440A-AB62-2E011D7B64E1}.Debug|Any CPU.Build.0 = Debug|Win32
{6B4D57EA-4642-440A-AB62-2E011D7B64E1}.Release|Any CPU.ActiveCfg = Release|Win32
{6B4D57EA-4642-440A-AB62-2E011D7B64E1}.Release|Any CPU.Build.0 = Release|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Debug|Any CPU.ActiveCfg = Debug|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Debug|Any CPU.Build.0 = Debug|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Release|Any CPU.ActiveCfg = Release|Win32
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1}.Release|Any CPU.Build.0 = Release|Win32
{CDED4B7C-91DF-45D3-9704-DB8750BDAF5B}.Debug|Any CPU.ActiveCfg = Debug|Win32
{CDED4B7C-91DF-45D3-9704-DB8750BDAF5B}.Debug|Any CPU.Build.0 = Debug|Win32
{CDED4B7C-91DF-45D3-9704-DB8750BDAF5B}.Release|Any CPU.ActiveCfg = Release|Win32
{CDED4B7C-91DF-45D3-9704-DB8750BDAF5B}.Release|Any CPU.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -305,8 +177,11 @@ Global
{212F8090-9775-4098-BD44-9ABC01FBE553} = {99E56312-A064-4AD3-8443-0B56A5F76E6B}
{1FAB6B9F-2585-46DC-81C0-579DC808C389} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{13ABF717-5809-441D-A5D8-66E1EE75A390} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{E391EA12-B929-466C-932F-DEF72B8CEB5D} = {0B9B0B74-3EB1-46A4-BCCC-F2D6AE59A9EE}
{068A8647-0A66-4E39-983B-43ACEAC5C937} = {2A8C6AAA-BA1F-4FB6-A598-114930217B6C}
{E391EA12-B929-466C-932F-DEF72B8CEB5D} = {2A8C6AAA-BA1F-4FB6-A598-114930217B6C}
{6B4D57EA-4642-440A-AB62-2E011D7B64E1} = {2A8C6AAA-BA1F-4FB6-A598-114930217B6C}
{C4138811-7CFA-4826-A3DD-AF2B618EAFC1} = {2A8C6AAA-BA1F-4FB6-A598-114930217B6C}
{CDED4B7C-91DF-45D3-9704-DB8750BDAF5B} = {2A8C6AAA-BA1F-4FB6-A598-114930217B6C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1CD4EDE2-A5FB-4A58-A850-3506AB7E7B69}

View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)build.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\build.xml" />
<!-- Metadata -->
<PropertyGroup>
<AssemblyName>Syroot.Worms.Armageddon.ProjectX</AssemblyName>
<Description>.NET library for loading and modifying files of Worms Armageddon ProjectX.</Description>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageReleaseNotes>Fix StackOverflowException when using legacy .NET versions.</PackageReleaseNotes>
<PackageTags>$(PackageTags);project x;worms armageddon</PackageTags>
<Version>4.0.0</Version>
<Version>4.0.3</Version>
</PropertyGroup>
<!-- References -->

View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)build.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\build.xml" />
<!-- Metadata -->
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>.NET library for loading and modifying files of Team17's Worms Armageddon.</Description>
<PackageReleaseNotes>Overhaul implementation and documentation. Implement W:A V3 scheme format.</PackageReleaseNotes>
<PackageReleaseNotes>Fix StackOverflowException when using legacy .NET versions.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms armageddon</PackageTags>
<Version>4.0.2</Version>
<Version>4.0.3</Version>
</PropertyGroup>
<!-- References -->

View File

@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)build.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\build.xml" />
<!-- Metadata -->
<PropertyGroup>
<Description>.NET library for loading and modifying files of Mgame Worms clients.</Description>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageReleaseNotes>Fix StackOverflowException when using legacy .NET versions.</PackageReleaseNotes>
<PackageTags>$(PackageTags);online worms;worms world party aqua</PackageTags>
<Version>4.0.0</Version>
<Version>4.0.3</Version>
</PropertyGroup>
<!-- References -->
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />
</ItemGroup>

View File

@ -1,13 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)build.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\build.xml" />
<!-- Metadata -->
<PropertyGroup>
<AssemblyName>Syroot.Worms.WorldParty</AssemblyName>
<Description>.NET library for loading and modifying files of Team17's Worms World Party.</Description>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageReleaseNotes>Fix StackOverflowException when using legacy .NET versions.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms world party</PackageTags>
<Version>4.0.0</Version>
<Version>4.0.3</Version>
</PropertyGroup>
<!-- References -->

View File

@ -76,6 +76,8 @@ namespace Syroot.Worms.Worms2
public int DiggingTime;
/// <summary>Amount of airstrike clusters thrown.</summary>
public int StrikeClusterCount;
/// <summary>Damage of airstrike clusters measured in health points which also determines the blast radius.</summary>
public int StrikeClusterDamage;
/// <summary>Angle in which bullets are dispersed, measured in degrees.</summary>
public int BulletSpreadAngle;

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)build.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\build.xml" />
<!-- Metadata -->
<PropertyGroup>
<Description>.NET library for loading and modifying files of Team17's Worms 2.</Description>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<PackageReleaseNotes>Add missing SchemeWeapon StrikeClusterDamage field.</PackageReleaseNotes>
<PackageTags>$(PackageTags);worms 2</PackageTags>
<Version>4.0.0</Version>
<Version>4.0.4</Version>
</PropertyGroup>
<!-- References -->

View File

@ -0,0 +1,23 @@
using System;
using System.Text;
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents extension methods for <see cref="Encoding"/> instances.
/// </summary>
public static class EncodingExtensions
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public static string GetZeroTerminatedString(this Encoding encoding, ReadOnlySpan<byte> bytes)
=> encoding.GetString(bytes.Slice(0, Math.Max(0, bytes.IndexOf((byte)0))));
public static int GetZeroTerminatedBytes(this Encoding encoding, ReadOnlySpan<char> chars, Span<byte> bytes)
{
int length = encoding.GetBytes(chars, bytes);
bytes[length] = 0;
return ++length;
}
}
}

View File

@ -3,6 +3,7 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Syroot.Worms.Core;
namespace Syroot.Worms.IO
{
@ -21,13 +22,12 @@ namespace Syroot.Worms.IO
/// <param name="encoding">The 1-byte <see cref="Encoding"/> to use or <see langword="null"/> to use
/// <see cref="Encoding.ASCII"/>.</param>
/// <returns>The read string.</returns>
public static unsafe string ReadFixedString(this Stream stream, int length, Encoding? encoding = null)
public static string ReadFixedString(this Stream stream, int length, Encoding? encoding = null)
{
// Ensure to not try to decode any bytes after the 0 termination.
Span<byte> bytes = stackalloc byte[length];
stream.Read(bytes);
for (length = 0; length < bytes.Length && bytes[length] != 0; length++) ;
return (encoding ?? Encoding.ASCII).GetString(bytes.Slice(0, length));
return (encoding ?? Encoding.ASCII).GetZeroTerminatedString(bytes);
}
/// <summary>
@ -63,8 +63,7 @@ namespace Syroot.Worms.IO
public static void WriteFixedString(this Stream stream, string value, int length, Encoding? encoding = null)
{
Span<byte> bytes = stackalloc byte[length];
if (value != null)
(encoding ?? Encoding.ASCII).GetBytes(value.AsSpan(), bytes);
(encoding ?? Encoding.ASCII).GetBytes(value.AsSpan(), bytes);
stream.Write(bytes);
}
@ -117,42 +116,5 @@ namespace Syroot.Worms.IO
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="value">The instance to write into the current stream.</param>
public static void Save<T>(this Stream stream, T value) where T : ISaveable => value.Save(stream);
#if NETSTANDARD2_0
// ---- Backports ----
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the
/// position within the stream by the number of bytes read.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced
/// by the bytes read from the current source.</param>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes allocated
/// in the buffer if that many bytes are not currently available, or zero (0) if the end of the stream has been
/// reached.</returns>
/// <remarks>
/// This .NET Standard 2.0 backport requires a temporary copy.
/// </remarks>
public static int Read(this Stream stream, Span<byte> buffer)
{
byte[] bytes = new byte[buffer.Length];
int bytesRead = stream.Read(bytes);
bytes.AsSpan(0, bytesRead).CopyTo(buffer);
return bytesRead;
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the
/// current position within this stream by the number of bytes written.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance.</param>
/// <param name="value">A region of memory. This method copies the contents of this region to the current
/// stream.</param>
/// <remarks>
/// This .NET Standard 2.0 backport requires a temporary copy.
/// </remarks>
public static void Write(this Stream stream, ReadOnlySpan<byte> value) => stream.Write(value.ToArray());
#endif
}
}

View File

@ -0,0 +1,41 @@
#if NETSTANDARD2_0
namespace System.IO
{
/// <summary>
/// Represents extension methods for <see cref="Stream"/> instances.
/// </summary>
public static class StreamShims
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the
/// position within the stream by the number of bytes read.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance to write with.</param>
/// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced
/// by the bytes read from the current source.</param>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes allocated
/// in the buffer if that many bytes are not currently available, or zero (0) if the end of the stream has been
/// reached.</returns>
/// <remarks>This .NET Standard 2.0 backport requires a temporary copy.</remarks>
public static int Read(this Stream stream, Span<byte> buffer)
{
byte[] bytes = new byte[buffer.Length];
int bytesRead = stream.Read(bytes, 0, bytes.Length);
bytes.AsSpan(0, bytesRead).CopyTo(buffer);
return bytesRead;
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the
/// current position within this stream by the number of bytes written.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> instance.</param>
/// <param name="value">A region of memory. This method copies the contents of this region to the current
/// stream.</param>
/// <remarks>This .NET Standard 2.0 backport requires a temporary copy.</remarks>
public static void Write(this Stream stream, ReadOnlySpan<byte> value) => stream.Write(value.ToArray());
}
}
#endif

View File

@ -1,18 +1,10 @@
using System;
using System.Text;
namespace Syroot.Worms.IO
#if NETSTANDARD2_0
namespace System.Text
{
/// <summary>
/// Represents extension methods for <see cref="Encoding"/> instances.
/// </summary>
public static class EncodingExtensions
public static class EncodingShims
{
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
#if NETSTANDARD2_0
// ---- Backports ----
public unsafe static int GetBytes(this Encoding encoding, ReadOnlySpan<char> chars, Span<byte> bytes)
{
fixed (byte* pBytes = bytes)
@ -25,6 +17,6 @@ namespace Syroot.Worms.IO
fixed (byte* pBytes = bytes)
return encoding.GetString(pBytes, bytes.Length);
}
#endif
}
}
#endif

View File

@ -1,21 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)build.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\build.xml" />
<!-- Metadata -->
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>.NET library for loading and modifying files of Team17 Worms games.</Description>
<PackageReleaseNotes>Overhaul implementation and documentation.</PackageReleaseNotes>
<Version>4.0.0</Version>
<PackageReleaseNotes>Fix StackOverflowException when using legacy .NET versions.</PackageReleaseNotes>
<Version>4.0.3</Version>
</PropertyGroup>
<!-- References -->
<ItemGroup>
<PackageReference Include="Syroot.BinaryData" Version="5.2.1" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0" />
<PackageReference Include="Syroot.BinaryData" Version="5.2.2" />
<PackageReference Include="System.Drawing.Common" Version="4.7.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.0.0" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
</ItemGroup>
</Project>

View File

@ -35,13 +35,13 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{068a8647-0a66-4e39-983b-43aceac5c937}</ProjectGuid>
<RootNamespace>WormKitTools</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>WormKitTools</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;FRONTENDKITLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<ForcedIncludeFiles>pch.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;FRONTENDKITLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<ForcedIncludeFiles>pch.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\wkIni.cpp" />
<ClCompile Include="src\wkPatch.cpp" />
<ClCompile Include="src\wkUtils.cpp" />
<ClCompile Include="src\pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="src\wkExe.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\wkIni.h" />
<ClInclude Include="include\wkPatch.h" />
<ClInclude Include="include\wkUtils.h" />
<ClInclude Include="include\wkExe.h" />
<ClInclude Include="src\pch.h" />
</ItemGroup>
<ItemGroup>
<None Include="include\wkExe.inl" />
<None Include="include\wkPatch.inl" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\wkUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\wkPatch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\wkExe.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\wkIni.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\wkUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\wkPatch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\wkExe.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\wkIni.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="include\wkPatch.inl">
<Filter>Header Files</Filter>
</None>
<None Include="include\wkExe.inl">
<Filter>Header Files</Filter>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,88 @@
#pragma once
#include <stdint.h>
#include <string>
#include <Windows.h>
#include "wkPatch.h"
namespace wk
{
enum JumpType
{
JT_JMP, // jmp (0xE9)
JT_CALL, // call (0xE8)
JT_FARJMP, // farjump (0xEA)
JT_FARCALL, // farcall (0x9A)
JT_PUSHRET, // pushret
};
struct Exe
{
public:
// ---- FIELDS -------------------------------------------------------------------------------------------------
HANDLE Handle;
PIMAGE_DOS_HEADER DosHeader;
PIMAGE_NT_HEADERS NtHeader;
PIMAGE_FILE_HEADER FileHeader;
PIMAGE_OPTIONAL_HEADER OptHeader;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
Exe(HMODULE hModule = NULL);
// ---- METHODS ------------------------------------------------------------------------------------------------
/// <summary>
/// Returns the offset of the first match of the given signature. It must take the form "12 FF ?? EB 34", where
/// two question marks represent a wildcard.
/// </summary>
/// <param name="signature">The signature to find.</param>
/// <returns>The offset of the first match of the signature.</returns>
ULONG_PTR find(std::string signature);
/// <summary>
/// Returns the offset of the first match of the given signature. It is an array of bytes, where all values
/// being negative or bigger than 0xFF represent a wildcard.
/// </summary>
/// <param name="signature">The signature to find.</param>
/// <returns>The offset of the first match of the signature.</returns>
ULONG_PTR find(int16_t signature[], size_t signatureSize); // any non-uint8_t value is a wildcard
/// <summary>
/// Writes the given value to the specified offset.
/// </summary>
/// <typeparam name="T">The type of the value to write.</typeparam>
/// <param name="offset">The relative address at which to write the value.</param>
/// <param name="value">The value to write.</param>
template <class T> void set(ULONG_PTR offset, T value);
/// <summary>
/// Replaces the given import with the specified hook, which must have the same signature.
/// </summary>
/// <param name="import">The imported method to replace.</param>
/// <param name="hook">The method to replace the import with.</param>
void setImp(LPVOID import, LPVOID hook);
/// <summary>
/// Writes a jump to the given callee at the specified offset.
/// </summary>
/// <param name="offset">The relative address at which to write the value.</param>
/// <param name="size">The number of bytes overwritten for the jump opcode.</param>
/// <param name="callee">The called method.</param>
/// <param name="jumpType">The type of the jump to insert.</param>
void setJmp(ULONG_PTR offset, SIZE_T size, LPVOID callee, DWORD jumpType);
/// <summary>
/// Nops out the given number of bytes at the specified offset.
/// </summary>
/// <param name="offset">The relative address at which to nop out code.</param>
/// <param name="size">The number of bytes to nop out.</param>
void setNop(ULONG_PTR offset, SIZE_T size);
private:
LPVOID getAddress(ULONG_PTR offset);
PIMAGE_IMPORT_DESCRIPTOR getImports();
};
}
#include "wkExe.inl"

View File

@ -0,0 +1,9 @@
namespace wk
{
template <class T>
void Exe::set(ULONG_PTR offset, T newValue)
{
Patch patch(getAddress(offset), sizeof(T));
patch.write(newValue);
}
}

View File

@ -1,12 +1,12 @@
#pragma once
#include <Windows.h>
namespace fk
namespace wk
{
class Config
class Ini
{
public:
Config(LPCSTR fileName);
Ini(LPCSTR fileName);
void get(LPCSTR category, LPCSTR key, BOOL& result, UINT fallback) const;
void get(LPCSTR category, LPCSTR key, UINT& result, UINT fallback) const;

View File

@ -0,0 +1,31 @@
#pragma once
#include <Windows.h>
namespace wk
{
struct Patch
{
public:
// ---- FIELDS -------------------------------------------------------------------------------------------------
ULONG_PTR position;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
Patch(LPVOID address, SIZE_T size);
~Patch();
// ---- METHODS ------------------------------------------------------------------------------------------------
void close() const;
template <class T> void write(const T& value);
private:
// ---- FIELDS -------------------------------------------------------------------------------------------------
LPBYTE _address;
SIZE_T _size;
DWORD _oldProtect;
};
}
#include "wkPatch.inl"

View File

@ -0,0 +1,9 @@
namespace wk
{
template <class T>
void Patch::write(const T& value)
{
memcpy_s(_address + position, sizeof(T), &value, sizeof(T));
position += sizeof(T);
}
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <stdexcept>
#include <Windows.h>
namespace wk
{
// 0x 00 0000 00
// [Game][Version][Release]
enum GameID
{
GAMEID_NONE,
GAMEID_W2 = 0x20000000,
GAMEID_W2_1_05 = GAMEID_W2 | 0x010500,
GAMEID_W2_1_05_BR = GAMEID_W2_1_05 | 0x10, // Worms2 1.05 Br
GAMEID_W2_1_05_EN = GAMEID_W2_1_05 | 0x20, // Worms2 1.05 Du, En, Fr, It, Po, Sp, Sw
GAMEID_W2_1_05_GE = GAMEID_W2_1_05 | 0x30, // Worms2 1.05 De
GAMEID_W2_1_05_NA = GAMEID_W2_1_05 | 0x40, // Worms2 1.05 NA
GAMEID_W2_1_05_SA = GAMEID_W2_1_05 | 0x50, // Worms2 1.05 SA
GAMEID_W2_1_07 = GAMEID_W2 | 0x010700,
GAMEID_W2_1_07_TRY = GAMEID_W2_1_07 | 0x60, // Worms2 1.07 Trymedia
GAMEID_W2_LAST = GAMEID_W2_1_07_TRY,
GAMEID_WA = 0x30000000,
GAMEID_WA_3_6_31 = GAMEID_WA | 0x030630, // Worms Armageddon 3.6.31
GAMEID_WA_3_7_2_1 = GAMEID_WA | 0x030721, // Worms Armageddon 3.7.2.1
GAMEID_WA_3_8 = GAMEID_WA | 0x030800,
GAMEID_WA_3_8_CD = GAMEID_WA_3_8 | 0x10, // Worms Armageddon 3.8 CD
GAMEID_WA_LAST = GAMEID_WA_3_8_CD,
GAMEID_WWP = 0x40000000,
GAMEID_WWP_LAST = GAMEID_WWP,
GAMEID_OW = 0x50000000,
GAMEID_OW_LAST = GAMEID_OW,
GAMEID_WWPA = 0x60000000,
GAMEID_WWPA_LAST = GAMEID_WWPA,
};
int getGameID(DWORD timeDateStamp);
std::string getErrorMessage(int error);
}

View File

@ -0,0 +1,2 @@
// pch.cpp: source file corresponding to the pre-compiled header
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@ -0,0 +1,14 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif

View File

@ -0,0 +1,160 @@
#include "wkExe.h"
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <vector>
namespace wk
{
// ---- CONSTRUCTORS & DESTRUCTOR ----------------------------------------------------------------------------------
Exe::Exe(HMODULE hModule)
{
Handle = hModule == NULL ? GetModuleHandleA(NULL) : hModule;
DosHeader = (PIMAGE_DOS_HEADER)Handle;
NtHeader = (PIMAGE_NT_HEADERS)((DWORD)DosHeader + DosHeader->e_lfanew);
FileHeader = (PIMAGE_FILE_HEADER)&NtHeader->FileHeader;
OptHeader = (PIMAGE_OPTIONAL_HEADER)&NtHeader->OptionalHeader;
}
// ---- METHODS (PUBLIC) -------------------------------------------------------------------------------------------
ULONG_PTR Exe::find(std::string signature)
{
std::istringstream iss(signature);
std::istream_iterator<std::string> begin(iss), end;
std::vector<std::string> tokens(begin, end);
std::vector<int16_t> sig;
for (auto& token : tokens)
sig.push_back(token == "??" ? -1 : std::stoi(token, 0, 16));
return find(&sig[0], sig.size());
}
ULONG_PTR Exe::find(int16_t signature[], size_t signatureSize)
{
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(NtHeader);
for (int index = 0; index < FileHeader->NumberOfSections; index++)
{
if (sections[index].Characteristics != (IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ))
continue;
LPBYTE code = (LPBYTE)(ULONG_PTR)Handle + sections[index].VirtualAddress;
LPBYTE last = &code[sections[index].SizeOfRawData - signatureSize];
for (; code < last; code++)
{
bool found = true;
for (size_t i = 0; i < signatureSize; i++)
{
if (signature[i] < 0x00 || signature[i] > 0xFF || code[i] == (BYTE)signature[i])
{
continue;
}
else
{
found = false;
break;
}
}
if (found)
return (ULONG_PTR)code - (ULONG_PTR)Handle;
}
}
throw std::exception("Could not find signature.");
}
void Exe::setImp(LPVOID import, LPVOID hook)
{
bool success = false;
PIMAGE_IMPORT_DESCRIPTOR imports = getImports();
if (!imports)
throw std::exception("Import patching failed, import table not found.");
while (imports->Characteristics)
{
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((ULONG_PTR)Handle + imports->OriginalFirstThunk);
PIMAGE_THUNK_DATA table = (PIMAGE_THUNK_DATA)((ULONG_PTR)Handle + imports->FirstThunk);
while (thunk->u1.Ordinal)
{
if (table->u1.Function == (ULONG_PTR)import)
{
Patch patch(&table->u1.Function, sizeof(ULONG_PTR));
patch.write(hook);
success = true;
}
thunk++;
table++;
}
imports++;
}
if (!success)
throw std::exception("Import patching failed, import not found.");
}
void Exe::setJmp(ULONG_PTR offset, SIZE_T size, LPVOID callee, DWORD jumpType)
{
Patch patch(getAddress(offset), size);
if (size >= 5 && offset)
{
BYTE opSize, opCode;
switch (jumpType)
{
case JT_PUSHRET: opSize = 6; opCode = 0x68; break;
case JT_FARJMP: opSize = 7; opCode = 0xEA; break;
case JT_FARCALL: opSize = 7; opCode = 0x9A; break;
case JT_CALL: opSize = 5; opCode = 0xE8; break;
default: opSize = 5; opCode = 0xE9; break;
}
if (size < opSize)
throw std::exception("Not enough space to patch opcode.");
patch.write(opCode);
switch (opSize)
{
case 7:
patch.write((ULONG)callee);
patch.write<WORD>(0x23);
break;
case 6:
patch.write((ULONG)callee);
patch.write<BYTE>(0xC3);
break;
default:
patch.write((ULONG)callee - (ULONG_PTR)offset - 5);
break;
}
for (DWORD i = opSize; i < size; i++)
patch.write((uint8_t)0x90);
}
}
void Exe::setNop(ULONG_PTR offset, SIZE_T size)
{
Patch patch(getAddress(offset), size);
while (size--)
patch.write((uint8_t)0x90);
}
// ---- METHODS (PRIVATE) ------------------------------------------------------------------------------------------
LPVOID Exe::getAddress(ULONG_PTR offset)
{
return (LPVOID)((ULONG_PTR)Handle + offset);
}
PIMAGE_IMPORT_DESCRIPTOR Exe::getImports()
{
if ((FileHeader->SizeOfOptionalHeader
>= FIELD_OFFSET(IMAGE_OPTIONAL_HEADER, DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT + 1]))
&& OptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
&& OptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)
{
return (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)Handle
+ OptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
}
return NULL;
}
}

View File

@ -0,0 +1,39 @@
#include "wkIni.h"
#include <stdio.h>
namespace wk
{
Ini::Ini(LPCSTR fileName)
{
GetModuleFileName(NULL, _filePath, MAX_PATH);
char* sepIdx = strrchr(_filePath, '\\') + 1;
strcpy_s(sepIdx, MAX_PATH - (int)(sepIdx - _filePath), fileName);
}
void Ini::get(LPCSTR category, LPCSTR key, BOOL& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Ini::get(LPCSTR category, LPCSTR key, UINT& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Ini::get(LPCSTR category, LPCSTR key, LPSTR result, INT resultLength, LPCSTR fallback) const
{
GetPrivateProfileString(category, key, fallback, result, resultLength, _filePath);
}
void Ini::set(LPCSTR category, LPCSTR key, UINT value) const
{
CHAR buffer[32];
sprintf_s(buffer, "%d", value);
WritePrivateProfileString(category, key, buffer, _filePath);
}
void Ini::set(LPCSTR category, LPCSTR key, LPCSTR value) const
{
WritePrivateProfileString(category, key, value, _filePath);
}
}

View File

@ -0,0 +1,32 @@
#include "wkPatch.h"
#include <stdexcept>
namespace wk
{
// ---- CONSTRUCTORS & DESTRUCTOR ----------------------------------------------------------------------------------
Patch::Patch(LPVOID address, SIZE_T size)
: _address((LPBYTE)address)
, _size(size)
, position(0)
{
if (!_address || !_size)
throw std::invalid_argument("Address and size must not be 0.");
if (!VirtualProtect(_address, _size, PAGE_EXECUTE_READWRITE, &_oldProtect))
throw std::exception("VirtualProtect failed, call GetLastError for more info.");
}
Patch::~Patch()
{
close();
}
// ---- METHODS (PUBLIC) -------------------------------------------------------------------------------------------
void Patch::close() const
{
DWORD oldProtect;
if (!VirtualProtect(_address, _size, _oldProtect, &oldProtect))
throw std::exception("VirtualProtect failed, call GetLastError for more info.");
}
};

View File

@ -0,0 +1,46 @@
#include "wkUtils.h"
#include <string>
#include "wkPatch.h"
namespace wk
{
int getGameID(DWORD timeDateStamp)
{
switch (timeDateStamp)
{
case 0x3528DAFA: return GAMEID_W2_1_05_BR;
case 0x3528DCB1: return GAMEID_W2_1_05_EN;
case 0x3528DB52: return GAMEID_W2_1_05_GE;
case 0x3528DA98: return GAMEID_W2_1_05_NA;
case 0x3528DBDA: return GAMEID_W2_1_05_SA;
case 0x3587BE19: return GAMEID_W2_1_07_TRY;
case 0x4CE25091: return GAMEID_WA_3_6_31;
case 0x513D83BC: return GAMEID_WA_3_7_2_1;
case 0x5EF04515: return GAMEID_WA_3_8_CD;
default: return GAMEID_NONE;
}
}
std::string getErrorMessage(int error)
{
if (error == ERROR_SUCCESS)
return std::string();
LPTSTR buffer = NULL;
const DWORD cchMsg = FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL,
error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPTSTR>(&buffer), 0, NULL);
if (cchMsg > 0)
{
std::string message(buffer);
LocalFree(buffer);
return message;
}
else
{
CHAR buffer[32];
sprintf_s(buffer, "Error code 0x%08X.", error);
return buffer;
}
}
}

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{6b4d57ea-4642-440a-ab62-2e011d7b64e1}</ProjectGuid>
<RootNamespace>fkDesPatch</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;FKDESPATCH_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\WormKitTools\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;FKDESPATCH_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\WormKitTools\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WormKitTools\WormKitTools.vcxproj">
<Project>{068a8647-0a66-4e39-983b-43aceac5c937}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,84 @@
#define WIN32_LEAN_AND_MEAN
#include <stdint.h>
#include <Windows.h>
#include "wkExe.h"
#include "wkIni.h"
#include "wkUtils.h"
// ---- Initialization ----
BOOL iniAutoOssett;
void init()
{
wk::Ini ini("fkDesPatch.ini");
// Load INI settings.
ini.get("Frontend", "AutoOssett", iniAutoOssett, FALSE);
// Ensure INI file has been created with default setting.
ini.set("Frontend", "AutoOssett", iniAutoOssett);
}
// ---- Patch ----
void patch(wk::Exe& exe, int gameVersion)
{
if (gameVersion == wk::GAMEID_W2_1_07_TRY)
{
}
else
{
// getAddress file to code 0x400C02
if (iniAutoOssett)
exe.set(0x000446A2, (uint8_t)0xEB);
}
}
// ---- Main ----
int getVersion(DWORD timeDateStamp)
{
int id = wk::getGameID(timeDateStamp);
switch (id)
{
case wk::GAMEID_W2_1_05_BR:
case wk::GAMEID_W2_1_05_EN:
case wk::GAMEID_W2_1_05_GE:
case wk::GAMEID_W2_1_05_NA:
case wk::GAMEID_W2_1_05_SA:
case wk::GAMEID_W2_1_07_TRY:
return id;
default:
return 0;
}
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
{
wk::Exe exe;
int version = getVersion(exe.FileHeader->TimeDateStamp);
if (version)
{
init();
patch(exe, version);
}
else
{
MessageBox(NULL, "fkDesPatch is incompatible with your game version. Please run the 1.05 patch or 1.07 "
"release of Worms 2. Otherwise, you can delete the module to remove this warning.", "fkDesPatch",
MB_ICONWARNING);
}
}
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

View File

@ -51,8 +51,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEVERSION 1,0,1,0
PRODUCTVERSION 1,0,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -69,12 +69,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Syroot"
VALUE "FileDescription", "fkNetcode"
VALUE "FileVersion", "1.0.0.0"
VALUE "FileVersion", "1.0.1.0"
VALUE "InternalName", "fkNetcode.dll"
VALUE "LegalCopyright", "(c) Syroot, licensed under MIT"
VALUE "OriginalFilename", "fkNetcode.dll"
VALUE "ProductName", "fkNetcode"
VALUE "ProductVersion", "1.0.0.0"
VALUE "ProductVersion", "1.0.1.0"
END
END
BLOCK "VarFileInfo"

View File

@ -22,13 +22,13 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
@ -60,12 +60,13 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;FKNETCODE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\WormKitTools\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>wininet.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wininet.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@ -76,6 +77,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;FKNETCODE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\WormKitTools\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -83,30 +85,22 @@
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>wininet.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wininet.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="fkConfig.cpp" />
<ClCompile Include="fkPatch.cpp" />
<ClCompile Include="fkUtils.cpp" />
<ClCompile Include="fkWinHandle.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="PEInfo.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="fkConfig.h" />
<ClInclude Include="fkWinHandle.h" />
<ClInclude Include="fkPatch.h" />
<ClInclude Include="fkUtils.h" />
<ClInclude Include="PEInfo.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<None Include="fkPatch.inl" />
<ResourceCompile Include="fkNetcode.rc" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="fkNetcode.rc" />
<ProjectReference Include="..\WormKitTools\WormKitTools.vcxproj">
<Project>{068a8647-0a66-4e39-983b-43aceac5c937}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -0,0 +1,200 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinInet.h>
#include <winsock.h>
#include "wkIni.h"
#include "wkExe.h"
#include "wkPatch.h"
#include "wkUtils.h"
// ---- Initialization ----
CHAR iniFallbackIP[16];
CHAR iniServiceUrl[MAX_PATH];
BOOL iniShowErrors;
void init()
{
wk::Ini ini("fkNetcode.ini");
// Load INI settings.
ini.get("AddressResolval", "FallbackIP", iniFallbackIP, 16);
ini.get("AddressResolval", "ServiceUrl", iniServiceUrl, MAX_PATH, "http://ip.syroot.com");
ini.get("AddressResolval", "ShowErrors", iniShowErrors, TRUE);
// Ensure INI file has been created with default setting.
ini.set("AddressResolval", "FallbackIP", iniFallbackIP);
ini.set("AddressResolval", "ServiceUrl", iniServiceUrl);
ini.set("AddressResolval", "ShowErrors", iniShowErrors);
// Validate fallback IP.
BYTE b;
if (*iniFallbackIP && sscanf_s(iniFallbackIP, "%hhu.%hhu.%hhu.%hhu", &b, &b, &b, &b) != 4)
{
*iniFallbackIP = NULL;
MessageBox(NULL, "Invalid fallback IP setting in fkNetcode.ini has been ignored.", "fkNetcode", MB_ICONWARNING);
}
}
// ---- Patch: IP Resolval ----
CHAR cachedIP[16] = {};
bool resolveIPCached(LPSTR buffer)
{
if (!*cachedIP)
return false;
lstrcpy(buffer, cachedIP);
return true;
}
bool resolveIPExternal(LPSTR buffer)
{
if (!*iniServiceUrl)
return false;
// Query a web service which replies with the IP in plain text.
HINTERNET hInternet = 0, hFile = 0;
if (hInternet = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0))
{
if (hFile = InternetOpenUrl(hInternet, iniServiceUrl, NULL, 0,
INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_RELOAD, NULL))
{
DWORD responseLength = 0;
CHAR response[16];
if (InternetReadFile(hFile, response, 16, &responseLength))
{
if (responseLength >= 8)
{
response[responseLength] = '\0';
BYTE temp;
if (sscanf_s(response, "%hhu.%hhu.%hhu.%hhu", &temp, &temp, &temp, &temp) == 4)
lstrcpy(buffer, response);
else
SetLastError(0x20000002);
}
else
{
SetLastError(0x20000001);
}
}
}
}
DWORD error = GetLastError();
if (hFile) InternetCloseHandle(hFile);
if (hInternet) InternetCloseHandle(hInternet);
if (error && iniShowErrors)
{
CHAR msg[512];
sprintf_s(msg, "Could not resolve your IP through the web service. %s", wk::getErrorMessage(error).c_str());
MessageBox(NULL, msg, "fkNetcode", MB_ICONWARNING);
}
return !error;
}
bool resolveIPFallback(LPSTR buffer)
{
if (!*iniFallbackIP)
return false;
lstrcpy(buffer, iniFallbackIP);
return true;
}
bool resolveIPOriginal(LPSTR buffer)
{
// Use the original logic to "resolve" the (NAT) IP.
CHAR hostName[200];
hostent* host;
if (gethostname(hostName, 200) || !(host = gethostbyname(hostName)))
return false;
sprintf_s(hostName, "%hhu.%hhu.%hhu.%hhu",
host->h_addr_list[0][0],
host->h_addr_list[0][1],
host->h_addr_list[0][2],
host->h_addr_list[0][3]);
lstrcpy(buffer, hostName);
return true;
}
bool __stdcall patchResolveIP(LPSTR buffer, int bufferLength)
{
// Return value not used by W2, but meant to be 0 if no error.
if (resolveIPCached(buffer) || resolveIPExternal(buffer) || resolveIPFallback(buffer) || resolveIPOriginal(buffer))
{
lstrcpy(cachedIP, buffer);
return false;
}
else
{
return true;
}
}
// ---- Patch ----
void patch(wk::Exe& exe, int gameVersion)
{
exe.setJmp(0x00001799, 5, &patchResolveIP, wk::JT_JMP); // replace IP resolve with web service
if (gameVersion == wk::GAMEID_W2_1_07_TRY)
{
exe.setNop(0x00053B96, 5); // prevent overriding IP with user name
exe.setNop(0x00054693, 5); // prevent overriding IP with NAT IP
exe.setNop(0x00054635, 11); // useless sleep when connecting to server
}
else
{
exe.setNop(0x00053E96, 5); // prevent overriding IP with user name
exe.setNop(0x00054935, 11); // useless sleep when connecting to server
exe.setNop(0x00054993, 5); // prevent overriding IP with NAT IP
}
}
// ---- Main ----
int getVersion(DWORD timeDateStamp)
{
int id = wk::getGameID(timeDateStamp);
switch (id)
{
case wk::GAMEID_W2_1_05_BR:
case wk::GAMEID_W2_1_05_EN:
case wk::GAMEID_W2_1_05_GE:
case wk::GAMEID_W2_1_05_NA:
case wk::GAMEID_W2_1_05_SA:
case wk::GAMEID_W2_1_07_TRY:
return id;
default:
return 0;
}
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
{
wk::Exe exe;
int version = getVersion(exe.FileHeader->TimeDateStamp);
if (version)
{
init();
patch(exe, version);
}
else
{
MessageBox(NULL, "fkNetcode is incompatible with your game version. Please run the 1.05 patch or 1.07 "
"release of Worms 2. Otherwise, you can delete the module to remove this warning.", "fkNetcode",
MB_ICONWARNING);
}
}
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

View File

@ -0,0 +1,132 @@
#define WIN32_LEAN_AND_MEAN
#include <stdint.h>
#include <Windows.h>
#include "wkConfig.h"
#include "wkExe.h"
#include "wkUtils.h"
// ---- Initialization ----
BOOL iniAllowMultiInstance;
BOOL iniEnableAllControls;
BOOL iniShowAllControls;
BOOL iniUnlockCamera;
BOOL iniUnlockCursor;
void init(int gameID)
{
wk::Ini ini("wkUnlimiter.ini");
// Load INI settings.
ini.get("Common", "AllowMultiInstance", iniAllowMultiInstance, TRUE);
ini.get("Frontend", "EnableAllControls", iniEnableAllControls, TRUE);
ini.get("Frontend", "ShowAllControls", iniShowAllControls, FALSE);
if (gameID == wk::GAMEID_WA_3_8_CD)
{
ini.get("Game", "UnlockCamera", iniUnlockCamera, TRUE);
ini.get("Game", "UnlockCursor", iniUnlockCursor, TRUE);
}
// Ensure INI file has been created with default setting.
ini.set("Common", "AllowMultiInstance", iniAllowMultiInstance);
ini.set("Frontend", "EnableAllControls", iniEnableAllControls);
ini.set("Frontend", "ShowAllControls", iniShowAllControls);
if (gameID == wk::GAMEID_WA_3_8_CD)
{
ini.set("Game", "UnlockCamera", iniUnlockCamera);
ini.set("Game", "UnlockCursor", iniUnlockCursor);
}
}
// ---- Patch ----
HANDLE WINAPI Kernel32_CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,
LONG lMaximumCount, LPCSTR lpName)
{
HANDLE handle = CreateSemaphoreA(lpSemaphoreAttributes, lInitialCount, lMaximumCount, lpName);
SetLastError(ERROR_SUCCESS);
return handle;
}
void patch(wk::Exe& exe, int gameID)
{
if (iniAllowMultiInstance)
{
// Requires WormKitDS as game loads modules after this
exe.setImp(CreateSemaphoreA, Kernel32_CreateSemaphoreA);
}
if (iniEnableAllControls)
{
// CWnd::EnableWindow(bEnable) -> CWnd::EnableWindow(TRUE)
exe.set(exe.find("C2 04 00 8B 49 50 8B 01 FF A0 A8") - 13, 0x9090016A);
}
if (iniShowAllControls)
{
// CWnd::ShowWindow(bShow) -> CWnd::ShowWindow(TRUE)
exe.set(exe.find("C2 04 00 8B 49 50 8B 01 FF A0 A0") - 13, 0x9090016A);
}
if (iniUnlockCamera)
{
exe.setNop(exe.find("89 13 EB 06 3B C7 7E 02"), 2); // X- axis
exe.setNop(exe.find("89 3B 8B 43 04 3B C1 7D 09 5F 5E"), 2); // X+ axis
exe.setNop(exe.find("89 4B 04 5B C2 14 00 3B C6 7E 03"), 3); // Y- axis
exe.setNop(exe.find("89 73 04 5F 5E 5B C2 14 00"), 3); // Y+ axis
}
if (iniUnlockCursor)
{
exe.setNop(0x00159A72, 6); // X- axis 1
exe.setNop(0x00159A8F, 6); // X+ axis 1
exe.setNop(0x00159AE5, 6); // Y+ axis 1
exe.setNop(0x00159B00, 10); // Y- axis 1 cave
exe.setNop(0x00159B19, 6); // Y- axis 1 island
exe.setNop(0x00159EAD, 6); // X- axis 2
exe.setNop(0x00159ECA, 6); // X+ axis 2
exe.setNop(0x00159EE7, 6); // Y+ axis 2
exe.setNop(0x00159F02, 10); // Y- axis 2 cave
exe.setNop(0x00159F1B, 6); // Y- axis 2 island
}
}
// ---- Main ----
int getVersion(DWORD timeDateStamp)
{
int id = wk::getGameID(timeDateStamp);
switch (id)
{
case wk::GAMEID_WA_3_6_31:
case wk::GAMEID_WA_3_7_2_1:
case wk::GAMEID_WA_3_8_CD:
return id;
default:
return 0;
}
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
{
wk::Exe exe;
int version = getVersion(exe.FileHeader->TimeDateStamp);
if (version)
{
init(version);
patch(exe, version);
}
else
{
MessageBox(NULL, "wkUnlimiter is incompatible with your game version. Please run the 3.6.31.0, "
"3.7.2.1, or 3.8 CD release of Worms Armageddon. Otherwise, you can delete the module to remove "
"this warning.", "wkUnlimiter", MB_ICONWARNING);
}
}
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

View File

@ -0,0 +1,39 @@
#include "wkConfig.h"
#include <stdio.h>
namespace wk
{
Ini::Ini(LPCSTR fileName)
{
GetModuleFileName(NULL, _filePath, MAX_PATH);
char* sepIdx = strrchr(_filePath, '\\') + 1;
strcpy_s(sepIdx, MAX_PATH - (int)(sepIdx - _filePath), fileName);
}
void Ini::get(LPCSTR category, LPCSTR key, BOOL& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Ini::get(LPCSTR category, LPCSTR key, UINT& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Ini::get(LPCSTR category, LPCSTR key, LPSTR result, INT resultLength, LPCSTR fallback) const
{
GetPrivateProfileString(category, key, fallback, result, resultLength, _filePath);
}
void Ini::set(LPCSTR category, LPCSTR key, UINT value) const
{
CHAR buffer[32];
sprintf_s(buffer, "%d", value);
WritePrivateProfileString(category, key, buffer, _filePath);
}
void Ini::set(LPCSTR category, LPCSTR key, LPCSTR value) const
{
WritePrivateProfileString(category, key, value, _filePath);
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <Windows.h>
namespace wk
{
class Ini
{
public:
Ini(LPCSTR fileName);
void get(LPCSTR category, LPCSTR key, BOOL& result, UINT fallback) const;
void get(LPCSTR category, LPCSTR key, UINT& result, UINT fallback) const;
void get(LPCSTR category, LPCSTR key, LPSTR result, INT resultLength, LPCSTR fallback = NULL) const;
void set(LPCSTR category, LPCSTR key, UINT value) const;
void set(LPCSTR category, LPCSTR key, LPCSTR value) const;
private:
CHAR _filePath[MAX_PATH];
};
}

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{cded4b7c-91df-45d3-9704-db8750bdaf5b}</ProjectGuid>
<RootNamespace>wkUnlimiter</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>bin\$(Configuration)\</OutDir>
<IntDir>obj\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;WKUNLIMITER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\WormKitTools\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;WKUNLIMITER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\WormKitTools\include\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="wkConfig.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="wkConfig.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WormKitTools\WormKitTools.vcxproj">
<Project>{068a8647-0a66-4e39-983b-43aceac5c937}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="wkConfig.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="wkConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -10,9 +10,9 @@
<!-- References -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
</ItemGroup>
<!-- Files Linking -->

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)test.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\test.xml" />
<!-- References -->
<ItemGroup>

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)test.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\test.xml" />
<!-- References -->
<ItemGroup>

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)test.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\test.xml" />
<!-- References -->
<ItemGroup>

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)test.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\test.xml" />
<!-- References -->
<ItemGroup>

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)test.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\test.xml" />
<!-- References -->
<ItemGroup>

View File

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)test.xml" />
<Import Project="$(MSBuildThisFileDirectory)..\..\test.xml" />
<!-- References -->
<ItemGroup>

View File

@ -1,4 +1,5 @@
using System.Net;
using System.Net.Http;
namespace Syroot.Worms.Mgame.GameServer
{
@ -33,8 +34,9 @@ namespace Syroot.Worms.Mgame.GameServer
// Retrieve external IP if not yet done and given IP is invalid.
if (_ipAddress == null && (IP == null || !IPAddress.TryParse(IP, out _ipAddress)))
{
using WebClient webClient = new WebClient();
_ipAddress = IPAddress.Parse(webClient.DownloadString("https://ip.syroot.com"));
using HttpClient httpClient = new HttpClient();
_ipAddress = IPAddress.Parse(
httpClient.GetStringAsync("https://ip.syroot.com").GetAwaiter().GetResult());
}
return _ipAddress;
}

View File

@ -4,12 +4,12 @@
<AssemblyName>Server</AssemblyName>
<LangVersion>latest</LangVersion>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.32" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.32" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.32" />
<PackageReference Include="Syroot.BinaryData.Memory" Version="5.2.2" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
<ProjectReference Include="..\..\library\Syroot.Worms.Mgame\Syroot.Worms.Mgame.csproj" />

View File

@ -16,9 +16,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="4.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.32" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.32" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.32" />
<ProjectReference Include="..\..\library\Syroot.Worms.Mgame\Syroot.Worms.Mgame.csproj" />
<ProjectReference Include="..\..\library\Syroot.Worms\Syroot.Worms.csproj" />
<Reference Include="System.Windows.Forms" />

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\library\Syroot.Worms.Armageddon.ProjectX\Syroot.Worms.Armageddon.ProjectX.csproj" />

View File

@ -1,8 +1,4 @@
using System;
using System.IO;
using System.Text;
using Syroot.BinaryData;
using Syroot.Worms.IO;
using System.Text;
namespace Syroot.Worms.Worms2.GameServer
{
@ -55,57 +51,57 @@ namespace Syroot.Worms.Worms2.GameServer
/// <summary>
/// Gets or sets <see cref="PacketCode"/> describing the action of the packet.
/// </summary>
internal PacketCode Code { get; set; }
internal PacketCode Code;
/// <summary>
/// Gets or sets a parameter for the action.
/// </summary>
internal int? Value0 { get; set; }
internal int? Value0;
/// <summary>
/// Gets or sets a parameter for the action.
/// </summary>
internal int? Value1 { get; set; }
internal int? Value1;
/// <summary>
/// Gets or sets a parameter for the action.
/// </summary>
internal int? Value2 { get; set; }
internal int? Value2;
/// <summary>
/// Gets or sets a parameter for the action.
/// </summary>
internal int? Value3 { get; set; }
internal int? Value3;
/// <summary>
/// Gets or sets a parameter for the action.
/// </summary>
internal int? Value4 { get; set; }
internal int? Value4;
/// <summary>
/// Gets or sets a parameter for the action.
/// </summary>
internal int? Value10 { get; set; }
internal int? Value10;
/// <summary>
/// Gets or sets a textual parameter for the action.
/// </summary>
internal string? Data { get; set; }
internal string? Data;
/// <summary>
/// Gets or sets an error code returned from the server after executing the action.
/// </summary>
internal int? Error { get; set; }
internal int? Error;
/// <summary>
/// Gets or sets a named parameter for the action.
/// </summary>
internal string? Name { get; set; }
internal string? Name;
/// <summary>
/// Gets or sets a <see cref="SessionInfo"/> for the action.
/// </summary>
internal SessionInfo? Session { get; set; }
internal SessionInfo? Session;
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
@ -113,7 +109,6 @@ namespace Syroot.Worms.Worms2.GameServer
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"{Code:D} {Code}");
if (Value0.HasValue) sb.AppendLine($" {nameof(Value0),7}: {Value0:X8}");
if (Value1.HasValue) sb.AppendLine($" {nameof(Value1),7}: {Value1:X8}");
@ -125,97 +120,7 @@ namespace Syroot.Worms.Worms2.GameServer
if (Error.HasValue) sb.AppendLine($" {nameof(Error),7}: {Error:X8}");
if (Name != null) sb.AppendLine($" {nameof(Name),7}: {Name}");
if (Session.HasValue) sb.AppendLine($" {nameof(Session),7}: {Session}");
return sb.ToString().TrimEnd();
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Blocks and reads the packet data from the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read the packet data from.</param>
internal void Receive(Stream stream)
{
int dataLength = 0;
Code = (PacketCode)stream.ReadInt32();
Flags flags = (Flags)stream.ReadInt32();
if (flags.HasFlag(Flags.Value0)) Value0 = stream.ReadInt32();
if (flags.HasFlag(Flags.Value1)) Value1 = stream.ReadInt32();
if (flags.HasFlag(Flags.Value2)) Value2 = stream.ReadInt32();
if (flags.HasFlag(Flags.Value3)) Value3 = stream.ReadInt32();
if (flags.HasFlag(Flags.Value4)) Value4 = stream.ReadInt32();
if (flags.HasFlag(Flags.Value10)) Value10 = stream.ReadInt32();
if (flags.HasFlag(Flags.DataLength)) dataLength = stream.ReadInt32();
if (flags.HasFlag(Flags.Data) && dataLength != 0) Data = stream.ReadFixedString(dataLength, Encodings.Windows1252);
if (flags.HasFlag(Flags.Error)) Error = stream.ReadInt32();
if (flags.HasFlag(Flags.Name)) Name = stream.ReadFixedString(20, Encodings.Windows1252);
if (flags.HasFlag(Flags.Session)) Session = stream.ReadStruct<SessionInfo>();
}
/// <summary>
/// Blocks and writes the packet data to the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write the packet data to.</param>
internal void Send(Stream stream)
{
stream.WriteInt32((int)Code);
stream.WriteInt32((int)GetFlags());
if (Value0.HasValue) stream.WriteInt32(Value0.Value);
if (Value1.HasValue) stream.WriteInt32(Value1.Value);
if (Value2.HasValue) stream.WriteInt32(Value2.Value);
if (Value3.HasValue) stream.WriteInt32(Value3.Value);
if (Value4.HasValue) stream.WriteInt32(Value4.Value);
if (Value10.HasValue) stream.WriteInt32(Value10.Value);
if (Data != null)
{
stream.WriteInt32(Data.Length + 1);
stream.WriteFixedString(Data, Data.Length + 1, Encodings.Windows1252);
}
if (Error.HasValue) stream.WriteInt32(Error.Value);
if (Name != null) stream.WriteFixedString(Name, 20, Encodings.Windows1252);
if (Session.HasValue) stream.WriteStruct(Session.Value);
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private Flags GetFlags()
{
Flags flags = Flags.None;
if (Value0.HasValue) flags |= Flags.Value0;
if (Value1.HasValue) flags |= Flags.Value1;
if (Value2.HasValue) flags |= Flags.Value2;
if (Value3.HasValue) flags |= Flags.Value3;
if (Value4.HasValue) flags |= Flags.Value4;
if (Value10.HasValue) flags |= Flags.Value10;
if (Data != null)
{
flags |= Flags.DataLength;
flags |= Flags.Data;
}
if (Error.HasValue) flags |= Flags.Error;
if (Name != null) flags |= Flags.Name;
if (Session.HasValue) flags |= Flags.Session;
return flags;
}
// ---- CLASSES, STRUCTS & ENUMS -------------------------------------------------------------------------------
[Flags]
private enum Flags : int
{
None,
Value0 = 1 << 0,
Value1 = 1 << 1,
Value2 = 1 << 2,
Value3 = 1 << 3,
Value4 = 1 << 4,
Value10 = 1 << 10,
DataLength = 1 << 5,
Data = 1 << 6,
Error = 1 << 7,
Name = 1 << 8,
Session = 1 << 9
}
}
}

View File

@ -1,19 +1,32 @@
using System.IO;
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Syroot.BinaryData.Core;
using Syroot.Worms.Core;
namespace Syroot.Worms.Worms2.GameServer
{
/// <summary>
/// Represents a duplex connection to a client, allowing to receive and send <see cref="Packet"/> instances.
/// </summary>
internal class PacketConnection
internal sealed class PacketConnection
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _maxDataSize = 0x1000;
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly Stream _stream;
private readonly object _recvLock = new object();
private readonly object _sendLock = new object();
private readonly PipeReader _reader;
private readonly PipeWriter _writer;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
@ -24,8 +37,11 @@ namespace Syroot.Worms.Worms2.GameServer
/// <param name="client">The <see cref="TcpClient"/> to communicate with.</param>
internal PacketConnection(TcpClient client)
{
_stream = client.GetStream();
RemoteEndPoint = (IPEndPoint)client.Client.RemoteEndPoint;
Stream stream = client.GetStream();
_reader = PipeReader.Create(stream);
_writer = PipeWriter.Create(stream);
RemoteEndPoint = client.Client.RemoteEndPoint as IPEndPoint
?? throw new ArgumentException("TCP client is not connected.", nameof(client));
}
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
@ -38,27 +54,219 @@ namespace Syroot.Worms.Worms2.GameServer
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Blocks until a <see cref="Packet"/> was received, and returns it.
/// Receives a <see cref="Packet"/> instance asynchronously.
/// </summary>
/// <returns>The received <see cref="Packet"/>.</returns>
internal Packet Receive()
/// <returns>The read <see cref="Packet"/> instance.</returns>
internal async ValueTask<Packet> Read(CancellationToken ct)
{
lock (_recvLock)
Packet packet = new Packet();
PacketField at = PacketField.None;
PacketField fields = PacketField.None;
int dataLength = 0;
bool get(in ReadOnlySequence<byte> buffer, out SequencePosition consumedTo)
{
Packet packet = new Packet();
packet.Receive(_stream);
return packet;
consumedTo = default;
SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
PacketField prevAt = at;
switch (at)
{
case PacketField.None:
if (!reader.TryReadLittleEndian(out int codeValue)
|| !reader.TryReadLittleEndian(out int fieldsValue)) break;
if (!Enum.IsDefined(typeof(PacketCode), codeValue))
throw new InvalidDataException($"Bad packet code {codeValue}.");
if (!EnumTools.Validate(typeof(PacketField), fieldsValue))
throw new InvalidDataException($"Bad packet fields 0b{Convert.ToString(fieldsValue, 2)}.");
packet.Code = (PacketCode)codeValue;
fields = (PacketField)fieldsValue;
consumedTo = reader.Position;
goto case PacketField.Value0;
case PacketField.Value0:
if (!fields.HasFlag(at = PacketField.Value0)) goto case PacketField.Value1;
if (!reader.TryReadLittleEndian(out int value0)) break;
packet.Value0 = value0;
consumedTo = reader.Position;
goto case PacketField.Value1;
case PacketField.Value1:
if (!fields.HasFlag(at = PacketField.Value1)) goto case PacketField.Value2;
if (!reader.TryReadLittleEndian(out int value1)) break;
packet.Value1 = value1;
consumedTo = reader.Position;
goto case PacketField.Value2;
case PacketField.Value2:
if (!fields.HasFlag(at = PacketField.Value2)) goto case PacketField.Value3;
if (!reader.TryReadLittleEndian(out int value2)) break;
packet.Value2 = value2;
consumedTo = reader.Position;
goto case PacketField.Value3;
case PacketField.Value3:
if (!fields.HasFlag(at = PacketField.Value3)) goto case PacketField.Value4;
if (!reader.TryReadLittleEndian(out int value3)) break;
packet.Value3 = value3;
consumedTo = reader.Position;
goto case PacketField.Value4;
case PacketField.Value4:
if (!fields.HasFlag(at = PacketField.Value4)) goto case PacketField.Value10;
if (!reader.TryReadLittleEndian(out int value4)) break;
packet.Value4 = value4;
consumedTo = reader.Position;
goto case PacketField.Value10;
case PacketField.Value10:
if (!fields.HasFlag(at = PacketField.Value10)) goto case PacketField.DataLength;
if (!reader.TryReadLittleEndian(out int value10)) break;
packet.Value10 = value10;
consumedTo = reader.Position;
goto case PacketField.DataLength;
case PacketField.DataLength:
if (!fields.HasFlag(at = PacketField.DataLength)) goto case PacketField.Error;
if (!reader.TryReadLittleEndian(out dataLength)) break;
if (dataLength > _maxDataSize)
throw new InvalidDataException($"Data too large by {dataLength - _maxDataSize} bytes.");
consumedTo = reader.Position;
goto case PacketField.Data;
case PacketField.Data:
if (!fields.HasFlag(at = PacketField.Data)) goto case PacketField.Error;
Span<byte> dataBytes = stackalloc byte[dataLength];
if (!reader.TryCopyTo(dataBytes)) break;
reader.Advance(dataLength);
packet.Data = Encodings.Windows1252.GetZeroTerminatedString(dataBytes);
consumedTo = reader.Position;
goto case PacketField.Error;
case PacketField.Error:
if (!fields.HasFlag(at = PacketField.Error)) goto case PacketField.Name;
if (!reader.TryReadLittleEndian(out int error)) break;
packet.Error = error;
consumedTo = reader.Position;
goto case PacketField.Name;
case PacketField.Name:
if (!fields.HasFlag(at = PacketField.Name)) goto case PacketField.Session;
Span<byte> nameBytes = stackalloc byte[20];
if (!reader.TryCopyTo(nameBytes)) break;
reader.Advance(20);
packet.Name = Encodings.Windows1252.GetZeroTerminatedString(nameBytes);
consumedTo = reader.Position;
goto case PacketField.Session;
case PacketField.Session:
if (!fields.HasFlag(at = PacketField.Session)) goto case PacketField.All;
Span<byte> sessionBytes = stackalloc byte[Unsafe.SizeOf<SessionInfo>()];
if (!reader.TryCopyTo(sessionBytes)) break;
reader.Advance(sessionBytes.Length);
packet.Session = MemoryMarshal.Cast<byte, SessionInfo>(sessionBytes)[0];
consumedTo = reader.Position;
goto case PacketField.All;
case PacketField.All:
at = PacketField.All;
break;
default:
throw new InvalidOperationException("Invalid packet read state.");
}
return prevAt < at;
}
while (true)
{
ReadResult read = await _reader.ReadAsync(ct);
if (read.IsCanceled)
throw new OperationCanceledException("Packet read was canceled.");
ReadOnlySequence<byte> buffer = read.Buffer;
if (get(buffer, out SequencePosition consumedTo))
{
_reader.AdvanceTo(consumedTo);
if (at == PacketField.All)
return packet;
}
_reader.AdvanceTo(buffer.Start, buffer.End);
if (read.IsCompleted)
throw new EndOfStreamException("No more data.");
}
}
/// <summary>
/// Blocks until the given <paramref name="packet"/> was sent.
/// Sends a <see cref="Packet"/> instance asynchronously.
/// </summary>
/// <param name="packet">The <see cref="Packet"/> to send.</param>
internal void Send(Packet packet)
/// <param name="packet">The <see cref="Packet"/> instance to write.</param>
/// <returns>Whether the instance was written successfully.</returns>
internal async ValueTask<bool> Write(Packet packet, CancellationToken ct)
{
lock (_sendLock)
packet.Send(_stream);
unsafe int set()
{
// Calculate the (exact) length of the packet.
PacketField fields = PacketField.None;
int add(PacketField field, object? value, int size)
{
if (value == null)
return 0;
fields |= field;
return size;
}
int size = sizeof(PacketCode) + sizeof(PacketField)
+ add(PacketField.Value0, packet.Value0, sizeof(int))
+ add(PacketField.Value1, packet.Value1, sizeof(int))
+ add(PacketField.Value2, packet.Value2, sizeof(int))
+ add(PacketField.Value3, packet.Value3, sizeof(int))
+ add(PacketField.Value4, packet.Value4, sizeof(int))
+ add(PacketField.Value10, packet.Value10, sizeof(int))
+ add(PacketField.DataLength, packet.Data, sizeof(int))
+ add(PacketField.Data, packet.Data, (packet.Data?.Length ?? 0) + 1)
+ add(PacketField.Error, packet.Error, sizeof(int))
+ add(PacketField.Name, packet.Name, 20)
+ add(PacketField.Session, packet.Session, Unsafe.SizeOf<SessionInfo>());
// Write the data.
Span<byte> span = _writer.GetSpan(size);
static void writeInt(ref Span<byte> span, int value)
{
BinaryPrimitives.WriteInt32LittleEndian(span, value);
span = span.Slice(sizeof(int));
}
writeInt(ref span, (int)packet.Code);
writeInt(ref span, (int)fields);
if (packet.Value0 != null) writeInt(ref span, packet.Value0.Value);
if (packet.Value1 != null) writeInt(ref span, packet.Value1.Value);
if (packet.Value2 != null) writeInt(ref span, packet.Value2.Value);
if (packet.Value3 != null) writeInt(ref span, packet.Value3.Value);
if (packet.Value4 != null) writeInt(ref span, packet.Value4.Value);
if (packet.Value10 != null) writeInt(ref span, packet.Value10.Value);
if (packet.Data != null)
{
writeInt(ref span, packet.Data.Length + 1);
span = span.Slice(Encodings.Windows1252.GetBytes(packet.Data, span));
span[0] = 0;
span = span.Slice(1);
}
if (packet.Error != null) writeInt(ref span, packet.Error.Value);
if (packet.Name != null)
{
span[Encodings.Windows1252.GetBytes(packet.Name, span)..20].Clear();
span = span.Slice(20);
}
if (packet.Session != null)
{
SessionInfo session = packet.Session.Value;
new ReadOnlySpan<byte>(Unsafe.AsPointer(ref session), Unsafe.SizeOf<SessionInfo>()).CopyTo(span);
}
return size;
}
_writer.Advance(set());
FlushResult flush = await _writer.FlushAsync(ct);
return !flush.IsCanceled && !flush.IsCompleted;
}
}
}

View File

@ -0,0 +1,25 @@
using System;
namespace Syroot.Worms.Worms2.GameServer
{
/// <summary>
/// Represents a bitset determining which fields are available in a <see cref="Packet"/> instance.
/// </summary>
[Flags]
internal enum PacketField : int
{
None,
Value0 = 1 << 0,
Value1 = 1 << 1,
Value2 = 1 << 2,
Value3 = 1 << 3,
Value4 = 1 << 4,
Value10 = 1 << 10,
DataLength = 1 << 5,
Data = 1 << 6,
Error = 1 << 7,
Name = 1 << 8,
Session = 1 << 9,
All = Int32.MaxValue
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Net;
using System.Threading.Tasks;
namespace Syroot.Worms.Worms2.GameServer
{
@ -10,21 +11,23 @@ namespace Syroot.Worms.Worms2.GameServer
{
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void Main(string[] args)
private static async Task Main(string[] args)
{
string? argEndPoint = args.Length > 0 ? args[0] : null;
Server server = new Server();
server.Run(ParseEndPoint(argEndPoint, new IPEndPoint(IPAddress.Any, 17000)));
await server.Run(ParseEndPoint(argEndPoint, new IPEndPoint(IPAddress.Any, 17000)));
//Proxy.Run(ParseEndPoint(argEndPoint, new IPEndPoint(IPAddress.Any, 17001)));
//await Proxy.Run(ParseEndPoint(argEndPoint, new IPEndPoint(IPAddress.Any, 17001)));
}
private static IPEndPoint ParseEndPoint(string? s, IPEndPoint fallback)
{
if (UInt16.TryParse(s, out ushort port))
if (s == null)
return fallback;
else if (UInt16.TryParse(s, out ushort port))
return new IPEndPoint(fallback.Address, port);
else if (IPEndPoint.TryParse(s, out IPEndPoint endPoint))
else if (IPEndPoint.TryParse(s, out IPEndPoint? endPoint))
return endPoint;
else
return fallback;

View File

@ -2,7 +2,7 @@
"profiles": {
"Syroot.Worms.Worms2.GameServer": {
"commandName": "Project",
"commandLineArgs": "127.0.0.1:17002"
"commandLineArgs": "17001"
}
}
}

View File

@ -1,6 +1,8 @@
using System.Drawing;
using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Syroot.ColoredConsole;
@ -13,15 +15,15 @@ namespace Syroot.Worms.Worms2.GameServer
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
internal static void Run(IPEndPoint localEndPoint)
internal static async Task Run(IPEndPoint localEndPoint, CancellationToken ct = default)
{
// Start listening for clients to intercept.
TcpListener listener = new TcpListener(localEndPoint);
listener.Start();
ColorConsole.WriteLine(Color.Orange, $"Proxy listening under {localEndPoint}...");
Log(Color.Orange, $"Proxy listening under {localEndPoint}...");
TcpClient? client;
while ((client = listener.AcceptTcpClient()) != null)
while ((client = await listener.AcceptTcpClientAsync(ct)) != null)
{
// Connect to server.
TcpClient server = new TcpClient();
@ -29,25 +31,39 @@ namespace Syroot.Worms.Worms2.GameServer
PacketConnection clientConnection = new PacketConnection(client);
PacketConnection serverConnection = new PacketConnection(server);
ColorConsole.WriteLine(Color.Green, $"{clientConnection.RemoteEndPoint} connected.");
Log(Color.Green, $"{clientConnection.RemoteEndPoint} connected.");
Task.Run(() => Forward(clientConnection, serverConnection, true));
Task.Run(() => Forward(serverConnection, clientConnection, false));
CancellationTokenSource disconnectCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
_ = Task.WhenAny(
Forward(clientConnection, serverConnection, true, disconnectCts.Token),
Forward(serverConnection, clientConnection, false, disconnectCts.Token))
.ContinueWith((antecedent) => disconnectCts.Cancel());
}
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void Forward(PacketConnection from, PacketConnection to, bool fromClient)
private static void Log(Color color, string message)
=> ColorConsole.WriteLine(color, $"{DateTime.Now:HH:mm:ss} {message}");
private static async Task Forward(PacketConnection from, PacketConnection to, bool fromClient,
CancellationToken ct)
{
while (true)
string prefix = fromClient
? $"{from.RemoteEndPoint} >> {to.RemoteEndPoint}"
: $"{to.RemoteEndPoint} << {from.RemoteEndPoint}";
try
{
Packet packet = from.Receive();
if (fromClient)
ColorConsole.WriteLine(Color.Cyan, $"{from.RemoteEndPoint} >> {to.RemoteEndPoint} | {packet}");
else
ColorConsole.WriteLine(Color.Magenta, $"{to.RemoteEndPoint} << {from.RemoteEndPoint} | {packet}");
to.Send(packet);
while (true)
{
Packet packet = await from.Read(ct);
Log(fromClient ? Color.Cyan : Color.Magenta, $"{prefix} {packet}");
await to.Write(packet, ct);
}
}
catch (Exception ex)
{
Log(Color.Red, $"{prefix} closed. {ex.Message}");
}
}
}

View File

@ -1,10 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Syroot.ColoredConsole;
@ -15,34 +16,43 @@ namespace Syroot.Worms.Worms2.GameServer
/// </summary>
internal class Server
{
// ---- CONSTANTS ----------------------------------------------------------------------------------------------
private const int _authorizedTimeout = 10 * 60 * 1000;
private const int _unauthorizedTimeout = 3 * 1000;
// ---- FIELDS -------------------------------------------------------------------------------------------------
private int _lastID = 0x1000; // start at an offset to prevent bugs with chat
private readonly List<User> _users = new List<User>();
private readonly List<Room> _rooms = new List<Room>();
private readonly List<Game> _games = new List<Game>();
private readonly BlockingCollection<Action> _jobs = new BlockingCollection<Action>();
private readonly Dictionary<PacketCode, Action<PacketConnection, Packet>> _packetHandlers;
private readonly Channel<Func<ValueTask>> _jobs;
private readonly Dictionary<PacketCode, PacketHandler> _packetHandlers;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the <see cref="Server"/> class.
/// </summary>
internal Server() => _packetHandlers = new Dictionary<PacketCode, Action<PacketConnection, Packet>>
internal Server()
{
[PacketCode.ListRooms] = OnListRooms,
[PacketCode.ListUsers] = OnListUsers,
[PacketCode.ListGames] = OnListGames,
[PacketCode.Login] = OnLogin,
[PacketCode.CreateRoom] = OnCreateRoom,
[PacketCode.Join] = OnJoin,
[PacketCode.Leave] = OnLeave,
[PacketCode.Close] = OnClose,
[PacketCode.CreateGame] = OnCreateGame,
[PacketCode.ChatRoom] = OnChatRoom,
[PacketCode.ConnectGame] = OnConnectGame,
};
_jobs = Channel.CreateUnbounded<Func<ValueTask>>(new UnboundedChannelOptions { SingleReader = true });
_packetHandlers = new Dictionary<PacketCode, PacketHandler>
{
[PacketCode.ListRooms] = OnListRooms,
[PacketCode.ListUsers] = OnListUsers,
[PacketCode.ListGames] = OnListGames,
[PacketCode.Login] = OnLogin,
[PacketCode.CreateRoom] = OnCreateRoom,
[PacketCode.Join] = OnJoin,
[PacketCode.Leave] = OnLeave,
[PacketCode.Close] = OnClose,
[PacketCode.CreateGame] = OnCreateGame,
[PacketCode.ChatRoom] = OnChatRoom,
[PacketCode.ConnectGame] = OnConnectGame,
};
}
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
@ -50,28 +60,31 @@ namespace Syroot.Worms.Worms2.GameServer
/// Begins listening for new clients connecting to the given <paramref name="localEndPoint"/> and dispatches
/// them into their own threads.
/// </summary>
internal void Run(IPEndPoint localEndPoint)
internal async Task Run(IPEndPoint localEndPoint, CancellationToken ct = default)
{
// Begin handling any queued jobs.
Task.Run(() => HandleJobs());
// Begin listening for new connections. Currently synchronous and blocking.
HandleConnections(localEndPoint);
_ = HandleJobs(ct);
// Begin listening for new connections.
await HandleConnections(localEndPoint, ct);
}
// ---- METHODS (PRIVATE) --------------------------------------------------------------------------------------
private static void SendPacket(PacketConnection connection, Packet packet)
{
LogPacket(connection, packet, false);
connection.Send(packet);
}
private static void Log(Color color, string message)
=> ColorConsole.WriteLine(color, $"{DateTime.Now:HH:mm:ss} {message}");
private static void LogPacket(PacketConnection connection, Packet packet, bool fromClient)
{
if (fromClient)
ColorConsole.WriteLine(Color.Cyan, $"{DateTime.Now:HH:mm:ss} {connection.RemoteEndPoint} >> {packet}");
Log(Color.Cyan, $"{connection.RemoteEndPoint} >> {packet}");
else
ColorConsole.WriteLine(Color.Magenta, $"{DateTime.Now:HH:mm:ss} {connection.RemoteEndPoint} << {packet}");
Log(Color.Magenta, $"{connection.RemoteEndPoint} << {packet}");
}
private static async ValueTask SendPacket(PacketConnection connection, CancellationToken ct, Packet packet)
{
LogPacket(connection, packet, false);
await connection.Write(packet, ct);
}
private User? GetUser(PacketConnection connection)
@ -82,53 +95,62 @@ namespace Syroot.Worms.Worms2.GameServer
return null;
}
private void HandleJobs()
private async ValueTask HandleJobs(CancellationToken ct)
{
foreach (Action job in _jobs.GetConsumingEnumerable())
job();
await foreach (Func<ValueTask> job in _jobs.Reader.ReadAllAsync(ct))
await job();
}
private void HandleConnections(IPEndPoint localEndPoint)
private async ValueTask HandleConnections(IPEndPoint localEndPoint, CancellationToken ct)
{
// Start a new listener for new incoming connections.
TcpListener listener = new TcpListener(localEndPoint);
listener.Start();
ColorConsole.WriteLine(Color.Orange, $"Server listening under {listener.LocalEndpoint}...");
Log(Color.Orange, $"Server listening under {listener.LocalEndpoint}...");
// Dispatch each connection into its own thread.
TcpClient? client;
while ((client = listener.AcceptTcpClient()) != null)
Task.Run(() => HandleClient(client));
while ((client = await listener.AcceptTcpClientAsync(ct)) != null)
_ = HandleClient(client, ct);
}
private void HandleClient(TcpClient client)
private async Task HandleClient(TcpClient client, CancellationToken ct)
{
PacketConnection connection = new PacketConnection(client);
ColorConsole.WriteLine(Color.Green, $"{connection.RemoteEndPoint} connected.");
Log(Color.Green, $"{connection.RemoteEndPoint} connected.");
bool loggedIn = false;
try
{
while (true)
{
// Receive and log query.
Packet packet = connection.Receive();
// Receive packet during a valid time frame.
CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeoutCts.CancelAfter(loggedIn ? _authorizedTimeout : _unauthorizedTimeout);
Packet packet = await connection.Read(timeoutCts.Token);
// Log packet.
LogPacket(connection, packet, true);
if (packet.Code == PacketCode.Login)
loggedIn = true;
// Queue handling of known queries.
if (_packetHandlers.TryGetValue(packet.Code, out Action<PacketConnection, Packet>? handler))
_jobs.Add(() => handler(connection, packet));
if (_packetHandlers.TryGetValue(packet.Code, out PacketHandler? handler))
await _jobs.Writer.WriteAsync(() => handler(connection, packet, ct), ct);
else
ColorConsole.WriteLine(Color.Red, $"{connection.RemoteEndPoint} unhandled {packet.Code}.");
Log(Color.Red, $"{connection.RemoteEndPoint} unhandled {packet.Code}.");
}
}
catch (Exception ex)
{
ColorConsole.WriteLine(Color.Red, $"{connection.RemoteEndPoint} disconnected. {ex.Message}");
_jobs.Add(() => OnDisconnectUser(connection));
Log(Color.Red, $"{connection.RemoteEndPoint} disconnected. {ex.Message}");
ct.ThrowIfCancellationRequested();
await _jobs.Writer.WriteAsync(() => OnDisconnectUserAsync(connection, ct), ct);
}
}
private void LeaveRoom(Room? room, int leftID)
private async ValueTask LeaveRoom(Room? room, int leftID, CancellationToken ct)
{
// Close an abandoned room.
bool roomClosed = room != null
@ -143,19 +165,19 @@ namespace Syroot.Worms.Worms2.GameServer
// Notify room leave, if any.
if (room != null)
{
SendPacket(user.Connection, new Packet(PacketCode.Leave,
await SendPacket(user.Connection, ct, new Packet(PacketCode.Leave,
value2: room.ID,
value10: leftID));
}
// Notify room close, if any.
if (roomClosed)
SendPacket(user.Connection, new Packet(PacketCode.Close, value10: room!.ID));
await SendPacket(user.Connection, ct, new Packet(PacketCode.Close, value10: room!.ID));
}
}
// ---- Handlers ----
private void OnListRooms(PacketConnection connection, Packet packet)
private async ValueTask OnListRooms(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value4 != 0)
@ -163,16 +185,16 @@ namespace Syroot.Worms.Worms2.GameServer
foreach (Room room in _rooms)
{
SendPacket(connection, new Packet(PacketCode.ListItem,
await SendPacket(connection, ct, new Packet(PacketCode.ListItem,
value1: room.ID,
data: String.Empty, // do not report creator IP
name: room.Name,
session: room.Session));
}
SendPacket(connection, new Packet(PacketCode.ListEnd));
await SendPacket(connection, ct, new Packet(PacketCode.ListEnd));
}
private void OnListUsers(PacketConnection connection, Packet packet)
private async ValueTask OnListUsers(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value2 != fromUser.RoomID || packet.Value4 != 0)
@ -180,15 +202,15 @@ namespace Syroot.Worms.Worms2.GameServer
foreach (User user in _users.Where(x => x.RoomID == fromUser.RoomID)) // notably includes the user itself
{
SendPacket(connection, new Packet(PacketCode.ListItem,
await SendPacket(connection, ct, new Packet(PacketCode.ListItem,
value1: user.ID,
name: user.Name,
session: user.Session));
}
SendPacket(connection, new Packet(PacketCode.ListEnd));
await SendPacket(connection, ct, new Packet(PacketCode.ListEnd));
}
private void OnListGames(PacketConnection connection, Packet packet)
private async ValueTask OnListGames(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value2 != fromUser.RoomID || packet.Value4 != 0)
@ -196,16 +218,16 @@ namespace Syroot.Worms.Worms2.GameServer
foreach (Game game in _games.Where(x => x.RoomID == fromUser.RoomID))
{
SendPacket(connection, new Packet(PacketCode.ListItem,
await SendPacket(connection, ct, new Packet(PacketCode.ListItem,
value1: game.ID,
data: game.IPAddress.ToString(),
name: game.Name,
session: game.Session));
}
SendPacket(connection, new Packet(PacketCode.ListEnd));
await SendPacket(connection, ct, new Packet(PacketCode.ListEnd));
}
private void OnLogin(PacketConnection connection, Packet packet)
private async ValueTask OnLogin(PacketConnection connection, Packet packet, CancellationToken ct)
{
if (packet.Value1 == null || packet.Value4 == null || packet.Name == null || packet.Session == null)
return;
@ -213,7 +235,7 @@ namespace Syroot.Worms.Worms2.GameServer
// Check if user name is valid and not already taken.
if (_users.Any(x => x.Name.Equals(packet.Name, StringComparison.InvariantCultureIgnoreCase)))
{
SendPacket(connection, new Packet(PacketCode.LoginReply, value1: 0, error: 1));
await SendPacket(connection, ct, new Packet(PacketCode.LoginReply, value1: 0, error: 1));
}
else
{
@ -222,20 +244,17 @@ namespace Syroot.Worms.Worms2.GameServer
// Notify other users about new user.
foreach (User user in _users)
{
SendPacket(user.Connection, new Packet(PacketCode.Login,
value1: newUser.ID,
value4: 0,
name: newUser.Name,
session: newUser.Session));
await SendPacket(user.Connection, ct, new Packet(PacketCode.Login,
value1: newUser.ID, value4: 0, name: newUser.Name, session: newUser.Session));
}
// Register new user and send reply to him.
_users.Add(newUser);
SendPacket(connection, new Packet(PacketCode.LoginReply, value1: newUser.ID, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.LoginReply, value1: newUser.ID, error: 0));
}
}
private void OnCreateRoom(PacketConnection connection, Packet packet)
private async ValueTask OnCreateRoom(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value1 != 0 || packet.Value4 != 0 || packet.Data == null
@ -252,7 +271,7 @@ namespace Syroot.Worms.Worms2.GameServer
// Notify other users about new room.
foreach (User user in _users.Where(x => x != fromUser))
{
SendPacket(user.Connection, new Packet(PacketCode.CreateRoom,
await SendPacket(user.Connection, ct, new Packet(PacketCode.CreateRoom,
value1: newRoom.ID,
value4: 0,
data: String.Empty, // do not report creator IP
@ -261,19 +280,15 @@ namespace Syroot.Worms.Worms2.GameServer
}
// Send reply to creator.
SendPacket(connection, new Packet(PacketCode.CreateRoomReply,
value1: newRoom.ID,
error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.CreateRoomReply, value1: newRoom.ID, error: 0));
}
else
{
SendPacket(connection, new Packet(PacketCode.CreateRoomReply,
value1: 0,
error: 1));
await SendPacket(connection, ct, new Packet(PacketCode.CreateRoomReply, value1: 0, error: 1));
}
}
private void OnJoin(PacketConnection connection, Packet packet)
private async ValueTask OnJoin(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value2 == null || packet.Value10 != fromUser.ID)
@ -287,34 +302,34 @@ namespace Syroot.Worms.Worms2.GameServer
// Notify other users about the join.
foreach (User user in _users.Where(x => x != fromUser))
{
SendPacket(user.Connection, new Packet(PacketCode.Join,
await SendPacket(user.Connection, ct, new Packet(PacketCode.Join,
value2: fromUser.RoomID,
value10: fromUser.ID));
}
// Send reply to joiner.
SendPacket(connection, new Packet(PacketCode.JoinReply, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.JoinReply, error: 0));
}
else if (_games.Any(x => x.ID == packet.Value2 && x.RoomID == fromUser.RoomID))
{
// Notify other users about the join.
foreach (User user in _users.Where(x => x != fromUser))
{
SendPacket(user.Connection, new Packet(PacketCode.Join,
await SendPacket(user.Connection, ct, new Packet(PacketCode.Join,
value2: fromUser.RoomID,
value10: fromUser.ID));
}
// Send reply to joiner.
SendPacket(connection, new Packet(PacketCode.JoinReply, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.JoinReply, error: 0));
}
else
{
SendPacket(connection, new Packet(PacketCode.JoinReply, error: 1));
await SendPacket(connection, ct, new Packet(PacketCode.JoinReply, error: 1));
}
}
private void OnLeave(PacketConnection connection, Packet packet)
private async ValueTask OnLeave(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value2 == null || packet.Value10 != fromUser.ID)
@ -323,20 +338,20 @@ namespace Syroot.Worms.Worms2.GameServer
// Require valid room ID (never sent for games, users disconnect if leaving a game).
if (packet.Value2 == fromUser.RoomID)
{
LeaveRoom(_rooms.FirstOrDefault(x => x.ID == fromUser.RoomID), fromUser.ID);
await LeaveRoom(_rooms.FirstOrDefault(x => x.ID == fromUser.RoomID), fromUser.ID, ct);
fromUser.RoomID = 0;
// Reply to leaver.
SendPacket(connection, new Packet(PacketCode.LeaveReply, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.LeaveReply, error: 0));
}
else
{
// Reply to leaver.
SendPacket(connection, new Packet(PacketCode.LeaveReply, error: 1));
await SendPacket(connection, ct, new Packet(PacketCode.LeaveReply, error: 1));
}
}
private void OnDisconnectUser(PacketConnection connection)
private async ValueTask OnDisconnectUserAsync(PacketConnection connection, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null)
@ -356,23 +371,22 @@ namespace Syroot.Worms.Worms2.GameServer
// Notify other users.
foreach (User user in _users.Where(x => x != fromUser))
{
SendPacket(user.Connection, new Packet(PacketCode.Leave, value2: game.ID, value10: fromUser.ID));
SendPacket(user.Connection, new Packet(PacketCode.Close, value10: game.ID));
await SendPacket(user.Connection, ct, new Packet(PacketCode.Leave,
value2: game.ID, value10: fromUser.ID));
await SendPacket(user.Connection, ct, new Packet(PacketCode.Close,
value10: game.ID));
}
}
// Close any abandoned room.
LeaveRoom(_rooms.FirstOrDefault(x => x.ID == roomID), leftID);
await LeaveRoom(_rooms.FirstOrDefault(x => x.ID == roomID), leftID, ct);
// Notify user disconnect.
foreach (User user in _users)
{
SendPacket(user.Connection, new Packet(PacketCode.DisconnectUser,
value10: fromUser.ID));
}
await SendPacket(user.Connection, ct, new Packet(PacketCode.DisconnectUser, value10: fromUser.ID));
}
private void OnClose(PacketConnection connection, Packet packet)
private async ValueTask OnClose(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value10 == null)
@ -380,10 +394,10 @@ namespace Syroot.Worms.Worms2.GameServer
// Never sent for games, users disconnect if leaving a game.
// Simply reply success to client, the server decides when to actually close rooms.
SendPacket(connection, new Packet(PacketCode.CloseReply, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.CloseReply, error: 0));
}
private void OnCreateGame(PacketConnection connection, Packet packet)
private async ValueTask OnCreateGame(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value1 != 0 || packet.Value2 != fromUser.RoomID || packet.Value4 != 0x800
@ -391,7 +405,7 @@ namespace Syroot.Worms.Worms2.GameServer
return;
// Require valid room ID and IP.
if (IPAddress.TryParse(packet.Data, out IPAddress ip) && connection.RemoteEndPoint.Address.Equals(ip))
if (IPAddress.TryParse(packet.Data, out IPAddress? ip) && connection.RemoteEndPoint.Address.Equals(ip))
{
Game newGame = new Game(++_lastID, fromUser.Name, fromUser.Session.Nation, fromUser.RoomID,
connection.RemoteEndPoint.Address, // do not use bad NAT IP reported by users here
@ -401,7 +415,7 @@ namespace Syroot.Worms.Worms2.GameServer
// Notify other users about new game, even those in other rooms.
foreach (User user in _users.Where(x => x != fromUser))
{
SendPacket(user.Connection, new Packet(PacketCode.CreateGame,
await SendPacket(user.Connection, ct, new Packet(PacketCode.CreateGame,
value1: newGame.ID,
value2: newGame.RoomID,
value4: 0x800,
@ -411,21 +425,20 @@ namespace Syroot.Worms.Worms2.GameServer
}
// Send reply to host.
SendPacket(connection, new Packet(PacketCode.CreateGameReply, value1: newGame.ID, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.CreateGameReply, value1: newGame.ID, error: 0));
}
else
{
SendPacket(connection, new Packet(PacketCode.CreateGameReply, value1: 0, error: 2));
SendPacket(connection, new Packet(PacketCode.ChatRoom,
await SendPacket(connection, ct, new Packet(PacketCode.CreateGameReply, value1: 0, error: 2));
await SendPacket(connection, ct, new Packet(PacketCode.ChatRoom,
value0: fromUser.ID,
value3: fromUser.RoomID,
data: $"GRP:Cannot host your game. Please use the Worms 2 Memory Changer to set your IP "
+ $"{fromUser.Connection.RemoteEndPoint.Address}. For more information, visit "
+ "worms2d.info/Worms_2_Memory_Changer"));
data: $"GRP:Cannot host your game. Please use FrontendKitWS with fkNetcode. More information at "
+ "worms2d.info/fkNetcode"));
}
}
private void OnChatRoom(PacketConnection connection, Packet packet)
private async ValueTask OnChatRoom(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value0 != fromUser.ID || packet.Value3 == null || packet.Data == null)
@ -442,17 +455,17 @@ namespace Syroot.Worms.Worms2.GameServer
string message = packet.Data.Substring(prefix.Length);
foreach (User user in _users.Where(x => x.RoomID == fromUser.RoomID && x != fromUser))
{
SendPacket(user.Connection, new Packet(PacketCode.ChatRoom,
await SendPacket(user.Connection, ct, new Packet(PacketCode.ChatRoom,
value0: fromUser.ID,
value3: user.RoomID,
data: prefix + message));
}
// Notify sender.
SendPacket(connection, new Packet(PacketCode.ChatRoomReply, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.ChatRoomReply, error: 0));
}
else
{
SendPacket(connection, new Packet(PacketCode.ChatRoomReply, error: 1));
await SendPacket(connection, ct, new Packet(PacketCode.ChatRoomReply, error: 1));
}
}
else if (packet.Data.StartsWith(prefix = $"PRV:[ {fromUser.Name} ] ", StringComparison.InvariantCulture))
@ -461,23 +474,23 @@ namespace Syroot.Worms.Worms2.GameServer
User? user = _users.FirstOrDefault(x => x.RoomID == fromUser.RoomID && x.ID == targetID);
if (user == null)
{
SendPacket(connection, new Packet(PacketCode.ChatRoomReply, error: 1));
await SendPacket(connection, ct, new Packet(PacketCode.ChatRoomReply, error: 1));
}
else
{
// Notify receiver of the message.
string message = packet.Data.Substring(prefix.Length);
SendPacket(user.Connection, new Packet(PacketCode.ChatRoom,
await SendPacket(user.Connection, ct, new Packet(PacketCode.ChatRoom,
value0: fromUser.ID,
value3: user.ID,
data: prefix + message));
// Notify sender.
SendPacket(connection, new Packet(PacketCode.ChatRoomReply, error: 0));
await SendPacket(connection, ct, new Packet(PacketCode.ChatRoomReply, error: 0));
}
}
}
private void OnConnectGame(PacketConnection connection, Packet packet)
private async ValueTask OnConnectGame(PacketConnection connection, Packet packet, CancellationToken ct)
{
User? fromUser = GetUser(connection);
if (fromUser == null || packet.Value0 == null)
@ -487,16 +500,20 @@ namespace Syroot.Worms.Worms2.GameServer
Game? game = _games.FirstOrDefault(x => x.ID == packet.Value0 && x.RoomID == fromUser.RoomID);
if (game == null)
{
SendPacket(connection, new Packet(PacketCode.ConnectGameReply,
await SendPacket(connection, ct, new Packet(PacketCode.ConnectGameReply,
data: String.Empty,
error: 1));
}
else
{
SendPacket(connection, new Packet(PacketCode.ConnectGameReply,
await SendPacket(connection, ct, new Packet(PacketCode.ConnectGameReply,
data: game.IPAddress.ToString(),
error: 0));
}
}
// ---- CLASSES, STRUCTS & ENUMS -------------------------------------------------------------------------------
private delegate ValueTask PacketHandler(PacketConnection connection, Packet packet, CancellationToken ct);
}
}

View File

@ -2,17 +2,19 @@
<!-- Metadata -->
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationIcon>..\..\..\res\icon.ico</ApplicationIcon>
<AssemblyName>w2server</AssemblyName>
<Authors>Syroot</Authors>
<Copyright>(c) Syroot, licensed under MIT</Copyright>
<Description>Worms 2 Game Server</Description>
<Version>1.0.0</Version>
<Version>1.1.1</Version>
</PropertyGroup>
<!-- References -->
<ItemGroup>
<PackageReference Include="Syroot.ColoredConsole" Version="1.0.1" />
<PackageReference Include="System.IO.Pipelines" Version="4.7.5" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
<ProjectReference Include="..\..\library\Syroot.Worms\Syroot.Worms.csproj" />
</ItemGroup>
@ -23,7 +25,7 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,38 @@
using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Syroot.Worms.Worms2.GameServer
{
/// <summary>
/// Represents extension methods for <see cref="TcpListener"/> instances.
/// </summary>
internal static class TcpListenerExtensions
{
// ---- METHODS (INTERNAL) -------------------------------------------------------------------------------------
/// <summary>
/// Accepts a pending connection request as an asynchronous operation.
/// </summary>
/// <param name="tcpListener">The <see cref="TcpListener"/> instance.</param>
/// <returns>The task object representing the asynchronous operation. The <see cref="Task{TcpClient}.Result"/>
/// property on the task object returns a <see cref="TcpClient"/> used to send and receive data.</returns>
internal static async Task<TcpClient> AcceptTcpClientAsync(this TcpListener tcpListener,
CancellationToken cancellationToken = default)
{
using (cancellationToken.Register(() => tcpListener.Stop()))
{
try
{
return await tcpListener.AcceptTcpClientAsync();
}
catch (InvalidOperationException)
{
cancellationToken.ThrowIfCancellationRequested();
throw;
}
}
}
}
}

View File

@ -1,32 +0,0 @@
#include "PEInfo.h"
PEInfo::PEInfo(HMODULE hModule)
{
Reset(hModule);
}
void PEInfo::Reset(HMODULE hModule)
{
Handle = hModule == 0 ? GetModuleHandleA(0) : hModule;
DOS = (IMAGE_DOS_HEADER*)Handle;
NT = (IMAGE_NT_HEADERS*)((DWORD)DOS + DOS->e_lfanew);
FH = (IMAGE_FILE_HEADER*)&NT->FileHeader;
OPT = (IMAGE_OPTIONAL_HEADER*)&NT->OptionalHeader;
}
DWORD PEInfo::Offset(DWORD off)
{
return (DWORD)Handle + off;
}
BOOL PEInfo::PtrInCode(PVOID ptr)
{
return DWORD(ptr) >= Offset(OPT->BaseOfCode)
&& DWORD(ptr) < Offset(OPT->BaseOfCode) + OPT->SizeOfCode;
}
BOOL PEInfo::PtrInData(PVOID ptr)
{
return DWORD(ptr) >= Offset(OPT->BaseOfData)
&& DWORD(ptr) < Offset(OPT->BaseOfData) + OPT->SizeOfInitializedData + OPT->SizeOfUninitializedData;
}

View File

@ -1,18 +0,0 @@
#pragma once
#include <Windows.h>
typedef struct PEInfo
{
PEInfo(HMODULE hModule = 0);
void Reset(HMODULE hModule);
DWORD Offset(DWORD off);
BOOL PtrInCode(PVOID ptr);
BOOL PtrInData(PVOID ptr);
HANDLE Handle;
IMAGE_DOS_HEADER* DOS;
IMAGE_NT_HEADERS* NT;
IMAGE_FILE_HEADER* FH;
IMAGE_OPTIONAL_HEADER* OPT;
} *PPEInfo;

View File

@ -1,38 +0,0 @@
#include "fkConfig.h"
#include <stdio.h>
namespace fk
{
Config::Config(LPCSTR fileName)
{
DWORD length = GetModuleFileName(NULL, _filePath, MAX_PATH);
strcpy_s(strrchr(_filePath, '\\') + 1, MAX_PATH, fileName);
}
void Config::get(LPCSTR category, LPCSTR key, BOOL& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Config::get(LPCSTR category, LPCSTR key, UINT& result, UINT fallback) const
{
result = GetPrivateProfileInt(category, key, fallback, _filePath);
}
void Config::get(LPCSTR category, LPCSTR key, LPSTR result, INT resultLength, LPCSTR fallback) const
{
GetPrivateProfileString(category, key, fallback, result, resultLength, _filePath);
}
void Config::set(LPCSTR category, LPCSTR key, UINT value) const
{
CHAR buffer[32];
sprintf_s(buffer, "%d", value);
WritePrivateProfileString(category, key, buffer, _filePath);
}
void Config::set(LPCSTR category, LPCSTR key, LPCSTR value) const
{
WritePrivateProfileString(category, key, value, _filePath);
}
}

View File

@ -1,28 +0,0 @@
#include "fkPatch.h"
#include <stdexcept>
namespace fk
{
Patch::Patch(LPVOID lpAddress, SIZE_T dwSize)
: _lpAddress(static_cast<LPBYTE>(lpAddress))
, _dwSize(dwSize)
, dwPosition(0)
{
if (!_lpAddress || !_dwSize)
throw std::invalid_argument("Address and size must not be 0.");
if (!VirtualProtect(_lpAddress, _dwSize, PAGE_EXECUTE_READWRITE, &_flOldProtect))
throw std::exception("VirtualProtect failed, call GetLastError for more info.");
}
Patch::~Patch()
{
close();
}
void Patch::close() const
{
DWORD oldProtect;
if (!VirtualProtect(_lpAddress, _dwSize, _flOldProtect, &oldProtect))
throw std::exception("VirtualProtect failed, call GetLastError for more info.");
}
};

View File

@ -1,24 +0,0 @@
#pragma once
#include <Windows.h>
namespace fk
{
struct Patch
{
public:
DWORD dwPosition;
Patch(LPVOID lpAddress, SIZE_T dwSize);
~Patch();
void close() const;
template <class T> void write(const T& value);
private:
LPBYTE _lpAddress;
SIZE_T _dwSize;
DWORD _flOldProtect;
};
}
#include "fkPatch.inl"

View File

@ -1,10 +0,0 @@
namespace fk
{
template <class T>
void Patch::write(const T& value)
{
memcpy_s(_lpAddress + dwPosition, sizeof(T), &value, sizeof(T));
dwPosition += sizeof(T);
}
}

View File

@ -1,89 +0,0 @@
#include "fkUtils.h"
#include "fkPatch.h"
namespace fk
{
int getGameVersion(DWORD timeDateStamp)
{
switch (timeDateStamp)
{
case 0x3528DAFA: return GAME_VERSION_BR;
case 0x3528DCB1: return GAME_VERSION_EN;
case 0x3528DB52: return GAME_VERSION_GE;
case 0x3528DA98: return GAME_VERSION_NA;
case 0x3528DBDA: return GAME_VERSION_SA;
case 0x3587BE19: return GAME_VERSION_TRY;
}
return GAME_VERSION_NONE;
}
void throwLastError()
{
DWORD error = GetLastError();
if (error == ERROR_SUCCESS)
return;
LPTSTR buffer = NULL;
const DWORD cchMsg = FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL,
error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPTSTR>(&buffer), 0, NULL);
if (cchMsg > 0)
{
std::string message(buffer);
LocalFree(buffer);
throw std::exception(message.c_str());
}
else
{
CHAR buffer[32];
sprintf_s(buffer, "Error code 0x%08X.", error);
throw std::exception(buffer);
}
}
void patchNops(ULONG dwAddr, SIZE_T dwPatchSize)
{
fk::Patch patch(reinterpret_cast<void*>(dwAddr), dwPatchSize);
while (dwPatchSize--)
patch.write<BYTE>(0x90);
}
void patchJump(PVOID pDest, SIZE_T dwPatchSize, PVOID pCallee, DWORD dwJumpType)
{
fk::Patch patch(pDest, dwPatchSize);
if (dwPatchSize >= 5 && pDest)
{
BYTE OpSize, OpCode;
switch (dwJumpType)
{
case IJ_PUSHRET: OpSize = 6; OpCode = 0x68; break;
case IJ_FARJUMP: OpSize = 7; OpCode = 0xEA; break;
case IJ_FARCALL: OpSize = 7; OpCode = 0x9A; break;
case IJ_CALL: OpSize = 5; OpCode = 0xE8; break;
default: OpSize = 5; OpCode = 0xE9; break;
}
if (dwPatchSize < OpSize)
throw std::exception("Not enough space to patch opcode.");
patch.write(OpCode);
switch (OpSize)
{
case 7:
patch.write((ULONG)pCallee);
patch.write<WORD>(0x23);
break;
case 6:
patch.write((ULONG)pCallee);
patch.write<BYTE>(0xC3);
break;
default:
patch.write((ULONG)pCallee - (ULONG)pDest - 5);
break;
}
for (DWORD i = OpSize; i < dwPatchSize; i++)
patch.write<BYTE>(0x90);
}
}
}

View File

@ -1,32 +0,0 @@
#pragma once
#include <stdexcept>
#include <Windows.h>
namespace fk
{
enum GameVersion
{
GAME_VERSION_NONE = -1,
GAME_VERSION_BR, // 1.05 Br
GAME_VERSION_EN, // 1.05 Du, En, Fr, It, Po, Sp, Sw
GAME_VERSION_GE, // 1.05
GAME_VERSION_NA, // 1.05
GAME_VERSION_SA, // 1.05
GAME_VERSION_TRY // 1.07 Trymedia
};
enum InsertJump
{
IJ_JUMP, // Insert a jump (0xE9) with patchJump
IJ_CALL, // Insert a call (0xE8) with patchJump
IJ_FARJUMP, // Insert a farjump (0xEA) with patchJump
IJ_FARCALL, // Insert a farcall (0x9A) with patchJump
IJ_PUSHRET, // Insert a pushret with patchJump
};
int getGameVersion(DWORD timeDateStamp);
void throwLastError();
void patchNops(ULONG dwAddr, SIZE_T dwPatchSize);
void patchJump(PVOID pDest, SIZE_T dwPatchSize, PVOID pCallee, DWORD dwJumpType = IJ_JUMP);
}

View File

@ -1,20 +0,0 @@
#include "fkWinHandle.h"
#include "fkUtils.h"
#include <stdexcept>
namespace fk
{
WinHandle::WinHandle(HANDLE handle, BOOL(__stdcall* closeProc)(HANDLE))
: _handle(handle)
, _closeProc(closeProc)
{
if (!_handle || _handle == INVALID_HANDLE_VALUE)
fk::throwLastError();
}
WinHandle::~WinHandle()
{
if (_handle)
_closeProc(_handle);
}
}

View File

@ -1,19 +0,0 @@
#pragma once
#include <Windows.h>
namespace fk
{
struct WinHandle
{
public:
WinHandle(HANDLE handle, BOOL(__stdcall* closeProc)(HANDLE));
~WinHandle();
operator bool() { return _handle; }
operator HANDLE() { return _handle; }
private:
HANDLE _handle;
BOOL(__stdcall* _closeProc)(HANDLE);
};
}

View File

@ -1,126 +0,0 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinInet.h>
#include "fkConfig.h"
#include "fkUtils.h"
#include "fkWinHandle.h"
#include "PEInfo.h"
CHAR cfgFallbackIP[16];
CHAR cfgServiceUrl[MAX_PATH];
BOOL cfgShowErrors;
PEInfo pe;
CHAR resolvedIP[16] = {};
void configure()
{
fk::Config config("fkNetcode.ini");
// Load INI settings.
config.get("AddressResolval", "FallbackIP", cfgFallbackIP, 16);
config.get("AddressResolval", "ServiceUrl", cfgServiceUrl, MAX_PATH, "http://ip.syroot.com");
config.get("AddressResolval", "ShowErrors", cfgShowErrors, TRUE);
// Ensure INI file has been created with default setting.
config.set("AddressResolval", "FallbackIP", cfgFallbackIP);
config.set("AddressResolval", "ServiceUrl", cfgServiceUrl);
config.set("AddressResolval", "ShowErrors", cfgShowErrors);
// Validate fallback IP.
BYTE b;
if (*cfgFallbackIP && sscanf_s(cfgFallbackIP, "%hhu.%hhu.%hhu.%hhu", &b, &b, &b, &b) != 4)
{
*cfgFallbackIP = NULL;
MessageBox(NULL, "Invalid fallback IP setting in fkNetcode.ini has been ignored.", "fkNetcode", MB_ICONWARNING);
}
}
void resolveIP(LPTSTR buffer)
{
if (!*resolvedIP)
{
// Initially resolve the external IP through a web service.
fk::WinHandle hInternet(InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0), InternetCloseHandle);
fk::WinHandle hFile(InternetOpenUrl(hInternet, cfgServiceUrl, NULL, 0,
INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_RELOAD, NULL), InternetCloseHandle);
DWORD responseLength = 0;
CHAR response[16];
if (!InternetReadFile(hFile, response, 16, &responseLength))
fk::throwLastError();
if (responseLength < 8)
throw std::exception("Response was too short.");
response[responseLength] = '\0';
BYTE temp;
if (sscanf_s(response, "%hhu.%hhu.%hhu.%hhu", &temp, &temp, &temp, &temp) != 4)
throw std::exception("Response could not be parsed.");
lstrcpy(resolvedIP, response);
}
lstrcpy(buffer, resolvedIP);
}
bool __stdcall patchResolveIP(LPSTR buffer, int bufferLength)
{
try
{
resolveIP(buffer);
}
catch (std::exception& ex)
{
lstrcpy(buffer, *cfgFallbackIP ? cfgFallbackIP : "0.0.0.0");
if (cfgShowErrors)
{
CHAR message[512];
sprintf_s(message, "Could not resolve your IP address. %s", ex.what());
MessageBox(NULL, message, "fkNetcode", MB_ICONWARNING);
}
}
return true; // not used
}
void patch(int gameVersion)
{
fk::patchJump((PVOID)pe.Offset(0x00001799), 5, &patchResolveIP, fk::IJ_JUMP); // replace IP resolve with web service
if (gameVersion == fk::GAME_VERSION_TRY)
{
fk::patchNops(pe.Offset(0x00053B96), 5); // prevent overriding IP with user name
fk::patchNops(pe.Offset(0x00054693), 5); // prevent overriding IP with NAT IP
fk::patchNops(pe.Offset(0x00054635), 11); // useless sleep when connecting to server
}
else
{
fk::patchNops(pe.Offset(0x00053E96), 5); // prevent overriding IP with user name
fk::patchNops(pe.Offset(0x00054935), 11); // useless sleep when connecting to server
fk::patchNops(pe.Offset(0x00054993), 5); // prevent overriding IP with NAT IP
}
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
{
int version = fk::getGameVersion(pe.FH->TimeDateStamp);
if (version == fk::GAME_VERSION_NONE)
{
MessageBox(NULL, "fkNetcode is incompatible with your game version. Please run the 1.05 patch or "
"1.07 release of Worms 2. Otherwise, you can delete the module to remove this warning.",
"fkNetcode", MB_ICONWARNING);
}
else
{
configure();
patch(version);
}
}
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}