Skip to content

Commit 36e1aa8

Browse files
committed
C_AddOns.IsAddOnLoaded
1 parent 40532ac commit 36e1aa8

5 files changed

Lines changed: 75 additions & 14 deletions

File tree

AddOns/!!!ClassicAPI/Util/EventUtil.lua

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,11 @@ function EventUtil.TriggerOnVariablesLoaded()
8383
end
8484

8585
function EventUtil.ContinueOnAddOnLoaded(addOnName, callback)
86-
-- 1.12's IsAddOnLoaded returns a single bool. 3.3.5 split that into
87-
-- (isLoadedOrLoading, isLoaded); treat truthy as "ready to use".
88-
if IsAddOnLoaded(addOnName) then
89-
callback()
90-
return
91-
end
86+
local isLoadedOrLoading, isLoaded = C_AddOns.IsAddOnLoaded(addOnName)
87+
if isLoaded then
88+
callback();
89+
return;
90+
end
9291

9392
EventUtil.RegisterOnceFrameEventAndCallback("ADDON_LOADED", callback, addOnName)
9493
end

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
1818
| Namespace | Calls |
1919
|-----------|-------|
2020
| [Action](docs/API.md#action) | `GetActionInfo` |
21-
| [AddOns](docs/API.md#addons) | `C_AddOns.DoesAddOnExist`, `C_AddOns.GetAddOnName`, `C_AddOns.GetAddOnNotes`, `C_AddOns.GetAddOnSecurity`, `C_AddOns.GetAddOnTitle`, `C_AddOns.IsAddOnLoadable` |
21+
| [AddOns](docs/API.md#addons) | `C_AddOns.DoesAddOnExist`, `C_AddOns.GetAddOnName`, `C_AddOns.GetAddOnNotes`, `C_AddOns.GetAddOnSecurity`, `C_AddOns.GetAddOnTitle`, `C_AddOns.IsAddOnLoadable`, `C_AddOns.IsAddOnLoaded` |
2222
| [Chat](docs/API.md#chat) | `GetCurrentChatGUID` |
2323
| [Class](docs/API.md#class) | `FillLocalizedClassList` |
2424
| [Combat](docs/API.md#combat) | `InCombatLockdown` |

docs/API.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ build instructions.
1717
- [`C_AddOns.GetAddOnTitle(indexOrName)`](#c_addonsgetaddontitleindexorname)
1818
- [`C_AddOns.GetAddOnNotes(indexOrName)`](#c_addonsgetaddonnotesindexorname)
1919
- [`C_AddOns.IsAddOnLoadable(indexOrName)`](#c_addonsisaddonloadableindexorname)
20+
- [`C_AddOns.IsAddOnLoaded(indexOrName)`](#c_addonsisaddonloadedindexorname)
2021
- [`C_AddOns.GetAddOnSecurity(indexOrName)`](#c_addonsgetaddonsecurityindexorname)
2122
- [`C_AddOns.DoesAddOnExist(indexOrName)`](#c_addonsdoesaddonexistindexorname)
2223

@@ -565,6 +566,29 @@ C_AddOns.IsAddOnLoadable("HardcoreTooltips") -- false, "DISABLED"
565566
C_AddOns.IsAddOnLoadable("garbage") -- false, nil
566567
```
567568

569+
### `C_AddOns.IsAddOnLoaded(indexOrName)`
570+
571+
Returns `loadedOrLoading, loaded` — two booleans.
572+
573+
```lua
574+
C_AddOns.IsAddOnLoaded("DebugTools") -- true, true
575+
C_AddOns.IsAddOnLoaded("garbage") -- false, false
576+
C_AddOns.IsAddOnLoaded(1) -- true, true (first addon by index)
577+
```
578+
579+
Modern WoW splits the two returns to distinguish "load-in-progress"
580+
from "fully loaded" — the difference matters for `LoadOnDemand`
581+
addons whose load is split across multiple `LoadAddOn` callbacks.
582+
Vanilla 1.12's addon loader (`FUN_0051F240`) is fully synchronous:
583+
the `loaded` byte flips inside a single call, so the in-flight
584+
state is never observable from Lua. Both returns are always the
585+
same boolean here. We surface the two-return shape so consumer
586+
code written against modern API doesn't need to special-case
587+
vanilla.
588+
589+
Unknown addons (numeric index past `GetNumAddOns()`, or
590+
string name not in the registry) return `false, false`.
591+
568592
### `C_AddOns.GetAddOnSecurity(indexOrName)`
569593

570594
Returns an `Enum.AddOnSecurityStatus` integer (not a string — modern

src/Offsets.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2609,9 +2609,11 @@ enum Offsets {
26092609
// We dispatch to these directly so the wrappers don't need to
26102610
// bounce through `Script_GetAddOnInfo` and unwind 7 returns.
26112611
//
2612-
// IS_LOAD_ON_DEMAND `__fastcall(entry) → byte`
2613-
// Hash-table lookup in the addon registry. Non-zero ⇒
2614-
// `## LoadOnDemand: 1` in the .toc.
2612+
// IS_LOADED `__fastcall(entry) → byte`
2613+
// Hash-table lookup in the addon registry. Returns the byte
2614+
// at `entry+0x18`, which the addon loader (`FUN_0051F240`)
2615+
// writes `1` to when the addon has finished loading. Non-zero
2616+
// ⇒ the addon is loaded.
26152617
//
26162618
// CAN_LOAD `__fastcall(entry, char fullCheck=1, int *outStatus,
26172619
// int *outDepHandle, const char *accountName) → byte`
@@ -2632,7 +2634,7 @@ enum Offsets {
26322634
// Hash-table lookup that returns the security category index
26332635
// (0=SECURE, 1=INSECURE, 2=BANNED). Defaults to `1` (INSECURE)
26342636
// when the entry isn't in the override table.
2635-
FUN_ADDON_IS_LOAD_ON_DEMAND = 0x0051E6F0,
2637+
FUN_ADDON_IS_LOADED = 0x0051E6F0,
26362638
FUN_ADDON_CAN_LOAD = 0x0051E780,
26372639
FUN_ADDON_GET_SECURITY_INDEX = 0x0051E990,
26382640

src/addons/Info.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)