Skip to content

Instantly share code, notes, and snippets.

@Autoplay1999
Last active July 30, 2025 23:01
Show Gist options
  • Select an option

  • Save Autoplay1999/5c683989202cc30f5b67343c89579e32 to your computer and use it in GitHub Desktop.

Select an option

Save Autoplay1999/5c683989202cc30f5b67343c89579e32 to your computer and use it in GitHub Desktop.
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