├── .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
2 |
3 | [](https://github.com/alexrp/augmentrex/releases)
4 | [](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 |
--------------------------------------------------------------------------------