//
// 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.Collections.Concurrent;
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 nuint 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 nuint 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.CreateCustomAsmHookFromPattern
("NetworkBinding.SendPacket", binding.SendPacketDetour, options.PacketSendHook);
if (!sendHookResult.IsDefined(out var sendHook))
{
return Result.FromError(sendHookResult);
}
var receiveHookResult = bindingManager.CreateCustomAsmHookFromPattern
("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 NosAsmHook _sendHook = null!;
private NosAsmHook _receiveHook = null!;
private bool _callingReceive;
private bool _callingSend;
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
{
_callingSend = true;
using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet);
_sendHook.OriginalFunction.GetWrapper()(GetManagerAddress(false), nostaleString.Get());
_callingSend = false;
}
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
{
_callingReceive = true;
using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet);
_receiveHook.OriginalFunction.GetWrapper()(GetManagerAddress(true), nostaleString.Get());
_callingReceive = false;
}
catch (Exception e)
{
return e;
}
return Result.FromSuccess();
}
///
/// Enable all networking hooks.
///
public void EnableHooks()
{
_receiveHook.Hook.EnableOrActivate();
_sendHook.Hook.EnableOrActivate();
}
///
/// Disable all the hooks that are currently enabled.
///
public void DisableHooks()
{
_receiveHook.Hook.Disable();
_sendHook.Hook.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 nuint SendPacketDetour(nuint packetObject, nuint packetString)
{
if (_callingSend)
{
return 1;
}
var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
if (packet is null)
{ // ?
return 1;
}
var result = PacketSend?.Invoke(packet);
return result ?? true ? (nuint)1 : 0;
}
private nuint ReceivePacketDetour(nuint packetObject, nuint packetString)
{
if (_callingReceive)
{
return 1;
}
var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
if (packet is null)
{ // ?
return 1;
}
var result = PacketReceive?.Invoke(packet);
return result ?? true ? (nuint)1 : 0;
}
}