Files
benilla-octo/crates/benilla-ui/src/script/compat/classic_api/mod.rs
T
gromandClaude Opus 5 7bfdefb206 classicapi: C_EncodingUtil for real, and two namespaces 1.12 has no subsystem for
`C_EncodingUtil` is the one namespace in this family that is pure
algorithm, which is why it is built properly rather than stubbed: all
eight members, in the REAL formats. pfUI's profile sharing is the reader
and its own header names the pipeline — SerializeCBOR → CompressString
(zlib) → EncodeBase64, prefixed `!pf1!` — with the export path NOT inside
a pcall, so clicking Export raised, and the import path inside one, so it
failed silently.

A profile string is pasted between players, so a blob this client writes
has to be readable by the real one and the other way round. So: zlib
through flate2 (already the workspace's inflate for MPQ sectors and its
compressor for CMSG_AUTH_SESSION's addon block, so this adds a crate to
one more manifest and not to the build), RFC 4648 base64, RFC 8949 CBOR,
RFC 8259 JSON. Nothing invented here — the trap a hand-rolled codec falls
into is round-tripping with ITSELF while being wrong.

The decoders are written for hostile input, because a profile string
arrives pasted from a forum: every length is checked against what is
actually left, nesting is capped, the inflate output is capped at 16 MiB,
and a malformed blob answers nil rather than raising or allocating what a
crafted header asked for. Tests run the RFCs' own vectors in both
directions — including the half-float and tag forms we never write but a
real encoder may — a truncated/overlong/deep-nested set, and pfUI's whole
export/import pipeline end to end.

