M src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs => src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs +2 -0
@@ 31,12 31,14 @@ public static class ServiceCollectionExtensions
return serviceCollection
.AddSingleton<NosBindingManager>()
.AddSingleton<NosBrowserManager>()
+ .AddSingleton<NosThreadSynchronizer>()
.AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager)
.AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
.AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
.AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
.AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
.AddSingleton(p => p.GetRequiredService<NosBindingManager>().PlayerManager)
+ .AddSingleton(p => p.GetRequiredService<NosBindingManager>().Periodic)
.AddSingleton(p => p.GetRequiredService<NosBindingManager>().PetManager)
.AddSingleton(p => p.GetRequiredService<NosBindingManager>().UnitManager)
.AddSingleton(p => p.GetRequiredService<NosBindingManager>().Network);
M src/Core/NosSmooth.LocalBinding/HooksConfigBuilder.cs => src/Core/NosSmooth.LocalBinding/HooksConfigBuilder.cs +26 -0
@@ 34,6 34,7 @@ public class HooksConfigBuilder
private readonly HookOptionsBuilder _entityFocusHook;
private readonly HookOptionsBuilder _entityFollowHook;
private readonly HookOptionsBuilder _entityUnfollowHook;
+ private readonly HookOptionsBuilder _periodicHook;
/// <summary>
/// Initializes a new instance of the <see cref="HooksConfigBuilder"/> class.
@@ 47,6 48,7 @@ public class HooksConfigBuilder
_entityFocusHook = new HookOptionsBuilder(new UnitManagerBindingOptions().EntityFocusHook);
_entityFollowHook = new HookOptionsBuilder(new CharacterBindingOptions().EntityFollowHook);
_entityUnfollowHook = new HookOptionsBuilder(new CharacterBindingOptions().EntityUnfollowHook);
+ _periodicHook = new HookOptionsBuilder(new PeriodicBindingOptions().PeriodicHook);
}
/// <summary>
@@ 62,6 64,7 @@ public class HooksConfigBuilder
_entityFocusHook.Hook(false);
_entityFollowHook.Hook(false);
_entityUnfollowHook.Hook(false);
+ _periodicHook.Hook(false);
return this;
}
@@ 78,6 81,7 @@ public class HooksConfigBuilder
_entityFocusHook.Hook(true);
_entityFollowHook.Hook(true);
_entityUnfollowHook.Hook(true);
+ _periodicHook.Hook(true);
return this;
}
@@ 93,6 97,28 @@ public class HooksConfigBuilder
}
/// <summary>
+ /// Configure periodic hook. Can be any periodic function in NosTale called on every frame.
+ /// Enables the hook. In case you just want to change the pattern or offset
+ /// and do not want to enable the hook, call .Hook(false) in the builder.
+ /// </summary>
+ /// <param name="configure">The configuring action.</param>
+ /// <returns>This builder.</returns>
+ public HooksConfigBuilder HookPeriodic(Action<HookOptionsBuilder>? configure = default)
+ {
+ if (configure is not null)
+ {
+ _periodicHook.Hook();
+ configure(_periodicHook);
+ }
+ else
+ {
+ _periodicHook.Hook(true);
+ }
+
+ return this;
+ }
+
+ /// <summary>
/// Configure packet send hooks.
/// Enables the hook. In case you just want to change the pattern or offset
/// and do not want to enable the hook, call .Hook(false) in the builder.
M src/Core/NosSmooth.LocalBinding/NosBindingManager.cs => src/Core/NosSmooth.LocalBinding/NosBindingManager.cs +58 -1
@@ 30,11 30,13 @@ public class NosBindingManager : IDisposable
private readonly CharacterBindingOptions _characterBindingOptions;
private readonly NetworkBindingOptions _networkBindingOptions;
private readonly UnitManagerBindingOptions _unitManagerBindingOptions;
+ private readonly PeriodicBindingOptions _periodicBindingOptions;
private NetworkBinding? _networkBinding;
private PlayerManagerBinding? _characterBinding;
private UnitManagerBinding? _unitManagerBinding;
private PetManagerBinding? _petManagerBinding;
+ private PeriodicBinding? _periodicBinding;
/// <summary>
/// Initializes a new instance of the <see cref="NosBindingManager"/> class.
@@ 44,13 46,15 @@ public class NosBindingManager : IDisposable
/// <param name="networkBindingOptions">The network binding options.</param>
/// <param name="sceneManagerBindingOptions">The scene manager binding options.</param>
/// <param name="petManagerBindingOptions">The pet manager binding options.</param>
+ /// <param name="periodicBindingOptions">The periodic binding options.</param>
public NosBindingManager
(
NosBrowserManager browserManager,
IOptions<CharacterBindingOptions> characterBindingOptions,
IOptions<NetworkBindingOptions> networkBindingOptions,
IOptions<UnitManagerBindingOptions> sceneManagerBindingOptions,
- IOptions<PetManagerBindingOptions> petManagerBindingOptions
+ IOptions<PetManagerBindingOptions> petManagerBindingOptions,
+ IOptions<PeriodicBindingOptions> periodicBindingOptions
)
{
_browserManager = browserManager;
@@ 61,6 65,7 @@ public class NosBindingManager : IDisposable
_networkBindingOptions = networkBindingOptions.Value;
_unitManagerBindingOptions = sceneManagerBindingOptions.Value;
_petManagerBindingOptions = petManagerBindingOptions.Value;
+ _periodicBindingOptions = periodicBindingOptions.Value;
}
/// <summary>
@@ 79,6 84,26 @@ public class NosBindingManager : IDisposable
internal IMemory Memory { get; }
/// <summary>
+ /// Gets the periodic binding.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
+ public PeriodicBinding Periodic
+ {
+ get
+ {
+ if (_periodicBinding is null)
+ {
+ throw new InvalidOperationException
+ (
+ "Could not get periodic. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
+ );
+ }
+
+ return _periodicBinding;
+ }
+ }
+
+ /// <summary>
/// Gets the network binding.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
@@ 183,6 208,38 @@ public class NosBindingManager : IDisposable
try
{
+ var periodicBinding = PeriodicBinding.Create
+ (
+ this,
+ _periodicBindingOptions
+ );
+ if (!periodicBinding.IsSuccess)
+ {
+ errorResults.Add
+ (
+ Result.FromError
+ (
+ new CouldNotInitializeModuleError(typeof(PeriodicBinding), periodicBinding.Error),
+ periodicBinding
+ )
+ );
+ }
+ _periodicBinding = periodicBinding.Entity;
+ }
+ catch (Exception e)
+ {
+ errorResults.Add
+ (
+ Result.FromError
+ (
+ new CouldNotInitializeModuleError(typeof(PeriodicBinding), new ExceptionError(e)),
+ (Result)new ExceptionError(e)
+ )
+ );
+ }
+
+ try
+ {
var network = NetworkBinding.Create(this, _networkBindingOptions);
if (!network.IsSuccess)
{
A src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs => src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs +145 -0
@@ 0,0 1,145 @@
+//
+// NosThreadSynchronizer.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.Net;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using NosSmooth.LocalBinding.Objects;
+using NosSmooth.LocalBinding.Options;
+using Remora.Results;
+
+namespace NosSmooth.LocalBinding;
+
+/// <summary>
+/// Synchronizes with NosTale thread using a periodic function.
+/// </summary>
+public class NosThreadSynchronizer
+{
+ private readonly PeriodicBinding _periodicBinding;
+ private readonly NosThreadSynchronizerOptions _options;
+ private readonly ConcurrentQueue<SyncOperation> _queuedOperations;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NosThreadSynchronizer"/> class.
+ /// </summary>
+ /// <param name="periodicBinding">The periodic function binding.</param>
+ /// <param name="options">The options.</param>
+ public NosThreadSynchronizer(PeriodicBinding periodicBinding, IOptions<NosThreadSynchronizerOptions> options)
+ {
+ _periodicBinding = periodicBinding;
+ _options = options.Value;
+ _queuedOperations = new ConcurrentQueue<SyncOperation>();
+ }
+
+ /// <summary>
+ /// Start the synchronizer operation.
+ /// </summary>
+ public void StartSynchronizer()
+ {
+ _periodicBinding.Periodic += Periodic;
+ }
+
+ /// <summary>
+ /// Stop the synchronizer operation.
+ /// </summary>
+ public void StopSynchronizer()
+ {
+ _periodicBinding.Periodic -= Periodic;
+ }
+
+ private void Periodic()
+ {
+ var tasks = _options.MaxTasksPerIteration;
+
+ while (tasks-- > 0 && _queuedOperations.TryDequeue(out var operation))
+ {
+ ExecuteOperation(operation);
+ }
+ }
+
+ private void ExecuteOperation(SyncOperation operation)
+ {
+ try
+ {
+ var result = operation.action();
+ operation.Result = result;
+ }
+ catch (Exception e)
+ {
+ // TODO: log?
+ operation.Result = e;
+ }
+
+ if (operation.CancellationTokenSource is not null)
+ {
+ try
+ {
+ operation.CancellationTokenSource.Cancel();
+ }
+ catch (Exception)
+ {
+ // ignore
+ }
+ }
+ }
+
+ /// <summary>
+ /// Enqueue the given operation to execute on next frame.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ public void EnqueueOperation(Action action)
+ {
+ _queuedOperations.Enqueue
+ (
+ new SyncOperation
+ (
+ () =>
+ {
+ action();
+ return Result.FromSuccess();
+ },
+ null
+ )
+ );
+ }
+
+ /// <summary>
+ /// Synchronizes to NosTale thread, executes the given action and returns its result.
+ /// </summary>
+ /// <param name="action">The action to execute.</param>
+ /// <param name="ct">The cancellation token used for cancelling the operation.</param>
+ /// <returns>The result of the action.</returns>
+ public async Task<Result> SynchronizeAsync(Func<Result> action, CancellationToken ct = default)
+ {
+ var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ var syncOperation = new SyncOperation(action, linkedSource);
+ _queuedOperations.Enqueue(syncOperation);
+
+ try
+ {
+ await Task.Delay(Timeout.Infinite, linkedSource.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ if (ct.IsCancellationRequested)
+ { // Throw in case the top token was cancelled.
+ throw;
+ }
+ }
+ catch (Exception e)
+ {
+ return new ExceptionError(e);
+ }
+
+ return syncOperation.Result ?? Result.FromSuccess();
+ }
+
+ private record SyncOperation(Func<Result> action, CancellationTokenSource? CancellationTokenSource)
+ {
+ public Result? Result { get; set; }
+ }
+}<
\ No newline at end of file
A src/Core/NosSmooth.LocalBinding/Objects/PeriodicBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/PeriodicBinding.cs +82 -0
@@ 0,0 1,82 @@
+//
+// PeriodicBinding.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.Extensions;
+using NosSmooth.LocalBinding.Options;
+using Reloaded.Hooks.Definitions.X86;
+using Remora.Results;
+
+namespace NosSmooth.LocalBinding.Objects;
+
+/// <summary>
+/// Binds to a periodic function to allow synchronizing.
+/// </summary>
+public class PeriodicBinding
+{
+ [Function
+ (
+ new FunctionAttribute.Register[0],
+ FunctionAttribute.Register.eax,
+ FunctionAttribute.StackCleanup.Callee,
+ new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp, FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx }
+ )]
+ private delegate void PeriodicDelegate();
+
+ /// <summary>
+ /// Create the periodic binding with finding the periodic function.
+ /// </summary>
+ /// <param name="bindingManager">The binding manager.</param>
+ /// <param name="options">The options for the binding.</param>
+ /// <returns>A periodic binding or an error.</returns>
+ public static Result<PeriodicBinding> Create(NosBindingManager bindingManager, PeriodicBindingOptions options)
+ {
+ var binding = new PeriodicBinding();
+
+ var periodicHookResult = bindingManager.CreateCustomAsmHookFromPattern<PeriodicDelegate>
+ ("PeriodicBinding.Periodic", binding.PeriodicDetour, options.PeriodicHook);
+ if (!periodicHookResult.IsDefined(out var periodicHook))
+ {
+ return Result<PeriodicBinding>.FromError(periodicHookResult);
+ }
+
+ binding._periodicHook = periodicHook;
+ return binding;
+ }
+
+ private NosAsmHook<PeriodicDelegate>? _periodicHook;
+
+ private PeriodicBinding()
+ {
+ }
+
+ /// <summary>
+ /// An action called on every period.
+ /// </summary>
+ public event Action? Periodic;
+
+ /// <summary>
+ /// Enable all networking hooks.
+ /// </summary>
+ public void EnableHooks()
+ {
+ _periodicHook?.Hook.EnableOrActivate();
+ }
+
+ /// <summary>
+ /// Disable all the hooks that are currently enabled.
+ /// </summary>
+ public void DisableHooks()
+ {
+ _periodicHook?.Hook.Disable();
+ }
+
+ private void PeriodicDetour()
+ {
+ Periodic?.Invoke();
+ }
+}<
\ No newline at end of file
A src/Core/NosSmooth.LocalBinding/Options/NosThreadSynchronizerOptions.cs => src/Core/NosSmooth.LocalBinding/Options/NosThreadSynchronizerOptions.cs +18 -0
@@ 0,0 1,18 @@
+//
+// NosThreadSynchronizerOptions.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 NosSmooth.LocalBinding.Options;
+
+/// <summary>
+/// Options for <see cref="NosThreadSynchronizer"/>.
+/// </summary>
+public class NosThreadSynchronizerOptions
+{
+ /// <summary>
+ /// Gets or sets the number of max tasks per one iteration.
+ /// </summary>
+ public int MaxTasksPerIteration { get; set; } = 10;
+}<
\ No newline at end of file
A src/Core/NosSmooth.LocalBinding/Options/PeriodicBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/PeriodicBindingOptions.cs +21 -0
@@ 0,0 1,21 @@
+//
+// PeriodicBindingOptions.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;
+
+/// <summary>
+/// Options for <see cref="PeriodicBinding"/>.
+/// </summary>
+public class PeriodicBindingOptions
+{
+ /// <summary>
+ /// Gets or sets the configuration for any periodic function hook.
+ /// </summary>
+ public HookOptions PeriodicHook { get; set; }
+ = new HookOptions(true, "55 8B EC 53 56 83 C4", 0);
+}<
\ No newline at end of file