Implement loading non-bundled mods

This commit is contained in:
Lucas Schwiderski 2023-11-12 23:18:29 +01:00
parent 7b417f8b63
commit 04688ac719
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
6 changed files with 124 additions and 48 deletions

View file

@ -22,27 +22,25 @@ function dmf_mod_object:init()
dofile("scripts/mods/dmf/modules/core/misc") dofile("scripts/mods/dmf/modules/core/misc")
dofile("scripts/mods/dmf/modules/core/persistent_tables") dofile("scripts/mods/dmf/modules/core/persistent_tables")
dofile("scripts/mods/dmf/modules/core/io") dofile("scripts/mods/dmf/modules/core/io")
dofile("scripts/mods/dmf/modules/debug/dev_console")
dofile("scripts/mods/dmf/modules/debug/table_dump")
dofile("scripts/mods/dmf/modules/core/hooks")
dofile("scripts/mods/dmf/modules/core/require")
dofile("scripts/mods/dmf/modules/core/toggling")
dofile("scripts/mods/dmf/modules/core/keybindings")
dofile("scripts/mods/dmf/modules/core/chat")
dofile("scripts/mods/dmf/modules/core/localization")
dofile("scripts/mods/dmf/modules/core/options")
dofile("scripts/mods/dmf/modules/core/network")
dofile("scripts/mods/dmf/modules/core/commands")
dofile("scripts/mods/dmf/modules/gui/custom_textures")
dofile("scripts/mods/dmf/modules/gui/custom_views")
dofile("scripts/mods/dmf/modules/ui/chat/chat_actions")
dofile("scripts/mods/dmf/modules/ui/options/mod_options")
dofile("scripts/mods/dmf/modules/dmf_options")
dofile("scripts/mods/dmf/modules/core/mutators/mutators_manager")
dmf = get_mod("DMF") dmf = get_mod("DMF")
dmf:dofile("scripts/mods/dmf/modules/debug/dev_console")
dmf:dofile("scripts/mods/dmf/modules/debug/table_dump")
dmf:dofile("scripts/mods/dmf/modules/core/hooks")
dmf:dofile("scripts/mods/dmf/modules/core/require")
dmf:dofile("scripts/mods/dmf/modules/core/toggling")
dmf:dofile("scripts/mods/dmf/modules/core/keybindings")
dmf:dofile("scripts/mods/dmf/modules/core/chat")
dmf:dofile("scripts/mods/dmf/modules/core/localization")
dmf:dofile("scripts/mods/dmf/modules/core/options")
dmf:dofile("scripts/mods/dmf/modules/core/network")
dmf:dofile("scripts/mods/dmf/modules/core/commands")
dmf:dofile("scripts/mods/dmf/modules/gui/custom_textures")
dmf:dofile("scripts/mods/dmf/modules/gui/custom_views")
dmf:dofile("scripts/mods/dmf/modules/ui/chat/chat_actions")
dmf:dofile("scripts/mods/dmf/modules/ui/options/mod_options")
dmf:dofile("scripts/mods/dmf/modules/dmf_options")
dmf:dofile("scripts/mods/dmf/modules/core/mutators/mutators_manager")
dmf.delayed_chat_messages_hook() dmf.delayed_chat_messages_hook()
end end

View file

