Last active
July 30, 2025 23:01
-
-
Save Autoplay1999/5c683989202cc30f5b67343c89579e32 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 function isNaN(value) | |
| return type(value) == "number" and value ~= value | |
| end | |
| local function isInf(value) | |
| return type(value) == "number" and (value == math.huge or value == -math.huge) | |
| end | |
| local function escapeUTF8(str) | |
| local function charToHex(c) | |
| return string.format("\\u%04X", string.byte(c)) | |
| end | |
| local result = str:gsub('([\x00-\x1F\x80-\xFF])', function(c) | |
| return charToHex(c) | |
| end) | |
| result = result:gsub('\\', '\\\\') | |
| :gsub('"', '\\"') | |
| :gsub('\n', '\\n') | |
| :gsub('\r', '\\r') | |
| :gsub('\t', '\\t') | |
| :gsub('\b', '\\b') | |
| :gsub('\f', '\\f') | |
| return result | |
| end | |
| function toJsonVar(v) | |
| local vt = type(v) | |
| if vt == 'string' then | |
| return '"' .. escapeUTF8(v) .. '"' | |
| elseif vt == 'boolean' then | |
| return v and 'true' or 'false' | |
| elseif vt == 'number' then | |
| return ifThen(isNaN(v) or isInf(v), 'null', tostring(v)) | |
| elseif vt == 'nil' then | |
| return 'null' | |
| else | |
| return '"' .. vt .. '"' | |
| end | |
| end | |
| function getTableKeys(tbl) | |
| local keys = {} | |
| local isArray = true | |
| local expectedIndex = 1 | |
| for key in pairs(tbl) do | |
| if isArray and (type(key) ~= "number" or key ~= expectedIndex) then | |
| isArray = false | |
| end | |
| keys[#keys + 1] = key | |
| expectedIndex = expectedIndex + 1 | |
| end | |
| if not isArray then | |
| table.sort(keys, function(a, b) | |
| return tostring(a) < tostring(b) | |
| end) | |
| end | |
| return keys, isArray | |
| end | |
| function ifThen(cond, v1, v2) | |
| return cond and v1 or v2 | |
| end | |
| function pushIndent(level) | |
| return string.rep(' ', level) | |
| end | |
| function dumpVar(var) | |
| local function mkTMPTable(tbl) | |
| local keys, isArray = getTableKeys(tbl) | |
| return { | |
| table = tbl, | |
| keys = keys, | |
| is_array = isArray, | |
| key_count = #keys, | |
| current_key = #keys > 0 and 1 or 0 | |
| } | |
| end | |
| local result = toJsonVar(var) | |
| if type(var) ~= 'table' then | |
| return result | |
| end | |
| local visited = {} | |
| local tables_stack = {} | |
| local level = 1 | |
| local current_table = mkTMPTable(var) | |
| local output = ifThen(current_table.is_array, '[\n', '{\n') | |
| visited[var] = true | |
| while true do | |
| while current_table.current_key <= #current_table.keys do | |
| local key = current_table.keys[current_table.current_key] | |
| local value = current_table.table[key] | |
| local value_type = type(value) | |
| current_table.current_key = current_table.current_key + 1 | |
| if current_table.key_count == 0 then | |
| break | |
| end | |
| output = output .. pushIndent(level) | |
| if not current_table.is_array then | |
| output = output .. toJsonVar(tostring(key)) .. ': ' | |
| end | |
| if value_type == 'table' then | |
| if visited[value] then | |
| output = output .. '"[circular reference]"' | |
| if current_table.current_key <= #current_table.keys then | |
| output = output .. ',\n' | |
| else | |
| output = output .. '\n' | |
| end | |
| else | |
| visited[value] = true | |
| table.insert(tables_stack, current_table) | |
| current_table = mkTMPTable(value) | |
| level = level + 1 | |
| output = output .. ifThen(current_table.is_array, '[', '{') | |
| if current_table.key_count ~= 0 then | |
| output = output .. '\n' | |
| end | |
| end | |
| else | |
| output = output .. toJsonVar(value) | |
| if current_table.current_key <= #current_table.keys then | |
| output = output .. ',\n' | |
| else | |
| output = output .. '\n' | |
| end | |
| end | |
| end | |
| level = level - 1 | |
| if current_table.key_count ~= 0 then | |
| output = output .. pushIndent(level) | |
| end | |
| output = output .. ifThen(current_table.is_array, ']', '}') | |
| if #tables_stack > 0 then | |
| current_table = table.remove(tables_stack) | |
| if current_table.current_key <= #current_table.keys then | |
| output = output .. ',\n' | |
| else | |
| output = output .. '\n' | |
| end | |
| else | |
| break | |
| end | |
| end | |
| return output | |
| end | |
| -- Creates a node for the memory scanning structure | |
| local function CreateStructNode(Base, Offset, Level, Parent) | |
| return { | |
| Base = Base or 0, | |
| Offset = Offset or 0, | |
| Level = Level or 0, | |
| Scanned = false, | |
| Locked = false, | |
| ParentNode = Parent, | |
| ChildNodes = {}, | |
| FoundOffset = {} | |
| } | |
| end | |
| -- Checks if an address is within any visited region | |
| local function IsInRegion(Address, VisitedList, RegionSize) | |
| for _, visitedAddr in ipairs(VisitedList) do | |
| if Address >= visitedAddr and Address < visitedAddr + RegionSize then | |
| return true | |
| end | |
| end | |
| VisitedList[#VisitedList + 1] = Address | |
| return false | |
| end | |
| -- Scans memory for a specific value and builds a path structure | |
| function ScanValue(Value, StartAddress, StructSize, MaxLevel) | |
| local visitedList = {[1] = StartAddress} | |
| local root = CreateStructNode(StartAddress) | |
| local curNode = root | |
| local path = {} | |
| local out = {} | |
| -- First pass: Build the node tree | |
| while curNode do | |
| if not curNode.Scanned then | |
| local buffer = readBytes(curNode.Base, StructSize, true) | |
| if not buffer then | |
| return nil | |
| end | |
| -- Process 4-byte chunks | |
| for i = 0, (StructSize // 4) - 1 do | |
| local idx = i * 4 | |
| local curValue = buffer[idx + 1] | | |
| (buffer[idx + 2] << 8) | | |
| (buffer[idx + 3] << 16) | | |
| (buffer[idx + 4] << 24) | |
| if curValue ~= 0 and readInteger(curValue) then | |
| if curValue == Value then | |
| curNode.FoundOffset[#curNode.FoundOffset + 1] = idx | |
| curNode.Locked = true | |
| end | |
| if curNode.Level < MaxLevel and not IsInRegion(curValue, visitedList, StructSize) then | |
| curNode.ChildNodes[#curNode.ChildNodes + 1] = CreateStructNode(curValue, idx, curNode.Level + 1, curNode) | |
| end | |
| end | |
| end | |
| curNode.Scanned = true | |
| end | |
| -- Find next unscanned child | |
| local nextNode | |
| for _, child in ipairs(curNode.ChildNodes) do | |
| if not child.Scanned then | |
| nextNode = child | |
| break | |
| end | |
| end | |
| if nextNode then | |
| curNode = nextNode | |
| else | |
| local curBase = curNode.Base | |
| local isLocked = curNode.Locked | |
| curNode = curNode.ParentNode | |
| if curNode and not isLocked then | |
| for i, child in ipairs(curNode.ChildNodes) do | |
| if child.Base == curBase then | |
| table.remove(curNode.ChildNodes, i) | |
| break | |
| end | |
| end | |
| elseif curNode then | |
| curNode.Locked = true | |
| end | |
| end | |
| end | |
| -- Second pass: Collect paths | |
| curNode = root | |
| while curNode do | |
| local nextNode | |
| for _, child in ipairs(curNode.ChildNodes) do | |
| if child.Scanned then | |
| nextNode = child | |
| break | |
| end | |
| end | |
| if not nextNode then | |
| for _, offset in ipairs(curNode.FoundOffset) do | |
| path[#path + 1] = offset | |
| out[#out + 1] = {table.unpack(path)} | |
| path[#path] = nil | |
| end | |
| curNode = curNode.ParentNode | |
| if curNode then | |
| table.remove(curNode.ChildNodes, 1) | |
| path[#path] = nil | |
| end | |
| else | |
| curNode = nextNode | |
| path[#path + 1] = curNode.Offset | |
| end | |
| end | |
| return out | |
| end | |
| -- Formats scan results as hexadecimal strings | |
| function DumpResultScanValue(Result) | |
| if not Result then | |
| return {} | |
| end | |
| local out = {} | |
| for _, path in ipairs(Result) do | |
| local str = table.concat({ | |
| table.concat( | |
| {table.unpack(path, 1, #path)}, | |
| " -> ", | |
| 1, | |
| #path | |
| ) | |
| }, string.format("%X")) | |
| out[#out + 1] = str | |
| end | |
| return out | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment