// 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 "xml/FrameLoad.h" #include "Game.h" #include "xml/Inherits.h" #include "Offsets.h" #include namespace Xml { namespace { using GetAttr_t = const char *(__thiscall *)(const void *node, const char *name); using TemplateLookup_t = const void *(__fastcall *)(const char *name); using SStrCmpI_t = int(__stdcall *)(const char *a, const char *b, int n); using Hash_t = uint32_t(__stdcall *)(const char *s); using StatusAdd_t = void(__cdecl *)(void *status, int level, const char *fmt, ...); } // namespace const void *FirstChild(const void *node) { return Game::Read(node, Offsets::OFF_XML_NODE_CHILD); } const void *NextSibling(const void *node) { return Game::Read(node, Offsets::OFF_XML_NODE_SIBLING); } const char *Tag(const void *node) { return Game::Read(node, Offsets::OFF_XML_NODE_TAG); } bool TagIs(const void *node, const char *tag) { const char *t = Tag(node); return t != nullptr && reinterpret_cast(Offsets::FUN_SSTR_CMP_I)(t, tag, 0x7FFFFFFF) == 0; } const char *Attr(const void *node, const char *name) { return reinterpret_cast(Offsets::FUN_XML_NODE_GET_ATTRIBUTE)(node, name); } const void *Template(const char *name) { return reinterpret_cast(Offsets::FUN_XML_TEMPLATE_LOOKUP)(name); } // FUN_XML_TEMPLATE_LOOKUP's own walk — hash, masked bucket, chain compare on // hash then SStrCmpI — returning the node's context instead of its definition. const char *TemplateContext(const char *name) { const uint32_t mask = Game::Read(Offsets::VAR_XML_TEMPLATE_MASK); const uint8_t *base = Game::Read(Offsets::VAR_XML_TEMPLATE_TABLE); if (name == nullptr || mask == 0xFFFFFFFFu || base == nullptr) return nullptr; const uint32_t hash = reinterpret_cast(Offsets::FUN_SSTR_HASH_HT)(name); const uint8_t *bucket = base + (hash & mask) * Offsets::XML_TEMPLATE_BUCKET_STRIDE; const int linkOff = Game::Read(bucket, Offsets::OFF_XML_TEMPLATE_BUCKET_LINKOFF); auto cmp = reinterpret_cast(Offsets::FUN_SSTR_CMP_I); for (const uint8_t *node = Game::Read(bucket, Offsets::OFF_XML_TEMPLATE_BUCKET_HEAD); node != nullptr && (reinterpret_cast(node) & 1) == 0; node = Game::Read(node, linkOff + 4)) { if (Game::Read(node, Offsets::OFF_XML_TEMPLATE_NODE_HASH) == hash && cmp(Game::Read(node, Offsets::OFF_XML_TEMPLATE_NODE_NAME), name, 0x7FFFFFFF) == 0) return Game::Read(node, Offsets::OFF_XML_TEMPLATE_NODE_CONTEXT); } return nullptr; } void LoadNode(void *object, const void *node, void *status) { using NodeLoader_t = uintptr_t(__thiscall *)(void *self, const void *node, void *status); void *self = static_cast(object) + Offsets::OFF_XML_LOADER_SUBOBJECT; const void *vtable = Game::Read(self, 0); Game::Read(vtable, Offsets::VTBL_XML_NODE_LOADER)(self, node, status); } void Warn(void *status, const char *fmt, const char *a, const char *b, const char *c) { if (status == nullptr) return; const void *vtable = Game::Read(status, 0); Game::Read(vtable, Offsets::VTBL_XML_STATUS_ADD)( status, Offsets::XML_STATUS_WARNING, fmt, a, b, c); } bool PushWrapper(void *L, void *object) { if (Game::Read(object, Offsets::OFF_COBJECT_LUA_REFCOUNT) == 0 && Game::Read(object, Offsets::OFF_SCRIPT_REGION_NAME) != nullptr) return false; const int ref = Game::Lua::ObjectLuaRef(object); if (ref <= 0) return false; Game::Lua::RawGetI(L, Game::Lua::REGISTRY_INDEX, ref); // [wrapper] if (Game::Lua::Type(L, -1) != Game::Lua::TYPE_TABLE) { Game::Lua::SetTop(L, -2); return false; } Game::Lua::RawGetI(L, -1, 0); // [wrapper, wrapper[0]] const bool same = Game::Lua::Type(L, -1) == Game::Lua::TYPE_LIGHTUSERDATA && Game::Lua::ToPointer(L, -1) == object; Game::Lua::SetTop(L, -2); // [wrapper] if (!same) { Game::Lua::SetTop(L, -2); return false; } return true; } namespace FrameLoad { namespace { // Chained at static-init time by each `AutoSubscribe` constructor, before // `DllMain`; zero-initialized ahead of any dynamic init. AutoSubscribe *g_head = nullptr; AutoSubscribeBefore *g_beforeHead = nullptr; using Loader_t = uintptr_t(__fastcall *)(void *self, void *edx, const void *node, void *status); Loader_t g_orig = nullptr; // `__thiscall` modelled as `__fastcall` with a dummy edx. uintptr_t __fastcall Hook(void *self, void *edx, const void *node, void *status) { if (self == nullptr || node == nullptr) return Inherits::LoadFrame(g_orig, self, edx, node, status); void *object = static_cast(self) - Offsets::OFF_XML_LOADER_SUBOBJECT; for (AutoSubscribeBefore *s = g_beforeHead; s != nullptr; s = s->next) s->fn(object, node, status); const uintptr_t r = Inherits::LoadFrame(g_orig, self, edx, node, status); for (AutoSubscribe *s = g_head; s != nullptr; s = s->next) s->fn(object, node, status); return r; } const Game::HookAutoRegister _hook{ Offsets::FUN_XML_FRAME_LOAD, reinterpret_cast(&Hook), reinterpret_cast(&g_orig)}; } // namespace AutoSubscribe::AutoSubscribe(Observer f) : fn(f), next(g_head) { g_head = this; } AutoSubscribeBefore::AutoSubscribeBefore(Observer f) : fn(f), next(g_beforeHead) { g_beforeHead = this; } } // namespace FrameLoad } // namespace Xml