Files
ClassicAPI/src/addons/LoadState.cpp
T
Brues 9d1e14410c refactor: share one bounded string copy as Common::BoundedCopy
addons/LoadState.cpp used strncpy, which MSVC deprecates (C4996) and
which does not terminate on truncation, so the call site had to add the
terminator itself. frame/Attributes.cpp had already met that warning and
answered it with a local BoundedCopy helper.

Promote that helper to Common rather than copy it a second time, and
point both files at it. The helper treats a null source as empty, so
LoadState loses its null-name branch as well. Behavior is unchanged:
strncpy(dst, src, kMaxName - 1) plus a manual terminator wrote at most
kMaxName - 1 characters, and so does BoundedCopy(dst, src, kMaxName).

strncpy_s was the other option and is worse here. It invokes the
invalid-parameter handler on truncation unless the caller passes
_TRUNCATE, and LoadState runs inside an engine hook.
2026-09-10 13:36:00 -05:00

91 lines
2.9 KiB
C++

// 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 <https://www.gnu.org/licenses/>.
// Tracks in-progress addon loads so `C_AddOns.IsAddOnLoaded` can report
// `loadedOrLoading` and `loaded` separately. See `LoadState.h` for why the
// engine's own byte can't answer both.
//
// Loads nest — an addon's dependencies load inside its own call — so this
// keeps a stack rather than a single name. The name is copied rather than
// stored by pointer: `LoadAddOn` passes a Lua string, which does not
// outlive the call.
#include "addons/LoadState.h"
#include "Common.h"
#include "Game.h"
#include "Offsets.h"
namespace AddOns::LoadState {
namespace {
// Dependency chains are shallow; anything deeper simply isn't tracked
// (the depth counter still balances, so the stack can't desync).
constexpr int kMaxDepth = 16;
constexpr int kMaxName = 64;
char g_stack[kMaxDepth][kMaxName];
int g_depth = 0;
bool EqualsIgnoreCase(const char *a, const char *b) {
for (; *a != '\0' && *b != '\0'; ++a, ++b) {
char ca = *a;
char cb = *b;
if (ca >= 'A' && ca <= 'Z')
ca = static_cast<char>(ca - 'A' + 'a');
if (cb >= 'A' && cb <= 'Z')
cb = static_cast<char>(cb - 'A' + 'a');
if (ca != cb)
return false;
}
return *a == *b;
}
using LoadOne_t = unsigned int(__fastcall *)(const char *name, char flag,
void *progressCtx);
LoadOne_t s_loadOne_o = nullptr;
unsigned int __fastcall LoadOne_h(const char *name, char flag, void *progressCtx) {
const int slot = g_depth;
if (slot >= 0 && slot < kMaxDepth)
Common::BoundedCopy(g_stack[slot], name, kMaxName);
++g_depth;
const unsigned int result = s_loadOne_o(name, flag, progressCtx);
--g_depth;
if (g_depth < 0)
g_depth = 0; // defensive: never let the stack run negative
return result;
}
const Game::HookAutoRegister _hookLoadOne{
Offsets::FUN_ADDON_LOAD_ONE,
reinterpret_cast<void *>(&LoadOne_h),
reinterpret_cast<void **>(&s_loadOne_o)};
} // namespace
bool IsLoading(const char *name) {
if (name == nullptr || *name == '\0')
return false;
const int depth = (g_depth < kMaxDepth) ? g_depth : kMaxDepth;
for (int i = 0; i < depth; ++i)
if (EqualsIgnoreCase(g_stack[i], name))
return true;
return false;
}
} // namespace AddOns::LoadState