This commit is contained in:
Bigjango13 2024-05-04 00:13:49 -07:00
commit cd20feda59
9 changed files with 256 additions and 465 deletions

View File

@ -33,7 +33,6 @@ export function parseTypeAndName(piece: string) {
// Return // Return
return {type, name}; return {type, name};
} }
export const MIN_SIZE = 1;
export function toUpperSnakeCase(str: string) { export function toUpperSnakeCase(str: string) {
let wasUpper = false; let wasUpper = false;
let nextIsUpper = false; let nextIsUpper = false;
@ -66,20 +65,13 @@ export const COMMENT = '//';
export function toHex(x: number) { export function toHex(x: number) {
return '0x' + x.toString(16); return '0x' + x.toString(16);
} }
export function getAssertFunction() { export function assertSize(name: string, size: number) {
return 'static_assert';
}
export function assertSize(name: string, size: number, isDefined: boolean) {
let out = ''; let out = '';
// Define Size Macro // Define Size Macro
const macro = toUpperSnakeCase(name) + '_SIZE'; const macro = toUpperSnakeCase(name) + '_SIZE';
out += `#define ${macro} ${toHex(size)}\n`; out += `#define ${macro} ${toHex(size)}\n`;
// Check Size // Check Size
out += `${getAssertFunction()}(sizeof(${name}) == ${macro}, "Invalid Size");\n`; out += `static_assert(sizeof(${name}) == ${macro}, "Invalid Size");\n`;
// Hide Structure Size If The Real Size Is Unknown
if (!isDefined) {
out += `#undef ${macro}\n`;
}
// Return // Return
return out; return out;
} }
@ -90,80 +82,9 @@ export function safeParseInt(str: string) {
} }
return x; 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) { export function getSelfArg(type: string) {
return `${type} *self`; 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) { export function getArgNames(args: string) {
// Remove Parentheses // Remove Parentheses
args = args.substring(1, args.length - 1); args = args.substring(1, args.length - 1);
@ -190,4 +111,13 @@ export function prependArg(args: string, arg: string) {
arg += ', '; arg += ', ';
} }
return '(' + arg + args.substring(1); 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();
} }

View File

@ -164,12 +164,11 @@ function makeMainHeader(output: string) {
result += '#include <map>\n'; result += '#include <map>\n';
result += '\n// Warnings\n'; result += '\n// Warnings\n';
result += '#pragma GCC diagnostic push\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 += makeHeaderPart();
result += '\n// Cleanup Warnings\n'; result += '\n// Cleanup Warnings\n';
result += '#pragma GCC diagnostic pop\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); fs.writeFileSync(output, result);
} }
makeMainHeader(headerOutput); makeMainHeader(headerOutput);
@ -212,16 +211,6 @@ function makeCompiledCode(output: string) {
result += init; result += init;
result += '}\n\n'; result += '}\n\n';
result += declarations; 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); fs.writeFileSync(output, result);
} }
makeCompiledCode(sourceOutput); makeCompiledCode(sourceOutput);

View File

