Merge branch 'master' of https://gitea.thebrokenrail.com/minecraft-pi-reborn/symbol-processor
This commit is contained in:
commit
cd20feda59
@ -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);
|
||||||
@ -191,3 +112,12 @@ export function prependArg(args: string, arg: string) {
|
|||||||
}
|
}
|
||||||
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();
|
||||||
|
}
|
15
src/index.ts
15
src/index.ts
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
174
src/property.ts
174
src/property.ts
@ -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`;
|
||||||
}
|
}
|
||||||
}
|
}
|
278
src/struct.ts
278
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, 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.returnType !== this.#name && method.returnType in STRUCTURE_FILES) {
|
||||||
|
this.#addDependency(method.returnType);
|
||||||
|
}
|
||||||
|
if (isVirtual) {
|
||||||
if (method.self !== this.#name) {
|
if (method.self !== this.#name) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
if (isVirtual) {
|
|
||||||
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,138 +89,155 @@ 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');
|
|
||||||
}
|
|
||||||
// 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)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute Padding Between Properties
|
// Add Properties
|
||||||
#computePadding(a: Property | null, b: Property | null) {
|
let out = '';
|
||||||
// Null A = Start Of Structure
|
for (let i = 0; i < sortedProperties.length; i++) {
|
||||||
// Null B = End Of Structure
|
const property = sortedProperties[i]!;
|
||||||
let neededPadding = 0;
|
const lastProperty = sortedProperties[i - 1];
|
||||||
const size = this.getSize(true);
|
|
||||||
if (a === null) {
|
// Padding
|
||||||
// Start Of Structure Padding
|
let offsetFromLastProperty = property.offset;
|
||||||
if (b !== null) {
|
if (lastProperty) {
|
||||||
neededPadding = b.propertyOffset();
|
offsetFromLastProperty -= lastProperty.offset;
|
||||||
} else {
|
|
||||||
// Both A And B Are Null
|
|
||||||
neededPadding = size;
|
|
||||||
}
|
}
|
||||||
} else if (b === null) {
|
let paddingSize = toHex(offsetFromLastProperty);
|
||||||
// End Of Structure Padding
|
if (lastProperty) {
|
||||||
const realSize = this.#getRealSize();
|
paddingSize += ` - sizeof(${lastProperty.type()})`;
|
||||||
const realRoundedSize = this.#roundSize(realSize);
|
|
||||||
if (realRoundedSize !== size) {
|
|
||||||
neededPadding = size - realSize;
|
|
||||||
}
|
}
|
||||||
} else {
|
out += `${INDENT}uchar __padding${i}[${paddingSize}];\n`;
|
||||||
// Inner Structure Padding
|
|
||||||
const realSizeSoFar = a.propertyOffset() + a.propertySize();
|
// The Actual Property
|
||||||
neededPadding = b.propertyOffset() - realSizeSoFar;
|
if (property !== sizeProperty) {
|
||||||
|
out += `${INDENT}${property.type()} ${property.name()};\n`;
|
||||||
}
|
}
|
||||||
if (neededPadding < 0) {
|
|
||||||
throw new Error('Overlapping Properties Detected!');
|
|
||||||
}
|
}
|
||||||
return neededPadding;
|
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 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) {
|
||||||
|
if (!method.isInherited) {
|
||||||
out += method.generateTypedef();
|
out += method.generateTypedef();
|
||||||
out += `extern ${method.generateDefinition()}`;
|
out += `extern ${method.generateDefinition()}`;
|
||||||
out += `extern ${method.generateDefinition('_unedited')}`;
|
out += `extern ${method.generateDefinition('_unedited')}`;
|
||||||
out += method.generateNewMethodTest(this.#directParent, '', '');
|
out += method.generateNewMethodTest(this.#directParent, '', '');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VTable
|
// VTable
|
||||||
if (this.#vtable !== null) {
|
if (this.#vtable !== null) {
|
||||||
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,22 +250,21 @@ 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) {
|
||||||
|
if (!method.isInherited) {
|
||||||
init += `${INDENT}${method.getName()} = (${method.getType()}) ${toHex(method.address)};\n`;
|
init += `${INDENT}${method.getName()} = (${method.getType()}) ${toHex(method.address)};\n`;
|
||||||
declarations += method.generateDefinition();
|
declarations += method.generateDefinition();
|
||||||
init += `${INDENT}${method.getName()}_unedited = (${method.getType()}) ${toHex(method.address)};\n`;
|
init += `${INDENT}${method.getName()}_unedited = (${method.getType()}) ${toHex(method.address)};\n`;
|
||||||
declarations += method.generateDefinition('_unedited');
|
declarations += method.generateDefinition('_unedited');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VTable
|
// VTable
|
||||||
if (this.#vtable !== null) {
|
if (this.#vtable !== null) {
|
||||||
@ -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;
|
||||||
|
109
src/vtable.ts
109
src/vtable.ts
@ -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`;
|
||||||
|
@ -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))\>"
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user