Auto-Generate Custom Wrappers

This commit is contained in:
TheBrokenRail 2025-04-02 03:28:16 -04:00
parent fd720d2321
commit 4018ff0fb9
12 changed files with 291 additions and 23 deletions

94
data/custom.h Normal file
View File

@ -0,0 +1,94 @@
// Get Custom Data
template <typename Data>
Data *custom_get(typename Data::__Self *self) {
return (Data *) (self + 1);
}
// Duplicate VTable
template <typename T>
T *__dup_vtable(const T *vtable) {
// Get Size
constexpr size_t rtti_size = sizeof(void *);
const unsigned char *const real_vtable = ((const unsigned char *) vtable) - rtti_size;
constexpr size_t real_vtable_size = sizeof(T) + rtti_size;
// Allocate
unsigned char *const new_vtable = (unsigned char *) ::operator new(real_vtable_size);
T *ret = (T *) (new_vtable + rtti_size);
new (ret) T;
// Copy
memcpy(new_vtable, real_vtable, real_vtable_size);
// Return
return ret;
}
// Base
template <typename A, typename B>
struct __CustomBase {
// Properties
typedef A __Self;
typedef B __VTable;
__Self *const self;
// Prevent Copying
__PREVENT_COPY(__CustomBase);
#define get_self(ptr) (((__Self *) ptr) - 1)
#ifdef {{ BUILDING_SYMBOLS_GUARD }}
// Constructor
__CustomBase():
self(get_self(this)) {}
#else
// Prevent Construction
__PREVENT_JUST_CONSTRUCTION(__CustomBase);
#endif
// Destructor
virtual ~__CustomBase() = default;
// Allocation
void *operator new(const size_t count) {
const size_t size = sizeof(__Self) + count;
__Self *out = (__Self *) ::operator new(size);
return (void *) custom_get<__CustomBase>(out);
}
void *operator new[](size_t) = delete;
// Deallocation
void operator delete(__CustomBase *ptr, std::destroying_delete_t) {
if (ptr) {
__Self *self = ptr->self;
// This Calls The Actual Destructor
self->vtable->destructor_deleting(self);
}
}
void operator delete(void *ptr) {
if (ptr) {
// Issue During Construction, Just Free Memory
::operator delete(get_self(ptr));
}
}
#undef get_self
void operator delete[](void *) = delete;
protected:
// VTable
virtual void __patch_vtable(__VTable *vtable) {
// Patch Destructors
#define patch(type) \
vtable->type = [](__Self *self) { \
custom_get<__CustomBase>(self)->~__CustomBase(); \
return __VTable::base->type(self); \
}
patch(destructor_complete);
patch(destructor_deleting);
#undef patch
}
void __set_vtable() {
static __VTable *vtable = nullptr;
if (!vtable) {
vtable = __dup_vtable(__VTable::base);
__patch_vtable(vtable);
}
this->self->vtable = vtable;
}
};

View File

@ -65,5 +65,6 @@ public:
// Disable Warnings // Disable Warnings
#pragma GCC diagnostic ignored "-Winvalid-offsetof" #pragma GCC diagnostic ignored "-Winvalid-offsetof"
#pragma GCC diagnostic ignored "-Wshadow"
{{ main }} {{ main }}

View File

@ -21,6 +21,13 @@
#include <map> #include <map>
#include <cstring> #include <cstring>
// Warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
// Custom Wrappers
{{ include custom.h }}
// Shortcuts // Shortcuts
typedef unsigned char uchar; typedef unsigned char uchar;
typedef unsigned short ushort; typedef unsigned short ushort;
@ -32,10 +39,6 @@ typedef unsigned int uint;
// Extra Headers // Extra Headers
{{ extraHeaders }} {{ extraHeaders }}
// Warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
{{ main }} {{ main }}
// Cleanup Warnings // Cleanup Warnings

View File

