/** * @file hook.h * @brief Hook authoring API: macros, typed imports, RAII helpers * * @copyright Copyright (c) 2026 Tom Clay * All rights reserved. * * @license BSD 3-Clause License * See LICENSE file in the project root for full license text. */ #pragma once #include #include #include #include #include #include #include #include #include #include //============================================================================= // Hook Macros //============================================================================= // Hook a recompiled function with an auto-marshaled native C++ function. // The native function uses plain types (u32, mapped_u32, etc.) and // HostToGuestFunction handles register translation automatically. #ifdef REXGLUE_ENABLE_PROFILING #include #define REX_HOOK(subroutine, function) \ extern "C" REX_FUNC(subroutine) { \ ZoneNamedN(___tracy_hook_zone, #subroutine, true); \ rex::ppc::HostToGuestFunction(ctx, base); \ } #else #define REX_HOOK(subroutine, function) \ extern "C" REX_FUNC(subroutine) { \ rex::ppc::HostToGuestFunction(ctx, base); \ } #endif // Define a raw hook with direct ctx/base access. #define REX_HOOK_RAW(name) extern "C" REX_FUNC(name) // Stub: logs a warning when called. #define REX_STUB(subroutine) \ extern "C" REX_FUNC(subroutine) { \ (void)base; \ REXKRNL_WARN("{} STUB", #subroutine); \ } #define REX_STUB_LOG(subroutine, msg) \ extern "C" REX_FUNC(subroutine) { \ (void)base; \ REXKRNL_WARN("{} STUB - {}", #subroutine, msg); \ } #define REX_STUB_RETURN(subroutine, value) \ extern "C" REX_FUNC(subroutine) { \ (void)base; \ REXKRNL_WARN("{} STUB - returning {:#x}", #subroutine, static_cast(value)); \ ctx.r3.u64 = (value); \ } // Export: hook + register in global registry for kernel ordinal lookup. #define REX_EXPORT(name, function) \ REX_HOOK(name, function) \ static rex::ppc::detail::PPCFuncRegistrar _ppc_reg_##name(#name, &name); #define REX_EXPORT_STUB(name) \ REX_STUB(name) \ static rex::ppc::detail::PPCFuncRegistrar _ppc_reg_##name(#name, &name); #define REX_EXPORT_STUB_RETURN(name, retval) \ REX_STUB_RETURN(name, retval) \ static rex::ppc::detail::PPCFuncRegistrar _ppc_reg_##name(#name, &name); namespace rex { //============================================================================= // CallFrame - Lightweight isolated calling context //============================================================================= // For side calls that must not disturb the outer hook's register state. // Stack-allocated, NOT zero-initialized. Only copies the registers a callee // actually needs from the parent context. struct CallFrame { PPCContext ctx; PPCContext& parent_; explicit CallFrame(PPCContext& parent) : parent_(parent) { ctx.r1 = parent.r1; ctx.r13 = parent.r13; ctx.fpscr = parent.fpscr; } ~CallFrame() { parent_.fpscr = ctx.fpscr; } operator PPCContext&() { return ctx; } CallFrame(const CallFrame&) = delete; CallFrame& operator=(const CallFrame&) = delete; }; } // namespace rex namespace rex::ppc { //============================================================================= // ImportFunction - Zero-overhead typed callable for recompiled functions //============================================================================= template struct ImportFunction; template struct ImportFunction { PPCFunc& fn; R operator()(PPCContext& ctx, uint8_t* base, Args... args) const { auto tpl = std::make_tuple(args...); _translate_args_to_guest(ctx, base, tpl); fn(ctx, base); if constexpr (std::is_void_v) { return; } else if constexpr (is_precise_v) { return static_cast(ctx.f1.f64); } else { return static_cast(ctx.r3.u64); } } R operator()(rex::CallFrame& frame, uint8_t* base, Args... args) const { return (*this)(frame.ctx, base, args...); } /// Auto-isolating call: retrieves ctx/base internally, creates isolated /// context with 0x70 frame, calls function, returns result. R operator()(Args... args) const { auto* ts = rex::runtime::ThreadState::Get(); PPCContext* parentCtx = ts->context(); auto* ks = rex::system::kernel_state(); uint8_t* base = ks->memory()->virtual_membase(); PPCContext ctx{}; ctx.r1 = parentCtx->r1; ctx.r1.u32 -= 0x70; ctx.r13 = parentCtx->r13; ctx.fpscr = parentCtx->fpscr; auto tpl = std::make_tuple(args...); _translate_args_to_guest(ctx, base, tpl); fn(ctx, base); parentCtx->fpscr = ctx.fpscr; if constexpr (std::is_void_v) { return; } else if constexpr (is_precise_v) { return static_cast(ctx.f1.f64); } else { return static_cast(ctx.r3.u64); } } }; } // namespace rex::ppc namespace rex { //============================================================================= // StackFrame - RAII guest stack allocation //============================================================================= class [[deprecated("Use rex::ppc::stack_push / stack_guard instead")]] StackFrame { PPCContext& ctx_; u32 size_; public: explicit StackFrame(PPCContext& ctx, u32 size) : ctx_(ctx), size_(size) { ctx_.r1.u32 -= size; } ~StackFrame() { ctx_.r1.u32 += size_; } u32 addr() const { return ctx_.r1.u32; } u32 addr(u32 offset) const { return ctx_.r1.u32 + offset; } void write_string(u32 offset, const char* str, u8* base) const { std::memcpy(rex::memory::GuestPtr(base, addr(offset)), str, std::strlen(str) + 1); } void write(u32 offset, const void* data, size_t len, u8* base) const { std::memcpy(rex::memory::GuestPtr(base, addr(offset)), data, len); } template size_t write_fmt(u32 offset, u8* base, fmt::format_string fmtstr, A&&... args) const { char* dst = rex::memory::GuestPtr(base, addr(offset)); auto result = fmt::format_to_n(dst, size_ - offset - 1, fmtstr, std::forward(args)...); *result.out = '\0'; return result.size; } StackFrame(const StackFrame&) = delete; StackFrame& operator=(const StackFrame&) = delete; }; } // namespace rex //============================================================================= // REX_IMPORT - Typed callable import of a recompiled function //============================================================================= // Three explicit arguments, no hidden prefix transformations: // symbol: the exact linker symbol to reference // callable: the name of the typed callable variable // sig: the function signature (e.g. u32(u32, u32)) #define REX_IMPORT(symbol, callable, sig) \ REX_EXTERN(symbol); \ inline rex::ppc::ImportFunction callable { \ symbol \ } //============================================================================= // Legacy Compat Aliases //============================================================================= #define XBOXKRNL_EXPORT(n, f) REX_EXPORT(n, f) #define XBOXKRNL_EXPORT_STUB(n) REX_EXPORT_STUB(n) #define XAM_EXPORT(n, f) REX_EXPORT(n, f) #define XAM_EXPORT_STUB(n) REX_EXPORT_STUB(n) #define REXCRT_EXPORT(n, f) REX_HOOK(n, f) #define REXCRT_EXPORT_STUB(n) REX_STUB(n)