-
-
Save matpratta/24e54390db09d2c71124185acafe8328 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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