Fancy C++ Methods!

This commit is contained in:
TheBrokenRail 2024-05-03 22:20:03 -04:00
parent dcc35de95c
commit 6ee26aad80
6 changed files with 94 additions and 41 deletions

View File

@ -112,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();
}

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

@ -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
@ -144,7 +144,7 @@ export function load(target: Struct, name: string, isExtended: boolean) {
} }
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,4 +1,4 @@
import { INDENT, STRUCTURE_FILES, toHex, assertSize } from './common'; import { INDENT, STRUCTURE_FILES, toHex, assertSize, formatType, getArgNames, removeFirstArg } from './common';
import { Method } from './method'; import { Method } from './method';
import { Property, StaticProperty } from './property'; import { Property, StaticProperty } from './property';
import { VTable } from './vtable'; import { VTable } from './vtable';
@ -58,13 +58,19 @@ export class Struct {
// 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);
} }
} }
@ -131,6 +137,55 @@ export class Struct {
return out; 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 = '';
@ -144,10 +199,12 @@ export class Struct {
// 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 += method.generateNewMethodTest(this.#directParent, '', ''); out += method.generateNewMethodTest(this.#directParent, '', '');
} }
}
// VTable // VTable
if (this.#vtable !== null) { if (this.#vtable !== null) {
@ -162,6 +219,7 @@ export class Struct {
// Structure // Structure
out += `struct ${this.#name} {\n`; out += `struct ${this.#name} {\n`;
out += this.#generateProperties(); out += this.#generateProperties();
out += this.#generateMethods();
out += `};\n`; out += `};\n`;
// Sanity Check Offsets // Sanity Check Offsets
@ -199,9 +257,11 @@ export class Struct {
// 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();
} }
}
// VTable // VTable
if (this.#vtable !== null) { if (this.#vtable !== null) {
@ -221,15 +281,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

@ -71,15 +71,15 @@ export class VTable {
} }
// Get Full Methods Table // Get Full Methods Table
#getMethods() { getMethods() {
// Copy Array // Copy Array
const out = []; const out = [];
out.push(...this.#methods); out.push(...this.#methods);
// Add Destructors (https://stackoverflow.com/a/17960941) // Add Destructors (https://stackoverflow.com/a/17960941)
const destructor_return = `${this.#self} *`; const destructor_return = `${this.#self} *`;
const destructor_args = `(${getSelfArg(this.#self)})`; const destructor_args = `(${getSelfArg(this.#self)})`;
this.#add(out, new Method(this.#self, 'destructor_complete', destructor_return, destructor_args, 0x0 + this.#destructorOffset, false)); this.#add(out, new Method(this.#self, 'destructor_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)); this.#add(out, new Method(this.#self, 'destructor_deleting', destructor_return, destructor_args, 0x4 + this.#destructorOffset, false, false));
// Return // Return
return out; return out;
} }
@ -92,7 +92,7 @@ export class VTable {
this.#check(); this.#check();
// Method Prototypes // Method Prototypes
const methods = this.#getMethods(); const methods = this.getMethods();
for (let i = 0; i < methods.length; i++) { for (let i = 0; i < methods.length; i++) {
const info = methods[i]; const info = methods[i];
if (info) { if (info) {
@ -158,7 +158,7 @@ export class VTable {
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
const methods = this.#getMethods(); const methods = this.getMethods();
for (let i = 0; i < methods.length; i++) { for (let i = 0; i < methods.length; i++) {
const info = methods[i]; const info = methods[i];
if (info) { if (info) {