Skip to content

SFUtils_HookManager

Shadowfen edited this page Jul 30, 2026 · 7 revisions

HookManager Documentation

Overview

SFUtils.HookManager is a lightweight registry for managing multiple ESO API hooks from a single object.

Rather than tracking hook handles throughout your addon, HookManager stores each hook in a central registry, allowing you to enable, disable, retrieve, or remove hooks individually or as a group.

Hooks created by HookManager remain permanently registered with the game just as the ESO hooks do. ESO's hook APIs do not provide any mechanism to unregister or remove a hook once it has been installed.

The HookManager-created hook wraps the hook function with an enable/disable capability. Enabling or disabling a hook simply determines whether its callback can execute.


Features

  • Centralized hook registry
  • Unique hook identifiers
  • Pre-hooks
  • Post-hooks
  • Secure post-hooks
  • Enable or disable individual hooks
  • Enable or disable all hooks simultaneously
  • Toggle hook state without re-registering
  • Safe execution for secure hooks using LibSFUtils.safeCall10

Key Benefits

Feature Benefit
Batch Control Enable/disable all hooks at once (ideal for feature toggles)
Dynamic State Toggle individual hooks on/off without re-registration
Safety Wraps callbacks with sfutil.safeCall to prevent UI-breaking errors
Identification Unique ID per hook for easy retrieval and manipulation
Centralized Tracking Single source of truth for all addon hooks

Installation

-- Assuming LibSFUtils is already loaded
local HookManager = LibSFUtils.HookManager
-- Or using the global SF reference (common in ESO addons)
local HookManager = SF.HookManager

Quick Start

-- Create a new HookManager instance
local myHooks = HookManager:New("MyAddon_Hooks")

-- Register a pre-hook
local mailHook = myHooks:PreHook(MAIL_INBOX, "SendMail", function(...)
    zo_dlog("Intercepting mail send...")
    return false  -- Continue with original function
end)

-- Later: disable hook temporarily
myHooks:disable(mailHook.id)

-- Re-enable
myHooks:enable(mailHook.id)

-- On addon unload
myHooks:disableAll()  -- Pause all hooks
myHooks:disableAll()  -- Clean up

Instance Creation

Constructor

local manager = HookManager:New(baseName)
ParameterTypeDefaultDescriptionbaseNamestring"HookManager"Prefix for generating unique hook IDs
Returns: New HookManager instance with internal state:{
    base = "MyAddon_Hooks",  -- Your custom prefix
    cnt = 1,                 -- Auto-incrementing counter
    hooks = {}               -- Internal registry (id → hook table)
}

Example:

-- Different base names allow multiple managers in same addon
local combatHooks = HookManager:New("CombatModule")
local uiHooks = HookManager:New("UI_Module")

Hook Registration Methods

All registration methods return a hook table object that represents the registered hook. All hooks are created with enabled = true by default.

Pre-Hook

Runs before the original function. Returning true from the callback cancels the original function execution.

local hookObj = manager:PreHook(target, method, fn)
Parameter Type Description
target table Object containing the method (e.g., MAIL_INBOX, _G)
method string Method name (case-sensitive)
fn function Callback function (signature matches original)

Returns: Hook table or nil if ID collision detected. Usage Example:

local validateHook = myHooks:PreHook(MAIL_INBOX, "SendMail", function(self, ...)
    -- Validate mail before sending
    local subject = ...
    if #subject > 50 then
        uierror("Subject too long!")
        return true  -- Cancel original
    end
    return false  -- Allow original to run
end)

Post-Hook

Runs after the original function. The callback's return value is ignored (cannot cancel the original).

local hookObj = manager:PostHook(target, method, fn)
Parameter Type Description
target table Object containing the method
method string Method name (case-sensitive)
fn function Callback function

Usage Example:

local logHook = myHooks:PostHook(SKILL_BAR, "RefreshSkills", function(...)
    zo_dlog("Skill bar refreshed at " .. ZO_GetTimeString())
end)

Secure Post-Hook

Uses SecurePostHook for combat/security-critical functions. Errors in the callback are swallowed via sfutil.safeCall10.

local hookObj = manager:SecurePostHook(target, method, fn)
Parameter Type Description
target table Object containing the method (e.g., COMBAT)
method string Method name (case-sensitive)
fn function Callback function

Important: Secure hooks cannot be cancelled and use sfutil.safeCall10 for error handling.

Usage Example:

local combatHook = myHooks:SecurePostHook(_G, "CastAbility", function(...)
    -- Track ability casts (secure context)
    LogAbilityUsed(...)
end)

Hook Table Properties

The object returned by registration methods contains:

Property Type Description
id string Unique identifier (e.g., "MyAddon_Hooks_1")
target table The object that owns the method
method string Method name (case-sensitive)
fn function Original callback function
kind string Hook type ("pre", "post", or "secure")
enabled boolean Whether hook is currently active

Metatable: Hook objects inherit from HookManager, allowing method calls:

