From f8a901cda2c558c993c646e931adca56c57be4e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Sat, 15 Jan 2022 20:09:30 +0100 Subject: [PATCH] feat: add .net nostale bindings --- .../Errors/BindingNotFoundError.cs | 17 ++ .../Extensions/ServiceCollectionExtensions.cs | 35 +++ .../NosSmooth.LocalBinding/IsExternalInit.cs | 15 ++ .../NosBindingManager.cs | 146 +++++++++++ .../NosSmooth.LocalBinding.csproj | 18 ++ .../Objects/CharacterBinding.cs | 137 ++++++++++ .../Objects/NetworkBinding.cs | 236 ++++++++++++++++++ .../Objects/NostaleStringA.cs | 84 +++++++ .../Options/CharacterBindingOptions.cs | 34 +++ .../Options/NetworkBindingOptions.cs | 44 ++++ 10 files changed, 766 insertions(+) create mode 100644 Local/NosSmooth.LocalBinding/Errors/BindingNotFoundError.cs create mode 100644 Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs create mode 100644 Local/NosSmooth.LocalBinding/IsExternalInit.cs create mode 100644 Local/NosSmooth.LocalBinding/NosBindingManager.cs create mode 100644 Local/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj create mode 100644 Local/NosSmooth.LocalBinding/Objects/CharacterBinding.cs create mode 100644 Local/NosSmooth.LocalBinding/Objects/NetworkBinding.cs create mode 100644 Local/NosSmooth.LocalBinding/Objects/NostaleStringA.cs create mode 100644 Local/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs create mode 100644 Local/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs diff --git a/Local/NosSmooth.LocalBinding/Errors/BindingNotFoundError.cs b/Local/NosSmooth.LocalBinding/Errors/BindingNotFoundError.cs new file mode 100644 index 0000000..182db6d --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Errors/BindingNotFoundError.cs @@ -0,0 +1,17 @@ +// +// BindingNotFoundError.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Remora.Results; + +namespace NosSmooth.LocalBinding.Errors; + +/// +/// The memory pattern was not found in the memory. +/// +/// The pattern that could not be found. +/// The entity the pattern should represent. +public record BindingNotFoundError(string Pattern, string Path) + : ResultError($"Could not find pattern ({Pattern}) in the memory while searching for {Path}."); diff --git a/Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs b/Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..1db8c2e --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,35 @@ +// +// ServiceCollectionExtensions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.DependencyInjection; +using NosSmooth.LocalBinding.Objects; + +namespace NosSmooth.LocalBinding.Extensions; + +/// +/// Contains extension methods for . +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds bindings to Nostale objects along with to initialize those. + /// + /// + /// Adds and . + /// You have to initialize these using + /// prior to requesting them from the provider, otherwise an exception + /// will be thrown. + /// + /// The service collection. + /// The collection. + public static IServiceCollection AddNostaleBindings(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddSingleton() + .AddSingleton(p => p.GetRequiredService().Character) + .AddSingleton(p => p.GetRequiredService().Network); + } +} \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/IsExternalInit.cs b/Local/NosSmooth.LocalBinding/IsExternalInit.cs new file mode 100644 index 0000000..7c63200 --- /dev/null +++ b/Local/NosSmooth.LocalBinding/IsExternalInit.cs @@ -0,0 +1,15 @@ +// +// IsExternalInit.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Runtime.CompilerServices +{ + /// + /// Dummy. + /// + public class IsExternalInit + { + } +} \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/NosBindingManager.cs b/Local/NosSmooth.LocalBinding/NosBindingManager.cs new file mode 100644 index 0000000..3c82b8f --- /dev/null +++ b/Local/NosSmooth.LocalBinding/NosBindingManager.cs @@ -0,0 +1,146 @@ +// +// NosBindingManager.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using Microsoft.Extensions.Options; +using NosSmooth.LocalBinding.Objects; +using NosSmooth.LocalBinding.Options; +using Reloaded.Hooks; +using Reloaded.Hooks.Definitions; +using Reloaded.Memory.Sigscan; +using Reloaded.Memory.Sources; +using Remora.Results; + +namespace NosSmooth.LocalBinding; + +/// +/// Nostale entity binding manager. +/// +public class NosBindingManager : IDisposable +{ + private readonly CharacterBindingOptions _characterBindingOptions; + private readonly NetworkBindingOptions _networkBindingOptions; + + private NetworkBinding? _networkBinding; + private CharacterBinding? _characterBinding; + + /// + /// Initializes a new instance of the class. + /// + /// The character binding options. + /// The network binding options. + public NosBindingManager + ( + IOptions characterBindingOptions, + IOptions networkBindingOptions + ) + { + Hooks = new ReloadedHooks(); + Memory = new Memory(); + Scanner = new Scanner(Process.GetCurrentProcess(), Process.GetCurrentProcess().MainModule); + _characterBindingOptions = characterBindingOptions.Value; + _networkBindingOptions = networkBindingOptions.Value; + } + + /// + /// Gets the memory scanner. + /// + internal Scanner Scanner { get; } + + /// + /// Gets the reloaded hooks. + /// + internal IReloadedHooks Hooks { get; } + + /// + /// Gets the current process memory. + /// + internal IMemory Memory { get; } + + /// + /// Gets the network binding. + /// + /// Thrown if the manager is not initialized yet. + public NetworkBinding Network + { + get + { + if (_networkBinding is null) + { + throw new InvalidOperationException + ("Could not get network. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"); + } + + return _networkBinding; + } + } + + /// + /// Gets the character binding. + /// + /// Thrown if the manager is not initialized yet. + public CharacterBinding Character + { + get + { + if (_characterBinding is null) + { + throw new InvalidOperationException + ("Could not get character. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"); + } + + return _characterBinding; + } + } + + /// + /// Initialize the existing bindings and hook NosTale functions. + /// + /// A result that may or may not have succeeded. + public Result Initialize() + { + var network = NetworkBinding.Create(this, _networkBindingOptions); + if (!network.IsSuccess) + { + return Result.FromError(network); + } + _networkBinding = network.Entity; + + var character = CharacterBinding.Create(this, _characterBindingOptions); + if (!character.IsSuccess) + { + return Result.FromError(character); + } + _characterBinding = character.Entity; + + return Result.FromSuccess(); + } + + /// + /// Disable the currently enabled nostale hooks. + /// + /// A result that may or may not have succeeded. + public Result DisableHooks() + { + if (_networkBinding is not null) + { + var result = _networkBinding.DisableHooks(); + if (!result.IsSuccess) + { + return result; + } + } + + return Result.FromSuccess(); + } + + /// + public void Dispose() + { + Scanner.Dispose(); + DisableHooks(); + } +} \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj b/Local/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj new file mode 100644 index 0000000..e9f722a --- /dev/null +++ b/Local/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + enable + enable + 10 + + + + + + + + + + + diff --git a/Local/NosSmooth.LocalBinding/Objects/CharacterBinding.cs b/Local/NosSmooth.LocalBinding/Objects/CharacterBinding.cs new file mode 100644 index 0000000..b7b9cee --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Objects/CharacterBinding.cs @@ -0,0 +1,137 @@ +// +// CharacterBinding.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using NosSmooth.LocalBinding.Errors; +using NosSmooth.LocalBinding.Options; +using Reloaded.Hooks.Definitions; +using Reloaded.Hooks.Definitions.X86; +using Remora.Results; + +namespace NosSmooth.LocalBinding.Objects; + +/// +/// The nostale binding of a character. +/// +public class CharacterBinding +{ + [Function + ( + new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx }, + FunctionAttribute.Register.eax, + FunctionAttribute.StackCleanup.Callee + )] + private delegate void WalkDelegate(IntPtr characterObject, int position, int unknown = 1); + + /// + /// Create the network binding with finding the network object and functions. + /// + /// The binding manager. + /// The options for the binding. + /// A network binding or an error. + public static Result Create(NosBindingManager bindingManager, CharacterBindingOptions options) + { + var process = Process.GetCurrentProcess(); + var characterObjectAddress = bindingManager.Scanner.CompiledFindPattern(options.CharacterObjectPattern); + if (!characterObjectAddress.Found) + { + return new BindingNotFoundError(options.CharacterObjectPattern, "CharacterBinding"); + } + + var walkFunctionAddress = bindingManager.Scanner.CompiledFindPattern(options.WalkFunctionPattern); + if (!walkFunctionAddress.Found) + { + return new BindingNotFoundError(options.WalkFunctionPattern, "CharacterBinding.Walk"); + } + + var walkFunction = bindingManager.Hooks.CreateFunction + (walkFunctionAddress.Offset + (int)process.MainModule!.BaseAddress); + var walkWrapper = walkFunction.GetWrapper(); + + var binding = new CharacterBinding + ( + bindingManager, + (IntPtr)(characterObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x06), + walkWrapper + ); + + if (options.HookWalk) + { + binding._walkHook = walkFunction + .Hook(binding.WalkDetour); + binding._originalWalk = binding._walkHook.OriginalFunction; + } + + binding._walkHook?.Activate(); + return binding; + } + + private readonly NosBindingManager _bindingManager; + private readonly IntPtr _characterAddress; + private IHook? _walkHook; + private WalkDelegate _originalWalk; + + private CharacterBinding + ( + NosBindingManager bindingManager, + IntPtr characterAddress, + WalkDelegate originalWalk + ) + { + _bindingManager = bindingManager; + _characterAddress = characterAddress; + _originalWalk = originalWalk; + } + + /// + /// Event that is called when walk was called by NosTale. + /// + /// + /// The walk must be hooked for this event to be called. + /// + public event Func? WalkCall; + + /// + /// Disable all the hooks that are currently enabled. + /// + /// A result that may or may not have succeeded. + public Result DisableHooks() + { + _walkHook?.Disable(); + return Result.FromSuccess(); + } + + private IntPtr GetCharacterAddress() + { + IntPtr characterAddress = _characterAddress; + _bindingManager.Memory.Read(characterAddress, out characterAddress); + _bindingManager.Memory.Read(characterAddress, out characterAddress); + + return characterAddress; + } + + /// + /// Walk to the given position. + /// + /// The x coordinate. + /// The y coordinate. + /// A result that may or may not have succeeded. + public Result Walk(ushort x, ushort y) + { + int param = (y << 16) | x; + _originalWalk(GetCharacterAddress(), param); + return Result.FromSuccess(); + } + + private void WalkDetour(IntPtr characterObject, int position, int unknown) + { + var result = WalkCall?.Invoke((ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF)); + if (result ?? true) + { + _originalWalk(characterObject, position, unknown); + } + } +} \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/Objects/NetworkBinding.cs b/Local/NosSmooth.LocalBinding/Objects/NetworkBinding.cs new file mode 100644 index 0000000..97ce0fc --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Objects/NetworkBinding.cs @@ -0,0 +1,236 @@ +// +// NetworkBinding.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using NosSmooth.LocalBinding.Errors; +using NosSmooth.LocalBinding.Options; +using Reloaded.Hooks.Definitions; +using Reloaded.Hooks.Definitions.X86; +using Remora.Results; + +namespace NosSmooth.LocalBinding.Objects; + +/// +/// The binding to nostale network object. +/// +public class NetworkBinding +{ + [Function + ( + new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx }, + FunctionAttribute.Register.eax, + FunctionAttribute.StackCleanup.Callee + )] + private delegate void PacketSendDelegate(IntPtr packetObject, IntPtr packetString); + + [Function + ( + new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx }, + FunctionAttribute.Register.eax, + FunctionAttribute.StackCleanup.Callee + )] + private delegate void PacketReceiveDelegate(IntPtr packetObject, IntPtr packetString); + + /// + /// Create the network binding with finding the network object and functions. + /// + /// The binding manager. + /// The options for the binding. + /// A network binding or an error. + public static Result Create(NosBindingManager bindingManager, NetworkBindingOptions options) + { + var process = Process.GetCurrentProcess(); + var networkObjectAddress = bindingManager.Scanner.CompiledFindPattern(options.NetworkObjectPattern); + if (!networkObjectAddress.Found) + { + return new BindingNotFoundError(options.NetworkObjectPattern, "NetworkBinding"); + } + + var packetSendAddress = bindingManager.Scanner.CompiledFindPattern(options.SendFunctionPattern); + if (!packetSendAddress.Found) + { + return new BindingNotFoundError(options.SendFunctionPattern, "NetworkBinding.SendPacket"); + } + + var packetReceiveAddress = bindingManager.Scanner.CompiledFindPattern(options.ReceiveFunctionPattern); + if (!packetReceiveAddress.Found) + { + return new BindingNotFoundError(options.ReceiveFunctionPattern, "NetworkBinding.ReceivePacket"); + } + + var sendFunction = bindingManager.Hooks.CreateFunction + (packetSendAddress.Offset + (int)process.MainModule!.BaseAddress); + var sendWrapper = sendFunction.GetWrapper(); + + var receiveFunction = bindingManager.Hooks.CreateFunction + (packetReceiveAddress.Offset + (int)process.MainModule!.BaseAddress); + var receiveWrapper = receiveFunction.GetWrapper(); + + var binding = new NetworkBinding + ( + bindingManager, + (IntPtr)(networkObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x01), + sendWrapper, + receiveWrapper + ); + + if (options.HookSend) + { + binding._sendHook = sendFunction + .Hook(binding.SendPacketDetour); + binding._originalSend = binding._sendHook.OriginalFunction; + } + + if (options.HookReceive) + { + binding._receiveHook = receiveFunction + .Hook(binding.ReceivePacketDetour); + binding._originalReceive = binding._receiveHook.OriginalFunction; + } + + binding._sendHook?.Activate(); + binding._receiveHook?.Activate(); + return binding; + } + + private readonly NosBindingManager _bindingManager; + private readonly IntPtr _networkManagerAddress; + private IHook? _sendHook; + private IHook? _receiveHook; + private PacketSendDelegate _originalSend; + private PacketReceiveDelegate _originalReceive; + + private NetworkBinding + ( + NosBindingManager bindingManager, + IntPtr networkManagerAddress, + PacketSendDelegate originalSend, + PacketReceiveDelegate originalReceive + ) + { + _bindingManager = bindingManager; + _networkManagerAddress = networkManagerAddress; + _originalSend = originalSend; + _originalReceive = originalReceive; + } + + /// + /// Event that is called when packet send was called by NosTale. + /// + /// + /// The send must be hooked for this event to be called. + /// + public event Func? PacketSend; + + /// + /// Event that is called when packet receive was called by NosTale. + /// + /// + /// The receive must be hooked for this event to be called. + /// + public event Func? PacketReceive; + + /// + /// Send the given packet. + /// + /// The packet to send. + /// A result that may or may not have succeeded. + public Result SendPacket(string packet) + { + try + { + using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet); + _originalSend(GetManagerAddress(false), nostaleString.Get()); + } + catch (Exception e) + { + return e; + } + + return Result.FromSuccess(); + } + + /// + /// Receive the given packet. + /// + /// The packet to receive. + /// A result that may or may not have succeeded. + public Result ReceivePacket(string packet) + { + try + { + using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet); + _originalReceive(GetManagerAddress(true), nostaleString.Get()); + } + catch (Exception e) + { + return e; + } + + return Result.FromSuccess(); + } + + /// + /// Disable all the hooks that are currently enabled. + /// + /// A result that may or may not have succeeded. + public Result DisableHooks() + { + _receiveHook?.Disable(); + _sendHook?.Disable(); + return Result.FromSuccess(); + } + + private IntPtr GetManagerAddress(bool third) + { + IntPtr networkManager = _networkManagerAddress; + _bindingManager.Memory.Read(networkManager, out networkManager); + _bindingManager.Memory.Read(networkManager, out networkManager); + _bindingManager.Memory.Read(networkManager, out networkManager); + + if (third) + { + _bindingManager.Memory.Read(networkManager + 0x34, out networkManager); + } + + return networkManager; + } + + private void SendPacketDetour(IntPtr packetObject, IntPtr packetString) + { + var packet = Marshal.PtrToStringAnsi(packetString); + if (packet is null) + { // ? + _originalSend(packetObject, packetString); + } + else + { + var result = PacketSend?.Invoke(packet); + if (result ?? true) + { + _originalSend(packetObject, packetString); + } + } + } + + private void ReceivePacketDetour(IntPtr packetObject, IntPtr packetString) + { + var packet = Marshal.PtrToStringAnsi(packetString); + if (packet is null) + { // ? + _originalReceive(packetObject, packetString); + } + else + { + var result = PacketReceive?.Invoke(packet); + if (result ?? true) + { + _originalReceive(packetObject, packetString); + } + } + } +} \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/Objects/NostaleStringA.cs b/Local/NosSmooth.LocalBinding/Objects/NostaleStringA.cs new file mode 100644 index 0000000..9744f04 --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Objects/NostaleStringA.cs @@ -0,0 +1,84 @@ +// +// NostaleStringA.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using Reloaded.Memory.Sources; + +namespace NosSmooth.LocalBinding.Objects; + +/// +/// Represents nostale string object. +/// +public class NostaleStringA : IDisposable +{ + private readonly IMemory _memory; + private IntPtr _pointer; + + /// + /// Create an instance of . + /// + /// The memory to allocate the string on. + /// The string contents. + /// A nostale string. + public static NostaleStringA Create(IMemory memory, string data) + { + var bytes = Encoding.ASCII.GetBytes(data); + var allocated = memory.Allocate(bytes.Length + 1 + 8); + memory.SafeWrite(allocated, 1); + memory.SafeWrite(allocated + 4, data.Length); + memory.SafeWriteRaw(allocated + 8, bytes); + memory.SafeWrite(allocated + 8 + data.Length, 0); + + return new NostaleStringA(memory, allocated); + } + + private NostaleStringA(IMemory memory, IntPtr pointer) + { + _memory = memory; + _pointer = pointer; + + } + + /// + /// Finalizes an instance of the class. + /// + ~NostaleStringA() + { + Free(); + } + + /// + /// Gets whether the string is still allocated. + /// + public bool Allocated => _pointer != IntPtr.Zero; + + /// + /// Get the pointer to the string. + /// + /// A pointer to the string to pass to NosTale. + public IntPtr Get() + { + return _pointer + 0x08; + } + + /// + /// Free the memory allocated by the string. + /// + public void Free() + { + if (Allocated) + { + _memory.Free(_pointer); + _pointer = IntPtr.Zero; + } + } + + /// + public void Dispose() + { + Free(); + } +} \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs b/Local/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs new file mode 100644 index 0000000..e233aea --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs @@ -0,0 +1,34 @@ +// +// CharacterBindingOptions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NosSmooth.LocalBinding.Objects; + +namespace NosSmooth.LocalBinding.Options; + +/// +/// Options for . +/// +public class CharacterBindingOptions +{ + /// + /// Gets or sets whether to hook the walk function. + /// + public bool HookWalk { get; set; } = true; + + /// + /// Gets or sets the pattern to find the character object at. + /// + /// + /// The address of the object is "three pointers down" from address found on this pattern. + /// + public string CharacterObjectPattern { get; set; } + = "33 C9 8B 55 FC A1 ?? ?? ?? ?? E8 ?? ?? ?? ??"; + + /// + /// Gets or sets the pattern to find the walk function at. + /// + public string WalkFunctionPattern { get; set; } = "55 8B EC 83 C4 EC 53 56 57 66 89 4D FA"; +} \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs b/Local/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs new file mode 100644 index 0000000..8a0b9a1 --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs @@ -0,0 +1,44 @@ +// +// NetworkBindingOptions.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NosSmooth.LocalBinding.Objects; + +namespace NosSmooth.LocalBinding.Options; + +/// +/// Options for . +/// +public class NetworkBindingOptions +{ + /// + /// Gets or sets whether to hook the send packet function. + /// + public bool HookSend { get; set; } = true; + + /// + /// Gets or sets whether to hook the receive packet function. + /// + public bool HookReceive { get; set; } = true; + + /// + /// Gets or sets the pattern to find the network object at. + /// + /// + /// The address of the object is "three pointers down" from address found on this pattern. + /// + public string NetworkObjectPattern { get; set; } + = "A1 ?? ?? ?? ?? 8B 00 BA ?? ?? ?? ?? E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? A1 ?? ?? ?? ?? 8B 00 8B 40 40"; + + /// + /// Gets or sets the pattern to find the send packet function at. + /// + public string SendFunctionPattern { get; set; } = "53 56 8B F2 8B D8 EB 04"; + + /// + /// Gets or sets the pattern to find the receive function at. + /// + public string ReceiveFunctionPattern { get; set; } = "55 8B EC 83 C4 F4 53 56 57 33 C9 89 4D F4 89 55 FC 8B D8 8B 45 FC"; +} \ No newline at end of file -- 2.48.1