feat(baselib): gmatch, table.removemulti, math hyperbolics, difftime, newproxy, scrub

The base-library names that 1.12.1's Lua 5.0 and its compat.lua do not
have:

- gmatch: the global alias of string.gmatch (5.0's str_gfind,
  0x7FCFA0), set by value like strmatch and strrev.
- table.removemulti(t [, pos [, count]]): removes count elements from
  pos, shifts the rest down with lua_rawgeti/lua_rawseti and returns the
  removed elements. 5.0 keeps the length apart from the keys, so like
  the engine's table.remove (0x7FB770) it records the new length with
  luaL_setn (0x6F4EA0).
- math.cosh, math.sinh, math.tanh: luaL_checknumber (0x6F4C80) and the
  C function.
- difftime(t2 [, t1]): C difftime over the two times, next to the
  engine's date and time globals.
- newproxy([false | true | proxy]): an empty userdata from
  lua_newuserdata. The closure's upvalue is a weak table (its own
  metatable, __mode "kv") that marks the metatables newproxy(true)
  makes, so only those can be shared.
- scrub(...): replaces each argument that is not a boolean, number or
  string with nil, in place with lua_replace (0x6F32B0).

All are registered on both Lua states.

Game::Lua gains RawSetI (lua_rawseti, 0x6F3F60), Replace, CheckLString
(luaL_checklstring, 0x6F4BE0), CheckNumber and OptNumber (luaL_optnumber,
0x6F4CD0); string.byte now uses them instead of local typedefs.

