diff --git a/data/function.h b/data/function.h new file mode 100644 index 0000000..bb26b05 --- /dev/null +++ b/data/function.h @@ -0,0 +1,116 @@ +#include +#include + +// Information Interface +template +class __FunctionInfo { + typedef Ret (*type)(Args...); +public: + [[nodiscard]] virtual bool can_overwrite() const = 0; + [[nodiscard]] virtual type get() const = 0; + [[nodiscard]] virtual type *get_addr() const = 0; + virtual void update(type new_func) = 0; +}; + +// Thunks +typedef void *(*thunk_enabler_t)(void *target, void *thunk); +extern thunk_enabler_t thunk_enabler; + +// Function +template +class __Function; +template +class __Function final { + // Prevent Copying + __PREVENT_COPY(__Function); + __PREVENT_DESTRUCTION(__Function); + + // Instance + static __Function *instance; + + // Current Function + typedef __FunctionInfo *func_t; + const func_t func; + +public: + // Types + typedef Ret (*ptr_type)(Args...); + typedef std::function type; + typedef std::function overwrite_type; + + // State + const bool enabled; + const char *const name; + + // Backup Of Original Function Pointer + const ptr_type backup; + +#ifdef {{ BUILDING_SYMBOLS_GUARD }} + // Constructor + __Function(const char *const name_, const func_t func_): + func(func_), + enabled(func->can_overwrite()), + name(name_), + backup(func->get()) + { + instance = this; + } +#else + // Prevent Construction + __PREVENT_JUST_CONSTRUCTION(__Function); +#endif + + // Overwrite Function + [[nodiscard]] bool overwrite(const overwrite_type &target) { + // Check If Enabled + if (!enabled) { + return false; + } + // Enable Thunk + enable_thunk(); + // Overwrite + type original = get_thunk_target(); + thunk_target = [original, target](Args... args) { + return target(original, std::forward(args)...); + }; + return true; + } + + // Getters + [[nodiscard]] ptr_type get(const bool result_will_be_stored) { + if (!enabled) { + return nullptr; + } else { + if (result_will_be_stored) { + enable_thunk(); + } + return func->get(); + } + } + [[nodiscard]] ptr_type *get_vtable_addr() const { + return func->get_addr(); + } + +private: + // Thunk + [[nodiscard]] type get_thunk_target() const { + if (thunk_target) { + return thunk_target; + } else { + return backup; + } + } + static Ret thunk(Args... args) { + return instance->get_thunk_target()(std::forward(args)...); + } + // Enable Thunk + type thunk_target; + bool thunk_enabled = false; + void enable_thunk() { + if (!thunk_enabled && enabled) { + ptr_type real_thunk = (ptr_type) thunk_enabler((void *) backup, (void *) thunk); + func->update(real_thunk); + thunk_enabled = true; + } + } +}; \ No newline at end of file diff --git a/data/internal.h b/data/internal.h new file mode 100644 index 0000000..2befb23 --- /dev/null +++ b/data/internal.h @@ -0,0 +1,10 @@ +#define __PREVENT_DESTRUCTION(self) \ + ~self() = delete +#define __PREVENT_JUST_CONSTRUCTION(self) \ + self() = delete +#define __PREVENT_CONSTRUCTION(self) \ + __PREVENT_JUST_CONSTRUCTION(self); \ + __PREVENT_DESTRUCTION(self) +#define __PREVENT_COPY(self) \ + self(const self &) = delete; \ + self &operator=(const self &) = delete \ No newline at end of file diff --git a/data/out.cpp b/data/out.cpp index 282ca35..46e248b 100644 --- a/data/out.cpp +++ b/data/out.cpp @@ -1,10 +1,66 @@ -#define LEAN_SYMBOLS_HEADER +#define {{ BUILDING_SYMBOLS_GUARD }} #include "{{ headerPath }}" -// Thunk Template -template -decltype(auto) __thunk(auto... args) { - return (*func)->get_thunk_target()(std::forward(args)...); -} +// Global Instance +template +__Function *__Function::instance; + +// Normal Function Information +template +class __NormalFunctionInfo final : public __FunctionInfo { + typedef Ret (*type)(Args...); + type func; +public: + // Constructor + explicit __NormalFunctionInfo(const type func_): + func(func_) {} + // Functions + [[nodiscard]] bool can_overwrite() const override { + return true; + } + [[nodiscard]] type get() const override { + return func; + } + [[nodiscard]] type *get_addr() const override { + return nullptr; + } + void update(const type new_func) override { + func = new_func; + } +}; + +// Virtual Function Information +template +class __VirtualFunctionInfo final : public __FunctionInfo { + typedef Ret (*type)(Args...); + type *const addr; + void *const parent; +public: + // Constructor + __VirtualFunctionInfo(type *const addr_, void *const parent_): + addr(addr_), + parent(parent_) {} + // Functions + [[nodiscard]] bool can_overwrite() const override { + // If this function's address matches its parent's, + // then it was just inherited and does not actually exist. + // Overwriting this function would also overwrite its parent + // which would cause undesired behavior. + return get() != parent; + } + [[nodiscard]] type get() const override { + return *get_addr(); + } + [[nodiscard]] type *get_addr() const override { + return addr; + } + void update(const type new_func) override { + // Address Should Have Already Been Updated + if (get() != new_func) { + __builtin_trap(); + } + } +}; +#undef super {{ main }} \ No newline at end of file diff --git a/data/out.h b/data/out.h index 30b2561..ab181bb 100644 --- a/data/out.h +++ b/data/out.h @@ -5,153 +5,19 @@ #error "Symbols Are ARM-Only" #endif +// Internal Macros +{{ include internal.h }} + +// Function Object +{{ include function.h }} + // Headers -#include -#include #include #include #include #include -#include #include -// Internal Macros -#define __PREVENT_DESTRUCTION(self) \ - ~self() = delete -#define __PREVENT_CONSTRUCTION(self) \ - self() = delete; \ - __PREVENT_DESTRUCTION(self) -#define __PREVENT_COPY(self) \ - self(const self &) = delete; \ - self &operator=(const self &) = delete - -// Virtual Function Information -struct __VirtualFunctionInfo { - // Constructors - template - __VirtualFunctionInfo(Ret (**const addr_)(Self, Args...), Ret (*const parent_)(Super, Args...)): - addr((void **) addr_), - parent((void *) parent_) {} - template - __VirtualFunctionInfo(T **const addr_, const std::nullptr_t parent_): - __VirtualFunctionInfo(addr_, (T *) parent_) {} - // Method - [[nodiscard]] bool can_overwrite() const { - return *addr != parent; - } - // Properties - void **const addr; - void *const parent; -}; - -// Thunks -typedef void *(*thunk_enabler_t)(void *target, void *thunk); -extern thunk_enabler_t thunk_enabler; - -// Function Information -template -class __Function; -template -class __Function { - // Prevent Copying - __PREVENT_COPY(__Function); - __PREVENT_DESTRUCTION(__Function); - -public: - // Types - typedef Ret (*ptr_type)(Args...); - typedef std::function type; - typedef std::function overwrite_type; - - // Normal Function - __Function(const std::string name_, const ptr_type thunk_, const ptr_type func_): - func(func_), - enabled(true), - name(name_), - backup(func_), - thunk(thunk_) {} - // Virtual Function - template - __Function(const std::string name_, const ptr_type thunk_, ptr_type *const func_, const Parent parent): - func(__VirtualFunctionInfo(func_, parent)), - enabled(std::get<__VirtualFunctionInfo>(func).can_overwrite()), - name(name_), - backup(*get_vtable_addr()), - thunk(thunk_) {} - - // Overwrite Function - [[nodiscard]] bool overwrite(overwrite_type target) { - // Check If Enabled - if (!enabled) { - return false; - } - // Enable Thunk - enable_thunk(); - // Overwrite - type original = get_thunk_target(); - thunk_target = [original, target](Args... args) { - return target(original, std::forward(args)...); - }; - return true; - } - - // Getters - [[nodiscard]] ptr_type get(bool result_will_be_stored) { - if (!enabled) { - return nullptr; - } else { - if (result_will_be_stored) { - enable_thunk(); - } - if (is_virtual()) { - return *get_vtable_addr(); - } else { - return std::get(func); - } - } - } - [[nodiscard]] ptr_type *get_vtable_addr() const { - return (ptr_type *) std::get<__VirtualFunctionInfo>(func).addr; - } - [[nodiscard]] type get_thunk_target() const { - if (thunk_target) { - return thunk_target; - } else { - return backup; - } - } - -private: - // Current Function - std::variant func; - [[nodiscard]] bool is_virtual() const { - return func.index() == 1; - } - -public: - // State - const bool enabled; - const std::string name; - - // Backup Of Original Function Pointer - const ptr_type backup; - -private: - // Thunk - const ptr_type thunk; - type thunk_target; - bool thunk_enabled = false; - void enable_thunk() { - if (!thunk_enabled && enabled) { - ptr_type real_thunk = (ptr_type) thunk_enabler((void *) backup, (void *) thunk); - if (!is_virtual()) { - func = real_thunk; - } - thunk_enabled = true; - } - } -}; - // Shortcuts typedef unsigned char uchar; typedef unsigned short ushort; diff --git a/src/common.ts b/src/common.ts index 99c0ca7..8ab0e69 100644 --- a/src/common.ts +++ b/src/common.ts @@ -9,7 +9,7 @@ export const EXTENSION = '.def'; export const STRUCTURE_FILES: Record = {}; export const COMMENT = '//'; export const INTERNAL = '__'; -export const LEAN_HEADER_GUARD = '#ifndef LEAN_SYMBOLS_HEADER\n'; +export const BUILDING_SYMBOLS_GUARD = 'BUILDING_SYMBOLS_LIB'; // Read Definition File export function readDefinition(name: string) { if (!STRUCTURE_FILES[name]) { @@ -117,20 +117,23 @@ export function getDataDir() { return path.join(__dirname, '..', 'data'); } // Format File From Data Directory -export function formatFile(file: string, options: Record) { - // Include Other Files +export function formatFile(file: string, options: Record, includeOtherFiles = true) { + const newOptions = Object.assign({}, options); const dataDir = getDataDir(); - const otherFiles = fs.readdirSync(dataDir); - for (let otherFile of otherFiles) { - otherFile = path.join(dataDir, otherFile); - options[`include ${otherFile}`] = fs.readFileSync(otherFile, 'utf8'); + // Include Other Files + if (includeOtherFiles) { + const otherFiles = fs.readdirSync(dataDir); + for (const otherFile of otherFiles) { + newOptions[`include ${otherFile}`] = formatFile(otherFile, options, false); + } } // Format file = path.join(dataDir, file); let data = fs.readFileSync(file, 'utf8'); - for (const key in options) { - const value = options[key]; + for (const key in newOptions) { + let value = newOptions[key]; if (value) { + value = value.trim(); data = data.replace(`{{ ${key} }}`, value); } } diff --git a/src/index.ts b/src/index.ts index 15e2b7f..4426c0c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { STRUCTURE_FILES, EXTENSION, formatFile, getDataDir, extendErrorMessage } from './common'; +import { STRUCTURE_FILES, EXTENSION, formatFile, getDataDir, extendErrorMessage, BUILDING_SYMBOLS_GUARD } from './common'; import { getStructure } from './map'; import { Struct } from './struct'; @@ -41,7 +41,7 @@ while (process.argv.length > 0) { const fullName = file.base; const name = file.name; // Store - if (name in STRUCTURE_FILES) { + if (STRUCTURE_FILES[name]) { throw new Error(`Multiple Definition Files Provided: ${fullName}`); } STRUCTURE_FILES[name] = filePath; @@ -129,7 +129,7 @@ function makeHeaderPart() { try { structures += structure.generate(); } catch (e) { - throw new Error(extendErrorMessage(e, 'Error Generating Header: ' + name)); + throw new Error(extendErrorMessage(e, 'Generating Header: ' + name)); } structures += '\n'; } @@ -155,7 +155,7 @@ function makeMainHeader(output: string) { // Main const main = makeHeaderPart().trim(); // Write - const result = formatFile('out.h', {forwardDeclarations, extraHeaders, main, data: getDataDir()}); + const result = formatFile('out.h', {BUILDING_SYMBOLS_GUARD, forwardDeclarations, extraHeaders, main, data: getDataDir()}); fs.writeFileSync(output, result); } makeMainHeader(headerOutput); @@ -182,14 +182,14 @@ function makeCompiledCode(outputDir: string) { try { declarations += structure.generateCode().trim(); } catch (e) { - throw new Error(extendErrorMessage(e, 'Error Generating Code: ' + name)); + throw new Error(extendErrorMessage(e, 'Generating Code: ' + name)); } declarations += '\n'; // Write const headerPath = fs.realpathSync(headerOutput); const main = declarations.trim(); - const result = formatFile('out.cpp', {headerPath, main, data: getDataDir()}); + const result = formatFile('out.cpp', {BUILDING_SYMBOLS_GUARD, headerPath, main, data: getDataDir()}); const output = path.join(outputDir, name + '.cpp'); fs.writeFileSync(output, result); } diff --git a/src/method.ts b/src/method.ts index 4953d14..dcc9a44 100644 --- a/src/method.ts +++ b/src/method.ts @@ -1,6 +1,11 @@ import { INDENT, INTERNAL, formatType, toHex } from './common'; +// A Template Parameter So Each Template Instantiation Is Unique +let nextDiscriminator = 0; + +// An Individual Method export class Method { + readonly #discriminator: number; readonly self: string; readonly shortName: string; readonly returnType: string; @@ -11,6 +16,7 @@ export class Method { // Constructor constructor(self: string, name: string, returnType: string, args: string, address: number, isInherited: boolean, isStatic: boolean) { + this.#discriminator = nextDiscriminator++; this.self = self; this.shortName = name; this.returnType = returnType; @@ -34,7 +40,7 @@ export class Method { return `${INDENT}${this.#getRawType()} *${this.shortName};\n`; } #getFullType() { - return `${INTERNAL}Function<${this.#getRawType()}>`; + return `${INTERNAL}Function<${this.#discriminator.toString()}, ${this.#getRawType()}>`; } // Typedefs @@ -52,16 +58,14 @@ export class Method { generate(code: boolean, isVirtual: boolean, parentSelf?: string) { let out = ''; out += 'extern '; - const type = this.#getFullType(); - out += `${type} *const ${this.getName()}`; + out += `${this.#getFullType()} *const ${this.getName()}`; if (code) { - out += ` = new ${type}(${JSON.stringify(this.getName('::'))}, `; - out += `${INTERNAL}thunk<&${this.getName()}>, `; + out += ` = new ${this.#getFullType()}(${JSON.stringify(this.getName('::'))}, `; if (isVirtual) { const parentMethod = parentSelf ? this.#getVirtualCall(parentSelf) : 'nullptr'; - out += `&${this.#getVirtualCall()}, ${parentMethod}`; + out += `new ${INTERNAL}VirtualFunctionInfo(&${this.#getVirtualCall()}, (void *) ${parentMethod})`; } else { - out += `(${this.#getRawType()} *) ${toHex(this.address)}`; + out += `new ${INTERNAL}NormalFunctionInfo((${this.#getRawType()} *) ${toHex(this.address)})`; } out += ')'; } diff --git a/src/struct.ts b/src/struct.ts index 7d6f97f..a4047bb 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -1,4 +1,4 @@ -import { INDENT, STRUCTURE_FILES, toHex, assertSize, INTERNAL, preventConstruction, LEAN_HEADER_GUARD } from './common'; +import { INDENT, STRUCTURE_FILES, toHex, assertSize, INTERNAL, preventConstruction, BUILDING_SYMBOLS_GUARD } from './common'; import { Method } from './method'; import { Property, StaticProperty } from './property'; import { VTable } from './vtable'; @@ -71,7 +71,7 @@ export class Struct { this.#properties.push(property); // Add Dependency If Needed const type = property.rawType(); - if (type in STRUCTURE_FILES) { + if (STRUCTURE_FILES[type]) { this.#addDependency(type); } } @@ -165,7 +165,7 @@ export class Struct { } #generateMethods() { let out = ''; - out += LEAN_HEADER_GUARD; + out += `#ifndef ${BUILDING_SYMBOLS_GUARD}\n`; // Normal Methods for (const method of this.#methods) { out += this.#generateMethod(method, false); @@ -174,7 +174,9 @@ export class Struct { if (this.#vtable !== null) { const virtualMethods = this.#vtable.getMethods(); for (const method of virtualMethods) { - out += this.#generateMethod(method, true); + if (method) { + out += this.#generateMethod(method, true); + } } } // Allocation Method @@ -223,7 +225,7 @@ export class Struct { } } out += typedefs; - out += LEAN_HEADER_GUARD; + out += `#ifndef ${BUILDING_SYMBOLS_GUARD}\n`; out += methodsOut; out += '#endif\n'; diff --git a/src/vtable.ts b/src/vtable.ts index 6e40aff..89b8e2b 100644 --- a/src/vtable.ts +++ b/src/vtable.ts @@ -1,4 +1,4 @@ -import { INDENT, LEAN_HEADER_GUARD, POINTER_SIZE, assertSize, getSelfArg, preventConstruction, toHex } from './common'; +import { BUILDING_SYMBOLS_GUARD, INDENT, POINTER_SIZE, assertSize, getSelfArg, preventConstruction, toHex } from './common'; import { Method } from './method'; import { Property } from './property'; @@ -7,9 +7,10 @@ export class VTable { readonly #self: string; #address: number | null; #size: number | null; - readonly #methods: Method[]; + readonly #methods: (Method | undefined)[]; readonly property: Property; #destructorOffset: number; + readonly #destructors: Method[]; // Constructor constructor(self: string) { @@ -18,6 +19,7 @@ export class VTable { this.#size = null; this.#methods = []; this.#destructorOffset = 0; + this.#destructors = []; // Create Property this.property = new Property(0, this.#getName() + ' *', 'vtable', this.#self); } @@ -35,7 +37,7 @@ export class VTable { } // Add To VTable - #add(target: Method[], method: Method) { + #add(target: (Method | undefined)[], method: Method) { // Check Offset const offset = method.address; if ((offset % POINTER_SIZE) !== 0) { @@ -43,7 +45,7 @@ export class VTable { } // Add const index = offset / POINTER_SIZE; - if (index in target) { + if (target[index]) { throw new Error(`Duplicate Virtual Method At Offset: ${toHex(offset)}`); } target[index] = method; @@ -80,8 +82,15 @@ export class VTable { // 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)); + if (this.#destructors.length === 0) { + this.#destructors.push( + new Method(this.#self, 'destructor_complete', destructor_return, destructor_args, 0x0 + 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) { + this.#add(out, destructor); + } // Return return out; } @@ -119,13 +128,15 @@ export class VTable { let typedefs = ''; let methodsOut = ''; for (const info of methods) { - typedefs += info.generateTypedefs(); - if (this.canGenerateWrappers()) { - methodsOut += info.generate(false, true); + if (info) { + typedefs += info.generateTypedefs(); + if (this.canGenerateWrappers()) { + methodsOut += info.generate(false, true); + } } } out += typedefs; - out += LEAN_HEADER_GUARD; + out += `#ifndef ${BUILDING_SYMBOLS_GUARD}\n`; out += methodsOut; out += '#endif\n'; @@ -176,7 +187,9 @@ export class VTable { if (this.canGenerateWrappers()) { const methods = this.getMethods(); for (const info of methods) { - out += info.generate(true, true, this.#getParentSelf(info, directParent)); + if (info) { + out += info.generate(true, true, this.#getParentSelf(info, directParent)); + } } }