dobrograd-13-06-2022/garrysmod/addons/util-dlib/lua/dlib/modules/hook.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

1555 lines
36 KiB
Lua

-- Copyright (C) 2017-2020 DBotThePony
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do so,
-- subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.
-- performance and functionality to the core
jit.on()
local pairs = pairs
local ipairs = ipairs
local print = print
local debug = debug
local tostring = tostring
local tonumber = tonumber
local type = type
local traceback = debug.traceback
local DLib = DLib
local unpack = unpack
local gxpcall = xpcall
local SysTime = SysTime
DLib.hook = DLib.hook or {}
local ghook = _G.hook
local hook = DLib.hook
hook.PROFILING = false
hook.PROFILING_RESULTS_EXISTS = false
hook.PROFILE_STARTED = 0
hook.PROFILE_ENDS = 0
hook.__tableOptimized = hook.__tableOptimized or {}
hook.__table = hook.__table or {}
hook.__tableGmod = hook.__tableGmod or {}
hook.__tableModifiersPost = hook.__tableModifiersPost or {}
hook.__tableModifiersPostOptimized = hook.__tableModifiersPostOptimized or {}
hook.__disabled = hook.__disabled or {}
local __table = hook.__table
local __disabled = hook.__disabled
local __tableOptimized = hook.__tableOptimized
local __tableGmod = hook.__tableGmod
local __tableModifiersPost = hook.__tableModifiersPost
local __tableModifiersPostOptimized = hook.__tableModifiersPostOptimized
-- ULib compatibility
-- ugh
_G.HOOK_MONITOR_HIGH = -2
_G.HOOK_HIGH = -1
_G.HOOK_NORMAL = 0
_G.HOOK_LOW = 1
_G.HOOK_MONITOR_LOW = 2
local maximalPriority = -10
local minimalPriority = 10
--[[
@doc
@fname hook.GetTable
@replaces
@returns
table: `table<string, table<string, function>>`
]]
local function GetTable()
return __tableGmod
end
--[[
@doc
@fname hook.GetDLibOptimizedTable
@returns
table: of `eventName -> array of functions`
]]
function hook.GetDLibOptimizedTable()
return __tableOptimized
end
--[[
@doc
@fname hook.GetDLibModifiers
@returns
table
]]
function hook.GetDLibModifiers()
return __tableModifiersPost
end
--[[
@doc
@fname hook.GetDLibSortedTable
@returns
table
]]
function hook.GetDLibSortedTable()
return __tableOptimized
end
--[[
@doc
@replaces
@fname hook.GetULibTable
@desc
For mods like DarkRP
Althrough, DarkRP can use DLib's Post Modifiers instead for things that
DarkRP currently do with table provided by `GetULibTable`
@enddesc
@returns
table
]]
function hook.GetULibTable()
return __table
end
--[[
@doc
@fname hook.GetDLibTable
@returns
table
]]
function hook.GetDLibTable()
return __table
end
--[[
@doc
@fname hook.GetDisabledHooks
@returns
table
]]
function hook.GetDisabledHooks()
return __disabled
end
local oldHooks
if ghook ~= DLib.ghook then
if ghook.GetULibTable then
oldHooks = ghook.GetULibTable()
else
oldHooks = {}
for event, eventData in pairs(ghook.GetTable()) do
oldHooks[event] = {}
oldHooks[event][0] = {}
local target = oldHooks[event][0]
for hookID, hookFunc in pairs(eventData) do
target[hookID] = {fn = hookFunc}
end
end
end
end
local function transformStringID(stringID, event)
if type(stringID) == 'number' then
stringID = tostring(stringID)
end
if type(stringID) == 'boolean' then
error('booleans are not allowed as hookID value', 3)
end
if type(stringID) ~= 'string' then
local success = pcall(function()
stringID.IsValid(stringID)
end)
if not success then
--if DLib.DEBUG_MODE:GetBool() then
--DLib.Message(traceback('hook.Add - hook ID is not a string and not a valid object! Using tostring() instead. ' .. type(stringID)))
error('hook.Add - hook ID is not a string and not a valid object! ' .. type(stringID), 3)
--end
stringID = tostring(stringID)
end
end
return stringID
end
--[[
@doc
@fname hook.DisableHook
@args string event, any hookID
@returns
boolean
]]
function hook.DisableHook(event, stringID)
assert(type(event) == 'string', 'hook.DisableHook - event is not a string! ' .. type(event))
if not stringID then
if __disabled[event] then
return false
end
__disabled[event] = true
return true
else
if not __table[event] then return false end
stringID = transformStringID(stringID, event)
for priority = -10, 10 do
if __table[event][priority] and __table[event][priority][stringID] then
local wasDisabled = __table[event][priority][stringID].disabled
__table[event][priority][stringID].disabled = true
hook.Reconstruct(event)
return not wasDisabled
end
end
return false
end
end
--[[
@doc
@fname hook.DisableAllHooksExcept
@args string event, any hookID
@returns
boolean
]]
function hook.DisableAllHooksExcept(event, stringID)
assert(type(event) == 'string', 'hook.DisableAllHooksExcept - event is not a string! ' .. type(event))
assert(type(stringID) ~= 'nil', 'hook.DisableAllHooksExcept - ID is not a valid value! ' .. type(stringID))
if not __table[event] then return false end
stringID = transformStringID(stringID, event)
for priority = -10, 10 do
if __table[event][priority] then
for id, hookData in pairs(__table[event][priority]) do
if id ~= stringID then
hookData.disabled = true
end
end
end
end
hook.Reconstruct(event)
return true
end
--[[
@doc
@fname hook.DisableHooksByPredicate
@args string event, function predicate
@desc
Predicate should return true to disable hook
and return false to enable hook
Arguments passed to predicate are `event, id, priority, dlibHookData`
@enddesc
@returns
boolean
]]
function hook.DisableHooksByPredicate(event, predicate)
assert(type(event) == 'string', 'hook.DisableAllHooksByPredicate - event is not a string! ' .. type(event))
assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate))
if not __table[event] then return false end
for priority = -10, 10 do
if __table[event][priority] then
for id, hookData in pairs(__table[event][priority]) do
local reply = predicate(event, id, priority, hookData)
if reply then
hookData.disabled = true
end
end
end
end
hook.Reconstruct(event)
return true
end
--[[
@doc
@fname hook.DisableAllHooksByPredicate
@args function predicate
@desc
same as `hook.DisableHooksByPredicate`, except it is ran for all events
pass `function() return true end` as predicate to disable **EVERYTHING**
@enddesc
@returns
boolean
]]
function hook.DisableAllHooksByPredicate(predicate)
assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate))
for event, eventData in pairs(__table) do
for priority = -10, 10 do
if eventData[priority] then
for id, hookData in pairs(eventData[priority]) do
local reply = predicate(event, id, priority, hookData)
if reply then
hookData.disabled = true
end
end
end
end
hook.Reconstruct(event)
end
return true
end
--[[
@doc
@fname hook.EnableHooksByPredicate
@args string event, function predicate
@desc
counterpart of `hook.DisableHooksByPredicate`
@enddesc
@returns
boolean
]]
function hook.EnableHooksByPredicate(event, predicate)
assert(type(event) == 'string', 'hook.DisableAllHooksByPredicate - event is not a string! ' .. type(event))
assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate))
if not __table[event] then return false end
for priority = -10, 10 do
if __table[event][priority] then
for id, hookData in pairs(__table[event][priority]) do
local reply = predicate(event, id, priority, hookData)
if reply then
hookData.disabled = false
end
end
end
end
hook.Reconstruct(event)
return true
end
--[[
@doc
@fname hook.EnableAllHooksByPredicate
@args function predicate
@desc
counterpart of `hook.DisableAllHooksByPredicate`
@enddesc
@returns
boolean
]]
function hook.EnableAllHooksByPredicate(predicate)
assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate))
for event, eventData in pairs(__table) do
for priority = -10, 10 do
if eventData[priority] then
for id, hookData in pairs(eventData[priority]) do
local reply = predicate(event, id, priority, hookData)
if reply then
hookData.disabled = false
end
end
end
end
hook.Reconstruct(event)
end
return true
end
--[[
@doc
@fname hook.EnableAllHooks
@returns
table: enabled hooks (copy of __disabled)
]]
function hook.EnableAllHooks()
local toenable = {}
for k, v in pairs(__disabled) do
table.insert(toenable, k)
end
for i, v in ipairs(toenable) do
__disabled[v] = nil
end
for event, eventData in pairs(__table) do
for priority = -10, 10 do
if eventData[priority] then
for id, hookData in pairs(eventData[priority]) do
hookData.disabled = false
end
end
end
hook.Reconstruct(event)
end
return toenable
end
--[[
@doc
@fname hook.EnableHook
@args string event, any hookID
@returns
boolean
]]
function hook.EnableHook(event, stringID)
assert(type(event) == 'string', 'hook.EnableHook - event is not a string! ' .. type(event))
if not stringID then
if not __disabled[event] then
return false
end
__disabled[event] = nil
return true
end
if not __table[event] then return false end
stringID = transformStringID(stringID, event)
for priority = -10, 10 do
if __table[event][priority] and __table[event][priority][stringID] then
local wasDisabled = __table[event][priority][stringID].disabled
__table[event][priority][stringID].disabled = false
hook.Reconstruct(event)
return wasDisabled
end
end
return false
end
--[[
@doc
@fname hook.Add
@replaces
@args string event, any hookID, function callback, number priority = 0
@desc
Refer to !g:hook.Add for main information
`priority` can be a number within range of -10 to 10 inclusive
`hookID` **can** be a number, unlike default gmod behavior, but can not be a boolean
prints tracebacks when some of arguments are invalid instead of silent fail, unlike original hook
throws an error when something goes horribly wrong instead of silent fail, unlike original hook
if priority argument is omitted, then it uses `0` as priority (if hook was not defined before)
and use previous priority if hook already exists (assuming we want to overwrite old hook definition) unlike ULib's hook
this can be useful with software which can provide hook benchmarking by re-defining every single hook using hook.Add
and it doesn't know about hook priorities
@enddesc
]]
function hook.Add(event, stringID, funcToCall, priority)
if type(event) ~= 'string' then
--if DLib.DEBUG_MODE:GetBool() then
DLib.Message(traceback('hook.Add - event is not a string! ' .. type(event)))
--end
return
end
__table[event] = __table[event] or {}
if type(funcToCall) ~= 'function' then
--if DLib.DEBUG_MODE:GetBool() then
DLib.Message(traceback('hook.Add - function is not a function! ' .. type(funcToCall)))
--end
return
end
stringID = transformStringID(stringID, event)
for priority = maximalPriority, minimalPriority do
local eventsTable = __table[event][priority]
if eventsTable and eventsTable[stringID] then
if not priority then
priority = eventsTable[stringID].priority
end
eventsTable[stringID] = nil
end
end
priority = priority or 0
if type(priority) ~= 'number' then
priority = tonumber(priority) or 0
end
priority = math.Clamp(math.floor(priority), maximalPriority, minimalPriority)
local hookData = {
event = event,
priority = priority,
funcToCall = funcToCall,
fn = funcToCall, -- ULib
isstring = type(stringID) == 'string', -- ULib
id = stringID,
idString = tostring(stringID),
registeredAt = SysTime(),
typeof = type(stringID) == 'string'
}
__table[event][priority] = __table[event][priority] or {}
__table[event][priority][stringID] = hookData
__tableGmod[event] = __tableGmod[event] or {}
__tableGmod[event][stringID] = funcToCall
hook.Reconstruct(event)
return
end
hook._O_SALT = hook._O_SALT or -0xFFFFFFF
--[[
@doc
@fname hook.Once
@replaces
@args string event, function callback, number priority = 0
@desc
`hook.Add`, but function would be called back only once.
@enddesc
]]
function hook.Once(event, funcToCall, priority)
hook._O_SALT = hook._O_SALT + 1
local id = 'hook.Once.' .. hook._O_SALT
hook.Add(event, id, function(...)
hook.Remove(event, id)
return funcToCall(...)
end, priority)
end
--[[
@doc
@fname hook.Remove
@replaces
@args string event, any hookID
]]
function hook.Remove(event, stringID)
if type(event) ~= 'string' then
--if DLib.DEBUG_MODE:GetBool() then
DLib.Message(traceback('hook.Remove - event is not a string! ' .. type(event)))
--end
return
end
if type(stringID) == 'nil' then
--if DLib.DEBUG_MODE:GetBool() then
DLib.Message(traceback('hook.Remove - hook id is nil!'))
--end
return
end
if not __table[event] then return end
__tableGmod[event] = __tableGmod[event] or {}
stringID = transformStringID(stringID, event)
__tableGmod[event][stringID] = nil
for priority = maximalPriority, minimalPriority do
local eventsTable = __table[event][priority]
if eventsTable ~= nil then
local oldData = eventsTable[stringID]
if oldData ~= nil then
eventsTable[stringID] = nil
hook.Reconstruct(event)
return
end
end
end
end
--[[
@doc
@fname hook.AddPostModifier
@args string event, any hookID, function callback
@desc
Unique feature of DLib hook
This allows you to define "post-hooks"
hooks which transform data returned by previous hook
Hooks bound to event will receive values returned by upvalue hook.
**This is meant for advanced users only. Use with care and don't try anything stupid!**
If you somehow don't want to mess with passed arguments, **you must return them back**
otherwise engine/hook.Run caller/other post modifiers will receive empty values, like none of hooks returned values
this can be useful for doing "final fixes" to data
like custom final logic for PlayerSay (local chat/roleyplay for example) or fixes to CalcView
using this will not affect admin/fun mods (they will use hook (e.g. PlayerSay) as usual)
and final fixes will always work too despite type of hook
*Limitation of this hook is that it can not see original arguments passed to* `hook.Run`
@enddesc
@returns
boolean
table: hookData
]]
function hook.AddPostModifier(event, stringID, funcToCall)
__tableModifiersPost[event] = __tableModifiersPost[event] or {}
if type(event) ~= 'string' then
--if DLib.DEBUG_MODE:GetBool() then
DLib.Message(traceback('hook.AddPostModifier - event is not a string! ' .. type(event)))
--end
return false
end
if type(funcToCall) ~= 'function' then
--if DLib.DEBUG_MODE:GetBool() then
DLib.Message(traceback('hook.AddPostModifier - function is not a function! ' .. type(funcToCall)))
--end
return false
end
stringID = transformStringID(stringID, event)
local hookData = {
event = event,
funcToCall = funcToCall,
id = stringID,
idString = tostring(stringID),
registeredAt = SysTime(),
typeof = type(stringID) == 'string'
}
__tableModifiersPost[event][stringID] = hookData
hook.ReconstructPostModifiers(event)
return true, hookData
end
--[[
@doc
@fname hook.RemovePostModifier
@args string event, any hookID
@returns
boolean
table: hookData
]]
function hook.RemovePostModifier(event, stringID)
if not __tableModifiersPost[event] then return false end
stringID = transformStringID(stringID, event)
if __tableModifiersPost[event][stringID] then
local old = __tableModifiersPost[event][stringID]
__tableModifiersPost[event][stringID] = nil
hook.ReconstructPostModifiers(event)
return true, old
end
return false
end
--[[
@doc
@fname hook.ReconstructPostModifiers
@args string event
@internal
@desc
builds optimized hook table
@enddesc
@returns
table: sorted array of functions
table: sorted array of hookData
]]
function hook.ReconstructPostModifiers(eventToReconstruct)
if not eventToReconstruct then
for event, tab in pairs(__tableModifiersPost) do
hook.ReconstructPostModifiers(event)
end
return
end
__tableModifiersPostOptimized[eventToReconstruct] = {}
local event = __tableModifiersPost[eventToReconstruct]
local ordered = {}
if event then
for stringID, hookData in pairs(event) do
if hookData.typeof == false then
if hookData.id:IsValid() then
table.insert(ordered, hookData)
else
event[stringID] = nil
end
else
table.insert(ordered, hookData)
end
end
end
local cnt = #ordered
if cnt == 0 then
__tableModifiersPostOptimized[eventToReconstruct] = nil
else
local target = __tableModifiersPostOptimized[eventToReconstruct]
for i = 1, cnt do
table.insert(target, ordered[i].funcToCall)
end
end
return __tableModifiersPostOptimized, ordered
end
--[[
@doc
@fname hook.ListAllHooks
@args boolean includeDisabled = true
@returns
table: sorted array of hookData
]]
function hook.ListAllHooks(includeDisabled)
if includeDisabled == nil then includeDisabled = true end
local output = {}
for event, priorityTable in pairs(__table) do
for priority = maximalPriority, minimalPriority do
local hookList = priorityTable[priority]
if hookList then
for stringID, hookData in pairs(hookList) do
if not hookData.disabled or includeDisabled then
table.insert(output, hookData)
end
end
end
end
end
return output
end
--[[
@doc
@fname hook.Reconstruct
@args string event
@internal
@desc
builds optimized hook table
@enddesc
@returns
table: sorted array of functions
table: sorted array of hookData
]]
function hook.Reconstruct(eventToReconstruct)
if not eventToReconstruct then
for event, data in pairs(__table) do
hook.Reconstruct(event)
end
return
end
__tableOptimized[eventToReconstruct] = {}
local ordered = {}
local priorityTable = __table[eventToReconstruct]
local inboundgmod = __tableGmod[eventToReconstruct]
if priorityTable then
for priority = maximalPriority, minimalPriority do
local hookList = priorityTable[priority]
if hookList then
for stringID, hookData in pairs(hookList) do
if not hookData.disabled then
if hookData.typeof == false then
if hookData.id:IsValid() then
table.insert(ordered, hookData)
else
hookList[stringID] = nil
inboundgmod[stringID] = nil
end
else
table.insert(ordered, hookData)
end
end
end
end
end
end
local cnt = #ordered
if cnt == 0 then
__tableOptimized[eventToReconstruct] = nil
else
local target = __tableOptimized[eventToReconstruct]
for i = 1, cnt do
local callable
local hookData = ordered[i]
if type(hookData.id) == 'string' then
callable = hookData.funcToCall
else
local self = hookData.id
local upfuncCallableSelf = hookData.funcToCall
callable = function(...)
if not self:IsValid() then
hook.Remove(hookData.event, self)
return
end
return upfuncCallableSelf(self, ...)
end
end
if hook.PROFILING then
local THIS_RUNTIME = 0
local THIS_CALLS = 0
local upfuncProfiled = callable
callable = function(...)
THIS_CALLS = THIS_CALLS + 1
local t = SysTime()
local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = upfuncProfiled(...)
local t2 = SysTime()
THIS_RUNTIME = THIS_RUNTIME + (t2 - t)
return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M
end
hookData.profileEnds = function()
hookData.THIS_RUNTIME = THIS_RUNTIME
hookData.THIS_CALLS = THIS_CALLS
end
end
table.insert(target, callable)
end
end
return __tableOptimized, ordered
end
local function Call(...)
return hook.Call2(...)
end
local function Run(...)
return hook.Run2(...)
end
local __breakage1 = {
'HUDPaint',
'PreDrawHUD',
'PostDrawHUD',
'Initialize',
'InitPostEntity',
'PreGamemodeInit',
'PostGamemodeInit',
'PostGamemodeInitialize',
'PreGamemodeInitialize',
'PostGamemodeLoaded',
'PreGamemodeLoaded',
'PostRenderVGUI',
'OnGamemodeLoaded',
'CreateMove',
'StartCommand',
'SetupMove',
}
local __breakage = {}
for i, str in ipairs(__breakage1) do
__breakage[str] = true
end
-- these hooks can't return any values
hook.StaticHooks = __breakage
--[[
@doc
@fname hook.HasHooks
@args string event
@returns
boolean
]]
function hook.HasHooks(event)
return __tableOptimized[event] ~= nil and #__tableOptimized[event] ~= 0
end
--[[
@doc
@fname hook.CallStatic
@args string event, table hookTable, vararg arguments
@desc
functions called can not interrupt call loop by returning arguments
can not return arguments
internall used to call some of most popular hooks
which will break the game if at least one function in hook list will return value
@enddesc
@internal
]]
function hook.CallStatic(event, hookTable, ...)
local post = __tableModifiersPostOptimized[event]
local events = __tableOptimized[event]
if events == nil then
if hookTable == nil then
return
end
local gamemodeFunction = hookTable[event]
if gamemodeFunction == nil then
return
end
return gamemodeFunction(hookTable, ...)
end
local i = 1
local nextevent = events[i]
::loop::
nextevent(...)
i = i + 1
nextevent = events[i]
if nextevent ~= nil then
goto loop
end
if hookTable == nil then
return
end
local gamemodeFunction = hookTable[event]
if gamemodeFunction == nil then
return
end
return gamemodeFunction(hookTable, ...)
end
--[[
@doc
@fname hook.Call
@replaces
@args string event, table hookTable, vararg arguments
@returns
vararg: values
]]
function hook.Call2(event, hookTable, ...)
if __disabled[event] then
return
end
ITERATING = event
if __breakage[event] == true then
hook.CallStatic(event, hookTable, ...)
return
end
local post = __tableModifiersPostOptimized[event]
local events = __tableOptimized[event]
if events == nil then
if hookTable == nil then
return
end
local gamemodeFunction = hookTable[event]
if gamemodeFunction == nil then
return
end
if post == nil then
return gamemodeFunction(hookTable, ...)
end
local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = gamemodeFunction(hookTable, ...)
local i = 1
local nextevent = post[i]
::post_mloop1::
Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M)
i = i + 1
nextevent = post[i]
if nextevent ~= nil then
goto post_mloop1
end
return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M
end
local i = 1
local nextevent = events[i]
::loop::
local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(...)
if Q ~= nil then
if post == nil then
return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M
end
local i = 1
local nextevent = post[i]
::post_mloop2::
Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M)
i = i + 1
nextevent = post[i]
if nextevent ~= nil then
goto post_mloop2
end
return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M
end
i = i + 1
nextevent = events[i]
if nextevent ~= nil then
goto loop
end
if hookTable == nil then
return
end
local gamemodeFunction = hookTable[event]
if gamemodeFunction == nil then
return
end
if post == nil then
return gamemodeFunction(hookTable, ...)
end
local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = gamemodeFunction(hookTable, ...)
local i = 1
local nextevent = post[i]
::post_mloop3::
Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M)
i = i + 1
nextevent = post[i]
if nextevent ~= nil then
goto post_mloop3
end
return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M
end
--[[
@doc
@fname hook.Run
@replaces
@args string event, vararg arguments
@returns
vararg: values
]]
--[[
@doc
@fname gamemode.Call
@replaces
@args string event, vararg arguments
@returns
vararg: values
]]
if gmod then
local GetGamemode = gmod.GetGamemode
function hook.Run2(event, ...)
return hook.Call2(event, GetGamemode(), ...)
end
function gamemode.Call(event, ...)
local gm = GetGamemode()
if gm == nil then return false end
if gm[event] == nil then return false end
return hook.Call2(event, gm, ...)
end
else
function hook.Run2(event, ...)
return hook.Call2(event, GAMEMODE, ...)
end
function gamemode.Call()
return false
end
end
for k, v in pairs(hook) do
if type(v) == 'function' then
hook[k:sub(1, 1):lower() .. k:sub(2)] = v
end
end
-- Engine permanently remembers function address
-- So we need to transmit the call to our subfunction in order to modify it on the fly (with no runtime costs because JIT is <3)
-- and local "hook" will never point at wrong table
hook.Call = Call
hook.Run = Run
hook.GetTable = GetTable
if oldHooks then
for event, priorityTable in pairs(oldHooks) do
for priority, hookTable in pairs(priorityTable) do
for hookID, hookFunc in pairs(hookTable) do
hook.Add(event, hookID, hookFunc.fn, priority)
end
end
end
end
setmetatable(hook, {
__call = function(self, ...)
return self.Add(...)
end
})
if ghook ~= DLib.ghook and ents.GetCount() < 10 then
DLib.ghook = ghook
for k, v in pairs(ghook) do
rawset(ghook, k, nil)
end
setmetatable(DLib.ghook, {
__index = hook,
__newindex = function(self, key, value)
if hook[key] == value then return end
if DLib.DEBUG_MODE:GetBool() then
DLib.Message(traceback('DEPRECATED: Do NOT mess with hook system directly! https://goo.gl/NDAQqY\nReport this message to addon author which is involved in this stack trace:\nhook.' .. tostring(key) .. ' (' .. tostring(hook[key]) .. ') -> ' .. tostring(value), 2))
end
local status = hook.Call('DLibHookChange', nil, key, value)
if status == false then return end
rawset(hook, key, value)
end,
__call = function(self, ...)
return self.Add(...)
end
})
elseif ghook ~= DLib.ghook then
function ghook.AddPostModifier()
end
end
DLib.benchhook = {
Add = hook.Add,
Call = hook.Call2,
Run = hook.Run2,
Remove = hook.Remove,
GetTable = hook.GetTable,
}
local function lua_findhooks(eventName, ply)
DLib.MessagePlayer(ply, '----------------------------------')
DLib.MessagePlayer(ply, string.format('Finding %s hooks for event %q', CLIENT and 'CLIENTSIDE' or 'SERVERSIDE', eventName))
local tableToUse = __table[eventName]
if tableToUse and table.Count(tableToUse) ~= 0 then
for priority = maximalPriority, minimalPriority do
local hookList = tableToUse[priority]
if hookList then
for stringID, hookData in pairs(hookList) do
local info = debug.getinfo(hookData.funcToCall)
DLib.MessagePlayer(ply,
string.format(
'\t\t%q [%s] at %p (%s: %i->%i)',
stringID,
priority,
hookData.funcToCall,
info.source,
info.linedefined,
info.lastlinedefined
)
)
end
end
end
else
DLib.MessagePlayer(ply, 'No hooks defined for specified event')
end
DLib.MessagePlayer(ply, '----------------------------------')
end
--[[
@doc
@fname hook.GetDumpStr
@internal
@returns
string
]]
function hook.GetDumpStr()
local lines = {}
local sorted = {}
for eventName, eventData in pairs(__table) do
table.insert(sorted, eventName)
end
table.sort(sorted)
for i, eventName in ipairs(sorted) do
local eventData = __table[eventName]
for priority = maximalPriority, minimalPriority do
local hookList = eventData[priority]
if hookList then
local llines = {}
table.insert(lines, '// Begin list hooks of event ' .. eventName)
for stringID, hookData in pairs(hookList) do
local info = debug.getinfo(hookData.funcToCall)
table.insert(llines,
string.format(
'\t%q [%s] at %p (%s: %i->%i)',
tostring(stringID),
tostring(priority),
hookData.funcToCall,
info.source,
info.linedefined,
info.lastlinedefined
)
)
end
table.sort(llines)
table.append(lines, llines)
table.insert(lines, '// End list hooks of event ' .. eventName .. '\n')
end
end
end
return table.concat(lines, '\n')
end
local function lua_findhooks_cl(ply, cmd, args)
if not game.SinglePlayer() and IsValid(ply) and ply:IsPlayer() and not ply:IsAdmin() then return end
if not args[1] then
DLib.Message('No event name were provided!')
return
end
lua_findhooks(table.concat(args, ' '):trim(), ply)
end
local function lua_findhooks_sv(ply, cmd, args)
if not game.SinglePlayer() and IsValid(ply) and ply:IsPlayer() and not ply:IsAdmin() then return end
if not args[1] then
DLib.Message('No event name were provided!')
return
end
lua_findhooks(table.concat(args, ' '):trim(), ply)
end
do
local function autocomplete(cmd, args)
args = args:lower():trim()
if args[1] == '"' then
args = args:sub(2)
end
if args[#args] == '"' then
args = args:sub(1, #args - 1)
end
local output = {}
for k, v in pairs(__tableGmod) do
if k:lower():startsWith(args) then
table.insert(output, cmd .. ' "' .. k .. '"')
end
end
table.sort(output)
return output
end
timer.Simple(0, function()
if CLIENT then
concommand.Add('lua_findhooks_cl', lua_findhooks_cl, autocomplete)
else
concommand.Add('lua_findhooks', lua_findhooks_sv, autocomplete)
end
end)
end
local function printProfilingResults(ply)
local deftable = {}
local totalRuntime = 0
for i, hookData in ipairs(hook.ListAllHooks(false)) do
deftable[hookData.event] = deftable[hookData.event] or {runtime = 0, calls = 0, list = {}, name = hookData.event}
table.insert(deftable[hookData.event].list, hookData)
deftable[hookData.event].runtime = deftable[hookData.event].runtime + hookData.THIS_RUNTIME
totalRuntime = totalRuntime + hookData.THIS_RUNTIME
deftable[hookData.event].calls = deftable[hookData.event].calls + hookData.THIS_CALLS
end
local sortedtable = {}
for event, eventTable in pairs(deftable) do
table.sort(eventTable.list, function(a, b)
return a.THIS_RUNTIME > b.THIS_RUNTIME
end)
table.insert(sortedtable, eventTable)
end
table.sort(sortedtable, function(a, b)
return a.runtime > b.runtime
end)
DLib.MessagePlayer(ply, '-----------------------------------')
DLib.MessagePlayer(ply, '------ HOOK PROFILING REPORT ------')
DLib.MessagePlayer(ply, '-----------------------------------')
local time = hook.PROFILE_ENDS - hook.PROFILE_STARTED
for pos, eventTable in ipairs(sortedtable) do
if pos > 10 then
DLib.MessagePlayer(ply, '... tail of events ... (', #sortedtable - 10, ' are not shown)')
break
end
DLib.MessagePlayer(ply, '/// ' .. eventTable.name .. ': Runtime position: ', pos, string.format(' (%.2f%% of game runtime); - Total hook calls: ', (eventTable.runtime / time) * 100), eventTable.calls,
string.format('; Total runtime: %.2f milliseconds (~%.2f microseconds per hook call on average)', eventTable.runtime * 1000, (eventTable.runtime * 1000000) / eventTable.calls))
for pos2, hookData in ipairs(eventTable.list) do
if hookData.THIS_RUNTIME <= 0.001 then
DLib.MessagePlayer(ply, '(', #eventTable.list - pos2 + 1, ' are not shown)')
break
end
DLib.MessagePlayer(ply, 'Hook ID: ', hookData.id)
DLib.MessagePlayer(ply, string.format('\t - Runtime: %.2f milliseconds; %i calls; ~%.2f microseconds per call on average',
hookData.THIS_RUNTIME * 1000, hookData.THIS_CALLS, (hookData.THIS_RUNTIME * 1000000) / hookData.THIS_CALLS))
end
end
DLib.MessagePlayer(ply, '--')
DLib.MessagePlayer(ply, 'In total, regular hooks took around ', math.floor((totalRuntime / time) * 10000) / 100, '% of game runtime.')
DLib.MessagePlayer(ply, '--')
DLib.MessagePlayer(ply, '-----------------------------------')
DLib.MessagePlayer(ply, '--- END OF HOOK PROFILING REPORT --')
DLib.MessagePlayer(ply, '-----------------------------------')
end
if CLIENT then
concommand.Add('dlib_profile_hooks_cl', function(ply, cmd, args)
if hook.PROFILING then
hook.PROFILE_ENDS = SysTime()
hook.PROFILING = false
hook.PROFILING_RESULTS_EXISTS = true
hook.Reconstruct()
for i, hookData in ipairs(hook.ListAllHooks(false)) do
hookData.profileEnds()
end
printProfilingResults(LocalPlayer())
return
end
hook.PROFILE_STARTED = SysTime()
hook.PROFILING = true
DLib.Message('Hook profiling were started')
DLib.Message('When you are ready you can type dlib_profile_hooks_cl again')
DLib.Message('/// NOTE THAT RESULTS BECOME MORE ACCURATE AS PROFILING GOES!')
DLib.Message('/// Disabling it too early will produce false results')
hook.Reconstruct()
end)
concommand.Add('dlib_profile_hooks_last_cl', function(ply, cmd, args)
if not hook.PROFILING_RESULTS_EXISTS then
DLib.Message('No profiling results exists!')
DLib.Message('Start a new one by typing dlib_profile_hooks_cl')
return
end
printProfilingResults(LocalPlayer())
end)
else
concommand.Add('dlib_profile_hooks_last_sv', function(ply, cmd, args)
if IsValid(ply) and not ply:IsSuperAdmin() then
DLib.MessagePlayer(ply, 'Not a super admin!')
return
end
if not hook.PROFILING_RESULTS_EXISTS then
DLib.MessagePlayer(ply, 'No profiling results exists!')
DLib.MessagePlayer(ply, 'Start a new one by typing dlib_profile_hooks_cl')
return
end
DLib.Message(IsValid(ply) and ply or 'Console', ' requested hook profiling results')
printProfilingResults(ply)
end)
concommand.Add('dlib_profile_hooks_sv', function(ply, cmd, args)
if IsValid(ply) and not ply:IsSuperAdmin() then
DLib.MessagePlayer(ply, 'Not a super admin!')
return
end
if hook.PROFILING then
DLib.Message(IsValid(ply) and ply or 'Console', ' stopped hook profiling')
hook.PROFILE_ENDS = SysTime()
hook.PROFILING = false
hook.PROFILING_RESULTS_EXISTS = true
hook.Reconstruct()
for i, hookData in ipairs(hook.ListAllHooks(false)) do
hookData.profileEnds()
end
printProfilingResults(ply)
return
end
DLib.Message(IsValid(ply) and ply or 'Console', ' started hook profiling')
hook.PROFILE_STARTED = SysTime()
hook.PROFILING = true
DLib.MessagePlayer(ply, 'Hook profiling were started')
DLib.MessagePlayer(ply, 'When you are ready you can type dlib_profile_hooks_cl again')
DLib.MessagePlayer(ply, '/// NOTE THAT RESULTS BECOME MORE ACCURATE AS PROFILING GOES!')
DLib.MessagePlayer(ply, '/// Disabling it too early will produce false results')
hook.Reconstruct()
end)
end
if file.Exists('autorun/hat_init.lua', 'LUA') then
DLib.Message(string.rep('-', 63))
DLib.Message(string.rep('W', 63))
DLib.Message(string.rep('A', 63))
DLib.Message(string.rep('R', 63))
DLib.Message(string.rep('N', 63))
DLib.Message(string.rep('I', 63))
DLib.Message(string.rep('N', 63))
DLib.Message(string.rep('G', 63))
DLib.Message('HAT INSTALLATION DETECTED')
DLib.Message('HAT IS BASICALLY BROKEN FOR YEARS')
DLib.Message('AND IT ALSO BREAK THE GAME, OBVIOUSLY')
DLib.Message('IMPLEMENTING HAT LOADER TRAP')
DLib.Message(string.rep('-', 63))
table._DLibCopy = table._DLibCopy or table.Copy
function table.Copy(tableIn)
if tableIn == _G.concommand or tableIn == _G.hook then
table.Copy = table._DLibCopy
error('Nuh uh. No. Definitely Not. I dont even.', 2)
end
return table._DLibCopy(tableIn)
end
end