Compare commits

...

7 Commits

11 changed files with 275 additions and 118 deletions

View File

@ -66,14 +66,7 @@ export function toHex(x: number) {
return '0x' + x.toString(16);
}
export function assertSize(name: string, size: number) {
let out = '';
// Define Size Macro
const macro = toUpperSnakeCase(name) + '_SIZE';
out += `#define ${macro} ${toHex(size)}\n`;
// Check Size
out += `static_assert(sizeof(${name}) == ${macro}, "Invalid Size");\n`;
// Return
return out;
return `static_assert(sizeof(${name}) == ${toHex(size)}, "Invalid Size");\n`;
}
export function safeParseInt(str: string) {
const x = parseInt(str);
@ -120,4 +113,7 @@ export function removeFirstArg(args: string) {
index++;
}
return '(' + args.substring(index).trim();
}
}
export const ORIGINAL_SUFFIX = '_backup_do_not_use';
export const VTABLE_ADDR_SUFFIX = `_vtable_addr`;
export const OVERWRITE_TEST_SUFFIX = '__is_overwritable_';

View File

@ -1,5 +1,5 @@
import * as fs from 'node:fs';
import { STRUCTURE_FILES, EXTENSION, INDENT } from './common';
import { STRUCTURE_FILES, EXTENSION } from './common';
import { getStructure } from './map';
import { Struct } from './struct';
@ -130,11 +130,7 @@ function makeHeaderPart() {
}
// Return
let result = '';
result += '// Init\n';
result += 'void init_symbols();\n\n';
result += structures;
return result;
return structures;
}
// Create Main Header
@ -145,10 +141,6 @@ function makeMainHeader(output: string) {
result += '#ifndef __arm__\n';
result += '#error "Symbols Are ARM-Only"\n';
result += '#endif\n';
result += '\n// Shortcuts\n';
result += 'typedef unsigned char uchar;\n';
result += 'typedef unsigned short ushort;\n';
result += 'typedef unsigned int uint;\n';
result += '\n// Forward Declarations\n';
for (const name in STRUCTURE_FILES) {
result += `typedef struct ${name} ${name};\n`;
@ -162,6 +154,12 @@ function makeMainHeader(output: string) {
result += '#include <string>\n';
result += '#include <vector>\n';
result += '#include <map>\n';
result += `#include <type_traits>\n`;
result += `#include <functional>\n`;
result += '\n// Shortcuts\n';
result += 'typedef unsigned char uchar;\n';
result += 'typedef unsigned short ushort;\n';
result += 'typedef unsigned int uint;\n';
result += '\n// Warnings\n';
result += '#pragma GCC diagnostic push\n';
result += '#pragma GCC diagnostic ignored "-Winvalid-offsetof"\n';
@ -180,7 +178,6 @@ function makeCompiledCode(output: string) {
// Generate
let declarations = '';
let init = '';
let isFirst = true;
for (const structure of structureObjects) {
const name = structure.getName();
@ -188,14 +185,11 @@ function makeCompiledCode(output: string) {
isFirst = false;
} else {
declarations += '\n';
init += '\n';
}
declarations += `// ${name}\n`;
init += `${INDENT}// ${name}\n`;
try {
const code = structure.generateCode();
declarations += code.functions;
init += code.init;
declarations += code;
} catch (e) {
console.log(`Error Generating Code: ${name}: ${e instanceof Error ? e.stack : e}`);
process.exit(1);
@ -205,11 +199,7 @@ function makeCompiledCode(output: string) {
// Write
let result = '';
result += `#include "${fs.realpathSync(headerOutput)}"\n`;
result += '\n#include <cstring>\n';
result += '\n// Init\n';
result += 'void init_symbols() {\n';
result += init;
result += '}\n\n';
result += '\n#include <cstring>\n\n';
result += declarations;
fs.writeFileSync(output, result);
}

View File

@ -1,4 +1,4 @@
import { INDENT, formatType, getArgNames } from './common';
import { INDENT, OVERWRITE_TEST_SUFFIX, VTABLE_ADDR_SUFFIX, formatType, getArgNames, prependArg } from './common';
export class Method {
readonly self: string;
@ -29,45 +29,91 @@ export class Method {
getName() {
return this.getNameWithCustomSelf(this.self);
}
getType() {
#getBaseType() {
return `${this.getName()}_t`;
}
getType() {
return `__raw_${this.#getBaseType()}`;
}
// Generate Type Definition
generateTypedef() {
let out = '';
// Normal Definition
const returnType = formatType(this.returnType);
out += `typedef ${returnType}(*${this.getType()})${this.args};\n`;
// Fancy Overwrite Does Not Support Varargs
if (!this.hasVarargs) {
// 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`;
}
out += `typedef ${formatType(this.returnType)}(*${this.getType()})${this.args};\n`;
// Return
return out;
}
// Generate Variable Definition
generateDefinition(nameSuffix?: string) {
return `${this.getType()} ${this.getName()}${nameSuffix !== undefined ? nameSuffix : ''};\n`;
// Overwrite Helper
#hasOverwriteHelper() {
// Fancy Overwrite Does Not Support Varargs
return !this.hasVarargs;
}
// 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})`;
generateOverwriteHelper(code: boolean, isVirtual: boolean) {
let out = '';
if (this.#hasOverwriteHelper()) {
// Add Additional Typedefs
const baseType = this.#getBaseType();
const overwriteType = `__overwrite_${baseType}`;
const functionType = baseType;
const rawType = this.getType();
if (!code) {
out += `typedef std::function<std::remove_pointer<${rawType}>::type> ${functionType};\n`;
out += `typedef std::function<${this.returnType}${prependArg(this.args, functionType + ' original')}> ${overwriteType};\n`;
}
// Thunk
const name = this.getName();
const currentOverwriteName = '__current_overwrite_for_' + name;
const thunkName = '__overwrite_thunk_for_' + name;
if (code) {
out += `static ${functionType} ${currentOverwriteName};\n`;
out += `static ${formatType(this.returnType)}${thunkName}${this.args} {\n`;
out += `${INDENT}${this.returnType.trim() === 'void' ? '' : 'return '}${currentOverwriteName}(${getArgNames(this.args).join(', ')});\n`;
out += `}\n`;
}
// Overwrite Helper
out += `std::string __set_overwrite_for_${name}(const ${overwriteType} &method, const std::function<void *(void *, void *)> &overwriter)`;
if (code) {
out += ' {\n';
if (isVirtual) {
// Check If Method Can Be Overwritten
out += `${INDENT}if (!${OVERWRITE_TEST_SUFFIX}${name}()) {\n`;
out += `${INDENT}${INDENT}return "Unable To Overwrite Virtual Method";\n`;
out += `${INDENT}}\n`;
}
// Redirect Method Calls To Thunk
out += `${INDENT}static bool enabled = false;\n`;
out += `${INDENT}if (!enabled) {\n`;
if (isVirtual) {
// Get Address Of Virtual Function
out += `${INDENT}${INDENT}${rawType} ${name} = *${name}${VTABLE_ADDR_SUFFIX};\n`;
}
out += `${INDENT}${INDENT}${rawType} &original = ${name};\n`;
out += `${INDENT}${INDENT}${currentOverwriteName} = original;\n`;
out += `${INDENT}${INDENT}original = (${rawType}) overwriter((void *) original, (void *) ${thunkName});\n`;
out += `${INDENT}${INDENT}enabled = true;\n`;
out += `${INDENT}}\n`;
// Bind Original Parameter
out += `${INDENT}${functionType} original = ${currentOverwriteName};\n`;
out += `${INDENT}${currentOverwriteName} = [method, original]${this.args} {\n`;
out += `${INDENT}${INDENT}${this.returnType.trim() === 'void' ? '' : 'return '}method(${['original'].concat(getArgNames(this.args)).join(', ')});\n`;
out += `${INDENT}};\n`;
// Return Success
out += `${INDENT}return "";\n`;
out += `}`;
} else {
out += ';';
}
out += '\n';
}
out += ')\n';
return out;
}
// Generate Variable Definition
generateDefinition(nameSuffix?: string) {
return `${this.getType()} ${this.getName()}${nameSuffix !== undefined ? nameSuffix : ''}`;
}
}

View File

@ -34,8 +34,14 @@ export class Property {
}
return name;
}
#fullName(separator: string) {
return this.#self + separator + this.name();
}
fullName() {
return `${this.#self}_${this.name()}`;
return this.#fullName('_');
}
prettyName() {
return this.#fullName('::');
}
rawType() {
return this.#type;
@ -48,13 +54,8 @@ export class StaticProperty extends Property {
super(address, type, name, self);
}
// Generate Variable Definition
globalPointerDefinition() {
return `${this.type()} *${this.fullName()}_pointer;\n`;
}
// Generate Macro
macro() {
return `#define ${this.fullName()} (*${this.fullName()}_pointer)\n`;
// Reference
referenceDefinition(addSelf: boolean) {
return `${this.type()} &${addSelf ? this.prettyName() : this.name()}`;
}
}

View File

@ -1,4 +1,4 @@
import { INDENT, STRUCTURE_FILES, toHex, assertSize, formatType, getArgNames, removeFirstArg } from './common';
import { INDENT, STRUCTURE_FILES, toHex, assertSize, formatType, getArgNames, removeFirstArg, ORIGINAL_SUFFIX } from './common';
import { Method } from './method';
import { Property, StaticProperty } from './property';
import { VTable } from './vtable';
@ -163,7 +163,7 @@ export class Struct {
const shortName = method.shortName;
const fullName = method.getName();
const args = getArgsOuter(method);
out += `${INDENT}inline ${formatType(returnType)}${shortName}${args} { \\\n`;
out += `${INDENT}${method.isStatic ? 'static ' : ''}inline ${formatType(returnType)}${shortName}${args} { \\\n`;
out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}${fullName}(${getArgsInner(method)});\n`;
out += `${INDENT}}\n`;
}
@ -193,22 +193,21 @@ export class Struct {
// Static Properties
for (const property of this.#staticProperties) {
out += property.typedef();
out += `extern ${property.globalPointerDefinition()}`;
out += property.macro();
}
// Methods
for (const method of this.#methods) {
if (!method.isInherited) {
out += method.generateTypedef();
out += `extern ${method.generateDefinition()}`;
out += method.generateNewMethodTest(this.#directParent, '', '');
out += `extern ${method.generateDefinition()};\n`;
out += `extern ${method.generateDefinition(ORIGINAL_SUFFIX)};\n`;
out += method.generateOverwriteHelper(false, false);
}
}
// VTable
if (this.#vtable !== null) {
out += this.#vtable.generate(this.#directParent);
out += this.#vtable.generate();
}
// Property Typedefs
@ -220,6 +219,16 @@ export class Struct {
out += `struct ${this.#name} {\n`;
out += this.#generateProperties();
out += this.#generateMethods();
for (const property of this.#staticProperties) {
// Static Property References
out += `${INDENT}static ${property.referenceDefinition(false)};\n`;
}
if (this.#size === null) {
// Prevent Manually Copying/Allocating Structure With Undefined
out += `${INDENT}${this.#name}() = delete;\n`;
out += `${INDENT}${this.#name}(const ${this.#name} &) = delete;\n`;
out += `${INDENT}${this.#name} &operator=(const ${this.#name} &) = delete;\n`;
}
out += `};\n`;
// Sanity Check Offsets
@ -235,50 +244,36 @@ export class Struct {
out += assertSize(this.#name, this.#size);
}
// Allocation Function
if (this.#size !== null) {
out += `${this.#name} *alloc_${this.#name}();\n`;
}
// Return
return out;
}
// Generate Compiled Code
generateCode() {
let declarations = '';
let init = '';
let out = '';
// Static Properties
for (const property of this.#staticProperties) {
init += `${INDENT}${property.fullName()}_pointer = (${property.type()} *) ${toHex(property.offset)};\n`;
declarations += property.globalPointerDefinition();
out += `${property.referenceDefinition(true)} = *(${property.type()} *) ${toHex(property.offset)};\n`;
}
// Methods
for (const method of this.#methods) {
if (!method.isInherited) {
init += `${INDENT}${method.getName()} = (${method.getType()}) ${toHex(method.address)};\n`;
declarations += method.generateDefinition();
out += `${method.generateDefinition()} = (${method.getType()}) ${toHex(method.address)};\n`;
out += `${method.generateDefinition(ORIGINAL_SUFFIX)} = ${method.getName()};\n`;
out += method.generateOverwriteHelper(true, false);
}
}
// 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 new ${this.#name};\n`;
declarations += '}\n';
const vtable = this.#vtable.generateCode(this.#directParent);
out += vtable;
}
// Return
return {functions: declarations, init};
return out;
}
// Set Direct Parent (Used For "New Method" Testing)

View File

@ -1,7 +1,8 @@
import { INDENT, POINTER_SIZE, RTTI_SIZE, assertSize, getSelfArg, toHex } from './common';
import { INDENT, ORIGINAL_SUFFIX, OVERWRITE_TEST_SUFFIX, POINTER_SIZE, RTTI_SIZE, VTABLE_ADDR_SUFFIX, assertSize, getSelfArg, toHex } from './common';
import { Method } from './method';
import { Property } from './property';
const structuresWithVTableAddress: string[] = [];
export class VTable {
readonly #self: string;
#address: number | null;
@ -24,6 +25,7 @@ export class VTable {
// Setters
setAddress(address: number) {
this.#address = address;
structuresWithVTableAddress.push(this.#self);
}
setSize(size: number) {
this.#size = size;
@ -84,8 +86,33 @@ export class VTable {
return out;
}
// Overwritable Test (Only For Virtual Methods)
generateOverwritableTest(method: Method, parent: string | null) {
// Test If Overwriting Method Would Also Modify Parent
let out = '';
out += `static inline bool ${OVERWRITE_TEST_SUFFIX}${method.getName()}() {\n`;
let ret: string;
if (method.isInherited) {
if (structuresWithVTableAddress.includes(parent!)) {
// Check If Method Differs From Parent
out += `${INDENT}void *parent = (void *) *${method.getNameWithCustomSelf(parent!)}${VTABLE_ADDR_SUFFIX};\n`;
out += `${INDENT}void *child = (void *) *${method.getName()}${VTABLE_ADDR_SUFFIX};\n`;
ret = 'parent != child';
} else {
// Unable To Determine
ret = 'false';
}
} else {
// No Parent
ret = 'true';
}
out += `${INDENT}return ${ret};\n`;
out += '}\n';
return out;
}
// Generate Header Code
generate(directParent: string | null) {
generate() {
let out = '';
// Check
@ -128,9 +155,12 @@ export class VTable {
for (let i = 0; i < methods.length; i++) {
const info = methods[i];
if (info) {
const type = `${info.getType()} *`;
out += `extern ${type}${info.getName()}_vtable_addr;\n`;
out += info.generateNewMethodTest(directParent, '*', '_vtable_addr');
const name = info.getName();
const type = info.getType();
const pointerType = `${type} *`;
out += `extern ${pointerType}${name}${VTABLE_ADDR_SUFFIX};\n`;
out += `extern ${info.generateDefinition(ORIGINAL_SUFFIX)};\n`;
out += info.generateOverwriteHelper(false, true);
}
}
}
@ -145,9 +175,8 @@ export class VTable {
}
// Generate Compiled Code
generateCode() {
let declarations = '';
let init = '';
generateCode(directParent: string | null) {
let out = '';
// Check
this.#check();
@ -155,38 +184,41 @@ export class VTable {
// Pointers
if (this.#address !== null) {
// Base
init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${toHex(this.#address)};\n`;
declarations += `${this.#getName()} *${this.#getName()}_base;\n`;
out += `${this.#getName()} *${this.#getName()}_base = (${this.#getName()} *) ${toHex(this.#address)};\n`;
// Methods
const methods = this.getMethods();
for (let i = 0; i < methods.length; i++) {
const info = methods[i];
if (info) {
const vtableAddress = this.#address + (i * POINTER_SIZE);
const type = `${info.getType()} *`;
init += `${INDENT}${info.getName()}_vtable_addr = (${type}) ${toHex(vtableAddress)};\n`;
declarations += `${type}${info.getName()}_vtable_addr;\n`;
const name = info.getName();
const type = info.getType();
const pointerType = `${type} *`;
out += `${pointerType}${name}${VTABLE_ADDR_SUFFIX} = (${pointerType}) ${toHex(vtableAddress)};\n`;
out += `${info.generateDefinition(ORIGINAL_SUFFIX)} = *${name}${VTABLE_ADDR_SUFFIX};\n`;
out += this.generateOverwritableTest(info, directParent);
out += info.generateOverwriteHelper(true, true);
}
}
}
// Duplication Method
if (this.#size !== null) {
declarations += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`;
declarations += `${INDENT}uchar *real_vtable = (uchar *) vtable;\n`;
declarations += `${INDENT}real_vtable -= ${RTTI_SIZE};\n`;
declarations += `${INDENT}size_t real_vtable_size = sizeof(${this.#getName()}) + ${RTTI_SIZE};\n`;
declarations += `${INDENT}uchar *new_vtable = (uchar *) ::operator new(real_vtable_size);\n`;
declarations += `${INDENT}if (new_vtable == NULL) {\n`;
declarations += `${INDENT}${INDENT}return NULL;\n`;
declarations += `${INDENT}}\n`;
declarations += `${INDENT}memcpy((void *) new_vtable, (void *) real_vtable, real_vtable_size);\n`;
declarations += `${INDENT}new_vtable += ${RTTI_SIZE};\n`;
declarations += `${INDENT}return (${this.#getName()} *) new_vtable;\n`;
declarations += '}\n';
out += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`;
out += `${INDENT}uchar *real_vtable = (uchar *) vtable;\n`;
out += `${INDENT}real_vtable -= ${RTTI_SIZE};\n`;
out += `${INDENT}size_t real_vtable_size = sizeof(${this.#getName()}) + ${RTTI_SIZE};\n`;
out += `${INDENT}uchar *new_vtable = (uchar *) ::operator new(real_vtable_size);\n`;
out += `${INDENT}if (new_vtable == NULL) {\n`;
out += `${INDENT}${INDENT}return NULL;\n`;
out += `${INDENT}}\n`;
out += `${INDENT}memcpy((void *) new_vtable, (void *) real_vtable, real_vtable_size);\n`;
out += `${INDENT}new_vtable += ${RTTI_SIZE};\n`;
out += `${INDENT}return (${this.#getName()} *) new_vtable;\n`;
out += '}\n';
}
// Return
return {declarations, init};
return out;
}
}

View File

@ -13,5 +13,7 @@ color yellow "0x[a-f0-9]+"
# Comments
color brightblue "//.*"
# Whitespace.
color normal "[[:space:]]+"
# Trailing whitespace.
color ,green "[[:space:]]+$"

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Comments</string>
<key>scope</key>
<string>source.toml</string>
<key>settings</key>
<dict>
<key>shellVariables</key>
<array>
<dict>
<key>name</key>
<string>TM_COMMENT_START</string>
<key>value</key>
<string>//</string>
</dict>
</array>
</dict>
<key>uuid</key>
<string>C5C885D7-2733-4632-B709-B5B9DD518F90</string>
</dict>
</plist>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>fileTypes</key>
<array>
<string>def</string>
</array>
<key>scopeName</key>
<string>source.symbol-processor</string>
<key>name</key>
<string>Symbol Processor Definition</string>
<key>patterns</key>
<array>
<dict>
<key>name</key>
<string>comment.line.double-slash.symbol-processor</string>
<key>match</key>
<string>//.*$</string>
</dict>
<dict>
<key>name</key>
<string>constant.numeric.symbol-processor.hex</string>
<key>match</key>
<string>\b0[xX][0-9a-fA-F]+</string>
</dict>
<dict>
<key>name</key>
<string>constant.numeric.symbol-processor.decimal</string>
<key>match</key>
<string>\b[0-9]+</string>
</dict>
<dict>
<key>name</key>
<string>keyword.control.symbol-processor</string>
<key>match</key>
<string>\b(extends|size|vtable-size|vtable|property|static-property|method|virtual-method|static-method|constructor|vtable-destructor-offset)\b</string>
</dict>
<dict>
<key>name</key>
<string>storage.type.symbol-processor</string>
<key>match</key>
<string>\b(char|uchar|short|ushort|int|uint|float|bool|void|std::string|std::vector|std::map)\b</string>
</dict>
<dict>
<key>name</key>
<string>keyword.operator.symbol-processor</string>
<key>match</key>
<string>(=|;)</string>
</dict>
</array>
<key>uuid</key>
<string>D44198D4-5AEB-40E5-B4E4-0E11C69FFA42</string>
</dict>
</plist>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>contactEmailRot13</key>
<string>connor24nolan@live.com</string>
<key>contactName</key>
<string>TheBrokenRail</string>
<key>description</key>
<string>Symbol Processor Definition File</string>
<key>name</key>
<string>Symbol Processor</string>
<key>uuid</key>
<string>8209EEB8-4193-4E63-BDBB-0407E47ADF50</string>
</dict>
</plist>