Search code examples
classooppointersluaclone

Lua - Function That Creates Detours


I need to make several detours to various functions and doing it one by one is just not an option. I'm looking for a function that will take a table, ideally, and this table is class. Loop through it and for each key, value pair that is a function make a function pointer with a prefix before the original function name. I have tried several variations to achieve this effect, but they all yield different problems. Some simply will not make detour pointer no matter what you give to them, others make detour pointers but they don't work, and some will overflow the stack or simply not be recognized.

I want to know if there is a way, ie rawsets, metatable overrides, constant looping until they do match, etc to make it so the function can get a table (or a string that has the same name as the table, thus a loadstring method would work here too) and loop through each function and make a working detour pointer...no matter what.

I prefer using the self:prefix_orig_name(...) syntax [... can be replaced with actual args].

Here's 2 variations I have tried with example useage.

-- 1st Method
detours = detours or {}

function detour(object, class) -- Class is an extra arg that I would send if for some reason just sending an object didn't work...it was theory oh'ed
    if detours[object] then -- Check if the detour already exists...might be worth remaking it especially if the function gets overridden several times in different places?
        print("detour: Previous " .. object .. " detour found, using previous detour")
        return
    end
    for name, func in pairs(class and class or loadstring("return " .. object)()) do
        -- the loadstring method here is used because the argument received is a string of the same name as the table...thus loading it will yield a table
        if type(func) == "function" then
            local execute, error = loadstring(object .. ".custom_detour_" .. name .. " = " .. object .. "." .. name) -- This makes the actual pointer
            if error then
                print("detour Error: " .. " Failed to detour: " .. object .. " Error: " .. error)
            end
            local luanch, assert = pcall(execute)
            if not luanch then
                print("detour Error: " .. " Failed to detour: " .. object .. " Error: " .. assert)
            end
        end
    end
    print("Table: " .. object .. " successfully detourd")
    detours[object] = true -- tells us we made a detour of this table/string
end

-- 2nd Method
function detour(object) -- Takes a table
    for k, v in pairs(object) do
        if type(v) == "function" and not detours[k] then
            if not object.custom_detour_ then
                object.custom_detour_ = clone(object) -- use a simple cloning function (shallow) to put a clone of the main table into a sub table of the main table
            end
            if object["custom_detour_" .. k] ~= object.custom_detour_[k] then
                object["custom_detour_" .. k] = object.custom_detour_[k] -- this makes it so the self:custom_detour_orig_name(...) syntax can be used, if I am not mistaken
            end
        end
    end
end

-- Example Usage:
MyClass = class() -- class function is relatively OOP standard

function MyClass:init()
    self._something = true
end

function MyClass:change(value)
    self._something = value
end

function MyClass:table_print(tbl) -- just making funcs up
    for k, v in pairs(tbl) do
        print(v)
    end
end

my_class = MyClass:new()

-- 1st Method
detour("MyClass")

--2nd Method
detour(MyClass)

I personally prefer the 1st method or at least a string, because I can log each detour and if a problem arises later on, it makes debugging easier...but I am for whatever will work.


Solution

  • Simple detouring is easy to do with closures; no need for loadstring:

    function detour(cls)
        local detours = {}
        for key, value in pairs(cls) do
            if type(value) == "function" then -- note: ignores objects with __call metamethod
                detours["custom_detour_"..key] = function(...)
                    -- Do whatever you want here
                    return value(...)
                end
            end
        end
        for key, value in pairs(detours) do
            cls[key] = value
        end
    end