From bc01c9b814a9cbe3f042c7e39cade7dbcbefad46 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Fri, 3 May 2024 00:46:21 -0400 Subject: [PATCH 1/4] Simplify Code --- src/common.ts | 83 +------- src/loader.ts | 22 +-- src/property.ts | 174 ++++------------- src/struct.ts | 199 ++++++-------------- src/vtable.ts | 50 ++--- syntax-highlighting/def.nanorc | 2 +- syntax-highlighting/ksyntaxhighlighting.xml | 1 - 7 files changed, 127 insertions(+), 404 deletions(-) diff --git a/src/common.ts b/src/common.ts index f513f82..b228b95 100644 --- a/src/common.ts +++ b/src/common.ts @@ -33,7 +33,6 @@ export function parseTypeAndName(piece: string) { // Return return {type, name}; } -export const MIN_SIZE = 1; export function toUpperSnakeCase(str: string) { let wasUpper = false; let nextIsUpper = false; @@ -66,20 +65,13 @@ export const COMMENT = '//'; export function toHex(x: number) { return '0x' + x.toString(16); } -export function getAssertFunction() { - return 'static_assert'; -} -export function assertSize(name: string, size: number, isDefined: boolean) { +export function assertSize(name: string, size: number) { let out = ''; // Define Size Macro const macro = toUpperSnakeCase(name) + '_SIZE'; out += `#define ${macro} ${toHex(size)}\n`; // Check Size - out += `${getAssertFunction()}(sizeof(${name}) == ${macro}, "Invalid Size");\n`; - // Hide Structure Size If The Real Size Is Unknown - if (!isDefined) { - out += `#undef ${macro}\n`; - } + out += `static_assert(sizeof(${name}) == ${macro}, "Invalid Size");\n`; // Return return out; } @@ -90,80 +82,9 @@ export function safeParseInt(str: string) { } return x; } -export function stripArrayData(propertyName: string) { - const index = propertyName.indexOf('['); - if (index !== -1) { - propertyName = propertyName.substring(0, index); - } - return propertyName; -} -export function getSizeMultiplierFromArrayData(propertyName: string) { - let multiplier = 1; - // Check If Array Data Is Present - const index = propertyName.indexOf('['); - if (index === -1) { - return multiplier; - } - propertyName = propertyName.substring(index + 1); - // Strip Last ] - if (!propertyName.endsWith(']')) { - syntaxError('Expecting ]'); - } else { - propertyName = propertyName.substring(0, propertyName.length - 1); - } - // Split - const parts = propertyName.split(']['); - // Calculate Multiplier - for (const part of parts) { - multiplier *= safeParseInt(part); - } - // Return - return multiplier; -} export function getSelfArg(type: string) { return `${type} *self`; } -export class Size { - #size: number | null; - #isExact: boolean; - readonly #pointerAligned: boolean; - - constructor(pointerAligned: boolean) { - this.#size = null; - this.#isExact = false; - this.#pointerAligned = pointerAligned; - } - - has() { - return this.#size !== null; - } - get() { - if (this.#size === null) { - throw new Error('No Size Specified'); - } - return this.#size; - } - set(size: number, isExact: boolean) { - if (size < MIN_SIZE) { - throw new Error(`Size Too Small: ${toHex(size)}`); - } - if (this.#isExact) { - throw new Error('Cannot Change Size After Being Set By Base Structure'); - } - if (this.#size !== null && size < this.#size) { - throw new Error('Cannot Decrease VTable Size'); - } - this.#size = size; - this.#isExact = isExact; - if (this.#pointerAligned && (this.#size % POINTER_SIZE) !== 0) { - throw new Error(`Invalid Size: ${toHex(this.#size)}`); - } - } - // Whether This Is An Exact Limit Or Just A Lower Bound - isExact() { - return this.#isExact; - } -} export function getArgNames(args: string) { // Remove Parentheses args = args.substring(1, args.length - 1); diff --git a/src/loader.ts b/src/loader.ts index 710ef96..497a395 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -1,6 +1,6 @@ import { COMMENT, EXTENSION, getSelfArg, parseTypeAndName, prependArg, readDefinition, safeParseInt, syntaxError } from './common'; import { Method } from './method'; -import { SimpleProperty, StaticProperty } from './property'; +import { Property, StaticProperty } from './property'; import { Struct } from './struct'; // Error Handling @@ -108,12 +108,16 @@ export function load(target: Struct, name: string, isExtended: boolean) { } case 'size': { // Set Size - target.setSize(safeParseInt(args), !isExtended); + if (!isExtended) { + target.setSize(safeParseInt(args)); + } break; } case 'vtable-size': { // Set VTable Size - target.setVTableSize(safeParseInt(args), !isExtended); + if (!isExtended) { + target.setVTableSize(safeParseInt(args)); + } break; } case 'vtable': { @@ -127,22 +131,14 @@ export function load(target: Struct, name: string, isExtended: boolean) { case 'property': { // Add Property const info = parseProperty(args); - target.addProperty(new SimpleProperty(info.offset, info.type, info.name)); + target.addProperty(new Property(info.offset, info.type, info.name, target.getName())); break; } case 'static-property': { // Add Static Property if (!isExtended) { const info = parseProperty(args); - target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName(), false)); - } - break; - } - case 'static-property-array': { - // Add Static Array Property - if (!isExtended) { - const info = parseProperty(args); - target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName(), true)); + target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName())); } break; } diff --git a/src/property.ts b/src/property.ts index 01b75ab..2798495 100644 --- a/src/property.ts +++ b/src/property.ts @@ -1,156 +1,60 @@ -import { POINTER_SIZE, getSizeMultiplierFromArrayData } from './common'; -import { getStructure } from './map'; +import { formatType } from './common'; -export interface Property { - propertyOffset(): number; - propertySize(): number; - propertyType(): string; - propertyName(): string; - propertyAlignment(): number; -} - -export class SimpleProperty implements Property { - readonly #offset: number; - readonly #type: string; - readonly #name: string; - readonly #arraySizeMultiplier: number; - - // Constructor - constructor(offset: number, type: string, name: string) { - this.#offset = offset; - this.#type = type; - this.#name = name; - this.#arraySizeMultiplier = getSizeMultiplierFromArrayData(this.#name); - } - - // Getters - propertyOffset() { - return this.#offset; - } - propertyType() { - return this.#type; - } - propertyName() { - return this.#name; - } - - // Size - #rawPropertySize() { - if (this.#type.endsWith('*')) { - // Pointer - return POINTER_SIZE; - } else if (this.#type === 'int' || this.#type === 'uint') { - // Integer - return 4; - } else if (this.#type === 'float') { - // Float - return 4; - } else if (this.#type === 'bool') { - // Boolean - return 1; - } else if (this.#type === 'std::string') { - // C++ String - return 4; - } else if (this.#type === 'char' || this.#type === 'uchar') { - // Character - return 1; - } else if (this.#type === 'short' || this.#type === 'ushort') { - // Short - return 2; - } else if (this.#type.startsWith('std::vector<')) { - // C++ Vector - return 12; - } else if (this.#type.startsWith('std::map<')) { - // C++ Map - return 24; - } else { - // Structure - const structure = getStructure(this.#type); - return structure.getSize(true); - } - } - propertySize() { - return this.#arraySizeMultiplier * this.#rawPropertySize(); - } - - // Alignment - propertyAlignment() { - if (this.#type.endsWith('*')) { - // Pointer - return POINTER_SIZE; - } else if (this.#type === 'int' || this.#type === 'uint') { - // Integer - return 4; - } else if (this.#type === 'float') { - // Float - return 4; - } else if (this.#type === 'bool') { - // Boolean - return 1; - } else if (this.#type === 'std::string') { - // C++ String - return 4; - } else if (this.#type === 'char' || this.#type === 'uchar') { - // Character - return 1; - } else if (this.#type === 'short' || this.#type === 'ushort') { - // Short - return 2; - } else if (this.#type.startsWith('std::vector<')) { - // C++ Vector - return 4; - } else if (this.#type.startsWith('std::map<')) { - // C++ Map - return 4; - } else { - // Structure - const structure = getStructure(this.#type); - return structure.getAlignment(); - } - } -} - -export class StaticProperty { - readonly address: number; +export class Property { + readonly offset: number; readonly #type: string; readonly #name: string; readonly #self: string; - readonly #isArray: boolean; // Constructor - constructor(address: number, type: string, name: string, self: string, isArray: boolean) { - if (name.includes('[')) { - throw new Error('Use "static-property-array" For Arrays'); - } - this.address = address; + constructor(offset: number, type: string, name: string, self: string) { + this.offset = offset; this.#type = type; this.#name = name; this.#self = self; - this.#isArray = isArray; } - // Name And Type - getName() { - return `${this.#self}_${this.#name}`; + // Getters + type() { + return `__type_${this.fullName()}`; } - getPointerType() { - let type = this.#type; - // Convert Type To Pointer - if (!type.endsWith('*')) { - type += ' '; + typedef() { + let arrayInfo = ''; + const arrayInfoIndex = this.#name.indexOf('['); + if (arrayInfoIndex !== -1) { + arrayInfo = this.#name.substring(arrayInfoIndex); } - type += '*'; - // Return - return type; + return `typedef ${formatType(this.#type)}${this.type()}${arrayInfo};\n`; + } + name() { + let name = this.#name; + const arrayInfoIndex = this.#name.indexOf('['); + if (arrayInfoIndex !== -1) { + name = name.substring(0, arrayInfoIndex); + } + return name; + } + fullName() { + return `${this.#self}_${this.name()}`; + } + rawType() { + return this.#type; + } +} + +export class StaticProperty extends Property { + // Constructor + constructor(address: number, type: string, name: string, self: string) { + super(address, type, name, self); } // Generate Variable Definition - generateDefinition() { - return `${this.getPointerType()}${this.getName()}_pointer;\n`; + globalPointerDefinition() { + return `${this.type()} *${this.fullName()}_pointer;\n`; } // Generate Macro - generateMacro() { - return `#define ${this.getName()} (${this.#isArray ? '' : '*'}${this.getName()}_pointer)\n`; + macro() { + return `#define ${this.fullName()} (*${this.fullName()}_pointer)\n`; } } \ No newline at end of file diff --git a/src/struct.ts b/src/struct.ts index 6589297..bd5d821 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -1,4 +1,4 @@ -import { INDENT, MIN_SIZE, formatType, STRUCTURE_FILES, toHex, getAssertFunction, assertSize, stripArrayData, Size } from './common'; +import { INDENT, STRUCTURE_FILES, toHex, assertSize } from './common'; import { Method } from './method'; import { Property, StaticProperty } from './property'; import { VTable } from './vtable'; @@ -8,7 +8,7 @@ export class Struct { #vtable: VTable | null; readonly #methods: Method[]; readonly #properties: Property[]; - readonly #size: Size; + #size: number | null; readonly #dependencies: string[]; readonly #staticProperties: StaticProperty[]; #directParent: string | null; @@ -20,7 +20,7 @@ export class Struct { this.#methods = []; this.#properties = []; this.#vtable = null; - this.#size = new Size(false); + this.#size = null; this.#dependencies = []; this.#staticProperties = []; this.#directParent = null; @@ -39,7 +39,7 @@ export class Struct { ensureVTable() { if (this.#vtable === null) { this.#vtable = new VTable(this.#name, this.#vtableDestructorOffset); - this.addProperty(this.#vtable); + this.addProperty(this.#vtable.property); } } @@ -52,58 +52,13 @@ export class Struct { } // Setters - setSize(size: number, isExact: boolean) { - this.#size.set(size, isExact); + setSize(size: number) { + this.#size = size; } // Getters - #roundSize(size: number) { - const alignment = this.getAlignment(); - return Math.ceil(size / alignment) * alignment; - } - #getRealSize() { - // Get Size Computed Purely From Properties - let size = MIN_SIZE; - for (const property of this.#properties) { - const newSize = property.propertyOffset() + property.propertySize(); - if (newSize > size) { - size = newSize; - } - } - return size; - } - getSize(round: boolean) { - let size; - if (this.#size.isExact()) { - // Exact Size Is Specified - size = this.#size.get(); - } else { - // Specified Size Is A Lower Bound - size = this.#getRealSize(); - if (this.#size.has()) { - const specifiedSize = this.#size.get(); - if (specifiedSize > size) { - size = specifiedSize; - } - } - } - if (round) { - size = this.#roundSize(size); - } - return size; - } getName() { return this.#name; } - getAlignment() { - let alignment = 1; - for (const property of this.#properties) { - const x = property.propertyAlignment(); - if (x > alignment) { - alignment = x; - } - } - return alignment; - } // Add Method addMethod(method: Method, isVirtual: boolean) { @@ -121,7 +76,7 @@ export class Struct { addProperty(property: Property) { this.#properties.push(property); // Add Dependency If Needed - const type = property.propertyType(); + const type = property.rawType(); if (type in STRUCTURE_FILES) { this.#addDependency(type); } @@ -132,80 +87,63 @@ export class Struct { } // Configure VTable - setVTableSize(size: number, isExact: boolean) { + setVTableSize(size: number) { this.ensureVTable(); - this.#vtable!.setSize(size, isExact); + this.#vtable!.setSize(size); } setVTableAddress(address: number) { this.ensureVTable(); this.#vtable!.setAddress(address); } - // Check - #check() { + // Generate Properties + #generateProperties() { // Sort Properties - this.#properties.sort((a, b) => a.propertyOffset() - b.propertyOffset()); + const sortedProperties = []; + sortedProperties.push(...this.#properties); + sortedProperties.sort((a, b) => a.offset - b.offset); - // Check Size - if (this.#size.isExact()) { - const size = this.getSize(true); - // Check Alignment - const specifiedSize = this.#size.get()!; - if (size !== specifiedSize) { - throw new Error('Size Misaligned'); - } - // Check If Size Is Too Small - let realSize = this.#getRealSize(); - realSize = this.#roundSize(realSize); - if (realSize > specifiedSize) { - throw new Error(`Structure Size Too Small: ${toHex(specifiedSize)}`); - } + // Fake Property To Pad Structure Size + let sizeProperty = null; + if (this.#size) { + sizeProperty = new Property(this.#size, '', '', ''); + sortedProperties.push(sizeProperty); } - } - // Compute Padding Between Properties - #computePadding(a: Property | null, b: Property | null) { - // Null A = Start Of Structure - // Null B = End Of Structure - let neededPadding = 0; - const size = this.getSize(true); - if (a === null) { - // Start Of Structure Padding - if (b !== null) { - neededPadding = b.propertyOffset(); - } else { - // Both A And B Are Null - neededPadding = size; + // Add Properties + let out = ''; + for (let i = 0; i < sortedProperties.length; i++) { + const property = sortedProperties[i]!; + const lastProperty = sortedProperties[i - 1]; + + // Padding + let offsetFromLastProperty = property.offset; + if (lastProperty) { + offsetFromLastProperty -= lastProperty.offset; } - } else if (b === null) { - // End Of Structure Padding - const realSize = this.#getRealSize(); - const realRoundedSize = this.#roundSize(realSize); - if (realRoundedSize !== size) { - neededPadding = size - realSize; + let paddingSize = toHex(offsetFromLastProperty); + if (lastProperty) { + paddingSize += ` - sizeof(${lastProperty.type()})`; + } + out += `${INDENT}uchar _padding${i}[${paddingSize}];\n`; + + // The Actual Property + if (property !== sizeProperty) { + out += `${INDENT}${property.type()} ${property.name()};\n`; } - } else { - // Inner Structure Padding - const realSizeSoFar = a.propertyOffset() + a.propertySize(); - neededPadding = b.propertyOffset() - realSizeSoFar; } - if (neededPadding < 0) { - throw new Error('Overlapping Properties Detected!'); - } - return neededPadding; + return out; } // Generate Header generate() { let out = ''; - // Check - this.#check(); - // Static Properties for (const property of this.#staticProperties) { - out += `extern ${property.generateDefinition()}`; - out += property.generateMacro(); + out += property.typedef(); + out += `extern ${property.globalPointerDefinition()}`; + out += property.macro(); } // Methods @@ -220,49 +158,31 @@ export class Struct { out += this.#vtable.generate(this.#directParent); } + // Property Typedefs + for (const property of this.#properties) { + out += property.typedef(); + } + // Structure out += `struct ${this.#name} {\n`; - for (let i = 0; i <= this.#properties.length; i++) { - const property = i < this.#properties.length ? this.#properties[i]! : null; - - // Padding - const lastProperty = (i >= 1 && (i - 1) < this.#properties.length) ? this.#properties[i - 1]! : null; - const neededPadding = this.#computePadding(lastProperty, property); - if (neededPadding > 0) { - out += `${INDENT}uchar padding${i}[${neededPadding}];\n`; - } - - // Property - if (property !== null) { - // Check Offset - const offset = property.propertyOffset(); - const alignment = property.propertyAlignment(); - if ((offset % alignment) !== 0) { - throw new Error('Misaligned Property Offset'); - } - // Add - const type = formatType(property.propertyType()); - out += `${INDENT}${type}${property.propertyName()};\n`; - } - } + out += this.#generateProperties(); out += `};\n`; // Sanity Check Offsets - const assertFunction = getAssertFunction(); for (let i = 0; i < this.#properties.length; i++) { const property = this.#properties[i]!; - const name = stripArrayData(property.propertyName()); - const offset = property.propertyOffset(); - out += `${assertFunction}(offsetof(${this.#name}, ${name}) == ${toHex(offset)}, "Invalid Offset");\n`; + const name = property.name(); + const offset = property.offset; + out += `static_assert(offsetof(${this.#name}, ${name}) == ${toHex(offset)}, "Invalid Offset");\n`; } // Sanity Check Size - const size = this.getSize(true); - const isSizeDefined = this.#size.isExact(); - out += assertSize(this.#name, size, isSizeDefined); + if (this.#size !== null) { + out += assertSize(this.#name, this.#size); + } // Allocation Function - if (isSizeDefined) { + if (this.#size !== null) { out += `${this.#name} *alloc_${this.#name}();\n`; } @@ -275,13 +195,10 @@ export class Struct { let declarations = ''; let init = ''; - // Check - this.#check(); - // Static Properties for (const property of this.#staticProperties) { - init += `${INDENT}${property.getName()}_pointer = (${property.getPointerType()}) ${toHex(property.address)};\n`; - declarations += property.generateDefinition(); + init += `${INDENT}${property.fullName()}_pointer = (${property.type()} *) ${toHex(property.offset)};\n`; + declarations += property.globalPointerDefinition(); } // Methods @@ -298,7 +215,7 @@ export class Struct { } // Allocation Function - if (this.#size.isExact()) { + if (this.#size !== null) { declarations += `${this.#name} *alloc_${this.#name}() {\n`; declarations += `${INDENT}return new ${this.#name};\n`; declarations += '}\n'; diff --git a/src/vtable.ts b/src/vtable.ts index 84c44a2..87caaf8 100644 --- a/src/vtable.ts +++ b/src/vtable.ts @@ -1,49 +1,35 @@ -import { INDENT, POINTER_SIZE, RTTI_SIZE, Size, assertSize, getSelfArg, toHex } from './common'; +import { INDENT, POINTER_SIZE, RTTI_SIZE, assertSize, getSelfArg, toHex } from './common'; import { Method } from './method'; import { Property } from './property'; -export class VTable implements Property { +export class VTable { readonly #name: string; #address: number | null; - readonly #size: Size; + #size: number | null; readonly #methods: Method[]; + readonly property: Property; // Constructor constructor(name: string, destructorOffset: number) { this.#name = name; this.#address = null; - this.#size = new Size(true); + this.#size = null; this.#methods = []; // Add Destructors (https://stackoverflow.com/a/17960941) const destructor_return = `${name} *`; const destructor_args = `(${getSelfArg(name)})`; this.add(new Method(name, 'destructor_complete', destructor_return, destructor_args, 0x0 + destructorOffset, false)); this.add(new Method(name, 'destructor_deleting', destructor_return, destructor_args, 0x4 + destructorOffset, false)); - } - - // Property Information - propertyOffset() { - return 0; - } - propertySize() { - return POINTER_SIZE; - } - propertyType() { - return this.#getName() + ' *'; - } - propertyName() { - return 'vtable'; - } - propertyAlignment() { - return 4; + // Create Property + this.property = new Property(0, this.#getName() + ' *', 'vtable', this.#name); } // Setters setAddress(address: number) { this.#address = address; } - setSize(size: number, isExact: boolean) { - this.#size.set(size, isExact); + setSize(size: number) { + this.#size = size; } // Add To VTable @@ -69,10 +55,10 @@ export class VTable implements Property { // Check #check() { // Check Size - if (this.#size.has()) { - const size = this.#size.get(); + if (this.#size !== null) { + const size = this.#size; const maxMethodCount = size / POINTER_SIZE; - if (this.#size.isExact() && maxMethodCount < this.#methods.length) { + if (maxMethodCount < this.#methods.length) { throw new Error(`VTable Size Too Small: ${toHex(size)}`); } if (maxMethodCount > this.#methods.length) { @@ -81,7 +67,7 @@ export class VTable implements Property { } } - // Generate Code + // Generate Header Code generate(directParent: string | null) { let out = ''; @@ -112,9 +98,9 @@ export class VTable implements Property { out += `};\n`; // Sanity Check Size - const isSizeDefined = this.#size.isExact(); - const size = isSizeDefined ? this.#size.get() : (this.#methods.length * POINTER_SIZE); - out += assertSize(this.#getName(), size, isSizeDefined); + if (this.#size !== null) { + out += assertSize(this.#getName(), this.#size); + } // Pointers if (this.#address !== null) { @@ -132,7 +118,7 @@ export class VTable implements Property { } // Duplication Method - if (isSizeDefined) { + if (this.#size !== null) { out += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable);\n`; } @@ -166,7 +152,7 @@ export class VTable implements Property { } // Duplication Method - if (this.#size.isExact()) { + if (this.#size !== null) { declarations += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`; declarations += `${INDENT}uchar *real_vtable = (uchar *) vtable;\n`; declarations += `${INDENT}real_vtable -= ${RTTI_SIZE};\n`; diff --git a/syntax-highlighting/def.nanorc b/syntax-highlighting/def.nanorc index 199975f..926abe1 100644 --- a/syntax-highlighting/def.nanorc +++ b/syntax-highlighting/def.nanorc @@ -2,7 +2,7 @@ syntax def "\.def$" comment "//" # Commands -color magenta "\<(extends|size|vtable(-size|-destructor-offset)?|property|static-property(-array)?|((static|virtual)-)?method|constructor)\>" +color magenta "\<(extends|size|vtable(-size|-destructor-offset)?|property|static-property|((static|virtual)-)?method|constructor)\>" # Types color green "\<(char|uchar|short|ushort|int|uint|float|bool|void|std::(string|vector|map))\>" diff --git a/syntax-highlighting/ksyntaxhighlighting.xml b/syntax-highlighting/ksyntaxhighlighting.xml index 7a90c54..0d5e74c 100644 --- a/syntax-highlighting/ksyntaxhighlighting.xml +++ b/syntax-highlighting/ksyntaxhighlighting.xml @@ -8,7 +8,6 @@ vtable property static-property - static-property-array method virtual-method static-method From 83b7e6db986a602ae50150a00bebdd964ffa452a Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Fri, 3 May 2024 00:58:57 -0400 Subject: [PATCH 2/4] Consistent Naming --- src/struct.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/struct.ts b/src/struct.ts index bd5d821..d7c00f3 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -125,7 +125,7 @@ export class Struct { if (lastProperty) { paddingSize += ` - sizeof(${lastProperty.type()})`; } - out += `${INDENT}uchar _padding${i}[${paddingSize}];\n`; + out += `${INDENT}uchar __padding${i}[${paddingSize}];\n`; // The Actual Property if (property !== sizeProperty) { From dcc35de95c941a04021c27c5a541a6f26ad52af2 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Fri, 3 May 2024 01:13:51 -0400 Subject: [PATCH 3/4] Make Destructor Offset Code Less Dumb --- src/struct.ts | 10 +++------ src/vtable.ts | 61 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/struct.ts b/src/struct.ts index d7c00f3..10f2d1a 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -12,7 +12,6 @@ export class Struct { readonly #dependencies: string[]; readonly #staticProperties: StaticProperty[]; #directParent: string | null; - #vtableDestructorOffset: number; // Constructor constructor(name: string) { @@ -24,7 +23,6 @@ export class Struct { this.#dependencies = []; this.#staticProperties = []; this.#directParent = null; - this.#vtableDestructorOffset = 0; } // Dependencies @@ -38,17 +36,15 @@ export class Struct { // Ensure VTable Exists ensureVTable() { if (this.#vtable === null) { - this.#vtable = new VTable(this.#name, this.#vtableDestructorOffset); + this.#vtable = new VTable(this.#name); this.addProperty(this.#vtable.property); } } // Set VTable Destructor Offset setVTableDestructorOffset(offset: number) { - if (this.#vtable) { - throw new Error('VTable Already Created'); - } - this.#vtableDestructorOffset = offset; + this.ensureVTable(); + this.#vtable!.setDestructorOffset(offset); } // Setters diff --git a/src/vtable.ts b/src/vtable.ts index 87caaf8..9e3147a 100644 --- a/src/vtable.ts +++ b/src/vtable.ts @@ -3,25 +3,22 @@ import { Method } from './method'; import { Property } from './property'; export class VTable { - readonly #name: string; + readonly #self: string; #address: number | null; #size: number | null; readonly #methods: Method[]; readonly property: Property; + #destructorOffset: number; // Constructor - constructor(name: string, destructorOffset: number) { - this.#name = name; + constructor(self: string) { + this.#self = self; this.#address = null; this.#size = null; this.#methods = []; - // Add Destructors (https://stackoverflow.com/a/17960941) - const destructor_return = `${name} *`; - const destructor_args = `(${getSelfArg(name)})`; - this.add(new Method(name, 'destructor_complete', destructor_return, destructor_args, 0x0 + destructorOffset, false)); - this.add(new Method(name, 'destructor_deleting', destructor_return, destructor_args, 0x4 + destructorOffset, false)); + this.#destructorOffset = 0; // Create Property - this.property = new Property(0, this.#getName() + ' *', 'vtable', this.#name); + this.property = new Property(0, this.#getName() + ' *', 'vtable', this.#self); } // Setters @@ -31,9 +28,12 @@ export class VTable { setSize(size: number) { this.#size = size; } + setDestructorOffset(destructorOffset: number) { + this.#destructorOffset = destructorOffset; + } // Add To VTable - add(method: Method) { + #add(target: Method[], method: Method) { // Check Offset const offset = method.address; if ((offset % POINTER_SIZE) !== 0) { @@ -41,15 +41,18 @@ export class VTable { } // Add const index = offset / POINTER_SIZE; - if (this.#methods[index]) { + if (target[index]) { throw new Error(`Duplicate Virtual Method At Offset: ${toHex(offset)}`); } - this.#methods[index] = method; + target[index] = method; + } + add(method: Method) { + this.#add(this.#methods, method); } // Get Structure Name #getName() { - return this.#name + '_vtable'; + return this.#self + '_vtable'; } // Check @@ -67,6 +70,20 @@ export class VTable { } } + // Get Full Methods Table + #getMethods() { + // Copy Array + const out = []; + out.push(...this.#methods); + // Add Destructors (https://stackoverflow.com/a/17960941) + const destructor_return = `${this.#self} *`; + const destructor_args = `(${getSelfArg(this.#self)})`; + this.#add(out, new Method(this.#self, 'destructor_complete', destructor_return, destructor_args, 0x0 + this.#destructorOffset, false)); + this.#add(out, new Method(this.#self, 'destructor_deleting', destructor_return, destructor_args, 0x4 + this.#destructorOffset, false)); + // Return + return out; + } + // Generate Header Code generate(directParent: string | null) { let out = ''; @@ -75,8 +92,9 @@ export class VTable { this.#check(); // Method Prototypes - for (let i = 0; i < this.#methods.length; i++) { - const info = this.#methods[i]; + const methods = this.#getMethods(); + for (let i = 0; i < methods.length; i++) { + const info = methods[i]; if (info) { out += info.generateTypedef(); } @@ -85,10 +103,10 @@ export class VTable { // Structure out += `typedef struct ${this.#getName()} ${this.#getName()};\n`; out += `struct ${this.#getName()} {\n`; - for (let i = 0; i < this.#methods.length; i++) { + for (let i = 0; i < methods.length; i++) { let name = `unknown${i}`; let type = 'void *'; - const info = this.#methods[i]; + const info = methods[i]; if (info) { name = info.shortName; type = info.getType() + ' '; @@ -107,8 +125,8 @@ export class VTable { // Base out += `extern ${this.#getName()} *${this.#getName()}_base;\n`; // Methods - for (let i = 0; i < this.#methods.length; i++) { - const info = this.#methods[i]; + for (let i = 0; i < methods.length; i++) { + const info = methods[i]; if (info) { const type = `${info.getType()} *`; out += `extern ${type}${info.getName()}_vtable_addr;\n`; @@ -140,8 +158,9 @@ export class VTable { init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${toHex(this.#address)};\n`; declarations += `${this.#getName()} *${this.#getName()}_base;\n`; // Methods - for (let i = 0; i < this.#methods.length; i++) { - const info = this.#methods[i]; + const methods = this.#getMethods(); + for (let i = 0; i < methods.length; i++) { + const info = methods[i]; if (info) { const vtableAddress = this.#address + (i * POINTER_SIZE); const type = `${info.getType()} *`; From 6ee26aad80aa697fc332a51b38cf1c2e8d4513ed Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Fri, 3 May 2024 22:20:03 -0400 Subject: [PATCH 4/4] Fancy C++ Methods! --- src/common.ts | 9 ++++++ src/index.ts | 15 ++------- src/loader.ts | 4 +-- src/method.ts | 12 +++++--- src/struct.ts | 85 ++++++++++++++++++++++++++++++++++++++++----------- src/vtable.ts | 10 +++--- 6 files changed, 94 insertions(+), 41 deletions(-) diff --git a/src/common.ts b/src/common.ts index b228b95..56a2aa1 100644 --- a/src/common.ts +++ b/src/common.ts @@ -111,4 +111,13 @@ export function prependArg(args: string, arg: string) { arg += ', '; } return '(' + arg + args.substring(1); +} +export function removeFirstArg(args: string) { + let index = args.indexOf(','); + if (index === -1) { + index = args.indexOf(')'); + } else { + index++; + } + return '(' + args.substring(index).trim(); } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ba98e5f..f3ff06a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -164,12 +164,11 @@ function makeMainHeader(output: string) { result += '#include \n'; result += '\n// Warnings\n'; result += '#pragma GCC diagnostic push\n'; - result += '#pragma GCC diagnostic ignored "-Winvalid-offsetof"\n\n'; + result += '#pragma GCC diagnostic ignored "-Winvalid-offsetof"\n'; + result += '#pragma GCC diagnostic ignored "-Wshadow"\n\n'; result += makeHeaderPart(); result += '\n// Cleanup Warnings\n'; result += '#pragma GCC diagnostic pop\n'; - result += '\n// Array Of All Method Symbols\n'; - result += 'extern void *_all_method_symbols[];\n'; fs.writeFileSync(output, result); } makeMainHeader(headerOutput); @@ -212,16 +211,6 @@ function makeCompiledCode(output: string) { result += init; result += '}\n\n'; result += declarations; - result += '\n// Setup Methods Array\n'; - result += 'void *_all_method_symbols[] = {\n'; - for (const structure of structureObjects) { - const methods = structure.getMethodSymbols(); - for (const method of methods) { - result += `${INDENT}&${method},\n`; - } - } - result += `${INDENT}nullptr\n`; - result += '};\n'; fs.writeFileSync(output, result); } makeCompiledCode(sourceOutput); diff --git a/src/loader.ts b/src/loader.ts index 497a395..8ef89be 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -47,7 +47,7 @@ function parseMethod(args: string, self: string, insertSelfArg: boolean, isInher methodArgs = prependArg(methodArgs, selfArg); } const address = safeParseInt(end[1]!); - return new Method(self, name, type, methodArgs, address, isInherited); + return new Method(self, name, type, methodArgs, address, isInherited, !insertSelfArg); } // Load Structure @@ -144,7 +144,7 @@ export function load(target: Struct, name: string, isExtended: boolean) { } case 'method': { // Add Method - const method = parseMethod(args, target.getName(), true, isExtended); + const method = parseMethod(args, name, true, isExtended); target.addMethod(method, false); break; } diff --git a/src/method.ts b/src/method.ts index d8ec734..8f7eda6 100644 --- a/src/method.ts +++ b/src/method.ts @@ -7,15 +7,19 @@ export class Method { readonly args: string; readonly address: number; readonly isInherited: boolean; + readonly hasVarargs: boolean; + readonly isStatic: boolean; // Constructor - constructor(self: string, name: string, returnType: string, args: string, address: number, isInherited: boolean) { + constructor(self: string, name: string, returnType: string, args: string, address: number, isInherited: boolean, isStatic: boolean) { this.self = self; this.shortName = name; this.returnType = returnType; this.args = args; this.address = address; this.isInherited = isInherited; + this.hasVarargs = this.args.includes('...'); + this.isStatic = isStatic; } // Get Type @@ -38,9 +42,9 @@ export class Method { out += `typedef ${returnType}(*${this.getType()})${this.args};\n`; // Fancy Overwrite Does Not Support Varargs - if (!this.args.includes('...')) { + if (!this.hasVarargs) { // Overwrite Helper - out += `#define _overwrite_helper_for_${this.getName()}(method, original) \\\n`; + out += `#define __overwrite_helper_for_${this.getName()}(method, original) \\\n`; out += `${INDENT}[]${this.args} { \\\n`; out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}method(${['original'].concat(getArgNames(this.args)).join(', ')}); \\\n`; out += `${INDENT}}\n`; @@ -57,7 +61,7 @@ export class Method { // Generate "New Method" Test generateNewMethodTest(parent: string | null, prefix: string, suffix: string) { - let out = `#define _is_new_method_${this.getName()}() (`; + let out = `#define __is_new_method_${this.getName()}() (`; if (!this.isInherited) { out += 'true'; } else { diff --git a/src/struct.ts b/src/struct.ts index 10f2d1a..7aa5801 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -1,4 +1,4 @@ -import { INDENT, STRUCTURE_FILES, toHex, assertSize } from './common'; +import { INDENT, STRUCTURE_FILES, toHex, assertSize, formatType, getArgNames, removeFirstArg } from './common'; import { Method } from './method'; import { Property, StaticProperty } from './property'; import { VTable } from './vtable'; @@ -58,13 +58,19 @@ export class Struct { // Add Method addMethod(method: Method, isVirtual: boolean) { - if (method.self !== this.#name) { - throw new Error(); + if (method.returnType !== this.#name && method.returnType in STRUCTURE_FILES) { + this.#addDependency(method.returnType); } if (isVirtual) { + if (method.self !== this.#name) { + throw new Error(); + } this.ensureVTable(); this.#vtable!.add(method); } else { + if (method.isInherited) { + this.#addDependency(method.self); + } this.#methods.push(method); } } @@ -131,6 +137,55 @@ export class Struct { return out; } + // Generate C++ Method Shortcuts + #generateMethods() { + let out = ''; + // Normal Methods + const getArgsOuter = (method: Method) => { + let out = method.args; + if (!method.isStatic) { + // Remove "self" + out = removeFirstArg(out); + } + return out; + }; + const getArgsInner = (method: Method) => { + const list = getArgNames(method.args); + if (!method.isStatic) { + // Replace "self" With "this" + list[0] = `(${method.self} *) this`; + } + return list.join(', '); + }; + for (const method of this.#methods) { + if (!method.hasVarargs) { + const returnType = method.returnType; + const shortName = method.shortName; + const fullName = method.getName(); + const args = getArgsOuter(method); + out += `${INDENT}inline ${formatType(returnType)}${shortName}${args} { \\\n`; + out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}${fullName}(${getArgsInner(method)});\n`; + out += `${INDENT}}\n`; + } + } + // Virtual Methods + if (this.#vtable !== null) { + const virtualMethods = this.#vtable.getMethods(); + for (const method of virtualMethods) { + if (method && !method.hasVarargs) { + const returnType = method.returnType; + const shortName = method.shortName; + const args = getArgsOuter(method); + out += `${INDENT}inline ${formatType(returnType)}${shortName}${args} { \\\n`; + out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}this->vtable->${shortName}(${getArgsInner(method)});\n`; + out += `${INDENT}}\n`; + } + } + } + // Return + return out; + } + // Generate Header generate() { let out = ''; @@ -144,9 +199,11 @@ export class Struct { // Methods for (const method of this.#methods) { - out += method.generateTypedef(); - out += `extern ${method.generateDefinition()}`; - out += method.generateNewMethodTest(this.#directParent, '', ''); + if (!method.isInherited) { + out += method.generateTypedef(); + out += `extern ${method.generateDefinition()}`; + out += method.generateNewMethodTest(this.#directParent, '', ''); + } } // VTable @@ -162,6 +219,7 @@ export class Struct { // Structure out += `struct ${this.#name} {\n`; out += this.#generateProperties(); + out += this.#generateMethods(); out += `};\n`; // Sanity Check Offsets @@ -199,8 +257,10 @@ export class Struct { // Methods for (const method of this.#methods) { - init += `${INDENT}${method.getName()} = (${method.getType()}) ${toHex(method.address)};\n`; - declarations += method.generateDefinition(); + if (!method.isInherited) { + init += `${INDENT}${method.getName()} = (${method.getType()}) ${toHex(method.address)};\n`; + declarations += method.generateDefinition(); + } } // VTable @@ -221,15 +281,6 @@ export class Struct { return {functions: declarations, init}; } - // Get Method Symbols - getMethodSymbols() { - const ret = []; - for (const method of this.#methods) { - ret.push(method.getName()); - } - return ret; - } - // Set Direct Parent (Used For "New Method" Testing) setDirectParent(directParent: string) { this.#directParent = directParent; diff --git a/src/vtable.ts b/src/vtable.ts index 9e3147a..2c6b777 100644 --- a/src/vtable.ts +++ b/src/vtable.ts @@ -71,15 +71,15 @@ export class VTable { } // Get Full Methods Table - #getMethods() { + getMethods() { // Copy Array const out = []; out.push(...this.#methods); // Add Destructors (https://stackoverflow.com/a/17960941) const destructor_return = `${this.#self} *`; const destructor_args = `(${getSelfArg(this.#self)})`; - this.#add(out, new Method(this.#self, 'destructor_complete', destructor_return, destructor_args, 0x0 + this.#destructorOffset, false)); - this.#add(out, new Method(this.#self, 'destructor_deleting', destructor_return, destructor_args, 0x4 + this.#destructorOffset, false)); + this.#add(out, new Method(this.#self, 'destructor_complete', destructor_return, destructor_args, 0x0 + this.#destructorOffset, false, false)); + this.#add(out, new Method(this.#self, 'destructor_deleting', destructor_return, destructor_args, 0x4 + this.#destructorOffset, false, false)); // Return return out; } @@ -92,7 +92,7 @@ export class VTable { this.#check(); // Method Prototypes - const methods = this.#getMethods(); + const methods = this.getMethods(); for (let i = 0; i < methods.length; i++) { const info = methods[i]; if (info) { @@ -158,7 +158,7 @@ export class VTable { init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${toHex(this.#address)};\n`; declarations += `${this.#getName()} *${this.#getName()}_base;\n`; // Methods - const methods = this.#getMethods(); + const methods = this.getMethods(); for (let i = 0; i < methods.length; i++) { const info = methods[i]; if (info) {