feat: Implement hook registry and related interfaces for dynamic hook management
This commit is contained in:
@@ -37,7 +37,7 @@ The framework is strictly layered (as detailed in `modding_core_architecture_sum
|
||||
1. `HarmonyPatches` intercept Unity methods (Prefix/Postfix).
|
||||
2. Data is extracted into primitive/struct DTOs.
|
||||
3. Dispatched via `GregEventBus` using canonical hook names: `greg.<Domain>.<Event>`.
|
||||
4. Mappings are defined in `assets/greg_hooks.json` and `NativeEventHooks.cs`.
|
||||
4. Mappings are defined in `assets/greg_hooks.json` and loaded dynamically via `GregHookRegistry` (`IGregHookRegistry`), providing stable hashed Event-IDs for the FFI.
|
||||
|
||||
## 4. Conventions and Rules
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
5. Hook-Payload wird normalisiert (`BuildPayload(...)`) und an Bus/FFI weitergereicht.
|
||||
|
||||
### Canonical Hook-Namen
|
||||
- Mapping zentral in `src/gregSdk/gregNativeEventHooks.cs`.
|
||||
- Mapping erfolgt dynamisch via `GregHookRegistry` (`assets/greg_hooks.json`), was Hash-basierte Event-IDs für das FFI generiert.
|
||||
- Primärquelle für Namen: `greg_hooks.json` + framework-only Ergänzungen via `gregHookName.Create(...)`.
|
||||
- Fallback bei unbekannten IDs: `greg.SYSTEM.UnmappedNativeEvent`.
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using gregCore.Core.Models;
|
||||
|
||||
namespace gregCore.Core.Abstractions;
|
||||
|
||||
public interface IGregHookRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Ruft alle geladenen Hooks ab.
|
||||
/// </summary>
|
||||
IEnumerable<GregHookDef> GetAllHooks();
|
||||
|
||||
/// <summary>
|
||||
/// Sucht einen Hook anhand seines Namens.
|
||||
/// </summary>
|
||||
bool TryGetHook(string name, out GregHookDef hookDef);
|
||||
|
||||
/// <summary>
|
||||
/// Sucht eine Event-ID für einen Hooknamen (FFI Kompatibilität).
|
||||
/// </summary>
|
||||
bool TryGetEventId(string hookName, out int eventId);
|
||||
|
||||
/// <summary>
|
||||
/// Sucht einen Hooknamen anhand der Event-ID (FFI Kompatibilität).
|
||||
/// </summary>
|
||||
bool TryGetHookName(int eventId, out string hookName);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace gregCore.Core.Models;
|
||||
|
||||
public class GregHookPayloadSchema
|
||||
{
|
||||
public string TargetType { get; set; } = string.Empty;
|
||||
public bool IsStatic { get; set; }
|
||||
public string HookSubject { get; set; } = string.Empty;
|
||||
public string ReturnType { get; set; } = string.Empty;
|
||||
public string Parameters { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class GregHookDef
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Legacy { get; set; } = string.Empty;
|
||||
public string PatchTarget { get; set; } = string.Empty;
|
||||
public string Strategy { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string HookSubject { get; set; } = string.Empty;
|
||||
public bool HotLoop { get; set; }
|
||||
public string ClassName { get; set; } = string.Empty;
|
||||
public string MethodName { get; set; } = string.Empty;
|
||||
public string FriendlyAlias { get; set; } = string.Empty;
|
||||
|
||||
public GregHookPayloadSchema? PayloadSchema { get; set; }
|
||||
}
|
||||
|
||||
public class GregHooksManifest
|
||||
{
|
||||
public int Version { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string GeneratedFrom { get; set; } = string.Empty;
|
||||
public List<GregHookDef> Hooks { get; set; } = new();
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using gregCore.Infrastructure.Plugins;
|
||||
using gregCore.Infrastructure.Scripting.Lua;
|
||||
using gregCore.Infrastructure.Scripting.Js;
|
||||
using gregCore.GameLayer.Hooks;
|
||||
using gregCore.Core.Abstractions;
|
||||
|
||||
namespace gregCore.GameLayer.Bootstrap;
|
||||
|
||||
@@ -28,6 +29,7 @@ internal static class GregBootstrapper
|
||||
container.Register<IGregEventBus>(bus);
|
||||
container.Register<IGregConfigService>(new GregConfigService(logger));
|
||||
container.Register<IGregPersistenceService>(new GregPersistenceService(logger));
|
||||
container.Register<IGregHookRegistry>(new GregHookRegistry(logger));
|
||||
|
||||
var apiContext = new global::gregCore.PublicApi.GregApiContext {
|
||||
Logger = logger,
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using gregCore.Core.Abstractions;
|
||||
using gregCore.Core.Models;
|
||||
|
||||
namespace gregCore.Infrastructure.Config;
|
||||
|
||||
/// <summary>
|
||||
/// Registry, die greg_hooks.json zur Laufzeit lädt und verwaltet.
|
||||
/// </summary>
|
||||
public class GregHookRegistry : IGregHookRegistry
|
||||
{
|
||||
private readonly IGregLogger _logger;
|
||||
private readonly List<GregHookDef> _hooks = new();
|
||||
private readonly Dictionary<string, GregHookDef> _hookByName = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, int> _hookToId = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<int, string> _idToHook = new();
|
||||
|
||||
public GregHookRegistry(IGregLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
LoadHooks();
|
||||
}
|
||||
|
||||
private void LoadHooks()
|
||||
{
|
||||
try
|
||||
{
|
||||
string hooksFile = ResolveHooksFilePath();
|
||||
|
||||
if (File.Exists(hooksFile))
|
||||
{
|
||||
_logger.Debug($"Lade Hooks von: {hooksFile}");
|
||||
var content = File.ReadAllText(hooksFile);
|
||||
var manifest = JsonConvert.DeserializeObject<GregHooksManifest>(content);
|
||||
|
||||
if (manifest?.Hooks != null)
|
||||
{
|
||||
_hooks.AddRange(manifest.Hooks);
|
||||
|
||||
foreach (var hook in manifest.Hooks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hook.Name)) continue;
|
||||
|
||||
_hookByName[hook.Name] = hook;
|
||||
|
||||
if (!string.IsNullOrEmpty(hook.FriendlyAlias))
|
||||
{
|
||||
_hookByName[hook.FriendlyAlias] = hook;
|
||||
}
|
||||
|
||||
// Generiere konsistente Hash-basierte ID für FFI
|
||||
int eventId = GetStableHashCode(hook.Name);
|
||||
_hookToId[hook.Name] = eventId;
|
||||
_idToHook[eventId] = hook.Name;
|
||||
}
|
||||
|
||||
_logger.Info($"Erfolgreich {_hooks.Count} Hooks aus greg_hooks.json geladen.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warning($"greg_hooks.json nicht gefunden bei: {hooksFile}. Hook-Registry bleibt leer.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("Fehler beim Laden der greg_hooks.json", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveHooksFilePath()
|
||||
{
|
||||
// 1. Priorität: MelonLoader Mods Ordner
|
||||
var modsDir = global::MelonLoader.Utils.MelonEnvironment.ModsDirectory;
|
||||
if (!string.IsNullOrEmpty(modsDir))
|
||||
{
|
||||
var path = Path.Combine(modsDir, "greg_hooks.json");
|
||||
if (File.Exists(path)) return path;
|
||||
}
|
||||
|
||||
// 2. Priorität: Assembly Location
|
||||
var asmLoc = Assembly.GetExecutingAssembly().Location;
|
||||
var asmDir = Path.GetDirectoryName(asmLoc);
|
||||
if (!string.IsNullOrEmpty(asmDir))
|
||||
{
|
||||
var path = Path.Combine(asmDir, "greg_hooks.json");
|
||||
if (File.Exists(path)) return path;
|
||||
}
|
||||
|
||||
// 3. Fallback: Project assets
|
||||
return Path.Combine(Environment.CurrentDirectory, "assets", "greg_hooks.json");
|
||||
}
|
||||
|
||||
private static int GetStableHashCode(string str)
|
||||
{
|
||||
// Garantiert einen stabilen, positiven 32-bit Hash über verschiedene Laufzeiten hinweg
|
||||
unchecked
|
||||
{
|
||||
int hash1 = 5381;
|
||||
int hash2 = hash1;
|
||||
for (int i = 0; i < str.Length && str[i] != '\0'; i += 2)
|
||||
{
|
||||
hash1 = ((hash1 << 5) + hash1) ^ str[i];
|
||||
if (i == str.Length - 1 || str[i + 1] == '\0') break;
|
||||
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
|
||||
}
|
||||
return Math.Abs(hash1 + (hash2 * 1566083941));
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<GregHookDef> GetAllHooks() => _hooks;
|
||||
|
||||
public bool TryGetHook(string name, out GregHookDef hookDef) =>
|
||||
_hookByName.TryGetValue(name, out hookDef!);
|
||||
|
||||
public bool TryGetEventId(string hookName, out int eventId) =>
|
||||
_hookToId.TryGetValue(hookName, out eventId);
|
||||
|
||||
public bool TryGetHookName(int eventId, out string hookName) =>
|
||||
_idToHook.TryGetValue(eventId, out hookName!);
|
||||
}
|
||||
Reference in New Issue
Block a user