Pure Evil

This commit is contained in:
TheBrokenRail 2024-04-03 03:18:51 -04:00
parent 842f05a288
commit 67c4adaa77
9 changed files with 139 additions and 156 deletions

View File

@ -163,4 +163,31 @@ export class Size {
isExact() { isExact() {
return this.#isExact; return this.#isExact;
} }
}
export function getArgNames(args: string) {
// Remove Parentheses
args = args.substring(1, args.length - 1);
if (args.length === 0) {
return [];
}
// Split
const argsList = args.split(',');
// Parse
const out = [];
for (let arg of argsList) {
arg = arg.trim();
// Remove Type
const nameStart = Math.max(arg.lastIndexOf(' '), arg.lastIndexOf('*')) + 1;
arg = arg.substring(nameStart);
// Collect
out.push(arg);
}
// Return
return out;
}
export function prependArg(args: string, arg: string) {
if (args !== '()') {
arg += ', ';
}
return '(' + arg + args.substring(1);
} }

View File

@ -1,110 +0,0 @@
import { INDENT } from './common';
import { SimpleProperty } from './property';
// List Of Supported C++ Types
const CPP_TYPES = [
'std::string',
'std::vector<',
'std::map<'
];
const CPP_HEADERS = [
'string',
'vector',
'map'
];
// Get Fake Type Name
function isGeneric(type: string) {
return type.endsWith('<');
}
function removeGeneric(type: string) {
if (isGeneric(type)) {
// Remove Generic Parameters
type = type.substring(0, type.length - 1);
}
return type;
}
function getFakeName(type: string) {
return removeGeneric(type).replaceAll('std::', '_std_');
}
// Generate C Type Definition
function generateStruct(type: string) {
// Get Information
const property = new SimpleProperty(0, type, 'fake');
const size = property.propertySize();
const alignment = property.propertyAlignment();
// Generate
let struct = '';
struct += `typedef struct __attribute__((aligned(${alignment}))) __attribute__((packed)) {\n`;
struct += `${INDENT}uchar data[${size}];\n`;
struct += `} ${getFakeName(type)};\n`;
return struct;
}
// Turn A C++ Header Into A C/C++ Headeer
export function fixupCpp(data: string) {
// Replace Types
for (const type of CPP_TYPES) {
// Check If Type Is Generic
if (isGeneric(type)) {
// Generic
while (data.includes(type)) {
// Find Type
let index = data.indexOf(type);
// Replace Type
data = data.substring(0, index) + getFakeName(type) + '(' + data.substring(index + type.length);
// Replace > With (
index = data.indexOf('>', index);
if (index !== -1) {
data = data.substring(0, index) + ')' + data.substring(index + 1);
}
}
} else {
// Normal
data = data.replaceAll(type, getFakeName(type));
}
}
// Setup Macros
let header = '';
header += '// C++ Types\n';
// C++ Support
header += '#ifdef __cplusplus\n';
for (const headerFile of CPP_HEADERS) {
header += `#include <${headerFile}>\n`;
}
for (const type of CPP_TYPES) {
const fakeName = getFakeName(type);
if (isGeneric(type)) {
header += `#define ${fakeName}(...) ${type}__VA_ARGS__>\n`;
} else {
header += `#define ${fakeName} ${type}\n`;
}
}
// C Support
header += '#else\n';
for (let type of CPP_TYPES) {
const fakeName = getFakeName(type);
header += generateStruct(type);
// Strip Generic Information
if (isGeneric(type)) {
type = removeGeneric(type);
header += `#define ${fakeName}(...) ${fakeName}\n`;
}
}
header += '#endif\n';
// Cleanup Macros
let footer = '';
footer += '// Cleanup C++ Types\n';
for (const type of CPP_TYPES) {
const fakeName = getFakeName(type);
footer += `#undef ${fakeName}\n`;
}
// Return
return header + '\n' + data + '\n' + footer;
}

View File

