Skip to content

SFUtils_HookManager

Shadowfen edited this page Jul 29, 2026 · 7 revisions

HookManager Documentation

Overview

HookManager is a centralized registry-based utility for efficiently managing multiple Elder Scrolls Online (ESO) API hooks. Instead of tracking individual hook variables scattered throughout your code, HookManager stores all hooks in a single manager instance, enabling powerful batch control and lifecycle management.

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

Dependencies

  • Requires LibSFUtils (accessed via global SF or LibSFUtils)

Installationlua

-- 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)

ParameterTypeDescriptiontargettableObject containing the methodmethodstringMethod name (case-sensitive)fnfunctionCallback 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 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