Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions apps/player-management-sw/src/ConnectGate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public partial class PlayerManagementPlugin
{
/**
* FiveStack_ConnectClient in shared/gamedata/fivestack.gamedata.json, which
* the gamedata validator checks after every game update. Signature near:
* "CNetworkGameServerBase::ConnectClient( name='%s', remote='%s' )\n"
* ships with the plugin and which the gamedata validator checks after every
* game update.
*
* Function signature:
* <pre>
Expand All @@ -43,11 +43,7 @@ public partial class PlayerManagementPlugin
* );
* </pre>
*/
private static readonly string ConnectClientSignature = RuntimeInformation.IsOSPlatform(
OSPlatform.Linux
)
? "55 48 89 E5 41 57 49 89 D7 41 56 49 89 FE 41 55 41 54 53 89 CB 48 81 EC ? ? ? ?"
: "48 89 5C 24 18 44 89 4C 24 20 55 41 54 41 55 41 56 41 57 48 8D 6C 24 F1 48 81 EC ? ? ? ? 81 64 24 4C FF FF 0F FF";
private const string ConnectClientSignature = "FiveStack_ConnectClient";

private IUnmanagedFunction<ConnectClientDelegate>? _connectClientFunc;
private Guid _connectClientHookId;
Expand All @@ -59,7 +55,15 @@ private void InstallConnectGate()
{
try
{
nint? found = Core.Memory.GetAddressBySignature(Library.Engine, ConnectClientSignature);
GamedataSignature? signature = GamedataUtility.Load(
Core.PluginPath,
ConnectClientSignature
);

nint? found =
signature == null
? null
: Core.Memory.GetAddressBySignature(signature.Library, signature.Pattern);

if (found == null || found == nint.Zero)
{
Expand Down
4 changes: 4 additions & 0 deletions apps/player-management-sw/src/PlayerManagement.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/PlayerManagementReport.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/ChatUtility.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/HttpClientProvider.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/GamedataUtility.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup>
<Content Include="resources/translations/**/*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)../../../shared/gamedata/fivestack.gamedata.json" Link="resources/gamedata/fivestack.gamedata.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
21 changes: 9 additions & 12 deletions apps/swiftly/src/FiveStack.Events/InitConnect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ public partial class FiveStackPlugin
public static Dictionary<ulong, string> PendingPlayers = new();

/**
* Signature near:
* "CNetworkGameServerBase::ConnectClient( name='%s', remote='%s' )\n"
*
* Function signature:
* <pre>
* virtual CServerSideClientBase* CNetworkGameServerBase::ConnectClient(
Expand All @@ -45,9 +42,7 @@ public partial class FiveStackPlugin
* );
* </pre>
*/
private static string ConnectClientSignature = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? "55 48 89 E5 41 57 49 89 D7 41 56 49 89 FE 41 55 41 54 53 89 CB 48 81 EC ? ? ? ?"
: "48 89 5C 24 18 44 89 4C 24 20 55 41 54 41 55 41 56 41 57 48 8D 6C 24 F1 48 81 EC ? ? ? ? 81 64 24 4C FF FF 0F FF";
private const string ConnectClientSignature = "FiveStack_ConnectClient";

private IUnmanagedFunction<ConnectClientDelegate>? _connectClientFunc;
private Guid _connectClientHookId;
Expand All @@ -61,13 +56,15 @@ private void InitializeConnectClientHook()
return;
}

if (string.IsNullOrEmpty(ConnectClientSignature))
{
_logger.LogWarning("ConnectClient signature is not available for this platform");
return;
}
GamedataSignature? signature = GamedataUtility.Load(
Core.PluginPath,
ConnectClientSignature
);

var address = Core.Memory.GetAddressBySignature(Library.Engine, ConnectClientSignature);
nint? address =
signature == null
? null
: Core.Memory.GetAddressBySignature(signature.Library, signature.Pattern);

if (address == null || address == nint.Zero)
{
Expand Down
14 changes: 8 additions & 6 deletions apps/swiftly/src/FiveStack.Utilities/ClanTagUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ public static class ClanTagUtility
// any nonzero id; the client only shows the tag when one is set
private const uint CustomClanId = 1;

private static readonly string SetClanSignature = RuntimeInformation.IsOSPlatform(
OSPlatform.Linux
)
? "55 48 89 E5 41 57 41 56 41 55 49 89 D5 41 54 53 48 89 FB 48 81 EC ? ? ? ? 39 B7 ? ? ? ?"
: "48 89 5C 24 10 48 89 6C 24 18 48 89 74 24 20 57 48 83 EC 20 49 8B E8 8B FA 48 8B F1 39 91 ? ? ? ? 74 ? BA FF FF FF FF";
private const string SetClanSignature = "FiveStack_CCSPlayerController_SetClan";

private static IUnmanagedFunction<SetClanDelegate>? _setClan;
private static Guid _hookId;

public static void Hook(ISwiftlyCore core)
{
nint? address = core.Memory.GetAddressBySignature(Library.Server, SetClanSignature);
GamedataSignature? signature = GamedataUtility.Load(core.PluginPath, SetClanSignature);

nint? address =
signature == null
? null
: core.Memory.GetAddressBySignature(signature.Library, signature.Pattern);

if (address == null || address == nint.Zero)
{
throw new InvalidOperationException("SetClan signature not found");
Expand Down
3 changes: 3 additions & 0 deletions apps/swiftly/src/FiveStack.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@
<Content Include="resources/translations/**/*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)../../../shared/gamedata/fivestack.gamedata.json" Link="resources/gamedata/fivestack.gamedata.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
46 changes: 46 additions & 0 deletions apps/swiftly/test/GamedataUtilityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using FiveStack.Utilities;
using Xunit;

public class GamedataUtilityTests
{
// The file the plugin ships, copied beside the tests by the project reference.
private static string Shipped()
{
return File.ReadAllText(GamedataUtility.PathIn(AppContext.BaseDirectory));
}

[Theory]
[InlineData("FiveStack_ConnectClient", "engine2", true)]
[InlineData("FiveStack_ConnectClient", "engine2", false)]
[InlineData("FiveStack_CCSPlayerController_SetClan", "server", true)]
[InlineData("FiveStack_CCSPlayerController_SetClan", "server", false)]
public void EverySignatureAPluginAsksForShipsWithIt(string name, string library, bool linux)
{
GamedataSignature? signature = GamedataUtility.Parse(Shipped(), name, linux);

Assert.NotNull(signature);
Assert.Equal(library, signature.Library);
Assert.Matches("^[0-9A-F?]{1,2}( [0-9A-F?]{1,2})+$", signature.Pattern);
}

[Fact]
public void LinuxAndWindowsGetTheirOwnPattern()
{
string json = """
{ "Thing": { "signatures": { "library": "server", "linux": "55 48", "windows": "48 89" } } }
""";

Assert.Equal("55 48", GamedataUtility.Parse(json, "Thing", linux: true)?.Pattern);
Assert.Equal("48 89", GamedataUtility.Parse(json, "Thing", linux: false)?.Pattern);
}

[Theory]
[InlineData("""{ }""")]
[InlineData("""{ "Thing": { "offsets": { "linux": 24 } } }""")]
[InlineData("""{ "Thing": { "signatures": { "library": "server", "windows": "48 89" } } }""")]
[InlineData("""{ "Thing": { "signatures": { "library": "server", "linux": " " } } }""")]
public void AnEntryWithNoPatternForThisPlatformIsNotFound(string json)
{
Assert.Null(GamedataUtility.Parse(json, "Thing", linux: true));
}
}
17 changes: 10 additions & 7 deletions apps/utility-sw/src/Events/PracticeConnect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ public partial class UtilityPracticePlugin
public static Dictionary<ulong, string> PendingPlayers = new();

/**
* Signature near:
* "CNetworkGameServerBase::ConnectClient( name='%s', remote='%s' )\n"
*
* Function signature:
* <pre>
* virtual CServerSideClientBase* CNetworkGameServerBase::ConnectClient(
Expand All @@ -47,9 +44,7 @@ public partial class UtilityPracticePlugin
* );
* </pre>
*/
private static string ConnectClientSignature = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? "55 48 89 E5 41 57 49 89 D7 41 56 49 89 FE 41 55 41 54 53 89 CB 48 81 EC ? ? ? ?"
: "48 89 5C 24 18 44 89 4C 24 20 55 41 54 41 55 41 56 41 57 48 8D 6C 24 F1 48 81 EC ? ? ? ? 81 64 24 4C FF FF 0F FF";
private const string ConnectClientSignature = "FiveStack_ConnectClient";

private IUnmanagedFunction<ConnectClientDelegate>? _connectClientFunc;
private Guid _connectClientHookId;
Expand All @@ -63,7 +58,15 @@ private void InitializeConnectClientHook()
return;
}

var address = Core.Memory.GetAddressBySignature(Library.Engine, ConnectClientSignature);
GamedataSignature? signature = GamedataUtility.Load(
Core.PluginPath,
ConnectClientSignature
);

nint? address =
signature == null
? null
: Core.Memory.GetAddressBySignature(signature.Library, signature.Pattern);

if (address == null || address == nint.Zero)
{
Expand Down
4 changes: 4 additions & 0 deletions apps/utility-sw/src/UtilityPractice.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/PracticeLibraryLoadUtility.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/HttpClientProvider.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/SteamIdUtility.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/GamedataUtility.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/HudSlots.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/HudLayoutContract.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/HudAimGrid.cs" LinkBase="Shared" />
Expand All @@ -70,6 +71,9 @@
<None Include="$(MSBuildThisFileDirectory)../hud/radars/metadata.json"
Link="radars/metadata.json"
CopyToOutputDirectory="PreserveNewest" />
<None Include="$(MSBuildThisFileDirectory)../../../shared/gamedata/fivestack.gamedata.json"
Link="resources/gamedata/fivestack.gamedata.json"
CopyToOutputDirectory="PreserveNewest" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/HudSteady.cs" LinkBase="Shared" />
<Compile Include="$(MSBuildThisFileDirectory)../../../shared/dotnet/FiveStack.Utilities/LineupNaming.cs" LinkBase="Shared" />
</ItemGroup>
Expand Down
51 changes: 51 additions & 0 deletions shared/dotnet/FiveStack.Utilities/GamedataUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Runtime.InteropServices;
using System.Text.Json;

namespace FiveStack.Utilities;

public record GamedataSignature(string Library, string Pattern);

// Signatures come from shared/gamedata/fivestack.gamedata.json, which ships
// beside the plugin. It is the file the gamedata validator checks after every
// game update, so what a plugin scans for is what was validated.
public static class GamedataUtility
{
public static string PathIn(string pluginPath)
{
return Path.Join(pluginPath, "resources", "gamedata", "fivestack.gamedata.json");
}

public static GamedataSignature? Load(string pluginPath, string name)
{
return Parse(
File.ReadAllText(PathIn(pluginPath)),
name,
RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
);
}

public static GamedataSignature? Parse(string json, string name, bool linux)
{
using JsonDocument document = JsonDocument.Parse(json);

if (
!document.RootElement.TryGetProperty(name, out JsonElement entry)
|| !entry.TryGetProperty("signatures", out JsonElement signatures)
|| !signatures.TryGetProperty("library", out JsonElement library)
|| !signatures.TryGetProperty(linux ? "linux" : "windows", out JsonElement pattern)
)
{
return null;
}

string? libraryName = library.GetString();
string? bytes = pattern.GetString();

if (string.IsNullOrWhiteSpace(libraryName) || string.IsNullOrWhiteSpace(bytes))
{
return null;
}

return new GamedataSignature(libraryName, bytes);
}
}
Loading