@ -1,6 +1,6 @@
import { COMMENT, EXTENSION, getSelfArg, parseTypeAndName, prependArg, readDefinition, safeParseInt, syntaxError } from './common'; import { COMMENT, EXTENSION, getSelfArg, parseTypeAndName, prependArg, readDefinition, safeParseInt, syntaxError } from './common';
import { Method } from './method'; import { Method } from './method';
import { SimpleProperty, StaticProperty } from './property'; import { Property, StaticProperty } from './property';
import { Struct } from './struct'; import { Struct } from './struct';
// Error Handling // Error Handling
@ -47,7 +47,7 @@ function parseMethod(args: string, self: string, insertSelfArg: boolean, isInher
methodArgs = prependArg(methodArgs, selfArg); methodArgs = prependArg(methodArgs, selfArg);
} }
const address = safeParseInt(end[1]!); 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 // Load Structure
@ -108,12 +108,16 @@ export function load(target: Struct, name: string, isExtended: boolean) {
} }
case 'size': { case 'size': {
// Set Size // Set Size
target.setSize(safeParseInt(args), !isExtended); if (!isExtended) {
target.setSize(safeParseInt(args));
}
break; break;
} }
case 'vtable-size': { case 'vtable-size': {
// Set VTable Size // Set VTable Size
target.setVTableSize(safeParseInt(args), !isExtended); if (!isExtended) {
target.setVTableSize(safeParseInt(args));
}
break; break;
} }
case 'vtable': { case 'vtable': {
@ -127,28 +131,20 @@ export function load(target: Struct, name: string, isExtended: boolean) {
case 'property': { case 'property': {
// Add Property // Add Property
const info = parseProperty(args); 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; break;
} }
case 'static-property': { case 'static-property': {
// Add Static Property // Add Static Property
if (!isExtended) { if (!isExtended) {
const info = parseProperty(args); const info = parseProperty(args);
target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName(), false)); target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName()));
}
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));
} }
break; break;
} }
case 'method': { case 'method': {
// Add Method // Add Method
const method = parseMethod(args, target.getName(), true, isExtended); const method = parseMethod(args, name, true, isExtended);
target.addMethod(method, false); target.addMethod(method, false);
break; break;
} }

View File

