local CE = { vtByte = 0, vtWord = 1, vtDword = 2, vtQword = 3, vtSingle = 4, vtDouble = 5, vtString = 6, vtGrouped = 14, } local Types = { -- Unsigned integers uint8 = { ce = CE.vtByte, size = 1 }, uint16 = { ce = CE.vtWord, size = 2 }, uint32 = { ce = CE.vtDword, size = 4 }, uint64 = { ce = CE.vtQword, size = 8 }, -- Signed integers int8 = { ce = CE.vtByte, size = 1 }, int16 = { ce = CE.vtWord, size = 2 }, int32 = { ce = CE.vtDword, size = 4 }, int64 = { ce = CE.vtQword, size = 8 }, -- Floating point float = { ce = CE.vtSingle, size = 4 }, double = { ce = CE.vtDouble, size = 8 }, -- Pointers ptr = { ce = CE.vtDword, size = 4 }, ptr32 = { ce = CE.vtDword, size = 4 }, ptr64 = { ce = CE.vtQword, size = 8 }, -- Aliases bool = { ce = CE.vtByte, size = 1 }, bool32 = { ce = CE.vtDword, size = 4 }, char = { ce = CE.vtByte, size = 1 }, byte = { ce = CE.vtByte, size = 1 }, word = { ce = CE.vtWord, size = 2 }, dword = { ce = CE.vtDword, size = 4 }, qword = { ce = CE.vtQword, size = 8 }, } function SetPointerSize(bits) if bits == 64 then Types.ptr = { ce = CE.vtQword, size = 8 } else Types.ptr = { ce = CE.vtDword, size = 4 } end end local StructDef = {} StructDef.__index = StructDef -- @param name -- @param parent function StructDef.new(name, parent) local self = setmetatable({}, StructDef) self.name = name self.parent = parent self.ownFields = {} self.embeddings = {} if parent then self._offset = parent:totalSize() else self._offset = 0 end return self end -- @param name -- @param typeName (uint32, float, ptr etc) -- @param opts {hex=bool, color=number} function StructDef:field(name, typeName, opts) opts = opts or {} local typeInfo = Types[typeName] if not typeInfo then error(string.format("Unknown type '%s' for field '%s'", typeName, name)) end table.insert(self.ownFields, { fieldOffset = self._offset, name = name, type = typeInfo.ce, size = typeInfo.size, color = opts.color, hex = opts.hex, }) self._offset = self._offset + typeInfo.size return self end StructDef.f = StructDef.field --- Add hex-field function StructDef:hex(name, typeName, opts) opts = opts or {} opts.hex = true return self:field(name, typeName, opts) end --- Add string function StructDef:string(name, size, opts) opts = opts or {} table.insert(self.ownFields, { fieldOffset = self._offset, name = name, type = CE.vtString, string_size = size, size = size, color = opts.color, }) self._offset = self._offset + size return self end --- Add array function StructDef:array(name, typeName, count, opts) opts = opts or {} local typeInfo = Types[typeName] if not typeInfo then error(string.format("Unknown type '%s' for array '%s'", typeName, name)) end for i = 0, count - 1 do table.insert(self.ownFields, { fieldOffset = self._offset + i * typeInfo.size, name = string.format("%s[%d]", name, i), type = typeInfo.ce, size = typeInfo.size, color = opts.color, hex = opts.hex, }) end self._offset = self._offset + count * typeInfo.size return self end --- skip to X bytes function StructDef:paddingTo(targetOffset, opts) local size = targetOffset - self._offset if size <= 0 then return self end opts = opts or {} local remaining = size while remaining >= 4 do local name = string.format("unk_%04X", self._offset) self:field(name, "uint32", opts) remaining = remaining - 4 end while remaining >= 1 do local name = string.format("unk_%04X", self._offset) self:field(name, "uint8", opts) remaining = remaining - 1 end return self end --- skip N bytes function StructDef:padding(size, opts) opts = opts or {} local remaining = size while remaining >= 4 do local name = string.format("unk_%04X", self._offset) self:field(name, "uint32", opts) remaining = remaining - 4 end while remaining >= 1 do local name = string.format("unk_%04X", self._offset) self:field(name, "uint8", opts) remaining = remaining - 1 end return self end function StructDef:skip(size) self._offset = self._offset + size return self end function StructDef:unk(size, opts) opts = opts or {} local name = string.format("unk_%04X", self._offset) if size == 1 then return self:field(name, "uint8", opts) elseif size == 2 then return self:field(name, "uint16", opts) elseif size == 4 then return self:field(name, "uint32", opts) elseif size == 8 then return self:field(name, "uint64", opts) else return self:array(name, "uint8", size, opts) end end function StructDef:alignTo(alignment) local rem = self._offset % alignment if rem ~= 0 then self._offset = self._offset + (alignment - rem) end return self end function StructDef:at(offset) self._offset = offset return self end function StructDef:currentOffset() return self._offset end -- @param name -- @param otherStruct -- @param opts {expand=bool} function StructDef:embed(name, otherStruct, opts) opts = opts or {} local baseOffset = self._offset table.insert(self.embeddings, { type = "embed", name = name, struct = otherStruct, offset = baseOffset, size = otherStruct:totalSize(), expand = opts.expand or false, }) for _, f in ipairs(otherStruct:getAllFields()) do local newField = {} for k, v in pairs(f) do newField[k] = v end newField.fieldOffset = baseOffset + f.fieldOffset newField.name = name .. "." .. f.name newField.structName = nil newField._embeddedIn = name table.insert(self.ownFields, newField) end self._offset = self._offset + otherStruct:totalSize() return self end -- @param name -- @param otherStruct -- @param count -- @param opts {expand=bool} function StructDef:structArray(name, otherStruct, count, opts) opts = opts or {} local structSize = otherStruct:totalSize() local baseOffset = self._offset table.insert(self.embeddings, { type = "structArray", name = name, struct = otherStruct, offset = baseOffset, count = count, size = count * structSize, elemSize = structSize, expand = opts.expand or false, }) for i = 0, count - 1 do local elemOffset = self._offset + i * structSize for _, f in ipairs(otherStruct:getAllFields()) do local newField = {} for k, v in pairs(f) do newField[k] = v end newField.fieldOffset = elemOffset + f.fieldOffset newField.name = string.format("%s[%d].%s", name, i, f.name) newField._embeddedIn = name newField._arrayIndex = i table.insert(self.ownFields, newField) end end self._offset = self._offset + count * structSize return self end -- :ptrArray("fieldName", count) -- void*[] -- :ptrArray("fieldName", count, opts) -- void*[] with opts -- :ptrArray("TypeName", "fieldName", count) -- TypeName*[] -- :ptrArray("TypeName", "fieldName", count, opts) -- TypeName*[] with opts function StructDef:ptrArray(arg1, arg2, arg3, arg4) local typeName, fieldName, count, opts if type(arg2) == "number" then -- ptrArray("fieldName", count) or ptrArray("fieldName", count, opts) typeName = nil fieldName = arg1 count = arg2 opts = arg3 or {} elseif type(arg2) == "string" then -- ptrArray("TypeName", "fieldName", count) or ptrArray("TypeName", "fieldName", count, opts) typeName = arg1 fieldName = arg2 count = arg3 opts = arg4 or {} else error("Invalid arguments for ptrArray()") end local typeInfo = Types.ptr for i = 0, count - 1 do table.insert(self.ownFields, { fieldOffset = self._offset + i * typeInfo.size, name = string.format("%s[%d]", fieldName, i), type = typeInfo.ce, size = typeInfo.size, color = opts.color, hex = true, isPointer = true, ptrType = typeName, _ptrArrayBase = fieldName, }) end self._offset = self._offset + count * typeInfo.size return self end function StructDef:ptr(arg1, arg2, arg3) local typeName, fieldName, opts if arg2 == nil then -- ptr("fieldName") typeName = nil fieldName = arg1 opts = {} elseif type(arg2) == "table" then -- ptr("fieldName", opts) typeName = nil fieldName = arg1 opts = arg2 elseif type(arg2) == "string" then -- ptr("TypeName", "fieldName") or ptr("TypeName", "fieldName", opts) typeName = arg1 fieldName = arg2 opts = arg3 or {} else error("Invalid arguments for ptr()") end local typeInfo = Types.ptr table.insert(self.ownFields, { fieldOffset = self._offset, name = fieldName, type = typeInfo.ce, size = typeInfo.size, color = opts.color, hex = true, isPointer = true, ptrType = typeName, }) self._offset = self._offset + typeInfo.size return self end function StructDef:toCStruct(options) options = options or {} local indent = options.indent or " " local collapsePadding = options.collapsePadding ~= false local flattenInheritance = options.flattenInheritance or false local lines = {} local ceToC = { [CE.vtByte] = "uint8_t", [CE.vtWord] = "uint16_t", [CE.vtDword] = "uint32_t", [CE.vtQword] = "uint64_t", [CE.vtSingle] = "float", [CE.vtDouble] = "double", [CE.vtString] = "char", } local function getPtrTypeName(field) return field.ptrType or "void" end local function sanitizeName(name) return name:gsub("%.", "_"):gsub("%[", "_"):gsub("%]", "") end local function isUnkField(name) return name:match("^unk_[%dA-Fa-f]+$") ~= nil end local function isUnkArray(name) return name:match("^unk_[%dA-Fa-f]+%[%d+%]$") ~= nil end local function isAnyUnk(name) return isUnkField(name) or isUnkArray(name) end local fields local embeddingMap = {} if flattenInheritance then fields = self:getAllFields() for _, emb in ipairs(self:getAllEmbeddings()) do embeddingMap[emb.offset] = emb end else fields = self.ownFields for _, emb in ipairs(self.embeddings) do embeddingMap[emb.offset] = emb end end if flattenInheritance and self.parent then table.insert(lines, string.format("// Inherits from: %s (size: 0x%X)", self.parent.name, self.parent:totalSize())) end table.insert(lines, string.format("struct %s {", self.name)) if not flattenInheritance and self.parent then table.insert(lines, string.format("%s%s base; // 0x%04X", indent, self.parent.name, 0)) end local i = 1 local processedEmbeddings = {} while i <= #fields do local field = fields[i] local cType = ceToC[field.type] or "uint8_t" local emb = embeddingMap[field.fieldOffset] if emb and not processedEmbeddings[emb.name] then processedEmbeddings[emb.name] = true if emb.type == "embed" then if emb.expand then table.insert(lines, string.format("%sstruct { // %s::%s", indent, emb.struct.name, emb.name)) for _, sf in ipairs(emb.struct:getAllFields()) do local subType = ceToC[sf.type] or "uint8_t" if sf.type == CE.vtString and sf.string_size then table.insert(lines, string.format("%s%schar %s[%d]; // +0x%02X", indent, indent, sanitizeName(sf.name), sf.string_size, sf.fieldOffset)) elseif sf.isPointer then table.insert(lines, string.format("%s%s%s* %s; // +0x%02X", indent, indent, getPtrTypeName(sf), sanitizeName(sf.name), sf.fieldOffset)) else table.insert(lines, string.format("%s%s%s %s; // +0x%02X", indent, indent, subType, sanitizeName(sf.name), sf.fieldOffset)) end end table.insert(lines, string.format("%s} %s; // 0x%04X", indent, emb.name, emb.offset)) else table.insert(lines, string.format("%s%s %s; // 0x%04X", indent, emb.struct.name, emb.name, emb.offset)) end while i <= #fields and fields[i]._embeddedIn == emb.name do i = i + 1 end elseif emb.type == "structArray" then if emb.expand then table.insert(lines, string.format("%sstruct { // %s element", indent, emb.struct.name)) for _, sf in ipairs(emb.struct:getAllFields()) do local subType = ceToC[sf.type] or "uint8_t" if sf.isPointer then table.insert(lines, string.format("%s%s%s* %s; // +0x%02X", indent, indent, getPtrTypeName(sf), sanitizeName(sf.name), sf.fieldOffset)) else table.insert(lines, string.format("%s%s%s %s; // +0x%02X", indent, indent, subType, sanitizeName(sf.name), sf.fieldOffset)) end end table.insert(lines, string.format("%s} %s[%d]; // 0x%04X (0x%X bytes)", indent, emb.name, emb.count, emb.offset, emb.size)) else table.insert(lines, string.format("%s%s %s[%d]; // 0x%04X (0x%X bytes)", indent, emb.struct.name, emb.name, emb.count, emb.offset, emb.size)) end while i <= #fields and fields[i]._embeddedIn == emb.name do i = i + 1 end end elseif collapsePadding and isAnyUnk(field.name) then local startOffset = field.fieldOffset local totalSize = 0 local j = i while j <= #fields do local f = fields[j] local expectedOffset = startOffset + totalSize if isAnyUnk(f.name) and f.fieldOffset == expectedOffset then totalSize = totalSize + f.size j = j + 1 else break end end if totalSize > 0 then table.insert(lines, string.format("%suint8_t pad_%04X[0x%X]; // 0x%04X", indent, startOffset, totalSize, startOffset)) end i = j elseif field.isPointer and field._ptrArrayBase and field.name:match("%[0%]$") then local baseName = field._ptrArrayBase local j = i + 1 local count = 1 while j <= #fields do if fields[j]._ptrArrayBase == baseName then count = count + 1 j = j + 1 else break end end table.insert(lines, string.format("%s%s* %s[%d]; // 0x%04X", indent, getPtrTypeName(field), baseName, count, field.fieldOffset)) i = j elseif field.name:match("%[0%]$") and not field.name:find("%.") then local baseName = field.name:match("^([^%[]+)") local j = i + 1 local count = 1 while j <= #fields do local bn, ci = fields[j].name:match("^([^%[%.]+)%[(%d+)%]$") if bn == baseName and tonumber(ci) == count then count = count + 1 j = j + 1 else break end end table.insert(lines, string.format("%s%s %s[%d]; // 0x%04X", indent, cType, baseName, count, field.fieldOffset)) i = j elseif field.type == CE.vtString and field.string_size then table.insert(lines, string.format("%schar %s[%d]; // 0x%04X", indent, sanitizeName(field.name), field.string_size, field.fieldOffset)) i = i + 1 elseif field.isPointer then table.insert(lines, string.format("%s%s* %s; // 0x%04X", indent, getPtrTypeName(field), sanitizeName(field.name), field.fieldOffset)) i = i + 1 else table.insert(lines, string.format("%s%s %s; // 0x%04X", indent, cType, sanitizeName(field.name), field.fieldOffset)) i = i + 1 end end table.insert(lines, string.format("}; // sizeof: 0x%X (%d bytes)", self:totalSize(), self:totalSize())) return table.concat(lines, "\n") end function StructDef:getDependencies() local deps = {} local seen = {} local function collect(struct) if seen[struct.name] then return end seen[struct.name] = true if struct.parent then collect(struct.parent) end for _, emb in ipairs(struct.embeddings) do collect(emb.struct) end table.insert(deps, struct) end collect(self) return deps end function StructDef:toCStructWithDeps(options) local deps = self:getDependencies() local parts = {} for _, struct in ipairs(deps) do table.insert(parts, struct:toCStruct(options)) end return table.concat(parts, "\n\n") end function StructDef:getAllEmbeddings() local result = {} if self.parent then for _, e in ipairs(self.parent:getAllEmbeddings()) do table.insert(result, e) end end for _, e in ipairs(self.embeddings) do table.insert(result, e) end return result end function StructDef:totalSize() return self._offset end function StructDef:getOwnFields() return self.ownFields end function StructDef:getAllFields() local fields = {} if self.parent then for _, f in ipairs(self.parent:getAllFields()) do table.insert(fields, f) end end for _, f in ipairs(self.ownFields) do local newField = {} for k, v in pairs(f) do newField[k] = v end newField.structName = self.name table.insert(fields, newField) end return fields end -- @param struct -- @param baseAddress -- @param options function loadStructToTable(struct, baseAddress, options) options = options or {} local showStructName = options.showStructName ~= false local parentRecord = options.parentRecord local fields = struct:getAllFields() for _, field in ipairs(fields) do local memrec = AddressList.createMemoryRecord() if type(baseAddress) == "string" then memrec.Address = string.format("%s+0x%X", baseAddress, field.fieldOffset) else memrec.Address = string.format("0x%X", field.fieldOffset + baseAddress) end if showStructName and field.structName then memrec.Description = string.format("0x%03X %s::%s", field.fieldOffset, field.structName, field.name) else memrec.Description = string.format("0x%03X %s", field.fieldOffset, field.name) end memrec.Type = field.type if field.string_size then memrec.String.Size = field.string_size end if field.color then memrec.Color = field.color end if field.hex then memrec.ShowAsHex = true end if parentRecord then memrec.appendToEntry(parentRecord) end end end function Struct(name, parent) return StructDef.new(name, parent) end function GetCGObjectAddr(guidValue) if type(guidValue) == "string" then guidValue = tonumber(guidValue, 16) end if not guidValue or guidValue == 0 then return nil end local CGUNIT_C_VTABLE = 0xa34d90 local CGPLAYER_C_VTABLE = 0xa326c8 local memScan = createMemScan() local foundList = createFoundList(memScan) memScan.firstScan( soExactValue, vtQword, rtRounded, string.format("%016X", guidValue), nil, 0x0, 0x7FFFFFFFFFFFFFFF, "", fsmNotAligned, nil, true, false, false, false ) memScan.waitTillDone() foundList.initialize() local resultAddr, resultType = nil, nil for i = 0, foundList.Count - 1 do local addrGUIDvalue = tonumber(foundList.Address[i], 16) if addrGUIDvalue then local base = addrGUIDvalue - 0x30 local vtbl = readInteger(base) if vtbl == CGPLAYER_C_VTABLE then resultAddr, resultType = base, "player" break elseif vtbl == CGUNIT_C_VTABLE then resultAddr, resultType = base, "unit" break end end end foundList.destroy() memScan.destroy() if resultAddr then return resultAddr, resultType end return nil end