@ -2,7 +2,6 @@ import * as fs from 'node:fs';
import { STRUCTURE_FILES, EXTENSION, INDENT } from './common'; import { STRUCTURE_FILES, EXTENSION, INDENT } from './common';
import { getStructure } from './map'; import { getStructure } from './map';
import { Struct } from './struct'; import { Struct } from './struct';
import { fixupCpp } from './cpp';
// Arguments // Arguments
if (process.argv.length < 5) { if (process.argv.length < 5) {
@ -158,25 +157,19 @@ function makeMainHeader(output: string) {
for (const file of extraHeaders) { for (const file of extraHeaders) {
result += fs.readFileSync(file, {encoding: 'utf8'}); result += fs.readFileSync(file, {encoding: 'utf8'});
} }
let core = ''; result += '\n// Headers\n';
core += '// C/C++ Support\n'; result += '#include <cstddef>\n';
core += '#ifndef __cplusplus\n'; result += '#include <string>\n';
core += '#include <stddef.h>\n'; result += '#include <vector>\n';
core += '#include <assert.h>\n'; result += '#include <map>\n';
core += 'typedef uchar bool;\n'; result += '\n// Warnings\n';
core += '#else\n'; result += '#pragma GCC diagnostic push\n';
core += '#include <cstddef>\n'; result += '#pragma GCC diagnostic ignored "-Winvalid-offsetof"\n\n';
core += 'extern "C" {\n'; result += makeHeaderPart();
core += '#pragma GCC diagnostic push\n'; result += '\n// Cleanup Warnings\n';
core += '#pragma GCC diagnostic ignored "-Winvalid-offsetof"\n'; result += '#pragma GCC diagnostic pop\n';
core += '#endif\n\n'; result += '\n// Array Of All Method Symbols\n';
core += makeHeaderPart(); result += 'extern void *_all_method_symbols[];\n';
core += '\n// Cleanup C++ Support\n';
core += '#ifdef __cplusplus\n';
core += '#pragma GCC diagnostic pop\n';
core += '}\n';
core += '#endif\n';
result += '\n' + fixupCpp(core);
fs.writeFileSync(output, result); fs.writeFileSync(output, result);
} }
makeMainHeader(headerOutput); makeMainHeader(headerOutput);
@ -219,6 +212,16 @@ 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,4 +1,4 @@
import { COMMENT, EXTENSION, getSelfArg, parseTypeAndName, 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 { SimpleProperty, StaticProperty } from './property';
import { Struct } from './struct'; import { Struct } from './struct';
@ -27,7 +27,7 @@ function parseProperty(args: string) {
} }
// Parse Method // Parse Method
function parseMethod(args: string, self: string, insertSelfArg: boolean) { function parseMethod(args: string, self: string, insertSelfArg: boolean, isInherited: boolean) {
const argsStart = args.indexOf('('); const argsStart = args.indexOf('(');
if (argsStart === -1) { if (argsStart === -1) {
syntaxError('Cannot Find Arguments'); syntaxError('Cannot Find Arguments');
@ -43,14 +43,11 @@ function parseMethod(args: string, self: string, insertSelfArg: boolean) {
syntaxError('Invalid Method Arguments'); syntaxError('Invalid Method Arguments');
} }
if (insertSelfArg) { if (insertSelfArg) {
let selfArg = `(${getSelfArg(self)}`; const selfArg = getSelfArg(self);
if (methodArgs !== '()') { methodArgs = prependArg(methodArgs, selfArg);
selfArg += ', ';
}
methodArgs = selfArg + methodArgs.substring(1);
} }
const address = safeParseInt(end[1]!); const address = safeParseInt(end[1]!);
return new Method(self, name, type, methodArgs, address); return new Method(self, name, type, methodArgs, address, isInherited);
} }
// Load Structure // Load Structure
@ -103,6 +100,9 @@ export function load(target: Struct, name: string, isExtended: boolean) {
switch (command) { switch (command) {
case 'extends': { case 'extends': {
// Load Parent // Load Parent
if (!isExtended) {
target.setDirectParent(args);
}
load(target, args, true); load(target, args, true);
break; break;
} }
@ -148,20 +148,20 @@ export function load(target: Struct, name: string, isExtended: boolean) {
} }
case 'method': { case 'method': {
// Add Method // Add Method
const method = parseMethod(args, target.getName(), true); const method = parseMethod(args, target.getName(), true, isExtended);
target.addMethod(method, false); target.addMethod(method, false);
break; break;
} }
case 'virtual-method': { case 'virtual-method': {
// Add Virtual Method // Add Virtual Method
const method = parseMethod(args, target.getName(), true); const method = parseMethod(args, target.getName(), true, isExtended);
target.addMethod(method, true); target.addMethod(method, true);
break; break;
} }
case 'static-method': { case 'static-method': {
// Add Static Method // Add Static Method
if (!isExtended) { if (!isExtended) {
const method = parseMethod(args, target.getName(), false); const method = parseMethod(args, target.getName(), false, false);
target.addMethod(method, false); target.addMethod(method, false);
} }
break; break;
@ -178,11 +178,16 @@ export function load(target: Struct, name: string, isExtended: boolean) {
data += '_'; data += '_';
} }
data += args; data += args;
const method = parseMethod(data, target.getName(), true); const method = parseMethod(data, target.getName(), true, false);
target.addMethod(method, false); target.addMethod(method, false);
} }
break; break;
} }
case 'vtable-destructor-offset': {
// Set VTable Destructor Offset
target.setVTableDestructorOffset(safeParseInt(args));
break;
}
default: { default: {
throw new Error(`Invalid Command: ${command}`); throw new Error(`Invalid Command: ${command}`);
} }

View File

@ -1,4 +1,4 @@
import { formatType } from './common'; import { INDENT, formatType, getArgNames } from './common';
export class Method { export class Method {
readonly self: string; readonly self: string;
@ -6,19 +6,24 @@ export class Method {
readonly returnType: string; readonly returnType: string;
readonly args: string; readonly args: string;
readonly address: number; readonly address: number;
readonly isInherited: boolean;
// Constructor // Constructor
constructor(self: string, name: string, returnType: string, args: string, address: number) { constructor(self: string, name: string, returnType: string, args: string, address: number, isInherited: 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;
} }
// Get Type // Get Type
getNameWithCustomSelf(self: string) {
return `${self}_${this.shortName}`;
}
getName() { getName() {
return `${this.self}_${this.shortName}`; return this.getNameWithCustomSelf(this.self);
} }
getType() { getType() {
return `${this.getName()}_t`; return `${this.getName()}_t`;
@ -26,12 +31,39 @@ export class Method {
// Generate Type Definition // Generate Type Definition
generateTypedef() { generateTypedef() {
let out = '';
// Normal Definition
const returnType = formatType(this.returnType); const returnType = formatType(this.returnType);
return `typedef ${returnType}(*${this.getType()})${this.args};\n`; out += `typedef ${returnType}(*${this.getType()})${this.args};\n`;
// Fancy Overwrite Does Not Support Varargs
if (!this.args.includes('...')) {
// Overwrite Helper
out += `#define _overwrite_helper_for_${this.getName()}(method, original) \\\n`;
out += `${INDENT}[]${this.args} { \\\n`;
out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}method(${['original'].concat(getArgNames(this.args)).join(', ')}); \\\n`;
out += `${INDENT}}\n`;
}
// Return
return out;
} }
// Generate Variable Definition // Generate Variable Definition
generateDefinition(nameSuffix?: string) { generateDefinition(nameSuffix?: string) {
return `${this.getType()} ${this.getName()}${nameSuffix !== undefined ? nameSuffix : ''};\n`; return `${this.getType()} ${this.getName()}${nameSuffix !== undefined ? nameSuffix : ''};\n`;
} }
// Generate "New Method" Test
generateNewMethodTest(parent: string | null, prefix: string, suffix: string) {
let out = `#define _is_new_method_${this.getName()}() (`;
if (!this.isInherited) {
out += 'true';
} else {
out += `((void *) ${prefix}${this.getName()}${suffix}) != ((void *) ${prefix}${this.getNameWithCustomSelf(parent!)}${suffix})`;
}
out += ')\n';
return out;
}
} }

View File

@ -11,6 +11,8 @@ export class Struct {
readonly #size: Size; readonly #size: Size;
readonly #dependencies: string[]; readonly #dependencies: string[];
readonly #staticProperties: StaticProperty[]; readonly #staticProperties: StaticProperty[];
#directParent: string | null;
#vtableDestructorOffset: number;
// Constructor // Constructor
constructor(name: string) { constructor(name: string) {
@ -21,6 +23,8 @@ export class Struct {
this.#size = new Size(false); this.#size = new Size(false);
this.#dependencies = []; this.#dependencies = [];
this.#staticProperties = []; this.#staticProperties = [];
this.#directParent = null;
this.#vtableDestructorOffset = 0;
} }
// Dependencies // Dependencies
@ -34,11 +38,19 @@ 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.#vtable = new VTable(this.#name, this.#vtableDestructorOffset);
this.addProperty(this.#vtable); this.addProperty(this.#vtable);
} }
} }
// Set VTable Destructor Offset
setVTableDestructorOffset(offset: number) {
if (this.#vtable) {
throw new Error('VTable Already Created');
}
this.#vtableDestructorOffset = offset;
}
// Setters // Setters
setSize(size: number, isExact: boolean) { setSize(size: number, isExact: boolean) {
this.#size.set(size, isExact); this.#size.set(size, isExact);
@ -200,11 +212,12 @@ export class Struct {
for (const method of this.#methods) { for (const method of this.#methods) {
out += method.generateTypedef(); out += method.generateTypedef();
out += `extern ${method.generateDefinition()}`; out += `extern ${method.generateDefinition()}`;
out += method.generateNewMethodTest(this.#directParent, '', '');
} }
// VTable // VTable
if (this.#vtable !== null) { if (this.#vtable !== null) {
out += this.#vtable.generate(); out += this.#vtable.generate(this.#directParent);
} }
// Structure // Structure
@ -294,4 +307,18 @@ export class Struct {
// Return // Return
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)
setDirectParent(directParent: string) {
this.#directParent = directParent;
}
} }

View File

@ -9,7 +9,7 @@ export class VTable implements Property {
readonly #methods: Method[]; readonly #methods: Method[];
// Constructor // Constructor
constructor(name: string) { constructor(name: string, destructorOffset: number) {
this.#name = name; this.#name = name;
this.#address = null; this.#address = null;
this.#size = new Size(true); this.#size = new Size(true);
@ -17,8 +17,8 @@ export class VTable implements Property {
// Add Destructors (https://stackoverflow.com/a/17960941) // Add Destructors (https://stackoverflow.com/a/17960941)
const destructor_return = `${name} *`; const destructor_return = `${name} *`;
const destructor_args = `(${getSelfArg(name)})`; const destructor_args = `(${getSelfArg(name)})`;
this.add(new Method(name, 'destructor_complete', destructor_return, destructor_args, 0x0)); 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)); this.add(new Method(name, 'destructor_deleting', destructor_return, destructor_args, 0x4 + destructorOffset, false));
} }
// Property Information // Property Information
@ -82,7 +82,7 @@ export class VTable implements Property {
} }
// Generate Code // Generate Code
generate() { generate(directParent: string | null) {
let out = ''; let out = '';
// Check // Check
@ -126,7 +126,7 @@ export class VTable implements Property {
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`;
out += `extern ${info.generateDefinition('_non_virtual')}`; out += info.generateNewMethodTest(directParent, '*', '_vtable_addr');
} }
} }
} }
@ -161,8 +161,6 @@ export class VTable implements Property {
const type = `${info.getType()} *`; const type = `${info.getType()} *`;
init += `${INDENT}${info.getName()}_vtable_addr = (${type}) ${toHex(vtableAddress)};\n`; init += `${INDENT}${info.getName()}_vtable_addr = (${type}) ${toHex(vtableAddress)};\n`;
declarations += `${type}${info.getName()}_vtable_addr;\n`; declarations += `${type}${info.getName()}_vtable_addr;\n`;
init += `${INDENT}${info.getName()}_non_virtual = *${info.getName()}_vtable_addr;\n`;
declarations += info.generateDefinition('_non_virtual');
} }
} }
} }

View File

@ -2,7 +2,7 @@ syntax def "\.def$"
comment "//" comment "//"
# Commands # Commands
color magenta "\<(extends|size|vtable(-size)?|property|static-property(-array)?|((static|virtual)-)?method|constructor)\>" color magenta "\<(extends|size|vtable(-size|-destructor-offset)?|property|static-property(-array)?|((static|virtual)-)?method|constructor)\>"
# Types # Types
color green "\<(char|uchar|short|ushort|int|uint|float|bool|void|std::(string|vector|map))\>" color green "\<(char|uchar|short|ushort|int|uint|float|bool|void|std::(string|vector|map))\>"

View File

@ -13,6 +13,7 @@
<item>virtual-method</item> <item>virtual-method</item>
<item>static-method</item> <item>static-method</item>
<item>constructor</item> <item>constructor</item>
<item>vtable-destructor-offset</item>
</list> </list>
<list name="types"> <list name="types">
<item>char</item> <item>char</item>