2024-01-04 17:00:19 -05:00
|
|
|
import { INDENT, MIN_SIZE, toUpperSnakeCase, formatType, STRUCTURE_FILES } from './common';
|
2024-01-04 15:27:02 -05:00
|
|
|
import { isCppAllowed } from './map';
|
|
|
|
import { Method } from './method';
|
|
|
|
import { Property, StaticProperty } from './property';
|
|
|
|
import { VTable } from './vtable';
|
|
|
|
|
|
|
|
export class Struct {
|
|
|
|
readonly #name: string;
|
|
|
|
#vtable: VTable | null;
|
|
|
|
readonly #methods: Method[];
|
|
|
|
readonly #properties: Property[];
|
|
|
|
#size: number | null;
|
|
|
|
readonly #dependencies: string[];
|
|
|
|
readonly #staticProperties: StaticProperty[];
|
|
|
|
|
|
|
|
// Constructor
|
|
|
|
constructor(name: string) {
|
|
|
|
this.#name = name;
|
|
|
|
this.#methods = [];
|
|
|
|
this.#properties = [];
|
|
|
|
this.#vtable = null;
|
|
|
|
this.#size = null;
|
|
|
|
this.#dependencies = [];
|
|
|
|
this.#staticProperties = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dependencies
|
2024-01-04 17:00:19 -05:00
|
|
|
#addDependency(dependency: string) {
|
2024-01-04 15:27:02 -05:00
|
|
|
this.#dependencies.push(dependency);
|
|
|
|
}
|
|
|
|
getDependencies() {
|
|
|
|
return this.#dependencies;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure VTable Exists
|
|
|
|
#ensureVTable() {
|
|
|
|
if (this.#vtable === null) {
|
|
|
|
this.#vtable = new VTable(this.#name);
|
|
|
|
this.addProperty(this.#vtable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setters
|
|
|
|
setSize(size: number) {
|
|
|
|
this.#size = size;
|
|
|
|
}
|
|
|
|
// Getters
|
|
|
|
#roundSize(size: number) {
|
|
|
|
const alignment = this.getAlignment();
|
|
|
|
return Math.ceil(size / alignment) * alignment;
|
|
|
|
}
|
|
|
|
getSize() {
|
|
|
|
let size;
|
|
|
|
if (this.#size !== null) {
|
|
|
|
size = this.#size;
|
|
|
|
} else {
|
|
|
|
size = MIN_SIZE;
|
|
|
|
for (const property of this.#properties) {
|
|
|
|
const newSize = property.propertyOffset() + property.propertySize();
|
|
|
|
if (newSize > size) {
|
|
|
|
size = newSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
if (method.self !== this.#name) {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
if (isVirtual) {
|
|
|
|
this.#ensureVTable();
|
|
|
|
this.#vtable!.add(method);
|
|
|
|
} else {
|
|
|
|
this.#methods.push(method);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add Property
|
|
|
|
addProperty(property: Property) {
|
|
|
|
this.#properties.push(property);
|
2024-01-04 17:00:19 -05:00
|
|
|
// Add Dependency If Needed
|
|
|
|
const type = property.propertyType();
|
|
|
|
if (type in STRUCTURE_FILES) {
|
|
|
|
this.#addDependency(type);
|
|
|
|
}
|
2024-01-04 15:27:02 -05:00
|
|
|
}
|
|
|
|
// Add Static Property
|
|
|
|
addStaticProperty(property: StaticProperty) {
|
|
|
|
this.#staticProperties.push(property);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure VTable
|
|
|
|
setVTableSize(size: number) {
|
|
|
|
this.#ensureVTable();
|
|
|
|
this.#vtable!.setSize(size);
|
|
|
|
}
|
|
|
|
setVTableAddress(address: number) {
|
|
|
|
this.#ensureVTable();
|
|
|
|
this.#vtable!.setAddress(address);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check
|
|
|
|
#check() {
|
|
|
|
// Sort Properties
|
|
|
|
this.#properties.sort((a, b) => a.propertyOffset() - b.propertyOffset());
|
|
|
|
|
|
|
|
// Check Size
|
|
|
|
const size = this.getSize();
|
|
|
|
if (this.#size !== null) {
|
|
|
|
// Check Alignment
|
|
|
|
if (size !== this.#size) {
|
|
|
|
throw new Error('Size Misaligned');
|
|
|
|
}
|
|
|
|
// Check If Size Is Too Small
|
|
|
|
const lastProperty = this.#properties[this.#properties.length - 1];
|
|
|
|
if (lastProperty) {
|
|
|
|
let realSize = lastProperty.propertyOffset() + lastProperty.propertySize();
|
|
|
|
realSize = this.#roundSize(realSize);
|
|
|
|
if (realSize > this.#size) {
|
|
|
|
throw new Error(`Structure Size Too Small: ${this.#size}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate Header
|
|
|
|
generate() {
|
|
|
|
let out = '';
|
|
|
|
|
|
|
|
// Check
|
|
|
|
this.#check();
|
|
|
|
|
|
|
|
// Static Properties
|
|
|
|
for (const property of this.#staticProperties) {
|
|
|
|
out += `extern ${property.generateDefinition()}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Methods
|
|
|
|
for (const method of this.#methods) {
|
|
|
|
out += method.generateTypedef();
|
|
|
|
out += `extern ${method.generateDefinition()}`;
|
|
|
|
}
|
|
|
|
|
2024-01-05 15:47:53 -05:00
|
|
|
// Early Exit For Undefined Structures
|
|
|
|
if (this.#properties.length === 0 && this.#size === null) {
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2024-01-04 15:27:02 -05:00
|
|
|
// VTable
|
|
|
|
if (this.#vtable !== null) {
|
|
|
|
out += this.#vtable.generate();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Structure
|
|
|
|
out += `struct ${this.#name} {\n`;
|
|
|
|
for (let i = 0; i <= this.#properties.length; i++) {
|
|
|
|
const property = this.#properties[i];
|
|
|
|
|
|
|
|
// Padding
|
|
|
|
const lastProperty = this.#properties[i - 1];
|
|
|
|
let neededPadding = 0;
|
|
|
|
if (i === 0) {
|
|
|
|
// Start Of Structure Padding
|
|
|
|
if (property) {
|
|
|
|
neededPadding = property.propertyOffset();
|
|
|
|
} else if (this.#properties.length === 0) {
|
|
|
|
if (this.#size !== null) {
|
|
|
|
neededPadding = this.#size;
|
|
|
|
} else {
|
|
|
|
neededPadding = MIN_SIZE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (i === this.#properties.length) {
|
|
|
|
// End Of Structure Padding
|
|
|
|
if (this.#size !== null && lastProperty) {
|
|
|
|
const realSize = lastProperty.propertyOffset() + lastProperty.propertySize();
|
|
|
|
neededPadding = this.#size - realSize;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Inner Structure Padding
|
|
|
|
if (property && lastProperty) {
|
|
|
|
const realSize = lastProperty.propertyOffset() + lastProperty.propertySize();
|
|
|
|
neededPadding = property.propertyOffset() - realSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (neededPadding > 0) {
|
|
|
|
out += `${INDENT}uchar padding${i}[${neededPadding}];\n`;
|
|
|
|
} else if (neededPadding < 0) {
|
|
|
|
throw new Error('Overlapping properties detected!');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Property
|
|
|
|
if (property) {
|
|
|
|
// 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`;
|
|
|
|
|
|
|
|
// Sanity Check Offsets
|
|
|
|
const assertFunction = isCppAllowed() ? 'static_assert' : '_Static_assert';
|
|
|
|
for (let i = 0; i < this.#properties.length; i++) {
|
|
|
|
const property = this.#properties[i]!;
|
|
|
|
const name = property.propertyName();
|
|
|
|
const offset = property.propertyOffset();
|
|
|
|
out += `${assertFunction}(offsetof(${this.#name}, ${name}) == ${offset}, "Invalid Offset");\n`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sanity Check Size
|
|
|
|
const size = this.getSize();
|
|
|
|
out += `#define ${toUpperSnakeCase(this.#name)}_SIZE ${size}\n`;
|
|
|
|
out += `${assertFunction}(sizeof (${this.#name}) == ${toUpperSnakeCase(this.#name)}_SIZE, "Invalid Structure Size");\n`;
|
|
|
|
if (this.#size === null) {
|
|
|
|
// Hide Structure Size As The Real Size Is Unknown
|
|
|
|
out += `#undef ${toUpperSnakeCase(this.#name)}_SIZE\n`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocation Function
|
|
|
|
if (this.#size !== null) {
|
|
|
|
out += `${this.#name} *alloc_${this.#name}();\n`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate Compiled Code
|
|
|
|
generateCode() {
|
|
|
|
let declarations = '';
|
|
|
|
let init = '';
|
|
|
|
|
|
|
|
// Check
|
|
|
|
this.#check();
|
|
|
|
|
|
|
|
// Static Properties
|
|
|
|
for (const property of this.#staticProperties) {
|
|
|
|
init += `${INDENT}${property.getName()} = (${property.getType()}) ${property.address};\n`;
|
|
|
|
declarations += property.generateDefinition();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Methods
|
|
|
|
for (const method of this.#methods) {
|
|
|
|
init += `${INDENT}${method.getName()} = (${method.getType()}) ${method.address};\n`;
|
|
|
|
declarations += method.generateDefinition();
|
|
|
|
}
|
|
|
|
|
|
|
|
// VTable
|
|
|
|
if (this.#vtable !== null) {
|
|
|
|
const vtable = this.#vtable.generateCode();
|
|
|
|
declarations += vtable.declarations;
|
|
|
|
init += vtable.init;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocation Function
|
|
|
|
if (this.#size !== null) {
|
|
|
|
declarations += `${this.#name} *alloc_${this.#name}() {\n`;
|
|
|
|
declarations += `${INDENT}return (${this.#name} *) ::operator new(${this.#size});\n`;
|
|
|
|
declarations += '}\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return
|
|
|
|
return {functions: declarations, init};
|
|
|
|
}
|
|
|
|
}
|