`C_EquipmentSet` and `C_LootHistory` are subsystems 1.12 does not have at
all, and both answer an honest nothing rather than being absent, because
pfUI builds both windows and reaches one member of each OUTSIDE its own
gate (the equipment manager's `CanUseEquipmentSets` test is one line in
one addon; the loot window's Clear button has no test at all).

What each would take is written down rather than guessed at. Equipment
sets are persistent state 1.12's wire has no concept of — a feature to
decide on, not a gap to close, and pfUI already supplies the UI. The loot
log is closer than it looks: both packets already carry what the getters
answer and `ui_loot_roll.rs` handles them, so it is a bounded list plus
three events — but the getters' tuple order is the one thing here that IS
a guess, read out of pfUI's own destructures, and building the log would
make that guess load-bearing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-25 17:42:44 -06:00

185 lines
7.7 KiB
Rust

//! ClassicAPI (<https://github.com/brues-code/ClassicAPI>) compatibility: 550+ functions and 50+
//! events across ~60 namespaces backported from modern WoW onto 1.12.1, plus a Lua 5.1 syntax
//! backport. That repo's own `docs/API.md` is the authoritative per-function spec (a point-in-time
//! names-only snapshot lives at `docs/classic-api-functions.md`, re-fetch upstream before
//! resuming); `docs/ADDON_COMPAT.md` tracks namespace-by-namespace status. One file per namespace
//! here, each a `pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()>` (a bare-global
//! namespace) or returning an `mlua::Table` (a `C_*` namespace), called from this file's own
//! `install`.
//!
//! **Open question, not resolved here**: ClassicAPI's Lua-5.1-*stdlib* backport (`select`,
//! `coroutine`, `string.reverse`/`match`/`gmatch`, `table.maxn`, …) collides with
//! `script/lua50.rs`, which deliberately nils every one of those out so a vanilla addon's own
//! `string.gmatch or string.gfind`-style feature test takes 1.12's real branch — not a ClassicAPI
//! concern, a *every other addon's* 1.12-fidelity one. Restoring them here would be always-on,
//! same as everything else in `compat/`, which would break that fidelity test for addons that
//! never asked for ClassicAPI. Parsing 5.1 *syntax* (`#`, hex literals, long brackets) is already
//! free — that lives in the grammar, not the stdlib, and nothing nils it. Left alone pending a
//! maintainer call on how the two should coexist.
//!
//! Detection: [`CLASSIC_API_VERSION`] is set, and deliberately so — see its own comment for what
//! the number claims and why it is the number it is. Most functions here need no flag at all (an
//! addon checks `if FnName then` directly), but pfUI gates its *entire* load on this global, so
//! withholding it is not neutral: it disables the addon outright.
use mlua::Lua;
mod action;
mod addons;
mod callback_registry;
mod chat_bubbles;
mod chat_info;
mod class_color;
mod color_mixin;
mod color_util;
mod container;
mod creature_info;
mod cvar;
mod date_and_time;
mod encoding_util;
mod enums;
mod equipment_set;
mod event_util;
mod expansion;
mod faction;
mod focus;
pub(in crate::script) mod frame;
mod friend_list;
mod gossip;
mod hooks;
mod input;
mod item;
mod item_location;
mod item_mixin;
mod loot_history;
mod loss_of_control;
mod lua_ext;
mod macro_ext;
mod macro_icons;
mod map;
mod math_ext;
mod merchant_frame;
mod misc;
// `pub(in …compat)` for its `plate_frame`, which SuperWoW's `UnitNameplate` shares.
pub(in crate::script::compat) mod nameplate;
mod new_items;
mod player_cache;
mod player_info;
mod player_util;
mod pools;
mod quest_log;
mod spell;
mod spellbook;
mod state;
mod stdlib_backport;
mod string_methods;
mod table_util;
mod taxi_map;
mod timer;
mod tooltip;
mod totem;
mod unit;
mod unit_auras;
mod unit_extra;
mod unit_menu;
/// `CLASSIC_API_VERSION`, packed `X*10000 + Y*100 + Z` as upstream packs it.
///
/// **What this number claims, and why it is this number.** It is not a version of benilla's code:
/// it is a claim about which ClassicAPI *API surface* is present, and real addons gate on it. pfUI
/// 9.0.25 refuses to load at all below `11304` (its `API_Check.lua`'s `PFUI_CLASSIC_API_MIN`), so
/// a lower number is not a humbler claim — it is "pfUI is disabled", which is strictly worse for a
/// player than a few missing functions that error where they are actually used.
///
/// So this says v1.13.4: the surface pfUI asks for. We do **not** implement every function of that
/// release (`docs/ADDON_COMPAT.md` has the per-namespace truth, and several namespaces are partial
/// or absent). An addon reaching for something unbuilt gets a clean `nil`-call error naming the
/// function, which is how the rest of this compat layer already behaves and is a far better
/// failure than a silent wrong answer. Raise this only alongside real coverage, and never to
/// quiet a specific addon without checking what else keys on it.
const CLASSIC_API_VERSION: i64 = 11304;
/// `INTERFACE_VERSION`: the client's own interface number, 1.12.1's `11200` — the `## Interface`
/// value a 1.12 `.toc` carries. Unrelated to [`CLASSIC_API_VERSION`] despite living beside it.
const INTERFACE_VERSION: i64 = 11200;
pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
unit::install(lua)?;
unit_menu::install(lua)?;
unit_extra::install(lua)?;
focus::install(lua)?;
let g = lua.globals();
g.set("CLASSIC_API_VERSION", CLASSIC_API_VERSION)?;
g.set("INTERFACE_VERSION", INTERFACE_VERSION)?;
g.set("Enum", enums::install(lua)?)?;
g.set("C_Timer", timer::install(lua)?)?;
g.set("C_DateAndTime", date_and_time::install(lua)?)?;
g.set("C_ChatBubbles", chat_bubbles::install(lua)?)?;
g.set("C_ChatInfo", chat_info::install(lua)?)?;
g.set("C_EncodingUtil", encoding_util::install(lua)?)?;
g.set("C_EquipmentSet", equipment_set::install(lua)?)?;
g.set("C_LootHistory", loot_history::install(lua)?)?;
g.set("C_LossOfControl", loss_of_control::install(lua)?)?;
g.set("C_Container", container::install(lua)?)?;
g.set("C_CreatureInfo", creature_info::install(lua)?)?;
g.set("C_CVar", cvar::install(lua)?)?;
g.set("C_NamePlate", nameplate::install(lua)?)?;
g.set("C_NewItems", new_items::install(lua)?)?;
g.set("C_Macro", macro_ext::install(lua)?)?;
g.set("C_Item", item::install(lua)?)?;
g.set("C_AddOns", addons::install(lua)?)?;
// `C_Spell`'s bare half hands out the very same function values the table holds, so the
// namespaced and bare spellings cannot drift — hence the table before the globals.
let c_spell = spell::install(lua)?;
spell::install_globals(lua, &c_spell)?;
g.set("C_Spell", c_spell)?;
g.set("C_SpellBook", spellbook::install(lua)?)?;
g.set("C_UnitAuras", unit_auras::install(lua)?)?;
g.set("C_ColorUtil", color_util::install(lua)?)?;
g.set("C_ClassColor", class_color::install(lua)?)?;
g.set("C_QuestLog", quest_log::install(lua)?)?;
g.set("C_Reputation", faction::install(lua)?)?;
g.set("C_FriendList", friend_list::install(lua)?)?;
g.set("C_GossipInfo", gossip::install(lua)?)?;
g.set("C_MerchantFrame", merchant_frame::install(lua)?)?;
g.set("C_Map", map::install(lua)?)?;
taxi_map::install(lua)?;
g.set("C_PlayerCache", player_cache::install(lua)?)?;
g.set("C_PlayerInfo", player_info::install(lua)?)?;
g.set("PlayerUtil", player_util::install(lua)?)?;
item::install_globals(lua)?;
macro_ext::install_globals(lua)?;
action::install(lua)?;
state::install(lua)?;
event_util::install(lua)?;
expansion::install(lua)?;
input::install(lua)?;
lua_ext::install(lua)?;
math_ext::install(lua)?;
pools::install(lua)?;
totem::install(lua)?;
timer::install_globals(lua)?;
callback_registry::install(lua)?;
color_mixin::install(lua)?;
misc::install(lua)?;
faction::install_globals(lua)?;
merchant_frame::install_mail_globals(lua)?;
hooks::install(lua)?;
frame::install(lua)?;
tooltip::install(lua)?;
stdlib_backport::install(lua)?;
string_methods::install(lua)?;
table_util::install(lua)?;
// After `item::install`: the mixin's `IsValid` calls `C_Item.DoesItemExist`. Only at *call*
// time, as `color_mixin.rs` explains for `CreateColor`, so the order is for the reader rather
// than for correctness.
item_location::install(lua)?;
item_mixin::install(lua)?;
// After `lua_ext::install`: the mixin chunk is a `CreateFromMixins` consumer and the picker
// constructs it through `CreateAndInitFromMixin`, both of which that file defines. Unlike the
// two above, this order is load-time, not call-time — `CreateAndInitFromMixin` is itself a
// chunk, so it must already be a global.
macro_icons::install(lua)?;
Ok(())
}