Skip to content

Instantly share code, notes, and snippets.

@d-lua-stuff
Last active September 1, 2022 16:03
Show Gist options
  • Save d-lua-stuff/180707c172382d06d2c80213a1638f66 to your computer and use it in GitHub Desktop.
Save d-lua-stuff/180707c172382d06d2c80213a1638f66 to your computer and use it in GitHub Desktop.
Global variable dumping script for Dual Universe. See https://board.dualthegame.com/index.php?/topic/20052-lua-all-global-variables/ for more info. License: WTFPL
local max = math.max
local concat, insert = table.concat, table.insert
local byte, gsub, format, match, rep = string.byte, string.gsub, string.format, string.match, string.rep
local buffer = { "Lua globals dump: \r\n\r\n" }
local should_get_function_params = true
local should_dump_functions = false
local table_visited = {}
local indentChange = " "
local function_names_to_call = {
"^g$",
"^get",
"^is",
"^gravity",
"^distance",
"^world",
"^local",
"Axis$",
"Direction$"
}
local string_names_to_hide = { -- these may contain personally-identifiable information or system configuration details
"^path",
"^cpath"
}
---@param timeToSleepInSeconds number
local function sleep (timeToSleepInSeconds)
if not coroutine.isyieldable() then
error("sleep called from outside a coroutine!")
end
local wakeUpTime = system.getTime() + timeToSleepInSeconds
while system.getTime() < wakeUpTime do coroutine.yield() end
end
---@param str any
---@param patterns string[]
local function matches_any_pattern (str, patterns)
str = tostring(str)
for _, pattern in ipairs(patterns) do
if str:find(pattern) then return true end
end
return false
end
---@param name any
local function should_call_function (name)
return matches_any_pattern(name, function_names_to_call)
end
---@param name any
local function should_hide_string (name)
return matches_any_pattern(name, string_names_to_hide)
end
---@param tbl table
local function get_sorted_keys (tbl)
local keys = {}
for key in pairs(tbl) do
keys[#keys + 1] = key
end
table.sort(keys, function (key1, key2)
return tostring(key1) < tostring(key2)
end)
return keys
end
---@param str string
local function escape_string (str)
str = format("%q", str):gsub("\\\n", "\\n")
local chr
repeat
chr = match(str, "[^%g ]")
if chr then
str = gsub(str, chr, format("\\x%02x", byte(chr)))
end
until chr == nil
return str
end
---@param fn_name string
---@param fn function
local function get_function_params (fn_name, fn)
-- By default, load() uses the string chunk itself as the chunk name,
-- and the chunk name may be included in debug information.
-- This may allow getting names and count of parameters from some functions.
local dump_success, dump_result = pcall(string.dump, fn)
if not dump_success then return nil end
local params = match(dump_result, "function%s+[^%s)]*" .. fn_name .. "%s*%(([^)]*)%)")
if not params then return nil end
params = params:gsub(",%s+", ",") -- remove whitespace after function parameter names
return params
end
---@param value any
local function better_tostring (value)
if type(value) == "string" then
return "string: " .. escape_string(value)
elseif type(value) == "number" then
return "number: " .. value
elseif type(value) == "boolean" then
return "boolean: " .. tostring(value)
else
return tostring(value)
end
end
---@param key any
---@param value any
---@param indent string|nil
local function dump_value (key, value, indent)
indent = indent and (indent .. indentChange) or ""
local key_column = indent .. tostring(key)
local value_column = better_tostring(value)
if type(value) == "string" and should_hide_string(key) then
value_column = "string: <hidden by the dumping script>"
end
if type(value) == "function" and should_get_function_params then
local fn_params = get_function_params(key, value)
if fn_params then
key_column = key_column .. "(" .. fn_params .. ")"
end
end
local column_separator = rep(' ', max(2, 50 - #key_column))
insert(buffer, key_column .. column_separator .. value_column .. "\r\n")
if type(value) == "table" then
if table_visited[value] then
insert(buffer, indent .. indentChange .. "[see above]" .. "\r\n")
else
table_visited[value] = true
local metatable = getmetatable(value)
if metatable then
dump_value("[metatable]", metatable, indent)
end
local sorted_keys = get_sorted_keys(value)
for _, sorted_key in ipairs(sorted_keys) do
dump_value(sorted_key, value[sorted_key], indent)
end
coroutine.yield()
end
end
if type(value) == "function" and should_dump_functions then
local dump_success, dump_result = pcall(string.dump, value)
if dump_success and dump_result then
dump_value('[dump]', dump_result, indent)
end
end
if type(value) == "function" and should_call_function(key) then
local pcall_result = { pcall(value) }
dump_value("[pcall]", pcall_result, indent)
end
end
if not system then
-- prepare a dummy object to test this script
-- luacheck: ignore meaning_of_life
meaning_of_life = {
get_answer = function () return 42 end,
get_question = function () error("Giant computer not available") end
}
setmetatable(meaning_of_life, {})
end
dumping_coroutine = coroutine.create(function ()
if screen then
screen.setCenteredText("Please wait...")
end
if system then
-- wait for element widget data to be populated
sleep(5)
end
dump_value("_G", _G)
dump_value("_ENV", _ENV)
local output = concat(buffer)
if screen then
screen.clear()
screen.setCenteredText("Done!")
end
if system then
system.logInfo(output)
unit.exit()
else
print(output)
end
end)
if not system then
while coroutine.resume(dumping_coroutine) do end
end
-- Add a system update() event handler:
-- coroutine.resume(dumping_coroutine)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment