lua_injection.lua 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. --[[
  2. Sample output: https://pastebin.com/raw/ciay6HXj
  3. File name: "no message" (no extension)
  4. Instructions:
  5. Symlink this file into the GI working directory
  6. That's usually where GenshinImpact.exe is.
  7. Function description:
  8. sb_1184180413 c-call function with args
  9. Hints for modifying this script:
  10. 1) There is mostly no error handling in GI!
  11. 2) Run the script in Lua CLI (i.e. 'lua "no message"') to check for syntax errors
  12. 3) Uncomment functions until you see basic log information again
  13. ]]
  14. local HOOK_RECURSIVE = true -- more precise hooks, but may stall the game
  15. local DUMP_ON_XPCALL = true -- xpcall is called once after login. dump _G there
  16. local LH = {} -- file-wide access
  17. local function dump(val, depth, seen)
  18. depth = depth or 0
  19. seen = seen or {}
  20. if seen[val] then return "<CIRCULAR REF>" end
  21. if depth > 10 then return "<TOO DEEP>" end
  22. if val == nil then return "nil" end
  23. local txt = {}
  24. if type(val) == "table" then
  25. seen[val] = true
  26. txt[#txt + 1] = "{"
  27. for k, v in pairs(val) do
  28. txt[#txt + 1] = string.rep("\t", depth + 1) ..
  29. ("[\"%s\"] = %s"):format(tostring(k), dump(v, depth + 1, seen))
  30. end
  31. txt[#txt + 1] = string.rep("\t", depth) .. "}"
  32. elseif type(val) == "string" then
  33. txt[#txt + 1] = '"' .. val:gsub('\n', "$LF"):gsub('"', '\\"') .. '"'
  34. else
  35. txt[#txt + 1] = tostring(val)
  36. end
  37. return table.concat(txt, "\n")
  38. end
  39. local sb_c_exec, sb_alt_time = sb_1184180413, os.time
  40. LH.get_time = function()
  41. if true or not sb_c_exec then
  42. -- Special func missing (for script dry run)
  43. return sb_alt_time()
  44. end
  45. -- I don't know why, but os.time() yields better results
  46. return sb_c_exec("GetTickCount64") / 1000
  47. end
  48. local F = io.open("lualog_" .. os.date("%H%M-%S") .. ".txt", "a")
  49. F:write("=== LOG START: " .. os.date() .. "\n")
  50. local start_time = LH.get_time()
  51. local function LOG(msg)
  52. if LH and LH.get_time then
  53. F:write(("[%.2f] "):format(LH.get_time() - start_time))
  54. end
  55. F:write(msg)
  56. F:write("\n")
  57. F:flush() -- Causes delays but that's okay
  58. end
  59. -- HOOK FUNCTIONS
  60. table.unpack = table.unpack or unpack -- Lua compatibility
  61. local function hook_wrapper(func, name, ...)
  62. -- These must not be logged -> deadlock
  63. local no_log = (name == "getmetatable" or name == "setmetatable"
  64. or name == "rawset" or name == "rawget")
  65. LOG(("> CALL %s = %s"):format(name, no_log and "<>" or dump({...}, 1)))
  66. local ret_vals = {func(...)}
  67. LOG(("< RET %s = %s"):format(name, no_log and "<>" or dump(ret_vals, 1)))
  68. LH.interval_func()
  69. if DUMP_ON_XPCALL and name == "xpcall" then
  70. LOG("=== XPCALL!!")
  71. LOG(dump(_G))
  72. end
  73. return table.unpack(ret_vals)
  74. end
  75. local hooked = {}
  76. local recursion_depth = 0
  77. local function hook_recursive(tb, name, v, path_text, seen)
  78. path = path or ""
  79. v = v or tb[name] or tb
  80. path_text = path_text or ""
  81. if not seen and recursion_depth > 0 then
  82. return -- Recursion still ongoing
  83. end
  84. seen = seen or {} -- For circular references
  85. -- If a custom table is provided, hook all functions
  86. if type(v) == "table" then
  87. if not HOOK_RECURSIVE and v ~= _G then
  88. return
  89. end
  90. if seen[v] or v == table or v == io or v == math
  91. or v == package.searchers
  92. or v == debug or v == string then
  93. return
  94. end
  95. -- Traverse table to hook all functions
  96. recursion_depth = recursion_depth + 1
  97. seen[v] = true
  98. for k2, v2 in pairs(v) do
  99. if type(v2) == "table" then
  100. hook_recursive(v, k2, v2, path_text .. k2 .. ".", seen)
  101. else
  102. hook_recursive(v, k2, v2, path_text, seen)
  103. end
  104. end
  105. recursion_depth = recursion_depth - 1
  106. return
  107. end
  108. if hooked[v] or type(v) ~= "function" then
  109. return
  110. end
  111. -- Do not hook common functions to avoid recursion
  112. local k = name
  113. if k == "tostring" or k == "tonumber" or k == "type" or k == "typeof"
  114. or k == "ipairs" or k == "pairs" or k == "next" or k == "select"
  115. or k == "rawget" or k == "getmetatable"
  116. or k == "print" then
  117. return
  118. end
  119. LOG("HOOK " .. path_text .. name)
  120. tb[name] = function(...)
  121. return hook_wrapper(v, path_text .. name, ...)
  122. end
  123. hooked[tb[name]] = true
  124. end
  125. -- INTERVAL-TRIGGERED FUNCTIONS
  126. local last_time = 0
  127. local func_locked = false
  128. local my_pcall = pcall -- Not hooked
  129. local my_sethook = debug.sethook
  130. LH.cfuncs = {}
  131. LH.ctimers = {}
  132. LH.interval_func = function()
  133. if func_locked then return end
  134. my_sethook(LH.interval_func, "r", 1E10)
  135. local new_time = LH.get_time()
  136. if new_time < last_time + 0.1 then
  137. return
  138. end
  139. -- Lock it to avoid recursion deadlocks
  140. func_locked = true
  141. last_time = new_time
  142. hook_recursive(_G)
  143. LOG("==> Running " .. #LH.cfuncs .. " callbacks")
  144. for i, func in ipairs(LH.cfuncs) do
  145. local due = LH.ctimers[i] or 0
  146. if due <= new_time then
  147. -- Trigger it!
  148. local ok, delay = my_pcall(func)
  149. if not ok then
  150. LOG("==> Callback error:")
  151. LOG(delay)
  152. -- Retry later
  153. delay = nil
  154. end
  155. -- Trigger again in at least X or 10 seconds
  156. LH.ctimers[i] = new_time + (delay or 13)
  157. end
  158. end
  159. func_locked = false
  160. end
  161. local function reg_interval_func(start_delay, func)
  162. LH.cfuncs[#LH.cfuncs + 1] = func
  163. LH.ctimers[#LH.cfuncs] = LH.get_time() + (start_delay or 0)
  164. end
  165. reg_interval_func(0.1, function()
  166. -- This is pretty huge.
  167. LOG("=== DUMP OF _G")
  168. LOG(dump(_G))
  169. return 99 -- never again
  170. end)
  171. reg_interval_func(0, function()
  172. if not sb_75657375 then
  173. return 0 -- Call asap again
  174. end
  175. -- Use launch arg "-nolog" to see this (Linux)
  176. io.stdout:write(">>> CALLING FUNCTIONS\n")
  177. local ok, pkg = pcall(require, "Hotfix/Hotfix")
  178. if okg then
  179. pkg.PrintTime()
  180. LOG(dump(pkg.GetPatchTable()))
  181. end
  182. local ok, console = pcall(require, "Console")
  183. if ok then
  184. --[[
  185. "ConsolePrint"
  186. "onSocketData"
  187. "onSocketConnected"
  188. "socketCrFunc"
  189. ]]
  190. end
  191. --[[if xlua then
  192. xlua.load_assembly("")
  193. end]]
  194. return 0.1
  195. end)
  196. -- FINAL: SET HOOK READY, START FUNCS
  197. LH.interval_func()