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