├── .github ├── CODEOWNERS └── CONTRIBUTING.md ├── .gitattributes ├── Augmentrex.ico ├── .gitignore ├── .editorconfig ├── Augmentrex ├── Properties │ ├── AssemblyInfo.cs │ ├── App.manifest │ └── App.config ├── Win32.cs ├── Plugins │ ├── Plugin.cs │ └── PluginManager.cs ├── Finally.cs ├── Commands │ ├── Core │ │ ├── ExitCommand.cs │ │ ├── KillCommand.cs │ │ ├── ClearCommand.cs │ │ ├── BreakCommand.cs │ │ ├── ReloadCommand.cs │ │ ├── HelpCommand.cs │ │ ├── DisassembleCommand.cs │ │ ├── KeyCommand.cs │ │ ├── EvaluateCommand.cs │ │ ├── ReadCommand.cs │ │ └── WriteCommand.cs │ ├── Command.cs │ └── CommandInterpreter.cs ├── Keyboard │ ├── HotKeyHandler.cs │ ├── HotKeyInfo.cs │ └── HotKeyRegistrar.cs ├── ConsoleWindow.cs ├── Memory │ ├── FunctionHook.cs │ ├── MemoryWindow.cs │ ├── MemoryOffset.cs │ ├── MemoryAddress.cs │ └── InstructionExtensions.cs ├── Ipc │ ├── IpcBridge.cs │ └── IpcChannel.cs ├── Configuration.cs ├── DebugListener.cs ├── EntryPoint.cs ├── Log.cs ├── Program.cs ├── Augmentrex.csproj └── AugmentrexContext.cs ├── Augmentrex.Plugins.SimpleTest ├── Properties │ └── AssemblyInfo.cs ├── SimpleTestPlugin.cs └── Augmentrex.Plugins.SimpleTest.csproj ├── Augmentrex.Commands.PatchCCAgent ├── Properties │ └── AssemblyInfo.cs ├── Augmentrex.Commands.PatchCCAgent.csproj └── PatchCCAgentCommand.cs ├── Augmentrex.Commands.PatchLongRayVM ├── Properties │ └── AssemblyInfo.cs ├── Augmentrex.Commands.PatchLongRayVM.csproj └── PatchLongRayVMCommand.cs ├── Augmentrex.Archive ├── packages.config └── Augmentrex.archive.proj ├── appveyor.yml ├── SolutionInfo.cs ├── .markdownlint.json ├── LICENSE.md ├── Augmentrex.sln └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @alexrp 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text 2 | *.csproj eol=crlf 3 | *.ico binary 4 | *.sln eol=crlf 5 | -------------------------------------------------------------------------------- /Augmentrex.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrp/augmentrex/HEAD/Augmentrex.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*/bin 2 | /*/obj 3 | /Build 4 | /packages 5 | .vs 6 | .vscode 7 | *.user 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | max_line_length = off 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you're submitting a fix for a non-trivial bug, please include a full 4 | description of the bug and how to reproduce it, as well as an explanation of 5 | your fix. 6 | -------------------------------------------------------------------------------- /Augmentrex/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle(nameof(Augmentrex))] 4 | [assembly: AssemblyDescription("A reverse engineering tool for the Steam version of Hellgate: London.")] 5 | -------------------------------------------------------------------------------- /Augmentrex.Plugins.SimpleTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("simple-test")] 4 | [assembly: AssemblyDescription("A simple test plugin to verify that the plugin manager works.")] 5 | -------------------------------------------------------------------------------- /Augmentrex.Commands.PatchCCAgent/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("patch-cc-agent")] 4 | [assembly: AssemblyDescription("Patches the Havok capsule-capsule collision agent with a return instruction, effectively disabling it.")] 5 | -------------------------------------------------------------------------------- /Augmentrex.Commands.PatchLongRayVM/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("patch-long-ray-vm")] 4 | [assembly: AssemblyDescription("Patches the Havok long ray virtual machine with a return instruction, effectively disabling it.")] 5 | -------------------------------------------------------------------------------- /Augmentrex.Archive/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Augmentrex/Win32.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Augmentrex 5 | { 6 | public static class Win32 7 | { 8 | public static string GetLastError() 9 | { 10 | return new Win32Exception(Marshal.GetLastWin32Error()).Message; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Augmentrex/Plugins/Plugin.cs: -------------------------------------------------------------------------------- 1 | namespace Augmentrex.Plugins 2 | { 3 | public abstract class Plugin 4 | { 5 | public abstract string Name { get; } 6 | 7 | public virtual void Start(AugmentrexContext context) 8 | { 9 | } 10 | 11 | public virtual void Stop(AugmentrexContext context) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | after_build: 2 | - cmd: | 3 | forfiles /m *.proj /S /C "cmd /C msbuild @path" 4 | artifacts: 5 | - path: 'Build\*.zip' 6 | build: 7 | verbosity: detailed 8 | before_build: 9 | - cmd: | 10 | nuget restore 11 | image: Visual Studio 2019 Preview 12 | pull_requests: 13 | do_not_increment_build_number: true 14 | skip_tags: true 15 | -------------------------------------------------------------------------------- /Augmentrex/Finally.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Augmentrex 4 | { 5 | public struct Finally : IDisposable 6 | { 7 | public readonly Action Action; 8 | 9 | public Finally(Action action) 10 | { 11 | Action = action; 12 | } 13 | 14 | public void Dispose() 15 | { 16 | Action?.Invoke(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SolutionInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyCompany("Alex Rønne Petersen")] 5 | [assembly: AssemblyProduct(nameof(Augmentrex))] 6 | [assembly: AssemblyCopyright("ISC License (No Attribution)")] 7 | [assembly: ComVisible(false)] 8 | [assembly: AssemblyVersion("2.0.0")] 9 | [assembly: AssemblyFileVersion("2.0.0")] 10 | [assembly: AssemblyInformationalVersion("2.0.0")] 11 | -------------------------------------------------------------------------------- /Augmentrex/Properties/App.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD003": { 3 | "style": "atx" 4 | }, 5 | "MD004": { 6 | "style": "asterisk" 7 | }, 8 | "MD007": { 9 | "indent": 4 10 | }, 11 | "MD009": { 12 | "br_spaces": 0 13 | }, 14 | "MD013": { 15 | "code_blocks": false, 16 | "headings": false, 17 | "tables": false 18 | }, 19 | "MD029": { 20 | "style": "ordered" 21 | }, 22 | "MD035": { 23 | "style": "---" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/ExitCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Augmentrex.Commands.Core 4 | { 5 | sealed class ExitCommand : Command 6 | { 7 | public override IReadOnlyList Names { get; } = 8 | new[] { "exit", "quit" }; 9 | 10 | public override string Description => 11 | "Detaches Augmentrex from the game and exits, leaving the game open."; 12 | 13 | public override int? Run(AugmentrexContext context, string[] args) 14 | { 15 | return 0; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Augmentrex/Keyboard/HotKeyHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Augmentrex.Keyboard 4 | { 5 | public sealed class HotKeyHandler : MarshalByRefObject 6 | { 7 | public Action Handler { get; } 8 | 9 | public HotKeyHandler(Action handler) 10 | { 11 | Handler = handler; 12 | } 13 | 14 | public override object InitializeLifetimeService() 15 | { 16 | return null; 17 | } 18 | 19 | public void Invoke(HotKeyInfo info) 20 | { 21 | Handler(info); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/KillCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Augmentrex.Commands.Core 4 | { 5 | sealed class KillCommand : Command 6 | { 7 | public override IReadOnlyList Names { get; } = 8 | new[] { "kill" }; 9 | 10 | public override string Description => 11 | "Detaches Augmentrex from the game, kills the game process, then exits."; 12 | 13 | public override int? Run(AugmentrexContext context, string[] args) 14 | { 15 | context.Ipc.Channel.KillRequested = true; 16 | 17 | return 0; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/ClearCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Augmentrex.Commands.Core 5 | { 6 | sealed class ClearCommand : Command 7 | { 8 | public override IReadOnlyList Names { get; } = 9 | new[] { "clear" }; 10 | 11 | public override string Description => 12 | "Clears the console buffer, including the scrollback buffer."; 13 | 14 | public override int? Run(AugmentrexContext context, string[] args) 15 | { 16 | context.Ipc.Channel.Clear(); 17 | Console.Clear(); 18 | 19 | return null; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Augmentrex.Plugins.SimpleTest/SimpleTestPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Augmentrex.Plugins.SimpleTest 4 | { 5 | public sealed class SimpleTestPlugin : Plugin 6 | { 7 | public override string Name { get; } = 8 | Assembly.GetExecutingAssembly().GetCustomAttribute().Title; 9 | 10 | public override void Start(AugmentrexContext context) 11 | { 12 | context.InfoLine("Simple test plugin started."); 13 | } 14 | 15 | public override void Stop(AugmentrexContext context) 16 | { 17 | context.InfoLine("Simple test plugin stopped."); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # ISC License (No Attribution) 2 | 3 | ```text 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | ``` 15 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/BreakCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | 4 | namespace Augmentrex.Commands.Core 5 | { 6 | sealed class BreakCommand : Command 7 | { 8 | public override IReadOnlyList Names { get; } = 9 | new[] { "break" }; 10 | 11 | public override string Description => 12 | "Signals a breakpoint to an attached debugger (attaches one if not present)."; 13 | 14 | public override int? Run(AugmentrexContext context, string[] args) 15 | { 16 | if (!Debugger.IsAttached) 17 | Debugger.Launch(); 18 | 19 | Debugger.Break(); 20 | 21 | return null; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/ReloadCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Plugins; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Augmentrex.Commands.Core 6 | { 7 | sealed class ReloadCommand : Command 8 | { 9 | public override IReadOnlyList Names { get; } = 10 | new[] { "reload" }; 11 | 12 | public override string Description => 13 | "Unloads all active plugins and reloads all plugins from disk."; 14 | 15 | public override int? Run(AugmentrexContext context, string[] args) 16 | { 17 | PluginManager.UnloadPlugins(); 18 | 19 | GC.Collect(GC.MaxGeneration); 20 | GC.WaitForPendingFinalizers(); 21 | 22 | PluginManager.LoadPlugins(context); 23 | 24 | return null; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Augmentrex/ConsoleWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Vanara.PInvoke; 4 | 5 | namespace Augmentrex 6 | { 7 | sealed class ConsoleWindow : IDisposable 8 | { 9 | bool _disposed; 10 | 11 | public ConsoleWindow(AugmentrexContext context) 12 | { 13 | context.Info("Allocating game console window... "); 14 | 15 | if (Kernel32.AllocConsole()) 16 | { 17 | context.SuccessLine("OK."); 18 | 19 | var asmName = Assembly.GetExecutingAssembly().GetName(); 20 | 21 | Console.Title = $"{asmName.Name} {asmName.Version} - Game Process"; 22 | } 23 | else 24 | context.WarningLine("{0}.", Win32.GetLastError()); 25 | } 26 | 27 | ~ConsoleWindow() 28 | { 29 | RealDispose(); 30 | } 31 | 32 | public void Dispose() 33 | { 34 | RealDispose(); 35 | GC.SuppressFinalize(this); 36 | } 37 | 38 | void RealDispose() 39 | { 40 | if (_disposed) 41 | return; 42 | 43 | _disposed = true; 44 | 45 | Kernel32.FreeConsole(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Augmentrex/Memory/FunctionHook.cs: -------------------------------------------------------------------------------- 1 | using EasyHook; 2 | using System; 3 | 4 | namespace Augmentrex.Memory 5 | { 6 | public sealed class FunctionHook : IDisposable 7 | where T : Delegate 8 | { 9 | public MemoryAddress Address { get; } 10 | 11 | public string Name { get; } 12 | 13 | public T Original { get; } 14 | 15 | public T Hook { get; } 16 | 17 | public bool IsInstalled { get; private set; } 18 | 19 | readonly LocalHook _hook; 20 | 21 | bool _disposed; 22 | 23 | internal FunctionHook(MemoryAddress address, string name, T original, T hook) 24 | { 25 | Address = address; 26 | Name = name; 27 | Original = original; 28 | Hook = hook; 29 | 30 | _hook = LocalHook.Create(address, hook, this); 31 | } 32 | 33 | ~FunctionHook() 34 | { 35 | RealDispose(); 36 | } 37 | 38 | void IDisposable.Dispose() 39 | { 40 | RealDispose(); 41 | GC.SuppressFinalize(this); 42 | } 43 | 44 | void RealDispose() 45 | { 46 | if (_disposed) 47 | return; 48 | 49 | _disposed = true; 50 | 51 | _hook?.Dispose(); 52 | } 53 | 54 | public void Install() 55 | { 56 | _hook.ThreadACL.SetExclusiveACL(null); 57 | IsInstalled = true; 58 | } 59 | 60 | public void Uninstall() 61 | { 62 | _hook.ThreadACL.SetInclusiveACL(null); 63 | IsInstalled = false; 64 | } 65 | 66 | public void Toggle() 67 | { 68 | if (!IsInstalled) 69 | Install(); 70 | else 71 | Uninstall(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Augmentrex/Ipc/IpcBridge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Augmentrex.Ipc 6 | { 7 | sealed class IpcBridge : IDisposable 8 | { 9 | public string ChannelName { get; } 10 | 11 | public IpcChannel Channel { get; } 12 | 13 | public Configuration Configuration { get; } 14 | 15 | readonly CancellationTokenSource _cts; 16 | 17 | readonly Task _task; 18 | 19 | bool _disposed; 20 | 21 | internal IpcBridge(string channelName, bool shallow) 22 | { 23 | ChannelName = channelName; 24 | Channel = IpcChannel.Connect(channelName); 25 | 26 | Channel.Ping(); 27 | 28 | Configuration = Channel.Configuration; 29 | 30 | if (shallow) 31 | return; 32 | 33 | Channel.KeepAlive(); 34 | 35 | _cts = new CancellationTokenSource(); 36 | _task = Task.Factory.StartNew(async () => 37 | { 38 | while (true) 39 | { 40 | _cts.Token.ThrowIfCancellationRequested(); 41 | 42 | Channel.KeepAlive(); 43 | 44 | await Task.Delay(Configuration.IpcKeepAlive); 45 | } 46 | }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); 47 | } 48 | 49 | ~IpcBridge() 50 | { 51 | RealDispose(); 52 | } 53 | 54 | public void Dispose() 55 | { 56 | RealDispose(); 57 | GC.SuppressFinalize(this); 58 | } 59 | 60 | void RealDispose() 61 | { 62 | if (_disposed) 63 | return; 64 | 65 | _disposed = true; 66 | 67 | _cts?.Cancel(); 68 | _task?.Wait(); 69 | _task?.Dispose(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Augmentrex.Plugins.SimpleTest/Augmentrex.Plugins.SimpleTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x86 7 | true 8 | augmentrex-plugin-simple-test 9 | true 10 | embedded 11 | DEBUG;TRACE 12 | true 13 | 8.0 14 | ..\Build\ 15 | Library 16 | {394AFD4F-3461-4C52-AECC-01BFFAB961EF} 17 | Augmentrex.Plugins.SimpleTest 18 | v4.7.2 19 | true 20 | 21 | 22 | x86 23 | 24 | 25 | 26 | Properties\SolutionInfo.cs 27 | 28 | 29 | 30 | 31 | 32 | 33 | {6E5B1E1C-5C31-444F-906D-C1C04DB0CB5F} 34 | Augmentrex 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Augmentrex.Commands.PatchCCAgent/Augmentrex.Commands.PatchCCAgent.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x86 7 | true 8 | augmentrex-command-patch-cc-agent 9 | true 10 | embedded 11 | DEBUG;TRACE 12 | true 13 | 8.0 14 | ..\Build\ 15 | Library 16 | {0AC3B157-4389-4FDB-8778-F87E48C1C663} 17 | Augmentrex.Commands.PatchCCAgent 18 | v4.7.2 19 | true 20 | 21 | 22 | x86 23 | 24 | 25 | 26 | Properties\SolutionInfo.cs 27 | 28 | 29 | 30 | 31 | 32 | 33 | {6E5B1E1C-5C31-444F-906D-C1C04DB0CB5F} 34 | Augmentrex 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Augmentrex.Commands.PatchLongRayVM/Augmentrex.Commands.PatchLongRayVM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x86 7 | true 8 | augmentrex-command-patch-long-ray-vm 9 | true 10 | embedded 11 | DEBUG;TRACE 12 | true 13 | 8.0 14 | ..\Build\ 15 | Library 16 | {3649819A-6DB2-4D13-92D1-9AF8DCF1F6E6} 17 | Augmentrex.Commands.PatchLongRayVM 18 | v4.7.2 19 | true 20 | 21 | 22 | x86 23 | 24 | 25 | 26 | Properties\SolutionInfo.cs 27 | 28 | 29 | 30 | 31 | 32 | 33 | {6E5B1E1C-5C31-444F-906D-C1C04DB0CB5F} 34 | Augmentrex 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Command.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLine.Text; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Augmentrex.Commands 7 | { 8 | public abstract class Command 9 | { 10 | public abstract IReadOnlyList Names { get; } 11 | 12 | public abstract string Description { get; } 13 | 14 | public virtual string Syntax => string.Empty; 15 | 16 | public virtual IReadOnlyList Details => Array.Empty(); 17 | 18 | protected static void PrintHelp(AugmentrexContext context, Command command, bool details) 19 | { 20 | context.SuccessLine(" {0} {1}", string.Join("|", command.Names), command.Syntax); 21 | context.InfoLine(" {0}", command.Description); 22 | 23 | if (details && command.Details.Count != 0) 24 | { 25 | foreach (var detail in command.Details) 26 | { 27 | context.Line(); 28 | context.InfoLine(" {0}", detail); 29 | } 30 | } 31 | } 32 | 33 | public abstract int? Run(AugmentrexContext context, string[] args); 34 | 35 | protected T Parse(AugmentrexContext context, string[] args) 36 | where T : class 37 | { 38 | return new Parser(opts => 39 | { 40 | opts.AutoHelp = false; 41 | opts.AutoVersion = false; 42 | opts.CaseInsensitiveEnumValues = true; 43 | opts.CaseSensitive = false; 44 | }).ParseArguments(args).MapResult( 45 | result => result, 46 | errors => 47 | { 48 | var builder = SentenceBuilder.Create(); 49 | 50 | foreach (var error in errors) 51 | context.ErrorLine("{0}", builder.FormatError(error)); 52 | 53 | context.Line(); 54 | PrintHelp(context, this, true); 55 | context.Line(); 56 | 57 | return null; 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Augmentrex.Commands.Core 6 | { 7 | sealed class HelpCommand : Command 8 | { 9 | sealed class HelpOptions 10 | { 11 | [Value(0)] 12 | public IEnumerable Commands { get; set; } 13 | } 14 | 15 | public override IReadOnlyList Names { get; } = 16 | new[] { "help", "?" }; 17 | 18 | public override string Description => 19 | "Requests help for given commands, or lists all commands."; 20 | 21 | public override string Syntax => 22 | "[command ...]"; 23 | 24 | public override int? Run(AugmentrexContext context, string[] args) 25 | { 26 | var opts = Parse(context, args); 27 | 28 | if (opts == null) 29 | return null; 30 | 31 | if (opts.Commands.Any()) 32 | { 33 | var cmds = opts.Commands.Select(x => (x, CommandInterpreter.GetCommand(x))); 34 | var unknown = false; 35 | 36 | foreach (var (name, cmd) in cmds) 37 | { 38 | if (cmd == null) 39 | { 40 | unknown = true; 41 | context.ErrorLine("Unknown command '{0}'.", name); 42 | } 43 | } 44 | 45 | if (unknown) 46 | return null; 47 | 48 | context.Line(); 49 | 50 | foreach (var (_, cmd) in cmds) 51 | { 52 | PrintHelp(context, cmd, true); 53 | context.Line(); 54 | } 55 | } 56 | else 57 | { 58 | context.Line(); 59 | context.InfoLine("Available commands:"); 60 | context.Line(); 61 | 62 | foreach (var cmd in CommandInterpreter.Commands) 63 | { 64 | PrintHelp(context, cmd, false); 65 | context.Line(); 66 | } 67 | } 68 | 69 | return null; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Augmentrex/Keyboard/HotKeyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace Augmentrex.Keyboard 6 | { 7 | [Serializable] 8 | public sealed class HotKeyInfo : IEquatable 9 | { 10 | public Keys Key { get; } 11 | 12 | public bool Alt { get; } 13 | 14 | public bool Control { get; } 15 | 16 | public bool Shift { get; } 17 | 18 | internal int Id { get; set; } 19 | 20 | internal HashSet Handlers { get; } = new HashSet(); 21 | 22 | public HotKeyInfo(Keys key, bool alt, bool control, bool shift) 23 | { 24 | Key = key; 25 | Alt = alt; 26 | Control = control; 27 | Shift = shift; 28 | } 29 | 30 | public static bool operator ==(HotKeyInfo left, HotKeyInfo right) 31 | { 32 | return left.Key == right.Key && left.Alt == right.Alt && left.Control == right.Control && left.Shift == right.Shift; 33 | } 34 | 35 | public static bool operator !=(HotKeyInfo left, HotKeyInfo right) 36 | { 37 | return !(left == right); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | var hash = -1938637507; 43 | 44 | hash = hash * -1521134295 + Key.GetHashCode(); 45 | hash = hash * -1521134295 + Alt.GetHashCode(); 46 | hash = hash * -1521134295 + Control.GetHashCode(); 47 | hash = hash * -1521134295 + Shift.GetHashCode(); 48 | 49 | return hash; 50 | } 51 | 52 | public bool Equals(HotKeyInfo other) 53 | { 54 | return this == other; 55 | } 56 | 57 | public override bool Equals(object obj) 58 | { 59 | return obj is HotKeyInfo info && this == info; 60 | } 61 | 62 | public override string ToString() 63 | { 64 | var str = string.Empty; 65 | 66 | if (Alt) 67 | str += "Alt+"; 68 | 69 | if (Control) 70 | str += "Ctrl+"; 71 | 72 | if (Shift) 73 | str += "Shift+"; 74 | 75 | return $"{str}{Key}"; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Augmentrex/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | using System.Configuration; 4 | using System.Linq; 5 | 6 | namespace Augmentrex 7 | { 8 | [Serializable] 9 | public sealed class Configuration 10 | { 11 | static readonly Lazy _lazy = 12 | new Lazy(() => new Configuration(ConfigurationManager.AppSettings)); 13 | 14 | internal static Configuration Instance => _lazy.Value; 15 | 16 | public string GamePath { get; set; } 17 | 18 | public string GameArguments { get; set; } 19 | 20 | public TimeSpan IpcTimeout { get; set; } 21 | 22 | public TimeSpan IpcKeepAlive { get; set; } 23 | 24 | public bool GameConsoleEnabled { get; set; } 25 | 26 | public bool DebugListenerEnabled { get; set; } 27 | 28 | public TimeSpan DebugListenerInterval { get; set; } 29 | 30 | public int HotKeyBeepFrequency { get; set; } 31 | 32 | public TimeSpan HotKeyBeepDuration { get; set; } 33 | 34 | public string[] DisabledPlugins { get; set; } 35 | 36 | public string[] RunCommands { get; set; } 37 | 38 | Configuration(NameValueCollection cfg) 39 | { 40 | GamePath = cfg["gamePath"]; 41 | GameArguments = cfg["gameArguments"]; 42 | IpcTimeout = TimeSpan.FromMilliseconds(int.Parse(cfg["ipcTimeout"])); 43 | IpcKeepAlive = TimeSpan.FromMilliseconds(int.Parse(cfg["ipcKeepAlive"])); 44 | DebugListenerInterval = TimeSpan.FromMilliseconds(int.Parse(cfg["debugListenerInterval"])); 45 | GameConsoleEnabled = bool.Parse(cfg["gameConsoleEnabled"]); 46 | DebugListenerEnabled = bool.Parse(cfg["debugListenerEnabled"]); 47 | HotKeyBeepFrequency = int.Parse(cfg["hotKeyBeepFrequency"]); 48 | HotKeyBeepDuration = TimeSpan.FromMilliseconds(int.Parse(cfg["HotKeyBeepDuration"])); 49 | DisabledPlugins = Split(cfg["disabledPlugins"], ','); 50 | RunCommands = Split(cfg["runCommands"], ';'); 51 | } 52 | 53 | static string[] Split(string value, params char[] separators) 54 | { 55 | return value.Split(separators, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Augmentrex/Properties/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 17 | 18 | 23 | 24 | 27 | 28 | 31 | 32 | 36 | 37 | 41 | 42 | 45 | 46 | 50 | 51 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Augmentrex.Archive/Augmentrex.archive.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {9522FC5A-8E6E-40C2-853E-6947B656D0D6} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Augmentrex/DebugListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.MemoryMappedFiles; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Augmentrex 9 | { 10 | sealed class DebugListener : IDisposable 11 | { 12 | readonly CancellationTokenSource _cts; 13 | 14 | readonly Task _task; 15 | 16 | bool _disposed; 17 | 18 | public DebugListener(AugmentrexContext context) 19 | { 20 | context.InfoLine("Starting debug message listener..."); 21 | 22 | _cts = new CancellationTokenSource(); 23 | _task = Task.Factory.StartNew(() => 24 | { 25 | const int BufferSize = 4096; 26 | 27 | using var mmf = MemoryMappedFile.CreateNew("DBWIN_BUFFER", BufferSize); 28 | using var bufReady = new EventWaitHandle(false, EventResetMode.AutoReset, "DBWIN_BUFFER_READY"); 29 | using var dataReady = new EventWaitHandle(false, EventResetMode.AutoReset, "DBWIN_DATA_READY"); 30 | 31 | while (true) 32 | { 33 | _cts.Token.ThrowIfCancellationRequested(); 34 | 35 | bufReady.Set(); 36 | 37 | if (!dataReady.WaitOne(context.Configuration.DebugListenerInterval)) 38 | continue; 39 | 40 | using var stream = mmf.CreateViewStream(0, BufferSize, MemoryMappedFileAccess.Read); 41 | using var reader = new BinaryReader(stream); 42 | 43 | if (reader.ReadUInt32() != context.Game.Id) 44 | continue; 45 | 46 | var bytes = reader.ReadBytes(BufferSize - sizeof(int)); 47 | var str = Encoding.GetEncoding("euc-kr").GetString(bytes, 0, Array.IndexOf(bytes, 0)); 48 | 49 | Log.DebugLine("{0}", str.Substring(0, str.Length - 1)); 50 | } 51 | }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); 52 | } 53 | 54 | ~DebugListener() 55 | { 56 | RealDispose(); 57 | } 58 | 59 | public void Dispose() 60 | { 61 | RealDispose(); 62 | GC.SuppressFinalize(this); 63 | } 64 | 65 | void RealDispose() 66 | { 67 | if (_disposed) 68 | return; 69 | 70 | _disposed = true; 71 | 72 | _cts?.Cancel(); 73 | _task?.Wait(); 74 | _task?.Dispose(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Augmentrex/EntryPoint.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Commands; 2 | using Augmentrex.Memory; 3 | using Augmentrex.Plugins; 4 | using EasyHook; 5 | using System; 6 | using System.Diagnostics; 7 | using Vanara.PInvoke; 8 | 9 | namespace Augmentrex 10 | { 11 | public sealed class EntryPoint : IEntryPoint 12 | { 13 | #pragma warning disable IDE0060 // Remove unused parameter 14 | public EntryPoint(RemoteHooking.IContext context, string channelName) 15 | #pragma warning restore IDE0060 // Remove unused parameter 16 | { 17 | } 18 | 19 | public void Run(RemoteHooking.IContext context, string channelName) 20 | { 21 | AugmentrexContext ctx = null; 22 | 23 | AppDomain.CurrentDomain.UnhandledException += (sender, e) => 24 | { 25 | ctx?.Line(); 26 | ctx?.ErrorLine("Injected assembly crashed: {0}", e.ExceptionObject); 27 | }; 28 | 29 | ctx = new AugmentrexContext(false, Process.GetProcessById(context.HostPID), Process.GetCurrentProcess(), channelName, null); 30 | 31 | try 32 | { 33 | var mod = ctx.Game.MainModule; 34 | var addr = (MemoryAddress)mod.BaseAddress; 35 | var size = mod.ModuleMemorySize; 36 | 37 | ctx.Info("Making main module ({0}..{1}) writable... ", addr, addr + (MemoryOffset)size); 38 | 39 | if (Kernel32.VirtualProtectEx(ctx.Game.Handle, addr, size, Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE, out var _)) 40 | ctx.SuccessLine("OK."); 41 | else 42 | ctx.WarningLine("{0}.", Win32.GetLastError()); 43 | 44 | ctx.InfoLine("Waking up process... "); 45 | 46 | RemoteHooking.WakeUpProcess(); 47 | 48 | PluginManager.LoadPlugins(ctx); 49 | CommandInterpreter.LoadCommands(false); 50 | 51 | var rc = ctx.Configuration.RunCommands; 52 | var interp = ctx.Interpreter; 53 | 54 | if (rc.Length != 0) 55 | { 56 | ctx.InfoLine("Running startup commands..."); 57 | 58 | foreach (var cmd in rc) 59 | interp.RunCommand(cmd, false); 60 | } 61 | 62 | ctx.SuccessLine("Injection completed successfully. Type 'help' for a list of commands."); 63 | ctx.Line(); 64 | 65 | interp.RunLoop(); 66 | } 67 | finally 68 | { 69 | PluginManager.UnloadPlugins(); 70 | 71 | if (ctx is IDisposable disp) 72 | disp.Dispose(); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/DisassembleCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Memory; 2 | using CommandLine; 3 | using SharpDisasm; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | 8 | namespace Augmentrex.Commands.Core 9 | { 10 | sealed class DisassembleCommand : Command 11 | { 12 | sealed class DisassembleOptions 13 | { 14 | [Option('a')] 15 | public bool Absolute { get; set; } 16 | 17 | [Value(0, Required = true)] 18 | public string Offset { get; set; } 19 | 20 | [Value(1, Required = true)] 21 | public uint Length { get; set; } 22 | } 23 | 24 | public override IReadOnlyList Names { get; } = 25 | new[] { "disassemble", "disasm" }; 26 | 27 | public override string Description => 28 | "Disassembles instructions from a given memory range based on the main module."; 29 | 30 | public override string Syntax => 31 | "[-a] "; 32 | 33 | public override IReadOnlyList Details { get; } = 34 | new[] 35 | { 36 | "If '-a' is given, 'offset' is interpreted as an absolute address instead.", 37 | }; 38 | 39 | public override int? Run(AugmentrexContext context, string[] args) 40 | { 41 | var opts = Parse(context, args); 42 | 43 | if (opts == null) 44 | return null; 45 | 46 | var parsed = (int)TypeDescriptor.GetConverter(typeof(int)).ConvertFromString(opts.Offset); 47 | var address = opts.Absolute ? (MemoryAddress)parsed : context.Memory.ToAddress((MemoryOffset)parsed); 48 | 49 | using var disasm = new Disassembler(address, (int)opts.Length, ArchitectureMode.x86_32, (uint)address, true); 50 | var insns = new List(); 51 | Instruction insn; 52 | string error = null; 53 | 54 | while ((insn = disasm.NextInstruction()) != null) 55 | { 56 | if (insn.Error) 57 | { 58 | error = insn.ErrorMessage; 59 | break; 60 | } 61 | 62 | insns.Add(insn); 63 | } 64 | 65 | var length = insns.Max(x => x.Bytes.Length); 66 | 67 | foreach (var i in insns) 68 | i.PrintColored(context, length); 69 | 70 | if (error != null) 71 | { 72 | if (insns.Count != 0) 73 | context.Line(); 74 | 75 | context.WarningLine("Could not disassemble further: {0}", error); 76 | } 77 | 78 | return null; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Augmentrex/Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Augmentrex 4 | { 5 | static class Log 6 | { 7 | static readonly object _lock = new object(); 8 | 9 | static void Output(ConsoleColor color, bool line, string format, params object[] args) 10 | { 11 | lock (_lock) 12 | { 13 | Console.ForegroundColor = color; 14 | 15 | try 16 | { 17 | Console.Write(format, args); 18 | 19 | if (line) 20 | Console.WriteLine(); 21 | } 22 | finally 23 | { 24 | Console.ResetColor(); 25 | } 26 | } 27 | } 28 | 29 | public static void Info(string format, params object[] args) 30 | { 31 | Output(ConsoleColor.White, false, format, args); 32 | } 33 | 34 | public static void InfoLine(string format, params object[] args) 35 | { 36 | Output(ConsoleColor.White, true, format, args); 37 | } 38 | 39 | public static void Warning(string format, params object[] args) 40 | { 41 | Output(ConsoleColor.Yellow, false, format, args); 42 | } 43 | 44 | public static void WarningLine(string format, params object[] args) 45 | { 46 | Output(ConsoleColor.Yellow, true, format, args); 47 | } 48 | 49 | public static void Error(string format, params object[] args) 50 | { 51 | Output(ConsoleColor.Red, false, format, args); 52 | } 53 | 54 | public static void ErrorLine(string format, params object[] args) 55 | { 56 | Output(ConsoleColor.Red, true, format, args); 57 | } 58 | 59 | public static void Success(string format, params object[] args) 60 | { 61 | Output(ConsoleColor.Green, false, format, args); 62 | } 63 | 64 | public static void SuccessLine(string format, params object[] args) 65 | { 66 | Output(ConsoleColor.Green, true, format, args); 67 | } 68 | 69 | public static void Debug(string format, params object[] args) 70 | { 71 | Output(ConsoleColor.Magenta, false, format, args); 72 | } 73 | 74 | public static void DebugLine(string format, params object[] args) 75 | { 76 | Output(ConsoleColor.Magenta, true, format, args); 77 | } 78 | 79 | public static void Color(ConsoleColor color, string format, params object[] args) 80 | { 81 | Output(color, false, format, args); 82 | } 83 | 84 | public static void ColorLine(ConsoleColor color, string format, params object[] args) 85 | { 86 | Output(color, true, format, args); 87 | } 88 | 89 | public static void Line() 90 | { 91 | Output(ConsoleColor.White, true, string.Empty); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Augmentrex.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28721.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C02001A0-750D-4A8B-89A2-8765A3FCE7A6}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | .gitattributes = .gitattributes 10 | .gitignore = .gitignore 11 | .markdownlint.json = .markdownlint.json 12 | appveyor.yml = appveyor.yml 13 | Augmentrex.ico = Augmentrex.ico 14 | LICENSE.md = LICENSE.md 15 | README.md = README.md 16 | SolutionInfo.cs = SolutionInfo.cs 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{047B8436-9292-466C-B8C3-A5B133A58A51}" 20 | ProjectSection(SolutionItems) = preProject 21 | .github\CODEOWNERS = .github\CODEOWNERS 22 | .github\CONTRIBUTING.md = .github\CONTRIBUTING.md 23 | EndProjectSection 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Augmentrex", "Augmentrex\Augmentrex.csproj", "{6E5B1E1C-5C31-444F-906D-C1C04DB0CB5F}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Augmentrex.Archive", "Augmentrex.Archive\Augmentrex.Archive.proj", "{9522FC5A-8E6E-40C2-853E-6947B656D0D6}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Augmentrex.Commands.PatchCCAgent", "Augmentrex.Commands.PatchCCAgent\Augmentrex.Commands.PatchCCAgent.csproj", "{0AC3B157-4389-4FDB-8778-F87E48C1C663}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Augmentrex.Commands.PatchLongRayVM", "Augmentrex.Commands.PatchLongRayVM\Augmentrex.Commands.PatchLongRayVM.csproj", "{3649819A-6DB2-4D13-92D1-9AF8DCF1F6E6}" 32 | EndProject 33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Augmentrex.Plugins.SimpleTest", "Augmentrex.Plugins.SimpleTest\Augmentrex.Plugins.SimpleTest.csproj", "{394AFD4F-3461-4C52-AECC-01BFFAB961EF}" 34 | EndProject 35 | Global 36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 37 | Debug|x86 = Debug|x86 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {6E5B1E1C-5C31-444F-906D-C1C04DB0CB5F}.Debug|x86.ActiveCfg = Debug|x86 41 | {6E5B1E1C-5C31-444F-906D-C1C04DB0CB5F}.Debug|x86.Build.0 = Debug|x86 42 | {9522FC5A-8E6E-40C2-853E-6947B656D0D6}.Debug|x86.ActiveCfg = Debug|x86 43 | {0AC3B157-4389-4FDB-8778-F87E48C1C663}.Debug|x86.ActiveCfg = Debug|x86 44 | {0AC3B157-4389-4FDB-8778-F87E48C1C663}.Debug|x86.Build.0 = Debug|x86 45 | {3649819A-6DB2-4D13-92D1-9AF8DCF1F6E6}.Debug|x86.ActiveCfg = Debug|x86 46 | {3649819A-6DB2-4D13-92D1-9AF8DCF1F6E6}.Debug|x86.Build.0 = Debug|x86 47 | {394AFD4F-3461-4C52-AECC-01BFFAB961EF}.Debug|x86.ActiveCfg = Debug|x86 48 | {394AFD4F-3461-4C52-AECC-01BFFAB961EF}.Debug|x86.Build.0 = Debug|x86 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(NestedProjects) = preSolution 54 | {047B8436-9292-466C-B8C3-A5B133A58A51} = {C02001A0-750D-4A8B-89A2-8765A3FCE7A6} 55 | EndGlobalSection 56 | GlobalSection(ExtensibilityGlobals) = postSolution 57 | SolutionGuid = {EFE5AB69-2C61-4A8E-888B-C5C8E75494AE} 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /Augmentrex/Plugins/PluginManager.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Keyboard; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace Augmentrex.Plugins 10 | { 11 | sealed class PluginManager : MarshalByRefObject 12 | { 13 | const string AssemblyFormat = "augmentrex-plugin-{0}.dll"; 14 | 15 | static readonly List<(AppDomain, PluginManager)> _managers = new List<(AppDomain, PluginManager)>(); 16 | 17 | readonly List _plugins = new List(); 18 | 19 | AugmentrexContext _context; 20 | 21 | public static void LoadPlugins(AugmentrexContext context) 22 | { 23 | var exe = Assembly.GetExecutingAssembly(); 24 | var loc = exe.Location; 25 | var asms = Directory.EnumerateFiles(Path.GetDirectoryName(loc), string.Format(AssemblyFormat, "*")); 26 | 27 | foreach (var asm in asms) 28 | { 29 | if (context.Configuration.DisabledPlugins.Any(x => 30 | Path.GetFileName(asm).Equals(string.Format(AssemblyFormat, x), StringComparison.CurrentCultureIgnoreCase))) 31 | continue; 32 | 33 | var path = Path.GetDirectoryName(loc); 34 | var domain = AppDomain.CreateDomain(string.Empty, null, new AppDomainSetup 35 | { 36 | ApplicationBase = path, 37 | LoaderOptimization = LoaderOptimization.MultiDomainHost, 38 | ShadowCopyFiles = "true", 39 | }); 40 | var mgr = (PluginManager)domain.CreateInstanceAndUnwrap(exe.GetName().FullName, typeof(PluginManager).FullName); 41 | 42 | mgr.StartPlugins(asm, context.Host.Id, context.Game.Id, context.Ipc.ChannelName, context.HotKeys); 43 | _managers.Add((domain, mgr)); 44 | } 45 | } 46 | 47 | public static void UnloadPlugins() 48 | { 49 | foreach (var (domain, mgr) in _managers) 50 | { 51 | mgr.StopPlugins(); 52 | AppDomain.Unload(domain); 53 | } 54 | 55 | _managers.Clear(); 56 | } 57 | 58 | public override object InitializeLifetimeService() 59 | { 60 | return null; 61 | } 62 | 63 | internal void StartPlugins(string path, int hostId, int gameId, string channelName, HotKeyRegistrar hotKeys) 64 | { 65 | var host = Process.GetProcessById(hostId); 66 | var game = Process.GetProcessById(gameId); 67 | 68 | _context = new AugmentrexContext(true, host, game, channelName, hotKeys); 69 | 70 | foreach (var type in Assembly.UnsafeLoadFrom(path).DefinedTypes.Where(x => x.BaseType == typeof(Plugin))) 71 | { 72 | var plugin = (Plugin)Activator.CreateInstance(type); 73 | 74 | _context.InfoLine("Starting plugin '{0}'...", plugin.Name); 75 | 76 | plugin.Start(_context); 77 | _plugins.Add(plugin); 78 | } 79 | } 80 | 81 | internal void StopPlugins() 82 | { 83 | foreach (var plugin in _plugins) 84 | { 85 | _context.InfoLine("Stopping plugin '{0}'...", plugin.Name); 86 | 87 | plugin.Stop(_context); 88 | } 89 | 90 | ((IDisposable)_context).Dispose(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Augmentrex/Memory/MemoryWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Augmentrex.Memory 7 | { 8 | [Serializable] 9 | public sealed class MemoryWindow 10 | { 11 | public MemoryAddress Address { get; } 12 | 13 | public uint Length { get; } 14 | 15 | public MemoryWindow(MemoryAddress address, uint length) 16 | { 17 | Address = address; 18 | Length = length; 19 | } 20 | 21 | public MemoryAddress ToAddress(MemoryOffset offset) 22 | { 23 | return Address + offset; 24 | } 25 | 26 | public MemoryOffset ToOffset(MemoryAddress address) 27 | { 28 | return address - Address; 29 | } 30 | 31 | public bool IsInRange(MemoryAddress address) 32 | { 33 | return address >= Address && address < Address + (MemoryOffset)Length; 34 | } 35 | 36 | public bool IsInRange(MemoryOffset offset) 37 | { 38 | var offs = (int)offset; 39 | 40 | return offs >= 0 && offs < Length; 41 | } 42 | 43 | public MemoryOffset[] Search(params byte?[][] patterns) 44 | { 45 | bool IsMatch(byte?[] pattern, MemoryOffset offset) 46 | { 47 | for (var j = 0; j < pattern.Length; j++) 48 | { 49 | var b = pattern[j]; 50 | 51 | if (b != null && Read(offset + j) != b) 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | return (from i in Enumerable.Range(0, (int)Length) 59 | from p in patterns 60 | where i + p.Length < Length 61 | let o = (MemoryOffset)i 62 | where IsMatch(p, o) 63 | select o).Distinct().ToArray(); 64 | } 65 | 66 | public unsafe T Read(MemoryOffset offset) 67 | where T : unmanaged 68 | { 69 | return Unsafe.Read(ToAddress(offset)); 70 | } 71 | 72 | public MemoryOffset ReadOffset(MemoryOffset offset) 73 | { 74 | return ToOffset((MemoryAddress)Read(offset)); 75 | } 76 | 77 | public byte[] ReadBytes(MemoryOffset offset, int count) 78 | { 79 | var bytes = new byte[count]; 80 | 81 | Marshal.Copy(ToAddress(offset), bytes, 0, count); 82 | 83 | return bytes; 84 | } 85 | 86 | public T ReadStructure(MemoryOffset offset) 87 | where T : struct 88 | { 89 | return Marshal.PtrToStructure(ToAddress(offset)); 90 | } 91 | 92 | public unsafe void Write(MemoryOffset offset, T value) 93 | where T : unmanaged 94 | { 95 | Unsafe.Write(ToAddress(offset), value); 96 | } 97 | 98 | public void WriteOffset(MemoryOffset offset, MemoryOffset value) 99 | { 100 | Write(offset, (uint)ToAddress(value)); 101 | } 102 | 103 | public void WriteBytes(MemoryOffset offset, byte[] values) 104 | { 105 | Marshal.Copy(values, 0, ToAddress(offset), values.Length); 106 | } 107 | 108 | public void WriteStructure(MemoryOffset offset, T value) 109 | where T : struct 110 | { 111 | Marshal.StructureToPtr(value, ToAddress(offset), false); 112 | } 113 | 114 | public void DestroyStructure(MemoryOffset offset) 115 | where T : struct 116 | { 117 | Marshal.DestroyStructure(ToAddress(offset)); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Augmentrex.Commands.PatchCCAgent/PatchCCAgentCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Memory; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace Augmentrex.Commands.PatchCCAgent 8 | { 9 | public sealed class PatchCCAgentCommand : Command 10 | { 11 | public override IReadOnlyList Names { get; } = 12 | new[] { Assembly.GetExecutingAssembly().GetCustomAttribute().Title }; 13 | 14 | public override string Description { get; } = 15 | Assembly.GetExecutingAssembly().GetCustomAttribute().Description; 16 | 17 | public override IReadOnlyList Details => 18 | new[] 19 | { 20 | "Run the command again to undo the patch.", 21 | }; 22 | 23 | [UnmanagedFunctionPointer(CallingConvention.ThisCall)] 24 | delegate void ProcessCollisionFunc(IntPtr self, IntPtr body1, IntPtr body2, IntPtr input, IntPtr output); 25 | 26 | FunctionHook _hook; 27 | 28 | public override int? Run(AugmentrexContext context, string[] args) 29 | { 30 | if (_hook == null) 31 | { 32 | static void ProcessCollisionHook(IntPtr self, IntPtr body1, IntPtr body2, IntPtr input, IntPtr output) 33 | { 34 | } 35 | 36 | context.Info("Searching for function... "); 37 | 38 | _hook = context.CreateHook(new byte?[] 39 | { 40 | 0x55, // push ebp 41 | 0x8b, 0xec, // mov ebp, esp 42 | 0x83, 0xe4, 0xf0, // and esp, 0FFFFFFF0h 43 | 0xa1, null, null, null, null, // mov eax, 44 | 0x81, 0xec, 0xf4, 0x00, 0x00, 0x00, // sub esp, 0F4h 45 | 0x53, // push ebx 46 | 0x56, // push esi 47 | 0x57, // push edi 48 | 0x50, // push eax 49 | 0x8b, 0xd9, // mov ebx, ecx 50 | 0xff, 0x15, null, null, null, null, // call ds:TlsGetValue 51 | 0x8b, 0xc8, // mov ecx, eax 52 | 0x8b, 0x71, 0x04, // mov esi, [ecx+4] 53 | 0x3b, 0x71, 0x0c, // cmp esi, [ecx+0Ch] 54 | 0x73, null, // jnb 55 | 0xc7, 0x06, null, null, null, null, // mov dword ptr [esi], "TtCapsCaps" 56 | 0x0f, 0x31, // rdtsc 57 | 0x89, 0x44, 0x24, 0x14, // mov [esp+100h+var_EC], eax 58 | 0x8b, 0x54, 0x24, 0x14, // mov edx, [esp+100h+var_EC] 59 | 0x89, 0x56, 0x04, // mov [esi+4], edx 60 | }, "hkCapsuleCapsuleAgent::processCollision", ProcessCollisionHook); 61 | 62 | if (_hook == null) 63 | { 64 | context.ErrorLine("No matches. Was the game updated?"); 65 | 66 | return null; 67 | } 68 | else 69 | { 70 | var memory = context.Memory; 71 | 72 | context.SuccessLine("{0} at {1}{2}={3}.", _hook.Name, memory.Address, memory.ToOffset(_hook.Address), _hook.Address); 73 | } 74 | } 75 | 76 | _hook.Toggle(); 77 | 78 | context.InfoLine("Patch {0}installed successfully.", !_hook.IsInstalled ? "un" : string.Empty); 79 | 80 | return null; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Augmentrex/Memory/MemoryOffset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Augmentrex.Memory 4 | { 5 | [Serializable] 6 | public struct MemoryOffset : IEquatable, IComparable 7 | { 8 | public static readonly MemoryOffset MinValue = new MemoryOffset(int.MinValue); 9 | 10 | public static readonly MemoryOffset MaxValue = new MemoryOffset(int.MaxValue); 11 | 12 | readonly int _offset; 13 | 14 | public bool IsZero => this == MinValue; 15 | 16 | public bool IsNegative => _offset < 0; 17 | 18 | public MemoryOffset(int offset) 19 | { 20 | _offset = offset; 21 | } 22 | 23 | public MemoryOffset(uint offset) 24 | { 25 | _offset = (int)offset; 26 | } 27 | 28 | public static explicit operator MemoryOffset(int offset) 29 | { 30 | return new MemoryOffset(offset); 31 | } 32 | 33 | public static explicit operator MemoryOffset(uint offset) 34 | { 35 | return new MemoryOffset(offset); 36 | } 37 | 38 | public static explicit operator int(MemoryOffset offset) 39 | { 40 | return offset._offset; 41 | } 42 | 43 | public static explicit operator uint(MemoryOffset offset) 44 | { 45 | return (uint)offset._offset; 46 | } 47 | 48 | public static bool operator ==(MemoryOffset left, MemoryOffset right) 49 | { 50 | return left._offset == right._offset; 51 | } 52 | 53 | public static bool operator !=(MemoryOffset left, MemoryOffset right) 54 | { 55 | return left._offset != right._offset; 56 | } 57 | 58 | public static bool operator >(MemoryOffset left, MemoryOffset right) 59 | { 60 | return left._offset > right._offset; 61 | } 62 | 63 | public static bool operator <(MemoryOffset left, MemoryOffset right) 64 | { 65 | return left._offset < right._offset; 66 | } 67 | 68 | public static bool operator >=(MemoryOffset left, MemoryOffset right) 69 | { 70 | return left._offset >= right._offset; 71 | } 72 | 73 | public static bool operator <=(MemoryOffset left, MemoryOffset right) 74 | { 75 | return left._offset <= right._offset; 76 | } 77 | 78 | public static MemoryOffset operator +(MemoryOffset left, int right) 79 | { 80 | return new MemoryOffset(left._offset + right); 81 | } 82 | 83 | public static MemoryOffset operator +(MemoryOffset left, uint right) 84 | { 85 | return new MemoryOffset(left._offset + (int)right); 86 | } 87 | 88 | public static MemoryOffset operator -(MemoryOffset left, int right) 89 | { 90 | return new MemoryOffset(left._offset - right); 91 | } 92 | 93 | public static MemoryOffset operator -(MemoryOffset left, uint right) 94 | { 95 | return new MemoryOffset(left._offset - (int)right); 96 | } 97 | 98 | public override int GetHashCode() 99 | { 100 | return _offset.GetHashCode(); 101 | } 102 | 103 | public bool Equals(MemoryOffset other) 104 | { 105 | return this == other; 106 | } 107 | 108 | public override bool Equals(object obj) 109 | { 110 | return obj is MemoryOffset offset && this == offset; 111 | } 112 | 113 | public int CompareTo(MemoryOffset other) 114 | { 115 | return _offset.CompareTo(other._offset); 116 | } 117 | 118 | public override string ToString() 119 | { 120 | return _offset == 0 ? "+0x0" : 121 | IsNegative ? $"-0x{-_offset:x}" : $"+0x{_offset:x}"; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Augmentrex.Commands.PatchLongRayVM/PatchLongRayVMCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Memory; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace Augmentrex.Commands.PatchLongRayVM 8 | { 9 | public sealed class PatchLongRayVMCommand : Command 10 | { 11 | public override IReadOnlyList Names { get; } = 12 | new[] { Assembly.GetExecutingAssembly().GetCustomAttribute().Title }; 13 | 14 | public override string Description { get; } = 15 | Assembly.GetExecutingAssembly().GetCustomAttribute().Description; 16 | 17 | public override IReadOnlyList Details => 18 | new[] 19 | { 20 | "Run the command again to undo the patch.", 21 | }; 22 | 23 | [UnmanagedFunctionPointer(CallingConvention.ThisCall)] 24 | delegate void QueryRayOnTreeFunc(IntPtr self, IntPtr qint, IntPtr code, IntPtr qfloat); 25 | 26 | FunctionHook _hook; 27 | 28 | public override int? Run(AugmentrexContext context, string[] args) 29 | { 30 | if (_hook == null) 31 | { 32 | static void QueryRayOnTreeHook(IntPtr self, IntPtr qint, IntPtr code, IntPtr qfloat) 33 | { 34 | } 35 | 36 | context.Info("Searching for function... "); 37 | 38 | _hook = context.CreateHook(new byte?[] 39 | { 40 | 0x55, // push ebp 41 | 0x8b, 0xec, // mov ebp, esp 42 | 0x83, 0xe4, 0xf0, // and esp, 0FFFFFFF0h 43 | 0x6a, 0xff, // push 0FFFFFFFFh 44 | 0x68, null, null, null, null, // push 45 | 0x64, 0xa1, 0x00, 0x00, 0x00, 0x00, // mov eax, large fs:0 46 | 0x50, // push eax 47 | 0x81, 0xec, 0xe8, 0x04, 0x00, 0x00, // sub esp, 4E8h 48 | 0xa1, null, null, null, null, // mov eax, 49 | 0x33, 0xc4, // xor eax, esp 50 | 0x89, 0x84, 0x24, 0xe0, 0x04, 0x00, 0x00, // mov [esp+4F4h+var_14], eax 51 | 0x53, // push ebx 52 | 0x56, // push esi 53 | 0x57, // push edi 54 | 0xa1, null, null, null, null, // mov eax, 55 | 0x33, 0xc4, // xor eax, esp 56 | 0x50, // push eax 57 | 0x8d, 0x84, 0x24, 0xf8, 0x04, 0x00, 0x00, // lea eax, [esp+504h+var_C] 58 | 0x64, 0xa3, 0x00, 0x00, 0x00, 0x00, // mov large fs:0, eax 59 | 0x80, 0x3d, null, null, null, null, 0x00, // cmp _HK_flyingcolors_mopp, 0 60 | }, "hkMoppLongRayVirtualMachine::queryRayOnTree", QueryRayOnTreeHook); 61 | 62 | if (_hook == null) 63 | { 64 | context.ErrorLine("No matches. Was the game updated?"); 65 | 66 | return null; 67 | } 68 | else 69 | { 70 | var memory = context.Memory; 71 | 72 | context.SuccessLine("{0} at {1}{2}={3}.", _hook.Name, memory.Address, memory.ToOffset(_hook.Address), _hook.Address); 73 | } 74 | } 75 | 76 | _hook.Toggle(); 77 | 78 | context.InfoLine("Patch {0}installed successfully.", !_hook.IsInstalled ? "un" : string.Empty); 79 | 80 | return null; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/KeyCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Keyboard; 2 | using CommandLine; 3 | using System.Collections.Generic; 4 | using System.Windows.Forms; 5 | 6 | namespace Augmentrex.Commands.Core 7 | { 8 | sealed class KeyCommand : Command 9 | { 10 | sealed class KeyOptions 11 | { 12 | [Option("add", SetName = "operation")] 13 | public bool Add { get; set; } 14 | 15 | [Option("del", SetName = "operation")] 16 | public bool Delete { get; set; } 17 | 18 | [Option('a')] 19 | public bool Alt { get; set; } 20 | 21 | [Option('c')] 22 | public bool Control { get; set; } 23 | 24 | [Option('s')] 25 | public bool Shift { get; set; } 26 | 27 | [Value(0)] 28 | public Keys Key { get; set; } 29 | 30 | [Value(1)] 31 | public IEnumerable Fragments { get; set; } = new string[0]; 32 | } 33 | 34 | public override IReadOnlyList Names { get; } = 35 | new[] { "key" }; 36 | 37 | public override string Description => 38 | "Manipulates global hot key bindings."; 39 | 40 | public override string Syntax => 41 | "[[-a] [-c] [-s] (--add |--del )]"; 42 | 43 | public override IReadOnlyList Details { get; } = 44 | new[] 45 | { 46 | "If --add or --del is given, a key binding will be added or deleted, respectively. " + 47 | "If neither is given, all active key bindings are listed.", 48 | "The -a, -c, and -s options correspond to the Alt, Control, and Shift modifier keys, " + 49 | "respectively. They can be combined (or left unused) as needed.", 50 | }; 51 | 52 | readonly Dictionary _bindings = new Dictionary(); 53 | 54 | HotKeyHandler _handler; 55 | 56 | public override int? Run(AugmentrexContext context, string[] args) 57 | { 58 | var opts = Parse(context, args); 59 | 60 | if (opts == null) 61 | return null; 62 | 63 | if (!opts.Add && !opts.Delete) 64 | { 65 | foreach (var kvp in _bindings) 66 | context.InfoLine("{0} = {1}", kvp.Key, kvp.Value); 67 | 68 | return null; 69 | } 70 | 71 | var info = new HotKeyInfo(opts.Key, opts.Alt, opts.Control, opts.Shift); 72 | 73 | if (_handler == null) 74 | { 75 | void KeyHandler(HotKeyInfo info) 76 | { 77 | var freq = context.Configuration.HotKeyBeepFrequency; 78 | 79 | if (freq != 0) 80 | context.Ipc.Channel.Beep(freq, context.Configuration.HotKeyBeepDuration); 81 | 82 | if (_bindings.TryGetValue(info, out var command)) 83 | context.Interpreter.RunCommand(command, true); 84 | } 85 | 86 | _handler = new HotKeyHandler(KeyHandler); 87 | } 88 | 89 | if (opts.Add) 90 | { 91 | var command = string.Join(" ", opts.Fragments); 92 | 93 | if (string.IsNullOrWhiteSpace(command)) 94 | { 95 | context.ErrorLine("No command given."); 96 | 97 | return null; 98 | } 99 | 100 | if (_bindings.TryAdd(info, command)) 101 | { 102 | context.HotKeys.Add(info, _handler); 103 | 104 | context.InfoLine("Added key binding: {0} = {1}", info, command); 105 | } 106 | else 107 | context.ErrorLine("Key binding already exists: {0} = {1}", info, _bindings[info]); 108 | } 109 | 110 | if (opts.Delete) 111 | { 112 | if (_bindings.Remove(info, out var command)) 113 | context.InfoLine("Deleted key binding: {0} = {1}", info, command); 114 | else 115 | context.ErrorLine("Key binding not found: {0}", info); 116 | } 117 | 118 | return null; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/EvaluateCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Keyboard; 2 | using Augmentrex.Memory; 3 | using CommandLine; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.CSharp.Scripting; 6 | using Microsoft.CodeAnalysis.Scripting; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Threading.Tasks; 13 | 14 | namespace Augmentrex.Commands.Core 15 | { 16 | public sealed class EvaluateCommand : Command 17 | { 18 | public sealed class Globals 19 | { 20 | #pragma warning disable IDE1006 // Naming Styles 21 | public AugmentrexContext ctx { get; } 22 | 23 | public Process host { get; } 24 | 25 | public Process game { get; } 26 | 27 | public MemoryWindow mem { get; } 28 | 29 | public Configuration cfg { get; } 30 | 31 | public HotKeyRegistrar keys { get; } 32 | #pragma warning restore IDE1006 // Naming Styles 33 | 34 | internal Globals(AugmentrexContext context) 35 | { 36 | ctx = context; 37 | host = context.Host; 38 | game = context.Game; 39 | mem = context.Memory; 40 | cfg = context.Configuration; 41 | keys = context.HotKeys; 42 | } 43 | } 44 | 45 | sealed class EvaluateOptions 46 | { 47 | [Option('c')] 48 | public bool Clear { get; set; } 49 | 50 | [Value(0)] 51 | public IEnumerable Fragments { get; set; } 52 | } 53 | 54 | public override IReadOnlyList Names { get; } = 55 | new[] { "evaluate", "print" }; 56 | 57 | public override string Description => 58 | "Evaluates a given C# expression."; 59 | 60 | public override string Syntax => 61 | "[-c] [expr]"; 62 | 63 | public override IReadOnlyList Details { get; } = 64 | new[] 65 | { 66 | "If '-c' is given, the REPL session will be reset.", 67 | "The global variables " + 68 | string.Join(", ", typeof(Globals).GetTypeInfo().DeclaredProperties.Select(x => $"'{x.Name}'")) + 69 | " are implicitly defined for convenience.", 70 | }; 71 | 72 | Script _script; 73 | 74 | internal EvaluateCommand() 75 | { 76 | } 77 | 78 | public override int? Run(AugmentrexContext context, string[] args) 79 | { 80 | var opts = Parse(context, args); 81 | 82 | if (opts == null) 83 | return null; 84 | 85 | if (_script == null || opts.Clear) 86 | _script = CSharpScript.Create(string.Empty, 87 | ScriptOptions.Default.WithFilePath("") 88 | .WithImports(ScriptOptions.Default.Imports.AddRange( 89 | Assembly.GetExecutingAssembly().DefinedTypes.Select(x => x.Namespace).Where(x => x != null).Distinct())) 90 | .WithLanguageVersion(LanguageVersion.CSharp8) 91 | .WithReferences(Assembly.GetExecutingAssembly()), 92 | typeof(Globals)); 93 | 94 | var code = string.Join(" ", opts.Fragments); 95 | 96 | if (string.IsNullOrWhiteSpace(code)) 97 | return null; 98 | 99 | Script script = _script.ContinueWith(code); 100 | 101 | Task task; 102 | 103 | try 104 | { 105 | task = script.RunAsync(new Globals(context)); 106 | } 107 | catch (CompilationErrorException ex) 108 | { 109 | foreach (var diag in ex.Diagnostics) 110 | context.ErrorLine("{0}", diag.ToString()); 111 | 112 | return null; 113 | } 114 | 115 | try 116 | { 117 | task.Wait(); 118 | } 119 | catch (AggregateException ex) 120 | { 121 | context.ErrorLine("{0}", ex.InnerException); 122 | 123 | return null; 124 | } 125 | 126 | _script = script; 127 | 128 | var result = task.Result.ReturnValue; 129 | 130 | context.InfoLine("{0} it = {1}", result?.GetType() ?? typeof(object), result ?? "null"); 131 | 132 | return null; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/ReadCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Memory; 2 | using CommandLine; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace Augmentrex.Commands.Core 9 | { 10 | sealed class ReadCommand : Command 11 | { 12 | enum ReadType : byte 13 | { 14 | I8, 15 | U8, 16 | I16, 17 | U16, 18 | I32, 19 | U32, 20 | I64, 21 | U64, 22 | F32, 23 | F64, 24 | Ptr, 25 | } 26 | 27 | sealed class ReadOptions 28 | { 29 | [Option('a')] 30 | public bool Absolute { get; set; } 31 | 32 | [Option('t', Default = ReadType.I32)] 33 | public ReadType Type { get; set; } 34 | 35 | [Value(0, Required = true)] 36 | public string Offset { get; set; } 37 | } 38 | 39 | public override IReadOnlyList Names { get; } = 40 | new[] { "read" }; 41 | 42 | public override string Description => 43 | "Reads data from a given memory offset based on the main module."; 44 | 45 | public override string Syntax => 46 | "[-a] [-t ] "; 47 | 48 | public override IReadOnlyList Details { get; } = 49 | new[] 50 | { 51 | "If '-a' is given, 'offset' is interpreted as an absolute address instead.", 52 | "If '-t' is given, the value will be read as the given 'type', which must be one of " + 53 | string.Join(", ", typeof(ReadType).GetEnumNames().Select(x => $"'{x.ToLower()}'")) + 54 | " (defaults to 'i32').", 55 | }; 56 | 57 | public unsafe override int? Run(AugmentrexContext context, string[] args) 58 | { 59 | var opts = Parse(context, args); 60 | 61 | if (opts == null) 62 | return null; 63 | 64 | var parsed = (int)TypeDescriptor.GetConverter(typeof(int)).ConvertFromString(opts.Offset); 65 | var memory = context.Memory; 66 | var offset = opts.Absolute ? memory.ToOffset((MemoryAddress)parsed) : (MemoryOffset)parsed; 67 | 68 | switch (opts.Type) 69 | { 70 | case ReadType.I8: 71 | var i8 = memory.Read(offset); 72 | context.InfoLine("{0} (0x{0:X})", i8); 73 | break; 74 | case ReadType.U8: 75 | var u8 = memory.Read(offset); 76 | context.InfoLine("{0} (0x{0:X})", u8); 77 | break; 78 | case ReadType.I16: 79 | var i16 = memory.Read(offset); 80 | context.InfoLine("{0} (0x{0:X})", i16); 81 | break; 82 | case ReadType.U16: 83 | var u16 = memory.Read(offset); 84 | context.InfoLine("{0} (0x{0:X})", u16); 85 | break; 86 | case ReadType.I32: 87 | var i32 = memory.Read(offset); 88 | context.InfoLine("{0} (0x{0:X})", i32); 89 | break; 90 | case ReadType.U32: 91 | var u32 = memory.Read(offset); 92 | context.InfoLine("{0} (0x{0:X})", u32); 93 | break; 94 | case ReadType.I64: 95 | var i64 = memory.Read(offset); 96 | context.InfoLine("{0} (0x{0:X})", i64); 97 | break; 98 | case ReadType.U64: 99 | var u64 = memory.Read(offset); 100 | context.InfoLine("{0} (0x{0:X})", u64); 101 | break; 102 | case ReadType.F32: 103 | var f32 = memory.Read(offset); 104 | context.InfoLine("{0} (0x{0:X})", f32, Unsafe.Read(&f32)); 105 | break; 106 | case ReadType.F64: 107 | var f64 = memory.Read(offset); 108 | context.InfoLine("{0} (0x{0:X})", f64, Unsafe.Read(&f64)); 109 | break; 110 | case ReadType.Ptr: 111 | var ptr = memory.ReadOffset(offset); 112 | context.InfoLine("{0}{1}={2}", memory.Address, ptr, memory.Address + ptr); 113 | break; 114 | } 115 | 116 | return null; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Augmentrex/Ipc/IpcChannel.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Commands; 2 | using EasyHook; 3 | using System; 4 | using System.Runtime.Remoting; 5 | using System.Threading; 6 | 7 | namespace Augmentrex.Ipc 8 | { 9 | sealed class IpcChannel : MarshalByRefObject 10 | { 11 | readonly ManualResetEventSlim _keepAlive = new ManualResetEventSlim(); 12 | 13 | int _counter; 14 | 15 | public Configuration Configuration { get; } 16 | 17 | public int? ExitCode { get; set; } 18 | 19 | public bool KillRequested { get; set; } 20 | 21 | public IpcChannel() 22 | { 23 | Configuration = Configuration.Instance; 24 | 25 | CommandInterpreter.LoadCommands(true); 26 | ReadLine.AutoCompletionHandler = new CommandInterpreter.CommandAutoCompletionHandler(); 27 | } 28 | 29 | public static string Create() 30 | { 31 | string name = null; 32 | 33 | RemoteHooking.IpcCreateServer(ref name, WellKnownObjectMode.Singleton); 34 | 35 | return name; 36 | } 37 | 38 | public static IpcChannel Connect(string name) 39 | { 40 | return RemoteHooking.IpcConnectClient(name); 41 | } 42 | 43 | public override object InitializeLifetimeService() 44 | { 45 | return null; 46 | } 47 | 48 | public void Ping() 49 | { 50 | } 51 | 52 | public bool Wait(TimeSpan timeout) 53 | { 54 | return _keepAlive.Wait(timeout); 55 | } 56 | 57 | public void KeepAlive() 58 | { 59 | _keepAlive.Set(); 60 | } 61 | 62 | public void Reset() 63 | { 64 | _keepAlive.Reset(); 65 | } 66 | 67 | public void Info(string format, params object[] args) 68 | { 69 | Log.Info(format, args); 70 | } 71 | 72 | public void InfoLine(string format, params object[] args) 73 | { 74 | Log.InfoLine(format, args); 75 | } 76 | 77 | public void Warning(string format, params object[] args) 78 | { 79 | Log.Warning(format, args); 80 | } 81 | 82 | public void WarningLine(string format, params object[] args) 83 | { 84 | Log.WarningLine(format, args); 85 | } 86 | 87 | public void Error(string format, params object[] args) 88 | { 89 | Log.Error(format, args); 90 | } 91 | 92 | public void ErrorLine(string format, params object[] args) 93 | { 94 | Log.ErrorLine(format, args); 95 | } 96 | 97 | public void Success(string format, params object[] args) 98 | { 99 | Log.Success(format, args); 100 | } 101 | 102 | public void SuccessLine(string format, params object[] args) 103 | { 104 | Log.SuccessLine(format, args); 105 | } 106 | 107 | public void Debug(string format, params object[] args) 108 | { 109 | Log.Debug(format, args); 110 | } 111 | 112 | public void DebugLine(string format, params object[] args) 113 | { 114 | Log.DebugLine(format, args); 115 | } 116 | 117 | public void Color(ConsoleColor color, string format, params object[] args) 118 | { 119 | Log.Color(color, format, args); 120 | } 121 | 122 | public void ColorLine(ConsoleColor color, string format, params object[] args) 123 | { 124 | Log.ColorLine(color, format, args); 125 | } 126 | 127 | public void Line() 128 | { 129 | Log.Line(); 130 | } 131 | 132 | public void PrintPrompt() 133 | { 134 | _counter++; 135 | 136 | Log.Color(ConsoleColor.Cyan, "hgl({0})> ", _counter); 137 | } 138 | 139 | public string ReadPrompt() 140 | { 141 | PrintPrompt(); 142 | 143 | string str; 144 | 145 | try 146 | { 147 | str = ReadLine.Read(); 148 | } 149 | catch (Exception) 150 | { 151 | return null; 152 | } 153 | 154 | if (!string.IsNullOrWhiteSpace(str)) 155 | ReadLine.AddHistory(str); 156 | 157 | return str; 158 | } 159 | 160 | public void Clear() 161 | { 162 | Console.Clear(); 163 | } 164 | 165 | public void Beep(int frequency, TimeSpan duration) 166 | { 167 | Console.Beep(frequency, (int)duration.TotalMilliseconds); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Augmentrex/Program.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Ipc; 2 | using EasyHook; 3 | using Microsoft.Win32; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text.RegularExpressions; 11 | 12 | namespace Augmentrex 13 | { 14 | static class Program 15 | { 16 | static int Main() 17 | { 18 | AppDomain.CurrentDomain.UnhandledException += (sender, e) => 19 | { 20 | Log.Line(); 21 | Log.ErrorLine("Host assembly crashed: {0}", e.ExceptionObject); 22 | Console.ReadLine(); 23 | }; 24 | 25 | var asm = Assembly.GetExecutingAssembly(); 26 | var asmName = asm.GetName(); 27 | var name = $"{asmName.Name} {asmName.Version}"; 28 | 29 | Console.Title = $"{name} - Host Process"; 30 | 31 | Log.InfoLine("{0} initializing...", name); 32 | 33 | var path = GetExecutablePath(); 34 | 35 | if (path == null) 36 | { 37 | Log.ErrorLine("Could not find Hellgate: London executable."); 38 | return 1; 39 | } 40 | 41 | var chanName = IpcChannel.Create(); 42 | 43 | Log.InfoLine("Setting up IPC channel '{0}'...", chanName); 44 | 45 | var chan = IpcChannel.Connect(chanName); 46 | 47 | chan.Ping(); 48 | 49 | Log.InfoLine("Launching '{0}'...", path); 50 | 51 | var location = asm.Location; 52 | 53 | RemoteHooking.CreateAndInject(path, Configuration.Instance.GameArguments, 0, location, location, out var pid, chanName); 54 | 55 | using var proc = Process.GetProcesses().FirstOrDefault(x => x.Id == pid); 56 | 57 | if (proc == null) 58 | { 59 | Log.ErrorLine("Could not locate Hellgate: London process ({0}).", pid); 60 | return 1; 61 | } 62 | 63 | while (chan.Wait(Configuration.Instance.IpcTimeout)) 64 | { 65 | if (chan.ExitCode is int c) 66 | { 67 | Log.SuccessLine("Exiting..."); 68 | 69 | if (chan.KillRequested) 70 | proc.Kill(); 71 | 72 | return c; 73 | } 74 | 75 | chan.Reset(); 76 | } 77 | 78 | Log.Line(); 79 | Log.ErrorLine("Lost connection to the game. Exiting..."); 80 | Console.ReadLine(); 81 | 82 | return 1; 83 | } 84 | 85 | static string GetExecutablePath() 86 | { 87 | var cfg = Configuration.Instance.GamePath; 88 | 89 | if (!string.IsNullOrWhiteSpace(cfg)) 90 | return cfg; 91 | 92 | string steam = null; 93 | 94 | using (var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Valve\Steam")) 95 | steam = (string)key?.GetValue("InstallPath"); 96 | 97 | if (steam == null) 98 | return null; 99 | 100 | var steamApps = Path.Combine(steam, "steamapps"); 101 | var libraryFolders = Path.Combine(steamApps, "libraryfolders.vdf"); 102 | var libraryDirs = new List 103 | { 104 | steamApps, 105 | }; 106 | 107 | if (File.Exists(libraryFolders)) 108 | { 109 | foreach (var line in File.ReadAllLines(libraryFolders)) 110 | { 111 | var match = Regex.Match(line, "^\\s*\"\\d*\"\\s*\"(?\\S*)\""); 112 | 113 | if (match.Success) 114 | libraryDirs.Add(match.Groups["path"].Value); 115 | } 116 | } 117 | 118 | foreach (var library in libraryDirs) 119 | { 120 | var manifest = Path.Combine(library, "appmanifest_939520.acf"); 121 | 122 | if (!File.Exists(manifest)) 123 | continue; 124 | 125 | foreach (var line in File.ReadAllLines(manifest)) 126 | { 127 | var match = Regex.Match(line, "^\\s*\"installdir\"\\s*\"(?\\S*)\""); 128 | 129 | if (match.Success) 130 | { 131 | var path = Path.Combine(library, "common", match.Groups["path"].Value, "bin", "Hellgate_sp_x86.exe"); 132 | 133 | if (File.Exists(path)) 134 | return path; 135 | } 136 | } 137 | } 138 | 139 | return null; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Augmentrex/Commands/CommandInterpreter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Augmentrex.Commands 8 | { 9 | sealed class CommandInterpreter 10 | { 11 | public sealed class CommandAutoCompletionHandler : IAutoCompleteHandler 12 | { 13 | public char[] Separators { get; set; } = new[] { ' ' }; 14 | 15 | public string[] GetSuggestions(string text, int index) 16 | { 17 | return Commands.SelectMany(x => x.Names).Where(y => MatchCommand(y, text)).ToArray(); 18 | } 19 | } 20 | 21 | static readonly List _commands = new List(); 22 | 23 | public static IReadOnlyCollection Commands => _commands; 24 | 25 | readonly object _lock = new object(); 26 | 27 | public AugmentrexContext Context { get; } 28 | 29 | public CommandInterpreter(AugmentrexContext context) 30 | { 31 | Context = context; 32 | } 33 | 34 | public static void LoadCommands(bool informational) 35 | { 36 | var exe = Assembly.GetExecutingAssembly(); 37 | var asms = Directory.EnumerateFiles(Path.GetDirectoryName(exe.Location), "augmentrex-command-*.dll") 38 | .Select(x => Assembly.UnsafeLoadFrom(x)) 39 | .Concat(new[] { exe }); 40 | 41 | static Command InstantiateCommand(Type type, bool informational) 42 | { 43 | const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; 44 | 45 | var ctor1 = type.GetConstructor(Flags, null, new[] { typeof(bool) }, null); 46 | var ctor2 = type.GetConstructor(Flags, null, Type.EmptyTypes, null); 47 | 48 | return (Command)(ctor1 != null ? ctor1.Invoke(new object[] { informational }) : ctor2.Invoke(null)); 49 | } 50 | 51 | foreach (var asm in asms) 52 | foreach (var type in asm.DefinedTypes.Where(x => x.BaseType == typeof(Command))) 53 | _commands.Add(InstantiateCommand(type, informational)); 54 | 55 | _commands.Sort((a, b) => string.Compare(a.GetType().Name, b.GetType().Name, StringComparison.InvariantCulture)); 56 | } 57 | 58 | public static Command GetCommand(string name) 59 | { 60 | return Commands.Where(x => x.Names.Any(y => MatchCommand(y, name))).FirstOrDefault(); 61 | } 62 | 63 | public bool RunCommand(string command, bool async) 64 | { 65 | if (string.IsNullOrWhiteSpace(command)) 66 | return true; 67 | 68 | var args = command.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); 69 | var name = args[0]; 70 | var cmd = GetCommand(name); 71 | 72 | lock (_lock) 73 | { 74 | var chan = Context.Ipc.Channel; 75 | 76 | if (async) 77 | { 78 | Context.Line(); 79 | chan.PrintPrompt(); 80 | chan.ColorLine(Console.ForegroundColor, "{0}", command); 81 | } 82 | 83 | using var _ = async ? new Finally(() => chan.PrintPrompt()) : default; 84 | 85 | if (cmd == null) 86 | { 87 | Context.ErrorLine("Unknown command '{0}'.", name); 88 | 89 | return true; 90 | } 91 | 92 | int? code = null; 93 | 94 | try 95 | { 96 | code = cmd.Run(Context, args.Skip(1).ToArray()); 97 | } 98 | catch (Exception ex) 99 | { 100 | Context.ErrorLine("Command '{0}' failed: {1}", name, ex); 101 | } 102 | 103 | if (code is int c) 104 | { 105 | chan.ExitCode = c; 106 | chan.KeepAlive(); 107 | 108 | return false; 109 | } 110 | 111 | return true; 112 | } 113 | } 114 | 115 | public void RunLoop() 116 | { 117 | while (true) 118 | { 119 | var chan = Context.Ipc.Channel; 120 | var str = chan.ReadPrompt(); 121 | 122 | if (str == null || chan.ExitCode != null || !RunCommand(str, false)) 123 | break; 124 | } 125 | } 126 | 127 | static bool MatchCommand(string command, string prefix) 128 | { 129 | return command.StartsWith(prefix, StringComparison.CurrentCultureIgnoreCase); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Augmentrex/Memory/MemoryAddress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Augmentrex.Memory 4 | { 5 | [Serializable] 6 | public unsafe struct MemoryAddress : IEquatable, IComparable 7 | { 8 | public static readonly MemoryAddress MinValue = new MemoryAddress(uint.MinValue); 9 | 10 | public static readonly MemoryAddress MaxValue = new MemoryAddress(uint.MaxValue); 11 | 12 | readonly uint _address; 13 | 14 | public bool IsNull => this == MinValue; 15 | 16 | public MemoryAddress(void* address) 17 | { 18 | _address = (uint)address; 19 | } 20 | 21 | public MemoryAddress(int address) 22 | { 23 | _address = (uint)address; 24 | } 25 | 26 | public MemoryAddress(uint address) 27 | { 28 | _address = address; 29 | } 30 | 31 | public MemoryAddress(IntPtr address) 32 | { 33 | _address = (uint)address; 34 | } 35 | 36 | public MemoryAddress(UIntPtr address) 37 | { 38 | _address = (uint)address; 39 | } 40 | 41 | public static implicit operator MemoryAddress(void* address) 42 | { 43 | return new MemoryAddress(address); 44 | } 45 | 46 | public static implicit operator MemoryAddress(IntPtr address) 47 | { 48 | return new MemoryAddress(address); 49 | } 50 | 51 | public static implicit operator MemoryAddress(UIntPtr address) 52 | { 53 | return new MemoryAddress(address); 54 | } 55 | 56 | public static explicit operator MemoryAddress(int address) 57 | { 58 | return new MemoryAddress(address); 59 | } 60 | 61 | public static explicit operator MemoryAddress(uint address) 62 | { 63 | return new MemoryAddress(address); 64 | } 65 | 66 | public static implicit operator void*(MemoryAddress address) 67 | { 68 | return (void*)address._address; 69 | } 70 | 71 | public static implicit operator IntPtr(MemoryAddress address) 72 | { 73 | return (IntPtr)address._address; 74 | } 75 | 76 | public static implicit operator UIntPtr(MemoryAddress address) 77 | { 78 | return (UIntPtr)address._address; 79 | } 80 | 81 | public static explicit operator int(MemoryAddress address) 82 | { 83 | return (int)address._address; 84 | } 85 | 86 | public static explicit operator uint(MemoryAddress address) 87 | { 88 | return address._address; 89 | } 90 | 91 | public static bool operator true(MemoryAddress address) 92 | { 93 | return !address.IsNull; 94 | } 95 | 96 | public static bool operator false(MemoryAddress address) 97 | { 98 | return address.IsNull; 99 | } 100 | 101 | public static bool operator ==(MemoryAddress left, MemoryAddress right) 102 | { 103 | return left._address == right._address; 104 | } 105 | 106 | public static bool operator !=(MemoryAddress left, MemoryAddress right) 107 | { 108 | return !(left == right); 109 | } 110 | 111 | public static bool operator >(MemoryAddress left, MemoryAddress right) 112 | { 113 | return left._address > right._address; 114 | } 115 | 116 | public static bool operator <(MemoryAddress left, MemoryAddress right) 117 | { 118 | return left._address < right._address; 119 | } 120 | 121 | public static bool operator >=(MemoryAddress left, MemoryAddress right) 122 | { 123 | return left._address >= right._address; 124 | } 125 | 126 | public static bool operator <=(MemoryAddress left, MemoryAddress right) 127 | { 128 | return left._address <= right._address; 129 | } 130 | 131 | public static MemoryAddress operator +(MemoryAddress left, MemoryOffset right) 132 | { 133 | return new MemoryAddress(left._address + (uint)right); 134 | } 135 | 136 | public static MemoryAddress operator -(MemoryAddress left, MemoryOffset right) 137 | { 138 | return new MemoryAddress(left._address - (uint)right); 139 | } 140 | 141 | public static MemoryOffset operator -(MemoryAddress left, MemoryAddress right) 142 | { 143 | return new MemoryOffset(left._address - right._address); 144 | } 145 | 146 | public override int GetHashCode() 147 | { 148 | return _address.GetHashCode(); 149 | } 150 | 151 | public bool Equals(MemoryAddress other) 152 | { 153 | return this == other; 154 | } 155 | 156 | public override bool Equals(object obj) 157 | { 158 | return obj is MemoryAddress addr && this == addr; 159 | } 160 | 161 | public int CompareTo(MemoryAddress other) 162 | { 163 | return _address.CompareTo(other._address); 164 | } 165 | 166 | public override string ToString() 167 | { 168 | return $"0x{_address:x}"; 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Augmentrex/Commands/Core/WriteCommand.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Memory; 2 | using CommandLine; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | 7 | namespace Augmentrex.Commands.Core 8 | { 9 | sealed class WriteCommand : Command 10 | { 11 | enum WriteType : byte 12 | { 13 | I8, 14 | U8, 15 | I16, 16 | U16, 17 | I32, 18 | U32, 19 | I64, 20 | U64, 21 | F32, 22 | F64, 23 | Ptr, 24 | } 25 | 26 | sealed class WriteOptions 27 | { 28 | [Option('a')] 29 | public bool Absolute { get; set; } 30 | 31 | [Option('t', Default = WriteType.I32)] 32 | public WriteType Type { get; set; } 33 | 34 | [Value(0, Required = true)] 35 | public string Offset { get; set; } 36 | 37 | [Value(1, Required = true)] 38 | public string Value { get; set; } 39 | } 40 | 41 | public override IReadOnlyList Names { get; } = 42 | new[] { "write" }; 43 | 44 | public override string Description => 45 | "Writes data to a given memory offset based on the main module."; 46 | 47 | public override string Syntax => 48 | "[-a] [-t ] "; 49 | 50 | public override IReadOnlyList Details { get; } = 51 | new[] 52 | { 53 | "If '-a' is given, 'offset' is interpreted as an absolute address instead.", 54 | "If '-t' is given, the value will be written as the given 'type', which must be one of " + 55 | string.Join(", ", typeof(WriteType).GetEnumNames().Select(x => $"'{x.ToLower()}'")) + 56 | " (defaults to 'i32').", 57 | }; 58 | 59 | public override int? Run(AugmentrexContext context, string[] args) 60 | { 61 | var opts = Parse(context, args); 62 | 63 | if (opts == null) 64 | return null; 65 | 66 | var parsed = (int)TypeDescriptor.GetConverter(typeof(int)).ConvertFromString(opts.Offset); 67 | 68 | long ivalue = 0; 69 | ulong uvalue = 0; 70 | var fvalue = 0.0f; 71 | var dvalue = 0.0d; 72 | var ovalue = default(MemoryOffset); 73 | 74 | switch (opts.Type) 75 | { 76 | case WriteType.I8: 77 | case WriteType.I16: 78 | case WriteType.I32: 79 | case WriteType.I64: 80 | ivalue = (long)TypeDescriptor.GetConverter(typeof(long)).ConvertFromString(opts.Value); 81 | break; 82 | case WriteType.U8: 83 | case WriteType.U16: 84 | case WriteType.U32: 85 | case WriteType.U64: 86 | uvalue = (ulong)TypeDescriptor.GetConverter(typeof(ulong)).ConvertFromString(opts.Value); 87 | break; 88 | case WriteType.F32: 89 | fvalue = (float)TypeDescriptor.GetConverter(typeof(float)).ConvertFromString(opts.Value); 90 | break; 91 | case WriteType.F64: 92 | dvalue = (double)TypeDescriptor.GetConverter(typeof(double)).ConvertFromString(opts.Value); 93 | break; 94 | case WriteType.Ptr: 95 | ovalue = (MemoryOffset)(int)TypeDescriptor.GetConverter(typeof(int)).ConvertFromString(opts.Value); 96 | break; 97 | } 98 | 99 | var memory = context.Memory; 100 | var offset = opts.Absolute ? memory.ToOffset((MemoryAddress)parsed) : (MemoryOffset)parsed; 101 | 102 | switch (opts.Type) 103 | { 104 | case WriteType.I8: 105 | memory.Write(offset, (sbyte)ivalue); 106 | break; 107 | case WriteType.U8: 108 | memory.Write(offset, (byte)uvalue); 109 | break; 110 | case WriteType.I16: 111 | memory.Write(offset, (short)ivalue); 112 | break; 113 | case WriteType.U16: 114 | memory.Write(offset, (ushort)uvalue); 115 | break; 116 | case WriteType.I32: 117 | memory.Write(offset, (int)ivalue); 118 | break; 119 | case WriteType.U32: 120 | memory.Write(offset, (uint)uvalue); 121 | break; 122 | case WriteType.I64: 123 | memory.Write(offset, ivalue); 124 | break; 125 | case WriteType.U64: 126 | memory.Write(offset, uvalue); 127 | break; 128 | case WriteType.F32: 129 | memory.Write(offset, fvalue); 130 | break; 131 | case WriteType.F64: 132 | memory.Write(offset, dvalue); 133 | break; 134 | case WriteType.Ptr: 135 | memory.WriteOffset(offset, ovalue); 136 | break; 137 | } 138 | 139 | return null; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Augmentrex/Augmentrex.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x86 7 | true 8 | ..\Augmentrex.ico 9 | Properties\App.manifest 10 | Augmentrex 11 | true 12 | embedded 13 | DEBUG;TRACE 14 | true 15 | 8.0 16 | ..\Build\ 17 | Exe 18 | true 19 | {6E5B1E1C-5C31-444F-906D-C1C04DB0CB5F} 20 | Augmentrex 21 | Augmentrex.Program 22 | v4.7.2 23 | true 24 | 25 | 26 | x86 27 | 28 | 29 | 30 | Properties\SolutionInfo.cs 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | ..\Augmentrex.ico 71 | 72 | 73 | 74 | 75 | 76 | 77 | PreserveNewest 78 | false 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 2.5.0 89 | 90 | 91 | 2.7.6789 92 | 93 | 94 | 1.3.0 95 | 96 | 97 | 3.1.0-beta3-final 98 | 99 | 100 | 2.0.1 101 | 102 | 103 | 1.1.11 104 | 105 | 106 | 4.5.2 107 | 108 | 109 | 2.3.8 110 | 111 | 112 | 2.3.8 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Augmentrex/Keyboard/HotKeyRegistrar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using Vanara.PInvoke; 8 | 9 | namespace Augmentrex.Keyboard 10 | { 11 | public sealed class HotKeyRegistrar : MarshalByRefObject, IDisposable 12 | { 13 | sealed class MessageWindow : NativeWindow, IDisposable 14 | { 15 | readonly HotKeyRegistrar _registrar; 16 | 17 | bool _disposed; 18 | 19 | public MessageWindow(HotKeyRegistrar registrar) 20 | { 21 | _registrar = registrar; 22 | 23 | CreateHandle(new CreateParams()); 24 | } 25 | 26 | ~MessageWindow() 27 | { 28 | RealDispose(); 29 | } 30 | 31 | public void Dispose() 32 | { 33 | RealDispose(); 34 | } 35 | 36 | void RealDispose() 37 | { 38 | if (_disposed) 39 | return; 40 | 41 | _disposed = true; 42 | 43 | DestroyHandle(); 44 | } 45 | 46 | protected override void WndProc(ref Message m) 47 | { 48 | const int HotKeyMessage = 0x312; 49 | 50 | if (m.Msg == HotKeyMessage) 51 | { 52 | var key = (Keys)(((int)m.LParam >> 16) & 0xffff); 53 | var mods = (User32.HotKeyModifiers)((int)m.LParam & 0xffff); 54 | 55 | _registrar.Invoke(new HotKeyInfo(key, 56 | mods.HasFlag(User32.HotKeyModifiers.MOD_ALT), 57 | mods.HasFlag(User32.HotKeyModifiers.MOD_CONTROL), 58 | mods.HasFlag(User32.HotKeyModifiers.MOD_SHIFT))); 59 | } 60 | 61 | base.WndProc(ref m); 62 | } 63 | } 64 | 65 | readonly CancellationTokenSource _cts; 66 | 67 | readonly Task _task; 68 | 69 | readonly ConcurrentQueue _newInfo = new ConcurrentQueue(); 70 | 71 | readonly HashSet _registry = new HashSet(); 72 | 73 | MessageWindow _window; 74 | 75 | bool _disposed; 76 | 77 | internal HotKeyRegistrar() 78 | { 79 | _cts = new CancellationTokenSource(); 80 | _task = Task.Factory.StartNew(() => 81 | { 82 | _window = new MessageWindow(this); 83 | 84 | while (true) 85 | { 86 | _cts.Token.ThrowIfCancellationRequested(); 87 | 88 | Application.Run(); 89 | 90 | while (_newInfo.TryDequeue(out var info)) 91 | { 92 | var mods = User32.HotKeyModifiers.MOD_NOREPEAT; 93 | 94 | if (info.Alt) 95 | mods |= User32.HotKeyModifiers.MOD_ALT; 96 | 97 | if (info.Control) 98 | mods |= User32.HotKeyModifiers.MOD_CONTROL; 99 | 100 | if (info.Shift) 101 | mods |= User32.HotKeyModifiers.MOD_SHIFT; 102 | 103 | User32.RegisterHotKey(_window.Handle, info.Id, mods, (uint)info.Key); 104 | } 105 | } 106 | }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); 107 | } 108 | 109 | ~HotKeyRegistrar() 110 | { 111 | RealDispose(); 112 | } 113 | 114 | void IDisposable.Dispose() 115 | { 116 | RealDispose(); 117 | GC.SuppressFinalize(this); 118 | } 119 | 120 | void RealDispose() 121 | { 122 | if (_disposed) 123 | return; 124 | 125 | _disposed = true; 126 | 127 | _cts?.Cancel(); 128 | Application.Exit(); 129 | _task?.Wait(); 130 | _task?.Dispose(); 131 | 132 | if (_window != null) 133 | { 134 | if (_registry != null) 135 | foreach (var info in _registry) 136 | User32.UnregisterHotKey(_window.Handle, info.Id); 137 | 138 | _window.Dispose(); 139 | } 140 | } 141 | 142 | public override object InitializeLifetimeService() 143 | { 144 | return null; 145 | } 146 | 147 | public bool Add(HotKeyInfo info, HotKeyHandler handler) 148 | { 149 | if (!_registry.TryGetValue(info, out var existing)) 150 | { 151 | info.Id = Kernel32.GlobalAddAtom(info.ToString()); 152 | 153 | _registry.Add(info); 154 | _newInfo.Enqueue(info); 155 | Application.Exit(); 156 | } 157 | else 158 | info = existing; 159 | 160 | return info.Handlers.Add(handler); 161 | } 162 | 163 | public bool Remove(HotKeyInfo info, HotKeyHandler handler) 164 | { 165 | return _registry.TryGetValue(info, out info) ? info.Handlers.Remove(handler) : false; 166 | } 167 | 168 | void Invoke(HotKeyInfo info) 169 | { 170 | if (_registry.TryGetValue(info, out info)) 171 | foreach (var handler in info.Handlers) 172 | handler.Invoke(info); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Augmentrex/AugmentrexContext.cs: -------------------------------------------------------------------------------- 1 | using Augmentrex.Commands; 2 | using Augmentrex.Ipc; 3 | using Augmentrex.Keyboard; 4 | using Augmentrex.Memory; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | using System.Runtime.InteropServices; 10 | 11 | namespace Augmentrex 12 | { 13 | public sealed class AugmentrexContext : IDisposable 14 | { 15 | public Process Host { get; } 16 | 17 | public Process Game { get; } 18 | 19 | public MemoryWindow Memory { get; } 20 | 21 | internal IpcBridge Ipc { get; } 22 | 23 | public Configuration Configuration => Ipc.Configuration; 24 | 25 | public HotKeyRegistrar HotKeys { get; } 26 | 27 | internal CommandInterpreter Interpreter { get; } 28 | 29 | readonly ConsoleWindow _console; 30 | 31 | readonly DebugListener _debug; 32 | 33 | readonly HashSet _hooks = new HashSet(); 34 | 35 | readonly bool _shallow; 36 | 37 | bool _disposed; 38 | 39 | internal AugmentrexContext(bool shallow, Process host, Process game, string channelName, HotKeyRegistrar hotKeys) 40 | { 41 | Host = host; 42 | Game = game; 43 | 44 | var mod = game.MainModule; 45 | 46 | Memory = new MemoryWindow(mod.BaseAddress, (uint)mod.ModuleMemorySize); 47 | Ipc = new IpcBridge(channelName, shallow); 48 | HotKeys = hotKeys ?? new HotKeyRegistrar(); 49 | 50 | if (!shallow) 51 | { 52 | Interpreter = new CommandInterpreter(this); 53 | _console = Configuration.GameConsoleEnabled ? new ConsoleWindow(this) : null; 54 | _debug = Configuration.DebugListenerEnabled ? new DebugListener(this) : null; 55 | } 56 | 57 | _shallow = shallow; 58 | } 59 | 60 | ~AugmentrexContext() 61 | { 62 | RealDispose(); 63 | } 64 | 65 | void IDisposable.Dispose() 66 | { 67 | RealDispose(); 68 | GC.SuppressFinalize(this); 69 | } 70 | 71 | void RealDispose() 72 | { 73 | if (_disposed) 74 | return; 75 | 76 | _disposed = true; 77 | 78 | if (_hooks != null) 79 | foreach (var hook in _hooks) 80 | hook.Dispose(); 81 | 82 | _debug?.Dispose(); 83 | _console?.Dispose(); 84 | 85 | if (!_shallow) 86 | ((IDisposable)HotKeys)?.Dispose(); 87 | 88 | Ipc?.Dispose(); 89 | Game?.Dispose(); 90 | Host?.Dispose(); 91 | } 92 | 93 | public void Info(string format, params object[] args) 94 | { 95 | Ipc.Channel.Info(format, args); 96 | } 97 | 98 | public void InfoLine(string format, params object[] args) 99 | { 100 | Ipc.Channel.InfoLine(format, args); 101 | } 102 | 103 | public void Warning(string format, params object[] args) 104 | { 105 | Ipc.Channel.Warning(format, args); 106 | } 107 | 108 | public void WarningLine(string format, params object[] args) 109 | { 110 | Ipc.Channel.WarningLine(format, args); 111 | } 112 | 113 | public void Error(string format, params object[] args) 114 | { 115 | Ipc.Channel.Error(format, args); 116 | } 117 | 118 | public void ErrorLine(string format, params object[] args) 119 | { 120 | Ipc.Channel.ErrorLine(format, args); 121 | } 122 | 123 | public void Success(string format, params object[] args) 124 | { 125 | Ipc.Channel.Success(format, args); 126 | } 127 | 128 | public void SuccessLine(string format, params object[] args) 129 | { 130 | Ipc.Channel.SuccessLine(format, args); 131 | } 132 | 133 | public void Debug(string format, params object[] args) 134 | { 135 | Ipc.Channel.Debug(format, args); 136 | } 137 | 138 | public void DebugLine(string format, params object[] args) 139 | { 140 | Ipc.Channel.DebugLine(format, args); 141 | } 142 | 143 | public void Color(ConsoleColor color, string format, params object[] args) 144 | { 145 | Ipc.Channel.Color(color, format, args); 146 | } 147 | 148 | public void ColorLine(ConsoleColor color, string format, params object[] args) 149 | { 150 | Ipc.Channel.ColorLine(color, format, args); 151 | } 152 | 153 | public void Line() 154 | { 155 | Ipc.Channel.Line(); 156 | } 157 | 158 | public FunctionHook CreateHook(MemoryOffset offset, string name, T hook) 159 | where T : Delegate 160 | { 161 | var address = Memory.ToAddress(offset); 162 | var original = Marshal.GetDelegateForFunctionPointer(address); 163 | var fh = new FunctionHook(address, name, original, hook); 164 | 165 | _hooks.Add(fh); 166 | 167 | return fh; 168 | } 169 | 170 | public FunctionHook CreateHook(byte?[] pattern, string name, T hook) 171 | where T : Delegate 172 | { 173 | return Memory.Search(pattern).Cast().FirstOrDefault() is MemoryOffset o ? CreateHook(o, name, hook) : null; 174 | } 175 | 176 | public void DeleteHook(FunctionHook hook) 177 | where T : Delegate 178 | { 179 | _hooks.Remove(hook); 180 | ((IDisposable)hook).Dispose(); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Augmentrex](Augmentrex.ico) Augmentrex 2 | 3 | [![Latest Release](https://img.shields.io/github/release/alexrp/augmentrex/all.svg)](https://github.com/alexrp/augmentrex/releases) 4 | [![Build Status](https://ci.appveyor.com/api/projects/status/github/alexrp/augmentrex?svg=true)](https://ci.appveyor.com/project/alexrp/augmentrex) 5 | 6 | **Augmentrex** is a reverse engineering tool for the 7 | [Steam version of Hellgate: London](https://store.steampowered.com/app/939520/HELLGATE_London). 8 | It provides a generic framework for doing in-memory interaction with the 9 | Hellgate: London process from managed code. Users can write plugins to add 10 | additional functionality. 11 | 12 | (The name comes from the in-game device, Augmentrex 3000, which is used to add 13 | new affixes to items.) 14 | 15 | ## Features 16 | 17 | Core features include: 18 | 19 | * [EasyHook](https://easyhook.github.io)-based injection into the game process. 20 | * Custom commands and reloadable plugins can be written in any managed language. 21 | * Core APIs and commands for manipulating game memory and global hot keys. 22 | * Built-in `OutputDebugString()` listener for game output. 23 | * Automatic detection and launching of the game. 24 | 25 | Custom commands included with the core: 26 | 27 | * `patch-long-ray-vm`: Disables the game's ray casting engine, fixing the vast 28 | majority of frame rate issues (commonly known as the 1 FPS bug). 29 | * `patch-cc-agent`: Disables the game's capsule-capsule collision agent, fixing 30 | frame rate issues for certain skills (e.g. Blademaster's Whirlwind). 31 | 32 | Plugins included with the core: 33 | 34 | * `simple-test`: Just a simple a plugin that outputs a message on startup and 35 | shutdown. 36 | 37 | (Developers can use these as examples for implementing custom commands and 38 | plugins.) 39 | 40 | ## Installation 41 | 42 | [Archives with compiled binaries are available from the releases page.](https://github.com/alexrp/augmentrex/releases) 43 | 44 | Augmentrex requires .NET Framework 4.7.2 to run. 45 | 46 | If you want to build Augmentrex from source, you will need Visual Studio 2019 47 | (any edition). The code base is written in C# 8.0, so earlier versions will not 48 | work. 49 | 50 | Simply open `Alkahest.sln` and build it with the `Debug` + `x86` configuration. 51 | All build artifacts will end up in the `Build` directory. 52 | 53 | ## Configuration 54 | 55 | Advanced users can have a look in `Augmentrex.exe.config` if they wish to change 56 | configuration values. Here are some values you might be interested in changing: 57 | 58 | * `gamePath`: If Augmentrex cannot locate the game executable via Steam, you can 59 | set this value explicitly. For example, on my system, I would set this to 60 | `C:\Program Files (x86)\Steam\steamapps\common\HELLGATE_London\bin\Hellgate_sp_x86.exe`. 61 | * `gameArguments`: Any command line arguments to pass to the game. I personally 62 | dislike the keyboard hook the game does to disable the Win key, so I would 63 | leave `-nokeyhooks` here. 64 | * `gameConsoleEnabled` and `debugListenerEnabled`: You can set these to `False` 65 | if you are not interested in debug output from the game process. 66 | * `hotKeyBeepFrequency`: Set to `0` if you do not want a beep sound when 67 | pressing a hot key. 68 | * `disabledPlugins`: This can be used to disable individual plugins without 69 | removing the files from the Augmentrex directory. 70 | * `runCommands`: You can use this to run commands at startup. For example, you 71 | could set it to 72 | `patch-cc-agent; patch-long-ray-vm; key --add -s F1 patch-long-ray-vm` to 73 | enable `patch-cc-agent` and `patch-long-ray-vm` at startup and set a Shift+F1 74 | key binding to toggle `patch-long-ray-vm`. 75 | 76 | ## Usage 77 | 78 | Simply launch `Augmentrex.exe`. Augmentrex will locate the game executable via 79 | Steam and launch it for you, then attach to the game process. 80 | 81 | Once the game is open, you can type `patch-long-ray-vm` and/or `patch-cc-agent` 82 | to toggle those patches (even during gameplay). 83 | 84 | If you get an EasyHook error along the lines of `STATUS_INTERNAL_ERROR` with 85 | code 5, you will need to do the following: 86 | 87 | * Close Augmentrex and the game. 88 | * Navigate to your `Hellgate_sp_x86.exe` file. 89 | * Right click -> Properties -> Compatibility. 90 | * Tick the "Run in 640 x 480 screen resolution" option. 91 | * Click OK. 92 | 93 | It is [unclear](https://github.com/EasyHook/EasyHook/issues/295) why this is 94 | necessary on some systems, but the good news is that the game will run with the 95 | correct video settings even with this option enabled (i.e. not actually 96 | 640x480). If you want to run the game without Augmentrex, though, you will have 97 | to go back and untick that option. 98 | 99 | ## Known Issues 100 | 101 | The `patch-long-ray-vm` command has a few side effects to be aware of: 102 | 103 | * You can see portal names and enemy nameplates through terrain. 104 | * Collision detection for ranged attacks will be non-functional, allowing both 105 | you and certain enemy types to attack through some types of terrain. 106 | * Enemy corpses will often just vanish when they die, or less frequently, end up 107 | in weird poses. 108 | * Certain bosses (Ash and Oculis, possibly others) rely on ray casting for some 109 | of their attacks. You have to toggle the command off before you engage these 110 | bosses, or you will not be able to kill them. 111 | 112 | As far as I am aware, the `patch-cc-agent` command has no negative side effects. 113 | 114 | ## Background 115 | 116 | I started work on this project because I finally got sick of the dreaded 1 FPS 117 | bug the game suffers from. Through various efforts (static reverse engineering, 118 | dynamic debugging, CPU profiling), I finally managed to figure out why the 119 | game's frame rate would drop so severely. It turns out that the game makes an 120 | excessive number of ray cast queries under certain circumstances. I still need 121 | to figure out exactly why that happens, but for now, disabling ray casting 122 | altogether makes the game actually playable. This is done by patching the 123 | `hkpMoppLongRayVirtualMachine::queryRayOnTree()` function from the Havok Physics 124 | library used in the game, so that it simply returns rather than running the 125 | bytecode passed to it. This effectively disables ray casting. Again, just to be 126 | clear, this is treating the symptom rather than the cause, but it does at least 127 | work. 128 | 129 | In the future, I will be investigating various other frame rate issues with the 130 | game, and so I figured I would need a more generic framework for making changes 131 | to the game executable. Thus, this project, and the fix being a plugin. 132 | 133 | By the way, while I love this game (which you can probably tell by the fact that 134 | I bothered to do all this), I **absolutely** cannot recommend that anyone go and 135 | buy it on Steam. This 1 FPS bug has existed for almost a decade, from all the 136 | way back when the game was online-only. The bug was almost certainly introduced 137 | by HanbitSoft when they took over the game from Flagship Studios. They have been 138 | aware of the bug for all this time. Even when they re-released the game as a 139 | single player title on Steam, they spent months pretending that people's PCs 140 | just did not meet the game's minimum requirements. 141 | 142 | To be perfectly clear: They are selling a broken product that is unplayable to 143 | the vast majority of people. They were fully aware of this, and proceeded with 144 | putting it on Steam anyway. They then went on to completely ignore the issue and 145 | to this day have not fixed it, nor even acknowledged it. I actively encourage 146 | everyone to report the game as being broken on the store page, because it is. 147 | They should not be allowed to make money by selling such a blatantly broken 148 | product. 149 | 150 | Still, for those who bought the game and would still like to play it, this 151 | project will help you do that. 152 | 153 | ## Contributing 154 | 155 | Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md). 156 | 157 | ## License 158 | 159 | Please see [LICENSE.md](LICENSE.md). 160 | 161 | ## Funding 162 | 163 | I work on open source software projects such as this one in my spare time, and 164 | make them available free of charge under permissive licenses. If you like my 165 | work and would like to support me, you might consider [sponsoring 166 | me](https://github.com/sponsors/alexrp). Please only donate if you want to and 167 | have the means to do so; I want to be very clear that all open source software I 168 | write will always be available for free and you should not feel obligated to 169 | donate or pay for it in any way. 170 | -------------------------------------------------------------------------------- /Augmentrex/Memory/InstructionExtensions.cs: -------------------------------------------------------------------------------- 1 | using ExposedObject; 2 | using SharpDisasm; 3 | using SharpDisasm.Translators; 4 | using SharpDisasm.Udis86; 5 | using System; 6 | using System.Linq; 7 | 8 | namespace Augmentrex.Memory 9 | { 10 | static class InstructionExtensions 11 | { 12 | sealed class HackyTranslator : Translator 13 | { 14 | protected override void TranslateInstruction(Instruction insn) 15 | { 16 | } 17 | 18 | public string GetRegisterName(ud_type type) 19 | { 20 | return RegisterForType(type); 21 | } 22 | 23 | public ulong GetRelativeJumpTarget(Instruction instruction, Operand operand) 24 | { 25 | return ud_syn_rel_target(instruction, operand); 26 | } 27 | } 28 | 29 | public static void PrintColored(this Instruction instruction, AugmentrexContext context, int length) 30 | { 31 | const ConsoleColor TriviaColor = ConsoleColor.DarkGray; 32 | const ConsoleColor AddressColor = ConsoleColor.White; 33 | const ConsoleColor BinaryColor = ConsoleColor.DarkCyan; 34 | const ConsoleColor MnemonicColor = ConsoleColor.DarkYellow; 35 | const ConsoleColor CastColor = ConsoleColor.DarkMagenta; 36 | const ConsoleColor RegisterColor = ConsoleColor.DarkGreen; 37 | const ConsoleColor LiteralColor = ConsoleColor.DarkRed; 38 | 39 | context.Color(AddressColor, "{0:X8} ", instruction.Offset); 40 | context.Color(BinaryColor, $"{{0,-{length * 2 + length - 1}}} ", 41 | string.Join(" ", instruction.Bytes.Select(x => x.ToString("X2")))); 42 | 43 | var dynIns = Exposed.From(instruction); 44 | 45 | string GetCast(Operand operand) 46 | { 47 | var value = dynIns.br_far != 0 ? "far " : string.Empty; 48 | 49 | switch (operand.Size) 50 | { 51 | case 8: 52 | value += "byte "; 53 | break; 54 | case 16: 55 | value += "word "; 56 | break; 57 | case 32: 58 | value += "dword "; 59 | break; 60 | case 64: 61 | value += "qword "; 62 | break; 63 | case 80: 64 | value += "tword "; 65 | break; 66 | case 128: 67 | value += "oword "; 68 | break; 69 | case 256: 70 | value += "yword "; 71 | break; 72 | case 512: 73 | value += "zword "; 74 | break; 75 | } 76 | 77 | return value; 78 | } 79 | 80 | var trans = new HackyTranslator(); 81 | 82 | void HandleOperand(Operand operand, int index) 83 | { 84 | var cast = false; 85 | 86 | switch (index) 87 | { 88 | case 0: 89 | if (operand.Type == ud_type.UD_OP_MEM) 90 | { 91 | var next = instruction.Operands.Skip(1).FirstOrDefault(); 92 | 93 | if (next != null && next.Type != ud_type.UD_OP_IMM && 94 | next.Type != ud_type.UD_OP_CONST && next.Size == operand.Size) 95 | { 96 | if (next.Type == ud_type.UD_OP_REG && next.Base == ud_type.UD_R_CL) 97 | { 98 | switch (instruction.Mnemonic) 99 | { 100 | case ud_mnemonic_code.UD_Ircl: 101 | case ud_mnemonic_code.UD_Irol: 102 | case ud_mnemonic_code.UD_Iror: 103 | case ud_mnemonic_code.UD_Ircr: 104 | case ud_mnemonic_code.UD_Ishl: 105 | case ud_mnemonic_code.UD_Ishr: 106 | case ud_mnemonic_code.UD_Isar: 107 | cast = true; 108 | break; 109 | } 110 | } 111 | } 112 | else 113 | cast = true; 114 | } 115 | break; 116 | case 1: 117 | if (operand.Type == ud_type.UD_OP_MEM && 118 | operand.Size != instruction.Operands[0].Size) 119 | cast = true; 120 | break; 121 | case 2: 122 | if (operand.Type == ud_type.UD_OP_MEM && 123 | operand.Size != instruction.Operands[1].Size) 124 | cast = true; 125 | break; 126 | case 3: 127 | break; 128 | } 129 | 130 | switch (operand.Type) 131 | { 132 | case ud_type.UD_OP_REG: 133 | context.Color(RegisterColor, trans.GetRegisterName(operand.Base)); 134 | break; 135 | case ud_type.UD_OP_MEM: 136 | if (cast) 137 | context.Color(CastColor, GetCast(operand)); 138 | 139 | context.Color(TriviaColor, "["); 140 | 141 | if (dynIns.pfx_seg != 0) 142 | { 143 | context.Color(RegisterColor, trans.GetRegisterName((ud_type)dynIns.pfx_seg)); 144 | context.Color(TriviaColor, ":"); 145 | } 146 | 147 | if (operand.Base != ud_type.UD_NONE) 148 | context.Color(RegisterColor, trans.GetRegisterName(operand.Base)); 149 | 150 | if (operand.Index != ud_type.UD_NONE) 151 | { 152 | if (operand.Base != ud_type.UD_NONE) 153 | context.Color(TriviaColor, "+"); 154 | 155 | context.Color(RegisterColor, trans.GetRegisterName(operand.Index)); 156 | 157 | if (operand.Scale != 0) 158 | { 159 | context.Color(TriviaColor, "*"); 160 | context.Color(RegisterColor, "{0}", operand.Scale); 161 | } 162 | } 163 | 164 | if (operand.Offset != 0) 165 | { 166 | if (operand.Base != ud_type.UD_NONE || operand.Index != ud_type.UD_NONE) 167 | { 168 | var value = operand.LvalSQWord; 169 | 170 | if (value > 0) 171 | context.Color(TriviaColor, "+"); 172 | else 173 | context.Color(TriviaColor, "-"); 174 | 175 | context.Color(LiteralColor, "0x{0:X}", value); 176 | } 177 | else 178 | context.Color(LiteralColor, "0x{0:X}", operand.LvalUQWord); 179 | } 180 | 181 | context.Color(TriviaColor, "]"); 182 | break; 183 | case ud_type.UD_OP_PTR: 184 | if (operand.Size == 32) 185 | { 186 | context.Color(CastColor, "word "); 187 | context.Color(LiteralColor, "0x{0:X}:0x{1:X}", operand.PtrSegment, operand.PtrOffset & 0xffff); 188 | } 189 | else if (operand.Size == 48) 190 | { 191 | context.Color(CastColor, "dword "); 192 | context.Color(LiteralColor, "0x{0:X}:0x{1:X}", operand.PtrSegment, operand.PtrOffset); 193 | } 194 | break; 195 | case ud_type.UD_OP_IMM: 196 | var imm = operand.LvalUQWord; 197 | 198 | if (operand.Opcode == ud_operand_code.OP_sI && 199 | operand.Size != dynIns.opr_mode && 200 | dynIns.opr_mode < 64) 201 | imm &= (1ul << dynIns.opr_mode) - 1; 202 | 203 | context.Color(LiteralColor, "0x{0:X}", imm); 204 | break; 205 | case ud_type.UD_OP_JIMM: 206 | context.Color(LiteralColor, "0x{0:X}", trans.GetRelativeJumpTarget(instruction, operand)); 207 | break; 208 | case ud_type.UD_OP_CONST: 209 | if (cast) 210 | context.Color(CastColor, GetCast(operand)); 211 | 212 | context.Color(LiteralColor, "{0}", operand.LvalUDWord); 213 | break; 214 | } 215 | } 216 | 217 | var dynBitOps = Exposed.From(typeof(Disassembler).Assembly.GetType("SharpDisasm.Udis86.BitOps")); 218 | 219 | if (dynBitOps.P_OSO((uint)dynIns.itab_entry.Prefix) == 0 && dynIns.pfx_opr != 0) 220 | { 221 | switch (instruction.dis_mode) 222 | { 223 | case ArchitectureMode.x86_16: 224 | context.Color(MnemonicColor, "o32 "); 225 | break; 226 | case ArchitectureMode.x86_64: 227 | context.Color(MnemonicColor, "o16 "); 228 | break; 229 | } 230 | } 231 | 232 | if (dynBitOps.P_ASO((uint)dynIns.itab_entry.Prefix) == 0 && dynIns.pfx_opr != 0) 233 | { 234 | switch (instruction.dis_mode) 235 | { 236 | case ArchitectureMode.x86_16: 237 | context.Color(MnemonicColor, "a32 "); 238 | break; 239 | case ArchitectureMode.x86_32: 240 | context.Color(MnemonicColor, "a16 "); 241 | break; 242 | case ArchitectureMode.x86_64: 243 | context.Color(MnemonicColor, "a32 "); 244 | break; 245 | } 246 | } 247 | 248 | if (dynIns.pfx_seg != 0 && instruction.Operands.Length >= 2 && 249 | instruction.Operands[0].Type != ud_type.UD_OP_MEM && 250 | instruction.Operands[1].Type != ud_type.UD_OP_MEM) 251 | context.Color(RegisterColor, "{0} ", trans.GetRegisterName((ud_type)dynIns.pfx_seg)); 252 | 253 | if (dynIns.pfx_lock != 0) 254 | context.Color(MnemonicColor, "lock "); 255 | 256 | if (dynIns.pfx_rep != 0) 257 | context.Color(MnemonicColor, "rep "); 258 | else if (dynIns.pfx_repe != 0) 259 | context.Color(MnemonicColor, "repe "); 260 | else if (dynIns.pfx_repne != 0) 261 | context.Color(MnemonicColor, "repne "); 262 | 263 | context.Color(MnemonicColor, "{0}", udis86.ud_lookup_mnemonic(instruction.Mnemonic)); 264 | 265 | for (var i = 0; i < instruction.Operands.Length; i++) 266 | { 267 | var op = instruction.Operands[i]; 268 | 269 | if (i != 0) 270 | context.Color(TriviaColor, ","); 271 | 272 | context.Info(" "); 273 | 274 | HandleOperand(op, i); 275 | } 276 | 277 | context.Line(); 278 | } 279 | } 280 | } 281 | --------------------------------------------------------------------------------