diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 380f0fc59..014edd09c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "v6", v* ] +env: + DOTNET_VERSION: 8.x + jobs: analyze: name: Analyze @@ -29,9 +32,28 @@ jobs: with: languages: ${{ matrix.language }} - - name: Autobuild + - if: matrix.language != 'csharp' + name: Autobuild uses: github/codeql-action/autobuild@v3 + - if: matrix.language == 'csharp' + name: Setup .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - if: matrix.language == 'csharp' + name: Build Patcher + run: | + export DOTNET_NOLOGO=true + export DOTNET_CLI_TELEMETRY_OPTOUT=true + cd $GITHUB_WORKSPACE/patcher + dotnet build HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -c Release -p:EnableWindowsTargeting=True + dotnet build HitmanPatcher.UI/HitmanPatcher.UI.csproj -c Release -p:EnableWindowsTargeting=True + dotnet build HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -r linux-x64 -c "Release - Linux" -p:IsLinux=true + dotnet build HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -c Release -p:EnableWindowsTargeting=True -p:IsLegacy=True + dotnet build HitmanPatcher.UI/HitmanPatcher.UI.csproj -c Release -p:EnableWindowsTargeting=True -p:IsLegacy=True + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: diff --git a/.github/workflows/patcher.yml b/.github/workflows/patcher.yml index 89fcb2f07..3806b5ace 100644 --- a/.github/workflows/patcher.yml +++ b/.github/workflows/patcher.yml @@ -8,23 +8,69 @@ on: branches: [ "v*" ] paths: [ "patcher/**/*" ] +env: + DOTNET_VERSION: 8.x + jobs: build: name: Build Patcher - runs-on: windows-latest + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} - name: Build Patcher - run: msbuild.exe patcher\HitmanPatcher.sln -t:Build -p:Configuration=Release -p:Platform=x64 -m + run: | + export DOTNET_NOLOGO=true + export DOTNET_CLI_TELEMETRY_OPTOUT=true + cd patcher + dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -c Release -p:PublishSingleFile=True --no-self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Portable + dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -r win-x64 -c Release -f net8.0 -p:PublishTrimmed=True -p:PublishSingleFile=True --self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-x64 + dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -r linux-x64 -c "Release - Linux" -p:PublishSingleFile=True --no-self-contained -p DebugType=none -p:IsLinux=true -o Publish/Linux-Portable + dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -r linux-x64 -c "Release - Linux" -f net8.0 -p:PublishTrimmed=True -p:PublishSingleFile=True --self-contained -p DebugType=none -p:IsLinux=true -o Publish/Linux-x64 + dotnet publish HitmanPatcher.UI/HitmanPatcher.UI.csproj -c Release -p:PublishSingleFile=True --no-self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Portable + dotnet publish HitmanPatcher.UI/HitmanPatcher.UI.csproj -r win-x64 -c Release -p:PublishSingleFile=True --self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-x64 + dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -c Release -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Legacy -p:IsLegacy=True + dotnet publish HitmanPatcher.UI/HitmanPatcher.UI.csproj -c Release -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Legacy -p:IsLegacy=True + + - name: Upload patcher-windows-portable + uses: actions/upload-artifact@v4 + with: + name: patcher-windows-portable + path: patcher/Publish/Windows-Portable + + - name: Upload patcher-cli-windows + uses: actions/upload-artifact@v4 + with: + name: patcher-cli-windows + path: patcher/Publish/Windows-x64/PeacockPatcher.CLI.exe + + - name: Upload patcher-ui-windows + uses: actions/upload-artifact@v4 + with: + name: patcher-ui-windows + path: patcher/Publish/Windows-x64/PeacockPatcher.UI.exe + + - name: Upload patcher-linux-portable + uses: actions/upload-artifact@v4 + with: + name: patcher-linux-portable + path: patcher/Publish/Linux-Portable + + - name: Upload patcher-linux + uses: actions/upload-artifact@v4 + with: + name: patcher-linux + path: patcher/Publish/Linux-x64 - - name: Upload Patcher Artifacts + - name: Upload patcher-windows-legacy uses: actions/upload-artifact@v4 with: - name: patcher-windows - path: patcher/bin/x64/Release/PeacockPatcher.exe + name: patcher-windows-legacy + path: patcher/Publish/Windows-Legacy/ILRepack diff --git a/patcher/.gitignore b/patcher/.gitignore index 1747fa209..3d855743a 100644 --- a/patcher/.gitignore +++ b/patcher/.gitignore @@ -1,6 +1,9 @@ -.vs/* -bin/* -obj/* +.vs +bin +obj +/Publish +/Res /HitmanPatcher.sln.DotSettings.user /.idea/.idea.HitmanPatcher/.idea/riderMarkupCache.xml *.suo +*.user \ No newline at end of file diff --git a/patcher/.idea/.idea.HitmanPatcher/.idea/.gitignore b/patcher/.idea/.idea.HitmanPatcher/.idea/.gitignore index 81d8650d4..b0bc3ebd5 100644 --- a/patcher/.idea/.idea.HitmanPatcher/.idea/.gitignore +++ b/patcher/.idea/.idea.HitmanPatcher/.idea/.gitignore @@ -1,9 +1,13 @@ -# Default ignored files +# Default ignored files /shelf/ /workspace.xml # Rider ignored files +/projectSettingsUpdater.xml /modules.xml /contentModel.xml -/projectSettingsUpdater.xml /.idea.HitmanPatcher.iml -discord.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/patcher/.idea/.idea.HitmanPatcher/.idea/copyright/AGPL_3_0.xml b/patcher/.idea/.idea.HitmanPatcher/.idea/copyright/AGPL_3_0.xml index 0e3ba8f2e..716f85dad 100644 --- a/patcher/.idea/.idea.HitmanPatcher/.idea/copyright/AGPL_3_0.xml +++ b/patcher/.idea/.idea.HitmanPatcher/.idea/copyright/AGPL_3_0.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/patcher/.idea/.idea.HitmanPatcher/.idea/encodings.xml b/patcher/.idea/.idea.HitmanPatcher/.idea/encodings.xml new file mode 100644 index 000000000..df87cf951 --- /dev/null +++ b/patcher/.idea/.idea.HitmanPatcher/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/patcher/App.config b/patcher/App.config deleted file mode 100644 index fcf2f62a0..000000000 --- a/patcher/App.config +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/patcher/CliLocale.Designer.cs b/patcher/CliLocale.Designer.cs deleted file mode 100644 index 679a1f453..000000000 --- a/patcher/CliLocale.Designer.cs +++ /dev/null @@ -1,84 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace HitmanPatcher { - using System; - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class CliLocale { - - private static System.Resources.ResourceManager resourceMan; - - private static System.Globalization.CultureInfo resourceCulture; - - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal CliLocale() { - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { - get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HitmanPatcher.CliLocale", typeof(CliLocale).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - internal static string HeadlessDescription { - get { - return ResourceManager.GetString("HeadlessDescription", resourceCulture); - } - } - - internal static string DomainDescription { - get { - return ResourceManager.GetString("DomainDescription", resourceCulture); - } - } - - internal static string UseHttpDescription { - get { - return ResourceManager.GetString("UseHttpDescription", resourceCulture); - } - } - - internal static string OptionalDynResDescription { - get { - return ResourceManager.GetString("OptionalDynResDescription", resourceCulture); - } - } - - internal static string HeadlessBanner { - get { - return ResourceManager.GetString("HeadlessBanner", resourceCulture); - } - } - - internal static string HelpHeader { - get { - return ResourceManager.GetString("HelpHeader", resourceCulture); - } - } - } -} diff --git a/patcher/CliLocale.resx b/patcher/CliLocale.resx deleted file mode 100644 index b251cf8ee..000000000 --- a/patcher/CliLocale.resx +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - If the patcher should be run without the GUI. - - - The domain for the game to connect to. - - - If the game should connect using "http://" instead of the default, "https://". - - - If the game should not force the user offline if the dynamic resources are missing or invalid. - - - ====== Peacock Patcher - Headless mode ====== - - - Peacock Patcher CLI - Copyright (c) 2020-2024 grappigegovert & The Peacock Project - - diff --git a/patcher/Cli.cs b/patcher/HitmanPatcher.CLI/Cli.cs similarity index 71% rename from patcher/Cli.cs rename to patcher/HitmanPatcher.CLI/Cli.cs index 413614f27..68490420a 100644 --- a/patcher/Cli.cs +++ b/patcher/HitmanPatcher.CLI/Cli.cs @@ -1,10 +1,3 @@ -using System; -using System.Diagnostics.CodeAnalysis; -#if !DEBUG -using System.Linq; -using System.Runtime.InteropServices; -#endif - namespace HitmanPatcher { internal static class Cli @@ -13,41 +6,30 @@ internal static class Cli // in any mode outside debug (which is probably what it's being run in while in IDE), // we attach the console to the parent process - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool AttachConsole(int dwProcessId); - private const int ATTACH_PARENT_PROCESS = -1; internal static void EnsureConsole(string[] args) { if (args.Any(arg => arg.Contains("-"))) { - AttachConsole(ATTACH_PARENT_PROCESS); + Pinvoke.AttachConsole(ATTACH_PARENT_PROCESS); } } #endif internal class CliOptions { - internal bool Headless { get; set; } - internal string Domain { get; set; } - internal bool UseHttp { get; set; } + internal bool? UseHttp { get; set; } - internal bool OptionalDynRes { get; set; } + internal bool? OptionalDynRes { get; set; } + + internal bool? KeepScanning { get; set; } - [SuppressMessage("ReSharper", "LocalizableElement")] internal static CliOptions FromArguments(string[] args) { - var options = new CliOptions - { - Domain = "127.0.0.1", - Headless = false, - OptionalDynRes = true, - UseHttp = true - }; + var options = new CliOptions(); var i = 0; @@ -63,9 +45,6 @@ internal static CliOptions FromArguments(string[] args) { switch (arg) { - case "--headless": - options.Headless = true; - break; case "--optional-dynamic-resources": options.OptionalDynRes = true; break; @@ -76,14 +55,17 @@ internal static CliOptions FromArguments(string[] args) case "--use-http": options.UseHttp = true; break; + case "--keep-scanning": + options.KeepScanning = true; + break; case "--help": Console.WriteLine(CliLocale.HelpHeader); Console.WriteLine(""); Console.WriteLine("Options:"); - Console.WriteLine($" --headless : {CliLocale.HeadlessDescription}"); Console.WriteLine($" --optional-dynamic-resources : {CliLocale.OptionalDynResDescription}"); Console.WriteLine($" --domain : {CliLocale.DomainDescription}"); Console.WriteLine($" --use-http : {CliLocale.UseHttpDescription}"); + Console.WriteLine($" --keep-scanning : {CliLocale.KeepScanning}"); Environment.Exit(0); break; } diff --git a/patcher/HitmanPatcher.CLI/CliLocale.Designer.cs b/patcher/HitmanPatcher.CLI/CliLocale.Designer.cs new file mode 100644 index 000000000..c8893e013 --- /dev/null +++ b/patcher/HitmanPatcher.CLI/CliLocale.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace HitmanPatcher { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CliLocale { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CliLocale() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HitmanPatcher.CliLocale", typeof(CliLocale).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The domain for the game to connect to.. + /// + internal static string DomainDescription { + get { + return ResourceManager.GetString("DomainDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ====== Peacock Patcher - Headless mode ======. + /// + internal static string HeadlessBanner { + get { + return ResourceManager.GetString("HeadlessBanner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Peacock Patcher CLI - Copyright (c) 2020-2024 grappigegovert & The Peacock Project. + /// + internal static string HelpHeader { + get { + return ResourceManager.GetString("HelpHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to If the patcher should keep scanning for processes after patching.. + /// + internal static string KeepScanning { + get { + return ResourceManager.GetString("KeepScanning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to If the game should not force the user offline if the dynamic resources are missing or invalid.. + /// + internal static string OptionalDynResDescription { + get { + return ResourceManager.GetString("OptionalDynResDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to If the game should connect using "http://" instead of the default, "https://".. + /// + internal static string UseHttpDescription { + get { + return ResourceManager.GetString("UseHttpDescription", resourceCulture); + } + } + } +} diff --git a/patcher/HitmanPatcher.CLI/CliLocale.resx b/patcher/HitmanPatcher.CLI/CliLocale.resx new file mode 100644 index 000000000..11c75da8e --- /dev/null +++ b/patcher/HitmanPatcher.CLI/CliLocale.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + If the patcher should keep scanning for processes after patching. + + + The domain for the game to connect to. + + + If the game should connect using "http://" instead of the default, "https://". + + + If the game should not force the user offline if the dynamic resources are missing or invalid. + + + ====== Peacock Patcher - Headless mode ====== + + + Peacock Patcher CLI - Copyright (c) 2020-2024 grappigegovert & The Peacock Project + + \ No newline at end of file diff --git a/patcher/HitmanPatcher.CLI/HitmanPatcher.CLI.csproj b/patcher/HitmanPatcher.CLI/HitmanPatcher.CLI.csproj new file mode 100644 index 000000000..84a0ec1ef --- /dev/null +++ b/patcher/HitmanPatcher.CLI/HitmanPatcher.CLI.csproj @@ -0,0 +1,59 @@ + + + Exe + net8.0 + net46 + enable + disable + + HitmanPatcher + PeacockPatcher.CLI + + Debug;Release;Debug - Linux;Release - Linux + + Peacock Patcher + Peacock's HITMAN™ World of Assassination trilogy game patcher. + The Peacock Project + Peacock Patcher + Copyright © 2020-2024 grappigegovert & The Peacock Project + true + DpiUnawareGdiScaled + patcher.ico + 8.0.0.0 + 8.0.0.0 + + latest + + + + $(DefineConstants);LINUX;DEBUG + + + + $(DefineConstants);LINUX + True + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + CliLocale.resx + True + True + + + CliLocale.Designer.cs + ResXFileCodeGenerator + + + \ No newline at end of file diff --git a/patcher/HitmanPatcher.CLI/ILRepack.targets b/patcher/HitmanPatcher.CLI/ILRepack.targets new file mode 100644 index 000000000..5ffd50f69 --- /dev/null +++ b/patcher/HitmanPatcher.CLI/ILRepack.targets @@ -0,0 +1,16 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/patcher/HitmanPatcher.CLI/Program.cs b/patcher/HitmanPatcher.CLI/Program.cs new file mode 100644 index 000000000..11c8055f9 --- /dev/null +++ b/patcher/HitmanPatcher.CLI/Program.cs @@ -0,0 +1,54 @@ +namespace HitmanPatcher +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main(string[] args) + { + Compositions.Logger = new Cli.ConsoleLogger(); + +#if !DEBUG + Cli.EnsureConsole(args); +#endif + + Cli.CliOptions o = Cli.CliOptions.FromArguments(args); + + // ReSharper disable once LocalizableElement + Console.WriteLine(CliLocale.HeadlessBanner); + + var settings = Settings.GetFromFile(); + settings.SaveToFile(); + + Console.WriteLine("Settings:"); + Console.WriteLine($"- File = {Path.GetFullPath(Settings.GetSavePath())}"); + Console.WriteLine($"- CustomConfigDomain = {settings.patchOptions.CustomConfigDomain}"); + Console.WriteLine($"- UseHttp = {settings.patchOptions.UseHttp}"); + Console.WriteLine($"- DisableForceDynamicResources = {settings.patchOptions.DisableForceOfflineOnFailedDynamicResources}"); + Console.WriteLine(); + + // Set the active values + settings.patchOptions.CustomConfigDomain = o.Domain ?? settings.patchOptions.CustomConfigDomain; + settings.patchOptions.DisableForceOfflineOnFailedDynamicResources = o.OptionalDynRes ?? settings.patchOptions.DisableForceOfflineOnFailedDynamicResources; + settings.patchOptions.UseHttp = o.UseHttp ?? settings.patchOptions.UseHttp; + + var keepScanning = o.KeepScanning ?? false; + + while (true) + { + var result = MemoryPatcher.PatchAllProcesses(Compositions.Logger, settings.patchOptions); + + if (result && !keepScanning) + { + break; + } + + Console.WriteLine("Waiting..."); + + Thread.Sleep(1000); + } + } + } +} diff --git a/patcher/patcher.ico b/patcher/HitmanPatcher.CLI/patcher.ico similarity index 100% rename from patcher/patcher.ico rename to patcher/HitmanPatcher.CLI/patcher.ico diff --git a/patcher/AOBScanner.cs b/patcher/HitmanPatcher.Core/AOBScanner.cs similarity index 97% rename from patcher/AOBScanner.cs rename to patcher/HitmanPatcher.Core/AOBScanner.cs index 89b9ee49c..97b5ed160 100644 --- a/patcher/AOBScanner.cs +++ b/patcher/HitmanPatcher.Core/AOBScanner.cs @@ -1,20 +1,16 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; namespace HitmanPatcher { internal static class AOBScanner { - public static bool TryGetHitmanVersionByScanning(Process process, + public static bool TryGetHitmanVersionByScanning(ProcessMetadata processMetadata, IntPtr hProcess, out HitmanVersion result) { Stopwatch bench = Stopwatch.StartNew(); - IntPtr baseAddress = process.MainModule.BaseAddress; - byte[] exeData = new byte[process.MainModule.ModuleMemorySize]; + IntPtr baseAddress = processMetadata.BaseAddress; + byte[] exeData = new byte[processMetadata.ModuleMemorySize]; Pinvoke.ReadProcessMemory(hProcess, baseAddress, exeData, (UIntPtr) exeData.Length, out _); // fuck it, just read the whole thing @@ -77,7 +73,7 @@ public static bool TryGetHitmanVersionByScanning(Process process, Task.WaitAll(alltasks); bench.Stop(); - Console.WriteLine(bench.Elapsed.ToString()); + Compositions.Logger.log(bench.Elapsed.ToString()); // error out if any task does not have exactly 1 result if (alltasks.Any(task => task.Result.Count() != 1)) @@ -113,7 +109,7 @@ public static bool TryGetHitmanVersionByScanning(Process process, #if DEBUG private static void Note(string name, Patch patch) { - MainForm.GetInstance().log($"{name}: {patch.offset:X} {BitConverter.ToString(patch.original).Replace("-", string.Empty)} {BitConverter.ToString(patch.patch).Replace("-", string.Empty)}"); + Compositions.Logger.log($"{name}: {patch.offset:X} {BitConverter.ToString(patch.original).Replace("-", string.Empty)} {BitConverter.ToString(patch.patch).Replace("-", string.Empty)}"); } #endif diff --git a/patcher/HitmanPatcher.Core/Compositions.cs b/patcher/HitmanPatcher.Core/Compositions.cs new file mode 100644 index 000000000..e6e1fc715 --- /dev/null +++ b/patcher/HitmanPatcher.Core/Compositions.cs @@ -0,0 +1,10 @@ +namespace HitmanPatcher +{ + public static class Compositions + { + //NOTE: This will only have to be determined once + public static bool HasAdmin { get; } = Pinvoke.CheckForAdmin(); + + public static ILoggingProvider Logger { get; set; } + } +} diff --git a/patcher/HitmanPatcher.Core/HitmanPatcher.Core.csproj b/patcher/HitmanPatcher.Core/HitmanPatcher.Core.csproj new file mode 100644 index 000000000..414ddfb3b --- /dev/null +++ b/patcher/HitmanPatcher.Core/HitmanPatcher.Core.csproj @@ -0,0 +1,30 @@ + + + net8.0 + net46 + enable + disable + + HitmanPatcher + PeacockPatcher.Core + + Debug;Release;Debug - Linux;Release - Linux + + latest + + + + CA1416 + + + + $(DefineConstants);LINUX;DEBUG + true + + + + $(DefineConstants);LINUX + true + True + + diff --git a/patcher/HitmanVersion.cs b/patcher/HitmanPatcher.Core/HitmanVersion.cs similarity index 86% rename from patcher/HitmanVersion.cs rename to patcher/HitmanPatcher.Core/HitmanVersion.cs index 24ac7dc77..72c451dd4 100644 --- a/patcher/HitmanVersion.cs +++ b/patcher/HitmanPatcher.Core/HitmanVersion.cs @@ -1,11 +1,19 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Remoting.Metadata.W3cXsd2001; using System.Text; using HitmanPatcher.PatchDefinitions; namespace HitmanPatcher { + public static class SoapHexBinary + { + public static byte[] Parse(string value) + { + return Enumerable.Range(0, value.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(value.Substring(x, 2), 16)) + .ToArray(); + } + } + public class Patch { public static readonly byte[] http = Encoding.ASCII.GetBytes("http://{0}\0").ToArray(); @@ -26,7 +34,7 @@ public Patch(int offset, byte[] original, byte[] patch, MemProtection defaultPro } public Patch(int offset, string original, string patch, MemProtection defaultProtection, string customPatch = "") - : this(offset, SoapHexBinary.Parse(original).Value, SoapHexBinary.Parse(patch).Value, defaultProtection, customPatch) + : this(offset, SoapHexBinary.Parse(original), SoapHexBinary.Parse(patch), defaultProtection, customPatch) { } diff --git a/patcher/MemoryPatcher.cs b/patcher/HitmanPatcher.Core/MemoryPatcher.cs similarity index 76% rename from patcher/MemoryPatcher.cs rename to patcher/HitmanPatcher.Core/MemoryPatcher.cs index 668556b30..f34e8840c 100644 --- a/patcher/MemoryPatcher.cs +++ b/patcher/HitmanPatcher.Core/MemoryPatcher.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -35,7 +35,7 @@ private static List GetProcessesByName(params string[] names) { try { - if (names.Contains(p.ProcessName, StringComparer.OrdinalIgnoreCase)) + if (names.Contains(Path.GetFileNameWithoutExtension(p.ProcessName), StringComparer.OrdinalIgnoreCase)) { result.Add(p); } @@ -52,8 +52,10 @@ private static List GetProcessesByName(params string[] names) return result; } - public static void PatchAllProcesses(ILoggingProvider logger, Options patchOptions) - { + public static bool PatchAllProcesses(ILoggingProvider logger, Options patchOptions) + { + bool patched = false; + IEnumerable hitmans = GetProcessesByName("HITMAN", "HITMAN2", "HITMAN3"); foreach (Process process in hitmans) { @@ -69,7 +71,7 @@ public static void PatchAllProcesses(ILoggingProvider logger, Options patchOptio } catch (Win32Exception ex) { - if (ex.NativeErrorCode == 5 && !Program.HasAdmin) + if (ex.NativeErrorCode == 5 && !Compositions.HasAdmin) { logger.log(String.Format("Access denied, try running the patcher as admin.")); process.Dispose(); @@ -95,7 +97,9 @@ public static void PatchAllProcesses(ILoggingProvider logger, Options patchOptio { logger.log(String.Format("Injected server: {0}", patchOptions.CustomConfigDomain)); } - } + + patched = true; + } else { // else: process not yet ready for patching, try again next timer tick @@ -114,39 +118,37 @@ public static void PatchAllProcesses(ILoggingProvider logger, Options patchOptio } process.Dispose(); } - } + + return patched; + } public static bool Patch(Process process, Options patchOptions) { - IntPtr hProcess = Pinvoke.OpenProcess( + var processMetadata = Pinvoke.GetProcessMetadata(process); + + if (processMetadata.BaseAddress == IntPtr.Zero || processMetadata.ModuleMemorySize == 0) + { + return false; // process has no main module (not initialized yet?), try again next timer tick. + } + + IntPtr hProcess = Pinvoke.OpenProcess( ProcessAccess.PROCESS_VM_READ | ProcessAccess.PROCESS_VM_WRITE | ProcessAccess.PROCESS_VM_OPERATION, - false, process.Id); + false, processMetadata.PID); if (hProcess == IntPtr.Zero) { - throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to get a process handle."); + throw new Win32Exception(Pinvoke.LastError, "Failed to get a process handle."); } try { - IntPtr b = IntPtr.Zero; - try - { - ProcessModule mainModule = process.MainModule; - b = mainModule.BaseAddress; - } - catch (NullReferenceException) - { - return false; // process has no main module (not initialized yet?), try again next timer tick. - } - - uint timestamp = getTimestamp(hProcess, b); + uint timestamp = getTimestamp(hProcess, processMetadata.BaseAddress); HitmanVersion v = HitmanVersion.GetVersion(timestamp); if (v == HitmanVersion.NotFound) { - if (AOBScanner.TryGetHitmanVersionByScanning(process, hProcess, out v)) + if (AOBScanner.TryGetHitmanVersionByScanning(processMetadata, hProcess, out v)) { // add it to the db so subsequent patches don't need searching again HitmanVersion.AddVersion(timestamp.ToString("X8"), timestamp, v); @@ -161,7 +163,7 @@ public static bool Patch(Process process, Options patchOptions) byte[] newurl = Encoding.ASCII.GetBytes(patchOptions.CustomConfigDomain).Concat(new byte[] { 0x00 }).ToArray(); List patches = new List(); - if (!IsReadyForPatching(hProcess, b, v)) + if (!IsReadyForPatching(hProcess, processMetadata.BaseAddress, v)) { // Online_ConfigDomain variable is not initialized yet, try again in 1 second. Pinvoke.CloseHandle(hProcess); @@ -212,24 +214,24 @@ public static bool Patch(Process process, Options patchOptions) throw new Exception("This shouldn't be able to happen."); } - if (!Pinvoke.VirtualProtectEx(hProcess, b + patch.offset, (UIntPtr)dataToWrite.Length, + if (!Pinvoke.VirtualProtectEx(hProcess, processMetadata.BaseAddress + patch.offset, (UIntPtr)dataToWrite.Length, newmemprotection, out oldprotectflags)) { - throw new Win32Exception(Marshal.GetLastWin32Error(), string.Format("error at {0} for offset {1:X}", "vpe1", patch.offset)); + throw new Win32Exception(Pinvoke.LastError, string.Format("error at {0} for offset {1:X}", "vpe1", patch.offset)); } - if (!Pinvoke.WriteProcessMemory(hProcess, b + patch.offset, dataToWrite, (UIntPtr)dataToWrite.Length, out byteswritten)) + if (!Pinvoke.WriteProcessMemory(hProcess, processMetadata.BaseAddress + patch.offset, dataToWrite, (UIntPtr)dataToWrite.Length, out byteswritten)) { - throw new Win32Exception(Marshal.GetLastWin32Error(), string.Format("error at {0} for offset {1:X}" + throw new Win32Exception(Pinvoke.LastError, string.Format("error at {0} for offset {1:X}" + "\nBytes written: {2}", "wpm", patch.offset, byteswritten)); } MemProtection protectionToRestore = oldprotectflags; - if (!Pinvoke.VirtualProtectEx(hProcess, b + patch.offset, (UIntPtr)dataToWrite.Length, + if (!Pinvoke.VirtualProtectEx(hProcess, processMetadata.BaseAddress + patch.offset, (UIntPtr)dataToWrite.Length, protectionToRestore, out oldprotectflags)) { - throw new Win32Exception(Marshal.GetLastWin32Error(), string.Format("error at {0} for offset {1:X}", "vpe2", patch.offset)); + throw new Win32Exception(Pinvoke.LastError, string.Format("error at {0} for offset {1:X}", "vpe2", patch.offset)); } } } @@ -253,15 +255,15 @@ private static bool IsReadyForPatching(IntPtr hProcess, IntPtr baseAddress, Hitm MemProtection oldprotectflags; if (!Pinvoke.VirtualProtectEx(hProcess, baseAddress + p.offset, (UIntPtr)1, newmemprotection, out oldprotectflags)) { - throw new Win32Exception(Marshal.GetLastWin32Error(), string.Format("error at vpe1Check for offset {0:X}", p.offset)); + throw new Win32Exception(Pinvoke.LastError, string.Format("error at vpe1Check for offset {0:X}", p.offset)); } if (!Pinvoke.ReadProcessMemory(hProcess, baseAddress + p.offset, buffer, (UIntPtr)1, out bytesread)) { - throw new Win32Exception(Marshal.GetLastWin32Error(), string.Format("error at rpmCheck for offset {0:X}", p.offset)); + throw new Win32Exception(Pinvoke.LastError, string.Format("error at rpmCheck for offset {0:X}", p.offset)); } if (!Pinvoke.VirtualProtectEx(hProcess, baseAddress + p.offset, (UIntPtr)1, oldprotectflags, out oldprotectflags)) { - throw new Win32Exception(Marshal.GetLastWin32Error(), string.Format("error at vpe2Check for offset {0:X}", p.offset)); + throw new Win32Exception(Pinvoke.LastError, string.Format("error at vpe2Check for offset {0:X}", p.offset)); } ready &= buffer[0] != 0; } diff --git a/patcher/PatchDefinitions/v1_12.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v1_12.cs similarity index 100% rename from patcher/PatchDefinitions/v1_12.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v1_12.cs diff --git a/patcher/PatchDefinitions/v1_15.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v1_15.cs similarity index 100% rename from patcher/PatchDefinitions/v1_15.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v1_15.cs diff --git a/patcher/PatchDefinitions/v1_16.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v1_16.cs similarity index 100% rename from patcher/PatchDefinitions/v1_16.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v1_16.cs diff --git a/patcher/PatchDefinitions/v2_13.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v2_13.cs similarity index 100% rename from patcher/PatchDefinitions/v2_13.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v2_13.cs diff --git a/patcher/PatchDefinitions/v2_71.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v2_71.cs similarity index 100% rename from patcher/PatchDefinitions/v2_71.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v2_71.cs diff --git a/patcher/PatchDefinitions/v2_72.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v2_72.cs similarity index 100% rename from patcher/PatchDefinitions/v2_72.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v2_72.cs diff --git a/patcher/PatchDefinitions/v3_10.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_10.cs similarity index 100% rename from patcher/PatchDefinitions/v3_10.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_10.cs diff --git a/patcher/PatchDefinitions/v3_100.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_100.cs similarity index 100% rename from patcher/PatchDefinitions/v3_100.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_100.cs diff --git a/patcher/PatchDefinitions/v3_11.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_11.cs similarity index 100% rename from patcher/PatchDefinitions/v3_11.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_11.cs diff --git a/patcher/PatchDefinitions/v3_110.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_110.cs similarity index 100% rename from patcher/PatchDefinitions/v3_110.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_110.cs diff --git a/patcher/PatchDefinitions/v3_120.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_120.cs similarity index 100% rename from patcher/PatchDefinitions/v3_120.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_120.cs diff --git a/patcher/PatchDefinitions/v3_20.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_20.cs similarity index 100% rename from patcher/PatchDefinitions/v3_20.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_20.cs diff --git a/patcher/PatchDefinitions/v3_30.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_30.cs similarity index 100% rename from patcher/PatchDefinitions/v3_30.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_30.cs diff --git a/patcher/PatchDefinitions/v3_40.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_40.cs similarity index 100% rename from patcher/PatchDefinitions/v3_40.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_40.cs diff --git a/patcher/PatchDefinitions/v3_50.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_50.cs similarity index 100% rename from patcher/PatchDefinitions/v3_50.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_50.cs diff --git a/patcher/PatchDefinitions/v3_70.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/v3_70.cs similarity index 100% rename from patcher/PatchDefinitions/v3_70.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/v3_70.cs diff --git a/patcher/PatchDefinitions/vScpc.cs b/patcher/HitmanPatcher.Core/PatchDefinitions/vScpc.cs similarity index 100% rename from patcher/PatchDefinitions/vScpc.cs rename to patcher/HitmanPatcher.Core/PatchDefinitions/vScpc.cs diff --git a/patcher/HitmanPatcher.Core/Pinvoke.Linux.cs b/patcher/HitmanPatcher.Core/Pinvoke.Linux.cs new file mode 100644 index 000000000..09b769187 --- /dev/null +++ b/patcher/HitmanPatcher.Core/Pinvoke.Linux.cs @@ -0,0 +1,286 @@ +#if LINUX +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace HitmanPatcher; + +public static partial class Pinvoke +{ + [StructLayout(LayoutKind.Sequential)] +#pragma warning disable CS8981 + private unsafe struct iovec +#pragma warning restore CS8981 + { + public void* iov_base; + public int iov_len; + } + + private enum PtraceRequest + { + //PTRACE_PEEKDATA = 2, + PTRACE_POKEDATA = 5, + PTRACE_ATTACH = 16, + PTRACE_DETACH = 17 + } + + [DllImport("libc", SetLastError = true)] + private static extern unsafe long ptrace(PtraceRequest request, int pid, void* address, void* data); + + [DllImport("libc", SetLastError = true)] + private static extern unsafe int process_vm_readv(int pid, iovec* local_iov, ulong liovcnt, iovec* remote_iov, ulong riovcnt, ulong flags); + + private static readonly Regex _modulePathRegex = new Regex("\\/.*?$", RegexOptions.Compiled); + + public static ProcessMetadata GetProcessMetadata(Process process) + { + var lines = File.ReadAllLines($"/proc/{process.Id}/maps"); + + var findProcess = process.ProcessName.ToLower(); + var foundModule = lines.First(x => x.Contains(findProcess, StringComparison.CurrentCultureIgnoreCase)); + var foundModulePath = _modulePathRegex.Match(foundModule).Value; + var (baseAddress, imageSize) = GetImageSizeFromPE(foundModulePath); + + return new ProcessMetadata + { + PID = process.Id, + BaseAddress = new IntPtr((long) baseAddress), + ModuleMemorySize = imageSize + }; + } + + /** + * Attach to the given process using ptrace, return the PID as the handle. + */ + public static unsafe IntPtr OpenProcess(ProcessAccess dwDesiredAccess, bool bInheritHandle, int dwProcessId) + { + Compositions.Logger.log("Open: " + dwProcessId); + + var result = ptrace(PtraceRequest.PTRACE_ATTACH, dwProcessId, null, null); + + if (result == -1) + { + Compositions.Logger.log($"- Attach error: {LastError}"); + + return IntPtr.Zero; + } + + Compositions.Logger.log("- Attach: " + result); + + return new IntPtr(dwProcessId); + } + + /** + * Retrieve the PID from the handle, and detach from the process using ptrace. + */ + public static unsafe void CloseHandle(IntPtr hProcess) + { + var pid = hProcess.ToInt32(); + + Compositions.Logger.log($"Close: {pid}"); + + var result = ptrace(PtraceRequest.PTRACE_DETACH, pid, null, null); + + if (result == -1) + { + Compositions.Logger.log($"- Detach error: {LastError}"); + } + else + { + Compositions.Logger.log($"- Detach: {result}"); + } + } + + /** + * Since process_vm_writev is too respectful of permissions, we have to use ptrace to write to memory. + * + * ptrace writes data in blocks of 8 bytes (long), so if a given buffer is non-divisible by 8, we have to pad it with data read from memory to prevent corruption. + */ + public static unsafe bool WriteProcessMemory(IntPtr hProcess, IntPtr address, byte[] buffer, UIntPtr size, out UIntPtr numberOfBytesWritten) + { + var pid = hProcess.ToInt32(); + var sizeAsUInt = (int)size.ToUInt32(); + + Compositions.Logger.log($"Write: {pid} {sizeAsUInt}"); + + var desiredBufferSize = (int)Math.Ceiling(sizeAsUInt / 8.0) * 8; + + byte[] paddedBuffer; + + if (sizeAsUInt < desiredBufferSize) + { + Compositions.Logger.log($"- Padding buffer to {desiredBufferSize}"); + + paddedBuffer = new byte[desiredBufferSize]; + ReadProcessMemory(hProcess, address, paddedBuffer, new UIntPtr((uint)desiredBufferSize), out _); + + for (var i = 0; i < sizeAsUInt; i++) + { + paddedBuffer[i] = buffer[i]; + } + } + else + { + paddedBuffer = buffer; + } + + Compositions.Logger.log($"- Padded: {BitConverter.ToString(paddedBuffer)}"); + + var debugBuffer = new byte[paddedBuffer.Length]; + ReadProcessMemory(hProcess, address, debugBuffer, new UIntPtr((uint)debugBuffer.Length), out _); + Compositions.Logger.log($"- Debug before: {BitConverter.ToString(debugBuffer)}"); + + for (int i = 0; i < desiredBufferSize; i += 8) + { + var value = BitConverter.ToInt64(paddedBuffer, i); + + var result = ptrace(PtraceRequest.PTRACE_POKEDATA, pid, (address + i).ToPointer(), (void*)value); + + if (result == -1) + { + Compositions.Logger.log($"- Poke error: {LastError}"); + + numberOfBytesWritten = UIntPtr.Zero; + + return false; + } + + Compositions.Logger.log($"- Poke: {result}"); + } + + debugBuffer = new byte[paddedBuffer.Length]; + ReadProcessMemory(hProcess, address, debugBuffer, new UIntPtr((uint)debugBuffer.Length), out _); + Compositions.Logger.log("- Debug after: " + BitConverter.ToString(debugBuffer)); + + numberOfBytesWritten = size; + + return true; + } + + /** + * Memory can always be read regardless of permissions, so we can use process_vm_readv. + * + * To prevent stack overflows, memory is read in chunks of 1MB. + */ + public static unsafe bool ReadProcessMemory(IntPtr hProcess, IntPtr address, byte[] buffer, UIntPtr size, out UIntPtr numberOfBytesRead) + { + var pid = hProcess.ToInt32(); + var sizeAsUInt = (int)size.ToUInt32(); + + Compositions.Logger.log($"Read: {pid} {sizeAsUInt}"); + + var chunkSize = Math.Min(sizeAsUInt, 1_000_000); + + var ptr = stackalloc byte[chunkSize]; + + for (var i = 0; i < sizeAsUInt; i += chunkSize) + { + if (sizeAsUInt - i < chunkSize) + { + chunkSize = sizeAsUInt - i; + } + + Compositions.Logger.log($"- Chunk: {i}/{sizeAsUInt} => {chunkSize}"); + + var localIo = new iovec + { + iov_base = ptr, + iov_len = chunkSize + }; + + var remoteIo = new iovec + { + iov_base = (address + i).ToPointer(), + iov_len = chunkSize + }; + + var res = process_vm_readv(pid, &localIo, 1, &remoteIo, 1, 0); + + if (res == -1) + { + numberOfBytesRead = UIntPtr.Zero; + + return false; + } + + Marshal.Copy((IntPtr)ptr, buffer, i, chunkSize); + } + + numberOfBytesRead = new UIntPtr((uint)sizeAsUInt); + + return true; + } + + /** + * Technically ptrace attach/detach would be the closest equivalent, but from a performance perspective, we only do that once. + */ + public static bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, MemProtection flNewProtect, out MemProtection lpflOldProtect) + { + lpflOldProtect = 0; + + return true; + } + + public static void AttachConsole(int attachParentProcess) + { + //Do nothing + } + + public static bool CheckForAdmin() + { + //TODO: Check if SUDO_USER is set + + return false; + } + + public static int GetProcessParentPid(Process process) + { + //TODO: Retrieve "PPid" from "/proc/pid/status" + + return -1; + } + + private static (ulong, uint) GetImageSizeFromPE(string modulePath) + { + using var module = File.OpenRead(modulePath); + using var reader = new BinaryReader(module); + + //Validate DOS Header (MZ) + var dosHeader = reader.ReadUInt16(); + + if(dosHeader != 0x5A4D) { + return (0, 0); + } + + //Skip to PE offset + reader.BaseStream.Seek(0x3A, SeekOrigin.Current); + var peOffset = reader.ReadInt32(); + + //Validate PE Header (PE\0\0) + reader.BaseStream.Seek(peOffset, SeekOrigin.Begin); + var peHeader = reader.ReadUInt32(); + + if(peHeader != 0x00004550) { + return (0, 0); + } + + //Validate PE Type (PE32+) + reader.BaseStream.Seek(20, SeekOrigin.Current); + var peType = reader.ReadUInt16(); + + if(peType != 0x020B) { + return (0, 0); + } + + //Skip to base address + reader.BaseStream.Seek(22, SeekOrigin.Current); + var baseAddress = reader.ReadUInt64(); + + //Skip to image size + reader.BaseStream.Seek(24, SeekOrigin.Current); + var imageSize = reader.ReadUInt32(); + + return (baseAddress, imageSize); + } +} +#endif \ No newline at end of file diff --git a/patcher/HitmanPatcher.Core/Pinvoke.Windows.cs b/patcher/HitmanPatcher.Core/Pinvoke.Windows.cs new file mode 100644 index 000000000..497668349 --- /dev/null +++ b/patcher/HitmanPatcher.Core/Pinvoke.Windows.cs @@ -0,0 +1,103 @@ +#if !LINUX +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Principal; + +namespace HitmanPatcher +{ + public static partial class Pinvoke + { + //Source: https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess + private enum PROCESSINFOCLASS + { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29, + ProcessSubsystemInformation = 75 + } + + //Source: https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_BASIC_INFORMATION + { + public IntPtr Reserved1; + public IntPtr PebBaseAddress; + public IntPtr Reserved2_0; + public IntPtr Reserved2_1; + public IntPtr UniqueProcessId; + public IntPtr Reserved3; + } + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool AttachConsole(int dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenProcess(ProcessAccess dwDesiredAccess, bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool WriteProcessMemory([In] IntPtr hProcess, [In] IntPtr address, [In, MarshalAs(UnmanagedType.LPArray)] byte[] buffer, [In] UIntPtr size, [Out] out UIntPtr byteswritten); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory([In] IntPtr hProcess, [In] IntPtr address, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] byte[] buffer, [In] UIntPtr size, [Out] out UIntPtr numberOfBytesRead); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool VirtualProtectEx([In] IntPtr hProcess, [In] IntPtr lpAddress, [In] UIntPtr dwSize, [In] MemProtection flNewProtect, [Out] out MemProtection lpflOldProtect); + + [DllImport("ntdll.dll")] + private static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS processInformationClass, out PROCESS_BASIC_INFORMATION processInformation, uint processInformationLength, out uint returnLength); + + public static bool CheckForAdmin() + { + using var identity = WindowsIdentity.GetCurrent(); + + var principal = new WindowsPrincipal(identity); + + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + public static int GetProcessParentPid(Process process) + { + IntPtr hProcess = OpenProcess( + ProcessAccess.PROCESS_VM_READ + | ProcessAccess.PROCESS_QUERY_INFORMATION, + false, process.Id); + + if (hProcess == IntPtr.Zero) + { + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to get a process handle."); + } + + PROCESS_BASIC_INFORMATION PEB = new PROCESS_BASIC_INFORMATION(); + + int result = NtQueryInformationProcess(hProcess, + PROCESSINFOCLASS.ProcessBasicInformation, out PEB, + (uint)Marshal.SizeOf(PEB), out _); + + CloseHandle(hProcess); + if (result != 0) + { + throw new Win32Exception(result, "(NTSTATUS)"); // not a w32 status code, but an NTSTATUS + } + + return PEB.Reserved3.ToInt32(); // undocumented, but should hold the parent PID + } + + public static ProcessMetadata GetProcessMetadata(Process process) + { + return new ProcessMetadata + { + PID = process.Id, + BaseAddress = process.MainModule?.BaseAddress ?? IntPtr.Zero, + ModuleMemorySize = process.MainModule?.ModuleMemorySize ?? 0 + }; + } + } +} +#endif \ No newline at end of file diff --git a/patcher/HitmanPatcher.Core/Pinvoke.cs b/patcher/HitmanPatcher.Core/Pinvoke.cs new file mode 100644 index 000000000..7001b2051 --- /dev/null +++ b/patcher/HitmanPatcher.Core/Pinvoke.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; + +namespace HitmanPatcher +{ + public class ProcessMetadata + { + public int PID { get; set; } + public IntPtr BaseAddress { get; set; } + public long ModuleMemorySize { get; set; } + } + + //Source: https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants + [Flags] + public enum MemProtection : uint + { + PAGE_EXECUTE = 0x00000010, + PAGE_EXECUTE_READ = 0x00000020, + PAGE_EXECUTE_READWRITE = 0x00000040, + PAGE_EXECUTE_WRITECOPY = 0x00000080, + PAGE_NOACCESS = 0x00000001, + PAGE_READONLY = 0x00000002, + PAGE_READWRITE = 0x00000004, + PAGE_WRITECOPY = 0x00000008, + PAGE_GUARD = 0x00000100, + PAGE_NOCACHE = 0x00000200, + PAGE_WRITECOMBINE = 0x00000400 + } + + //Source: https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights + [Flags] + public enum ProcessAccess : uint + { + PROCESS_QUERY_INFORMATION = 0x0400, // Required to retrieve certain information about a process. + PROCESS_VM_READ = 0x0010, // Required to read memory in a process using ReadProcessMemory. + PROCESS_VM_WRITE = 0x0020, // Required to write to memory in a process using WriteProcessMemory. + PROCESS_VM_OPERATION = 0x0008 // Required to perform an operation on the address space of a process using VirtualProtectEx + } + + public static partial class Pinvoke + { + public static int LastError => Marshal.GetLastWin32Error(); + } +} diff --git a/patcher/Settings.cs b/patcher/HitmanPatcher.Core/Settings.cs similarity index 82% rename from patcher/Settings.cs rename to patcher/HitmanPatcher.Core/Settings.cs index eb7cabece..1153029bd 100644 --- a/patcher/Settings.cs +++ b/patcher/HitmanPatcher.Core/Settings.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; - namespace HitmanPatcher { public class Settings @@ -30,20 +26,21 @@ public Settings() trayDomains = new List(); } - private static string GetSavePath() + public static string GetSavePath() { - if (!Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\PeacockProject")) +#if LINUX + return "peacock_patcher.conf"; +#else + string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + string folder = Path.Combine(appData, "PeacockProject"); + + if (!Directory.Exists(folder)) { - Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\PeacockProject"); + Directory.CreateDirectory(folder); } - string appData = Environment.GetFolderPath(Environment - .SpecialFolder - .ApplicationData); - - string folder = $@"{appData}\PeacockProject\"; - string config1 = folder + "peacock_patcher.conf"; - string config2 = folder + "peacock_patcher2.conf"; + string config1 = Path.Combine(folder, "peacock_patcher.conf"); + string config2 = Path.Combine(folder, "peacock_patcher2.conf"); if (File.Exists(config1)) { @@ -51,6 +48,7 @@ private static string GetSavePath() } return config2; +#endif } public void SaveToFile() @@ -59,7 +57,7 @@ public void SaveToFile() lines.Add(string.Format("CustomConfigDomain={0}", patchOptions.CustomConfigDomain)); lines.Add(string.Format("UseHttp={0}", patchOptions.UseHttp)); lines.Add(string.Format("DisableForceDynamicResources={0}", patchOptions.DisableForceOfflineOnFailedDynamicResources)); - lines.Add(string.Format("DarkModeEnabled={0}", darkModeEnabled)); + lines.Add(string.Format("DarkModeEnabled={0}", darkModeEnabled)); lines.Add(string.Format("startInTray={0}", startInTray)); lines.Add(string.Format("minToTray={0}", minimizeToTray)); @@ -100,9 +98,9 @@ public static Settings GetFromFile() case "DisableForceDynamicResources": result.patchOptions.DisableForceOfflineOnFailedDynamicResources = bool.Parse(linecontents[1]); break; - case "DarkModeEnabled": - result.darkModeEnabled = bool.Parse(linecontents[1]); - break; + case "DarkModeEnabled": + result.darkModeEnabled = bool.Parse(linecontents[1]); + break; case "startInTray": result.startInTray = bool.Parse(linecontents[1]); break; diff --git a/patcher/HitmanPatcher.UI/App.config b/patcher/HitmanPatcher.UI/App.config new file mode 100644 index 000000000..6abd8f17b --- /dev/null +++ b/patcher/HitmanPatcher.UI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/patcher/HitmanPatcher.UI/HitmanPatcher.UI.csproj b/patcher/HitmanPatcher.UI/HitmanPatcher.UI.csproj new file mode 100644 index 000000000..627e872f4 --- /dev/null +++ b/patcher/HitmanPatcher.UI/HitmanPatcher.UI.csproj @@ -0,0 +1,65 @@ + + + WinExe + net8.0-windows + net46 + enable + disable + + HitmanPatcher + PeacockPatcher.UI + + Debug;Release + + Peacock Patcher + Peacock's HITMAN™ World of Assassination trilogy game patcher. + The Peacock Project + Peacock Patcher + Copyright © 2020-2024 grappigegovert & The Peacock Project + true + true + DpiUnawareGdiScaled + patcher.ico + 8.0.0.0 + 8.0.0.0 + + true + $(DefineConstants);LEGACY + latest + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + Form + + + Form + + + Form + + + diff --git a/patcher/HitmanPatcher.UI/ILRepack.targets b/patcher/HitmanPatcher.UI/ILRepack.targets new file mode 100644 index 000000000..48ca8dd77 --- /dev/null +++ b/patcher/HitmanPatcher.UI/ILRepack.targets @@ -0,0 +1,16 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/patcher/MainForm.Designer.cs b/patcher/HitmanPatcher.UI/MainForm.Designer.cs similarity index 97% rename from patcher/MainForm.Designer.cs rename to patcher/HitmanPatcher.UI/MainForm.Designer.cs index 5778da907..026a930e4 100644 --- a/patcher/MainForm.Designer.cs +++ b/patcher/HitmanPatcher.UI/MainForm.Designer.cs @@ -1,4 +1,4 @@ -namespace HitmanPatcher +namespace HitmanPatcher { partial class MainForm { @@ -209,8 +209,8 @@ private void InitializeComponent() this.novikovPictureBox.AccessibleDescription = "Icon showing the patcher status."; this.novikovPictureBox.AccessibleName = "Status icon"; this.novikovPictureBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.novikovPictureBox.Image = global::HitmanPatcher.Properties.Resources.Novikov_standard; - this.novikovPictureBox.InitialImage = ((System.Drawing.Image)(resources.GetObject("novikovPictureBox.InitialImage"))); + this.novikovPictureBox.Image = Resources.Novikov_standard; + this.novikovPictureBox.InitialImage = Resources.Novikov_standard; this.novikovPictureBox.Location = new System.Drawing.Point(313, 44); this.novikovPictureBox.Margin = new System.Windows.Forms.Padding(4); this.novikovPictureBox.Name = "novikovPictureBox"; @@ -222,7 +222,7 @@ private void InitializeComponent() // trayIcon // this.trayIcon.ContextMenuStrip = this.trayMenu; - this.trayIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("trayIcon.Icon"))); + this.trayIcon.Icon = Resources.Icon; this.trayIcon.Text = "Peacock Patcher"; this.trayIcon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.menuItemOpen_Click); // @@ -284,7 +284,7 @@ private void InitializeComponent() this.Controls.Add(this.logLabel); this.Controls.Add(this.logListView); this.Controls.Add(this.serverAddressLabel); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Icon = Resources.Icon; this.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.MaximizeBox = false; this.MinimumSize = new System.Drawing.Size(423, 238); diff --git a/patcher/MainForm.cs b/patcher/HitmanPatcher.UI/MainForm.cs similarity index 97% rename from patcher/MainForm.cs rename to patcher/HitmanPatcher.UI/MainForm.cs index 5a30dfe0e..a16b8549e 100644 --- a/patcher/MainForm.cs +++ b/patcher/HitmanPatcher.UI/MainForm.cs @@ -6,6 +6,7 @@ using System.Drawing; using System.IO; using System.Text; +using Timer = System.Windows.Forms.Timer; namespace HitmanPatcher { @@ -206,15 +207,15 @@ private void SetStatusImage(int status) { if (status == 0) { - novikovPictureBox.Image = Properties.Resources.Novikov_standard; + novikovPictureBox.Image = Resources.Novikov_standard; } else if (status > 0) { - novikovPictureBox.Image = Properties.Resources.Novikov_success; + novikovPictureBox.Image = Resources.Novikov_success; } else { - novikovPictureBox.Image = Properties.Resources.Novikov_error; + novikovPictureBox.Image = Resources.Novikov_error; } } diff --git a/patcher/MainForm.resx b/patcher/HitmanPatcher.UI/MainForm.resx similarity index 100% rename from patcher/MainForm.resx rename to patcher/HitmanPatcher.UI/MainForm.resx diff --git a/patcher/Novikov_error.png b/patcher/HitmanPatcher.UI/Novikov_error.png similarity index 100% rename from patcher/Novikov_error.png rename to patcher/HitmanPatcher.UI/Novikov_error.png diff --git a/patcher/Novikov_standard.png b/patcher/HitmanPatcher.UI/Novikov_standard.png similarity index 100% rename from patcher/Novikov_standard.png rename to patcher/HitmanPatcher.UI/Novikov_standard.png diff --git a/patcher/Novikov_success.png b/patcher/HitmanPatcher.UI/Novikov_success.png similarity index 100% rename from patcher/Novikov_success.png rename to patcher/HitmanPatcher.UI/Novikov_success.png diff --git a/patcher/OptionsForm.Designer.cs b/patcher/HitmanPatcher.UI/OptionsForm.Designer.cs similarity index 100% rename from patcher/OptionsForm.Designer.cs rename to patcher/HitmanPatcher.UI/OptionsForm.Designer.cs diff --git a/patcher/OptionsForm.cs b/patcher/HitmanPatcher.UI/OptionsForm.cs similarity index 100% rename from patcher/OptionsForm.cs rename to patcher/HitmanPatcher.UI/OptionsForm.cs diff --git a/patcher/OptionsForm.resx b/patcher/HitmanPatcher.UI/OptionsForm.resx similarity index 100% rename from patcher/OptionsForm.resx rename to patcher/HitmanPatcher.UI/OptionsForm.resx diff --git a/patcher/HitmanPatcher.UI/Program.cs b/patcher/HitmanPatcher.UI/Program.cs new file mode 100644 index 000000000..98b04f32f --- /dev/null +++ b/patcher/HitmanPatcher.UI/Program.cs @@ -0,0 +1,25 @@ +namespace HitmanPatcher +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main(string[] args) + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + +#if !LEGACY + Application.SetHighDpiMode(HighDpiMode.SystemAware); +#endif + + var mainForm = MainForm.GetInstance(); + + Compositions.Logger = mainForm; + + Application.Run(mainForm); + } + } +} diff --git a/patcher/HitmanPatcher.UI/Resources.cs b/patcher/HitmanPatcher.UI/Resources.cs new file mode 100644 index 000000000..4d7bb9f53 --- /dev/null +++ b/patcher/HitmanPatcher.UI/Resources.cs @@ -0,0 +1,31 @@ +using System.Reflection; + +namespace HitmanPatcher +{ + public static class Resources + { + public static Icon Icon { get; } + public static Bitmap Novikov_standard { get; } + public static Bitmap Novikov_success { get; } + public static Bitmap Novikov_error { get; } + + static Resources() + { + Icon = new Icon(ReadResource("patcher.ico")); + Novikov_standard = new Bitmap(ReadResource("Novikov_standard.png")); + Novikov_success = new Bitmap(ReadResource("Novikov_success.png")); + Novikov_error = new Bitmap(ReadResource("Novikov_error.png")); + } + + private static Stream ReadResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + + string resourcePath = assembly + .GetManifestResourceNames() + .Single(str => str.EndsWith(name)); + + return assembly.GetManifestResourceStream(resourcePath); + } + } +} diff --git a/patcher/TrayOptionsForm.Designer.cs b/patcher/HitmanPatcher.UI/TrayOptionsForm.Designer.cs similarity index 100% rename from patcher/TrayOptionsForm.Designer.cs rename to patcher/HitmanPatcher.UI/TrayOptionsForm.Designer.cs diff --git a/patcher/TrayOptionsForm.cs b/patcher/HitmanPatcher.UI/TrayOptionsForm.cs similarity index 100% rename from patcher/TrayOptionsForm.cs rename to patcher/HitmanPatcher.UI/TrayOptionsForm.cs diff --git a/patcher/TrayOptionsForm.resx b/patcher/HitmanPatcher.UI/TrayOptionsForm.resx similarity index 100% rename from patcher/TrayOptionsForm.resx rename to patcher/HitmanPatcher.UI/TrayOptionsForm.resx diff --git a/patcher/HitmanPatcher.UI/patcher.ico b/patcher/HitmanPatcher.UI/patcher.ico new file mode 100644 index 000000000..82463fcea Binary files /dev/null and b/patcher/HitmanPatcher.UI/patcher.ico differ diff --git a/patcher/HitmanPatcher.csproj b/patcher/HitmanPatcher.csproj deleted file mode 100644 index c2914cd5e..000000000 --- a/patcher/HitmanPatcher.csproj +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - Debug - x64 - {0487D44B-B3E1-4A6E-91E0-47016C391A20} - WinExe - Properties - HitmanPatcher - PeacockPatcher - v4.6 - 512 - - True - False - False - False - False - None.None.None.Increment - None.None.None.Increment - 8 - - - - true - bin\x64\Debug\ - DEBUG;TRACE;PLATFORM_STEAM;PLATFORM_EPIC;PLATFORM_GOG;PLATFORM_SCARLETT - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - - bin\x64\Release\ - TRACE;PLATFORM_STEAM;PLATFORM_EPIC - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - - patcher.ico - - - - - - - - - - - - - - - - - - - True - True - CliLocale.resx - - - Form - - - MainForm.cs - - - - - Form - - - OptionsForm.cs - - - - - - - - - - - - - - - - - - - - - - Form - - - TrayOptionsForm.cs - - - ResXFileCodeGenerator - CliLocale.Designer.cs - - - MainForm.cs - Designer - - - OptionsForm.cs - Designer - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - TrayOptionsForm.Designer.cs - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - - - - - - diff --git a/patcher/HitmanPatcher.sln b/patcher/HitmanPatcher.sln index 1d06a1846..08c4dbd59 100644 --- a/patcher/HitmanPatcher.sln +++ b/patcher/HitmanPatcher.sln @@ -1,22 +1,49 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.40629.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34316.72 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HitmanPatcher", "HitmanPatcher.csproj", "{0487D44B-B3E1-4A6E-91E0-47016C391A20}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HitmanPatcher.Core", "HitmanPatcher.Core\HitmanPatcher.Core.csproj", "{70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HitmanPatcher.CLI", "HitmanPatcher.CLI\HitmanPatcher.CLI.csproj", "{799814F1-919B-4BCA-9348-4F7F5F429CB9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HitmanPatcher.UI", "HitmanPatcher.UI\HitmanPatcher.UI.csproj", "{50B900C8-B244-4AAA-9130-5C0A6093B22D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 + Debug - Linux|Any CPU = Debug - Linux|Any CPU + Debug|Any CPU = Debug|Any CPU + Release - Linux|Any CPU = Release - Linux|Any CPU + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0487D44B-B3E1-4A6E-91E0-47016C391A20}.Debug|x64.ActiveCfg = Debug|x64 - {0487D44B-B3E1-4A6E-91E0-47016C391A20}.Debug|x64.Build.0 = Debug|x64 - {0487D44B-B3E1-4A6E-91E0-47016C391A20}.Release|x64.ActiveCfg = Release|x64 - {0487D44B-B3E1-4A6E-91E0-47016C391A20}.Release|x64.Build.0 = Release|x64 + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Debug - Linux|Any CPU.ActiveCfg = Debug - Linux|Any CPU + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Debug - Linux|Any CPU.Build.0 = Debug - Linux|Any CPU + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Release - Linux|Any CPU.ActiveCfg = Release - Linux|Any CPU + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Release - Linux|Any CPU.Build.0 = Release - Linux|Any CPU + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70EA1386-6A9D-47C2-ADE8-2F32CDD9E946}.Release|Any CPU.Build.0 = Release|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Debug - Linux|Any CPU.ActiveCfg = Debug - Linux|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Debug - Linux|Any CPU.Build.0 = Debug - Linux|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Release - Linux|Any CPU.ActiveCfg = Release - Linux|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Release - Linux|Any CPU.Build.0 = Release - Linux|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {799814F1-919B-4BCA-9348-4F7F5F429CB9}.Release|Any CPU.Build.0 = Release|Any CPU + {50B900C8-B244-4AAA-9130-5C0A6093B22D}.Debug - Linux|Any CPU.ActiveCfg = Debug|Any CPU + {50B900C8-B244-4AAA-9130-5C0A6093B22D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50B900C8-B244-4AAA-9130-5C0A6093B22D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50B900C8-B244-4AAA-9130-5C0A6093B22D}.Release - Linux|Any CPU.ActiveCfg = Release|Any CPU + {50B900C8-B244-4AAA-9130-5C0A6093B22D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50B900C8-B244-4AAA-9130-5C0A6093B22D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E12BBAA6-1725-4F91-A0E6-824E4AC9328E} + EndGlobalSection EndGlobal diff --git a/patcher/HitmanPatcher.sln.DotSettings b/patcher/HitmanPatcher.sln.DotSettings index 80734dfb0..0d108e53e 100644 --- a/patcher/HitmanPatcher.sln.DotSettings +++ b/patcher/HitmanPatcher.sln.DotSettings @@ -9,6 +9,9 @@ DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW + PE True True - True \ No newline at end of file + True + True + True \ No newline at end of file diff --git a/patcher/Pinvoke.cs b/patcher/Pinvoke.cs deleted file mode 100644 index 1daecabbc..000000000 --- a/patcher/Pinvoke.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace HitmanPatcher -{ - // from https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants - [Flags] - public enum MemProtection : uint - { - PAGE_EXECUTE = 0x00000010, - PAGE_EXECUTE_READ = 0x00000020, - PAGE_EXECUTE_READWRITE = 0x00000040, - PAGE_EXECUTE_WRITECOPY = 0x00000080, - PAGE_NOACCESS = 0x00000001, - PAGE_READONLY = 0x00000002, - PAGE_READWRITE = 0x00000004, - PAGE_WRITECOPY = 0x00000008, - PAGE_GUARD = 0x00000100, - PAGE_NOCACHE = 0x00000200, - PAGE_WRITECOMBINE = 0x00000400 - } - - // from https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights - // I've only listed the ones that I use - [Flags] - public enum ProcessAccess : uint - { - PROCESS_QUERY_INFORMATION = 0x0400, // Required to retrieve certain information about a process. - PROCESS_VM_READ = 0x0010, // Required to read memory in a process using ReadProcessMemory. - PROCESS_VM_WRITE = 0x0020, // Required to write to memory in a process using WriteProcessMemory. - PROCESS_VM_OPERATION = 0x0008 // Required to perform an operation on the address space of a process using VirtualProtectEx - } - - // from https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess - public enum PROCESSINFOCLASS - { - ProcessBasicInformation = 0, - ProcessDebugPort = 7, - ProcessWow64Information = 26, - ProcessImageFileName = 27, - ProcessBreakOnTermination = 29, - ProcessSubsystemInformation = 75 - } - - // from https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess - [StructLayout(LayoutKind.Sequential)] - public struct PROCESS_BASIC_INFORMATION - { - public IntPtr Reserved1; - public IntPtr PebBaseAddress; - public IntPtr Reserved2_0; - public IntPtr Reserved2_1; - public IntPtr UniqueProcessId; - public IntPtr Reserved3; - } - - public static class Pinvoke - { - [DllImport("kernel32", SetLastError = true)] - public static extern IntPtr OpenProcess(ProcessAccess dwDesiredAccess, bool bInheritHandle, int dwProcessId); - - [DllImport("kernel32", SetLastError = true)] - public static extern bool CloseHandle(IntPtr hObject); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool WriteProcessMemory([In] IntPtr hProcess, [In] IntPtr address, [In, MarshalAs(UnmanagedType.LPArray)] byte[] buffer, [In] UIntPtr size, [Out] out UIntPtr byteswritten); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool ReadProcessMemory([In] IntPtr hProcess, [In] IntPtr address, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] byte[] buffer, [In] UIntPtr size, [Out] out UIntPtr numberOfBytesRead); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool VirtualProtectEx([In] IntPtr hProcess, [In] IntPtr lpAddress, [In] UIntPtr dwSize, [In] MemProtection flNewProtect, [Out] out MemProtection lpflOldProtect); - - [DllImport("ntdll.dll")] - public static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS processInformationClass, out PROCESS_BASIC_INFORMATION processInformation, uint processInformationLength, out uint returnLength); - - public static int GetProcessParentPid(Process process) - { - IntPtr hProcess = OpenProcess( - ProcessAccess.PROCESS_VM_READ - | ProcessAccess.PROCESS_QUERY_INFORMATION, - false, process.Id); - - if (hProcess == IntPtr.Zero) - { - throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to get a process handle."); - } - - PROCESS_BASIC_INFORMATION PEB = new PROCESS_BASIC_INFORMATION(); - - int result = NtQueryInformationProcess(hProcess, - PROCESSINFOCLASS.ProcessBasicInformation, out PEB, - (uint) Marshal.SizeOf(PEB), out _); - - CloseHandle(hProcess); - if (result != 0) - { - throw new Win32Exception(result, "(NTSTATUS)"); // not a w32 status code, but an NTSTATUS - } - - return PEB.Reserved3.ToInt32(); // undocumented, but should hold the parent PID - } - } -} diff --git a/patcher/Program.cs b/patcher/Program.cs deleted file mode 100644 index 2dadfe587..000000000 --- a/patcher/Program.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Security.Principal; -using System.Windows.Forms; - -namespace HitmanPatcher -{ - static class Program - { - public static bool HasAdmin; - - /// - /// The main entry point for the application. - /// - [STAThread] - private static void Main(string[] args) - { - HasAdmin = CheckForAdmin(); -#if !DEBUG - Cli.EnsureConsole(args); -#endif - - Cli.CliOptions o = Cli.CliOptions.FromArguments(args); - - if (o.Headless) - { - // ReSharper disable once LocalizableElement - Console.WriteLine(CliLocale.HeadlessBanner); - MemoryPatcher.PatchAllProcesses(new Cli.ConsoleLogger(), new MemoryPatcher.Options - { - AlwaysSendAuthHeader = true, - CustomConfigDomain = o.Domain, - DisableCertPinning = true, - DisableForceOfflineOnFailedDynamicResources = o.OptionalDynRes, - SetCustomConfigDomain = true, - UseHttp = o.UseHttp - }); - } - else - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - - Application.Run(MainForm.GetInstance()); - } - } - - static bool CheckForAdmin() - { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - - } -} diff --git a/patcher/Properties/AssemblyInfo.cs b/patcher/Properties/AssemblyInfo.cs deleted file mode 100644 index 40916fa24..000000000 --- a/patcher/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Peacock Patcher")] -[assembly: AssemblyDescription("Peacock's HITMAN™ World of Assassination trilogy game patcher.")] -#if DEBUG - [assembly: AssemblyConfiguration("Debug")] -#else - [assembly: AssemblyConfiguration("Release")] -#endif -[assembly: AssemblyCompany("The Peacock Project")] -[assembly: AssemblyProduct("Peacock Patcher")] -[assembly: AssemblyCopyright("Copyright © 2020-2024 grappigegovert & The Peacock Project")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("4f027fef-87be-47f7-a0c6-f3978dc707e3")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("8.0.0.0")] -[assembly: AssemblyFileVersion("8.0.0.0")] diff --git a/patcher/Properties/Resources.Designer.cs b/patcher/Properties/Resources.Designer.cs deleted file mode 100644 index 9aeb8e631..000000000 --- a/patcher/Properties/Resources.Designer.cs +++ /dev/null @@ -1,90 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.42000 -// -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. -// -//------------------------------------------------------------------------------ - -namespace HitmanPatcher.Properties { - /// - /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. - /// - // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert - // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. - // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HitmanPatcher.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap Novikov_error { - get { - object obj = ResourceManager.GetObject("Novikov_error", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap Novikov_standard { - get { - object obj = ResourceManager.GetObject("Novikov_standard", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap Novikov_success { - get { - object obj = ResourceManager.GetObject("Novikov_success", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - } -} diff --git a/patcher/Properties/Resources.resx b/patcher/Properties/Resources.resx deleted file mode 100644 index 8de4204db..000000000 --- a/patcher/Properties/Resources.resx +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\Novikov_standard.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\Novikov_error.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\Novikov_success.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - diff --git a/patcher/Properties/Settings.Designer.cs b/patcher/Properties/Settings.Designer.cs deleted file mode 100644 index eeee566bd..000000000 --- a/patcher/Properties/Settings.Designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace HitmanPatcher.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} diff --git a/patcher/Properties/Settings.settings b/patcher/Properties/Settings.settings deleted file mode 100644 index 39645652a..000000000 --- a/patcher/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/patcher/build.bat b/patcher/build.bat new file mode 100644 index 000000000..9b85e21b7 --- /dev/null +++ b/patcher/build.bat @@ -0,0 +1,18 @@ +@echo off +rem Windows - CLI +dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -c Release -p:PublishSingleFile=True --no-self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Portable +dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -r win-x64 -c Release -f net8.0 -p:PublishTrimmed=True -p:PublishSingleFile=True --self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-x64 + +rem Linux - CLI +dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -r linux-x64 -c "Release - Linux" -p:PublishSingleFile=True --no-self-contained -p DebugType=none -p:IsLinux=true -o Publish/Linux-Portable +dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -r linux-x64 -c "Release - Linux" -f net8.0 -p:PublishTrimmed=True -p:PublishSingleFile=True --self-contained -p DebugType=none -p:IsLinux=true -o Publish/Linux-x64 + +rem Windows - UI +dotnet publish HitmanPatcher.UI/HitmanPatcher.UI.csproj -c Release -p:PublishSingleFile=True --no-self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Portable +dotnet publish HitmanPatcher.UI/HitmanPatcher.UI.csproj -r win-x64 -c Release -p:PublishSingleFile=True --self-contained -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-x64 + +rem Windows (.NET Framework) +dotnet publish HitmanPatcher.CLI/HitmanPatcher.CLI.csproj -c Release -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Legacy -p:IsLegacy=True +dotnet publish HitmanPatcher.UI/HitmanPatcher.UI.csproj -c Release -p DebugType=none -p:EnableWindowsTargeting=True -o Publish/Windows-Legacy -p:IsLegacy=True + +pause \ No newline at end of file