@ -7,15 +7,19 @@ export class Method {
readonly args: string; readonly args: string;
readonly address: number; readonly address: number;
readonly isInherited: boolean; readonly isInherited: boolean;
readonly hasVarargs: boolean;
readonly isStatic: boolean;
// Constructor // 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.self = self;
this.shortName = name; this.shortName = name;
this.returnType = returnType; this.returnType = returnType;
this.args = args; this.args = args;
this.address = address; this.address = address;
this.isInherited = isInherited; this.isInherited = isInherited;
this.hasVarargs = this.args.includes('...');
this.isStatic = isStatic;
} }
// Get Type // Get Type
@ -38,9 +42,9 @@ export class Method {
out += `typedef ${returnType}(*${this.getType()})${this.args};\n`; out += `typedef ${returnType}(*${this.getType()})${this.args};\n`;
// Fancy Overwrite Does Not Support Varargs // Fancy Overwrite Does Not Support Varargs
if (!this.args.includes('...')) { if (!this.hasVarargs) {
// Overwrite Helper // 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}[]${this.args} { \\\n`;
out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}method(${['original'].concat(getArgNames(this.args)).join(', ')}); \\\n`; out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}method(${['original'].concat(getArgNames(this.args)).join(', ')}); \\\n`;
out += `${INDENT}}\n`; out += `${INDENT}}\n`;
@ -57,7 +61,7 @@ export class Method {
// Generate "New Method" Test // Generate "New Method" Test
generateNewMethodTest(parent: string | null, prefix: string, suffix: string) { 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) { if (!this.isInherited) {
out += 'true'; out += 'true';
} else { } else {

View File

@ -1,156 +1,60 @@
import { POINTER_SIZE, getSizeMultiplierFromArrayData } from './common'; import { formatType } from './common';
import { getStructure } from './map';
export interface Property { export class Property {
propertyOffset(): number; readonly offset: 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;
readonly #type: string; readonly #type: string;
readonly #name: string; readonly #name: string;
readonly #self: string; readonly #self: string;
readonly #isArray: boolean;
// Constructor // Constructor
constructor(address: number, type: string, name: string, self: string, isArray: boolean) { constructor(offset: number, type: string, name: string, self: string) {
if (name.includes('[')) { this.offset = offset;
throw new Error('Use "static-property-array" For Arrays');
}
this.address = address;
this.#type = type; this.#type = type;
this.#name = name; this.#name = name;
this.#self = self; this.#self = self;
this.#isArray = isArray;
} }
// Name And Type // Getters
getName() { type() {
return `${this.#self}_${this.#name}`; return `__type_${this.fullName()}`;
} }
getPointerType() { typedef() {
let type = this.#type; let arrayInfo = '';
// Convert Type To Pointer const arrayInfoIndex = this.#name.indexOf('[');
if (!type.endsWith('*')) { if (arrayInfoIndex !== -1) {
type += ' '; arrayInfo = this.#name.substring(arrayInfoIndex);
} }
type += '*'; return `typedef ${formatType(this.#type)}${this.type()}${arrayInfo};\n`;
// Return }
return type; 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 // Generate Variable Definition
generateDefinition() { globalPointerDefinition() {
return `${this.getPointerType()}${this.getName()}_pointer;\n`; return `${this.type()} *${this.fullName()}_pointer;\n`;
} }
// Generate Macro // Generate Macro
generateMacro() { macro() {
return `#define ${this.getName()} (${this.#isArray ? '' : '*'}${this.getName()}_pointer)\n`; return `#define ${this.fullName()} (*${this.fullName()}_pointer)\n`;
} }
} }

View File

@ -1,4 +1,4 @@
import { INDENT, MIN_SIZE, formatType, STRUCTURE_FILES, toHex, getAssertFunction, assertSize, stripArrayData, Size } from './common'; import { INDENT, STRUCTURE_FILES, toHex, assertSize, formatType, getArgNames, removeFirstArg } from './common';
import { Method } from './method'; import { Method } from './method';
import { Property, StaticProperty } from './property'; import { Property, StaticProperty } from './property';
import { VTable } from './vtable'; import { VTable } from './vtable';
@ -8,11 +8,10 @@ export class Struct {
#vtable: VTable | null; #vtable: VTable | null;
readonly #methods: Method[]; readonly #methods: Method[];
readonly #properties: Property[]; readonly #properties: Property[];
readonly #size: Size; #size: number | null;
readonly #dependencies: string[]; readonly #dependencies: string[];
readonly #staticProperties: StaticProperty[]; readonly #staticProperties: StaticProperty[];
#directParent: string | null; #directParent: string | null;
#vtableDestructorOffset: number;
// Constructor // Constructor
constructor(name: string) { constructor(name: string) {
@ -20,11 +19,10 @@ export class Struct {
this.#methods = []; this.#methods = [];
this.#properties = []; this.#properties = [];
this.#vtable = null; this.#vtable = null;
this.#size = new Size(false); this.#size = null;
this.#dependencies = []; this.#dependencies = [];
this.#staticProperties = []; this.#staticProperties = [];
this.#directParent = null; this.#directParent = null;
this.#vtableDestructorOffset = 0;
} }
// Dependencies // Dependencies
@ -38,82 +36,41 @@ export class Struct {
// Ensure VTable Exists // Ensure VTable Exists
ensureVTable() { ensureVTable() {
if (this.#vtable === null) { if (this.#vtable === null) {
this.#vtable = new VTable(this.#name, this.#vtableDestructorOffset); this.#vtable = new VTable(this.#name);
this.addProperty(this.#vtable); this.addProperty(this.#vtable.property);
} }
} }
// Set VTable Destructor Offset // Set VTable Destructor Offset
setVTableDestructorOffset(offset: number) { setVTableDestructorOffset(offset: number) {
if (this.#vtable) { this.ensureVTable();
throw new Error('VTable Already Created'); this.#vtable!.setDestructorOffset(offset);
}
this.#vtableDestructorOffset = offset;
} }
// Setters // Setters
setSize(size: number, isExact: boolean) { setSize(size: number) {
this.#size.set(size, isExact); this.#size = size;
} }
// Getters // 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() { getName() {
return this.#name; 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 // Add Method
addMethod(method: Method, isVirtual: boolean) { addMethod(method: Method, isVirtual: boolean) {
if (method.self !== this.#name) { if (method.returnType !== this.#name && method.returnType in STRUCTURE_FILES) {
throw new Error(); this.#addDependency(method.returnType);
} }
if (isVirtual) { if (isVirtual) {
if (method.self !== this.#name) {
throw new Error();
}
this.ensureVTable(); this.ensureVTable();
this.#vtable!.add(method); this.#vtable!.add(method);
} else { } else {
if (method.isInherited) {
this.#addDependency(method.self);
}
this.#methods.push(method); this.#methods.push(method);
} }
} }
@ -121,7 +78,7 @@ export class Struct {
addProperty(property: Property) { addProperty(property: Property) {
this.#properties.push(property); this.#properties.push(property);
// Add Dependency If Needed // Add Dependency If Needed
const type = property.propertyType(); const type = property.rawType();
if (type in STRUCTURE_FILES) { if (type in STRUCTURE_FILES) {
this.#addDependency(type); this.#addDependency(type);
} }
@ -132,88 +89,122 @@ export class Struct {
} }
// Configure VTable // Configure VTable
setVTableSize(size: number, isExact: boolean) { setVTableSize(size: number) {
this.ensureVTable(); this.ensureVTable();
this.#vtable!.setSize(size, isExact); this.#vtable!.setSize(size);
} }
setVTableAddress(address: number) { setVTableAddress(address: number) {
this.ensureVTable(); this.ensureVTable();
this.#vtable!.setAddress(address); this.#vtable!.setAddress(address);
} }
// Check // Generate Properties
#check() { #generateProperties() {
// Sort Properties // 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 // Fake Property To Pad Structure Size
if (this.#size.isExact()) { let sizeProperty = null;
const size = this.getSize(true); if (this.#size) {
// Check Alignment sizeProperty = new Property(this.#size, '', '', '');
const specifiedSize = this.#size.get()!; sortedProperties.push(sizeProperty);
if (size !== specifiedSize) { }
throw new Error('Size Misaligned');
// 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;
} }
// Check If Size Is Too Small let paddingSize = toHex(offsetFromLastProperty);
let realSize = this.#getRealSize(); if (lastProperty) {
realSize = this.#roundSize(realSize); paddingSize += ` - sizeof(${lastProperty.type()})`;
if (realSize > specifiedSize) { }
throw new Error(`Structure Size Too Small: ${toHex(specifiedSize)}`); out += `${INDENT}uchar __padding${i}[${paddingSize}];\n`;
// The Actual Property
if (property !== sizeProperty) {
out += `${INDENT}${property.type()} ${property.name()};\n`;
} }
} }
return out;
} }
// Compute Padding Between Properties // Generate C++ Method Shortcuts
#computePadding(a: Property | null, b: Property | null) { #generateMethods() {
// Null A = Start Of Structure let out = '';
// Null B = End Of Structure // Normal Methods
let neededPadding = 0; const getArgsOuter = (method: Method) => {
const size = this.getSize(true); let out = method.args;
if (a === null) { if (!method.isStatic) {
// Start Of Structure Padding // Remove "self"
if (b !== null) { out = removeFirstArg(out);
neededPadding = b.propertyOffset();
} else {
// Both A And B Are Null
neededPadding = size;
} }
} else if (b === null) { return out;
// End Of Structure Padding };
const realSize = this.#getRealSize(); const getArgsInner = (method: Method) => {
const realRoundedSize = this.#roundSize(realSize); const list = getArgNames(method.args);
if (realRoundedSize !== size) { if (!method.isStatic) {
neededPadding = size - realSize; // 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`;
} }
} else {
// Inner Structure Padding
const realSizeSoFar = a.propertyOffset() + a.propertySize();
neededPadding = b.propertyOffset() - realSizeSoFar;
} }
if (neededPadding < 0) { // Virtual Methods
throw new Error('Overlapping Properties Detected!'); 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 neededPadding; // Return
return out;
} }
// Generate Header // Generate Header
generate() { generate() {
let out = ''; let out = '';
// Check
this.#check();
// Static Properties // Static Properties
for (const property of this.#staticProperties) { for (const property of this.#staticProperties) {
out += `extern ${property.generateDefinition()}`; out += property.typedef();
out += property.generateMacro(); out += `extern ${property.globalPointerDefinition()}`;
out += property.macro();
} }
// Methods // Methods
for (const method of this.#methods) { for (const method of this.#methods) {
out += method.generateTypedef(); if (!method.isInherited) {
out += `extern ${method.generateDefinition()}`; out += method.generateTypedef();
out += `extern ${method.generateDefinition('_unedited')}`; out += `extern ${method.generateDefinition()}`;
out += method.generateNewMethodTest(this.#directParent, '', ''); out += `extern ${method.generateDefinition('_unedited')}`;
out += method.generateNewMethodTest(this.#directParent, '', '');
}
} }
// VTable // VTable
@ -221,49 +212,32 @@ export class Struct {
out += this.#vtable.generate(this.#directParent); out += this.#vtable.generate(this.#directParent);
} }
// Property Typedefs
for (const property of this.#properties) {
out += property.typedef();
}
// Structure // Structure
out += `struct ${this.#name} {\n`; out += `struct ${this.#name} {\n`;
for (let i = 0; i <= this.#properties.length; i++) { out += this.#generateProperties();
const property = i < this.#properties.length ? this.#properties[i]! : null; out += this.#generateMethods();
// 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 += `};\n`; out += `};\n`;
// Sanity Check Offsets // Sanity Check Offsets
const assertFunction = getAssertFunction();
for (let i = 0; i < this.#properties.length; i++) { for (let i = 0; i < this.#properties.length; i++) {
const property = this.#properties[i]!; const property = this.#properties[i]!;
const name = stripArrayData(property.propertyName()); const name = property.name();
const offset = property.propertyOffset(); const offset = property.offset;
out += `${assertFunction}(offsetof(${this.#name}, ${name}) == ${toHex(offset)}, "Invalid Offset");\n`; out += `static_assert(offsetof(${this.#name}, ${name}) == ${toHex(offset)}, "Invalid Offset");\n`;
} }
// Sanity Check Size // Sanity Check Size
const size = this.getSize(true); if (this.#size !== null) {
const isSizeDefined = this.#size.isExact(); out += assertSize(this.#name, this.#size);
out += assertSize(this.#name, size, isSizeDefined); }
// Allocation Function // Allocation Function
if (isSizeDefined) { if (this.#size !== null) {
out += `${this.#name} *alloc_${this.#name}();\n`; out += `${this.#name} *alloc_${this.#name}();\n`;
} }
@ -276,21 +250,20 @@ export class Struct {
let declarations = ''; let declarations = '';
let init = ''; let init = '';
// Check
this.#check();
// Static Properties // Static Properties
for (const property of this.#staticProperties) { for (const property of this.#staticProperties) {
init += `${INDENT}${property.getName()}_pointer = (${property.getPointerType()}) ${toHex(property.address)};\n`; init += `${INDENT}${property.fullName()}_pointer = (${property.type()} *) ${toHex(property.offset)};\n`;
declarations += property.generateDefinition(); declarations += property.globalPointerDefinition();
} }
// Methods // Methods
for (const method of this.#methods) { for (const method of this.#methods) {
init += `${INDENT}${method.getName()} = (${method.getType()}) ${toHex(method.address)};\n`; if (!method.isInherited) {
declarations += method.generateDefinition(); init += `${INDENT}${method.getName()} = (${method.getType()}) ${toHex(method.address)};\n`;
init += `${INDENT}${method.getName()}_unedited = (${method.getType()}) ${toHex(method.address)};\n`; declarations += method.generateDefinition();
declarations += method.generateDefinition('_unedited'); init += `${INDENT}${method.getName()}_unedited = (${method.getType()}) ${toHex(method.address)};\n`;
declarations += method.generateDefinition('_unedited');
}
} }
// VTable // VTable
@ -301,7 +274,7 @@ export class Struct {
} }
// Allocation Function // Allocation Function
if (this.#size.isExact()) { if (this.#size !== null) {
declarations += `${this.#name} *alloc_${this.#name}() {\n`; declarations += `${this.#name} *alloc_${this.#name}() {\n`;
declarations += `${INDENT}return new ${this.#name};\n`; declarations += `${INDENT}return new ${this.#name};\n`;
declarations += '}\n'; declarations += '}\n';
@ -311,15 +284,6 @@ export class Struct {
return {functions: declarations, init}; 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) // Set Direct Parent (Used For "New Method" Testing)
setDirectParent(directParent: string) { setDirectParent(directParent: string) {
this.#directParent = directParent; this.#directParent = directParent;

View File

@ -1,53 +1,39 @@
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 { Method } from './method';
import { Property } from './property'; import { Property } from './property';
export class VTable implements Property { export class VTable {
readonly #name: string; readonly #self: string;
#address: number | null; #address: number | null;
readonly #size: Size; #size: number | null;
readonly #methods: Method[]; readonly #methods: Method[];
readonly property: Property;
#destructorOffset: number;
// Constructor // Constructor
constructor(name: string, destructorOffset: number) { constructor(self: string) {
this.#name = name; this.#self = self;
this.#address = null; this.#address = null;
this.#size = new Size(true); this.#size = null;
this.#methods = []; this.#methods = [];
// Add Destructors (https://stackoverflow.com/a/17960941) this.#destructorOffset = 0;
const destructor_return = `${name} *`; // Create Property
const destructor_args = `(${getSelfArg(name)})`; this.property = new Property(0, this.#getName() + ' *', 'vtable', this.#self);
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;
} }
// Setters // Setters
setAddress(address: number) { setAddress(address: number) {
this.#address = address; this.#address = address;
} }
setSize(size: number, isExact: boolean) { setSize(size: number) {
this.#size.set(size, isExact); this.#size = size;
}
setDestructorOffset(destructorOffset: number) {
this.#destructorOffset = destructorOffset;
} }
// Add To VTable // Add To VTable
add(method: Method) { #add(target: Method[], method: Method) {
// Check Offset // Check Offset
const offset = method.address; const offset = method.address;
if ((offset % POINTER_SIZE) !== 0) { if ((offset % POINTER_SIZE) !== 0) {
@ -55,24 +41,27 @@ export class VTable implements Property {
} }
// Add // Add
const index = offset / POINTER_SIZE; const index = offset / POINTER_SIZE;
if (this.#methods[index]) { if (target[index]) {
throw new Error(`Duplicate Virtual Method At Offset: ${toHex(offset)}`); 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 // Get Structure Name
#getName() { #getName() {
return this.#name + '_vtable'; return this.#self + '_vtable';
} }
// Check // Check
#check() { #check() {
// Check Size // Check Size
if (this.#size.has()) { if (this.#size !== null) {
const size = this.#size.get(); const size = this.#size;
const maxMethodCount = size / POINTER_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)}`); throw new Error(`VTable Size Too Small: ${toHex(size)}`);
} }
if (maxMethodCount > this.#methods.length) { if (maxMethodCount > this.#methods.length) {
@ -81,7 +70,21 @@ export class VTable implements Property {
} }
} }
// Generate Code // 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, false));
this.#add(out, new Method(this.#self, 'destructor_deleting', destructor_return, destructor_args, 0x4 + this.#destructorOffset, false, false));
// Return
return out;
}
// Generate Header Code
generate(directParent: string | null) { generate(directParent: string | null) {
let out = ''; let out = '';
@ -89,8 +92,9 @@ export class VTable implements Property {
this.#check(); this.#check();
// Method Prototypes // Method Prototypes
for (let i = 0; i < this.#methods.length; i++) { const methods = this.getMethods();
const info = this.#methods[i]; for (let i = 0; i < methods.length; i++) {
const info = methods[i];
if (info) { if (info) {
out += info.generateTypedef(); out += info.generateTypedef();
} }
@ -99,10 +103,10 @@ export class VTable implements Property {
// Structure // Structure
out += `typedef struct ${this.#getName()} ${this.#getName()};\n`; out += `typedef struct ${this.#getName()} ${this.#getName()};\n`;
out += `struct ${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 name = `unknown${i}`;
let type = 'void *'; let type = 'void *';
const info = this.#methods[i]; const info = methods[i];
if (info) { if (info) {
name = info.shortName; name = info.shortName;
type = info.getType() + ' '; type = info.getType() + ' ';
@ -112,17 +116,17 @@ export class VTable implements Property {
out += `};\n`; out += `};\n`;
// Sanity Check Size // Sanity Check Size
const isSizeDefined = this.#size.isExact(); if (this.#size !== null) {
const size = isSizeDefined ? this.#size.get() : (this.#methods.length * POINTER_SIZE); out += assertSize(this.#getName(), this.#size);
out += assertSize(this.#getName(), size, isSizeDefined); }
// Pointers // Pointers
if (this.#address !== null) { if (this.#address !== null) {
// Base // Base
out += `extern ${this.#getName()} *${this.#getName()}_base;\n`; out += `extern ${this.#getName()} *${this.#getName()}_base;\n`;
// Methods // Methods
for (let i = 0; i < this.#methods.length; i++) { for (let i = 0; i < methods.length; i++) {
const info = this.#methods[i]; const info = methods[i];
if (info) { if (info) {
const type = `${info.getType()} *`; const type = `${info.getType()} *`;
out += `extern ${type}${info.getName()}_vtable_addr;\n`; out += `extern ${type}${info.getName()}_vtable_addr;\n`;
@ -133,7 +137,7 @@ export class VTable implements Property {
} }
// Duplication Method // Duplication Method
if (isSizeDefined) { if (this.#size !== null) {
out += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable);\n`; out += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable);\n`;
} }
@ -155,8 +159,9 @@ export class VTable implements Property {
init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${toHex(this.#address)};\n`; init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${toHex(this.#address)};\n`;
declarations += `${this.#getName()} *${this.#getName()}_base;\n`; declarations += `${this.#getName()} *${this.#getName()}_base;\n`;
// Methods // Methods
for (let i = 0; i < this.#methods.length; i++) { const methods = this.getMethods();
const info = this.#methods[i]; for (let i = 0; i < methods.length; i++) {
const info = methods[i];
if (info) { if (info) {
const vtableAddress = this.#address + (i * POINTER_SIZE); const vtableAddress = this.#address + (i * POINTER_SIZE);
const type = `${info.getType()} *`; const type = `${info.getType()} *`;
@ -169,7 +174,7 @@ export class VTable implements Property {
} }
// Duplication Method // Duplication Method
if (this.#size.isExact()) { if (this.#size !== null) {
declarations += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`; declarations += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`;
declarations += `${INDENT}uchar *real_vtable = (uchar *) vtable;\n`; declarations += `${INDENT}uchar *real_vtable = (uchar *) vtable;\n`;
declarations += `${INDENT}real_vtable -= ${RTTI_SIZE};\n`; declarations += `${INDENT}real_vtable -= ${RTTI_SIZE};\n`;

View File

@ -15,7 +15,7 @@ color red "^(vtable(-size|-destructor-offset))? .+$"
color normal "(\(|\))" color normal "(\(|\))"
# Commands # 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 # Types
color green "\<((u?(char|short|int))|float|bool|void|std::(string|vector|map))\>" color green "\<((u?(char|short|int))|float|bool|void|std::(string|vector|map))\>"

View File

@ -8,7 +8,6 @@
<item>vtable</item> <item>vtable</item>
<item>property</item> <item>property</item>
<item>static-property</item> <item>static-property</item>
<item>static-property-array</item>
<item>method</item> <item>method</item>
<item>virtual-method</item> <item>virtual-method</item>
<item>static-method</item> <item>static-method</item>