// 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 .
#include "ui/FrameObject.h"
#include "Game.h"
#include "Offsets.h"
#include
namespace UI::FrameObject {
namespace {
using LuaRawGetI_t = void(__fastcall *)(void *L, int idx, int n);
using ScriptRegister_t = void(__fastcall *)(void *this_, void *edx_unused,
const char *name);
} // namespace
// Delegate wrapper construction to the engine's own
// `FrameScript_Object::ScriptRegister`. First call for a never-seen frame
// builds a `{[0] = lightuserdata}` table with the framescript metatable,
// `luaL_ref`s it into the registry, and writes the refkey to `frame +
// 0x08` (`OFF_COBJECT_LUA_REGISTRY_REF`). Subsequent calls find a
// populated refkey and just rawgeti.
//
// Why delegate instead of building our own wrapper: single source of
// truth. Every code path that pushes this frame — ours, addons'
// `CreateFrame(..., parent)` extracting the frame, the engine's own
// pushers — funnels through `rawgeti(REGISTRY, frame + 0x08)` and gets the
// same Lua table, so addon fields set on the wrapper don't get orphaned
// onto a divergent copy. See the long note in `nameplate/Info.cpp`'s
// original implementation for the pfUI-divergence story that motivated
// this.
//
// `ScriptRegister` unconditionally bumps the CObject refcount at `+0x04`,
// which pins the frame against GC. That's benign for the pooled/permanent
// frames we push here (nameplates, chat bubbles) — we call it at most
// once per frame pointer (gated by `refKey <= 0`), so the refcount tops
// out at one increment per pool slot.
//
// Defensive fallback: if the registry slot exists but isn't a table any
// more (freed elsewhere), re-register to rebuild a fresh wrapper. This
// shouldn't happen in practice — engines only unref on frame destruction.
void Push(void *L, void *frame) {
auto rawgeti = reinterpret_cast(
Offsets::FUN_FRAMESCRIPT_PUSH_OBJECT);
auto scriptRegister = reinterpret_cast(
static_cast(Offsets::FUN_FRAMESCRIPT_OBJECT_SCRIPT_REGISTER));
auto *cobj = static_cast(frame);
int refKey = *reinterpret_cast(
cobj + Offsets::OFF_COBJECT_LUA_REGISTRY_REF);
if (refKey <= 0) {
scriptRegister(frame, nullptr, nullptr);
refKey = *reinterpret_cast(
cobj + Offsets::OFF_COBJECT_LUA_REGISTRY_REF);
}
rawgeti(L, Game::Lua::REGISTRY_INDEX, refKey);
if (Game::Lua::Type(L, -1) == Game::Lua::TYPE_TABLE)
return;
Game::Lua::SetTop(L, -2);
// Slot got freed somewhere — re-register to rebuild a fresh wrapper
// and slot. Zero the refcount so `ScriptRegister` re-enters its build
// branch.
*reinterpret_cast(cobj + Offsets::OFF_COBJECT_LUA_REFCOUNT) = 0;
scriptRegister(frame, nullptr, nullptr);
refKey = *reinterpret_cast(
cobj + Offsets::OFF_COBJECT_LUA_REGISTRY_REF);
rawgeti(L, Game::Lua::REGISTRY_INDEX, refKey);
}
} // namespace UI::FrameObject