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/persistent_tables")
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: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()
end

View file

@ -114,15 +114,50 @@ local function handle_io(mod, local_path, file_name, file_extension, args, safe_
-- If the initial open failed, report failure
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
end
end
-- #####################################################################################################################
-- ##### 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
function DMFMod:io_read_content(file_path, file_extension)
return handle_io(self, file_path, nil, file_extension, nil, true, "data")

View file

@ -35,24 +35,63 @@ end
-- ##### 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)
add_io_require_path(path)
end
-- Remove a file path that was previously loaded through io instead of require()
function DMFMod:remove_require_path(path)
remove_io_require_path(path)
end
-- Get all instances of a file created through require()
function DMFMod:get_require_store(path)
return get_require_store(path)
end
-- Get a file through the original, unhooked require() function
function DMFMod:original_require(path, ...)
return original_require(path, ...)

View file

@ -12,17 +12,18 @@ local function pack_pcall(status, ...)
end
local function print_error_callstack(error_message)
if type(error_message) == "table" and error_message.error then
print(string.format(
"<<Script Error>>%s<</Script Error>>\n<<Lua Stack>>%s<</Lua Stack>>\n<<Lua Locals>>%s<</Lua Locals>>\n<<Lua Self>>%s<</Lua Self>>",
error_message.error, error_message.traceback, error_message.locals, error_message.self
))
local function print_error_callstack(err)
if type(err) == "table" and err.error then
Log.error(
"DMF",
"%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
print("Error: " .. tostring(error_message) .. "\n" .. Script.callstack())
Log.error("DMF", "Error: %s\n%s", tostring(err), Script.callstack())
end
return error_message
return err
end
@ -45,12 +46,6 @@ function DMFMod:pcall(...)
return dmf.safe_call(self, "(pcall)", ...)
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 ##########################################################################
-- #####################################################################################################################
@ -65,7 +60,6 @@ function dmf.safe_call(mod, error_prefix_data, func, ...)
return success, unpack(return_values, 1, return_values.n)
end
-- Safe Call [No return values]
function dmf.safe_call_nr(mod, error_prefix_data, func, ...)
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
end
-- Safe Call [No return values and error callstack]
function dmf.safe_call_nrc(mod, error_prefix_data, func, ...)
local success, error_message = pcall(func, ...)
@ -85,7 +78,6 @@ function dmf.safe_call_nrc(mod, error_prefix_data, func, ...)
return success
end
-- Safe Call [dofile]
function dmf.safe_call_dofile(mod, error_prefix_data, file_path)
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)
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.
function dmf.throw_error(error_message, ...)

View file

@ -24,7 +24,7 @@ function DMFMod:init(mod_name)
self._data = setmetatable({}, {
__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)
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_name", vanilla_mod_data.name)
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,
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)
if type_value == "string" then
if mod:get_internal_data("is_bundled") then
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
return dmf.safe_call(mod, error_prefix_data, resource_value, mod)
elseif type_value == "table" then
@ -131,7 +135,6 @@ function new_mod(mod_name, mod_resources)
return mod
end
function get_mod(mod_name)
return _mods[mod_name]
end