mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-14 15:10:39 +00:00
Backport UpdateAddOnMemoryUsage / GetAddOnMemoryUsage and the script profiling family (scriptProfile, GetFunctionCPUUsage, UpdateAddOnCPUUsage, GetAddOnCPUUsage, GetFrameCPUUsage, GetEventCPUUsage, GetScriptCPUUsage, ResetCPUUsage), mirroring 3.3.5's engine mechanism. - Gc::Hooks owns the GC lifecycle hooks (luaC_link, newlstr, luaS_newudata, freeobj); Gc::Tag stamps each new object with the engine's context word (3.3.5's lua_tainted rule) and sums sizes with the engine's own free sizes. - lua_gctag pins: the addon load window (two call patches in FUN_ADDON_LOAD_ONE) and template application (engine single-name sites + Xml::Inherits lists). - Profile::Cpu keeps 3.3.5's per-closure Profile records through Lua's own call/return hook, installed only when scriptProfile is 1; tail calls, error unwinds and coroutines handled. - ScriptArgs observers time every handler run; signals counted at each dispatcher (call-site redirect inside FUN_FIRE_EVENT, not a MinHook). - Frame::ScriptSlots enumerates a frame's script slots without creating them, for GetFrameCPUUsage. - Add the unordered_dense submodule (v5.3.2); share ResolveRecord via addons/Registry; fix the AddOnEntry inline-name comments.
491 lines
20 KiB
C++
491 lines
20 KiB
C++
// This file is part of ClassicAPI.
|
|
//
|
|
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
|
// of the GNU General Public License as published by the Free Software Foundation, either
|
|
// version 3 of the License, or (at your option) any later version.
|
|
//
|
|
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
// PURPOSE. See the GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
// Modern positional script arguments — make every frame-script handler
|
|
// receive `(self, arg1..argN)` the way later clients do, on top of the
|
|
// vanilla `this`/`arg1`/`event` globals.
|
|
//
|
|
// frame:SetScript("OnMouseWheel", function() print(this, arg1) end) -- vanilla
|
|
// frame:SetScript("OnMouseWheel", function(self, delta) print(self, delta) end) -- modern
|
|
//
|
|
// Vanilla ALWAYS invokes a script handler with ZERO Lua arguments: the two
|
|
// runners the engine funnels every dispatch through — FUN_FRAME_INVOKE_SCRIPT
|
|
// (no-arg scripts) and FUN_FRAME_RUN_SCRIPT_ARGS (scripts with values) — set
|
|
// the `this`/`arg1..argN` globals and then `lua_pcall(handler, /*nargs*/0)`.
|
|
// So `function(self, delta)` gets nothing today.
|
|
//
|
|
// We co-hook both runners and REPLACE their pcall tail: keep setting the same
|
|
// globals (so handlers reading `this`/`arg1` keep working), but push `self` +
|
|
// `arg1..argN` as real arguments and `pcall` with `nargs = 1 + N`. The
|
|
// reimplementation mirrors the engine's message-handler errfunc, but saves the
|
|
// clobbered globals on the Lua stack (not registry refs like the engine) so an
|
|
// out-of-memory longjmp mid-push unwinds them for free — no leaked refs, no
|
|
// per-call ref churn. See RunModern.
|
|
//
|
|
// The reimplemented path is taken ONLY for a handler that actually declares a
|
|
// parameter or is vararg (checked via its Proto — see HandlerWantsArgs). A
|
|
// handler that declares NO parameters can't observe positional args, so pushing
|
|
// them is a no-op — those route through the original engine runner unchanged:
|
|
// provably identical to vanilla, and OFF the engine's hottest Lua path (the vast
|
|
// majority of vanilla handlers, which read `this`/`arg1`, are param-less). So a
|
|
// param-less vanilla handler is untouched; only a param-declaring handler sees
|
|
// the `(self, [event,] arg1..argN)` shape.
|
|
//
|
|
// Irreducible caveat: a param-declaring handler and a modern handler are
|
|
// indistinguishable to the dispatcher — both are one-param Lua functions. So a
|
|
// vanilla handler that declares a parameter expecting it to be nil (e.g. a
|
|
// function reused as both a direct call and a handler) WILL now receive a real
|
|
// value. There is no signal to tell the two apart; such a handler must opt out
|
|
// per-session via `SetModernScriptArgs(false)`.
|
|
//
|
|
// Convention: `(self, arg1..argN)` for every script, plus `event` inserted as
|
|
// the first positional for OnEvent — `(self, event, arg1..argN)` — matching
|
|
// retail. An OnEvent dispatch is detected
|
|
// structurally at the runner: the handler ref being invoked equals the frame's
|
|
// OnEvent slot handler (frame+OFF_FRAME_ONEVENT_SLOT). This is exact — each
|
|
// SetScript creates a distinct ref, so a function bound to two scripts still
|
|
// differs per slot — so no fragile "are we mid event-dispatch" flag is needed.
|
|
//
|
|
// Runtime switch, default ON — modern handler signatures are a core 5.1
|
|
// feature, so ports that write `function(self, delta)` work out of the box.
|
|
// `SetModernScriptArgs(false)` disables it, `GetModernScriptArgs()` reads the
|
|
// state. While disabled both detours are a straight passthrough to the original
|
|
// runner (exact vanilla behavior, zero reimplementation risk) — the OFF escape
|
|
// valve, since this lands on the hottest Lua path in the engine
|
|
// (FUN_FRAME_RUN_SCRIPT_ARGS fires for every OnUpdate frame).
|
|
|
|
#include "frame/ScriptArgs.h"
|
|
|
|
#include "Game.h"
|
|
#include "HookSecureFunc.h"
|
|
#include "Offsets.h"
|
|
|
|
#include <cstdint>
|
|
|
|
namespace Frame::ScriptArgs {
|
|
|
|
namespace {
|
|
|
|
// Runtime switch. Default ON (core 5.1 behavior). When false, both detours
|
|
// tail-call the original runner unchanged (exact vanilla behavior, no
|
|
// reimplementation risk) — the OFF escape valve for this hot path.
|
|
bool g_enabled = true;
|
|
|
|
// Chained at static-init time by each `AutoObserve` constructor.
|
|
AutoObserve *g_observers = nullptr;
|
|
|
|
void Notify(bool begin, int handlerRef, void *frame) {
|
|
for (AutoObserve *o = g_observers; o != nullptr; o = o->next)
|
|
o->fn(begin, handlerRef, frame);
|
|
}
|
|
|
|
// Engine cap: the runner stops setting arg globals at index 19 (arg1..arg19).
|
|
constexpr int kMaxArgs = 19;
|
|
|
|
using Game::Lua::RawGetI;
|
|
|
|
// Writes "arg" + k (k in 1..19) into buf; buf must hold at least 6 bytes.
|
|
void ArgName(char *buf, int k) {
|
|
buf[0] = 'a';
|
|
buf[1] = 'r';
|
|
buf[2] = 'g';
|
|
if (k < 10) {
|
|
buf[3] = static_cast<char>('0' + k);
|
|
buf[4] = '\0';
|
|
} else {
|
|
buf[3] = static_cast<char>('0' + k / 10);
|
|
buf[4] = static_cast<char>('0' + k % 10);
|
|
buf[5] = '\0';
|
|
}
|
|
}
|
|
|
|
// One parsed positional argument from the runner's printf-style format.
|
|
// Numbers ('d'/'u'/'f') land in `num`; strings ('s') keep the char* the engine
|
|
// handed us (stable for the duration of the call).
|
|
struct Arg {
|
|
char kind; // 'd' | 'u' | 'f' | 's'
|
|
double num;
|
|
const char *str;
|
|
};
|
|
|
|
// Decodes `fmt`/`vaPtr` (the runner's %d/%u/%f/%s specs over a packed vararg
|
|
// buffer) into `out`, returning the count (capped at kMaxArgs). Unknown specs
|
|
// are skipped and consume no vararg — mirroring the engine runner's switch.
|
|
// Pure C, touches no Lua state, so it can never throw.
|
|
int ParseArgs(const char *fmt, const void *vaPtr, Arg *out) {
|
|
if (fmt == nullptr)
|
|
return 0;
|
|
const char *cur = static_cast<const char *>(vaPtr);
|
|
int n = 0;
|
|
for (const char *p = fmt; *p != '\0' && n < kMaxArgs; ++p) {
|
|
if (*p != '%')
|
|
continue;
|
|
++p;
|
|
if (*p == '\0')
|
|
break;
|
|
Arg &a = out[n];
|
|
switch (*p) {
|
|
case 'd':
|
|
a.num = static_cast<double>(*reinterpret_cast<const int *>(cur));
|
|
cur += 4;
|
|
break;
|
|
case 'u':
|
|
a.num = static_cast<double>(
|
|
*reinterpret_cast<const unsigned int *>(cur));
|
|
cur += 4;
|
|
break;
|
|
case 'f':
|
|
a.num = *reinterpret_cast<const double *>(cur);
|
|
cur += 8;
|
|
break;
|
|
case 's':
|
|
a.str = *reinterpret_cast<const char *const *>(cur);
|
|
cur += 4;
|
|
break;
|
|
default:
|
|
continue; // unknown spec — consume nothing
|
|
}
|
|
a.kind = *p;
|
|
++n;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// Pushes a parsed argument's value onto the Lua stack.
|
|
void PushArg(void *L, const Arg &a) {
|
|
if (a.kind == 's')
|
|
Game::Lua::PushString(L, a.str);
|
|
else
|
|
Game::Lua::PushNumber(L, a.num);
|
|
}
|
|
|
|
// Where RunCore's arguments come from: the runner's parsed printf-style
|
|
// list, or `n` Lua values already on the stack from absolute index
|
|
// `stackBase` (they sit below everything RunCore pushes, so the index holds).
|
|
struct ArgSource {
|
|
const Arg *parsed = nullptr;
|
|
int stackBase = 0;
|
|
int n = 0;
|
|
void Push(void *L, int k) const {
|
|
if (parsed != nullptr)
|
|
PushArg(L, parsed[k]);
|
|
else
|
|
Game::Lua::PushValue(L, stackBase + k);
|
|
}
|
|
};
|
|
|
|
// Reimplements the runner tail: set the `this`/`arg1..argN` globals, invoke the
|
|
// handler with modern positional args `(self, [event,] arg1..argN)` under the
|
|
// engine's own message-handler errfunc, then restore the globals. `fmt`/`vaPtr`
|
|
// are null for the no-arg runner (N == 0).
|
|
//
|
|
// Unlike the engine (and our first cut), the old global values are saved on the
|
|
// Lua STACK, not in the registry via luaL_ref. A registry save leaks its ref if
|
|
// any push before the protected call longjmps — and out-of-memory, the one
|
|
// failure that actually fires here, does exactly that. Stack slots need no
|
|
// cleanup: an unwinding error resets `L->top` past them for free, so nothing
|
|
// leaks and there's no per-call ref churn on the OnUpdate hot path. The cost is
|
|
// a deeper stack (up to ~2*N slots live at once), so we CheckStack up front.
|
|
//
|
|
// Returns false WITHOUT side effects when it declines (no Lua state, or the
|
|
// stack can't grow); the caller then runs the original. Every false path is
|
|
// before the first push, so falling back never double-runs the handler.
|
|
//
|
|
// `positional` false sets the globals but calls the handler with no
|
|
// arguments — the vanilla shape, for RunWithValues while the switch is off.
|
|
bool RunCore(void *L, int handlerRef, void *frame, const ArgSource &src,
|
|
bool positional) {
|
|
using namespace Game::Lua;
|
|
// Headroom for the saved-old block plus the transient set/call pushes.
|
|
if (CheckStack(L, 2 * src.n + 16) == 0)
|
|
return false;
|
|
|
|
const bool haveThis = frame != nullptr;
|
|
// OnEvent dispatch? The event dispatchers invoke the frame's OnEvent slot
|
|
// (frame+0x0C); matching the handler ref against that slot identifies it
|
|
// exactly. When so, `event` (a global the dispatcher has set) is prepended
|
|
// as the first positional to mirror retail's (self, event, arg1..argN).
|
|
const bool isOnEvent =
|
|
haveThis &&
|
|
Game::Read<int>(frame, Offsets::OFF_FRAME_ONEVENT_SLOT) == handlerRef;
|
|
const int selfRef = haveThis ? Game::Lua::ObjectLuaRef(frame) : 0;
|
|
|
|
const int n = src.n;
|
|
char argName[6];
|
|
|
|
// --- save the globals we're about to clobber, onto the stack ------------
|
|
// Absolute indices, live until the closing SetTop(base):
|
|
// base+1 = old _G.this (if haveThis)
|
|
// savedArgBase+1 .. +n = old _G.arg1..argN
|
|
const int base = GetTop(L);
|
|
if (haveThis) {
|
|
PushString(L, "this");
|
|
GetTable(L, GLOBALS_INDEX); // [.. oldThis]
|
|
}
|
|
const int savedArgBase = base + (haveThis ? 1 : 0);
|
|
for (int k = 0; k < n; ++k) {
|
|
ArgName(argName, k + 1);
|
|
PushString(L, argName);
|
|
GetTable(L, GLOBALS_INDEX); // [.. oldArgk]
|
|
}
|
|
|
|
// --- set the new globals (each op is stack-neutral) ---------------------
|
|
if (haveThis) {
|
|
PushString(L, "this");
|
|
RawGetI(L, REGISTRY_INDEX, selfRef); // frame object
|
|
SetTable(L, GLOBALS_INDEX);
|
|
}
|
|
for (int k = 0; k < n; ++k) {
|
|
ArgName(argName, k + 1);
|
|
PushString(L, argName);
|
|
src.Push(L, k);
|
|
SetTable(L, GLOBALS_INDEX);
|
|
}
|
|
|
|
// --- call handler(self, [event,] arg1..argN) ---------------------------
|
|
const int callBase = GetTop(L); // == base + saved-block size
|
|
const int errRef = Game::Read<int>(
|
|
static_cast<uintptr_t>(Offsets::VAR_FRAMESCRIPT_ERROR_HANDLER_REF));
|
|
int errIdx = 0;
|
|
if (errRef > 0) {
|
|
RawGetI(L, REGISTRY_INDEX, errRef); // message handler
|
|
errIdx = callBase + 1; // absolute index of the errfunc
|
|
}
|
|
RawGetI(L, REGISTRY_INDEX, handlerRef); // handler
|
|
int nargs = 0;
|
|
if (positional) {
|
|
if (haveThis)
|
|
RawGetI(L, REGISTRY_INDEX, selfRef); // self
|
|
else
|
|
PushNil(L);
|
|
if (isOnEvent) { // event goes before the args, retail-style
|
|
PushString(L, "event");
|
|
GetTable(L, GLOBALS_INDEX);
|
|
}
|
|
for (int k = 0; k < n; ++k)
|
|
src.Push(L, k);
|
|
nargs = 1 + (isOnEvent ? 1 : 0) + n;
|
|
}
|
|
PCall(L, nargs, 0, errIdx);
|
|
SetTop(L, callBase); // drop the message handler + any leftover error object
|
|
|
|
// --- restore globals from the saved block, then drop it -----------------
|
|
for (int k = 0; k < n; ++k) {
|
|
ArgName(argName, k + 1);
|
|
PushString(L, argName);
|
|
PushValue(L, savedArgBase + 1 + k);
|
|
SetTable(L, GLOBALS_INDEX);
|
|
}
|
|
if (haveThis) {
|
|
PushString(L, "this");
|
|
PushValue(L, base + 1);
|
|
SetTable(L, GLOBALS_INDEX);
|
|
}
|
|
SetTop(L, base);
|
|
return true;
|
|
}
|
|
|
|
// The runner path: arguments from the engine's printf-style format.
|
|
bool RunModern(int handlerRef, void *frame, const char *fmt, const void *vaPtr) {
|
|
void *L = Game::Lua::State();
|
|
if (L == nullptr)
|
|
return false;
|
|
Arg args[kMaxArgs];
|
|
ArgSource src;
|
|
src.parsed = args;
|
|
src.n = ParseArgs(fmt, vaPtr, args);
|
|
return RunCore(L, handlerRef, frame, src, /*positional*/ true);
|
|
}
|
|
|
|
// Lua 5.0 internal offsets for reading a handler's arity — verified against the
|
|
// engine's own luaD_precall (0x006F6050) / lua_dump (0x006F4370) /
|
|
// lua_iscfunction (0x006F34A0):
|
|
// lua_State: top @ +0x08 (TValue *, next free slot)
|
|
// TValue: tag @ +0x00 (int); GC/closure pointer @ +0x08 (16-byte TValue)
|
|
// Closure: isC byte @ +0x06; union member (C func / Proto) @ +0x0C
|
|
// CClosure: C function @ +0x0C (same union slot); upvalue[i] (TValue) @
|
|
// +0x10 + i*0x10 — lua_pushcclosure (0x006F3920) copies them there
|
|
// Single-use here, so kept local (like the lua_State field offsets elsewhere).
|
|
// The Proto fields this reads (numparams @ +0x45, is_vararg @ +0x46) are shared
|
|
// with LuaSyntax::Upvalues and live in Offsets.h as OFF_LUA_PROTO_*.
|
|
constexpr uintptr_t OFF_LUASTATE_TOP = 0x08;
|
|
constexpr uintptr_t OFF_TVALUE_GC = 0x08;
|
|
constexpr uintptr_t OFF_CLOSURE_ISC = 0x06;
|
|
constexpr uintptr_t OFF_LCLOSURE_PROTO = 0x0C;
|
|
constexpr uintptr_t OFF_CCLOSURE_FN = 0x0C;
|
|
constexpr uintptr_t OFF_CCLOSURE_UPVALUES = 0x10;
|
|
constexpr int kLuaTFunction = 6;
|
|
constexpr uintptr_t kTValueSize = 0x10;
|
|
|
|
// A HookScript chain nests one wrapper per hook. Past this depth, answer
|
|
// "yes": positional args are harmless to a handler that ignores them, so
|
|
// erring that way costs only the slower path.
|
|
constexpr int kMaxWrapperDepth = 32;
|
|
|
|
// Does the function in TValue `tv` observe positional args? A Lua closure
|
|
// does when it declares a parameter or is vararg — a modern
|
|
// `function(self, delta)` / `function(self, ...)`. The hook wrapper
|
|
// (`HookSecureFunc::Wrapper`, what HookScript installs) forwards its args to
|
|
// both functions it holds, so it does when either of them does. Any other C
|
|
// closure reads globals/upvalues, never positional args.
|
|
bool FunctionWantsArgs(const void *tv, int depth) {
|
|
if (Game::Read<int>(tv, 0) != kLuaTFunction)
|
|
return false;
|
|
const void *cl =
|
|
reinterpret_cast<const void *>(Game::Read<uintptr_t>(tv, OFF_TVALUE_GC));
|
|
if (cl == nullptr)
|
|
return false;
|
|
if (Game::Read<uint8_t>(cl, OFF_CLOSURE_ISC) == 0) { // Lua closure
|
|
const void *proto = reinterpret_cast<const void *>(
|
|
Game::Read<uintptr_t>(cl, OFF_LCLOSURE_PROTO));
|
|
return proto != nullptr &&
|
|
(Game::Read<uint8_t>(proto, Offsets::OFF_LUA_PROTO_NUMPARAMS) > 0 ||
|
|
Game::Read<uint8_t>(proto, Offsets::OFF_LUA_PROTO_IS_VARARG) != 0);
|
|
}
|
|
if (Game::Read<uintptr_t>(cl, OFF_CCLOSURE_FN) !=
|
|
reinterpret_cast<uintptr_t>(&HookSecureFunc::Wrapper))
|
|
return false;
|
|
if (depth >= kMaxWrapperDepth)
|
|
return true;
|
|
const auto *upvalues = static_cast<const uint8_t *>(cl) + OFF_CCLOSURE_UPVALUES;
|
|
return FunctionWantsArgs(upvalues, depth + 1) || // original
|
|
FunctionWantsArgs(upvalues + kTValueSize, depth + 1); // hook
|
|
}
|
|
|
|
// Does this handler actually observe positional args? A vanilla handler reads
|
|
// this/arg1 globals and declares NO parameters, so pushing positional args to
|
|
// it is observably identical to vanilla — those route through the original
|
|
// engine runner instead (exact vanilla, and off the hottest Lua path). Only a
|
|
// handler FunctionWantsArgs accepts takes the reimplemented positional path.
|
|
//
|
|
// Reads the closures directly (the engine exposes no arity via the C API);
|
|
// every read is bounded — a non-function ref, a foreign C closure, or a null
|
|
// Proto all fall through to "no".
|
|
bool HandlerWantsArgs(void *L, int handlerRef) {
|
|
RawGetI(L, Game::Lua::REGISTRY_INDEX, handlerRef); // push registry[handlerRef]
|
|
const void *tv = reinterpret_cast<const void *>(
|
|
Game::Read<uintptr_t>(L, OFF_LUASTATE_TOP) - kTValueSize);
|
|
const bool wants = FunctionWantsArgs(tv, 0);
|
|
Game::Lua::SetTop(L, -2); // pop the handler
|
|
return wants;
|
|
}
|
|
|
|
// --- FUN_FRAME_RUN_SCRIPT_ARGS co-hook (scripts with values) ----------------
|
|
using RunArgs_t = void(__cdecl *)(int handlerRef, void *frame, const char *fmt,
|
|
const void *vaPtr);
|
|
RunArgs_t g_origRunArgs = nullptr;
|
|
|
|
void RunArgs(int handlerRef, void *frame, const char *fmt, const void *vaPtr) {
|
|
if (g_enabled && handlerRef != 0 && fmt != nullptr) {
|
|
void *L = Game::Lua::State();
|
|
if (L != nullptr && HandlerWantsArgs(L, handlerRef) &&
|
|
RunModern(handlerRef, frame, fmt, vaPtr))
|
|
return;
|
|
}
|
|
g_origRunArgs(handlerRef, frame, fmt, vaPtr);
|
|
}
|
|
|
|
void __cdecl RunArgs_h(int handlerRef, void *frame, const char *fmt,
|
|
const void *vaPtr) {
|
|
Notify(true, handlerRef, frame);
|
|
RunArgs(handlerRef, frame, fmt, vaPtr);
|
|
Notify(false, handlerRef, frame);
|
|
}
|
|
|
|
// --- FUN_FRAME_INVOKE_SCRIPT co-hook (no-arg scripts) -----------------------
|
|
// Same address Tooltip::SetEvents calls directly to fire OnTooltipSet*; with
|
|
// the switch enabled those fire with (self) too, which is the modern shape.
|
|
using Invoke_t = void(__fastcall *)(int handlerRef, void *frame);
|
|
Invoke_t g_origInvoke = nullptr;
|
|
|
|
void Invoke(int handlerRef, void *frame) {
|
|
if (g_enabled && handlerRef != 0 && frame != nullptr) {
|
|
void *L = Game::Lua::State();
|
|
if (L != nullptr && HandlerWantsArgs(L, handlerRef) &&
|
|
RunModern(handlerRef, frame, /*fmt*/ nullptr, /*vaPtr*/ nullptr))
|
|
return;
|
|
}
|
|
g_origInvoke(handlerRef, frame);
|
|
}
|
|
|
|
void __fastcall Invoke_h(int handlerRef, void *frame) {
|
|
Notify(true, handlerRef, frame);
|
|
Invoke(handlerRef, frame);
|
|
Notify(false, handlerRef, frame);
|
|
}
|
|
|
|
const Game::HookAutoRegister _runArgsHook{
|
|
Offsets::FUN_FRAME_RUN_SCRIPT_ARGS,
|
|
reinterpret_cast<void *>(&RunArgs_h),
|
|
reinterpret_cast<void **>(&g_origRunArgs)};
|
|
|
|
const Game::HookAutoRegister _invokeHook{
|
|
Offsets::FUN_FRAME_INVOKE_SCRIPT,
|
|
reinterpret_cast<void *>(&Invoke_h),
|
|
reinterpret_cast<void **>(&g_origInvoke)};
|
|
|
|
// --- Lua toggle -------------------------------------------------------------
|
|
int __fastcall Script_SetModernScriptArgs(void *L) {
|
|
g_enabled = Game::Lua::ToBoolean(L, 1) != 0;
|
|
Game::Lua::PushBool(L, g_enabled);
|
|
return 1;
|
|
}
|
|
|
|
int __fastcall Script_GetModernScriptArgs(void *L) {
|
|
Game::Lua::PushBool(L, g_enabled);
|
|
return 1;
|
|
}
|
|
|
|
void RegisterLuaFunctions() {
|
|
Game::Lua::RegisterGlobalFunction("SetModernScriptArgs",
|
|
&Script_SetModernScriptArgs);
|
|
Game::Lua::RegisterGlobalFunction("GetModernScriptArgs",
|
|
&Script_GetModernScriptArgs);
|
|
}
|
|
|
|
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
|
|
|
} // namespace
|
|
|
|
void RunWithValues(void *L, int handlerRef, void *frame, int firstArgIdx, int nargs) {
|
|
if (handlerRef == 0 || frame == nullptr)
|
|
return;
|
|
ArgSource src;
|
|
src.stackBase = firstArgIdx;
|
|
src.n = nargs;
|
|
Notify(true, handlerRef, frame);
|
|
RunCore(L, handlerRef, frame, src, /*positional*/ g_enabled);
|
|
Notify(false, handlerRef, frame);
|
|
}
|
|
|
|
AutoObserve::AutoObserve(Observer f) : fn(f), next(g_observers) { g_observers = this; }
|
|
|
|
void RunSlotWithValues(void *L, const uint32_t *slot, void *frame, int firstArgIdx, int nargs) {
|
|
if (slot == nullptr || slot[0] == 0)
|
|
return;
|
|
// 0x00702690: depth up, context from the slot, run, restore the context
|
|
// while the depth is still nonzero, depth down and floored at 0.
|
|
auto &context = Game::Ref<uint32_t>(Offsets::VAR_FRAMESCRIPT_EXEC_CONTEXT);
|
|
auto &depth = Game::Ref<int32_t>(Offsets::VAR_FRAMESCRIPT_EXEC_CONTEXT_DEPTH);
|
|
const uint32_t saved = context;
|
|
++depth;
|
|
context = slot[1];
|
|
RunWithValues(L, static_cast<int>(slot[0]), frame, firstArgIdx, nargs);
|
|
if (depth != 0)
|
|
context = saved;
|
|
if (--depth <= 0)
|
|
depth = 0;
|
|
}
|
|
|
|
} // namespace Frame::ScriptArgs
|