From 15dc40b9ab1a262986b6da74b3900a343dcf9fca Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Wed, 22 Feb 2023 10:23:06 +0100 Subject: [PATCH] refactor: Move indexed objects to local and change whitespace --- .luacheckrc | 31 ++- scripts/mods/dml/class.lua | 24 +- scripts/mods/dml/hook.lua | 515 ++++++++++++++++++----------------- scripts/mods/dml/init.lua | 3 +- scripts/mods/dml/require.lua | 56 ++-- 5 files changed, 327 insertions(+), 302 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 552b17e..f213500 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -10,7 +10,30 @@ ignore = { "212/self", -- Disable unused self warnings. } -std = "+DT" +std = "+DT+DML" + +stds["DML"] = { + read_globals = { + "MODS_HOOKS", "MODS_HOOKS_BY_FILE", "Log", + Mods = { fields = { + lua = { fields = { "debug", "io", "ffi", "os" }}, + hook = { fields = { + "set", + "set_on_file", + "enable", + "enable_by_file", + "remove", + "front", + "_get_item", + "_get_item_hook", + "_patch", + }}, + "original_require", + "require_store", + "original_class", + }}, + }, +} stds["DT"] = { read_globals = { @@ -32,13 +55,9 @@ stds["DT"] = { Managers = { fields = { "mod", "event", "chat" }}, - Mods = { fields = { - lua = { fields = { "debug", "io", "ffi", "os" }}, - "original_require", - "require_store", - }}, "Crashify","Keyboard","Mouse","Application","Color","Quarternion","Vector3","Vector2","RESOLUTION_LOOKUP", "ModManager", "Utf8", "StateGame", "ResourcePackage", "class", "Gui", "fassert", "printf", "__print", "ffi", + "class", }, } diff --git a/scripts/mods/dml/class.lua b/scripts/mods/dml/class.lua index 19b0c91..53896de 100644 --- a/scripts/mods/dml/class.lua +++ b/scripts/mods/dml/class.lua @@ -1,9 +1,13 @@ -Mods.original_class = Mods.original_class or class +local original_class = Mods.original_class or class +Mods.original_class = original_class local _G = _G local rawget = rawget local rawset = rawset +-- The `__index` metamethod maps a proper identifier `CLASS.MyClassName` to the +-- stringified version of the key: `"MyClassName"`. +-- This allows using LuaCheck for the stringified class names in hook parameters. _G.CLASS = _G.CLASS or setmetatable({}, { __index = function(_, key) return key @@ -11,12 +15,12 @@ _G.CLASS = _G.CLASS or setmetatable({}, { }) class = function(class_name, super_name, ...) - local result = Mods.original_class(class_name, super_name, ...) - if not rawget(_G, class_name) then - rawset(_G, class_name, result) - end - if not rawget(_G.CLASS, class_name) then - rawset(_G.CLASS, class_name, result) - end - return result -end \ No newline at end of file + local result = original_class(class_name, super_name, ...) + if not rawget(_G, class_name) then + rawset(_G, class_name, result) + end + if not rawget(_G.CLASS, class_name) then + rawset(_G.CLASS, class_name, result) + end + return result +end diff --git a/scripts/mods/dml/hook.lua b/scripts/mods/dml/hook.lua index f74822f..7f17888 100644 --- a/scripts/mods/dml/hook.lua +++ b/scripts/mods/dml/hook.lua @@ -1,274 +1,275 @@ ---[[ - Mods Hook v2: - New version with better control ---]] - -- Hook structure MODS_HOOKS = MODS_HOOKS or {} MODS_HOOKS_BY_FILE = MODS_HOOKS_BY_FILE or {} +local function NOOP() end + local item_template = { - name = "", - func = EMPTY_FUNC, - hooks = {}, + name = "", + func = NOOP, + hooks = {}, } local item_hook_template = { - name = "", - func = EMPTY_FUNC, - enable = false, - exec = EMPTY_FUNC, + name = "", + func = NOOP, + enable = false, + exec = NOOP, } local Log = Log -local print_log_info = function(mod_name, message) - Log = Log or rawget(_G, "Log") - if Log then - Log._info(mod_name, message) - else - print("[" .. mod_name .. "]: " .. message) - end +local function print_log_info(mod_name, message) + Log = Log or rawget(_G, "Log") + if Log then + Log._info(mod_name, message) + else + print("[" .. mod_name .. "]: " .. message) + end end -local print_log_warning = function(mod_name, message) - Log = Log or rawget(_G, "Log") - if Log then - Log._warning(mod_name, message) - else - print("[" .. mod_name .. "]: " .. message) - end +-- +-- Get function by function name +-- +local function get_func(func_name) + return assert(loadstring("return " .. func_name))() +end + +-- +-- Get item by function name +-- +local function get_item(func_name) + -- Find existing item + for _, item in ipairs(MODS_HOOKS) do + if item.name == func_name then + return item + end + end + + -- Create new item + local item = table.clone(item_template) + item.name = func_name + item.func = get_func(func_name) + + -- Save + table.insert(MODS_HOOKS, item) + + return item +end + +-- +-- Get item hook by mod name +-- +local function get_item_hook(item, mod_name) + -- Find existing item + for _, hook in ipairs(item.hooks) do + if hook.name == mod_name then + return hook + end + end + + -- Create new item + local item_hook = table.clone(item_hook_template) + item_hook.name = mod_name + + -- Save + table.insert(item.hooks, 1, item_hook) + + return item_hook +end + +-- +-- If settings are changed the hook itself needs to be updated +-- +local function patch() + for i, item in ipairs(MODS_HOOKS) do + local item_name = "MODS_HOOKS[" .. i .. "]" + + local last_j = 1 + for j, hook in ipairs(item.hooks) do + local hook_name = item_name .. ".hooks[" .. j .. "]" + local before_hook_name = item_name .. ".hooks[" .. (j - 1) .. "]" + + if j == 1 then + if hook.enable then + assert( + loadstring( + hook_name .. ".exec = function(...)" .. + " return " .. hook_name .. ".func(" .. item_name .. ".func, ...)" .. + "end" + ) + )() + else + assert( + loadstring( + hook_name .. ".exec = function(...)" .. + " return " .. item_name .. ".func(...)" .. + "end" + ) + )() + end + else + if hook.enable then + assert( + loadstring( + hook_name .. ".exec = function(...)" .. + " return " .. hook_name .. ".func(" .. before_hook_name .. ".exec, ...)" .. + "end" + ) + )() + else + assert( + loadstring( + hook_name .. ".exec = function(...)" .. + " return " .. before_hook_name .. ".exec(...)" .. + "end" + ) + )() + end + end + + last_j = j + end + + -- Patch orginal function call + assert(loadstring(item.name .. " = " .. item_name .. ".hooks[" .. last_j .. "].exec"))() + end +end + +-- +-- Set hook +-- +local function set(mod_name, func_name, hook_func) + local item = get_item(func_name) + local item_hook = get_item_hook(item, mod_name) + + print_log_info(mod_name, "Hooking " .. func_name) + + item_hook.enable = true + item_hook.func = hook_func + + patch() +end + +-- +-- Set hook on every instance of the given file +-- +local function set_on_file(mod_name, filepath, func_name, hook_func) + -- Add hook create function to list for the file + MODS_HOOKS_BY_FILE[filepath] = MODS_HOOKS_BY_FILE[filepath] or {} + local hook_create_func = function(this_filepath, this_index) + local dynamic_func_name = string.format( + "Mods.require_store[\"%s\"][%i].%s", + this_filepath, this_index, func_name + ) + set(mod_name, dynamic_func_name, hook_func, false) + end + table.insert(MODS_HOOKS_BY_FILE[filepath], hook_create_func) + + -- Add the new hook to every instance of the file + local all_file_instances = Mods.require_store[filepath] + if all_file_instances then + for i, item in ipairs(all_file_instances) do + if item then + hook_create_func(filepath, i) + end + end + end +end + +-- +-- Enable/Disable hook +-- +local function enable(value, mod_name, func_name) + for _, item in ipairs(MODS_HOOKS) do + if item.name == func_name or func_name == nil then + for _, hook in ipairs(item.hooks) do + if hook.name == mod_name then + hook.enable = value + patch() + end + end + end + end + + return +end + +-- +-- Enable all hooks on a stored file +-- +local function enable_by_file(filepath, store_index) + local all_file_instances = Mods.require_store[filepath] + local file_instance = all_file_instances and all_file_instances[store_index] + + local all_file_hooks = MODS_HOOKS_BY_FILE[filepath] + + if all_file_hooks and file_instance then + for _, hook_create_func in ipairs(all_file_hooks) do + hook_create_func(filepath, store_index) + end + end +end + +-- +-- Remove hook from chain +-- +local function remove(func_name, mod_name) + for i, item in ipairs(MODS_HOOKS) do + if item.name == func_name then + if mod_name ~= nil then + for j, hook in ipairs(item.hooks) do + if hook.name == mod_name then + table.remove(item.hooks, j) + + patch() + end + end + else + local item_name = "MODS_HOOKS[" .. tostring(i) .. "]" + + -- Restore orginal function + assert(loadstring(item.name .. " = " .. item_name .. ".func"))() + + -- Remove hook function + table.remove(MODS_HOOKS, i) + + return + end + end + end + + return +end + +-- +-- Move hook to front of the hook chain +-- +local function front(mod_name, func_name) + for _, item in ipairs(MODS_HOOKS) do + if item.name == func_name or func_name == nil then + for i, hook in ipairs(item.hooks) do + if hook.name == mod_name then + local saved_hook = table.clone(hook) + table.remove(item.hooks, i) + table.insert(item.hooks, saved_hook) + + patch() + end + end + end + end + + return end Mods.hook = { - -- - -- Set hook - -- - set = function(mod_name, func_name, hook_func) - local item = Mods.hook._get_item(func_name) - local item_hook = Mods.hook._get_item_hook(item, mod_name) - - print_log_info(mod_name, "Hooking " .. func_name) - - item_hook.enable = true - item_hook.func = hook_func - - Mods.hook._patch() - end, - - -- - -- Set hook on every instance of the given file - -- - set_on_file = function(mod_name, filepath, func_name, hook_func) - -- Add hook create function to list for the file - MODS_HOOKS_BY_FILE[filepath] = MODS_HOOKS_BY_FILE[filepath] or {} - local hook_create_func = function(this_filepath, this_index) - local dynamic_func_name = "Mods.require_store[\"" .. this_filepath .. "\"][" .. tostring(this_index) .. "]." .. func_name - Mods.hook.set(mod_name, dynamic_func_name, hook_func, false) - end - table.insert(MODS_HOOKS_BY_FILE[filepath], hook_create_func) - - -- Add the new hook to every instance of the file - local all_file_instances = Mods.require_store[filepath] - if all_file_instances then - for i, item in ipairs(all_file_instances) do - if item then - hook_create_func(filepath, i) - end - end - end - end, - - -- - -- Enable/Disable hook - -- - enable = function(value, mod_name, func_name) - for _, item in ipairs(MODS_HOOKS) do - if item.name == func_name or func_name == nil then - for _, hook in ipairs(item.hooks) do - if hook.name == mod_name then - hook.enable = value - Mods.hook._patch() - end - end - end - end - - return - end, - - -- - -- Enable all hooks on a stored file - -- - enable_by_file = function(filepath, store_index) - local all_file_instances = Mods.require_store[filepath] - local file_instance = all_file_instances and all_file_instances[store_index] - - local all_file_hooks = MODS_HOOKS_BY_FILE[filepath] - - if all_file_hooks and file_instance then - for i, hook_create_func in ipairs(all_file_hooks) do - hook_create_func(filepath, store_index) - end - end - end, - - -- - -- Remove hook from chain - -- - ["remove"] = function(func_name, mod_name) - for i, item in ipairs(MODS_HOOKS) do - if item.name == func_name then - if mod_name ~= nil then - for j, hook in ipairs(item.hooks) do - if hook.name == mod_name then - table.remove(item.hooks, j) - - Mods.hook._patch() - end - end - else - local item_name = "MODS_HOOKS[" .. tostring(i) .. "]" - - -- Restore orginal function - assert(loadstring(item.name .. " = " .. item_name .. ".func"))() - - -- Remove hook function - table.remove(MODS_HOOKS, i) - - return - end - end - end - - return - end, - - -- - -- Move hook to front of the hook chain - -- - front = function(mod_name, func_name) - for _, item in ipairs(MODS_HOOKS) do - if item.name == func_name or func_name == nil then - for i, hook in ipairs(item.hooks) do - if hook.name == mod_name then - local saved_hook = table.clone(hook) - table.remove(item.hooks, i) - table.insert(item.hooks, saved_hook) - - Mods.hook._patch() - end - end - end - end - - return - end, - - -- - -- Get function by function name - -- - _get_func = function(func_name) - return assert(loadstring("return " .. func_name))() - end, - - -- - -- Get item by function name - -- - _get_item = function(func_name) - -- Find existing item - for _, item in ipairs(MODS_HOOKS) do - if item.name == func_name then - return item - end - end - - -- Create new item - local item = table.clone(item_template) - item.name = func_name - item.func = Mods.hook._get_func(func_name) - - -- Save - table.insert(MODS_HOOKS, item) - - return item - end, - - -- - -- Get item hook by mod name - -- - _get_item_hook = function(item, mod_name) - -- Find existing item - for _, hook in ipairs(item.hooks) do - if hook.name == mod_name then - return hook - end - end - - -- Create new item - local item_hook = table.clone(item_hook_template) - item_hook.name = mod_name - - -- Save - table.insert(item.hooks, 1, item_hook) - - return item_hook - end, - - -- - -- If settings are changed the hook itself needs to be updated - -- - _patch = function(mods_hook_item) - for i, item in ipairs(MODS_HOOKS) do - local item_name = "MODS_HOOKS[" .. i .. "]" - - local last_j = 1 - for j, hook in ipairs(item.hooks) do - local hook_name = item_name .. ".hooks[" .. j .. "]" - local before_hook_name = item_name .. ".hooks[" .. (j - 1) .. "]" - - if j == 1 then - if hook.enable then - assert( - loadstring( - hook_name .. ".exec = function(...)" .. - " return " .. hook_name .. ".func(" .. item_name .. ".func, ...)" .. - "end" - ) - )() - else - assert( - loadstring( - hook_name .. ".exec = function(...)" .. - " return " .. item_name .. ".func(...)" .. - "end" - ) - )() - end - else - if hook.enable then - assert( - loadstring( - hook_name .. ".exec = function(...)" .. - " return " .. hook_name .. ".func(" .. before_hook_name .. ".exec, ...)" .. - "end" - ) - )() - else - assert( - loadstring( - hook_name .. ".exec = function(...)" .. - " return " .. before_hook_name .. ".exec(...)" .. - "end" - ) - )() - end - end - - last_j = j - end - - -- Patch orginal function call - assert(loadstring(item.name .. " = " .. item_name .. ".hooks[" .. last_j .. "].exec"))() - end - end, + set = set, + set_on_file = set_on_file, + enable = enable, + enable_by_file = enable_by_file, + remove = remove, + front = front, + _get_item = get_item, + _get_item_hook = get_item_hook, + _patch = patch, } diff --git a/scripts/mods/dml/init.lua b/scripts/mods/dml/init.lua index 34583d1..71f299d 100644 --- a/scripts/mods/dml/init.lua +++ b/scripts/mods/dml/init.lua @@ -3,7 +3,6 @@ local loader = {} Mods = { - hook = {}, lua = setmetatable({}, { __index = { debug = debug, io = io, ffi = ffi, os = os }, }), @@ -11,7 +10,7 @@ Mods = { dofile("scripts/mods/dml/require") dofile("scripts/mods/dml/class") -dofile("scripts/mods/dml/hook") +Mods.hook = dofile("scripts/mods/dml/hook") function loader:init(boot_gui, mod_data) local ModLoader = dofile("scripts/mods/dml/mod_loader") diff --git a/scripts/mods/dml/require.lua b/scripts/mods/dml/require.lua index 6e876f7..d311b56 100644 --- a/scripts/mods/dml/require.lua +++ b/scripts/mods/dml/require.lua @@ -1,35 +1,37 @@ -Mods.require_store = Mods.require_store or {} -Mods.original_require = Mods.original_require or require +local require_store = Mods.require_store or {} +Mods.require_store = require_store + +local original_require = Mods.original_require or require +Mods.original_require = original_require local can_insert = function(filepath, new_result) - local store = Mods.require_store[filepath] - if not store or #store == 0 then - return true - end - - if store[#store] ~= new_result then - return true - end + local store = require_store[filepath] + local num_store = #store + if not store or num_store == 0 then + return true + end + + if store[num_store] ~= new_result then + return true + end end require = function(filepath, ...) - local Mods = Mods + local result = original_require(filepath, ...) + if result and type(result) == "table" then + if can_insert(filepath, result) then + require_store[filepath] = require_store[filepath] or {} + local store = require_store[filepath] - local result = Mods.original_require(filepath, ...) - if result and type(result) == "table" then + table.insert(store, result) - if can_insert(filepath, result) then - Mods.require_store[filepath] = Mods.require_store[filepath] or {} - local store = Mods.require_store[filepath] + --print("[Require] #" .. tostring(#store) .. " of " .. filepath) + local Mods = Mods + if Mods.hook then + Mods.hook.enable_by_file(filepath, #store) + end + end + end - table.insert(store, result) - - --print("[Require] #" .. tostring(#store) .. " of " .. filepath) - if Mods.hook then - Mods.hook.enable_by_file(filepath, #store) - end - end - end - - return result -end \ No newline at end of file + return result +end