// // 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.Extensions; 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, new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp } )] private delegate void PacketSendDelegate(nuint packetObject, nuint packetString); [Function ( new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx }, FunctionAttribute.Register.eax, FunctionAttribute.StackCleanup.Callee, new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp } )] private delegate void PacketReceiveDelegate(nuint packetObject, nuint 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.FindPattern(options.NetworkObjectPattern); if (!networkObjectAddress.Found) { return new BindingNotFoundError(options.NetworkObjectPattern, "NetworkBinding"); } var binding = new NetworkBinding ( bindingManager, (nuint)(networkObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x01) ); var sendHookResult = bindingManager.CreateHookFromPattern ("NetworkBinding.SendPacket", binding.SendPacketDetour, options.PacketSendHook); if (!sendHookResult.IsDefined(out var sendHook)) { return Result.FromError(sendHookResult); } var receiveHookResult = bindingManager.CreateHookFromPattern ("NetworkBinding.ReceivePacket", binding.ReceivePacketDetour, options.PacketReceiveHook); if (!receiveHookResult.IsDefined(out var receiveHook)) { return Result.FromError(receiveHookResult); } binding._sendHook = sendHook; binding._receiveHook = receiveHook; return binding; } private readonly NosBindingManager _bindingManager; private readonly nuint _networkManagerAddress; private IHook _sendHook = null!; private IHook _receiveHook = null!; private NetworkBinding ( NosBindingManager bindingManager, nuint networkManagerAddress ) { _bindingManager = bindingManager; _networkManagerAddress = networkManagerAddress; } /// /// 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); _sendHook.OriginalFunction(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); _receiveHook.OriginalFunction(GetManagerAddress(true), nostaleString.Get()); } catch (Exception e) { return e; } return Result.FromSuccess(); } /// /// Enable all networking hooks. /// public void EnableHooks() { _receiveHook.EnableOrActivate(); _sendHook.EnableOrActivate(); } /// /// Disable all the hooks that are currently enabled. /// public void DisableHooks() { _receiveHook.Disable(); _sendHook.Disable(); } private nuint GetManagerAddress(bool third) { nuint 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(nuint packetObject, nuint packetString) { var packet = Marshal.PtrToStringAnsi((IntPtr)packetString); if (packet is null) { // ? _sendHook.OriginalFunction(packetObject, packetString); } else { var result = PacketSend?.Invoke(packet); if (result ?? true) { _sendHook.OriginalFunction(packetObject, packetString); } } } private bool _receivedCancel = false; private void ReceivePacketDetour(nuint packetObject, nuint packetString) { var packet = Marshal.PtrToStringAnsi((IntPtr)packetString); if (packet is null) { // ? _receiveHook.OriginalFunction(packetObject, packetString); } else { var result = PacketReceive?.Invoke(packet); if (result ?? true) { // This is a TEMPORARY fix, I don't know why, // but upon logging in (for OpenNos servers) // there is an exception when receiving packet // cancel. // TODO FIX THIS correctly if (_receivedCancel || !packet.StartsWith("cancel")) { _receiveHook.OriginalFunction(packetObject, packetString); } else { _receivedCancel = true; } } } } }