From be1cff9f3cbc5eaf4be36e991ed5afb919526b50 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Wed, 1 Mar 2023 00:20:45 +0100 Subject: [PATCH] feat(dtmm): Move class and require hooks into early loading These need to be executed as early as possible if they're supposed to capture all of their respective calls. --- crates/dtmm/assets/mod_main.lua | 226 +++++++++++++++++++++----------- 1 file changed, 147 insertions(+), 79 deletions(-) diff --git a/crates/dtmm/assets/mod_main.lua b/crates/dtmm/assets/mod_main.lua index 285d15e..715397f 100644 --- a/crates/dtmm/assets/mod_main.lua +++ b/crates/dtmm/assets/mod_main.lua @@ -1,3 +1,7 @@ +local _G = _G +local rawget = rawget +local rawset = rawset + local log = function(category, format, ...) local Log = rawget(_G, "Log") if Log then @@ -7,87 +11,69 @@ local log = function(category, format, ...) end end -log("mod_main", " Initializing mods...") --- Keep a backup of certain system libraries before --- Fatshark's code scrubs them. --- The loader can then decide to pass them on to mods, or ignore them -local libs = { - io = io, - debug = debug, - ffi = ffi, - os = os, - load = load, - loadfile = loadfile, - loadstring = loadstring, -} - -require("scripts/main") -log("mod_main", "'scripts/main' loaded") - -local StateBootSubStateBase = require("scripts/game_states/boot/state_boot_sub_state_base") - --- A necessary override. --- The original does not proxy `dt` to `_state_update`, but we need that. -StateBootSubStateBase.update = function (self, dt) - local done, error = self:_state_update(dt) - local params = self._params - - if error then - return StateError, { error } - elseif done then - local next_index = params.sub_state_index + 1 - params.sub_state_index = next_index - local next_state_data = params.states[next_index] - - if next_state_data then - return next_state_data[1], self._params - else - self._parent:sub_states_done() - end - end -end - -local StateBootLoadMods = class("StateBootLoadMods", "StateBootSubStateBase") - -StateBootLoadMods.on_enter = function (self, parent, params) - log("StateBootLoadMods", "Entered") - StateBootLoadMods.super.on_enter(self, parent, params) - - local state_params = self:_state_params() - local package_manager = state_params.package_manager - - self._state = "load_package" - self._package_manager = package_manager - self._package_handles = { - ["packages/mods"] = package_manager:load("packages/mods", "StateBootLoadMods", nil), - ["packages/dml"] = package_manager:load("packages/dml", "StateBootLoadMods", nil), - } -end - -StateBootLoadMods._state_update = function (self, dt) - local state = self._state - local package_manager = self._package_manager - - if state == "load_package" and package_manager:update() then - log("StateBootLoadMods", "Packages loaded, loading mods") - self._state = "load_mods" - local mod_loader = require("scripts/mods/dml/init") - self._mod_loader = mod_loader - - local mod_data = require("scripts/mods/mod_data") - mod_loader:init(mod_data, libs, self._parent:gui()) - elseif state == "load_mods" and self._mod_loader:update(dt) then - log("StateBootLoadMods", "Mods loaded, exiting") - return true, false - end - - return false, false -end - -- Patch `GameStateMachine.init` to add our own state for loading mods. -- In the future, Fatshark might provide us with a dedicated way to do this. local function patch_mod_loading_state() - log("mod_main", "Adding mod loading state") + local StateBootSubStateBase = require("scripts/game_states/boot/state_boot_sub_state_base") + + -- A necessary override. + -- The original does not proxy `dt` to `_state_update`, but we need that. + StateBootSubStateBase.update = function (self, dt) + local done, error = self:_state_update(dt) + local params = self._params + + if error then + return StateError, { error } + elseif done then + local next_index = params.sub_state_index + 1 + params.sub_state_index = next_index + local next_state_data = params.states[next_index] + + if next_state_data then + return next_state_data[1], self._params + else + self._parent:sub_states_done() + end + end + end + + local StateBootLoadMods = class("StateBootLoadMods", "StateBootSubStateBase") + + StateBootLoadMods.on_enter = function (self, parent, params) + log("StateBootLoadMods", "Entered") + StateBootLoadMods.super.on_enter(self, parent, params) + + local state_params = self:_state_params() + local package_manager = state_params.package_manager + + self._state = "load_package" + self._package_manager = package_manager + self._package_handles = { + ["packages/mods"] = package_manager:load("packages/mods", "StateBootLoadMods", nil), + ["packages/dml"] = package_manager:load("packages/dml", "StateBootLoadMods", nil), + } + end + + StateBootLoadMods._state_update = function (self, dt) + local state = self._state + local package_manager = self._package_manager + + if state == "load_package" and package_manager:update() then + log("StateBootLoadMods", "Packages loaded, loading mods") + self._state = "load_mods" + local mod_loader = require("scripts/mods/dml/init") + self._mod_loader = mod_loader + + local mod_data = require("scripts/mods/mod_data") + mod_loader:init(mod_data, self._parent:gui()) + elseif state == "load_mods" and self._mod_loader:update(dt) then + log("StateBootLoadMods", "Mods loaded, exiting") + return true, false + end + + return false, false + end + local GameStateMachine = require("scripts/foundation/utilities/game_state_machine") local patched = false @@ -95,6 +81,7 @@ local function patch_mod_loading_state() local GameStateMachine_init = GameStateMachine.init GameStateMachine.init = function(self, parent, start_state, params, ...) if not patched then + log("mod_main", "Injecting mod loading state") patched = true -- Hardcoded position after `StateRequireScripts`. @@ -112,13 +99,94 @@ local function patch_mod_loading_state() GameStateMachine_init(self, parent, start_state, params, ...) end + log("mod_main", "Mod patching complete") end +log("mod_main", "Initializing mods...") + +local require_store = {} + +Mods = { + -- Keep a backup of certain system libraries before + -- Fatshark's code scrubs them. + -- The loader can then decide to pass them on to mods, or ignore them + lua = setmetatable({}, { + io = io, + debug = debug, + ffi = ffi, + os = os, + load = load, + loadfile = loadfile, + loadstring = loadstring, + }), + require_store = require_store +} + +local can_insert = function(filepath, new_result) + local store = require_store[filepath] + if not store or #store then + return true + end + + if store[#store] ~= new_result then + return true + end +end + +local original_require = require +require = function(filepath, ...) + 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] + + table.insert(store, result) + + if Mods.hook then + Mods.hook.enable_by_file(filepath, #store) + end + end + end + + return result +end + +require("scripts/boot_init") +require("scripts/foundation/utilities/class") + +-- 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 = setmetatable({}, { + __index = function(_, key) + return key + end +}) + +local original_class = class +class = function(class_name, super_name, ...) + 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 + +require("scripts/main") +log("mod_main", "'scripts/main' loaded") + +-- Override `init` to run our injection function init() + patch_mod_loading_state() + + -- As requested by Fatshark local StateRequireScripts = require("scripts/game_states/boot/state_require_scripts") StateRequireScripts._get_is_modded = function() return true end - patch_mod_loading_state() Main:init() end