diff --git a/scripts/mods/dmf/dmf_loader.lua b/scripts/mods/dmf/dmf_loader.lua index ff0b3c9..99a2bf4 100644 --- a/scripts/mods/dmf/dmf_loader.lua +++ b/scripts/mods/dmf/dmf_loader.lua @@ -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 diff --git a/scripts/mods/dmf/modules/core/io.lua b/scripts/mods/dmf/modules/core/io.lua index b9308ab..3e5e3ca 100644 --- a/scripts/mods/dmf/modules/core/io.lua +++ b/scripts/mods/dmf/modules/core/io.lua @@ -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 safe_call then - status, result = pcall(function () + status, result = pcall(function() return read_or_execute(file_path, args, return_type) 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 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") diff --git a/scripts/mods/dmf/modules/core/require.lua b/scripts/mods/dmf/modules/core/require.lua index aed69b8..a52feb4 100644 --- a/scripts/mods/dmf/modules/core/require.lua +++ b/scripts/mods/dmf/modules/core/require.lua @@ -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, ...) @@ -63,7 +102,7 @@ end -- ##################################################################################################################### -- 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 return dmf:dofile(path) else diff --git a/scripts/mods/dmf/modules/core/safe_calls.lua b/scripts/mods/dmf/modules/core/safe_calls.lua index c8683ed..e2b2777 100644 --- a/scripts/mods/dmf/modules/core/safe_calls.lua +++ b/scripts/mods/dmf/modules/core/safe_calls.lua @@ -8,21 +8,22 @@ local print = __print -- ##################################################################################################################### local function pack_pcall(status, ...) - return status, {n = select('#', ...), ...} + return status, { n = select('#', ...), ... } end -local function print_error_callstack(error_message) - if type(error_message) == "table" and error_message.error then - print(string.format( - "<>\n<>%s<>\n<>%s<>\n<>%s<>", - 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<>%s<>\n<>%s<>\n<>%s<>", + 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, ...) diff --git a/scripts/mods/dmf/modules/dmf_mod_data.lua b/scripts/mods/dmf/modules/dmf_mod_data.lua index 048d299..43baaef 100644 --- a/scripts/mods/dmf/modules/dmf_mod_data.lua +++ b/scripts/mods/dmf/modules/dmf_mod_data.lua @@ -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 }) @@ -37,7 +37,8 @@ function DMFMod:init(mod_name) local vanilla_mod_data = Managers.mod:mod_data(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, "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)) diff --git a/scripts/mods/dmf/modules/dmf_mod_manager.lua b/scripts/mods/dmf/modules/dmf_mod_manager.lua index 5399aab..31bc1f5 100644 --- a/scripts/mods/dmf/modules/dmf_mod_manager.lua +++ b/scripts/mods/dmf/modules/dmf_mod_manager.lua @@ -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 - return dmf.safe_call_dofile(mod, error_prefix_data, resource_value) + 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