@@ -163,9 +163,13 @@ int __fastcall Script_IsAddOnLoadable(void *L) {
163163 return 2 ;
164164 }
165165
166- auto isLoD = reinterpret_cast <EntryByteFn_t>(
167- Offsets::FUN_ADDON_IS_LOAD_ON_DEMAND );
168- if (isLoD (entry) != 0 ) {
166+ // Already-loaded addons are trivially loadable — skip the
167+ // recursive dep walk and return (true, nil) directly. `IS_LOADED`
168+ // reads the byte at `entry+0x18` (the loader sets that to 1 on
169+ // successful load).
170+ auto isLoaded = reinterpret_cast <EntryByteFn_t>(
171+ Offsets::FUN_ADDON_IS_LOADED );
172+ if (isLoaded (entry) != 0 ) {
169173 Game::Lua::PushBoolean (L, 1 );
170174 Game::Lua::PushNil (L);
171175 return 2 ;
@@ -229,6 +233,36 @@ int __fastcall Script_GetAddOnSecurity(void *L) {
229233 return 1 ;
230234}
231235
236+ // `C_AddOns.IsAddOnLoaded(indexOrName)` → `(loadedOrLoading, loaded)`.
237+ //
238+ // Modern WoW distinguishes "load-in-progress" from "fully loaded" —
239+ // matters for LoD addons whose load is split across multiple
240+ // `LoadAddOn` callbacks. Vanilla 1.12's `FUN_0051F240` is fully
241+ // synchronous: the loaded byte at `entry+0x18` flips from 0 → 1
242+ // inside a single call, so the "loading" state is never observable
243+ // from Lua. We return the same boolean twice — `loadedOrLoading`
244+ // and `loaded` are identical here.
245+ //
246+ // Resolves both numeric indices (via `FUN_ADDON_GET_BY_INDEX`) and
247+ // string names. `IS_LOADED` accepts an entry pointer as its name arg
248+ // because the entry's first 12 bytes are the inline NUL-terminated
249+ // directory name (same as the other per-field accessors), so we can
250+ // feed it either an entry or a Lua-side string transparently.
251+ int __fastcall Script_IsAddOnLoaded (void *L) {
252+ const uint8_t *entry = ResolveAddOnName (L);
253+ if (entry == nullptr ) {
254+ Game::Lua::PushBool (L, false );
255+ Game::Lua::PushBool (L, false );
256+ return 2 ;
257+ }
258+ auto isLoaded = reinterpret_cast <EntryByteFn_t>(
259+ Offsets::FUN_ADDON_IS_LOADED );
260+ const bool loaded = isLoaded (entry) != 0 ;
261+ Game::Lua::PushBool (L, loaded);
262+ Game::Lua::PushBool (L, loaded);
263+ return 2 ;
264+ }
265+
232266// `C_AddOns.DoesAddOnExist(indexOrName)` — true iff the engine's
233267// addon registry has a matching entry. For numeric input we trust
234268// `GetByIndex`'s OOR-NULL; for string input we walk the flat array,
@@ -276,6 +310,8 @@ static void RegisterLuaFunctions() {
276310 &Script_GetAddOnSecurity);
277311 Game::Lua::RegisterTableFunction (" C_AddOns" , " DoesAddOnExist" ,
278312 &Script_DoesAddOnExist);
313+ Game::Lua::RegisterTableFunction (" C_AddOns" , " IsAddOnLoaded" ,
314+ &Script_IsAddOnLoaded);
279315 Game::Lua::RegisterIntegerEnum (
280316 " Enum" , " AddOnSecurityStatus" ,
281317 kAddOnSecurityStatusEntries ,
0 commit comments