@ -10,6 +10,8 @@ export const STRUCTURE_FILES: Record<string, string> = {};
export const COMMENT = '//'; export const COMMENT = '//';
export const INTERNAL = '__'; export const INTERNAL = '__';
export const BUILDING_SYMBOLS_GUARD = 'BUILDING_SYMBOLS_LIB'; export const BUILDING_SYMBOLS_GUARD = 'BUILDING_SYMBOLS_LIB';
export const CONSTRUCTOR_FUNCTION_NAME = 'constructor';
export const SELF_ARG = 'self';
// Read Definition File // Read Definition File
export function readDefinition(name: string) { export function readDefinition(name: string) {
if (!STRUCTURE_FILES[name]) { if (!STRUCTURE_FILES[name]) {
@ -115,7 +117,7 @@ export function safeParseInt(str: string) {
} }
// Generate "Self" Argument For Functions // Generate "Self" Argument For Functions
export function getSelfArg(type: string) { export function getSelfArg(type: string) {
return `${type} *self`; return type + ' *' + SELF_ARG;
} }
// Prepend Argument To Function // Prepend Argument To Function
export function prependArg(args: string, arg: string) { export function prependArg(args: string, arg: string) {

128
src/custom.ts Normal file
View File

@ -0,0 +1,128 @@
import { CONSTRUCTOR_FUNCTION_NAME, formatType, forwardArguments, INDENT, INTERNAL, SELF_ARG } from './common';
import { Struct } from './struct';
import { structuresWithVTableAddress } from './vtable';
// Allow Making Custom Structures
export class CustomWrapperGenerator {
readonly #struct: Struct;
// Constructor
constructor(struct: Struct) {
this.#struct = struct;
}
// Getter
#getBase() {
return `${INTERNAL}CustomBase<${this.#struct.name}, ${this.#struct.getVTable().getName()}>`;
}
#getMethods() {
const out = [];
out.push(this.#struct.getMainConstructor());
const methods = this.#struct.getVTable().getMethods(false);
for (const method of methods) {
if (method) {
out.push(method);
}
}
return out;
}
#getName() {
return 'Custom' + this.#struct.name;
}
// Generate Header
generate() {
let out = '';
// Check
if (!structuresWithVTableAddress.includes(this.#struct.name)) {
throw new Error('Generating A Custom Wrapper Requires The VTable Address To Be Specified');
}
// Structure
const base = this.#getBase();
out += `struct ${this.#getName()} : ${base} {\n`;
// Methods
const methods = this.#getMethods();
for (const method of methods) {
// Generate Method
const name = method.shortName;
out += INDENT;
if (name === CONSTRUCTOR_FUNCTION_NAME) {
out += 'explicit ' + this.#getName();
} else {
out += 'virtual ' + formatType(method.returnType) + name;
}
out += method.getArgs(false) + ';\n';
}
// VTable
out += 'private:\n';
out += INDENT + `virtual void ${INTERNAL}patch_vtable(${base}::${INTERNAL}VTable *) override;\n`;
out += '};\n';
// Return
return out;
}
// Generate Code
generateCode() {
let out = '';
// Methods
const methods = this.#getMethods();
for (const method of methods) {
const name = method.shortName;
const isConstructor = name === CONSTRUCTOR_FUNCTION_NAME;
// Generate Method
if (!isConstructor) {
out += formatType(method.returnType);
}
out += this.#getName() + '::';
out += isConstructor ? this.#getName() : name;
const args = method.getArgs(false);
out += args + ' {\n';
out += INDENT;
const forwardedArgs = forwardArguments(args, ['this->self']);
if (!isConstructor) {
// Virtual Method
if (method.doesReturnValue()) {
out += 'return ';
}
out += `${INTERNAL}VTable::base->${name}${forwardedArgs}`;
} else {
// Constructor
out += method.getDirectCall() + forwardedArgs;
}
out += ';\n';
if (isConstructor) {
out += `${INDENT}${INTERNAL}set_vtable();\n`;
}
out += '}\n';
}
// VTable
const base = this.#getBase();
out += `void ${this.#getName()}::${INTERNAL}patch_vtable(${base}::${INTERNAL}VTable *vtable) {\n`;
out += `${INDENT}${base}::${INTERNAL}patch_vtable(vtable);\n`;
for (const method of methods) {
const name = method.shortName;
if (name === CONSTRUCTOR_FUNCTION_NAME) {
continue;
}
// Patch Entry
out += `${INDENT}vtable->${name} = []${method.getArgs()} {\n`;
out += INDENT + INDENT;
if (method.doesReturnValue()) {
out += 'return ';
}
out += `custom_get<${this.#getName()}>(${SELF_ARG})->${name}${forwardArguments(method.getArgs(false), [])};\n`;
out += INDENT + '};\n';
}
out += '}\n';
// Return
return out;
}
};

View File

@ -1,4 +1,4 @@
import { COMMENT, extendErrorMessage, EXTENSION, parseTypeAndName, readDefinition, safeParseInt, syntaxError } from './common'; import { COMMENT, CONSTRUCTOR_FUNCTION_NAME, extendErrorMessage, EXTENSION, parseTypeAndName, readDefinition, safeParseInt, syntaxError } from './common';
import { Method } from './method'; import { Method } from './method';
import { Property, StaticProperty } from './property'; import { Property, StaticProperty } from './property';
import { Struct } from './struct'; import { Struct } from './struct';
@ -166,7 +166,7 @@ export function load(target: Struct, name: string, isExtended: boolean) {
case 'constructor': { case 'constructor': {
// Constructor // Constructor
if (!isExtended) { if (!isExtended) {
let data = `${target.name} *constructor`; let data = target.name + ' *' + CONSTRUCTOR_FUNCTION_NAME;
if (args.startsWith('(')) { if (args.startsWith('(')) {
// No Custom Name // No Custom Name
} else { } else {
@ -192,6 +192,13 @@ export function load(target: Struct, name: string, isExtended: boolean) {
target.markAsSimple(); target.markAsSimple();
break; break;
} }
case 'generate-custom-wrapper': {
// Custom Wrapper
if (!isExtended) {
target.enableCustomWrapper();
}
break;
}
default: { default: {
throw new Error(`Invalid Command: ${command}`); throw new Error(`Invalid Command: ${command}`);
} }

View File

@ -53,6 +53,9 @@ export class Method {
} }
return args; return args;
} }
getDirectCall() {
return this.getName() + '->get(false)';
}
// Typedefs // Typedefs
generateTypedefs() { generateTypedefs() {

View File

@ -1,4 +1,5 @@
import { INDENT, STRUCTURE_FILES, toHex, assertSize, INTERNAL, preventConstruction, BUILDING_SYMBOLS_GUARD, formatType, forwardArguments } from './common'; import { INDENT, STRUCTURE_FILES, toHex, assertSize, INTERNAL, preventConstruction, BUILDING_SYMBOLS_GUARD, formatType, forwardArguments, CONSTRUCTOR_FUNCTION_NAME } from './common';
import { CustomWrapperGenerator } from './custom';
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';
@ -14,6 +15,7 @@ export class Struct {
readonly #staticProperties: StaticProperty[]; readonly #staticProperties: StaticProperty[];
#directParent: string | null; #directParent: string | null;
#isSimple: boolean; #isSimple: boolean;
#custom: CustomWrapperGenerator | null;
// Constructor // Constructor
constructor(name: string) { constructor(name: string) {
@ -26,6 +28,7 @@ export class Struct {
this.#staticProperties = []; this.#staticProperties = [];
this.#directParent = null; this.#directParent = null;
this.#isSimple = false; this.#isSimple = false;
this.#custom = null;
} }
// Dependencies // Dependencies
@ -98,6 +101,20 @@ export class Struct {
} }
} }
// "Custom" Wrapper
enableCustomWrapper() {
this.getVTable();
this.#custom = new CustomWrapperGenerator(this);
}
getMainConstructor() {
for (const method of this.#methods) {
if (method.shortName === CONSTRUCTOR_FUNCTION_NAME) {
return method;
}
}
throw new Error('Unable To Find Main Constructor');
}
// Generate Properties // Generate Properties
#generateProperties() { #generateProperties() {
// Sort Properties // Sort Properties
@ -160,7 +177,7 @@ export class Struct {
if (isVirtual) { if (isVirtual) {
out += `this->vtable->${method.shortName}`; out += `this->vtable->${method.shortName}`;
} else { } else {
out += `${method.getName()}->get(false)`; out += method.getDirectCall();
} }
const extra = []; const extra = [];
if (!method.isStatic) { if (!method.isStatic) {
@ -240,6 +257,11 @@ export class Struct {
} }
out += '};\n'; out += '};\n';
// Generate Custom Wrapper
if (this.#custom !== null) {
out += this.#custom.generate();
}
// Return // Return
return out; return out;
} }
@ -278,6 +300,11 @@ export class Struct {
out += vtable; out += vtable;
} }
// Generate Custom Wrapper
if (this.#custom !== null) {
out += this.#custom.generateCode();
}
// Return // Return
return out; return out;
} }

View File

@ -3,7 +3,7 @@ import { Method } from './method';
import { Property } from './property'; import { Property } from './property';
// A VTable // A VTable
const structuresWithVTableAddress: string[] = []; export const structuresWithVTableAddress: string[] = [];
export class VTable { export class VTable {
readonly #self: string; readonly #self: string;
#address: number | null; #address: number | null;
@ -22,7 +22,7 @@ export class VTable {
this.#destructorOffset = 0; this.#destructorOffset = 0;
this.#destructors = []; this.#destructors = [];
// Create Property // Create Property
this.property = new Property(0, this.#getName() + ' *', 'vtable', this.#self); this.property = new Property(0, this.getName() + ' *', 'vtable', this.#self);
} }
// Setters // Setters
@ -56,7 +56,7 @@ export class VTable {
} }
// Get Structure Name // Get Structure Name
#getName() { getName() {
return this.#self + '_vtable'; return this.#self + '_vtable';
} }
@ -76,7 +76,7 @@ export class VTable {
} }
// Get Full Methods Table // Get Full Methods Table
getMethods() { getMethods(includeDestructors = true) {
// Copy Array // Copy Array
const out = []; const out = [];
out.push(...this.#methods); out.push(...this.#methods);
@ -89,8 +89,10 @@ export class VTable {
new Method(this.#self, 'destructor_deleting', destructor_return, destructor_args, 0x4 + this.#destructorOffset, false, false) new Method(this.#self, 'destructor_deleting', destructor_return, destructor_args, 0x4 + this.#destructorOffset, false, false)
); );
} }
for (const destructor of this.#destructors) { if (includeDestructors) {
this.#add(out, destructor); for (const destructor of this.#destructors) {
this.#add(out, destructor);
}
} }
// Return // Return
return out; return out;
@ -142,8 +144,8 @@ export class VTable {
out += '#endif\n'; out += '#endif\n';
// Structure // Structure
out += `typedef struct ${this.#getName()} ${this.#getName()};\n`; out += `typedef struct ${this.getName()} ${this.getName()};\n`;
out += `struct ${this.#getName()} final {\n`; out += `struct ${this.getName()} final {\n`;
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) {
@ -154,11 +156,11 @@ export class VTable {
} }
if (this.#size === null) { if (this.#size === null) {
// Prevent Construction // Prevent Construction
out += preventConstruction(this.#getName()); out += preventConstruction(this.getName());
} }
if (this.#address !== null) { if (this.#address !== null) {
// Base // Base
out += `${INDENT}static ${this.#getName()} *base;\n`; out += `${INDENT}static ${this.getName()} *base;\n`;
} }
out += '};\n'; out += '};\n';
@ -176,7 +178,7 @@ export class VTable {
// Pointers // Pointers
if (this.#address !== null) { if (this.#address !== null) {
// Base // Base
out += `${this.#getName()} *${this.#getName()}::base = (${this.#getName()} *) ${toHex(this.#address)};\n`; out += `${this.getName()} *${this.getName()}::base = (${this.getName()} *) ${toHex(this.#address)};\n`;
} }
// Method Wrappers // Method Wrappers
@ -191,7 +193,7 @@ export class VTable {
// Sanity Check Size // Sanity Check Size
if (this.#size !== null) { if (this.#size !== null) {
out += assertSize(this.#getName(), this.#size); out += assertSize(this.getName(), this.#size);
} }
// Return // Return

View File

@ -14,6 +14,7 @@
<item>constructor</item> <item>constructor</item>
<item>vtable-destructor-offset</item> <item>vtable-destructor-offset</item>
<item>mark-as-simple</item> <item>mark-as-simple</item>
<item>generate-custom-wrapper</item>
</list> </list>
<list name="types"> <list name="types">
<item>const</item> <item>const</item>

View File

@ -2,7 +2,7 @@ syntax def "\.def$"
comment "//" comment "//"
# Commands # Commands
color magenta "\<(extends|size|vtable(-size|-destructor-offset)?|property|static-property|((static|virtual)-)?method|constructor|mark-as-simple)\>" color magenta "\<(extends|size|vtable(-size|-destructor-offset)?|property|static-property|((static|virtual)-)?method|constructor|mark-as-simple|generate-custom-wrapper)\>"
# 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))\>"

View File

@ -34,7 +34,7 @@
<key>name</key> <key>name</key>
<string>keyword.control.symbol-processor</string> <string>keyword.control.symbol-processor</string>
<key>match</key> <key>match</key>
<string>\b(extends|size|vtable-size|vtable-destructor-offset|vtable|property|static-property|method|virtual-method|static-method|constructor|mark-as-simple)\b</string> <string>\b(extends|size|vtable-size|vtable-destructor-offset|vtable|property|static-property|method|virtual-method|static-method|constructor|mark-as-simple|generate-custom-wrapper)\b</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>