-- You can call manager methods passing the hook table directly
-- (though typically you'll use the id)
local hook = myHooks:PreHook(...)
d(hook.id)           -- "MyAddon_Hooks_1"
d(hook.kind)         -- "pre"
d(hook.target)       -- MAIL_INBOX (table)
d(hook.method)       -- "SendMail" (string)

Hook Management Methods

Individual Hook Control

Method Description
manager:get(id) Retrieve hook table by ID (returns nil if not found)
manager:enable(id) Activate specific hook
manager:disable(id) Deactivate specific hook (callback skipped, but registered)
manager:toggle(id) Flip hook state (active ↔ inactive)
manager:remove(id) Completely remove hook from registry

Examples:

-- Get hook info
local hook = myHooks:get("MyAddon_Hooks_1")
if hook then
    d(hook.kind, hook.method, hook.enabled)
end

-- Toggle debug logging
myHooks:toggle(logHook.id)

-- Remove hook permanently
myHooks:remove(logHook.id)

Batch Control

Method Description
manager:enableAll() Activate all registered hooks
manager:disableAll() Deactivate all registered hooks
manager:toggleAll() Flip state of all registered hooks

Use Cases:

-- Pause all hooks when addon is disabled
local function OnAddonUnload(...)
    myHooks:disableAll()  -- Stop all callbacks
    -- Optionally remove if cleanup needed
end

-- Resume all hooks when addon reloads
local function OnAddonLoaded(...)
    myHooks:enableAll()   -- Reactivate all
end

-- Toggle all debugging at once
local function ToggleDebug()
    myHooks:toggleAll()
end

Complete Workflow Example

-- =============================================================================
-- MyAddon Main File (EsoMain.txt / Init.lua)
-- =============================================================================

local MY_ADDON_NAME = "MyAddon"
local myHooks = nil

-- Event Handler for Addon Loading
local function OnAddonLoaded(eventCode, addonName)
    if addonName ~= MY_ADDON_NAME then return end
    
    -- Initialize HookManager
    myHooks = HookManager:New(MY_ADDON_NAME .. "_Hooks")
    
    -- Register hooks
    myHooks:PreHook(MAIL_INBOX, "SendMail", OnMailValidate)
    myHooks:PostHook(INVENTORY_MANAGER, "UpdateSlot", OnInventoryUpdated)
    myHooks:SecurePostHook(_G, "CastAbility", OnAbilityUsed)
    
    zo_dlog("MyAddon: All hooks registered")
end

-- Event Handler for Addon Unloading
local function OnAddonUnload(eventCode, addonName)
    if addonName ~= MY_ADDON_NAME then return end
    
    -- Disable all hooks before unload
    if myHooks then
        myHooks:disableAll()
        myHooks = nil
    end
    
    zo_dlog("MyAddon: Hooks cleaned up")
end

-- Hook Callbacks
local function OnMailValidate(self, ...)
    zo_dlog("Validating mail send...")
    return false  -- Allow original
end

local function OnInventoryUpdated(...)
    zo_dlog("Inventory slot updated")
end

local function OnAbilityUsed(...)
    zo_dlog("Player cast an ability")
end

-- Register for addon events
EVENT_MANAGER:RegisterForEvent(
    MY_ADDON_NAME, 
    EVENT_ADD_ON_LOADED, 
    OnAddonLoaded
)

EVENT_MANAGER:RegisterForEvent(
    MY_ADDON_NAME, 
    EVENT_ADD_ON_UNLOADING, 
    OnAddonUnload
)

Debugging & Inspection

While there's no built-in describeAll method in the current implementation, you can add debugging utilities:

-- Add to your addon code
function InspectHooks(manager)
    d("=== Hook Manager: " .. manager.base .. " ===")
    for id, hook in pairs(manager.hooks) do
        local status = hook.enabled and "ACTIVE" or "INACTIVE"
        d(string.format(
            "%s - %s.%s [%s]",
            id,
            hook.kind,
            hook.method,
            status
        ))
    end
    d("Total hooks: " .. #manager.hooks)
end

-- Usage
InspectHooks(myHooks)

Best Practices

✅ Do

  • Use unique base names for different modules (CombatModule_Hooks, UIModule_Hooks)
  • Disable hooks on unload to prevent ghost callbacks
  • Store hook IDs if you need to reference them later
  • Test hooks with errors to ensure safeCall protects your game UI
-- Good: Store reference for later use
local inventoryHook = myHooks:PostHook(INVENTORY_MANAGER, "UpdateSlot", OnUpdate)
inventoryHookId = inventoryHook.id  -- Save for later

-- Later...
myHooks:toggle(inventoryHookId)

❌ Don't

  • Register the same hook twice with same ID (will return nil)
  • Assume hooks are always active (check enabled property)
  • Forget to clean up on addon unload (memory leak risk)
  • Mix hook types (pre/post/secure) without understanding behavior differences

Limitations & Known Issues

Issue Workaround
Hook IDs must be unique per manager Use different base names for separate managers
Hooks remain registered even when disabled Use remove(id) if you need to truly unregister
No built-in hook description/debug output Add your own inspection utility (see above)
Cannot retrieve original function (only callback) Store original function reference separately if needed

API Reference Summary

Class Methods

Signature Purpose
HookManager:New(baseName?) Create new instance
manager:PreHook(target, method, fn) Register pre-hook
manager:PostHook(target, method, fn) Register post-hook
manager:SecurePostHook(target, method, fn) Register secure hook

Instance Methods

Signature Purpose
manager:get(id) Get hook by ID
manager:enable(id) Activate hook
manager:disable(id) Deactivate hook
manager:toggle(id) Flip hook state
manager:remove(id) Remove hook permanently
manager:enableAll() Activate all hooks
manager:disableAll() Deactivate all hooks
manager:toggleAll() Flip all hook states

Hook Object Properties

Property Type Read/Write
id string Read-only
target table Read-only
method string Read-only
fn function Read-only
kind string Read-only
enabled boolean Read/Write

Related Resources

Full LibSFUtils Documentation - Complete library reference ESO API Reference - Official ESO addon documentation HookManager Source - Raw implementation

Generated from SFUtils_HookManager.lua. Last updated: July 2026

Clone this wiki locally