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.
This commit is contained in:
Lucas Schwiderski 2023-03-01 00:20:45 +01:00
parent f0450285ad
commit be1cff9f3c
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8

View file

@ -1,3 +1,7 @@
local _G = _G
local rawget = rawget
local rawset = rawset
local log = function(category, format, ...) local log = function(category, format, ...)
local Log = rawget(_G, "Log") local Log = rawget(_G, "Log")
if Log then if Log then
@ -7,87 +11,69 @@ local log = function(category, format, ...)
end end
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. -- 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. -- In the future, Fatshark might provide us with a dedicated way to do this.
local function patch_mod_loading_state() 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 GameStateMachine = require("scripts/foundation/utilities/game_state_machine")
local patched = false local patched = false
@ -95,6 +81,7 @@ local function patch_mod_loading_state()
local GameStateMachine_init = GameStateMachine.init local GameStateMachine_init = GameStateMachine.init
GameStateMachine.init = function(self, parent, start_state, params, ...) GameStateMachine.init = function(self, parent, start_state, params, ...)
if not patched then if not patched then
log("mod_main", "Injecting mod loading state")
patched = true patched = true
-- Hardcoded position after `StateRequireScripts`. -- Hardcoded position after `StateRequireScripts`.
@ -112,13 +99,94 @@ local function patch_mod_loading_state()
GameStateMachine_init(self, parent, start_state, params, ...) GameStateMachine_init(self, parent, start_state, params, ...)
end end
log("mod_main", "Mod patching complete") log("mod_main", "Mod patching complete")
end 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() function init()
patch_mod_loading_state()
-- As requested by Fatshark
local StateRequireScripts = require("scripts/game_states/boot/state_require_scripts") local StateRequireScripts = require("scripts/game_states/boot/state_require_scripts")
StateRequireScripts._get_is_modded = function() return true end StateRequireScripts._get_is_modded = function() return true end
patch_mod_loading_state()
Main:init() Main:init()
end end