@ -95,7 +95,7 @@ local function handle_io(mod, local_path, file_name, file_extension, args, safe_
-- If this is a safe call, wrap it in a pcall -- If this is a safe call, wrap it in a pcall
if safe_call then if safe_call then
status, result = pcall(function () status, result = pcall(function()
return read_or_execute(file_path, args, return_type) return read_or_execute(file_path, args, return_type)
end) end)
@ -114,15 +114,50 @@ local function handle_io(mod, local_path, file_name, file_extension, args, safe_
-- If the initial open failed, report failure -- If the initial open failed, report failure
else else
mod:error("Error opening '" .. file_path .. "': " .. tostring(err_io)) mod:error("Error during I/O: %s\n%s", tostring(err_io), Script.callstack())
return false return false
end end
end end
-- ##################################################################################################################### -- #####################################################################################################################
-- ##### DMFMod ######################################################################################################## -- ##### DMFMod ########################################################################################################
-- ##################################################################################################################### -- #####################################################################################################################
-- Use the io library to execute the given file with a pcall, without return
function DMFMod:io_exec(local_path, file_name, file_extension, args)
return handle_io(self, local_path, file_name, file_extension, args, true, "exec_boolean")
end
-- Use the io library to execute the given file without a pcall, without return
function DMFMod:io_exec_unsafe(local_path, file_name, file_extension, args)
return handle_io(self, local_path, file_name, file_extension, args, false, "exec_boolean")
end
-- Use the io library to execute the given file with a pcall and return the result
function DMFMod:io_exec_with_return(local_path, file_name, file_extension, args)
return handle_io(self, local_path, file_name, file_extension, args, true, "exec_result")
end
-- Use the io library to execute the given file without a pcall and return the result
function DMFMod:io_exec_unsafe_with_return(local_path, file_name, file_extension, args)
return handle_io(self, local_path, file_name, file_extension, args, false, "exec_result")
end
-- Use the io library to execute the given file with a pcall and return the result,
-- but treat the first parameter as the entire path to the file, and assume .lua.
-- IO version of the dofile method with a pcall.
function DMFMod:io_dofile(file_path)
return handle_io(self, file_path, nil, nil, nil, true, "exec_result")
end
-- Use the io library to execute the given file without a pcall and return the result,
-- but treat the first parameter as the entire path to the file, and assume .lua.
-- IO version of the dofile method.
function DMFMod:io_dofile_unsafe(file_path)
return handle_io(self, file_path, nil, nil, nil, false, "exec_result")
end
-- Use the io library to return the contents of the given file -- Use the io library to return the contents of the given file
function DMFMod:io_read_content(file_path, file_extension) function DMFMod:io_read_content(file_path, file_extension)
return handle_io(self, file_path, nil, file_extension, nil, true, "data") return handle_io(self, file_path, nil, file_extension, nil, true, "data")

View file

@ -35,24 +35,63 @@ end
-- ##### DMFMod ######################################################################################################## -- ##### DMFMod ########################################################################################################
-- ##################################################################################################################### -- #####################################################################################################################
-- Add a file path to be loaded through io instead of require() --- Loads the given file with the same semantics as Lua's `require`.
---
--- This provides a unified API for both bundled and non-bundled mods.
---
--- @param path string The file to load
function DMFMod:require(path)
local is_bundled = self:get_internal_data("is_bundled")
if is_bundled then
return require(path)
else
local loaded = self:io_dofile_unsafe(path)
if loaded then
package.loaded[path] = loaded
end
return loaded
end
end
--- Loads the given file with the same semantics as Lua's `dofile`.
---
--- This provides a unified API for both bundled and non-bundled mods.
---
--- @param path string The file to load
function DMFMod:dofile(path)
local is_bundled = self:get_internal_data("is_bundled")
if is_bundled then
return dofile(path)
else
return dmf.io_dofile_unsafe(self, path)
end
end
-- Add a file path to be loaded through `io` instead of `require`.
--
-- Certain game systems will be given a path value and then call `require`
-- internally, where a mod cannot easily hook and replace the call.
--
-- This function allows non-bundled mods to inject a file such that these systems
-- can `require` them without additional hooks.
--
-- Bundled mods already have all their files available through regular `require`.
function DMFMod:add_require_path(path) function DMFMod:add_require_path(path)
add_io_require_path(path) add_io_require_path(path)
end end
-- Remove a file path that was previously loaded through io instead of require() -- Remove a file path that was previously loaded through io instead of require()
function DMFMod:remove_require_path(path) function DMFMod:remove_require_path(path)
remove_io_require_path(path) remove_io_require_path(path)
end end
-- Get all instances of a file created through require() -- Get all instances of a file created through require()
function DMFMod:get_require_store(path) function DMFMod:get_require_store(path)
return get_require_store(path) return get_require_store(path)
end end
-- Get a file through the original, unhooked require() function -- Get a file through the original, unhooked require() function
function DMFMod:original_require(path, ...) function DMFMod:original_require(path, ...)
return original_require(path, ...) return original_require(path, ...)
@ -63,7 +102,7 @@ end
-- ##################################################################################################################### -- #####################################################################################################################
-- Handles the swap to io for registered files and the application of file hooks -- Handles the swap to io for registered files and the application of file hooks
dmf:hook(_G, "require", function (func, path, ...) dmf:hook(_G, "require", function(func, path, ...)
if _io_requires[path] then if _io_requires[path] then
return dmf:dofile(path) return dmf:dofile(path)
else else

View file

@ -8,21 +8,22 @@ local print = __print
-- ##################################################################################################################### -- #####################################################################################################################
local function pack_pcall(status, ...) local function pack_pcall(status, ...)
return status, {n = select('#', ...), ...} return status, { n = select('#', ...), ... }
end end
local function print_error_callstack(error_message) local function print_error_callstack(err)
if type(error_message) == "table" and error_message.error then if type(err) == "table" and err.error then
print(string.format( Log.error(
"<<Script Error>>%s<</Script Error>>\n<<Lua Stack>>%s<</Lua Stack>>\n<<Lua Locals>>%s<</Lua Locals>>\n<<Lua Self>>%s<</Lua Self>>", "DMF",
error_message.error, error_message.traceback, error_message.locals, error_message.self "%s\n<<Lua Stack>>%s<</Lua Stack>>\n<<Lua Locals>>%s<</Lua Locals>>\n<<Lua Self>>%s<</Lua Self>>",
)) err.error, err.traceback, err.locals, err.self
)
else else
print("Error: " .. tostring(error_message) .. "\n" .. Script.callstack()) Log.error("DMF", "Error: %s\n%s", tostring(err), Script.callstack())
end end
return error_message return err
end end
@ -45,12 +46,6 @@ function DMFMod:pcall(...)
return dmf.safe_call(self, "(pcall)", ...) return dmf.safe_call(self, "(pcall)", ...)
end end
function DMFMod:dofile(file_path)
local _, return_values = pack_pcall(dmf.safe_call_dofile(self, "(dofile)", file_path))
return unpack(return_values, 1, return_values.n)
end
-- ##################################################################################################################### -- #####################################################################################################################
-- ##### DMF internal functions and variables ########################################################################## -- ##### DMF internal functions and variables ##########################################################################
-- ##################################################################################################################### -- #####################################################################################################################
@ -65,7 +60,6 @@ function dmf.safe_call(mod, error_prefix_data, func, ...)
return success, unpack(return_values, 1, return_values.n) return success, unpack(return_values, 1, return_values.n)
end end
-- Safe Call [No return values] -- Safe Call [No return values]
function dmf.safe_call_nr(mod, error_prefix_data, func, ...) function dmf.safe_call_nr(mod, error_prefix_data, func, ...)
local success, error_message = xpcall(func, print_error_callstack, ...) local success, error_message = xpcall(func, print_error_callstack, ...)
@ -75,7 +69,6 @@ function dmf.safe_call_nr(mod, error_prefix_data, func, ...)
return success return success
end end
-- Safe Call [No return values and error callstack] -- Safe Call [No return values and error callstack]
function dmf.safe_call_nrc(mod, error_prefix_data, func, ...) function dmf.safe_call_nrc(mod, error_prefix_data, func, ...)
local success, error_message = pcall(func, ...) local success, error_message = pcall(func, ...)
@ -85,7 +78,6 @@ function dmf.safe_call_nrc(mod, error_prefix_data, func, ...)
return success return success
end end
-- Safe Call [dofile] -- Safe Call [dofile]
function dmf.safe_call_dofile(mod, error_prefix_data, file_path) function dmf.safe_call_dofile(mod, error_prefix_data, file_path)
if type(file_path) ~= "string" then if type(file_path) ~= "string" then
@ -95,6 +87,14 @@ function dmf.safe_call_dofile(mod, error_prefix_data, file_path)
return dmf.safe_call(mod, error_prefix_data, dofile, file_path) return dmf.safe_call(mod, error_prefix_data, dofile, file_path)
end end
-- Safe Call [io_dofile]
function dmf.safe_call_io_dofile(mod, error_prefix_data, file_path)
if type(file_path) ~= "string" then
show_error(mod, error_prefix_data, "file path should be a string.")
return false
end
return dmf.safe_call(mod, error_prefix_data, mod.io_dofile_unsafe, mod, file_path)
end
-- Format error message and throw error. -- Format error message and throw error.
function dmf.throw_error(error_message, ...) function dmf.throw_error(error_message, ...)

View file

@ -24,7 +24,7 @@ function DMFMod:init(mod_name)
self._data = setmetatable({}, { self._data = setmetatable({}, {
__index = {}, __index = {},
__newindex = function(t_, k) __newindex = function(_, k)
self:warning("Attempt to change internal mod data value (\"%s\"). Changing internal mod data is forbidden.", k) self:warning("Attempt to change internal mod data value (\"%s\"). Changing internal mod data is forbidden.", k)
end end
}) })
@ -38,6 +38,7 @@ function DMFMod:init(mod_name)
set_internal_data(self, "workshop_id", vanilla_mod_data.id) set_internal_data(self, "workshop_id", vanilla_mod_data.id)
set_internal_data(self, "workshop_name", vanilla_mod_data.name) set_internal_data(self, "workshop_name", vanilla_mod_data.name)
set_internal_data(self, "mod_handle", vanilla_mod_data.handle) set_internal_data(self, "mod_handle", vanilla_mod_data.handle)
set_internal_data(self, "is_bundled", vanilla_mod_data.bundled or false)
print(string.format("Init DMF mod '%s' [workshop_name: '%s', workshop_id: %s]", mod_name, vanilla_mod_data.name, print(string.format("Init DMF mod '%s' [workshop_name: '%s', workshop_id: %s]", mod_name, vanilla_mod_data.name,
vanilla_mod_data.id)) vanilla_mod_data.id))

View file

@ -50,7 +50,11 @@ local function resolve_resource(mod, error_prefix_data, resource, resource_value
local type_value = type(resource_value) local type_value = type(resource_value)
if type_value == "string" then if type_value == "string" then
if mod:get_internal_data("is_bundled") then
return dmf.safe_call_dofile(mod, error_prefix_data, resource_value) return dmf.safe_call_dofile(mod, error_prefix_data, resource_value)
else
return dmf.safe_call_io_dofile(mod, error_prefix_data, resource_value)
end
elseif type_value == "function" then elseif type_value == "function" then
return dmf.safe_call(mod, error_prefix_data, resource_value, mod) return dmf.safe_call(mod, error_prefix_data, resource_value, mod)
elseif type_value == "table" then elseif type_value == "table" then
@ -131,7 +135,6 @@ function new_mod(mod_name, mod_resources)
return mod return mod
end end
function get_mod(mod_name) function get_mod(mod_name)
return _mods[mod_name] return _mods[mod_name]
end end