/ cheatengine / StructDef.lua
StructDef.lua
1 local CE = { 2 vtByte = 0, 3 vtWord = 1, 4 vtDword = 2, 5 vtQword = 3, 6 vtSingle = 4, 7 vtDouble = 5, 8 vtString = 6, 9 vtGrouped = 14, 10 } 11 12 local Types = { 13 -- Unsigned integers 14 uint8 = { ce = CE.vtByte, size = 1 }, 15 uint16 = { ce = CE.vtWord, size = 2 }, 16 uint32 = { ce = CE.vtDword, size = 4 }, 17 uint64 = { ce = CE.vtQword, size = 8 }, 18 19 -- Signed integers 20 int8 = { ce = CE.vtByte, size = 1 }, 21 int16 = { ce = CE.vtWord, size = 2 }, 22 int32 = { ce = CE.vtDword, size = 4 }, 23 int64 = { ce = CE.vtQword, size = 8 }, 24 25 -- Floating point 26 float = { ce = CE.vtSingle, size = 4 }, 27 double = { ce = CE.vtDouble, size = 8 }, 28 29 -- Pointers 30 ptr = { ce = CE.vtDword, size = 4 }, 31 ptr32 = { ce = CE.vtDword, size = 4 }, 32 ptr64 = { ce = CE.vtQword, size = 8 }, 33 34 -- Aliases 35 bool = { ce = CE.vtByte, size = 1 }, 36 bool32 = { ce = CE.vtDword, size = 4 }, 37 char = { ce = CE.vtByte, size = 1 }, 38 byte = { ce = CE.vtByte, size = 1 }, 39 word = { ce = CE.vtWord, size = 2 }, 40 dword = { ce = CE.vtDword, size = 4 }, 41 qword = { ce = CE.vtQword, size = 8 }, 42 } 43 44 function SetPointerSize(bits) 45 if bits == 64 then 46 Types.ptr = { ce = CE.vtQword, size = 8 } 47 else 48 Types.ptr = { ce = CE.vtDword, size = 4 } 49 end 50 end 51 52 local StructDef = {} 53 StructDef.__index = StructDef 54 55 -- @param name 56 -- @param parent 57 function StructDef.new(name, parent) 58 local self = setmetatable({}, StructDef) 59 self.name = name 60 self.parent = parent 61 self.ownFields = {} 62 self.embeddings = {} 63 64 if parent then 65 self._offset = parent:totalSize() 66 else 67 self._offset = 0 68 end 69 70 return self 71 end 72 73 -- @param name 74 -- @param typeName (uint32, float, ptr etc) 75 -- @param opts {hex=bool, color=number} 76 function StructDef:field(name, typeName, opts) 77 opts = opts or {} 78 79 local typeInfo = Types[typeName] 80 if not typeInfo then 81 error(string.format("Unknown type '%s' for field '%s'", typeName, name)) 82 end 83 84 table.insert(self.ownFields, { 85 fieldOffset = self._offset, 86 name = name, 87 type = typeInfo.ce, 88 size = typeInfo.size, 89 color = opts.color, 90 hex = opts.hex, 91 }) 92 93 self._offset = self._offset + typeInfo.size 94 return self 95 end 96 97 StructDef.f = StructDef.field 98 99 --- Add hex-field 100 function StructDef:hex(name, typeName, opts) 101 opts = opts or {} 102 opts.hex = true 103 return self:field(name, typeName, opts) 104 end 105 106 --- Add string 107 function StructDef:string(name, size, opts) 108 opts = opts or {} 109 110 table.insert(self.ownFields, { 111 fieldOffset = self._offset, 112 name = name, 113 type = CE.vtString, 114 string_size = size, 115 size = size, 116 color = opts.color, 117 }) 118 119 self._offset = self._offset + size 120 return self 121 end 122 123 --- Add array 124 function StructDef:array(name, typeName, count, opts) 125 opts = opts or {} 126 127 local typeInfo = Types[typeName] 128 if not typeInfo then 129 error(string.format("Unknown type '%s' for array '%s'", typeName, name)) 130 end 131 132 for i = 0, count - 1 do 133 table.insert(self.ownFields, { 134 fieldOffset = self._offset + i * typeInfo.size, 135 name = string.format("%s[%d]", name, i), 136 type = typeInfo.ce, 137 size = typeInfo.size, 138 color = opts.color, 139 hex = opts.hex, 140 }) 141 end 142 143 self._offset = self._offset + count * typeInfo.size 144 return self 145 end 146 147 --- skip to X bytes 148 function StructDef:paddingTo(targetOffset, opts) 149 local size = targetOffset - self._offset 150 if size <= 0 then 151 return self 152 end 153 154 opts = opts or {} 155 local remaining = size 156 157 while remaining >= 4 do 158 local name = string.format("unk_%04X", self._offset) 159 self:field(name, "uint32", opts) 160 remaining = remaining - 4 161 end 162 163 while remaining >= 1 do 164 local name = string.format("unk_%04X", self._offset) 165 self:field(name, "uint8", opts) 166 remaining = remaining - 1 167 end 168 169 return self 170 end 171 172 --- skip N bytes 173 function StructDef:padding(size, opts) 174 opts = opts or {} 175 local remaining = size 176 177 while remaining >= 4 do 178 local name = string.format("unk_%04X", self._offset) 179 self:field(name, "uint32", opts) 180 remaining = remaining - 4 181 end 182 183 while remaining >= 1 do 184 local name = string.format("unk_%04X", self._offset) 185 self:field(name, "uint8", opts) 186 remaining = remaining - 1 187 end 188 189 return self 190 end 191 192 function StructDef:skip(size) 193 self._offset = self._offset + size 194 return self 195 end 196 197 function StructDef:unk(size, opts) 198 opts = opts or {} 199 local name = string.format("unk_%04X", self._offset) 200 201 if size == 1 then 202 return self:field(name, "uint8", opts) 203 elseif size == 2 then 204 return self:field(name, "uint16", opts) 205 elseif size == 4 then 206 return self:field(name, "uint32", opts) 207 elseif size == 8 then 208 return self:field(name, "uint64", opts) 209 else 210 return self:array(name, "uint8", size, opts) 211 end 212 end 213 214 function StructDef:alignTo(alignment) 215 local rem = self._offset % alignment 216 if rem ~= 0 then 217 self._offset = self._offset + (alignment - rem) 218 end 219 return self 220 end 221 222 function StructDef:at(offset) 223 self._offset = offset 224 return self 225 end 226 227 function StructDef:currentOffset() 228 return self._offset 229 end 230 231 -- @param name 232 -- @param otherStruct 233 -- @param opts {expand=bool} 234 function StructDef:embed(name, otherStruct, opts) 235 opts = opts or {} 236 local baseOffset = self._offset 237 238 table.insert(self.embeddings, { 239 type = "embed", 240 name = name, 241 struct = otherStruct, 242 offset = baseOffset, 243 size = otherStruct:totalSize(), 244 expand = opts.expand or false, 245 }) 246 247 for _, f in ipairs(otherStruct:getAllFields()) do 248 local newField = {} 249 for k, v in pairs(f) do 250 newField[k] = v 251 end 252 newField.fieldOffset = baseOffset + f.fieldOffset 253 newField.name = name .. "." .. f.name 254 newField.structName = nil 255 newField._embeddedIn = name 256 table.insert(self.ownFields, newField) 257 end 258 259 self._offset = self._offset + otherStruct:totalSize() 260 return self 261 end 262 263 -- @param name 264 -- @param otherStruct 265 -- @param count 266 -- @param opts {expand=bool} 267 function StructDef:structArray(name, otherStruct, count, opts) 268 opts = opts or {} 269 local structSize = otherStruct:totalSize() 270 local baseOffset = self._offset 271 272 table.insert(self.embeddings, { 273 type = "structArray", 274 name = name, 275 struct = otherStruct, 276 offset = baseOffset, 277 count = count, 278 size = count * structSize, 279 elemSize = structSize, 280 expand = opts.expand or false, 281 }) 282 283 for i = 0, count - 1 do 284 local elemOffset = self._offset + i * structSize 285 286 for _, f in ipairs(otherStruct:getAllFields()) do 287 local newField = {} 288 for k, v in pairs(f) do 289 newField[k] = v 290 end 291 newField.fieldOffset = elemOffset + f.fieldOffset 292 newField.name = string.format("%s[%d].%s", name, i, f.name) 293 newField._embeddedIn = name 294 newField._arrayIndex = i 295 table.insert(self.ownFields, newField) 296 end 297 end 298 299 self._offset = self._offset + count * structSize 300 return self 301 end 302 303 -- :ptrArray("fieldName", count) -- void*[] 304 -- :ptrArray("fieldName", count, opts) -- void*[] with opts 305 -- :ptrArray("TypeName", "fieldName", count) -- TypeName*[] 306 -- :ptrArray("TypeName", "fieldName", count, opts) -- TypeName*[] with opts 307 function StructDef:ptrArray(arg1, arg2, arg3, arg4) 308 local typeName, fieldName, count, opts 309 310 if type(arg2) == "number" then 311 -- ptrArray("fieldName", count) or ptrArray("fieldName", count, opts) 312 typeName = nil 313 fieldName = arg1 314 count = arg2 315 opts = arg3 or {} 316 elseif type(arg2) == "string" then 317 -- ptrArray("TypeName", "fieldName", count) or ptrArray("TypeName", "fieldName", count, opts) 318 typeName = arg1 319 fieldName = arg2 320 count = arg3 321 opts = arg4 or {} 322 else 323 error("Invalid arguments for ptrArray()") 324 end 325 326 local typeInfo = Types.ptr 327 328 for i = 0, count - 1 do 329 table.insert(self.ownFields, { 330 fieldOffset = self._offset + i * typeInfo.size, 331 name = string.format("%s[%d]", fieldName, i), 332 type = typeInfo.ce, 333 size = typeInfo.size, 334 color = opts.color, 335 hex = true, 336 isPointer = true, 337 ptrType = typeName, 338 _ptrArrayBase = fieldName, 339 }) 340 end 341 342 self._offset = self._offset + count * typeInfo.size 343 return self 344 end 345 346 function StructDef:ptr(arg1, arg2, arg3) 347 local typeName, fieldName, opts 348 349 if arg2 == nil then 350 -- ptr("fieldName") 351 typeName = nil 352 fieldName = arg1 353 opts = {} 354 elseif type(arg2) == "table" then 355 -- ptr("fieldName", opts) 356 typeName = nil 357 fieldName = arg1 358 opts = arg2 359 elseif type(arg2) == "string" then 360 -- ptr("TypeName", "fieldName") or ptr("TypeName", "fieldName", opts) 361 typeName = arg1 362 fieldName = arg2 363 opts = arg3 or {} 364 else 365 error("Invalid arguments for ptr()") 366 end 367 368 local typeInfo = Types.ptr 369 370 table.insert(self.ownFields, { 371 fieldOffset = self._offset, 372 name = fieldName, 373 type = typeInfo.ce, 374 size = typeInfo.size, 375 color = opts.color, 376 hex = true, 377 isPointer = true, 378 ptrType = typeName, 379 }) 380 381 self._offset = self._offset + typeInfo.size 382 return self 383 end 384 385 function StructDef:toCStruct(options) 386 options = options or {} 387 local indent = options.indent or " " 388 local collapsePadding = options.collapsePadding ~= false 389 local flattenInheritance = options.flattenInheritance or false 390 local lines = {} 391 392 local ceToC = { 393 [CE.vtByte] = "uint8_t", 394 [CE.vtWord] = "uint16_t", 395 [CE.vtDword] = "uint32_t", 396 [CE.vtQword] = "uint64_t", 397 [CE.vtSingle] = "float", 398 [CE.vtDouble] = "double", 399 [CE.vtString] = "char", 400 } 401 402 local function getPtrTypeName(field) 403 return field.ptrType or "void" 404 end 405 406 local function sanitizeName(name) 407 return name:gsub("%.", "_"):gsub("%[", "_"):gsub("%]", "") 408 end 409 410 local function isUnkField(name) 411 return name:match("^unk_[%dA-Fa-f]+$") ~= nil 412 end 413 414 local function isUnkArray(name) 415 return name:match("^unk_[%dA-Fa-f]+%[%d+%]$") ~= nil 416 end 417 418 local function isAnyUnk(name) 419 return isUnkField(name) or isUnkArray(name) 420 end 421 422 local fields 423 local embeddingMap = {} 424 425 if flattenInheritance then 426 fields = self:getAllFields() 427 for _, emb in ipairs(self:getAllEmbeddings()) do 428 embeddingMap[emb.offset] = emb 429 end 430 else 431 fields = self.ownFields 432 for _, emb in ipairs(self.embeddings) do 433 embeddingMap[emb.offset] = emb 434 end 435 end 436 437 if flattenInheritance and self.parent then 438 table.insert(lines, string.format("// Inherits from: %s (size: 0x%X)", self.parent.name, self.parent:totalSize())) 439 end 440 441 table.insert(lines, string.format("struct %s {", self.name)) 442 443 if not flattenInheritance and self.parent then 444 table.insert(lines, string.format("%s%s base; // 0x%04X", indent, self.parent.name, 0)) 445 end 446 447 local i = 1 448 local processedEmbeddings = {} 449 450 while i <= #fields do 451 local field = fields[i] 452 local cType = ceToC[field.type] or "uint8_t" 453 454 local emb = embeddingMap[field.fieldOffset] 455 456 if emb and not processedEmbeddings[emb.name] then 457 processedEmbeddings[emb.name] = true 458 459 if emb.type == "embed" then 460 if emb.expand then 461 table.insert(lines, string.format("%sstruct { // %s::%s", indent, emb.struct.name, emb.name)) 462 463 for _, sf in ipairs(emb.struct:getAllFields()) do 464 local subType = ceToC[sf.type] or "uint8_t" 465 if sf.type == CE.vtString and sf.string_size then 466 table.insert(lines, string.format("%s%schar %s[%d]; // +0x%02X", indent, indent, sanitizeName(sf.name), sf.string_size, sf.fieldOffset)) 467 elseif sf.isPointer then 468 table.insert(lines, string.format("%s%s%s* %s; // +0x%02X", indent, indent, getPtrTypeName(sf), sanitizeName(sf.name), sf.fieldOffset)) 469 else 470 table.insert(lines, string.format("%s%s%s %s; // +0x%02X", indent, indent, subType, sanitizeName(sf.name), sf.fieldOffset)) 471 end 472 end 473 474 table.insert(lines, string.format("%s} %s; // 0x%04X", 475 indent, emb.name, emb.offset)) 476 else 477 table.insert(lines, string.format("%s%s %s; // 0x%04X", 478 indent, emb.struct.name, emb.name, emb.offset)) 479 end 480 481 while i <= #fields and fields[i]._embeddedIn == emb.name do 482 i = i + 1 483 end 484 485 elseif emb.type == "structArray" then 486 if emb.expand then 487 table.insert(lines, string.format("%sstruct { // %s element", indent, emb.struct.name)) 488 489 for _, sf in ipairs(emb.struct:getAllFields()) do 490 local subType = ceToC[sf.type] or "uint8_t" 491 if sf.isPointer then 492 table.insert(lines, string.format("%s%s%s* %s; // +0x%02X", indent, indent, getPtrTypeName(sf), sanitizeName(sf.name), sf.fieldOffset)) 493 else 494 table.insert(lines, string.format("%s%s%s %s; // +0x%02X", indent, indent, subType, sanitizeName(sf.name), sf.fieldOffset)) 495 end 496 end 497 498 table.insert(lines, string.format("%s} %s[%d]; // 0x%04X (0x%X bytes)", indent, emb.name, emb.count, emb.offset, emb.size)) 499 else 500 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)) 501 end 502 503 while i <= #fields and fields[i]._embeddedIn == emb.name do 504 i = i + 1 505 end 506 end 507 508 elseif collapsePadding and isAnyUnk(field.name) then 509 local startOffset = field.fieldOffset 510 local totalSize = 0 511 local j = i 512 513 while j <= #fields do 514 local f = fields[j] 515 local expectedOffset = startOffset + totalSize 516 517 if isAnyUnk(f.name) and f.fieldOffset == expectedOffset then 518 totalSize = totalSize + f.size 519 j = j + 1 520 else 521 break 522 end 523 end 524 525 if totalSize > 0 then 526 table.insert(lines, string.format("%suint8_t pad_%04X[0x%X]; // 0x%04X", indent, startOffset, totalSize, startOffset)) 527 end 528 529 i = j 530 531 elseif field.isPointer and field._ptrArrayBase and field.name:match("%[0%]$") then 532 local baseName = field._ptrArrayBase 533 local j = i + 1 534 local count = 1 535 536 while j <= #fields do 537 if fields[j]._ptrArrayBase == baseName then 538 count = count + 1 539 j = j + 1 540 else 541 break 542 end 543 end 544 545 table.insert(lines, string.format("%s%s* %s[%d]; // 0x%04X", indent, getPtrTypeName(field), baseName, count, field.fieldOffset)) 546 i = j 547 548 elseif field.name:match("%[0%]$") and not field.name:find("%.") then 549 local baseName = field.name:match("^([^%[]+)") 550 local j = i + 1 551 local count = 1 552 553 while j <= #fields do 554 local bn, ci = fields[j].name:match("^([^%[%.]+)%[(%d+)%]$") 555 if bn == baseName and tonumber(ci) == count then 556 count = count + 1 557 j = j + 1 558 else 559 break 560 end 561 end 562 563 table.insert(lines, string.format("%s%s %s[%d]; // 0x%04X", indent, cType, baseName, count, field.fieldOffset)) 564 i = j 565 566 elseif field.type == CE.vtString and field.string_size then 567 table.insert(lines, string.format("%schar %s[%d]; // 0x%04X", indent, sanitizeName(field.name), field.string_size, field.fieldOffset)) 568 i = i + 1 569 570 elseif field.isPointer then 571 table.insert(lines, string.format("%s%s* %s; // 0x%04X", indent, getPtrTypeName(field), sanitizeName(field.name), field.fieldOffset)) 572 i = i + 1 573 574 else 575 table.insert(lines, string.format("%s%s %s; // 0x%04X", indent, cType, sanitizeName(field.name), field.fieldOffset)) 576 i = i + 1 577 end 578 end 579 580 table.insert(lines, string.format("}; // sizeof: 0x%X (%d bytes)", self:totalSize(), self:totalSize())) 581 582 return table.concat(lines, "\n") 583 end 584 585 function StructDef:getDependencies() 586 local deps = {} 587 local seen = {} 588 589 local function collect(struct) 590 if seen[struct.name] then return end 591 seen[struct.name] = true 592 593 if struct.parent then 594 collect(struct.parent) 595 end 596 597 for _, emb in ipairs(struct.embeddings) do 598 collect(emb.struct) 599 end 600 601 table.insert(deps, struct) 602 end 603 604 collect(self) 605 return deps 606 end 607 608 function StructDef:toCStructWithDeps(options) 609 local deps = self:getDependencies() 610 local parts = {} 611 612 for _, struct in ipairs(deps) do 613 table.insert(parts, struct:toCStruct(options)) 614 end 615 616 return table.concat(parts, "\n\n") 617 end 618 619 function StructDef:getAllEmbeddings() 620 local result = {} 621 622 if self.parent then 623 for _, e in ipairs(self.parent:getAllEmbeddings()) do 624 table.insert(result, e) 625 end 626 end 627 628 for _, e in ipairs(self.embeddings) do 629 table.insert(result, e) 630 end 631 632 return result 633 end 634 635 function StructDef:totalSize() 636 return self._offset 637 end 638 639 function StructDef:getOwnFields() 640 return self.ownFields 641 end 642 643 function StructDef:getAllFields() 644 local fields = {} 645 646 if self.parent then 647 for _, f in ipairs(self.parent:getAllFields()) do 648 table.insert(fields, f) 649 end 650 end 651 652 for _, f in ipairs(self.ownFields) do 653 local newField = {} 654 for k, v in pairs(f) do 655 newField[k] = v 656 end 657 newField.structName = self.name 658 table.insert(fields, newField) 659 end 660 661 return fields 662 end 663 664 -- @param struct 665 -- @param baseAddress 666 -- @param options 667 function loadStructToTable(struct, baseAddress, options) 668 options = options or {} 669 local showStructName = options.showStructName ~= false 670 local parentRecord = options.parentRecord 671 672 local fields = struct:getAllFields() 673 674 for _, field in ipairs(fields) do 675 local memrec = AddressList.createMemoryRecord() 676 677 if type(baseAddress) == "string" then 678 memrec.Address = string.format("%s+0x%X", baseAddress, field.fieldOffset) 679 else 680 memrec.Address = string.format("0x%X", field.fieldOffset + baseAddress) 681 end 682 683 if showStructName and field.structName then 684 memrec.Description = string.format("0x%03X %s::%s", field.fieldOffset, field.structName, field.name) 685 else 686 memrec.Description = string.format("0x%03X %s", field.fieldOffset, field.name) 687 end 688 689 memrec.Type = field.type 690 691 if field.string_size then 692 memrec.String.Size = field.string_size 693 end 694 if field.color then 695 memrec.Color = field.color 696 end 697 if field.hex then 698 memrec.ShowAsHex = true 699 end 700 701 if parentRecord then 702 memrec.appendToEntry(parentRecord) 703 end 704 end 705 end 706 707 function Struct(name, parent) 708 return StructDef.new(name, parent) 709 end 710 711 function GetCGObjectAddr(guidValue) 712 if type(guidValue) == "string" then 713 guidValue = tonumber(guidValue, 16) 714 end 715 if not guidValue or guidValue == 0 then 716 return nil 717 end 718 719 local VTABLE_TYPES = { 720 [0xA34D90] = "unit", 721 [0xA326C8] = "player", 722 [0xA33428] = "item", 723 [0xA332D0] = "container", 724 [0xA331C8] = "corpse", 725 } 726 727 local memScan = createMemScan() 728 local foundList = createFoundList(memScan) 729 730 local function cleanup() 731 foundList.destroy() 732 memScan.destroy() 733 end 734 735 memScan.firstScan( 736 soExactValue, vtQword, rtRounded, 737 string.format("%016X", guidValue), 738 nil, 0x0, 0x7FFFFFFFFFFFFFFF, 739 "", fsmNotAligned, nil, 740 true, false, false, false 741 ) 742 743 memScan.waitTillDone() 744 foundList.initialize() 745 746 for i = 0, foundList.Count - 1 do 747 local addr = tonumber(foundList.Address[i], 16) 748 if addr then 749 local base = addr - 0x30 750 local objType = VTABLE_TYPES[readInteger(base)] 751 if objType then 752 cleanup() 753 return base, objType 754 end 755 end 756 end 757 758 cleanup() 759 return nil 760 end