From 5b2afa312dd0fbce4685c861d03f49aeb19d1e8a Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Sat, 12 Sep 2026 16:26:13 +1200 Subject: [PATCH 1/2] feat: extract NosCore.ClientTools so a launcher can reuse the client plumbing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auth flow and the PE patches are not packet-logger concerns — they are what any NosCore-facing launcher needs: trade credentials for an auth code, point a client binary at a server, and hand it a gf_wrapper replacement. They lived in a WinExe, so nothing outside this repo could reference them. Moves NosCoreAuthClient and ClientPatcher into a packable AnyCPU library and gives the embedded stub a real API (GfStub.OpenStream / DeployTo) instead of a bare GetManifestResourceStream call in MainForm. The x86 constraint stays where it belongs: on the injector and the stub, not on consumers that only spawn the client. Also drops Build.0 for the two NativeAOT payload projects. The solution was building them AnyCPU while the Exec targets published them for win-x86 at the same time, racing on the same obj/ directory. Adding a third project changed the scheduling enough to start losing that race. Co-Authored-By: Claude Opus 5 (1M context) --- NosCore.DeveloperTools.sln | 29 +++++++------ .../ClientPatcher.cs | 6 +-- src/NosCore.ClientTools/GfStub.cs | 36 ++++++++++++++++ .../NosCore.ClientTools.csproj | 43 +++++++++++++++++++ .../NosCoreAuthClient.cs | 2 +- src/NosCore.DeveloperTools/Forms/MainForm.cs | 8 +--- .../Models/AppSettings.cs | 2 +- .../NosCore.DeveloperTools.csproj | 16 ++----- 8 files changed, 106 insertions(+), 36 deletions(-) rename src/{NosCore.DeveloperTools/Services => NosCore.ClientTools}/ClientPatcher.cs (98%) create mode 100644 src/NosCore.ClientTools/GfStub.cs create mode 100644 src/NosCore.ClientTools/NosCore.ClientTools.csproj rename src/{NosCore.DeveloperTools/Services => NosCore.ClientTools}/NosCoreAuthClient.cs (98%) diff --git a/NosCore.DeveloperTools.sln b/NosCore.DeveloperTools.sln index 3c77e87..95f8462 100644 --- a/NosCore.DeveloperTools.sln +++ b/NosCore.DeveloperTools.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.5.11709.299 stable +VisualStudioVersion = 18.5.11709.299 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosCore.DeveloperTools", "src\NosCore.DeveloperTools\NosCore.DeveloperTools.csproj", "{C6B8F7A1-0001-0001-0001-000000000001}" EndProject @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosCore.DeveloperTools.GfStub", "src\NosCore.DeveloperTools.GfStub\NosCore.DeveloperTools.GfStub.csproj", "{D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosCore.ClientTools", "src\NosCore.ClientTools\NosCore.ClientTools.csproj", "{893C8F61-2B59-472C-ADE4-28D76A086764}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,29 +36,29 @@ Global {C6B8F7A1-0001-0001-0001-000000000001}.Release|x86.ActiveCfg = Release|Any CPU {C6B8F7A1-0001-0001-0001-000000000001}.Release|x86.Build.0 = Release|Any CPU {C6B8F7A1-0002-0001-0001-000000000001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6B8F7A1-0002-0001-0001-000000000001}.Debug|Any CPU.Build.0 = Debug|Any CPU {C6B8F7A1-0002-0001-0001-000000000001}.Debug|x64.ActiveCfg = Debug|Any CPU - {C6B8F7A1-0002-0001-0001-000000000001}.Debug|x64.Build.0 = Debug|Any CPU {C6B8F7A1-0002-0001-0001-000000000001}.Debug|x86.ActiveCfg = Debug|Any CPU - {C6B8F7A1-0002-0001-0001-000000000001}.Debug|x86.Build.0 = Debug|Any CPU {C6B8F7A1-0002-0001-0001-000000000001}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6B8F7A1-0002-0001-0001-000000000001}.Release|Any CPU.Build.0 = Release|Any CPU {C6B8F7A1-0002-0001-0001-000000000001}.Release|x64.ActiveCfg = Release|Any CPU - {C6B8F7A1-0002-0001-0001-000000000001}.Release|x64.Build.0 = Release|Any CPU {C6B8F7A1-0002-0001-0001-000000000001}.Release|x86.ActiveCfg = Release|Any CPU - {C6B8F7A1-0002-0001-0001-000000000001}.Release|x86.Build.0 = Release|Any CPU {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Debug|x64.ActiveCfg = Debug|Any CPU - {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Debug|x64.Build.0 = Debug|Any CPU {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Debug|x86.ActiveCfg = Debug|Any CPU - {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Debug|x86.Build.0 = Debug|Any CPU {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Release|Any CPU.Build.0 = Release|Any CPU {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Release|x64.ActiveCfg = Release|Any CPU - {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Release|x64.Build.0 = Release|Any CPU {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Release|x86.ActiveCfg = Release|Any CPU - {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C}.Release|x86.Build.0 = Release|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Debug|Any CPU.Build.0 = Debug|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Debug|x64.ActiveCfg = Debug|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Debug|x64.Build.0 = Debug|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Debug|x86.ActiveCfg = Debug|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Debug|x86.Build.0 = Debug|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Release|Any CPU.ActiveCfg = Release|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Release|Any CPU.Build.0 = Release|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Release|x64.ActiveCfg = Release|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Release|x64.Build.0 = Release|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Release|x86.ActiveCfg = Release|Any CPU + {893C8F61-2B59-472C-ADE4-28D76A086764}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,5 +67,6 @@ Global {C6B8F7A1-0001-0001-0001-000000000001} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {C6B8F7A1-0002-0001-0001-000000000001} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {D3A3DE1B-5292-4AAB-A335-31D2F4885A3C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {893C8F61-2B59-472C-ADE4-28D76A086764} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection EndGlobal diff --git a/src/NosCore.DeveloperTools/Services/ClientPatcher.cs b/src/NosCore.ClientTools/ClientPatcher.cs similarity index 98% rename from src/NosCore.DeveloperTools/Services/ClientPatcher.cs rename to src/NosCore.ClientTools/ClientPatcher.cs index ace8083..ddcd336 100644 --- a/src/NosCore.DeveloperTools/Services/ClientPatcher.cs +++ b/src/NosCore.ClientTools/ClientPatcher.cs @@ -1,6 +1,6 @@ using System.Text; -namespace NosCore.DeveloperTools.Services; +namespace NosCore.ClientTools; /// /// In-place byte patches against the Gameforge NosTale client @@ -245,7 +245,7 @@ public static PatchResult PatchAllowNoArg(byte[] bytes) public static PatchResult PatchImportName(byte[] bytes) { var needle = Encoding.ASCII.GetBytes("gf_wrapper.dll\0"); - var replacement = Encoding.ASCII.GetBytes("noscore_gf.dll\0"); + var replacement = Encoding.ASCII.GetBytes(GfStub.FileName + "\0"); if (needle.Length != replacement.Length) { return new PatchResult(false, "Internal error: replacement DLL name must match original length."); @@ -259,7 +259,7 @@ public static PatchResult PatchImportName(byte[] bytes) for (var i = 0; i < replacement.Length; i++) bytes[offset + i] = replacement[i]; return new PatchResult(true, - $"Import rename: 'gf_wrapper.dll' -> 'noscore_gf.dll' at 0x{offset:X}. Drop noscore_gf.dll next to the patched exe."); + $"Import rename: 'gf_wrapper.dll' -> '{GfStub.FileName}' at 0x{offset:X}. Drop {GfStub.FileName} next to the patched exe."); } private static int FindBytes(byte[] haystack, byte[] needle, int startOffset) diff --git a/src/NosCore.ClientTools/GfStub.cs b/src/NosCore.ClientTools/GfStub.cs new file mode 100644 index 0000000..701efa8 --- /dev/null +++ b/src/NosCore.ClientTools/GfStub.cs @@ -0,0 +1,36 @@ +using System.Reflection; + +namespace NosCore.ClientTools; + +/// +/// Access to the embedded noscore_gf.dll payload — the NativeAOT x86 +/// replacement for the client's gf_wrapper.dll. A patched client +/// resolves its Gameforge imports against this file by name, so it has to sit +/// next to the patched exe under exactly . +/// +public static class GfStub +{ + /// + /// Import-table name the patched client loads the stub by. Must stay the + /// same length as gf_wrapper.dll so + /// can overwrite that literal + /// in place without relocating the PE import directory. + /// + public const string FileName = "noscore_gf.dll"; + + public static Stream OpenStream() => + typeof(GfStub).Assembly.GetManifestResourceStream(FileName) + ?? throw new FileNotFoundException( + $"{FileName} is not embedded in {Assembly.GetExecutingAssembly().GetName().Name}. " + + "Build NosCore.ClientTools so its GfStub publish target runs."); + + /// Write the stub into and return its full path. + public static string DeployTo(string directory) + { + var path = Path.Combine(directory, FileName); + using var source = OpenStream(); + using var destination = File.Create(path); + source.CopyTo(destination); + return path; + } +} diff --git a/src/NosCore.ClientTools/NosCore.ClientTools.csproj b/src/NosCore.ClientTools/NosCore.ClientTools.csproj new file mode 100644 index 0000000..131ac3a --- /dev/null +++ b/src/NosCore.ClientTools/NosCore.ClientTools.csproj @@ -0,0 +1,43 @@ + + + + net10.0 + NosCore.ClientTools + NosCore.ClientTools + + AnyCPU + + + + true + NosCore.ClientTools + Authenticate against a NosCore server and prepare a NosTale client to connect to it: auth-code retrieval, PE patches for server address and launch arguments, and the gf_wrapper replacement stub. + noscore;nostale + GPL-3.0-only + https://github.com/NosCoreIO/NosCore.DeveloperTools + https://github.com/NosCoreIO/NosCore.DeveloperTools + git + true + + + + $(MSBuildThisFileDirectory)..\NosCore.DeveloperTools.GfStub\NosCore.DeveloperTools.GfStub.csproj + $(MSBuildThisFileDirectory)..\NosCore.DeveloperTools.GfStub\bin\$(Configuration)\net10.0\win-x86\publish\ + $(GfStubPublishDir)noscore_gf.dll + + + + + + + + + + + + + diff --git a/src/NosCore.DeveloperTools/Services/NosCoreAuthClient.cs b/src/NosCore.ClientTools/NosCoreAuthClient.cs similarity index 98% rename from src/NosCore.DeveloperTools/Services/NosCoreAuthClient.cs rename to src/NosCore.ClientTools/NosCoreAuthClient.cs index b1f24fd..9bd2225 100644 --- a/src/NosCore.DeveloperTools/Services/NosCoreAuthClient.cs +++ b/src/NosCore.ClientTools/NosCoreAuthClient.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.Json.Serialization; -namespace NosCore.DeveloperTools.Services; +namespace NosCore.ClientTools; /// /// Thin HTTP client for the NosCore auth endpoints. Two-step flow: diff --git a/src/NosCore.DeveloperTools/Forms/MainForm.cs b/src/NosCore.DeveloperTools/Forms/MainForm.cs index 820bbb6..4ae8004 100644 --- a/src/NosCore.DeveloperTools/Forms/MainForm.cs +++ b/src/NosCore.DeveloperTools/Forms/MainForm.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Diagnostics; +using NosCore.ClientTools; using NosCore.DeveloperTools.Models; using NosCore.DeveloperTools.Remote; using NosCore.DeveloperTools.Services; @@ -617,14 +618,9 @@ private static void RunPatch(string newAddress, string exePath, string outputNam // clobber the original. if (stubResult.Success) { - var stubPath = Path.Combine(outDir, "noscore_gf.dll"); try { - using var stubStream = typeof(MainForm).Assembly.GetManifestResourceStream("noscore_gf.dll") - ?? throw new FileNotFoundException("Stub DLL not embedded in this build."); - using var outStream = File.Create(stubPath); - stubStream.CopyTo(outStream); - Log($"Wrote {stubPath}"); + Log($"Wrote {GfStub.DeployTo(outDir)}"); } catch (Exception ex) { diff --git a/src/NosCore.DeveloperTools/Models/AppSettings.cs b/src/NosCore.DeveloperTools/Models/AppSettings.cs index 57f5d1c..18152f5 100644 --- a/src/NosCore.DeveloperTools/Models/AppSettings.cs +++ b/src/NosCore.DeveloperTools/Models/AppSettings.cs @@ -1,4 +1,4 @@ -using NosCore.DeveloperTools.Services; +using NosCore.ClientTools; namespace NosCore.DeveloperTools.Models; diff --git a/src/NosCore.DeveloperTools/NosCore.DeveloperTools.csproj b/src/NosCore.DeveloperTools/NosCore.DeveloperTools.csproj index 44924a3..a099f16 100644 --- a/src/NosCore.DeveloperTools/NosCore.DeveloperTools.csproj +++ b/src/NosCore.DeveloperTools/NosCore.DeveloperTools.csproj @@ -40,14 +40,14 @@ + + + + $(MSBuildThisFileDirectory)..\NosCore.DeveloperTools.Hook\NosCore.DeveloperTools.Hook.csproj $(MSBuildThisFileDirectory)..\NosCore.DeveloperTools.Hook\bin\$(Configuration)\net10.0\win-x86\publish\ $(HookPublishDir)NosCore.DeveloperTools.Hook.dll - - $(MSBuildThisFileDirectory)..\NosCore.DeveloperTools.GfStub\NosCore.DeveloperTools.GfStub.csproj - $(MSBuildThisFileDirectory)..\NosCore.DeveloperTools.GfStub\bin\$(Configuration)\net10.0\win-x86\publish\ - $(GfStubPublishDir)noscore_gf.dll @@ -59,12 +59,4 @@ - - - - - - - - From 1c2a40a9752599a66b90b56fa2b7aca029123e41 Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Sat, 12 Sep 2026 16:37:34 +1200 Subject: [PATCH 2/2] fix: package license is MIT, matching the repo Co-Authored-By: Claude Opus 5 (1M context) --- src/NosCore.ClientTools/NosCore.ClientTools.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NosCore.ClientTools/NosCore.ClientTools.csproj b/src/NosCore.ClientTools/NosCore.ClientTools.csproj index 131ac3a..3fc1ede 100644 --- a/src/NosCore.ClientTools/NosCore.ClientTools.csproj +++ b/src/NosCore.ClientTools/NosCore.ClientTools.csproj @@ -15,7 +15,7 @@ NosCore.ClientTools Authenticate against a NosCore server and prepare a NosTale client to connect to it: auth-code retrieval, PE patches for server address and launch arguments, and the gf_wrapper replacement stub. noscore;nostale - GPL-3.0-only + MIT https://github.com/NosCoreIO/NosCore.DeveloperTools https://github.com/NosCoreIO/NosCore.DeveloperTools git