--[[
Sample output: https://pastebin.com/raw/ciay6HXj

File name: "no message" (no extension)

Instructions:
	Symlink this file into the GI working directory
	That's usually where GenshinImpact.exe is.

Function description:
	sb_1184180413  c-call function with args

Hints for modifying this script:
	1) There is mostly no error handling in GI!
	2) Run the script in Lua CLI (i.e. 'lua "no message"') to check for syntax errors
	3) Uncomment functions until you see basic log information again
]]

local HOOK_RECURSIVE = true -- more precise hooks, but may stall the game

local LH = {} -- file-wide access

local function dump(val, depth, seen)
	depth = depth or 0
	seen = seen or {}
	if seen[val] then return "<CIRCULAR REF>" end
	if depth > 10 then return "<TOO DEEP>" end
	if val == nil then return "nil" end

	local txt = {}
	if type(val) == "table" then
		seen[val] = true
		txt[#txt + 1] = "{"
		for k, v in pairs(val) do
			txt[#txt + 1] = string.rep("\t", depth + 1) ..
				("[\"%s\"] = %s"):format(tostring(k), dump(v, depth + 1, seen))
		end
		txt[#txt + 1] = string.rep("\t", depth) .. "}"
	elseif type(val) == "string" then
		txt[#txt + 1] =  '"' .. val:gsub('\n', "$LF"):gsub('"', '\\"') .. '"'
	else
		txt[#txt + 1] = tostring(val) 
	end
	return table.concat(txt, "\n")
end

local F = io.open("lualog_" .. os.date("%H%M-%S") .. ".txt", "a")
F:write("=== LOG START: " .. os.date() .. "\n")
local function LOG(msg)
	F:write(msg)
	F:write("\n")
end

-- HOOK FUNCTIONS
table.unpack = table.unpack or unpack -- Lua compatibility

local hooked = {}
local function hook_wrapper(func, name, ...)
	LOG(("> CALL %s = %s"):format(name, dump({...}, 1)))
	local ret_vals = {func(...)}
	LOG(("< RET  %s = %s"):format(name, dump(ret_vals, 1)))

	LH.interval_func()
	return table.unpack(ret_vals)
end

local function hook_recursive(tb, k, v, path)
	path = path or ""
	v = v or tb[k] or tb

	if hooked[v] then
		return
	end

	-- If a custom table is provided, hook all functions
	if type(v) == "table" then
		if not HOOK_RECURSIVE and v ~= _G then
			return
		end

		if v == table or v == io or v == math
				or v == debug or v == string then
			return
		end

		-- Traverse table to hook all functions
		hooked[v] = true
		for k2, v2 in pairs(v) do
			if type(v2) == "table" then
				hook_recursive(v, k2, v2, path..k2..".")
			else
				hook_recursive(v, k2, v2, path)
			end
		end
		return
	end

	if type(v) ~= "function" then
		return
	end
	-- Do not hook common functions to avoid recursion
	if k == "tostring" or k == "type" or k == "sethook" or k == "typeof"
			or k == "ipairs" or k == "pairs" or k == "next" or k == "select"
			or k == "print"
			or k == "rawset" or k == "rawget"
			or k == "setmetatable" or k == "getmetatable" then
		return
	end
	LOG("HOOK " .. path .. k)
	tb[k] = function(...)
		return hook_wrapper(v, path .. k, ...)
	end
	hooked[tb[k]] = true
end


-- INTERVAL-TRIGGERED FUNCTIONS
-- socket.gettime() cannot be loaded
local last_time = 0
local get_time = os.time
local set_hook = debug.sethook

LH.interval_func = function()
	if get_time() < last_time + 2 then
		return
	end
	last_time = get_time()

	LOG("=== DUMP OF _G")
	LOG(dump(_G))

	hook_recursive(_G)

	-- Manual function triggering
	set_hook(LH.interval_func, "r", 1000)
end

set_hook(LH.interval_func, "rl", 1)

hook_recursive(_G)

rawget(_G, "test call, returns nil")