issecure, issecurevariable and forceinsecure are left out. 1.12.1 has
no taint, so they could only return constants, and a constant
"secure" from issecurevariable makes AceHook-3.0 refuse every plain
hook.
This commit is contained in:
Brues
2026-11-03 14:23:25 -06:00
parent 1645e6b43c
commit 6ed0edc947
10 changed files with 398 additions and 18 deletions
+70 -1
View File
@@ -673,12 +673,16 @@ build instructions.
- [`getfenv` / `setfenv` environment protection](#getfenv--setfenv-environment-protection)
- [`select(index, ...)`](#selectindex-)
- [`unpack(list [, i [, j]])`](#unpacklist--i--j)
- [`newproxy([proxy])`](#newproxyproxy)
- [`scrub(...)`](#scrub)
- [`difftime(time2 [, time1])`](#difftimetime2--time1)
- [`xpcall(f, msgh [, arg1, ...])`](#xpcallf-msgh--arg1-)
- [`GetCallstackHeight()` / `GetErrorCallstackHeight()` / `SetErrorCallstackHeight([height])`](#getcallstackheight--geterrorcallstackheight--seterrorcallstackheightheight)
- [`collectgarbage(opt [, arg])`](#collectgarbageopt--arg)
- [`table.wipe(t)` / `wipe(t)`](#tablewipet--wipet)
- [`table.count(tbl)`](#tablecounttbl)
- [`table.maxn(t)`](#tablemaxnt)
- [`table.removemulti(t [, pos [, count]])`](#tableremovemultit--pos--count)
- [Stale table lengths (5.1 healing)](#stale-table-lengths-51-healing)
- [`Mixin(object, ...)` / `CreateFromMixins(...)`](#mixinobject---createfrommixins)
- [`string.match` / `string.gmatch`](#stringmatch--stringgmatch)
@@ -696,6 +700,7 @@ build instructions.
- [`math.fmod(x, y)`](#mathfmodx-y)
- [`math.modf(x)`](#mathmodfx)
- [`math.huge`](#mathhuge)
- [`math.cosh(x)` / `math.sinh(x)` / `math.tanh(x)`](#mathcoshx--mathsinhx--mathtanhx)
- [`coroutine.create(fn)`](#coroutinecreatefn)
- [`coroutine.resume(co, ...)`](#coroutineresumeco-)
- [`coroutine.yield(...)`](#coroutineyield)
@@ -16032,6 +16037,44 @@ unpack({10, 20, 30}, 2, 2) -- 20
length, so a vararg `arg` table's `n` field is honored and embedded nils
keep their slots. Errors on a non-table first argument, exactly like 5.1.
### `newproxy([proxy])`
Returns a new empty userdata. The argument sets its metatable:
- `false` or no argument: the userdata has no metatable.
- `true`: the userdata gets a new empty metatable. Use `getmetatable` to get
this metatable and add metamethods to it.
- A userdata from `newproxy(true)`: the new userdata shares the metatable of
that userdata.
Any other argument causes the error `boolean or proxy expected`.
```lua
local proxy = newproxy(true)
getmetatable(proxy).__gc = function() print("collected") end
local twin = newproxy(proxy) -- same metatable as proxy
```
### `scrub(...)`
Returns its arguments. Each argument that is not a boolean, a number or a
string becomes `nil`.
```lua
scrub(1, "a", true, {}, print) -- 1, "a", true, nil, nil
```
### `difftime(time2 [, time1])`
Returns the number of seconds from `time1` to `time2`. `time1` is 0 by
default. Both values are times in seconds, as `time()` returns.
```lua
local start = time()
-- ...
difftime(time(), start) -- the seconds since start
```
### `xpcall(f, msgh [, arg1, ...])`
Calls `f` under the message handler `msgh`, passing the trailing arguments
@@ -16167,6 +16210,21 @@ table.maxn({ a = 1 }) -- 0
Errors on non-table input.
### `table.removemulti(t [, pos [, count]])`
Removes `count` elements from `t`, starting at position `pos`, and returns
the removed elements in order. The elements after them move down to fill the
gap. `pos` is the last position by default, and `count` is 1 by default.
If `t` is empty, the function returns no values. If the range is not inside
the table, the function causes the error `parameters out of bounds`.
```lua
local t = { "a", "b", "c", "d", "e" }
table.removemulti(t, 2, 3) -- "b", "c", "d"
-- t is now { "a", "e" }
```
### Stale table lengths (5.1 healing)
Lua 5.0 stores a table's `table.insert` / `table.getn` length **out of
@@ -16253,7 +16311,8 @@ for word in string.gmatch("a,bb,ccc", "[^,]+") do print(word) end -- a / bb / cc
Both call forms work: `string.match(s, p)` and `s:match(p)`. See
[String methods](#string-methods-supper-sformat). `string.match` is also the
global `strmatch`, as on 3.x clients, alongside 1.12's own `strfind`, `strsub`
and the other globals for the 5.0 string functions.
and the other globals for the 5.0 string functions. `string.gmatch` is also the
global `gmatch`.
### `string.gsub` replacements
@@ -16486,6 +16545,16 @@ local best = math.huge
for _, v in ipairs(costs) do best = math.min(best, v) end
```
### `math.cosh(x)` / `math.sinh(x)` / `math.tanh(x)`
The hyperbolic cosine, sine and tangent of `x`.
```lua
math.cosh(0) -- 1
math.sinh(0) -- 0
math.tanh(1) -- 0.76159415595576
```
### `coroutine.*`
The Lua 5.1 `coroutine` library: `create`, `resume`, `running`,
+5
View File
@@ -73,12 +73,14 @@ namespace Lua {
F(GetTable, lua_gettable, LUA_GET_TABLE) \
F(RawGet, lua_rawget, LUA_RAW_GET) \
F(RawGetI, lua_rawgeti, LUA_RAWGETI) \
F(RawSetI, lua_rawseti, LUA_RAW_SET_I) \
F(SetTable, lua_settable, LUA_SET_TABLE) \
F(RawSet, lua_rawset, LUA_RAW_SET) \
F(GetMetatable,lua_getmetatable,LUA_GET_METATABLE)\
F(SetMetatable,lua_setmetatable,LUA_SET_METATABLE)\
F(Insert, lua_insert, LUA_INSERT) \
F(Remove, lua_remove, LUA_REMOVE) \
F(Replace, lua_replace, LUA_REPLACE) \
F(GetTop, lua_gettop, LUA_GET_TOP) \
F(SetTop, lua_settop, LUA_SET_TOP) \
F(Call, lua_call, LUA_CALL) \
@@ -98,6 +100,9 @@ namespace Lua {
F(Resume, lua_resume, LUA_RESUME) \
F(Yield, lua_yield, LUA_YIELD) \
F(ArgError, luaL_argerror, LUAL_ARG_ERROR) \
F(CheckLString,luaL_checklstring,LUAL_CHECK_LSTRING)\
F(CheckNumber, luaL_checknumber,LUAL_CHECK_NUMBER)\
F(OptNumber, luaL_optnumber, LUAL_OPT_NUMBER) \
F(SetN, luaL_setn, LUAL_SETN) \
F(Ref, luaL_ref, LUA_REF_REF) \
F(Unref, luaL_unref, LUA_REF_UNREF) \
+14
View File
@@ -270,6 +270,8 @@ using lua_rawget_t = void(__fastcall *)(void *L, int idx);
// Pushes t[n] for the table at idx, raw (no metamethods). With idx =
// REGISTRY_INDEX it pushes a value stored under an integer ref.
using lua_rawgeti_t = void(__fastcall *)(void *L, int idx, int n);
// Pops the top value into t[n] for the table at idx, raw.
using lua_rawseti_t = void(__fastcall *)(void *L, int idx, int n);
using lua_settable_t = void(__fastcall *)(void *L, int idx);
using lua_rawset_t = void(__fastcall *)(void *L, int idx);
// Pushes the metatable of the value at idx and returns 1, or pushes
@@ -280,6 +282,8 @@ using lua_getmetatable_t = int(__fastcall *)(void *L, int idx);
using lua_setmetatable_t = int(__fastcall *)(void *L, int idx);
using lua_insert_t = void(__fastcall *)(void *L, int idx);
using lua_remove_t = void(__fastcall *)(void *L, int idx);
// Pops the top value into slot idx.
using lua_replace_t = void(__fastcall *)(void *L, int idx);
using lua_gettop_t = int(__fastcall *)(void *L);
using lua_settop_t = void(__fastcall *)(void *L, int idx);
using lua_call_t = void(__fastcall *)(void *L, int nargs, int nresults);
@@ -313,6 +317,11 @@ using lua_newthread_t = void *(__fastcall *)(void *L);
using lua_resume_t = int(__fastcall *)(void *L, int nargs);
using lua_yield_t = int(__fastcall *)(void *L, int nresults);
using luaL_argerror_t = void(__fastcall *)(void *L, int narg, const char *msg);
// The auxlib argument readers: each raises the engine's own "bad argument"
// error. CheckLString's `len` may be null.
using luaL_checklstring_t = const char *(__fastcall *)(void *L, int narg, unsigned int *len);
using luaL_checknumber_t = double(__fastcall *)(void *L, int narg);
using luaL_optnumber_t = double(__fastcall *)(void *L, int narg, double def);
using luaL_setn_t = void(__fastcall *)(void *L, int t, int n);
// Pops the top value into the table at `t` under a fresh integer key and
// returns the key (LUA_REFNIL, -1, for a nil). With REGISTRY_INDEX it anchors
@@ -358,12 +367,14 @@ extern const lua_newtable_t NewTable;
extern const lua_gettable_t GetTable;
extern const lua_rawget_t RawGet;
extern const lua_rawgeti_t RawGetI;
extern const lua_rawseti_t RawSetI;
extern const lua_settable_t SetTable;
extern const lua_rawset_t RawSet;
extern const lua_getmetatable_t GetMetatable;
extern const lua_setmetatable_t SetMetatable;
extern const lua_insert_t Insert;
extern const lua_remove_t Remove;
extern const lua_replace_t Replace;
extern const lua_gettop_t GetTop;
extern const lua_settop_t SetTop;
extern const lua_call_t Call;
@@ -383,6 +394,9 @@ extern const lua_newthread_t NewThread;
extern const lua_resume_t Resume;
extern const lua_yield_t Yield;
extern const luaL_argerror_t ArgError;
extern const luaL_checklstring_t CheckLString;
extern const luaL_checknumber_t CheckNumber;
extern const luaL_optnumber_t OptNumber;
extern const luaL_setn_t SetN;
extern const luaL_ref_t Ref;
extern const luaL_unref_t Unref;
+6
View File
@@ -8360,6 +8360,9 @@ enum Offsets {
// CObjects — which is exactly why the engine pushes handler/frame
// through it rather than a plain rawget.
LUA_RAWGETI = 0x6F3BC0,
// `lua_rawseti(L, idx, n)` — __fastcall (n on the stack): t[n] = top,
// pops it. Calls luaH_setnum (0x006FAD80); stamps the GC context tag.
LUA_RAW_SET_I = 0x6F3F60,
// `luaA_indexAcceptable(L, idx)` — the API's index walk: stack, pseudo
// (registry / globals) or upvalue index → TObject*, or NULL for a
// positive index at or past the top. __fastcall(L /*ecx*/, idx /*edx*/),
@@ -8479,6 +8482,9 @@ enum Offsets {
// `luaL_optnumber(L, narg, double def)` — __fastcall (def on the stack,
// ret 8), returns the double in ST0. As str_gsub calls it (0x007FD12F).
LUAL_OPT_NUMBER = 0x6F4CD0,
// `luaL_checknumber(L, narg)` — __fastcall, returns the double in ST0.
// As str_format calls it (0x007FD570).
LUAL_CHECK_NUMBER = 0x6F4C80,
// `luaL_getn(L, tableIndex)` — Lua 5.0 auxlib table-length getter,
// `int __fastcall(L /*ecx*/, idx /*edx*/)`, plain RET (no stack
// args), length in eax. Verified by disassembly: a negative idx is
+58
View File
@@ -0,0 +1,58 @@
// 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/>.
// `difftime(t2 [, t1])` — the seconds from `t1` (0 by default) to `t2`, the
// companion of the engine's `date` and `time` globals. 4.3.4's
// Script_difftime: C `difftime` over the two arguments as time_t.
//
// Registered on BOTH states, as the engine registers `date` and `time`.
#include "Game.h"
#include <ctime>
namespace BaseLib::DiffTime {
namespace {
int __fastcall Script_difftime(void *L) {
const auto t2 = static_cast<time_t>(Game::Lua::CheckNumber(L, 1));
const auto t1 = static_cast<time_t>(Game::Lua::OptNumber(L, 2, 0));
Game::Lua::PushNumber(L, std::difftime(t2, t1));
return 1;
}
const Game::Doc::Field kDifftimeArgs[] = {
Game::Doc::Req("time2", "number", "A time in seconds, as `time()` returns."),
Game::Doc::Opt("time1", "number", "0"),
};
const Game::Doc::Field kDifftimeRets[] = {
Game::Doc::Req("seconds", "number", "time2 minus time1."),
};
const Game::Doc::Function kDifftime{"The number of seconds from time1 to time2.",
kDifftimeArgs, kDifftimeRets, "LuaGlobals"};
void RegisterInGame() {
Game::Lua::RegisterGlobalFunction("difftime", &Script_difftime, &kDifftime);
}
void RegisterGlue() {
Game::Lua::RegisterGlueFunction("difftime", &Script_difftime, &kDifftime);
}
const Game::ModuleAutoRegister _autoreg{&RegisterInGame};
const Game::GlueModuleAutoRegister _autoregGlue{&RegisterGlue};
} // namespace
} // namespace BaseLib::DiffTime
+22 -1
View File
@@ -16,13 +16,16 @@
// - `math.fmod(x, y)` — floating-point remainder of `x / y`.
// - `math.modf(x)` — split into integral + fractional parts.
// - `math.huge` — positive infinity constant.
// - `math.cosh(x)`, `math.sinh(x)`, `math.tanh(x)` — hyperbolic functions.
//
// `math.fmod` is a pure rename: Lua 5.0 ships the exact same C function under
// the name `math.mod`; 5.1 renamed the Lua-facing entry to `math.fmod`. We
// register it as a direct alias of the engine's `math.mod` C function
// (`FUN_LUA_MATH_FMOD`) — no wrapper. `math.mod` stays available too, so code
// written for either name works. `modf` and `huge` are genuinely new in 5.1
// (not in 1.12's mathlib table), so we implement/define them here.
// (not in 1.12's mathlib table), so we implement/define them here, as are the
// hyperbolic functions (5.1's math_cosh/sinh/tanh: luaL_checknumber, the C
// function, one number).
#include "Game.h"
#include "Offsets.h"
@@ -53,9 +56,27 @@ int __fastcall Script_math_modf(void *L) {
return 2;
}
int __fastcall Script_math_cosh(void *L) {
Game::Lua::PushNumber(L, std::cosh(Game::Lua::CheckNumber(L, 1)));
return 1;
}
int __fastcall Script_math_sinh(void *L) {
Game::Lua::PushNumber(L, std::sinh(Game::Lua::CheckNumber(L, 1)));
return 1;
}
int __fastcall Script_math_tanh(void *L) {
Game::Lua::PushNumber(L, std::tanh(Game::Lua::CheckNumber(L, 1)));
return 1;
}
void RegisterFns() {
Game::Lua::RegisterTableFunction("math", "fmod", Script_math_fmod);
Game::Lua::RegisterTableFunction("math", "modf", &Script_math_modf);
Game::Lua::RegisterTableFunction("math", "cosh", &Script_math_cosh);
Game::Lua::RegisterTableFunction("math", "sinh", &Script_math_sinh);
Game::Lua::RegisterTableFunction("math", "tanh", &Script_math_tanh);
// `math.huge` — a numeric constant, not a function. Fetch the (now
// guaranteed-present) `math` table and set the field directly.
+78
View File
@@ -0,0 +1,78 @@
// 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/>.
// `newproxy([false | true | proxy])` — Lua 5.1's empty userdata (luaB_newproxy,
// lbaselib.c). `false` or nothing: no metatable. `true`: a new metatable of
// its own. Another proxy: share that proxy's metatable. Only metatables made
// by `newproxy(true)` count; any other argument raises "boolean or proxy
// expected". 5.0 has userdata and their `__gc`, only not this function.
//
// As in 5.1, the closure's upvalue is a weak table that marks the valid
// metatables (weak[m] = true), and is its own metatable with __mode "kv".
//
// Registered on BOTH states (language-level helper, both are 5.0).
#include "Game.h"
namespace BaseLib::NewProxy {
namespace {
namespace Lua = Game::Lua;
int __fastcall Script_newproxy(void *L) {
Lua::SetTop(L, 1);
Lua::NewUserdata(L, 0); // the proxy, at 2
if (Lua::ToBoolean(L, 1) == 0)
return 1; // no metatable
if (Lua::Type(L, 1) == Lua::TYPE_BOOLEAN) {
Lua::NewTable(L); // a new metatable m ...
Lua::PushValue(L, -1);
Lua::PushBoolean(L, 1);
Lua::RawSet(L, Lua::UpvalueIndex(1)); // ... marked valid: weak[m] = true
} else {
bool valid = false;
if (Lua::GetMetatable(L, 1)) {
Lua::RawGet(L, Lua::UpvalueIndex(1));
valid = Lua::ToBoolean(L, -1) != 0;
Lua::SetTop(L, -2);
}
if (!valid)
Lua::ArgError(L, 1, "boolean or proxy expected");
Lua::GetMetatable(L, 1);
}
Lua::SetMetatable(L, 2);
return 1;
}
void Register() {
void *L = Lua::State();
if (L == nullptr)
return;
Lua::PushString(L, "newproxy");
Lua::NewTable(L); // the weak table w
Lua::PushString(L, "__mode");
Lua::PushString(L, "kv");
Lua::RawSet(L, -3);
Lua::PushValue(L, -1);
Lua::SetMetatable(L, -2); // w is its own metatable
Lua::PushCClosure(L, &Script_newproxy, 1);
Lua::SetTable(L, Lua::GLOBALS_INDEX);
}
const Game::ModuleAutoRegister _autoreg{&Register};
const Game::GlueModuleAutoRegister _autoregGlue{&Register};
} // namespace
} // namespace BaseLib::NewProxy
+63
View File
@@ -0,0 +1,63 @@
// 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/>.
// `scrub(...)` — returns its arguments, each one that is not a boolean,
// number or string replaced by nil. 4.3.4's Script_scrub.
//
// Registered on both Lua states, like the base-library helpers.
#include "Game.h"
namespace BaseLib::Scrub {
namespace {
namespace Lua = Game::Lua;
int __fastcall Script_scrub(void *L) {
const int top = Lua::GetTop(L);
Lua::CheckStack(L, 1);
for (int i = 1; i <= top; i++) {
const int type = Lua::Type(L, i);
if (type != Lua::TYPE_BOOLEAN && type != Lua::TYPE_NUMBER && type != Lua::TYPE_STRING) {
Lua::PushNil(L);
Lua::Replace(L, i);
}
}
return top;
}
const Game::Doc::Field kScrubArgs[] = {
Game::Doc::Vararg("any"),
};
const Game::Doc::Field kScrubRets[] = {
Game::Doc::Vararg("any", "Each argument, or nil when it is not a boolean, number or string."),
};
const Game::Doc::Function kScrub{
"Returns its arguments, with each one that is not a boolean, number or string replaced by nil.",
kScrubArgs, kScrubRets, "LuaGlobals"};
void RegisterInGame() {
Game::Lua::RegisterGlobalFunction("scrub", &Script_scrub, &kScrub);
}
void RegisterGlue() {
Game::Lua::RegisterGlueFunction("scrub", &Script_scrub, &kScrub);
}
const Game::ModuleAutoRegister _autoreg{&RegisterInGame};
const Game::GlueModuleAutoRegister _autoregGlue{&RegisterGlue};
} // namespace
} // namespace BaseLib::Scrub
+14 -16
View File
@@ -15,7 +15,7 @@
//
// - `string.match(s, pattern [, init])` — first-match extraction (5.1),
// also the global `strmatch`, as `string.find` is `strfind`.
// - `string.gmatch(s, pattern)` — match iterator (5.1).
// - `string.gmatch(s, pattern)` — match iterator (5.1), also `gmatch`.
// - `strsplit(sep, str [, pieces])` — WoW global; split on any char.
// - `strjoin(delimiter, ...)` — WoW global; join varargs.
// - `strtrim(str [, chars])` — WoW global; trim a char set.
@@ -347,8 +347,6 @@ int __fastcall Script_string_reverse(void *L) {
// result for every `i`. Positions truncate toward zero as the engine's
// str_byte and str_sub do. The engine's lua_checkstack (0x6F2F30) refuses
// more than 2048 stack slots, so the longest range is about 2000 bytes.
using luaL_checklstring_t = const char *(__fastcall *)(void *L, int narg, unsigned int *len);
using luaL_optnumber_t = double(__fastcall *)(void *L, int narg, double def);
// 5.1's posrelat: a negative position counts from the end; before the start is 0.
long PosRelat(long pos, long len) {
@@ -358,14 +356,12 @@ long PosRelat(long pos, long len) {
}
int __fastcall Script_string_byte(void *L) {
const auto checkLString =
reinterpret_cast<luaL_checklstring_t>(Offsets::LUAL_CHECK_LSTRING);
const auto optNumber = reinterpret_cast<luaL_optnumber_t>(Offsets::LUAL_OPT_NUMBER);
unsigned int l = 0;
const char *s = checkLString(L, 1, &l);
const char *s = Game::Lua::CheckLString(L, 1, &l);
const long len = static_cast<long>(l);
long posi = PosRelat(static_cast<long>(optNumber(L, 2, 1)), len);
long pose = PosRelat(static_cast<long>(optNumber(L, 3, static_cast<double>(posi))), len);
long posi = PosRelat(static_cast<long>(Game::Lua::OptNumber(L, 2, 1)), len);
long pose =
PosRelat(static_cast<long>(Game::Lua::OptNumber(L, 3, static_cast<double>(posi))), len);
if (posi <= 0)
posi = 1;
if (pose > len)
@@ -439,13 +435,15 @@ void RegisterFns() {
Game::Lua::RegisterTableFunction("string", "join", &Script_strjoin);
Game::Lua::RegisterTableFunction("string", "concat", &Script_strconcat, &kStrconcat);
// 4.3.4's compat.lua binds each short global and its string-table twin
// to ONE function by assignment (`strmatch = str.match`, `strrev =
// str.reverse`, `str.trim = strtrim`, `str.split = strsplit`, `str.join =
// strjoin`), so `strtrim == string.trim`. A second Register* call would
// mint a second closure over the same C function, so the globals alias
// the table entries by value instead. The string-table half is what lets
// `(" x "):trim()` work and libraries capture it at file scope
// (AceConfigCmd-3.0: `strtrim = string.trim`).
// to ONE function by assignment (`gmatch = str.gmatch`, `strmatch =
// str.match`, `strrev = str.reverse`, `str.trim = strtrim`, `str.split =
// strsplit`, `str.join = strjoin`), so `strtrim == string.trim`. A second
// Register* call would mint a second closure over the same C function, so
// the globals alias the table entries by value instead. The string-table
// half is what lets `(" x "):trim()` work and libraries capture it at
// file scope (AceConfigCmd-3.0: `strtrim = string.trim`). 1.12's own
// compat.lua set `strbyte` from the old string.byte, so it is re-aliased.
Game::Lua::RegisterGlobalAlias("gmatch", "string", "gmatch");
Game::Lua::RegisterGlobalAlias("strmatch", "string", "match");
Game::Lua::RegisterGlobalAlias("strsplit", "string", "split");
Game::Lua::RegisterGlobalAlias("strjoin", "string", "join");
+68
View File
@@ -0,0 +1,68 @@
// 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/>.
// `table.removemulti(t [, pos [, count]])` — WoW's addition to the table
// library (4.3.4's tremovemulti, ltablib.c): removes `count` elements (1 by
// default) from position `pos` (the last element by default), shifts the
// rest down and returns the removed elements in order.
//
// 5.0 keeps a table's length apart from its keys, so like the engine's own
// table.remove (0x7FB770) this records the new length with luaL_setn.
//
// Registered on BOTH states (language-level helper, both are 5.0).
#include "Game.h"
#include "Offsets.h"
namespace Table::RemoveMulti {
namespace {
using luaL_getn_t = int(__fastcall *)(void *L, int t);
int __fastcall Script_table_removemulti(void *L) {
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE)
Game::Lua::ArgError(L, 1, "table expected");
const int e = reinterpret_cast<luaL_getn_t>(Offsets::LUAL_GETN)(L, 1);
int pos = static_cast<int>(Game::Lua::OptNumber(L, 2, e));
const int n = static_cast<int>(Game::Lua::OptNumber(L, 3, 1));
if (e == 0)
return 0;
if (pos <= 0 || n < 0 || pos > e || pos + n - 1 > e)
Game::Lua::Error(L, "parameters out of bounds");
if (!Game::Lua::CheckStack(L, n))
Game::Lua::Error(L, "stack overflow");
for (int i = pos; i < pos + n; i++)
Game::Lua::RawGetI(L, 1, i);
for (; pos < e; pos++) {
Game::Lua::RawGetI(L, 1, pos + n);
Game::Lua::RawSetI(L, 1, pos);
}
for (; pos < e + n; pos++) {
Game::Lua::PushNil(L);
Game::Lua::RawSetI(L, 1, pos);
}
Game::Lua::SetN(L, 1, e - n);
return n;
}
void Register() {
Game::Lua::RegisterTableFunction("table", "removemulti", &Script_table_removemulti);
}
const Game::ModuleAutoRegister _autoreg{&Register};
const Game::GlueModuleAutoRegister _autoregGlue{&Register};
} // namespace
} // namespace Table::RemoveMulti