From 8ab29add6ff511d8dd6482c8f80ea24defed3291 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 02:40:32 -0700 Subject: [PATCH 001/124] Test implementation of lua --- Makefile.build | 4 +- arm9/Makefile | 3 +- arm9/link.ld | 6 +- arm9/source/lua/lapi.c | 1463 ++++++++++++++++++++++++ arm9/source/lua/lapi.h | 52 + arm9/source/lua/lauxlib.c | 1112 +++++++++++++++++++ arm9/source/lua/lauxlib.h | 301 +++++ arm9/source/lua/lbaselib.c | 549 +++++++++ arm9/source/lua/lcode.c | 1871 +++++++++++++++++++++++++++++++ arm9/source/lua/lcode.h | 104 ++ arm9/source/lua/lcorolib.c | 210 ++++ arm9/source/lua/lctype.c | 64 ++ arm9/source/lua/lctype.h | 101 ++ arm9/source/lua/ldblib.c | 483 ++++++++ arm9/source/lua/ldebug.c | 924 ++++++++++++++++ arm9/source/lua/ldebug.h | 63 ++ arm9/source/lua/ldo.c | 1024 +++++++++++++++++ arm9/source/lua/ldo.h | 88 ++ arm9/source/lua/ldump.c | 230 ++++ arm9/source/lua/lfunc.c | 294 +++++ arm9/source/lua/lfunc.h | 64 ++ arm9/source/lua/lgc.c | 1739 +++++++++++++++++++++++++++++ arm9/source/lua/lgc.h | 202 ++++ arm9/source/lua/linit.c | 65 ++ arm9/source/lua/liolib.c | 828 ++++++++++++++ arm9/source/lua/ljumptab.h | 112 ++ arm9/source/lua/llex.c | 581 ++++++++++ arm9/source/lua/llex.h | 91 ++ arm9/source/lua/llimits.h | 380 +++++++ arm9/source/lua/lmathlib.c | 764 +++++++++++++ arm9/source/lua/lmem.c | 215 ++++ arm9/source/lua/lmem.h | 93 ++ arm9/source/lua/loadlib.c | 767 +++++++++++++ arm9/source/lua/lobject.c | 602 ++++++++++ arm9/source/lua/lobject.h | 815 ++++++++++++++ arm9/source/lua/lopcodes.c | 104 ++ arm9/source/lua/lopcodes.h | 405 +++++++ arm9/source/lua/lopnames.h | 103 ++ arm9/source/lua/loslib.c | 428 +++++++ arm9/source/lua/lparser.c | 1967 +++++++++++++++++++++++++++++++++ arm9/source/lua/lparser.h | 171 +++ arm9/source/lua/lprefix.h | 45 + arm9/source/lua/lstate.c | 445 ++++++++ arm9/source/lua/lstate.h | 409 +++++++ arm9/source/lua/lstring.c | 273 +++++ arm9/source/lua/lstring.h | 57 + arm9/source/lua/lstrlib.c | 1874 +++++++++++++++++++++++++++++++ arm9/source/lua/ltable.c | 980 ++++++++++++++++ arm9/source/lua/ltable.h | 65 ++ arm9/source/lua/ltablib.c | 430 +++++++ arm9/source/lua/ltm.c | 271 +++++ arm9/source/lua/ltm.h | 104 ++ arm9/source/lua/lua.h | 523 +++++++++ arm9/source/lua/lua.hpp | 9 + arm9/source/lua/luaconf.h | 793 +++++++++++++ arm9/source/lua/lualib.h | 52 + arm9/source/lua/lundump.c | 335 ++++++ arm9/source/lua/lundump.h | 36 + arm9/source/lua/lutf8lib.c | 291 +++++ arm9/source/lua/lvm.c | 1901 +++++++++++++++++++++++++++++++ arm9/source/lua/lvm.h | 141 +++ arm9/source/lua/lzio.c | 68 ++ arm9/source/lua/lzio.h | 66 ++ arm9/source/utils/scripting.c | 70 +- init.lua | 9 + lua.gm9 | 1 + 66 files changed, 28685 insertions(+), 5 deletions(-) create mode 100644 arm9/source/lua/lapi.c create mode 100644 arm9/source/lua/lapi.h create mode 100644 arm9/source/lua/lauxlib.c create mode 100644 arm9/source/lua/lauxlib.h create mode 100644 arm9/source/lua/lbaselib.c create mode 100644 arm9/source/lua/lcode.c create mode 100644 arm9/source/lua/lcode.h create mode 100644 arm9/source/lua/lcorolib.c create mode 100644 arm9/source/lua/lctype.c create mode 100644 arm9/source/lua/lctype.h create mode 100644 arm9/source/lua/ldblib.c create mode 100644 arm9/source/lua/ldebug.c create mode 100644 arm9/source/lua/ldebug.h create mode 100644 arm9/source/lua/ldo.c create mode 100644 arm9/source/lua/ldo.h create mode 100644 arm9/source/lua/ldump.c create mode 100644 arm9/source/lua/lfunc.c create mode 100644 arm9/source/lua/lfunc.h create mode 100644 arm9/source/lua/lgc.c create mode 100644 arm9/source/lua/lgc.h create mode 100644 arm9/source/lua/linit.c create mode 100644 arm9/source/lua/liolib.c create mode 100644 arm9/source/lua/ljumptab.h create mode 100644 arm9/source/lua/llex.c create mode 100644 arm9/source/lua/llex.h create mode 100644 arm9/source/lua/llimits.h create mode 100644 arm9/source/lua/lmathlib.c create mode 100644 arm9/source/lua/lmem.c create mode 100644 arm9/source/lua/lmem.h create mode 100644 arm9/source/lua/loadlib.c create mode 100644 arm9/source/lua/lobject.c create mode 100644 arm9/source/lua/lobject.h create mode 100644 arm9/source/lua/lopcodes.c create mode 100644 arm9/source/lua/lopcodes.h create mode 100644 arm9/source/lua/lopnames.h create mode 100644 arm9/source/lua/loslib.c create mode 100644 arm9/source/lua/lparser.c create mode 100644 arm9/source/lua/lparser.h create mode 100644 arm9/source/lua/lprefix.h create mode 100644 arm9/source/lua/lstate.c create mode 100644 arm9/source/lua/lstate.h create mode 100644 arm9/source/lua/lstring.c create mode 100644 arm9/source/lua/lstring.h create mode 100644 arm9/source/lua/lstrlib.c create mode 100644 arm9/source/lua/ltable.c create mode 100644 arm9/source/lua/ltable.h create mode 100644 arm9/source/lua/ltablib.c create mode 100644 arm9/source/lua/ltm.c create mode 100644 arm9/source/lua/ltm.h create mode 100644 arm9/source/lua/lua.h create mode 100644 arm9/source/lua/lua.hpp create mode 100644 arm9/source/lua/luaconf.h create mode 100644 arm9/source/lua/lualib.h create mode 100644 arm9/source/lua/lundump.c create mode 100644 arm9/source/lua/lundump.h create mode 100644 arm9/source/lua/lutf8lib.c create mode 100644 arm9/source/lua/lvm.c create mode 100644 arm9/source/lua/lvm.h create mode 100644 arm9/source/lua/lzio.c create mode 100644 arm9/source/lua/lzio.h create mode 100755 init.lua create mode 100755 lua.gm9 diff --git a/Makefile.build b/Makefile.build index 0a36f6fed..a8f80e625 100755 --- a/Makefile.build +++ b/Makefile.build @@ -1,4 +1,6 @@ +LIBS ?= + OBJECTS := $(patsubst $(SOURCE)/%.s, $(BUILD)/%.o, \ $(patsubst $(SOURCE)/%.c, $(BUILD)/%.o, \ $(call rwildcard, $(SOURCE), *.s *.c))) @@ -15,7 +17,7 @@ clean: $(TARGET).elf: $(OBJECTS) $(OBJECTS_COMMON) @mkdir -p "$(@D)" - @$(CC) $(LDFLAGS) $^ -o $@ + @$(CC) $(LDFLAGS) $^ -o $@ $(LIBS) $(BUILD)/%.cmn.o: $(COMMON_DIR)/%.c @mkdir -p "$(@D)" diff --git a/arm9/Makefile b/arm9/Makefile index c631b1a27..69bccdd00 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -6,12 +6,13 @@ SOURCE := source BUILD := build SUBARCH := -D$(PROCESSOR) -march=armv5te -mtune=arm946e-s -mthumb -mfloat-abi=soft -INCDIRS := source source/common source/filesys source/crypto source/fatfs source/nand source/virtual source/game source/gamecart source/lodepng source/qrcodegen source/system source/utils +INCDIRS := source source/common source/filesys source/crypto source/fatfs source/nand source/virtual source/game source/gamecart source/lodepng source/lua source/qrcodegen source/system source/utils INCLUDE := $(foreach dir,$(INCDIRS),-I"$(shell pwd)/$(dir)") ASFLAGS += $(SUBARCH) $(INCLUDE) CFLAGS += $(SUBARCH) $(INCLUDE) -fno-builtin-memcpy -flto LDFLAGS += $(SUBARCH) -Wl,--use-blx,-Map,$(TARGET).map -flto +LIBS += -lm include ../Makefile.common include ../Makefile.build diff --git a/arm9/link.ld b/arm9/link.ld index e9f0a08dd..a8bfacdb3 100644 --- a/arm9/link.ld +++ b/arm9/link.ld @@ -4,7 +4,7 @@ ENTRY(_start) MEMORY { - AHBWRAM (RWX) : ORIGIN = 0x08006000, LENGTH = 512K + AHBWRAM (RWX) : ORIGIN = 0x08006000, LENGTH = 744K VECTORS (RX) : ORIGIN = 0x08000000, LENGTH = 64 } @@ -29,6 +29,10 @@ SECTIONS .rodata : ALIGN(4) { *(.rodata*); . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); } >AHBWRAM .data : ALIGN(4) { diff --git a/arm9/source/lua/lapi.c b/arm9/source/lua/lapi.c new file mode 100644 index 000000000..34e64af14 --- /dev/null +++ b/arm9/source/lua/lapi.c @@ -0,0 +1,1463 @@ +/* +** $Id: lapi.c $ +** Lua API +** See Copyright Notice in lua.h +*/ + +#define lapi_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" + + + +const char lua_ident[] = + "$LuaVersion: " LUA_COPYRIGHT " $" + "$LuaAuthors: " LUA_AUTHORS " $"; + + + +/* +** Test for a valid index (one that is not the 'nilvalue'). +** '!ttisnil(o)' implies 'o != &G(L)->nilvalue', so it is not needed. +** However, it covers the most common cases in a faster way. +*/ +#define isvalid(L, o) (!ttisnil(o) || o != &G(L)->nilvalue) + + +/* test for pseudo index */ +#define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) + +/* test for upvalue */ +#define isupvalue(i) ((i) < LUA_REGISTRYINDEX) + + +/* +** Convert an acceptable index to a pointer to its respective value. +** Non-valid indices return the special nil value 'G(L)->nilvalue'. +*/ +static TValue *index2value (lua_State *L, int idx) { + CallInfo *ci = L->ci; + if (idx > 0) { + StkId o = ci->func.p + idx; + api_check(L, idx <= ci->top.p - (ci->func.p + 1), "unacceptable index"); + if (o >= L->top.p) return &G(L)->nilvalue; + else return s2v(o); + } + else if (!ispseudo(idx)) { /* negative index */ + api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1), + "invalid index"); + return s2v(L->top.p + idx); + } + else if (idx == LUA_REGISTRYINDEX) + return &G(L)->l_registry; + else { /* upvalues */ + idx = LUA_REGISTRYINDEX - idx; + api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large"); + if (ttisCclosure(s2v(ci->func.p))) { /* C closure? */ + CClosure *func = clCvalue(s2v(ci->func.p)); + return (idx <= func->nupvalues) ? &func->upvalue[idx-1] + : &G(L)->nilvalue; + } + else { /* light C function or Lua function (through a hook)?) */ + api_check(L, ttislcf(s2v(ci->func.p)), "caller not a C function"); + return &G(L)->nilvalue; /* no upvalues */ + } + } +} + + + +/* +** Convert a valid actual index (not a pseudo-index) to its address. +*/ +l_sinline StkId index2stack (lua_State *L, int idx) { + CallInfo *ci = L->ci; + if (idx > 0) { + StkId o = ci->func.p + idx; + api_check(L, o < L->top.p, "invalid index"); + return o; + } + else { /* non-positive index */ + api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1), + "invalid index"); + api_check(L, !ispseudo(idx), "invalid index"); + return L->top.p + idx; + } +} + + +LUA_API int lua_checkstack (lua_State *L, int n) { + int res; + CallInfo *ci; + lua_lock(L); + ci = L->ci; + api_check(L, n >= 0, "negative 'n'"); + if (L->stack_last.p - L->top.p > n) /* stack large enough? */ + res = 1; /* yes; check is OK */ + else /* need to grow stack */ + res = luaD_growstack(L, n, 0); + if (res && ci->top.p < L->top.p + n) + ci->top.p = L->top.p + n; /* adjust frame top */ + lua_unlock(L); + return res; +} + + +LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { + int i; + if (from == to) return; + lua_lock(to); + api_checknelems(from, n); + api_check(from, G(from) == G(to), "moving among independent states"); + api_check(from, to->ci->top.p - to->top.p >= n, "stack overflow"); + from->top.p -= n; + for (i = 0; i < n; i++) { + setobjs2s(to, to->top.p, from->top.p + i); + to->top.p++; /* stack already checked by previous 'api_check' */ + } + lua_unlock(to); +} + + +LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) { + lua_CFunction old; + lua_lock(L); + old = G(L)->panic; + G(L)->panic = panicf; + lua_unlock(L); + return old; +} + + +LUA_API lua_Number lua_version (lua_State *L) { + UNUSED(L); + return LUA_VERSION_NUM; +} + + + +/* +** basic stack manipulation +*/ + + +/* +** convert an acceptable stack index into an absolute index +*/ +LUA_API int lua_absindex (lua_State *L, int idx) { + return (idx > 0 || ispseudo(idx)) + ? idx + : cast_int(L->top.p - L->ci->func.p) + idx; +} + + +LUA_API int lua_gettop (lua_State *L) { + return cast_int(L->top.p - (L->ci->func.p + 1)); +} + + +LUA_API void lua_settop (lua_State *L, int idx) { + CallInfo *ci; + StkId func, newtop; + ptrdiff_t diff; /* difference for new top */ + lua_lock(L); + ci = L->ci; + func = ci->func.p; + if (idx >= 0) { + api_check(L, idx <= ci->top.p - (func + 1), "new top too large"); + diff = ((func + 1) + idx) - L->top.p; + for (; diff > 0; diff--) + setnilvalue(s2v(L->top.p++)); /* clear new slots */ + } + else { + api_check(L, -(idx+1) <= (L->top.p - (func + 1)), "invalid new top"); + diff = idx + 1; /* will "subtract" index (as it is negative) */ + } + api_check(L, L->tbclist.p < L->top.p, "previous pop of an unclosed slot"); + newtop = L->top.p + diff; + if (diff < 0 && L->tbclist.p >= newtop) { + lua_assert(hastocloseCfunc(ci->nresults)); + newtop = luaF_close(L, newtop, CLOSEKTOP, 0); + } + L->top.p = newtop; /* correct top only after closing any upvalue */ + lua_unlock(L); +} + + +LUA_API void lua_closeslot (lua_State *L, int idx) { + StkId level; + lua_lock(L); + level = index2stack(L, idx); + api_check(L, hastocloseCfunc(L->ci->nresults) && L->tbclist.p == level, + "no variable to close at given level"); + level = luaF_close(L, level, CLOSEKTOP, 0); + setnilvalue(s2v(level)); + lua_unlock(L); +} + + +/* +** Reverse the stack segment from 'from' to 'to' +** (auxiliary to 'lua_rotate') +** Note that we move(copy) only the value inside the stack. +** (We do not move additional fields that may exist.) +*/ +l_sinline void reverse (lua_State *L, StkId from, StkId to) { + for (; from < to; from++, to--) { + TValue temp; + setobj(L, &temp, s2v(from)); + setobjs2s(L, from, to); + setobj2s(L, to, &temp); + } +} + + +/* +** Let x = AB, where A is a prefix of length 'n'. Then, +** rotate x n == BA. But BA == (A^r . B^r)^r. +*/ +LUA_API void lua_rotate (lua_State *L, int idx, int n) { + StkId p, t, m; + lua_lock(L); + t = L->top.p - 1; /* end of stack segment being rotated */ + p = index2stack(L, idx); /* start of segment */ + api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'"); + m = (n >= 0 ? t - n : p - n - 1); /* end of prefix */ + reverse(L, p, m); /* reverse the prefix with length 'n' */ + reverse(L, m + 1, t); /* reverse the suffix */ + reverse(L, p, t); /* reverse the entire segment */ + lua_unlock(L); +} + + +LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) { + TValue *fr, *to; + lua_lock(L); + fr = index2value(L, fromidx); + to = index2value(L, toidx); + api_check(L, isvalid(L, to), "invalid index"); + setobj(L, to, fr); + if (isupvalue(toidx)) /* function upvalue? */ + luaC_barrier(L, clCvalue(s2v(L->ci->func.p)), fr); + /* LUA_REGISTRYINDEX does not need gc barrier + (collector revisits it before finishing collection) */ + lua_unlock(L); +} + + +LUA_API void lua_pushvalue (lua_State *L, int idx) { + lua_lock(L); + setobj2s(L, L->top.p, index2value(L, idx)); + api_incr_top(L); + lua_unlock(L); +} + + + +/* +** access functions (stack -> C) +*/ + + +LUA_API int lua_type (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (isvalid(L, o) ? ttype(o) : LUA_TNONE); +} + + +LUA_API const char *lua_typename (lua_State *L, int t) { + UNUSED(L); + api_check(L, LUA_TNONE <= t && t < LUA_NUMTYPES, "invalid type"); + return ttypename(t); +} + + +LUA_API int lua_iscfunction (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (ttislcf(o) || (ttisCclosure(o))); +} + + +LUA_API int lua_isinteger (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return ttisinteger(o); +} + + +LUA_API int lua_isnumber (lua_State *L, int idx) { + lua_Number n; + const TValue *o = index2value(L, idx); + return tonumber(o, &n); +} + + +LUA_API int lua_isstring (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (ttisstring(o) || cvt2str(o)); +} + + +LUA_API int lua_isuserdata (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (ttisfulluserdata(o) || ttislightuserdata(o)); +} + + +LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { + const TValue *o1 = index2value(L, index1); + const TValue *o2 = index2value(L, index2); + return (isvalid(L, o1) && isvalid(L, o2)) ? luaV_rawequalobj(o1, o2) : 0; +} + + +LUA_API void lua_arith (lua_State *L, int op) { + lua_lock(L); + if (op != LUA_OPUNM && op != LUA_OPBNOT) + api_checknelems(L, 2); /* all other operations expect two operands */ + else { /* for unary operations, add fake 2nd operand */ + api_checknelems(L, 1); + setobjs2s(L, L->top.p, L->top.p - 1); + api_incr_top(L); + } + /* first operand at top - 2, second at top - 1; result go to top - 2 */ + luaO_arith(L, op, s2v(L->top.p - 2), s2v(L->top.p - 1), L->top.p - 2); + L->top.p--; /* remove second operand */ + lua_unlock(L); +} + + +LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { + const TValue *o1; + const TValue *o2; + int i = 0; + lua_lock(L); /* may call tag method */ + o1 = index2value(L, index1); + o2 = index2value(L, index2); + if (isvalid(L, o1) && isvalid(L, o2)) { + switch (op) { + case LUA_OPEQ: i = luaV_equalobj(L, o1, o2); break; + case LUA_OPLT: i = luaV_lessthan(L, o1, o2); break; + case LUA_OPLE: i = luaV_lessequal(L, o1, o2); break; + default: api_check(L, 0, "invalid option"); + } + } + lua_unlock(L); + return i; +} + + +LUA_API size_t lua_stringtonumber (lua_State *L, const char *s) { + size_t sz = luaO_str2num(s, s2v(L->top.p)); + if (sz != 0) + api_incr_top(L); + return sz; +} + + +LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum) { + lua_Number n = 0; + const TValue *o = index2value(L, idx); + int isnum = tonumber(o, &n); + if (pisnum) + *pisnum = isnum; + return n; +} + + +LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum) { + lua_Integer res = 0; + const TValue *o = index2value(L, idx); + int isnum = tointeger(o, &res); + if (pisnum) + *pisnum = isnum; + return res; +} + + +LUA_API int lua_toboolean (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return !l_isfalse(o); +} + + +LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { + TValue *o; + lua_lock(L); + o = index2value(L, idx); + if (!ttisstring(o)) { + if (!cvt2str(o)) { /* not convertible? */ + if (len != NULL) *len = 0; + lua_unlock(L); + return NULL; + } + luaO_tostring(L, o); + luaC_checkGC(L); + o = index2value(L, idx); /* previous call may reallocate the stack */ + } + if (len != NULL) + *len = vslen(o); + lua_unlock(L); + return svalue(o); +} + + +LUA_API lua_Unsigned lua_rawlen (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + switch (ttypetag(o)) { + case LUA_VSHRSTR: return tsvalue(o)->shrlen; + case LUA_VLNGSTR: return tsvalue(o)->u.lnglen; + case LUA_VUSERDATA: return uvalue(o)->len; + case LUA_VTABLE: return luaH_getn(hvalue(o)); + default: return 0; + } +} + + +LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + if (ttislcf(o)) return fvalue(o); + else if (ttisCclosure(o)) + return clCvalue(o)->f; + else return NULL; /* not a C function */ +} + + +l_sinline void *touserdata (const TValue *o) { + switch (ttype(o)) { + case LUA_TUSERDATA: return getudatamem(uvalue(o)); + case LUA_TLIGHTUSERDATA: return pvalue(o); + default: return NULL; + } +} + + +LUA_API void *lua_touserdata (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return touserdata(o); +} + + +LUA_API lua_State *lua_tothread (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return (!ttisthread(o)) ? NULL : thvalue(o); +} + + +/* +** Returns a pointer to the internal representation of an object. +** Note that ANSI C does not allow the conversion of a pointer to +** function to a 'void*', so the conversion here goes through +** a 'size_t'. (As the returned pointer is only informative, this +** conversion should not be a problem.) +*/ +LUA_API const void *lua_topointer (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + switch (ttypetag(o)) { + case LUA_VLCF: return cast_voidp(cast_sizet(fvalue(o))); + case LUA_VUSERDATA: case LUA_VLIGHTUSERDATA: + return touserdata(o); + default: { + if (iscollectable(o)) + return gcvalue(o); + else + return NULL; + } + } +} + + + +/* +** push functions (C -> stack) +*/ + + +LUA_API void lua_pushnil (lua_State *L) { + lua_lock(L); + setnilvalue(s2v(L->top.p)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { + lua_lock(L); + setfltvalue(s2v(L->top.p), n); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { + lua_lock(L); + setivalue(s2v(L->top.p), n); + api_incr_top(L); + lua_unlock(L); +} + + +/* +** Pushes on the stack a string with given length. Avoid using 's' when +** 'len' == 0 (as 's' can be NULL in that case), due to later use of +** 'memcmp' and 'memcpy'. +*/ +LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { + TString *ts; + lua_lock(L); + ts = (len == 0) ? luaS_new(L, "") : luaS_newlstr(L, s, len); + setsvalue2s(L, L->top.p, ts); + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return getstr(ts); +} + + +LUA_API const char *lua_pushstring (lua_State *L, const char *s) { + lua_lock(L); + if (s == NULL) + setnilvalue(s2v(L->top.p)); + else { + TString *ts; + ts = luaS_new(L, s); + setsvalue2s(L, L->top.p, ts); + s = getstr(ts); /* internal copy's address */ + } + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return s; +} + + +LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, + va_list argp) { + const char *ret; + lua_lock(L); + ret = luaO_pushvfstring(L, fmt, argp); + luaC_checkGC(L); + lua_unlock(L); + return ret; +} + + +LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { + const char *ret; + va_list argp; + lua_lock(L); + va_start(argp, fmt); + ret = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + luaC_checkGC(L); + lua_unlock(L); + return ret; +} + + +LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { + lua_lock(L); + if (n == 0) { + setfvalue(s2v(L->top.p), fn); + api_incr_top(L); + } + else { + CClosure *cl; + api_checknelems(L, n); + api_check(L, n <= MAXUPVAL, "upvalue index too large"); + cl = luaF_newCclosure(L, n); + cl->f = fn; + L->top.p -= n; + while (n--) { + setobj2n(L, &cl->upvalue[n], s2v(L->top.p + n)); + /* does not need barrier because closure is white */ + lua_assert(iswhite(cl)); + } + setclCvalue(L, s2v(L->top.p), cl); + api_incr_top(L); + luaC_checkGC(L); + } + lua_unlock(L); +} + + +LUA_API void lua_pushboolean (lua_State *L, int b) { + lua_lock(L); + if (b) + setbtvalue(s2v(L->top.p)); + else + setbfvalue(s2v(L->top.p)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { + lua_lock(L); + setpvalue(s2v(L->top.p), p); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API int lua_pushthread (lua_State *L) { + lua_lock(L); + setthvalue(L, s2v(L->top.p), L); + api_incr_top(L); + lua_unlock(L); + return (G(L)->mainthread == L); +} + + + +/* +** get functions (Lua -> stack) +*/ + + +l_sinline int auxgetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + setobj2s(L, L->top.p, slot); + api_incr_top(L); + } + else { + setsvalue2s(L, L->top.p, str); + api_incr_top(L); + luaV_finishget(L, t, s2v(L->top.p - 1), L->top.p - 1, slot); + } + lua_unlock(L); + return ttype(s2v(L->top.p - 1)); +} + + +/* +** Get the global table in the registry. Since all predefined +** indices in the registry were inserted right when the registry +** was created and never removed, they must always be in the array +** part of the registry. +*/ +#define getGtable(L) \ + (&hvalue(&G(L)->l_registry)->array[LUA_RIDX_GLOBALS - 1]) + + +LUA_API int lua_getglobal (lua_State *L, const char *name) { + const TValue *G; + lua_lock(L); + G = getGtable(L); + return auxgetstr(L, G, name); +} + + +LUA_API int lua_gettable (lua_State *L, int idx) { + const TValue *slot; + TValue *t; + lua_lock(L); + t = index2value(L, idx); + if (luaV_fastget(L, t, s2v(L->top.p - 1), slot, luaH_get)) { + setobj2s(L, L->top.p - 1, slot); + } + else + luaV_finishget(L, t, s2v(L->top.p - 1), L->top.p - 1, slot); + lua_unlock(L); + return ttype(s2v(L->top.p - 1)); +} + + +LUA_API int lua_getfield (lua_State *L, int idx, const char *k) { + lua_lock(L); + return auxgetstr(L, index2value(L, idx), k); +} + + +LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n) { + TValue *t; + const TValue *slot; + lua_lock(L); + t = index2value(L, idx); + if (luaV_fastgeti(L, t, n, slot)) { + setobj2s(L, L->top.p, slot); + } + else { + TValue aux; + setivalue(&aux, n); + luaV_finishget(L, t, &aux, L->top.p, slot); + } + api_incr_top(L); + lua_unlock(L); + return ttype(s2v(L->top.p - 1)); +} + + +l_sinline int finishrawget (lua_State *L, const TValue *val) { + if (isempty(val)) /* avoid copying empty items to the stack */ + setnilvalue(s2v(L->top.p)); + else + setobj2s(L, L->top.p, val); + api_incr_top(L); + lua_unlock(L); + return ttype(s2v(L->top.p - 1)); +} + + +static Table *gettable (lua_State *L, int idx) { + TValue *t = index2value(L, idx); + api_check(L, ttistable(t), "table expected"); + return hvalue(t); +} + + +LUA_API int lua_rawget (lua_State *L, int idx) { + Table *t; + const TValue *val; + lua_lock(L); + api_checknelems(L, 1); + t = gettable(L, idx); + val = luaH_get(t, s2v(L->top.p - 1)); + L->top.p--; /* remove key */ + return finishrawget(L, val); +} + + +LUA_API int lua_rawgeti (lua_State *L, int idx, lua_Integer n) { + Table *t; + lua_lock(L); + t = gettable(L, idx); + return finishrawget(L, luaH_getint(t, n)); +} + + +LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) { + Table *t; + TValue k; + lua_lock(L); + t = gettable(L, idx); + setpvalue(&k, cast_voidp(p)); + return finishrawget(L, luaH_get(t, &k)); +} + + +LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { + Table *t; + lua_lock(L); + t = luaH_new(L); + sethvalue2s(L, L->top.p, t); + api_incr_top(L); + if (narray > 0 || nrec > 0) + luaH_resize(L, t, narray, nrec); + luaC_checkGC(L); + lua_unlock(L); +} + + +LUA_API int lua_getmetatable (lua_State *L, int objindex) { + const TValue *obj; + Table *mt; + int res = 0; + lua_lock(L); + obj = index2value(L, objindex); + switch (ttype(obj)) { + case LUA_TTABLE: + mt = hvalue(obj)->metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(obj)->metatable; + break; + default: + mt = G(L)->mt[ttype(obj)]; + break; + } + if (mt != NULL) { + sethvalue2s(L, L->top.p, mt); + api_incr_top(L); + res = 1; + } + lua_unlock(L); + return res; +} + + +LUA_API int lua_getiuservalue (lua_State *L, int idx, int n) { + TValue *o; + int t; + lua_lock(L); + o = index2value(L, idx); + api_check(L, ttisfulluserdata(o), "full userdata expected"); + if (n <= 0 || n > uvalue(o)->nuvalue) { + setnilvalue(s2v(L->top.p)); + t = LUA_TNONE; + } + else { + setobj2s(L, L->top.p, &uvalue(o)->uv[n - 1].uv); + t = ttype(s2v(L->top.p)); + } + api_incr_top(L); + lua_unlock(L); + return t; +} + + +/* +** set functions (stack -> Lua) +*/ + +/* +** t[k] = value at the top of the stack (where 'k' is a string) +*/ +static void auxsetstr (lua_State *L, const TValue *t, const char *k) { + const TValue *slot; + TString *str = luaS_new(L, k); + api_checknelems(L, 1); + if (luaV_fastget(L, t, str, slot, luaH_getstr)) { + luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); + L->top.p--; /* pop value */ + } + else { + setsvalue2s(L, L->top.p, str); /* push 'str' (to make it a TValue) */ + api_incr_top(L); + luaV_finishset(L, t, s2v(L->top.p - 1), s2v(L->top.p - 2), slot); + L->top.p -= 2; /* pop value and key */ + } + lua_unlock(L); /* lock done by caller */ +} + + +LUA_API void lua_setglobal (lua_State *L, const char *name) { + const TValue *G; + lua_lock(L); /* unlock done in 'auxsetstr' */ + G = getGtable(L); + auxsetstr(L, G, name); +} + + +LUA_API void lua_settable (lua_State *L, int idx) { + TValue *t; + const TValue *slot; + lua_lock(L); + api_checknelems(L, 2); + t = index2value(L, idx); + if (luaV_fastget(L, t, s2v(L->top.p - 2), slot, luaH_get)) { + luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); + } + else + luaV_finishset(L, t, s2v(L->top.p - 2), s2v(L->top.p - 1), slot); + L->top.p -= 2; /* pop index and value */ + lua_unlock(L); +} + + +LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { + lua_lock(L); /* unlock done in 'auxsetstr' */ + auxsetstr(L, index2value(L, idx), k); +} + + +LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { + TValue *t; + const TValue *slot; + lua_lock(L); + api_checknelems(L, 1); + t = index2value(L, idx); + if (luaV_fastgeti(L, t, n, slot)) { + luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); + } + else { + TValue aux; + setivalue(&aux, n); + luaV_finishset(L, t, &aux, s2v(L->top.p - 1), slot); + } + L->top.p--; /* pop value */ + lua_unlock(L); +} + + +static void aux_rawset (lua_State *L, int idx, TValue *key, int n) { + Table *t; + lua_lock(L); + api_checknelems(L, n); + t = gettable(L, idx); + luaH_set(L, t, key, s2v(L->top.p - 1)); + invalidateTMcache(t); + luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1)); + L->top.p -= n; + lua_unlock(L); +} + + +LUA_API void lua_rawset (lua_State *L, int idx) { + aux_rawset(L, idx, s2v(L->top.p - 2), 2); +} + + +LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p) { + TValue k; + setpvalue(&k, cast_voidp(p)); + aux_rawset(L, idx, &k, 1); +} + + +LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n) { + Table *t; + lua_lock(L); + api_checknelems(L, 1); + t = gettable(L, idx); + luaH_setint(L, t, n, s2v(L->top.p - 1)); + luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1)); + L->top.p--; + lua_unlock(L); +} + + +LUA_API int lua_setmetatable (lua_State *L, int objindex) { + TValue *obj; + Table *mt; + lua_lock(L); + api_checknelems(L, 1); + obj = index2value(L, objindex); + if (ttisnil(s2v(L->top.p - 1))) + mt = NULL; + else { + api_check(L, ttistable(s2v(L->top.p - 1)), "table expected"); + mt = hvalue(s2v(L->top.p - 1)); + } + switch (ttype(obj)) { + case LUA_TTABLE: { + hvalue(obj)->metatable = mt; + if (mt) { + luaC_objbarrier(L, gcvalue(obj), mt); + luaC_checkfinalizer(L, gcvalue(obj), mt); + } + break; + } + case LUA_TUSERDATA: { + uvalue(obj)->metatable = mt; + if (mt) { + luaC_objbarrier(L, uvalue(obj), mt); + luaC_checkfinalizer(L, gcvalue(obj), mt); + } + break; + } + default: { + G(L)->mt[ttype(obj)] = mt; + break; + } + } + L->top.p--; + lua_unlock(L); + return 1; +} + + +LUA_API int lua_setiuservalue (lua_State *L, int idx, int n) { + TValue *o; + int res; + lua_lock(L); + api_checknelems(L, 1); + o = index2value(L, idx); + api_check(L, ttisfulluserdata(o), "full userdata expected"); + if (!(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue))) + res = 0; /* 'n' not in [1, uvalue(o)->nuvalue] */ + else { + setobj(L, &uvalue(o)->uv[n - 1].uv, s2v(L->top.p - 1)); + luaC_barrierback(L, gcvalue(o), s2v(L->top.p - 1)); + res = 1; + } + L->top.p--; + lua_unlock(L); + return res; +} + + +/* +** 'load' and 'call' functions (run Lua code) +*/ + + +#define checkresults(L,na,nr) \ + api_check(L, (nr) == LUA_MULTRET \ + || (L->ci->top.p - L->top.p >= (nr) - (na)), \ + "results from function overflow current stack size") + + +LUA_API void lua_callk (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k) { + StkId func; + lua_lock(L); + api_check(L, k == NULL || !isLua(L->ci), + "cannot use continuations inside hooks"); + api_checknelems(L, nargs+1); + api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); + checkresults(L, nargs, nresults); + func = L->top.p - (nargs+1); + if (k != NULL && yieldable(L)) { /* need to prepare continuation? */ + L->ci->u.c.k = k; /* save continuation */ + L->ci->u.c.ctx = ctx; /* save context */ + luaD_call(L, func, nresults); /* do the call */ + } + else /* no continuation or no yieldable */ + luaD_callnoyield(L, func, nresults); /* just do the call */ + adjustresults(L, nresults); + lua_unlock(L); +} + + + +/* +** Execute a protected call. +*/ +struct CallS { /* data to 'f_call' */ + StkId func; + int nresults; +}; + + +static void f_call (lua_State *L, void *ud) { + struct CallS *c = cast(struct CallS *, ud); + luaD_callnoyield(L, c->func, c->nresults); +} + + + +LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, + lua_KContext ctx, lua_KFunction k) { + struct CallS c; + int status; + ptrdiff_t func; + lua_lock(L); + api_check(L, k == NULL || !isLua(L->ci), + "cannot use continuations inside hooks"); + api_checknelems(L, nargs+1); + api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread"); + checkresults(L, nargs, nresults); + if (errfunc == 0) + func = 0; + else { + StkId o = index2stack(L, errfunc); + api_check(L, ttisfunction(s2v(o)), "error handler must be a function"); + func = savestack(L, o); + } + c.func = L->top.p - (nargs+1); /* function to be called */ + if (k == NULL || !yieldable(L)) { /* no continuation or no yieldable? */ + c.nresults = nresults; /* do a 'conventional' protected call */ + status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); + } + else { /* prepare continuation (call is already protected by 'resume') */ + CallInfo *ci = L->ci; + ci->u.c.k = k; /* save continuation */ + ci->u.c.ctx = ctx; /* save context */ + /* save information for error recovery */ + ci->u2.funcidx = cast_int(savestack(L, c.func)); + ci->u.c.old_errfunc = L->errfunc; + L->errfunc = func; + setoah(ci->callstatus, L->allowhook); /* save value of 'allowhook' */ + ci->callstatus |= CIST_YPCALL; /* function can do error recovery */ + luaD_call(L, c.func, nresults); /* do the call */ + ci->callstatus &= ~CIST_YPCALL; + L->errfunc = ci->u.c.old_errfunc; + status = LUA_OK; /* if it is here, there were no errors */ + } + adjustresults(L, nresults); + lua_unlock(L); + return status; +} + + +LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, + const char *chunkname, const char *mode) { + ZIO z; + int status; + lua_lock(L); + if (!chunkname) chunkname = "?"; + luaZ_init(L, &z, reader, data); + status = luaD_protectedparser(L, &z, chunkname, mode); + if (status == LUA_OK) { /* no errors? */ + LClosure *f = clLvalue(s2v(L->top.p - 1)); /* get new function */ + if (f->nupvalues >= 1) { /* does it have an upvalue? */ + /* get global table from registry */ + const TValue *gt = getGtable(L); + /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */ + setobj(L, f->upvals[0]->v.p, gt); + luaC_barrier(L, f->upvals[0], gt); + } + } + lua_unlock(L); + return status; +} + + +LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { + int status; + TValue *o; + lua_lock(L); + api_checknelems(L, 1); + o = s2v(L->top.p - 1); + if (isLfunction(o)) + status = luaU_dump(L, getproto(o), writer, data, strip); + else + status = 1; + lua_unlock(L); + return status; +} + + +LUA_API int lua_status (lua_State *L) { + return L->status; +} + + +/* +** Garbage-collection function +*/ +LUA_API int lua_gc (lua_State *L, int what, ...) { + va_list argp; + int res = 0; + global_State *g = G(L); + if (g->gcstp & GCSTPGC) /* internal stop? */ + return -1; /* all options are invalid when stopped */ + lua_lock(L); + va_start(argp, what); + switch (what) { + case LUA_GCSTOP: { + g->gcstp = GCSTPUSR; /* stopped by the user */ + break; + } + case LUA_GCRESTART: { + luaE_setdebt(g, 0); + g->gcstp = 0; /* (GCSTPGC must be already zero here) */ + break; + } + case LUA_GCCOLLECT: { + luaC_fullgc(L, 0); + break; + } + case LUA_GCCOUNT: { + /* GC values are expressed in Kbytes: #bytes/2^10 */ + res = cast_int(gettotalbytes(g) >> 10); + break; + } + case LUA_GCCOUNTB: { + res = cast_int(gettotalbytes(g) & 0x3ff); + break; + } + case LUA_GCSTEP: { + int data = va_arg(argp, int); + l_mem debt = 1; /* =1 to signal that it did an actual step */ + lu_byte oldstp = g->gcstp; + g->gcstp = 0; /* allow GC to run (GCSTPGC must be zero here) */ + if (data == 0) { + luaE_setdebt(g, 0); /* do a basic step */ + luaC_step(L); + } + else { /* add 'data' to total debt */ + debt = cast(l_mem, data) * 1024 + g->GCdebt; + luaE_setdebt(g, debt); + luaC_checkGC(L); + } + g->gcstp = oldstp; /* restore previous state */ + if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ + res = 1; /* signal it */ + break; + } + case LUA_GCSETPAUSE: { + int data = va_arg(argp, int); + res = getgcparam(g->gcpause); + setgcparam(g->gcpause, data); + break; + } + case LUA_GCSETSTEPMUL: { + int data = va_arg(argp, int); + res = getgcparam(g->gcstepmul); + setgcparam(g->gcstepmul, data); + break; + } + case LUA_GCISRUNNING: { + res = gcrunning(g); + break; + } + case LUA_GCGEN: { + int minormul = va_arg(argp, int); + int majormul = va_arg(argp, int); + res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; + if (minormul != 0) + g->genminormul = minormul; + if (majormul != 0) + setgcparam(g->genmajormul, majormul); + luaC_changemode(L, KGC_GEN); + break; + } + case LUA_GCINC: { + int pause = va_arg(argp, int); + int stepmul = va_arg(argp, int); + int stepsize = va_arg(argp, int); + res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; + if (pause != 0) + setgcparam(g->gcpause, pause); + if (stepmul != 0) + setgcparam(g->gcstepmul, stepmul); + if (stepsize != 0) + g->gcstepsize = stepsize; + luaC_changemode(L, KGC_INC); + break; + } + default: res = -1; /* invalid option */ + } + va_end(argp); + lua_unlock(L); + return res; +} + + + +/* +** miscellaneous functions +*/ + + +LUA_API int lua_error (lua_State *L) { + TValue *errobj; + lua_lock(L); + errobj = s2v(L->top.p - 1); + api_checknelems(L, 1); + /* error object is the memory error message? */ + if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg)) + luaM_error(L); /* raise a memory error */ + else + luaG_errormsg(L); /* raise a regular error */ + /* code unreachable; will unlock when control actually leaves the kernel */ + return 0; /* to avoid warnings */ +} + + +LUA_API int lua_next (lua_State *L, int idx) { + Table *t; + int more; + lua_lock(L); + api_checknelems(L, 1); + t = gettable(L, idx); + more = luaH_next(L, t, L->top.p - 1); + if (more) { + api_incr_top(L); + } + else /* no more elements */ + L->top.p -= 1; /* remove key */ + lua_unlock(L); + return more; +} + + +LUA_API void lua_toclose (lua_State *L, int idx) { + int nresults; + StkId o; + lua_lock(L); + o = index2stack(L, idx); + nresults = L->ci->nresults; + api_check(L, L->tbclist.p < o, "given index below or equal a marked one"); + luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ + if (!hastocloseCfunc(nresults)) /* function not marked yet? */ + L->ci->nresults = codeNresults(nresults); /* mark it */ + lua_assert(hastocloseCfunc(L->ci->nresults)); + lua_unlock(L); +} + + +LUA_API void lua_concat (lua_State *L, int n) { + lua_lock(L); + api_checknelems(L, n); + if (n > 0) + luaV_concat(L, n); + else { /* nothing to concatenate */ + setsvalue2s(L, L->top.p, luaS_newlstr(L, "", 0)); /* push empty string */ + api_incr_top(L); + } + luaC_checkGC(L); + lua_unlock(L); +} + + +LUA_API void lua_len (lua_State *L, int idx) { + TValue *t; + lua_lock(L); + t = index2value(L, idx); + luaV_objlen(L, L->top.p, t); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) { + lua_Alloc f; + lua_lock(L); + if (ud) *ud = G(L)->ud; + f = G(L)->frealloc; + lua_unlock(L); + return f; +} + + +LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { + lua_lock(L); + G(L)->ud = ud; + G(L)->frealloc = f; + lua_unlock(L); +} + + +void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) { + lua_lock(L); + G(L)->ud_warn = ud; + G(L)->warnf = f; + lua_unlock(L); +} + + +void lua_warning (lua_State *L, const char *msg, int tocont) { + lua_lock(L); + luaE_warning(L, msg, tocont); + lua_unlock(L); +} + + + +LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) { + Udata *u; + lua_lock(L); + api_check(L, 0 <= nuvalue && nuvalue < USHRT_MAX, "invalid value"); + u = luaS_newudata(L, size, nuvalue); + setuvalue(L, s2v(L->top.p), u); + api_incr_top(L); + luaC_checkGC(L); + lua_unlock(L); + return getudatamem(u); +} + + + +static const char *aux_upvalue (TValue *fi, int n, TValue **val, + GCObject **owner) { + switch (ttypetag(fi)) { + case LUA_VCCL: { /* C closure */ + CClosure *f = clCvalue(fi); + if (!(cast_uint(n) - 1u < cast_uint(f->nupvalues))) + return NULL; /* 'n' not in [1, f->nupvalues] */ + *val = &f->upvalue[n-1]; + if (owner) *owner = obj2gco(f); + return ""; + } + case LUA_VLCL: { /* Lua closure */ + LClosure *f = clLvalue(fi); + TString *name; + Proto *p = f->p; + if (!(cast_uint(n) - 1u < cast_uint(p->sizeupvalues))) + return NULL; /* 'n' not in [1, p->sizeupvalues] */ + *val = f->upvals[n-1]->v.p; + if (owner) *owner = obj2gco(f->upvals[n - 1]); + name = p->upvalues[n-1].name; + return (name == NULL) ? "(no name)" : getstr(name); + } + default: return NULL; /* not a closure */ + } +} + + +LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val = NULL; /* to avoid warnings */ + lua_lock(L); + name = aux_upvalue(index2value(L, funcindex), n, &val, NULL); + if (name) { + setobj2s(L, L->top.p, val); + api_incr_top(L); + } + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val = NULL; /* to avoid warnings */ + GCObject *owner = NULL; /* to avoid warnings */ + TValue *fi; + lua_lock(L); + fi = index2value(L, funcindex); + api_checknelems(L, 1); + name = aux_upvalue(fi, n, &val, &owner); + if (name) { + L->top.p--; + setobj(L, val, s2v(L->top.p)); + luaC_barrier(L, owner, val); + } + lua_unlock(L); + return name; +} + + +static UpVal **getupvalref (lua_State *L, int fidx, int n, LClosure **pf) { + static const UpVal *const nullup = NULL; + LClosure *f; + TValue *fi = index2value(L, fidx); + api_check(L, ttisLclosure(fi), "Lua function expected"); + f = clLvalue(fi); + if (pf) *pf = f; + if (1 <= n && n <= f->p->sizeupvalues) + return &f->upvals[n - 1]; /* get its upvalue pointer */ + else + return (UpVal**)&nullup; +} + + +LUA_API void *lua_upvalueid (lua_State *L, int fidx, int n) { + TValue *fi = index2value(L, fidx); + switch (ttypetag(fi)) { + case LUA_VLCL: { /* lua closure */ + return *getupvalref(L, fidx, n, NULL); + } + case LUA_VCCL: { /* C closure */ + CClosure *f = clCvalue(fi); + if (1 <= n && n <= f->nupvalues) + return &f->upvalue[n - 1]; + /* else */ + } /* FALLTHROUGH */ + case LUA_VLCF: + return NULL; /* light C functions have no upvalues */ + default: { + api_check(L, 0, "function expected"); + return NULL; + } + } +} + + +LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1, + int fidx2, int n2) { + LClosure *f1; + UpVal **up1 = getupvalref(L, fidx1, n1, &f1); + UpVal **up2 = getupvalref(L, fidx2, n2, NULL); + api_check(L, *up1 != NULL && *up2 != NULL, "invalid upvalue index"); + *up1 = *up2; + luaC_objbarrier(L, f1, *up1); +} + + diff --git a/arm9/source/lua/lapi.h b/arm9/source/lua/lapi.h new file mode 100644 index 000000000..a742427cd --- /dev/null +++ b/arm9/source/lua/lapi.h @@ -0,0 +1,52 @@ +/* +** $Id: lapi.h $ +** Auxiliary functions from Lua API +** See Copyright Notice in lua.h +*/ + +#ifndef lapi_h +#define lapi_h + + +#include "llimits.h" +#include "lstate.h" + + +/* Increments 'L->top.p', checking for stack overflows */ +#define api_incr_top(L) {L->top.p++; \ + api_check(L, L->top.p <= L->ci->top.p, \ + "stack overflow");} + + +/* +** If a call returns too many multiple returns, the callee may not have +** stack space to accommodate all results. In this case, this macro +** increases its stack space ('L->ci->top.p'). +*/ +#define adjustresults(L,nres) \ + { if ((nres) <= LUA_MULTRET && L->ci->top.p < L->top.p) \ + L->ci->top.p = L->top.p; } + + +/* Ensure the stack has at least 'n' elements */ +#define api_checknelems(L,n) \ + api_check(L, (n) < (L->top.p - L->ci->func.p), \ + "not enough elements in the stack") + + +/* +** To reduce the overhead of returning from C functions, the presence of +** to-be-closed variables in these functions is coded in the CallInfo's +** field 'nresults', in a way that functions with no to-be-closed variables +** with zero, one, or "all" wanted results have no overhead. Functions +** with other number of wanted results, as well as functions with +** variables to be closed, have an extra check. +*/ + +#define hastocloseCfunc(n) ((n) < LUA_MULTRET) + +/* Map [-1, inf) (range of 'nresults') into (-inf, -2] */ +#define codeNresults(n) (-(n) - 3) +#define decodeNresults(n) (-(n) - 3) + +#endif diff --git a/arm9/source/lua/lauxlib.c b/arm9/source/lua/lauxlib.c new file mode 100644 index 000000000..4ca6c6548 --- /dev/null +++ b/arm9/source/lua/lauxlib.c @@ -0,0 +1,1112 @@ +/* +** $Id: lauxlib.c $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + +#define lauxlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include + + +/* +** This file uses only the official API of Lua. +** Any function declared here could be written as an application function. +*/ + +#include "lua.h" + +#include "lauxlib.h" + + +#if !defined(MAX_SIZET) +/* maximum value for size_t */ +#define MAX_SIZET ((size_t)(~(size_t)0)) +#endif + + +/* +** {====================================================== +** Traceback +** ======================================================= +*/ + + +#define LEVELS1 10 /* size of the first part of the stack */ +#define LEVELS2 11 /* size of the second part of the stack */ + + + +/* +** Search for 'objidx' in table at index -1. ('objidx' must be an +** absolute index.) Return 1 + string at top if it found a good name. +*/ +static int findfield (lua_State *L, int objidx, int level) { + if (level == 0 || !lua_istable(L, -1)) + return 0; /* not found */ + lua_pushnil(L); /* start 'next' loop */ + while (lua_next(L, -2)) { /* for each pair in table */ + if (lua_type(L, -2) == LUA_TSTRING) { /* ignore non-string keys */ + if (lua_rawequal(L, objidx, -1)) { /* found object? */ + lua_pop(L, 1); /* remove value (but keep name) */ + return 1; + } + else if (findfield(L, objidx, level - 1)) { /* try recursively */ + /* stack: lib_name, lib_table, field_name (top) */ + lua_pushliteral(L, "."); /* place '.' between the two names */ + lua_replace(L, -3); /* (in the slot occupied by table) */ + lua_concat(L, 3); /* lib_name.field_name */ + return 1; + } + } + lua_pop(L, 1); /* remove value */ + } + return 0; /* not found */ +} + + +/* +** Search for a name for a function in all loaded modules +*/ +static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { + int top = lua_gettop(L); + lua_getinfo(L, "f", ar); /* push function */ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + if (findfield(L, top + 1, 2)) { + const char *name = lua_tostring(L, -1); + if (strncmp(name, LUA_GNAME ".", 3) == 0) { /* name start with '_G.'? */ + lua_pushstring(L, name + 3); /* push name without prefix */ + lua_remove(L, -2); /* remove original name */ + } + lua_copy(L, -1, top + 1); /* copy name to proper place */ + lua_settop(L, top + 1); /* remove table "loaded" and name copy */ + return 1; + } + else { + lua_settop(L, top); /* remove function and global table */ + return 0; + } +} + + +static void pushfuncname (lua_State *L, lua_Debug *ar) { + if (pushglobalfuncname(L, ar)) { /* try first a global name */ + lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); + lua_remove(L, -2); /* remove name */ + } + else if (*ar->namewhat != '\0') /* is there a name from code? */ + lua_pushfstring(L, "%s '%s'", ar->namewhat, ar->name); /* use it */ + else if (*ar->what == 'm') /* main? */ + lua_pushliteral(L, "main chunk"); + else if (*ar->what != 'C') /* for Lua functions, use */ + lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); + else /* nothing left... */ + lua_pushliteral(L, "?"); +} + + +static int lastlevel (lua_State *L) { + lua_Debug ar; + int li = 1, le = 1; + /* find an upper bound */ + while (lua_getstack(L, le, &ar)) { li = le; le *= 2; } + /* do a binary search */ + while (li < le) { + int m = (li + le)/2; + if (lua_getstack(L, m, &ar)) li = m + 1; + else le = m; + } + return le - 1; +} + + +LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, + const char *msg, int level) { + luaL_Buffer b; + lua_Debug ar; + int last = lastlevel(L1); + int limit2show = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1; + luaL_buffinit(L, &b); + if (msg) { + luaL_addstring(&b, msg); + luaL_addchar(&b, '\n'); + } + luaL_addstring(&b, "stack traceback:"); + while (lua_getstack(L1, level++, &ar)) { + if (limit2show-- == 0) { /* too many levels? */ + int n = last - level - LEVELS2 + 1; /* number of levels to skip */ + lua_pushfstring(L, "\n\t...\t(skipping %d levels)", n); + luaL_addvalue(&b); /* add warning about skip */ + level += n; /* and skip to last levels */ + } + else { + lua_getinfo(L1, "Slnt", &ar); + if (ar.currentline <= 0) + lua_pushfstring(L, "\n\t%s: in ", ar.short_src); + else + lua_pushfstring(L, "\n\t%s:%d: in ", ar.short_src, ar.currentline); + luaL_addvalue(&b); + pushfuncname(L, &ar); + luaL_addvalue(&b); + if (ar.istailcall) + luaL_addstring(&b, "\n\t(...tail calls...)"); + } + } + luaL_pushresult(&b); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Error-report functions +** ======================================================= +*/ + +LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) { + lua_Debug ar; + if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ + return luaL_error(L, "bad argument #%d (%s)", arg, extramsg); + lua_getinfo(L, "n", &ar); + if (strcmp(ar.namewhat, "method") == 0) { + arg--; /* do not count 'self' */ + if (arg == 0) /* error is in the self argument itself? */ + return luaL_error(L, "calling '%s' on bad self (%s)", + ar.name, extramsg); + } + if (ar.name == NULL) + ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?"; + return luaL_error(L, "bad argument #%d to '%s' (%s)", + arg, ar.name, extramsg); +} + + +LUALIB_API int luaL_typeerror (lua_State *L, int arg, const char *tname) { + const char *msg; + const char *typearg; /* name for the type of the actual argument */ + if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING) + typearg = lua_tostring(L, -1); /* use the given type name */ + else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA) + typearg = "light userdata"; /* special name for messages */ + else + typearg = luaL_typename(L, arg); /* standard name */ + msg = lua_pushfstring(L, "%s expected, got %s", tname, typearg); + return luaL_argerror(L, arg, msg); +} + + +static void tag_error (lua_State *L, int arg, int tag) { + luaL_typeerror(L, arg, lua_typename(L, tag)); +} + + +/* +** The use of 'lua_pushfstring' ensures this function does not +** need reserved stack space when called. +*/ +LUALIB_API void luaL_where (lua_State *L, int level) { + lua_Debug ar; + if (lua_getstack(L, level, &ar)) { /* check function at level */ + lua_getinfo(L, "Sl", &ar); /* get info about it */ + if (ar.currentline > 0) { /* is there info? */ + lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); + return; + } + } + lua_pushfstring(L, ""); /* else, no information available... */ +} + + +/* +** Again, the use of 'lua_pushvfstring' ensures this function does +** not need reserved stack space when called. (At worst, it generates +** an error with "stack overflow" instead of the given message.) +*/ +LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { + va_list argp; + va_start(argp, fmt); + luaL_where(L, 1); + lua_pushvfstring(L, fmt, argp); + va_end(argp); + lua_concat(L, 2); + return lua_error(L); +} + + +LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { + int en = errno; /* calls to Lua API may change this value */ + if (stat) { + lua_pushboolean(L, 1); + return 1; + } + else { + luaL_pushfail(L); + if (fname) + lua_pushfstring(L, "%s: %s", fname, strerror(en)); + else + lua_pushstring(L, strerror(en)); + lua_pushinteger(L, en); + return 3; + } +} + + +#if !defined(l_inspectstat) /* { */ + +#if defined(LUA_USE_POSIX) + +#include + +/* +** use appropriate macros to interpret 'pclose' return status +*/ +#define l_inspectstat(stat,what) \ + if (WIFEXITED(stat)) { stat = WEXITSTATUS(stat); } \ + else if (WIFSIGNALED(stat)) { stat = WTERMSIG(stat); what = "signal"; } + +#else + +#define l_inspectstat(stat,what) /* no op */ + +#endif + +#endif /* } */ + + +LUALIB_API int luaL_execresult (lua_State *L, int stat) { + if (stat != 0 && errno != 0) /* error with an 'errno'? */ + return luaL_fileresult(L, 0, NULL); + else { + const char *what = "exit"; /* type of termination */ + l_inspectstat(stat, what); /* interpret result */ + if (*what == 'e' && stat == 0) /* successful termination? */ + lua_pushboolean(L, 1); + else + luaL_pushfail(L); + lua_pushstring(L, what); + lua_pushinteger(L, stat); + return 3; /* return true/fail,what,code */ + } +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Userdata's metatable manipulation +** ======================================================= +*/ + +LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { + if (luaL_getmetatable(L, tname) != LUA_TNIL) /* name already in use? */ + return 0; /* leave previous value on top, but return 0 */ + lua_pop(L, 1); + lua_createtable(L, 0, 2); /* create metatable */ + lua_pushstring(L, tname); + lua_setfield(L, -2, "__name"); /* metatable.__name = tname */ + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ + return 1; +} + + +LUALIB_API void luaL_setmetatable (lua_State *L, const char *tname) { + luaL_getmetatable(L, tname); + lua_setmetatable(L, -2); +} + + +LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) { + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + luaL_getmetatable(L, tname); /* get correct metatable */ + if (!lua_rawequal(L, -1, -2)) /* not the same? */ + p = NULL; /* value is a userdata with wrong metatable */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + return NULL; /* value is not a userdata with a metatable */ +} + + +LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { + void *p = luaL_testudata(L, ud, tname); + luaL_argexpected(L, p != NULL, ud, tname); + return p; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Argument check functions +** ======================================================= +*/ + +LUALIB_API int luaL_checkoption (lua_State *L, int arg, const char *def, + const char *const lst[]) { + const char *name = (def) ? luaL_optstring(L, arg, def) : + luaL_checkstring(L, arg); + int i; + for (i=0; lst[i]; i++) + if (strcmp(lst[i], name) == 0) + return i; + return luaL_argerror(L, arg, + lua_pushfstring(L, "invalid option '%s'", name)); +} + + +/* +** Ensures the stack has at least 'space' extra slots, raising an error +** if it cannot fulfill the request. (The error handling needs a few +** extra slots to format the error message. In case of an error without +** this extra space, Lua will generate the same 'stack overflow' error, +** but without 'msg'.) +*/ +LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *msg) { + if (l_unlikely(!lua_checkstack(L, space))) { + if (msg) + luaL_error(L, "stack overflow (%s)", msg); + else + luaL_error(L, "stack overflow"); + } +} + + +LUALIB_API void luaL_checktype (lua_State *L, int arg, int t) { + if (l_unlikely(lua_type(L, arg) != t)) + tag_error(L, arg, t); +} + + +LUALIB_API void luaL_checkany (lua_State *L, int arg) { + if (l_unlikely(lua_type(L, arg) == LUA_TNONE)) + luaL_argerror(L, arg, "value expected"); +} + + +LUALIB_API const char *luaL_checklstring (lua_State *L, int arg, size_t *len) { + const char *s = lua_tolstring(L, arg, len); + if (l_unlikely(!s)) tag_error(L, arg, LUA_TSTRING); + return s; +} + + +LUALIB_API const char *luaL_optlstring (lua_State *L, int arg, + const char *def, size_t *len) { + if (lua_isnoneornil(L, arg)) { + if (len) + *len = (def ? strlen(def) : 0); + return def; + } + else return luaL_checklstring(L, arg, len); +} + + +LUALIB_API lua_Number luaL_checknumber (lua_State *L, int arg) { + int isnum; + lua_Number d = lua_tonumberx(L, arg, &isnum); + if (l_unlikely(!isnum)) + tag_error(L, arg, LUA_TNUMBER); + return d; +} + + +LUALIB_API lua_Number luaL_optnumber (lua_State *L, int arg, lua_Number def) { + return luaL_opt(L, luaL_checknumber, arg, def); +} + + +static void interror (lua_State *L, int arg) { + if (lua_isnumber(L, arg)) + luaL_argerror(L, arg, "number has no integer representation"); + else + tag_error(L, arg, LUA_TNUMBER); +} + + +LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int arg) { + int isnum; + lua_Integer d = lua_tointegerx(L, arg, &isnum); + if (l_unlikely(!isnum)) { + interror(L, arg); + } + return d; +} + + +LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int arg, + lua_Integer def) { + return luaL_opt(L, luaL_checkinteger, arg, def); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +/* userdata to box arbitrary data */ +typedef struct UBox { + void *box; + size_t bsize; +} UBox; + + +static void *resizebox (lua_State *L, int idx, size_t newsize) { + void *ud; + lua_Alloc allocf = lua_getallocf(L, &ud); + UBox *box = (UBox *)lua_touserdata(L, idx); + void *temp = allocf(ud, box->box, box->bsize, newsize); + if (l_unlikely(temp == NULL && newsize > 0)) { /* allocation error? */ + lua_pushliteral(L, "not enough memory"); + lua_error(L); /* raise a memory error */ + } + box->box = temp; + box->bsize = newsize; + return temp; +} + + +static int boxgc (lua_State *L) { + resizebox(L, 1, 0); + return 0; +} + + +static const luaL_Reg boxmt[] = { /* box metamethods */ + {"__gc", boxgc}, + {"__close", boxgc}, + {NULL, NULL} +}; + + +static void newbox (lua_State *L) { + UBox *box = (UBox *)lua_newuserdatauv(L, sizeof(UBox), 0); + box->box = NULL; + box->bsize = 0; + if (luaL_newmetatable(L, "_UBOX*")) /* creating metatable? */ + luaL_setfuncs(L, boxmt, 0); /* set its metamethods */ + lua_setmetatable(L, -2); +} + + +/* +** check whether buffer is using a userdata on the stack as a temporary +** buffer +*/ +#define buffonstack(B) ((B)->b != (B)->init.b) + + +/* +** Whenever buffer is accessed, slot 'idx' must either be a box (which +** cannot be NULL) or it is a placeholder for the buffer. +*/ +#define checkbufferlevel(B,idx) \ + lua_assert(buffonstack(B) ? lua_touserdata(B->L, idx) != NULL \ + : lua_touserdata(B->L, idx) == (void*)B) + + +/* +** Compute new size for buffer 'B', enough to accommodate extra 'sz' +** bytes. (The test for "not big enough" also gets the case when the +** computation of 'newsize' overflows.) +*/ +static size_t newbuffsize (luaL_Buffer *B, size_t sz) { + size_t newsize = (B->size / 2) * 3; /* buffer size * 1.5 */ + if (l_unlikely(MAX_SIZET - sz < B->n)) /* overflow in (B->n + sz)? */ + return luaL_error(B->L, "buffer too large"); + if (newsize < B->n + sz) /* not big enough? */ + newsize = B->n + sz; + return newsize; +} + + +/* +** Returns a pointer to a free area with at least 'sz' bytes in buffer +** 'B'. 'boxidx' is the relative position in the stack where is the +** buffer's box or its placeholder. +*/ +static char *prepbuffsize (luaL_Buffer *B, size_t sz, int boxidx) { + checkbufferlevel(B, boxidx); + if (B->size - B->n >= sz) /* enough space? */ + return B->b + B->n; + else { + lua_State *L = B->L; + char *newbuff; + size_t newsize = newbuffsize(B, sz); + /* create larger buffer */ + if (buffonstack(B)) /* buffer already has a box? */ + newbuff = (char *)resizebox(L, boxidx, newsize); /* resize it */ + else { /* no box yet */ + lua_remove(L, boxidx); /* remove placeholder */ + newbox(L); /* create a new box */ + lua_insert(L, boxidx); /* move box to its intended position */ + lua_toclose(L, boxidx); + newbuff = (char *)resizebox(L, boxidx, newsize); + memcpy(newbuff, B->b, B->n * sizeof(char)); /* copy original content */ + } + B->b = newbuff; + B->size = newsize; + return newbuff + B->n; + } +} + +/* +** returns a pointer to a free area with at least 'sz' bytes +*/ +LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) { + return prepbuffsize(B, sz, -1); +} + + +LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { + if (l > 0) { /* avoid 'memcpy' when 's' can be NULL */ + char *b = prepbuffsize(B, l, -1); + memcpy(b, s, l * sizeof(char)); + luaL_addsize(B, l); + } +} + + +LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { + luaL_addlstring(B, s, strlen(s)); +} + + +LUALIB_API void luaL_pushresult (luaL_Buffer *B) { + lua_State *L = B->L; + checkbufferlevel(B, -1); + lua_pushlstring(L, B->b, B->n); + if (buffonstack(B)) + lua_closeslot(L, -2); /* close the box */ + lua_remove(L, -2); /* remove box or placeholder from the stack */ +} + + +LUALIB_API void luaL_pushresultsize (luaL_Buffer *B, size_t sz) { + luaL_addsize(B, sz); + luaL_pushresult(B); +} + + +/* +** 'luaL_addvalue' is the only function in the Buffer system where the +** box (if existent) is not on the top of the stack. So, instead of +** calling 'luaL_addlstring', it replicates the code using -2 as the +** last argument to 'prepbuffsize', signaling that the box is (or will +** be) below the string being added to the buffer. (Box creation can +** trigger an emergency GC, so we should not remove the string from the +** stack before we have the space guaranteed.) +*/ +LUALIB_API void luaL_addvalue (luaL_Buffer *B) { + lua_State *L = B->L; + size_t len; + const char *s = lua_tolstring(L, -1, &len); + char *b = prepbuffsize(B, len, -2); + memcpy(b, s, len * sizeof(char)); + luaL_addsize(B, len); + lua_pop(L, 1); /* pop string */ +} + + +LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) { + B->L = L; + B->b = B->init.b; + B->n = 0; + B->size = LUAL_BUFFERSIZE; + lua_pushlightuserdata(L, (void*)B); /* push placeholder */ +} + + +LUALIB_API char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz) { + luaL_buffinit(L, B); + return prepbuffsize(B, sz, -1); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Reference system +** ======================================================= +*/ + +/* index of free-list header (after the predefined values) */ +#define freelist (LUA_RIDX_LAST + 1) + +/* +** The previously freed references form a linked list: +** t[freelist] is the index of a first free index, or zero if list is +** empty; t[t[freelist]] is the index of the second element; etc. +*/ +LUALIB_API int luaL_ref (lua_State *L, int t) { + int ref; + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* remove from stack */ + return LUA_REFNIL; /* 'nil' has a unique fixed reference */ + } + t = lua_absindex(L, t); + if (lua_rawgeti(L, t, freelist) == LUA_TNIL) { /* first access? */ + ref = 0; /* list is empty */ + lua_pushinteger(L, 0); /* initialize as an empty list */ + lua_rawseti(L, t, freelist); /* ref = t[freelist] = 0 */ + } + else { /* already initialized */ + lua_assert(lua_isinteger(L, -1)); + ref = (int)lua_tointeger(L, -1); /* ref = t[freelist] */ + } + lua_pop(L, 1); /* remove element from stack */ + if (ref != 0) { /* any free element? */ + lua_rawgeti(L, t, ref); /* remove it from list */ + lua_rawseti(L, t, freelist); /* (t[freelist] = t[ref]) */ + } + else /* no free elements */ + ref = (int)lua_rawlen(L, t) + 1; /* get a new reference */ + lua_rawseti(L, t, ref); + return ref; +} + + +LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { + if (ref >= 0) { + t = lua_absindex(L, t); + lua_rawgeti(L, t, freelist); + lua_assert(lua_isinteger(L, -1)); + lua_rawseti(L, t, ref); /* t[ref] = t[freelist] */ + lua_pushinteger(L, ref); + lua_rawseti(L, t, freelist); /* t[freelist] = ref */ + } +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Load functions +** ======================================================= +*/ + +typedef struct LoadF { + int n; /* number of pre-read characters */ + FILE *f; /* file being read */ + char buff[BUFSIZ]; /* area for reading file */ +} LoadF; + + +static const char *getF (lua_State *L, void *ud, size_t *size) { + LoadF *lf = (LoadF *)ud; + (void)L; /* not used */ + if (lf->n > 0) { /* are there pre-read characters to be read? */ + *size = lf->n; /* return them (chars already in buffer) */ + lf->n = 0; /* no more pre-read characters */ + } + else { /* read a block from file */ + /* 'fread' can return > 0 *and* set the EOF flag. If next call to + 'getF' called 'fread', it might still wait for user input. + The next check avoids this problem. */ + if (feof(lf->f)) return NULL; + *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); /* read block */ + } + return lf->buff; +} + + +static int errfile (lua_State *L, const char *what, int fnameindex) { + const char *serr = strerror(errno); + const char *filename = lua_tostring(L, fnameindex) + 1; + lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); + lua_remove(L, fnameindex); + return LUA_ERRFILE; +} + + +/* +** Skip an optional BOM at the start of a stream. If there is an +** incomplete BOM (the first character is correct but the rest is +** not), returns the first character anyway to force an error +** (as no chunk can start with 0xEF). +*/ +static int skipBOM (FILE *f) { + int c = getc(f); /* read first character */ + if (c == 0xEF && getc(f) == 0xBB && getc(f) == 0xBF) /* correct BOM? */ + return getc(f); /* ignore BOM and return next char */ + else /* no (valid) BOM */ + return c; /* return first character */ +} + + +/* +** reads the first character of file 'f' and skips an optional BOM mark +** in its beginning plus its first line if it starts with '#'. Returns +** true if it skipped the first line. In any case, '*cp' has the +** first "valid" character of the file (after the optional BOM and +** a first-line comment). +*/ +static int skipcomment (FILE *f, int *cp) { + int c = *cp = skipBOM(f); + if (c == '#') { /* first line is a comment (Unix exec. file)? */ + do { /* skip first line */ + c = getc(f); + } while (c != EOF && c != '\n'); + *cp = getc(f); /* next character after comment, if present */ + return 1; /* there was a comment */ + } + else return 0; /* no comment */ +} + + +LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, + const char *mode) { + LoadF lf; + int status, readstatus; + int c; + int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ + if (filename == NULL) { + lua_pushliteral(L, "=stdin"); + lf.f = stdin; + } + else { + lua_pushfstring(L, "@%s", filename); + lf.f = fopen(filename, "r"); + if (lf.f == NULL) return errfile(L, "open", fnameindex); + } + lf.n = 0; + if (skipcomment(lf.f, &c)) /* read initial portion */ + lf.buff[lf.n++] = '\n'; /* add newline to correct line numbers */ + if (c == LUA_SIGNATURE[0]) { /* binary file? */ + lf.n = 0; /* remove possible newline */ + if (filename) { /* "real" file? */ + lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ + if (lf.f == NULL) return errfile(L, "reopen", fnameindex); + skipcomment(lf.f, &c); /* re-read initial portion */ + } + } + if (c != EOF) + lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */ + status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode); + readstatus = ferror(lf.f); + if (filename) fclose(lf.f); /* close file (even in case of errors) */ + if (readstatus) { + lua_settop(L, fnameindex); /* ignore results from 'lua_load' */ + return errfile(L, "read", fnameindex); + } + lua_remove(L, fnameindex); + return status; +} + + +typedef struct LoadS { + const char *s; + size_t size; +} LoadS; + + +static const char *getS (lua_State *L, void *ud, size_t *size) { + LoadS *ls = (LoadS *)ud; + (void)L; /* not used */ + if (ls->size == 0) return NULL; + *size = ls->size; + ls->size = 0; + return ls->s; +} + + +LUALIB_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t size, + const char *name, const char *mode) { + LoadS ls; + ls.s = buff; + ls.size = size; + return lua_load(L, getS, &ls, name, mode); +} + + +LUALIB_API int luaL_loadstring (lua_State *L, const char *s) { + return luaL_loadbuffer(L, s, strlen(s), s); +} + +/* }====================================================== */ + + + +LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { + if (!lua_getmetatable(L, obj)) /* no metatable? */ + return LUA_TNIL; + else { + int tt; + lua_pushstring(L, event); + tt = lua_rawget(L, -2); + if (tt == LUA_TNIL) /* is metafield nil? */ + lua_pop(L, 2); /* remove metatable and metafield */ + else + lua_remove(L, -2); /* remove only metatable */ + return tt; /* return metafield type */ + } +} + + +LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { + obj = lua_absindex(L, obj); + if (luaL_getmetafield(L, obj, event) == LUA_TNIL) /* no metafield? */ + return 0; + lua_pushvalue(L, obj); + lua_call(L, 1, 1); + return 1; +} + + +LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) { + lua_Integer l; + int isnum; + lua_len(L, idx); + l = lua_tointegerx(L, -1, &isnum); + if (l_unlikely(!isnum)) + luaL_error(L, "object length is not an integer"); + lua_pop(L, 1); /* remove object */ + return l; +} + + +LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { + idx = lua_absindex(L,idx); + if (luaL_callmeta(L, idx, "__tostring")) { /* metafield? */ + if (!lua_isstring(L, -1)) + luaL_error(L, "'__tostring' must return a string"); + } + else { + switch (lua_type(L, idx)) { + case LUA_TNUMBER: { + if (lua_isinteger(L, idx)) + lua_pushfstring(L, "%I", (LUAI_UACINT)lua_tointeger(L, idx)); + else + lua_pushfstring(L, "%f", (LUAI_UACNUMBER)lua_tonumber(L, idx)); + break; + } + case LUA_TSTRING: + lua_pushvalue(L, idx); + break; + case LUA_TBOOLEAN: + lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false")); + break; + case LUA_TNIL: + lua_pushliteral(L, "nil"); + break; + default: { + int tt = luaL_getmetafield(L, idx, "__name"); /* try name */ + const char *kind = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : + luaL_typename(L, idx); + lua_pushfstring(L, "%s: %p", kind, lua_topointer(L, idx)); + if (tt != LUA_TNIL) + lua_remove(L, -2); /* remove '__name' */ + break; + } + } + } + return lua_tolstring(L, -1, len); +} + + +/* +** set functions from list 'l' into table at top - 'nup'; each +** function gets the 'nup' elements at the top as upvalues. +** Returns with only the table at the stack. +*/ +LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { + luaL_checkstack(L, nup, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + if (l->func == NULL) /* place holder? */ + lua_pushboolean(L, 0); + else { + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + } + lua_setfield(L, -(nup + 2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} + + +/* +** ensure that stack[idx][fname] has a table and push that table +** into the stack +*/ +LUALIB_API int luaL_getsubtable (lua_State *L, int idx, const char *fname) { + if (lua_getfield(L, idx, fname) == LUA_TTABLE) + return 1; /* table already there */ + else { + lua_pop(L, 1); /* remove previous result */ + idx = lua_absindex(L, idx); + lua_newtable(L); + lua_pushvalue(L, -1); /* copy to be left at top */ + lua_setfield(L, idx, fname); /* assign new table to field */ + return 0; /* false, because did not find table there */ + } +} + + +/* +** Stripped-down 'require': After checking "loaded" table, calls 'openf' +** to open a module, registers the result in 'package.loaded' table and, +** if 'glb' is true, also registers the result in the global table. +** Leaves resulting module on the top. +*/ +LUALIB_API void luaL_requiref (lua_State *L, const char *modname, + lua_CFunction openf, int glb) { + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, -1, modname); /* LOADED[modname] */ + if (!lua_toboolean(L, -1)) { /* package not already loaded? */ + lua_pop(L, 1); /* remove field */ + lua_pushcfunction(L, openf); + lua_pushstring(L, modname); /* argument to open function */ + lua_call(L, 1, 1); /* call 'openf' to open module */ + lua_pushvalue(L, -1); /* make copy of module (call result) */ + lua_setfield(L, -3, modname); /* LOADED[modname] = module */ + } + lua_remove(L, -2); /* remove LOADED table */ + if (glb) { + lua_pushvalue(L, -1); /* copy of module */ + lua_setglobal(L, modname); /* _G[modname] = module */ + } +} + + +LUALIB_API void luaL_addgsub (luaL_Buffer *b, const char *s, + const char *p, const char *r) { + const char *wild; + size_t l = strlen(p); + while ((wild = strstr(s, p)) != NULL) { + luaL_addlstring(b, s, wild - s); /* push prefix */ + luaL_addstring(b, r); /* push replacement in place of pattern */ + s = wild + l; /* continue after 'p' */ + } + luaL_addstring(b, s); /* push last suffix */ +} + + +LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, + const char *p, const char *r) { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addgsub(&b, s, p, r); + luaL_pushresult(&b); + return lua_tostring(L, -1); +} + + +static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { + (void)ud; (void)osize; /* not used */ + if (nsize == 0) { + free(ptr); + return NULL; + } + else + return realloc(ptr, nsize); +} + + +static int panic (lua_State *L) { + const char *msg = lua_tostring(L, -1); + if (msg == NULL) msg = "error object is not a string"; + lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", + msg); + return 0; /* return to Lua to abort */ +} + + +/* +** Warning functions: +** warnfoff: warning system is off +** warnfon: ready to start a new message +** warnfcont: previous message is to be continued +*/ +static void warnfoff (void *ud, const char *message, int tocont); +static void warnfon (void *ud, const char *message, int tocont); +static void warnfcont (void *ud, const char *message, int tocont); + + +/* +** Check whether message is a control message. If so, execute the +** control or ignore it if unknown. +*/ +static int checkcontrol (lua_State *L, const char *message, int tocont) { + if (tocont || *(message++) != '@') /* not a control message? */ + return 0; + else { + if (strcmp(message, "off") == 0) + lua_setwarnf(L, warnfoff, L); /* turn warnings off */ + else if (strcmp(message, "on") == 0) + lua_setwarnf(L, warnfon, L); /* turn warnings on */ + return 1; /* it was a control message */ + } +} + + +static void warnfoff (void *ud, const char *message, int tocont) { + checkcontrol((lua_State *)ud, message, tocont); +} + + +/* +** Writes the message and handle 'tocont', finishing the message +** if needed and setting the next warn function. +*/ +static void warnfcont (void *ud, const char *message, int tocont) { + lua_State *L = (lua_State *)ud; + lua_writestringerror("%s", message); /* write message */ + if (tocont) /* not the last part? */ + lua_setwarnf(L, warnfcont, L); /* to be continued */ + else { /* last part */ + lua_writestringerror("%s", "\n"); /* finish message with end-of-line */ + lua_setwarnf(L, warnfon, L); /* next call is a new message */ + } +} + + +static void warnfon (void *ud, const char *message, int tocont) { + if (checkcontrol((lua_State *)ud, message, tocont)) /* control message? */ + return; /* nothing else to be done */ + lua_writestringerror("%s", "Lua warning: "); /* start a new warning */ + warnfcont(ud, message, tocont); /* finish processing */ +} + + +LUALIB_API lua_State *luaL_newstate (void) { + lua_State *L = lua_newstate(l_alloc, NULL); + if (l_likely(L)) { + lua_atpanic(L, &panic); + lua_setwarnf(L, warnfoff, L); /* default is warnings off */ + } + return L; +} + + +LUALIB_API void luaL_checkversion_ (lua_State *L, lua_Number ver, size_t sz) { + lua_Number v = lua_version(L); + if (sz != LUAL_NUMSIZES) /* check numeric types */ + luaL_error(L, "core and library have incompatible numeric types"); + else if (v != ver) + luaL_error(L, "version mismatch: app. needs %f, Lua core provides %f", + (LUAI_UACNUMBER)ver, (LUAI_UACNUMBER)v); +} + diff --git a/arm9/source/lua/lauxlib.h b/arm9/source/lua/lauxlib.h new file mode 100644 index 000000000..5b977e2a3 --- /dev/null +++ b/arm9/source/lua/lauxlib.h @@ -0,0 +1,301 @@ +/* +** $Id: lauxlib.h $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include +#include + +#include "luaconf.h" +#include "lua.h" + + +/* global table */ +#define LUA_GNAME "_G" + + +typedef struct luaL_Buffer luaL_Buffer; + + +/* extra error code for 'luaL_loadfilex' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +/* key, in the registry, for table of loaded modules */ +#define LUA_LOADED_TABLE "_LOADED" + + +/* key, in the registry, for table of preloaded loaders */ +#define LUA_PRELOAD_TABLE "_PRELOAD" + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) + +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); +LUALIB_API int (luaL_execresult) (lua_State *L, int stat); + + +/* predefined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, + const char *mode); + +#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) + +LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); + +LUALIB_API void (luaL_addgsub) (luaL_Buffer *b, const char *s, + const char *p, const char *r); +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, + const char *p, const char *r); + +LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); + +LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); + +LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, + const char *msg, int level); + +LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, + lua_CFunction openf, int glb); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + + +#define luaL_newlibtable(L,l) \ + lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) + +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) + +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)(luai_likely(cond) || luaL_argerror(L, (arg), (extramsg)))) + +#define luaL_argexpected(L,cond,arg,tname) \ + ((void)(luai_likely(cond) || luaL_typeerror(L, (arg), (tname)))) + +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) + + +/* +** Perform arithmetic operations on lua_Integer values with wrap-around +** semantics, as the Lua core does. +*/ +#define luaL_intop(op,v1,v2) \ + ((lua_Integer)((lua_Unsigned)(v1) op (lua_Unsigned)(v2))) + + +/* push the value used to represent failure/error */ +#define luaL_pushfail(L) lua_pushnil(L) + + +/* +** Internal assertions for in-house debugging +*/ +#if !defined(lua_assert) + +#if defined LUAI_ASSERT + #include + #define lua_assert(c) assert(c) +#else + #define lua_assert(c) ((void)0) +#endif + +#endif + + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +struct luaL_Buffer { + char *b; /* buffer address */ + size_t size; /* buffer size */ + size_t n; /* number of characters in buffer */ + lua_State *L; + union { + LUAI_MAXALIGN; /* ensure maximum alignment for buffer */ + char b[LUAL_BUFFERSIZE]; /* initial buffer */ + } init; +}; + + +#define luaL_bufflen(bf) ((bf)->n) +#define luaL_buffaddr(bf) ((bf)->b) + + +#define luaL_addchar(B,c) \ + ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ + ((B)->b[(B)->n++] = (c))) + +#define luaL_addsize(B,s) ((B)->n += (s)) + +#define luaL_buffsub(B,s) ((B)->n -= (s)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); +LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); + +#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) + +/* }====================================================== */ + + + +/* +** {====================================================== +** File handles for IO library +** ======================================================= +*/ + +/* +** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and +** initial structure 'luaL_Stream' (it may contain other fields +** after that initial structure). +*/ + +#define LUA_FILEHANDLE "FILE*" + + +typedef struct luaL_Stream { + FILE *f; /* stream (NULL for incompletely created streams) */ + lua_CFunction closef; /* to close stream (NULL for closed streams) */ +} luaL_Stream; + +/* }====================================================== */ + +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + +#endif + + diff --git a/arm9/source/lua/lbaselib.c b/arm9/source/lua/lbaselib.c new file mode 100644 index 000000000..1d60c9ded --- /dev/null +++ b/arm9/source/lua/lbaselib.c @@ -0,0 +1,549 @@ +/* +** $Id: lbaselib.c $ +** Basic library +** See Copyright Notice in lua.h +*/ + +#define lbaselib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +static int luaB_print (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + for (i = 1; i <= n; i++) { /* for each argument */ + size_t l; + const char *s = luaL_tolstring(L, i, &l); /* convert it to string */ + if (i > 1) /* not the first element? */ + lua_writestring("\t", 1); /* add a tab before it */ + lua_writestring(s, l); /* print it */ + lua_pop(L, 1); /* pop result */ + } + lua_writeline(); + return 0; +} + + +/* +** Creates a warning with all given arguments. +** Check first for errors; otherwise an error may interrupt +** the composition of a warning, leaving it unfinished. +*/ +static int luaB_warn (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + luaL_checkstring(L, 1); /* at least one argument */ + for (i = 2; i <= n; i++) + luaL_checkstring(L, i); /* make sure all arguments are strings */ + for (i = 1; i < n; i++) /* compose warning */ + lua_warning(L, lua_tostring(L, i), 1); + lua_warning(L, lua_tostring(L, n), 0); /* close warning */ + return 0; +} + + +#define SPACECHARS " \f\n\r\t\v" + +static const char *b_str2int (const char *s, int base, lua_Integer *pn) { + lua_Unsigned n = 0; + int neg = 0; + s += strspn(s, SPACECHARS); /* skip initial spaces */ + if (*s == '-') { s++; neg = 1; } /* handle sign */ + else if (*s == '+') s++; + if (!isalnum((unsigned char)*s)) /* no digit? */ + return NULL; + do { + int digit = (isdigit((unsigned char)*s)) ? *s - '0' + : (toupper((unsigned char)*s) - 'A') + 10; + if (digit >= base) return NULL; /* invalid numeral */ + n = n * base + digit; + s++; + } while (isalnum((unsigned char)*s)); + s += strspn(s, SPACECHARS); /* skip trailing spaces */ + *pn = (lua_Integer)((neg) ? (0u - n) : n); + return s; +} + + +static int luaB_tonumber (lua_State *L) { + if (lua_isnoneornil(L, 2)) { /* standard conversion? */ + if (lua_type(L, 1) == LUA_TNUMBER) { /* already a number? */ + lua_settop(L, 1); /* yes; return it */ + return 1; + } + else { + size_t l; + const char *s = lua_tolstring(L, 1, &l); + if (s != NULL && lua_stringtonumber(L, s) == l + 1) + return 1; /* successful conversion to number */ + /* else not a number */ + luaL_checkany(L, 1); /* (but there must be some parameter) */ + } + } + else { + size_t l; + const char *s; + lua_Integer n = 0; /* to avoid warnings */ + lua_Integer base = luaL_checkinteger(L, 2); + luaL_checktype(L, 1, LUA_TSTRING); /* no numbers as strings */ + s = lua_tolstring(L, 1, &l); + luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); + if (b_str2int(s, (int)base, &n) == s + l) { + lua_pushinteger(L, n); + return 1; + } /* else not a number */ + } /* else not a number */ + luaL_pushfail(L); /* not a number */ + return 1; +} + + +static int luaB_error (lua_State *L) { + int level = (int)luaL_optinteger(L, 2, 1); + lua_settop(L, 1); + if (lua_type(L, 1) == LUA_TSTRING && level > 0) { + luaL_where(L, level); /* add extra information */ + lua_pushvalue(L, 1); + lua_concat(L, 2); + } + return lua_error(L); +} + + +static int luaB_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); + return 1; /* no metatable */ + } + luaL_getmetafield(L, 1, "__metatable"); + return 1; /* returns either __metatable field (if present) or metatable */ +} + + +static int luaB_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_checktype(L, 1, LUA_TTABLE); + luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table"); + if (l_unlikely(luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL)) + return luaL_error(L, "cannot change a protected metatable"); + lua_settop(L, 2); + lua_setmetatable(L, 1); + return 1; +} + + +static int luaB_rawequal (lua_State *L) { + luaL_checkany(L, 1); + luaL_checkany(L, 2); + lua_pushboolean(L, lua_rawequal(L, 1, 2)); + return 1; +} + + +static int luaB_rawlen (lua_State *L) { + int t = lua_type(L, 1); + luaL_argexpected(L, t == LUA_TTABLE || t == LUA_TSTRING, 1, + "table or string"); + lua_pushinteger(L, lua_rawlen(L, 1)); + return 1; +} + + +static int luaB_rawget (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + lua_settop(L, 2); + lua_rawget(L, 1); + return 1; +} + +static int luaB_rawset (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + luaL_checkany(L, 3); + lua_settop(L, 3); + lua_rawset(L, 1); + return 1; +} + + +static int pushmode (lua_State *L, int oldmode) { + if (oldmode == -1) + luaL_pushfail(L); /* invalid call to 'lua_gc' */ + else + lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental" + : "generational"); + return 1; +} + + +/* +** check whether call to 'lua_gc' was valid (not inside a finalizer) +*/ +#define checkvalres(res) { if (res == -1) break; } + +static int luaB_collectgarbage (lua_State *L) { + static const char *const opts[] = {"stop", "restart", "collect", + "count", "step", "setpause", "setstepmul", + "isrunning", "generational", "incremental", NULL}; + static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, + LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL, + LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC}; + int o = optsnum[luaL_checkoption(L, 1, "collect", opts)]; + switch (o) { + case LUA_GCCOUNT: { + int k = lua_gc(L, o); + int b = lua_gc(L, LUA_GCCOUNTB); + checkvalres(k); + lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024)); + return 1; + } + case LUA_GCSTEP: { + int step = (int)luaL_optinteger(L, 2, 0); + int res = lua_gc(L, o, step); + checkvalres(res); + lua_pushboolean(L, res); + return 1; + } + case LUA_GCSETPAUSE: + case LUA_GCSETSTEPMUL: { + int p = (int)luaL_optinteger(L, 2, 0); + int previous = lua_gc(L, o, p); + checkvalres(previous); + lua_pushinteger(L, previous); + return 1; + } + case LUA_GCISRUNNING: { + int res = lua_gc(L, o); + checkvalres(res); + lua_pushboolean(L, res); + return 1; + } + case LUA_GCGEN: { + int minormul = (int)luaL_optinteger(L, 2, 0); + int majormul = (int)luaL_optinteger(L, 3, 0); + return pushmode(L, lua_gc(L, o, minormul, majormul)); + } + case LUA_GCINC: { + int pause = (int)luaL_optinteger(L, 2, 0); + int stepmul = (int)luaL_optinteger(L, 3, 0); + int stepsize = (int)luaL_optinteger(L, 4, 0); + return pushmode(L, lua_gc(L, o, pause, stepmul, stepsize)); + } + default: { + int res = lua_gc(L, o); + checkvalres(res); + lua_pushinteger(L, res); + return 1; + } + } + luaL_pushfail(L); /* invalid call (inside a finalizer) */ + return 1; +} + + +static int luaB_type (lua_State *L) { + int t = lua_type(L, 1); + luaL_argcheck(L, t != LUA_TNONE, 1, "value expected"); + lua_pushstring(L, lua_typename(L, t)); + return 1; +} + + +static int luaB_next (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, 1)) + return 2; + else { + lua_pushnil(L); + return 1; + } +} + + +static int pairscont (lua_State *L, int status, lua_KContext k) { + (void)L; (void)status; (void)k; /* unused */ + return 3; +} + +static int luaB_pairs (lua_State *L) { + luaL_checkany(L, 1); + if (luaL_getmetafield(L, 1, "__pairs") == LUA_TNIL) { /* no metamethod? */ + lua_pushcfunction(L, luaB_next); /* will return generator, */ + lua_pushvalue(L, 1); /* state, */ + lua_pushnil(L); /* and initial value */ + } + else { + lua_pushvalue(L, 1); /* argument 'self' to metamethod */ + lua_callk(L, 1, 3, 0, pairscont); /* get 3 values from metamethod */ + } + return 3; +} + + +/* +** Traversal function for 'ipairs' +*/ +static int ipairsaux (lua_State *L) { + lua_Integer i = luaL_checkinteger(L, 2); + i = luaL_intop(+, i, 1); + lua_pushinteger(L, i); + return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; +} + + +/* +** 'ipairs' function. Returns 'ipairsaux', given "table", 0. +** (The given "table" may not be a table.) +*/ +static int luaB_ipairs (lua_State *L) { + luaL_checkany(L, 1); + lua_pushcfunction(L, ipairsaux); /* iteration function */ + lua_pushvalue(L, 1); /* state */ + lua_pushinteger(L, 0); /* initial value */ + return 3; +} + + +static int load_aux (lua_State *L, int status, int envidx) { + if (l_likely(status == LUA_OK)) { + if (envidx != 0) { /* 'env' parameter? */ + lua_pushvalue(L, envidx); /* environment for loaded function */ + if (!lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */ + lua_pop(L, 1); /* remove 'env' if not used by previous call */ + } + return 1; + } + else { /* error (message is on top of the stack) */ + luaL_pushfail(L); + lua_insert(L, -2); /* put before error message */ + return 2; /* return fail plus error message */ + } +} + + +static int luaB_loadfile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + const char *mode = luaL_optstring(L, 2, NULL); + int env = (!lua_isnone(L, 3) ? 3 : 0); /* 'env' index or 0 if no 'env' */ + int status = luaL_loadfilex(L, fname, mode); + return load_aux(L, status, env); +} + + +/* +** {====================================================== +** Generic Read function +** ======================================================= +*/ + + +/* +** reserved slot, above all arguments, to hold a copy of the returned +** string to avoid it being collected while parsed. 'load' has four +** optional arguments (chunk, source name, mode, and environment). +*/ +#define RESERVEDSLOT 5 + + +/* +** Reader for generic 'load' function: 'lua_load' uses the +** stack for internal stuff, so the reader cannot change the +** stack top. Instead, it keeps its resulting string in a +** reserved slot inside the stack. +*/ +static const char *generic_reader (lua_State *L, void *ud, size_t *size) { + (void)(ud); /* not used */ + luaL_checkstack(L, 2, "too many nested functions"); + lua_pushvalue(L, 1); /* get function */ + lua_call(L, 0, 1); /* call it */ + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* pop result */ + *size = 0; + return NULL; + } + else if (l_unlikely(!lua_isstring(L, -1))) + luaL_error(L, "reader function must return a string"); + lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */ + return lua_tolstring(L, RESERVEDSLOT, size); +} + + +static int luaB_load (lua_State *L) { + int status; + size_t l; + const char *s = lua_tolstring(L, 1, &l); + const char *mode = luaL_optstring(L, 3, "bt"); + int env = (!lua_isnone(L, 4) ? 4 : 0); /* 'env' index or 0 if no 'env' */ + if (s != NULL) { /* loading a string? */ + const char *chunkname = luaL_optstring(L, 2, s); + status = luaL_loadbufferx(L, s, l, chunkname, mode); + } + else { /* loading from a reader function */ + const char *chunkname = luaL_optstring(L, 2, "=(load)"); + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, RESERVEDSLOT); /* create reserved slot */ + status = lua_load(L, generic_reader, NULL, chunkname, mode); + } + return load_aux(L, status, env); +} + +/* }====================================================== */ + + +static int dofilecont (lua_State *L, int d1, lua_KContext d2) { + (void)d1; (void)d2; /* only to match 'lua_Kfunction' prototype */ + return lua_gettop(L) - 1; +} + + +static int luaB_dofile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + lua_settop(L, 1); + if (l_unlikely(luaL_loadfile(L, fname) != LUA_OK)) + return lua_error(L); + lua_callk(L, 0, LUA_MULTRET, 0, dofilecont); + return dofilecont(L, 0, 0); +} + + +static int luaB_assert (lua_State *L) { + if (l_likely(lua_toboolean(L, 1))) /* condition is true? */ + return lua_gettop(L); /* return all arguments */ + else { /* error */ + luaL_checkany(L, 1); /* there must be a condition */ + lua_remove(L, 1); /* remove it */ + lua_pushliteral(L, "assertion failed!"); /* default message */ + lua_settop(L, 1); /* leave only message (default if no other one) */ + return luaB_error(L); /* call 'error' */ + } +} + + +static int luaB_select (lua_State *L) { + int n = lua_gettop(L); + if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') { + lua_pushinteger(L, n-1); + return 1; + } + else { + lua_Integer i = luaL_checkinteger(L, 1); + if (i < 0) i = n + i; + else if (i > n) i = n; + luaL_argcheck(L, 1 <= i, 1, "index out of range"); + return n - (int)i; + } +} + + +/* +** Continuation function for 'pcall' and 'xpcall'. Both functions +** already pushed a 'true' before doing the call, so in case of success +** 'finishpcall' only has to return everything in the stack minus +** 'extra' values (where 'extra' is exactly the number of items to be +** ignored). +*/ +static int finishpcall (lua_State *L, int status, lua_KContext extra) { + if (l_unlikely(status != LUA_OK && status != LUA_YIELD)) { /* error? */ + lua_pushboolean(L, 0); /* first result (false) */ + lua_pushvalue(L, -2); /* error message */ + return 2; /* return false, msg */ + } + else + return lua_gettop(L) - (int)extra; /* return all results */ +} + + +static int luaB_pcall (lua_State *L) { + int status; + luaL_checkany(L, 1); + lua_pushboolean(L, 1); /* first result if no errors */ + lua_insert(L, 1); /* put it in place */ + status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall); + return finishpcall(L, status, 0); +} + + +/* +** Do a protected call with error handling. After 'lua_rotate', the +** stack will have ; so, the function passes +** 2 to 'finishpcall' to skip the 2 first values when returning results. +*/ +static int luaB_xpcall (lua_State *L) { + int status; + int n = lua_gettop(L); + luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */ + lua_pushboolean(L, 1); /* first result */ + lua_pushvalue(L, 1); /* function */ + lua_rotate(L, 3, 2); /* move them below function's arguments */ + status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall); + return finishpcall(L, status, 2); +} + + +static int luaB_tostring (lua_State *L) { + luaL_checkany(L, 1); + luaL_tolstring(L, 1, NULL); + return 1; +} + + +static const luaL_Reg base_funcs[] = { + {"assert", luaB_assert}, + {"collectgarbage", luaB_collectgarbage}, + {"dofile", luaB_dofile}, + {"error", luaB_error}, + {"getmetatable", luaB_getmetatable}, + {"ipairs", luaB_ipairs}, + {"loadfile", luaB_loadfile}, + {"load", luaB_load}, + {"next", luaB_next}, + {"pairs", luaB_pairs}, + {"pcall", luaB_pcall}, + {"print", luaB_print}, + {"warn", luaB_warn}, + {"rawequal", luaB_rawequal}, + {"rawlen", luaB_rawlen}, + {"rawget", luaB_rawget}, + {"rawset", luaB_rawset}, + {"select", luaB_select}, + {"setmetatable", luaB_setmetatable}, + {"tonumber", luaB_tonumber}, + {"tostring", luaB_tostring}, + {"type", luaB_type}, + {"xpcall", luaB_xpcall}, + /* placeholders */ + {LUA_GNAME, NULL}, + {"_VERSION", NULL}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_base (lua_State *L) { + /* open lib into global table */ + lua_pushglobaltable(L); + luaL_setfuncs(L, base_funcs, 0); + /* set global _G */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, LUA_GNAME); + /* set global _VERSION */ + lua_pushliteral(L, LUA_VERSION); + lua_setfield(L, -2, "_VERSION"); + return 1; +} + diff --git a/arm9/source/lua/lcode.c b/arm9/source/lua/lcode.c new file mode 100644 index 000000000..1a371ca94 --- /dev/null +++ b/arm9/source/lua/lcode.c @@ -0,0 +1,1871 @@ +/* +** $Id: lcode.c $ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + +#define lcode_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include +#include + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstring.h" +#include "ltable.h" +#include "lvm.h" + + +/* Maximum number of registers in a Lua function (must fit in 8 bits) */ +#define MAXREGS 255 + + +#define hasjumps(e) ((e)->t != (e)->f) + + +static int codesJ (FuncState *fs, OpCode o, int sj, int k); + + + +/* semantic error */ +l_noret luaK_semerror (LexState *ls, const char *msg) { + ls->t.token = 0; /* remove "near " from final message */ + luaX_syntaxerror(ls, msg); +} + + +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +static int tonumeral (const expdesc *e, TValue *v) { + if (hasjumps(e)) + return 0; /* not a numeral */ + switch (e->k) { + case VKINT: + if (v) setivalue(v, e->u.ival); + return 1; + case VKFLT: + if (v) setfltvalue(v, e->u.nval); + return 1; + default: return 0; + } +} + + +/* +** Get the constant value from a constant expression +*/ +static TValue *const2val (FuncState *fs, const expdesc *e) { + lua_assert(e->k == VCONST); + return &fs->ls->dyd->actvar.arr[e->u.info].k; +} + + +/* +** If expression is a constant, fills 'v' with its value +** and returns 1. Otherwise, returns 0. +*/ +int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v) { + if (hasjumps(e)) + return 0; /* not a constant */ + switch (e->k) { + case VFALSE: + setbfvalue(v); + return 1; + case VTRUE: + setbtvalue(v); + return 1; + case VNIL: + setnilvalue(v); + return 1; + case VKSTR: { + setsvalue(fs->ls->L, v, e->u.strval); + return 1; + } + case VCONST: { + setobj(fs->ls->L, v, const2val(fs, e)); + return 1; + } + default: return tonumeral(e, v); + } +} + + +/* +** Return the previous instruction of the current code. If there +** may be a jump target between the current instruction and the +** previous one, return an invalid instruction (to avoid wrong +** optimizations). +*/ +static Instruction *previousinstruction (FuncState *fs) { + static const Instruction invalidinstruction = ~(Instruction)0; + if (fs->pc > fs->lasttarget) + return &fs->f->code[fs->pc - 1]; /* previous instruction */ + else + return cast(Instruction*, &invalidinstruction); +} + + +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ +void luaK_nil (FuncState *fs, int from, int n) { + int l = from + n - 1; /* last register to set nil */ + Instruction *previous = previousinstruction(fs); + if (GET_OPCODE(*previous) == OP_LOADNIL) { /* previous is LOADNIL? */ + int pfrom = GETARG_A(*previous); /* get previous range */ + int pl = pfrom + GETARG_B(*previous); + if ((pfrom <= from && from <= pl + 1) || + (from <= pfrom && pfrom <= l + 1)) { /* can connect both? */ + if (pfrom < from) from = pfrom; /* from = min(from, pfrom) */ + if (pl > l) l = pl; /* l = max(l, pl) */ + SETARG_A(*previous, from); + SETARG_B(*previous, l - from); + return; + } /* else go through */ + } + luaK_codeABC(fs, OP_LOADNIL, from, n - 1, 0); /* else no optimization */ +} + + +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sJ(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +static void fixjump (FuncState *fs, int pc, int dest) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + lua_assert(dest != NO_JUMP); + if (!(-OFFSET_sJ <= offset && offset <= MAXARG_sJ - OFFSET_sJ)) + luaX_syntaxerror(fs->ls, "control structure too long"); + lua_assert(GET_OPCODE(*jmp) == OP_JMP); + SETARG_sJ(*jmp, offset); +} + + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; /* nothing to concatenate? */ + else if (*l1 == NO_JUMP) /* no original list? */ + *l1 = l2; /* 'l1' points to 'l2' */ + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); /* last element links to 'l2' */ + } +} + + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). +*/ +int luaK_jump (FuncState *fs) { + return codesJ(fs, OP_JMP, NO_JUMP, 0); +} + + +/* +** Code a 'return' instruction +*/ +void luaK_ret (FuncState *fs, int first, int nret) { + OpCode op; + switch (nret) { + case 0: op = OP_RETURN0; break; + case 1: op = OP_RETURN1; break; + default: op = OP_RETURN; break; + } + luaK_codeABC(fs, op, first, nret + 1, 0); +} + + +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ +static int condjump (FuncState *fs, OpCode op, int A, int B, int C, int k) { + luaK_codeABCk(fs, op, A, B, C, k); + return luaK_jump(fs); +} + + +/* +** returns current 'pc' and marks it as a jump target (to avoid wrong +** optimizations with consecutive instructions not in the same basic block). +*/ +int luaK_getlabel (FuncState *fs) { + fs->lasttarget = fs->pc; + return fs->pc; +} + + +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ +static Instruction *getjumpcontrol (FuncState *fs, int pc) { + Instruction *pi = &fs->f->code[pc]; + if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) + return pi-1; + else + return pi; +} + + +/* +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) +*/ +static int patchtestreg (FuncState *fs, int node, int reg) { + Instruction *i = getjumpcontrol(fs, node); + if (GET_OPCODE(*i) != OP_TESTSET) + return 0; /* cannot patch other instructions */ + if (reg != NO_REG && reg != GETARG_B(*i)) + SETARG_A(*i, reg); + else { + /* no register to put value or register already has the value; + change instruction to simple test */ + *i = CREATE_ABCk(OP_TEST, GETARG_B(*i), 0, 0, GETARG_k(*i)); + } + return 1; +} + + +/* +** Traverse a list of tests ensuring no one produces a value +*/ +static void removevalues (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) + patchtestreg(fs, list, NO_REG); +} + + +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ +static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, + int dtarget) { + while (list != NO_JUMP) { + int next = getjump(fs, list); + if (patchtestreg(fs, list, reg)) + fixjump(fs, list, vtarget); + else + fixjump(fs, list, dtarget); /* jump to default target */ + list = next; + } +} + + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ +void luaK_patchlist (FuncState *fs, int list, int target) { + lua_assert(target <= fs->pc); + patchlistaux(fs, list, target, NO_REG, target); +} + + +void luaK_patchtohere (FuncState *fs, int list) { + int hr = luaK_getlabel(fs); /* mark "here" as a jump target */ + luaK_patchlist(fs, list, hr); +} + + +/* limit for difference between lines in relative line info. */ +#define LIMLINEDIFF 0x80 + + +/* +** Save line info for a new instruction. If difference from last line +** does not fit in a byte, of after that many instructions, save a new +** absolute line info; (in that case, the special value 'ABSLINEINFO' +** in 'lineinfo' signals the existence of this absolute information.) +** Otherwise, store the difference from last line in 'lineinfo'. +*/ +static void savelineinfo (FuncState *fs, Proto *f, int line) { + int linedif = line - fs->previousline; + int pc = fs->pc - 1; /* last instruction coded */ + if (abs(linedif) >= LIMLINEDIFF || fs->iwthabs++ >= MAXIWTHABS) { + luaM_growvector(fs->ls->L, f->abslineinfo, fs->nabslineinfo, + f->sizeabslineinfo, AbsLineInfo, MAX_INT, "lines"); + f->abslineinfo[fs->nabslineinfo].pc = pc; + f->abslineinfo[fs->nabslineinfo++].line = line; + linedif = ABSLINEINFO; /* signal that there is absolute information */ + fs->iwthabs = 1; /* restart counter */ + } + luaM_growvector(fs->ls->L, f->lineinfo, pc, f->sizelineinfo, ls_byte, + MAX_INT, "opcodes"); + f->lineinfo[pc] = linedif; + fs->previousline = line; /* last line saved */ +} + + +/* +** Remove line information from the last instruction. +** If line information for that instruction is absolute, set 'iwthabs' +** above its max to force the new (replacing) instruction to have +** absolute line info, too. +*/ +static void removelastlineinfo (FuncState *fs) { + Proto *f = fs->f; + int pc = fs->pc - 1; /* last instruction coded */ + if (f->lineinfo[pc] != ABSLINEINFO) { /* relative line info? */ + fs->previousline -= f->lineinfo[pc]; /* correct last line saved */ + fs->iwthabs--; /* undo previous increment */ + } + else { /* absolute line information */ + lua_assert(f->abslineinfo[fs->nabslineinfo - 1].pc == pc); + fs->nabslineinfo--; /* remove it */ + fs->iwthabs = MAXIWTHABS + 1; /* force next line info to be absolute */ + } +} + + +/* +** Remove the last instruction created, correcting line information +** accordingly. +*/ +static void removelastinstruction (FuncState *fs) { + removelastlineinfo(fs); + fs->pc--; +} + + +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ +int luaK_code (FuncState *fs, Instruction i) { + Proto *f = fs->f; + /* put new instruction in code array */ + luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction, + MAX_INT, "opcodes"); + f->code[fs->pc++] = i; + savelineinfo(fs, f, fs->ls->lastline); + return fs->pc - 1; /* index of new instruction */ +} + + +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ +int luaK_codeABCk (FuncState *fs, OpCode o, int a, int b, int c, int k) { + lua_assert(getOpMode(o) == iABC); + lua_assert(a <= MAXARG_A && b <= MAXARG_B && + c <= MAXARG_C && (k & ~1) == 0); + return luaK_code(fs, CREATE_ABCk(o, a, b, c, k)); +} + + +/* +** Format and emit an 'iABx' instruction. +*/ +int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { + lua_assert(getOpMode(o) == iABx); + lua_assert(a <= MAXARG_A && bc <= MAXARG_Bx); + return luaK_code(fs, CREATE_ABx(o, a, bc)); +} + + +/* +** Format and emit an 'iAsBx' instruction. +*/ +int luaK_codeAsBx (FuncState *fs, OpCode o, int a, int bc) { + unsigned int b = bc + OFFSET_sBx; + lua_assert(getOpMode(o) == iAsBx); + lua_assert(a <= MAXARG_A && b <= MAXARG_Bx); + return luaK_code(fs, CREATE_ABx(o, a, b)); +} + + +/* +** Format and emit an 'isJ' instruction. +*/ +static int codesJ (FuncState *fs, OpCode o, int sj, int k) { + unsigned int j = sj + OFFSET_sJ; + lua_assert(getOpMode(o) == isJ); + lua_assert(j <= MAXARG_sJ && (k & ~1) == 0); + return luaK_code(fs, CREATE_sJ(o, j, k)); +} + + +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ +static int codeextraarg (FuncState *fs, int a) { + lua_assert(a <= MAXARG_Ax); + return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a)); +} + + +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ +static int luaK_codek (FuncState *fs, int reg, int k) { + if (k <= MAXARG_Bx) + return luaK_codeABx(fs, OP_LOADK, reg, k); + else { + int p = luaK_codeABx(fs, OP_LOADKX, reg, 0); + codeextraarg(fs, k); + return p; + } +} + + +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ +void luaK_checkstack (FuncState *fs, int n) { + int newstack = fs->freereg + n; + if (newstack > fs->f->maxstacksize) { + if (newstack >= MAXREGS) + luaX_syntaxerror(fs->ls, + "function or expression needs too many registers"); + fs->f->maxstacksize = cast_byte(newstack); + } +} + + +/* +** Reserve 'n' registers in register stack +*/ +void luaK_reserveregs (FuncState *fs, int n) { + luaK_checkstack(fs, n); + fs->freereg += n; +} + + +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +) +*/ +static void freereg (FuncState *fs, int reg) { + if (reg >= luaY_nvarstack(fs)) { + fs->freereg--; + lua_assert(reg == fs->freereg); + } +} + + +/* +** Free two registers in proper order +*/ +static void freeregs (FuncState *fs, int r1, int r2) { + if (r1 > r2) { + freereg(fs, r1); + freereg(fs, r2); + } + else { + freereg(fs, r2); + freereg(fs, r1); + } +} + + +/* +** Free register used by expression 'e' (if any) +*/ +static void freeexp (FuncState *fs, expdesc *e) { + if (e->k == VNONRELOC) + freereg(fs, e->u.info); +} + + +/* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { + int r1 = (e1->k == VNONRELOC) ? e1->u.info : -1; + int r2 = (e2->k == VNONRELOC) ? e2->u.info : -1; + freeregs(fs, r1, r2); +} + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). +** Use scanner's table to cache position of constants in constant list +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. +** Note that all functions share the same table, so entering or exiting +** a function can make some indices wrong. +*/ +static int addk (FuncState *fs, TValue *key, TValue *v) { + TValue val; + lua_State *L = fs->ls->L; + Proto *f = fs->f; + const TValue *idx = luaH_get(fs->ls->h, key); /* query scanner table */ + int k, oldsize; + if (ttisinteger(idx)) { /* is there an index there? */ + k = cast_int(ivalue(idx)); + /* correct value? (warning: must distinguish floats from integers!) */ + if (k < fs->nk && ttypetag(&f->k[k]) == ttypetag(v) && + luaV_rawequalobj(&f->k[k], v)) + return k; /* reuse index */ + } + /* constant not found; create a new entry */ + oldsize = f->sizek; + k = fs->nk; + /* numerical value does not need GC barrier; + table has no metatable, so it does not need to invalidate cache */ + setivalue(&val, k); + luaH_finishset(L, fs->ls->h, key, idx, &val); + luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); + while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); + setobj(L, &f->k[k], v); + fs->nk++; + luaC_barrier(L, f, v); + return k; +} + + +/* +** Add a string to list of constants and return its index. +*/ +static int stringK (FuncState *fs, TString *s) { + TValue o; + setsvalue(fs->ls->L, &o, s); + return addk(fs, &o, &o); /* use string itself as key */ +} + + +/* +** Add an integer to list of constants and return its index. +*/ +static int luaK_intK (FuncState *fs, lua_Integer n) { + TValue o; + setivalue(&o, n); + return addk(fs, &o, &o); /* use integer itself as key */ +} + +/* +** Add a float to list of constants and return its index. Floats +** with integral values need a different key, to avoid collision +** with actual integers. To that, we add to the number its smaller +** power-of-two fraction that is still significant in its scale. +** For doubles, that would be 1/2^52. +** (This method is not bulletproof: there may be another float +** with that value, and for floats larger than 2^53 the result is +** still an integer. At worst, this only wastes an entry with +** a duplicate.) +*/ +static int luaK_numberK (FuncState *fs, lua_Number r) { + TValue o; + lua_Integer ik; + setfltvalue(&o, r); + if (!luaV_flttointeger(r, &ik, F2Ieq)) /* not an integral value? */ + return addk(fs, &o, &o); /* use number itself as key */ + else { /* must build an alternative key */ + const int nbm = l_floatatt(MANT_DIG); + const lua_Number q = l_mathop(ldexp)(l_mathop(1.0), -nbm + 1); + const lua_Number k = (ik == 0) ? q : r + r*q; /* new key */ + TValue kv; + setfltvalue(&kv, k); + /* result is not an integral value, unless value is too large */ + lua_assert(!luaV_flttointeger(k, &ik, F2Ieq) || + l_mathop(fabs)(r) >= l_mathop(1e6)); + return addk(fs, &kv, &o); + } +} + + +/* +** Add a false to list of constants and return its index. +*/ +static int boolF (FuncState *fs) { + TValue o; + setbfvalue(&o); + return addk(fs, &o, &o); /* use boolean itself as key */ +} + + +/* +** Add a true to list of constants and return its index. +*/ +static int boolT (FuncState *fs) { + TValue o; + setbtvalue(&o); + return addk(fs, &o, &o); /* use boolean itself as key */ +} + + +/* +** Add nil to list of constants and return its index. +*/ +static int nilK (FuncState *fs) { + TValue k, v; + setnilvalue(&v); + /* cannot use nil as key; instead use table itself to represent nil */ + sethvalue(fs->ls->L, &k, fs->ls->h); + return addk(fs, &k, &v); +} + + +/* +** Check whether 'i' can be stored in an 'sC' operand. Equivalent to +** (0 <= int2sC(i) && int2sC(i) <= MAXARG_C) but without risk of +** overflows in the hidden addition inside 'int2sC'. +*/ +static int fitsC (lua_Integer i) { + return (l_castS2U(i) + OFFSET_sC <= cast_uint(MAXARG_C)); +} + + +/* +** Check whether 'i' can be stored in an 'sBx' operand. +*/ +static int fitsBx (lua_Integer i) { + return (-OFFSET_sBx <= i && i <= MAXARG_Bx - OFFSET_sBx); +} + + +void luaK_int (FuncState *fs, int reg, lua_Integer i) { + if (fitsBx(i)) + luaK_codeAsBx(fs, OP_LOADI, reg, cast_int(i)); + else + luaK_codek(fs, reg, luaK_intK(fs, i)); +} + + +static void luaK_float (FuncState *fs, int reg, lua_Number f) { + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ieq) && fitsBx(fi)) + luaK_codeAsBx(fs, OP_LOADF, reg, cast_int(fi)); + else + luaK_codek(fs, reg, luaK_numberK(fs, f)); +} + + +/* +** Convert a constant in 'v' into an expression description 'e' +*/ +static void const2exp (TValue *v, expdesc *e) { + switch (ttypetag(v)) { + case LUA_VNUMINT: + e->k = VKINT; e->u.ival = ivalue(v); + break; + case LUA_VNUMFLT: + e->k = VKFLT; e->u.nval = fltvalue(v); + break; + case LUA_VFALSE: + e->k = VFALSE; + break; + case LUA_VTRUE: + e->k = VTRUE; + break; + case LUA_VNIL: + e->k = VNIL; + break; + case LUA_VSHRSTR: case LUA_VLNGSTR: + e->k = VKSTR; e->u.strval = tsvalue(v); + break; + default: lua_assert(0); + } +} + + +/* +** Fix an expression to return the number of results 'nresults'. +** 'e' must be a multi-ret expression (function call or vararg). +*/ +void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { + Instruction *pc = &getinstruction(fs, e); + if (e->k == VCALL) /* expression is an open function call? */ + SETARG_C(*pc, nresults + 1); + else { + lua_assert(e->k == VVARARG); + SETARG_C(*pc, nresults + 1); + SETARG_A(*pc, fs->freereg); + luaK_reserveregs(fs, 1); + } +} + + +/* +** Convert a VKSTR to a VK +*/ +static void str2K (FuncState *fs, expdesc *e) { + lua_assert(e->k == VKSTR); + e->u.info = stringK(fs, e->u.strval); + e->k = VK; +} + + +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOC (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ +void luaK_setoneret (FuncState *fs, expdesc *e) { + if (e->k == VCALL) { /* expression is an open function call? */ + /* already returns 1 value */ + lua_assert(GETARG_C(getinstruction(fs, e)) == 2); + e->k = VNONRELOC; /* result has fixed position */ + e->u.info = GETARG_A(getinstruction(fs, e)); + } + else if (e->k == VVARARG) { + SETARG_C(getinstruction(fs, e), 2); + e->k = VRELOC; /* can relocate its simple result */ + } +} + + +/* +** Ensure that expression 'e' is not a variable (nor a ). +** (Expression still may have jump lists.) +*/ +void luaK_dischargevars (FuncState *fs, expdesc *e) { + switch (e->k) { + case VCONST: { + const2exp(const2val(fs, e), e); + break; + } + case VLOCAL: { /* already in a register */ + e->u.info = e->u.var.ridx; + e->k = VNONRELOC; /* becomes a non-relocatable value */ + break; + } + case VUPVAL: { /* move value to some (pending) register */ + e->u.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.info, 0); + e->k = VRELOC; + break; + } + case VINDEXUP: { + e->u.info = luaK_codeABC(fs, OP_GETTABUP, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXI: { + freereg(fs, e->u.ind.t); + e->u.info = luaK_codeABC(fs, OP_GETI, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXSTR: { + freereg(fs, e->u.ind.t); + e->u.info = luaK_codeABC(fs, OP_GETFIELD, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VINDEXED: { + freeregs(fs, e->u.ind.t, e->u.ind.idx); + e->u.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } + case VVARARG: case VCALL: { + luaK_setoneret(fs, e); + break; + } + default: break; /* there is one value available (somewhere) */ + } +} + + +/* +** Ensure expression value is in register 'reg', making 'e' a +** non-relocatable expression. +** (Expression still may have jump lists.) +*/ +static void discharge2reg (FuncState *fs, expdesc *e, int reg) { + luaK_dischargevars(fs, e); + switch (e->k) { + case VNIL: { + luaK_nil(fs, reg, 1); + break; + } + case VFALSE: { + luaK_codeABC(fs, OP_LOADFALSE, reg, 0, 0); + break; + } + case VTRUE: { + luaK_codeABC(fs, OP_LOADTRUE, reg, 0, 0); + break; + } + case VKSTR: { + str2K(fs, e); + } /* FALLTHROUGH */ + case VK: { + luaK_codek(fs, reg, e->u.info); + break; + } + case VKFLT: { + luaK_float(fs, reg, e->u.nval); + break; + } + case VKINT: { + luaK_int(fs, reg, e->u.ival); + break; + } + case VRELOC: { + Instruction *pc = &getinstruction(fs, e); + SETARG_A(*pc, reg); /* instruction will put result in 'reg' */ + break; + } + case VNONRELOC: { + if (reg != e->u.info) + luaK_codeABC(fs, OP_MOVE, reg, e->u.info, 0); + break; + } + default: { + lua_assert(e->k == VJMP); + return; /* nothing to do... */ + } + } + e->u.info = reg; + e->k = VNONRELOC; +} + + +/* +** Ensure expression value is in a register, making 'e' a +** non-relocatable expression. +** (Expression still may have jump lists.) +*/ +static void discharge2anyreg (FuncState *fs, expdesc *e) { + if (e->k != VNONRELOC) { /* no fixed register yet? */ + luaK_reserveregs(fs, 1); /* get a register */ + discharge2reg(fs, e, fs->freereg-1); /* put value there */ + } +} + + +static int code_loadbool (FuncState *fs, int A, OpCode op) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, op, A, 0, 0); +} + + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; + } + return 0; /* not found */ +} + + +/* +** Ensures final expression result (which includes results from its +** jump lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ +static void exp2reg (FuncState *fs, expdesc *e, int reg) { + discharge2reg(fs, e, reg); + if (e->k == VJMP) /* expression itself is a test? */ + luaK_concat(fs, &e->t, e->u.info); /* put this jump in 't' list */ + if (hasjumps(e)) { + int final; /* position after whole expression */ + int p_f = NO_JUMP; /* position of an eventual LOAD false */ + int p_t = NO_JUMP; /* position of an eventual LOAD true */ + if (need_value(fs, e->t) || need_value(fs, e->f)) { + int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); + p_f = code_loadbool(fs, reg, OP_LFALSESKIP); /* skip next inst. */ + p_t = code_loadbool(fs, reg, OP_LOADTRUE); + /* jump around these booleans if 'e' is not a test */ + luaK_patchtohere(fs, fj); + } + final = luaK_getlabel(fs); + patchlistaux(fs, e->f, final, reg, p_f); + patchlistaux(fs, e->t, final, reg, p_t); + } + e->f = e->t = NO_JUMP; + e->u.info = reg; + e->k = VNONRELOC; +} + + +/* +** Ensures final expression result is in next available register. +*/ +void luaK_exp2nextreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + freeexp(fs, e); + luaK_reserveregs(fs, 1); + exp2reg(fs, e, fs->freereg - 1); +} + + +/* +** Ensures final expression result is in some (any) register +** and return that register. +*/ +int luaK_exp2anyreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + if (e->k == VNONRELOC) { /* expression already has a register? */ + if (!hasjumps(e)) /* no jumps? */ + return e->u.info; /* result is already in a register */ + if (e->u.info >= luaY_nvarstack(fs)) { /* reg. is not a local? */ + exp2reg(fs, e, e->u.info); /* put final result in it */ + return e->u.info; + } + /* else expression has jumps and cannot change its register + to hold the jump values, because it is a local variable. + Go through to the default case. */ + } + luaK_exp2nextreg(fs, e); /* default: use next available register */ + return e->u.info; +} + + +/* +** Ensures final expression result is either in a register +** or in an upvalue. +*/ +void luaK_exp2anyregup (FuncState *fs, expdesc *e) { + if (e->k != VUPVAL || hasjumps(e)) + luaK_exp2anyreg(fs, e); +} + + +/* +** Ensures final expression result is either in a register +** or it is a constant. +*/ +void luaK_exp2val (FuncState *fs, expdesc *e) { + if (hasjumps(e)) + luaK_exp2anyreg(fs, e); + else + luaK_dischargevars(fs, e); +} + + +/* +** Try to make 'e' a K expression with an index in the range of R/K +** indices. Return true iff succeeded. +*/ +static int luaK_exp2K (FuncState *fs, expdesc *e) { + if (!hasjumps(e)) { + int info; + switch (e->k) { /* move constants to 'k' */ + case VTRUE: info = boolT(fs); break; + case VFALSE: info = boolF(fs); break; + case VNIL: info = nilK(fs); break; + case VKINT: info = luaK_intK(fs, e->u.ival); break; + case VKFLT: info = luaK_numberK(fs, e->u.nval); break; + case VKSTR: info = stringK(fs, e->u.strval); break; + case VK: info = e->u.info; break; + default: return 0; /* not a constant */ + } + if (info <= MAXINDEXRK) { /* does constant fit in 'argC'? */ + e->k = VK; /* make expression a 'K' expression */ + e->u.info = info; + return 1; + } + } + /* else, expression doesn't fit; leave it unchanged */ + return 0; +} + + +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns 1 iff expression is K. +*/ +int luaK_exp2RK (FuncState *fs, expdesc *e) { + if (luaK_exp2K(fs, e)) + return 1; + else { /* not a constant in the right range: put it in a register */ + luaK_exp2anyreg(fs, e); + return 0; + } +} + + +static void codeABRK (FuncState *fs, OpCode o, int a, int b, + expdesc *ec) { + int k = luaK_exp2RK(fs, ec); + luaK_codeABCk(fs, o, a, b, ec->u.info, k); +} + + +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ +void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { + switch (var->k) { + case VLOCAL: { + freeexp(fs, ex); + exp2reg(fs, ex, var->u.var.ridx); /* compute 'ex' into proper place */ + return; + } + case VUPVAL: { + int e = luaK_exp2anyreg(fs, ex); + luaK_codeABC(fs, OP_SETUPVAL, e, var->u.info, 0); + break; + } + case VINDEXUP: { + codeABRK(fs, OP_SETTABUP, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXI: { + codeABRK(fs, OP_SETI, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXSTR: { + codeABRK(fs, OP_SETFIELD, var->u.ind.t, var->u.ind.idx, ex); + break; + } + case VINDEXED: { + codeABRK(fs, OP_SETTABLE, var->u.ind.t, var->u.ind.idx, ex); + break; + } + default: lua_assert(0); /* invalid var kind to store */ + } + freeexp(fs, ex); +} + + +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ +void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { + int ereg; + luaK_exp2anyreg(fs, e); + ereg = e->u.info; /* register where 'e' was placed */ + freeexp(fs, e); + e->u.info = fs->freereg; /* base register for op_self */ + e->k = VNONRELOC; /* self expression has a fixed register */ + luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ + codeABRK(fs, OP_SELF, e->u.info, ereg, key); + freeexp(fs, key); +} + + +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +static void negatecondition (FuncState *fs, expdesc *e) { + Instruction *pc = getjumpcontrol(fs, e->u.info); + lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && + GET_OPCODE(*pc) != OP_TEST); + SETARG_k(*pc, (GETARG_k(*pc) ^ 1)); +} + + +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ +static int jumponcond (FuncState *fs, expdesc *e, int cond) { + if (e->k == VRELOC) { + Instruction ie = getinstruction(fs, e); + if (GET_OPCODE(ie) == OP_NOT) { + removelastinstruction(fs); /* remove previous OP_NOT */ + return condjump(fs, OP_TEST, GETARG_B(ie), 0, 0, !cond); + } + /* else go through */ + } + discharge2anyreg(fs, e); + freeexp(fs, e); + return condjump(fs, OP_TESTSET, NO_REG, e->u.info, 0, cond); +} + + +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ +void luaK_goiftrue (FuncState *fs, expdesc *e) { + int pc; /* pc of new jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VJMP: { /* condition? */ + negatecondition(fs, e); /* jump when it is false */ + pc = e->u.info; /* save jump position */ + break; + } + case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: { + pc = NO_JUMP; /* always true; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 0); /* jump when false */ + break; + } + } + luaK_concat(fs, &e->f, pc); /* insert new jump in false list */ + luaK_patchtohere(fs, e->t); /* true list jumps to here (to go through) */ + e->t = NO_JUMP; +} + + +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ +void luaK_goiffalse (FuncState *fs, expdesc *e) { + int pc; /* pc of new jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VJMP: { + pc = e->u.info; /* already jump if true */ + break; + } + case VNIL: case VFALSE: { + pc = NO_JUMP; /* always false; do nothing */ + break; + } + default: { + pc = jumponcond(fs, e, 1); /* jump if true */ + break; + } + } + luaK_concat(fs, &e->t, pc); /* insert new jump in 't' list */ + luaK_patchtohere(fs, e->f); /* false list jumps to here (to go through) */ + e->f = NO_JUMP; +} + + +/* +** Code 'not e', doing constant folding. +*/ +static void codenot (FuncState *fs, expdesc *e) { + switch (e->k) { + case VNIL: case VFALSE: { + e->k = VTRUE; /* true == not nil == not false */ + break; + } + case VK: case VKFLT: case VKINT: case VKSTR: case VTRUE: { + e->k = VFALSE; /* false == not "x" == not 0.5 == not 1 == not true */ + break; + } + case VJMP: { + negatecondition(fs, e); + break; + } + case VRELOC: + case VNONRELOC: { + discharge2anyreg(fs, e); + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, OP_NOT, 0, e->u.info, 0); + e->k = VRELOC; + break; + } + default: lua_assert(0); /* cannot happen */ + } + /* interchange true and false lists */ + { int temp = e->f; e->f = e->t; e->t = temp; } + removevalues(fs, e->f); /* values are useless when negated */ + removevalues(fs, e->t); +} + + +/* +** Check whether expression 'e' is a small literal string +*/ +static int isKstr (FuncState *fs, expdesc *e) { + return (e->k == VK && !hasjumps(e) && e->u.info <= MAXARG_B && + ttisshrstring(&fs->f->k[e->u.info])); +} + +/* +** Check whether expression 'e' is a literal integer. +*/ +int luaK_isKint (expdesc *e) { + return (e->k == VKINT && !hasjumps(e)); +} + + +/* +** Check whether expression 'e' is a literal integer in +** proper range to fit in register C +*/ +static int isCint (expdesc *e) { + return luaK_isKint(e) && (l_castS2U(e->u.ival) <= l_castS2U(MAXARG_C)); +} + + +/* +** Check whether expression 'e' is a literal integer in +** proper range to fit in register sC +*/ +static int isSCint (expdesc *e) { + return luaK_isKint(e) && fitsC(e->u.ival); +} + + +/* +** Check whether expression 'e' is a literal integer or float in +** proper range to fit in a register (sB or sC). +*/ +static int isSCnumber (expdesc *e, int *pi, int *isfloat) { + lua_Integer i; + if (e->k == VKINT) + i = e->u.ival; + else if (e->k == VKFLT && luaV_flttointeger(e->u.nval, &i, F2Ieq)) + *isfloat = 1; + else + return 0; /* not a number */ + if (!hasjumps(e) && fitsC(i)) { + *pi = int2sC(cast_int(i)); + return 1; + } + else + return 0; +} + + +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. Upvalues can only be indexed by literal strings. +** Keys can be literal strings in the constant table or arbitrary +** values in registers. +*/ +void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { + if (k->k == VKSTR) + str2K(fs, k); + lua_assert(!hasjumps(t) && + (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL)); + if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */ + luaK_exp2anyreg(fs, t); /* put it in a register */ + if (t->k == VUPVAL) { + t->u.ind.t = t->u.info; /* upvalue index */ + t->u.ind.idx = k->u.info; /* literal string */ + t->k = VINDEXUP; + } + else { + /* register index of the table */ + t->u.ind.t = (t->k == VLOCAL) ? t->u.var.ridx: t->u.info; + if (isKstr(fs, k)) { + t->u.ind.idx = k->u.info; /* literal string */ + t->k = VINDEXSTR; + } + else if (isCint(k)) { + t->u.ind.idx = cast_int(k->u.ival); /* int. constant in proper range */ + t->k = VINDEXI; + } + else { + t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */ + t->k = VINDEXED; + } + } +} + + +/* +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. +*/ +static int validop (int op, TValue *v1, TValue *v2) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: { /* conversion errors */ + lua_Integer i; + return (luaV_tointegerns(v1, &i, LUA_FLOORN2I) && + luaV_tointegerns(v2, &i, LUA_FLOORN2I)); + } + case LUA_OPDIV: case LUA_OPIDIV: case LUA_OPMOD: /* division by 0 */ + return (nvalue(v2) != 0); + default: return 1; /* everything else is valid */ + } +} + + +/* +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) +*/ +static int constfolding (FuncState *fs, int op, expdesc *e1, + const expdesc *e2) { + TValue v1, v2, res; + if (!tonumeral(e1, &v1) || !tonumeral(e2, &v2) || !validop(op, &v1, &v2)) + return 0; /* non-numeric operands or not safe to fold */ + luaO_rawarith(fs->ls->L, op, &v1, &v2, &res); /* does operation */ + if (ttisinteger(&res)) { + e1->k = VKINT; + e1->u.ival = ivalue(&res); + } + else { /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ + lua_Number n = fltvalue(&res); + if (luai_numisnan(n) || n == 0) + return 0; + e1->k = VKFLT; + e1->u.nval = n; + } + return 1; +} + + +/* +** Convert a BinOpr to an OpCode (ORDER OPR - ORDER OP) +*/ +l_sinline OpCode binopr2op (BinOpr opr, BinOpr baser, OpCode base) { + lua_assert(baser <= opr && + ((baser == OPR_ADD && opr <= OPR_SHR) || + (baser == OPR_LT && opr <= OPR_LE))); + return cast(OpCode, (cast_int(opr) - cast_int(baser)) + cast_int(base)); +} + + +/* +** Convert a UnOpr to an OpCode (ORDER OPR - ORDER OP) +*/ +l_sinline OpCode unopr2op (UnOpr opr) { + return cast(OpCode, (cast_int(opr) - cast_int(OPR_MINUS)) + + cast_int(OP_UNM)); +} + + +/* +** Convert a BinOpr to a tag method (ORDER OPR - ORDER TM) +*/ +l_sinline TMS binopr2TM (BinOpr opr) { + lua_assert(OPR_ADD <= opr && opr <= OPR_SHR); + return cast(TMS, (cast_int(opr) - cast_int(OPR_ADD)) + cast_int(TM_ADD)); +} + + +/* +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. +*/ +static void codeunexpval (FuncState *fs, OpCode op, expdesc *e, int line) { + int r = luaK_exp2anyreg(fs, e); /* opcodes operate only on registers */ + freeexp(fs, e); + e->u.info = luaK_codeABC(fs, op, 0, r, 0); /* generate opcode */ + e->k = VRELOC; /* all those operations are relocatable */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). +** Expression to produce final result will be encoded in 'e1'. +*/ +static void finishbinexpval (FuncState *fs, expdesc *e1, expdesc *e2, + OpCode op, int v2, int flip, int line, + OpCode mmop, TMS event) { + int v1 = luaK_exp2anyreg(fs, e1); + int pc = luaK_codeABCk(fs, op, 0, v1, v2, 0); + freeexps(fs, e1, e2); + e1->u.info = pc; + e1->k = VRELOC; /* all those operations are relocatable */ + luaK_fixline(fs, line); + luaK_codeABCk(fs, mmop, v1, v2, event, flip); /* to call metamethod */ + luaK_fixline(fs, line); +} + + +/* +** Emit code for binary expressions that "produce values" over +** two registers. +*/ +static void codebinexpval (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int line) { + OpCode op = binopr2op(opr, OPR_ADD, OP_ADD); + int v2 = luaK_exp2anyreg(fs, e2); /* make sure 'e2' is in a register */ + /* 'e1' must be already in a register or it is a constant */ + lua_assert((VNIL <= e1->k && e1->k <= VKSTR) || + e1->k == VNONRELOC || e1->k == VRELOC); + lua_assert(OP_ADD <= op && op <= OP_SHR); + finishbinexpval(fs, e1, e2, op, v2, 0, line, OP_MMBIN, binopr2TM(opr)); +} + + +/* +** Code binary operators with immediate operands. +*/ +static void codebini (FuncState *fs, OpCode op, + expdesc *e1, expdesc *e2, int flip, int line, + TMS event) { + int v2 = int2sC(cast_int(e2->u.ival)); /* immediate operand */ + lua_assert(e2->k == VKINT); + finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINI, event); +} + + +/* +** Code binary operators with K operand. +*/ +static void codebinK (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int flip, int line) { + TMS event = binopr2TM(opr); + int v2 = e2->u.info; /* K index */ + OpCode op = binopr2op(opr, OPR_ADD, OP_ADDK); + finishbinexpval(fs, e1, e2, op, v2, flip, line, OP_MMBINK, event); +} + + +/* Try to code a binary operator negating its second operand. +** For the metamethod, 2nd operand must keep its original value. +*/ +static int finishbinexpneg (FuncState *fs, expdesc *e1, expdesc *e2, + OpCode op, int line, TMS event) { + if (!luaK_isKint(e2)) + return 0; /* not an integer constant */ + else { + lua_Integer i2 = e2->u.ival; + if (!(fitsC(i2) && fitsC(-i2))) + return 0; /* not in the proper range */ + else { /* operating a small integer constant */ + int v2 = cast_int(i2); + finishbinexpval(fs, e1, e2, op, int2sC(-v2), 0, line, OP_MMBINI, event); + /* correct metamethod argument */ + SETARG_B(fs->f->code[fs->pc - 1], int2sC(v2)); + return 1; /* successfully coded */ + } + } +} + + +static void swapexps (expdesc *e1, expdesc *e2) { + expdesc temp = *e1; *e1 = *e2; *e2 = temp; /* swap 'e1' and 'e2' */ +} + + +/* +** Code binary operators with no constant operand. +*/ +static void codebinNoK (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int flip, int line) { + if (flip) + swapexps(e1, e2); /* back to original order */ + codebinexpval(fs, opr, e1, e2, line); /* use standard operators */ +} + + +/* +** Code arithmetic operators ('+', '-', ...). If second operand is a +** constant in the proper range, use variant opcodes with K operands. +*/ +static void codearith (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int flip, int line) { + if (tonumeral(e2, NULL) && luaK_exp2K(fs, e2)) /* K operand? */ + codebinK(fs, opr, e1, e2, flip, line); + else /* 'e2' is neither an immediate nor a K operand */ + codebinNoK(fs, opr, e1, e2, flip, line); +} + + +/* +** Code commutative operators ('+', '*'). If first operand is a +** numeric constant, change order of operands to try to use an +** immediate or K operator. +*/ +static void codecommutative (FuncState *fs, BinOpr op, + expdesc *e1, expdesc *e2, int line) { + int flip = 0; + if (tonumeral(e1, NULL)) { /* is first operand a numeric constant? */ + swapexps(e1, e2); /* change order */ + flip = 1; + } + if (op == OPR_ADD && isSCint(e2)) /* immediate operand? */ + codebini(fs, OP_ADDI, e1, e2, flip, line, TM_ADD); + else + codearith(fs, op, e1, e2, flip, line); +} + + +/* +** Code bitwise operations; they are all commutative, so the function +** tries to put an integer constant as the 2nd operand (a K operand). +*/ +static void codebitwise (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int line) { + int flip = 0; + if (e1->k == VKINT) { + swapexps(e1, e2); /* 'e2' will be the constant operand */ + flip = 1; + } + if (e2->k == VKINT && luaK_exp2K(fs, e2)) /* K operand? */ + codebinK(fs, opr, e1, e2, flip, line); + else /* no constants */ + codebinNoK(fs, opr, e1, e2, flip, line); +} + + +/* +** Emit code for order comparisons. When using an immediate operand, +** 'isfloat' tells whether the original value was a float. +*/ +static void codeorder (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int r1, r2; + int im; + int isfloat = 0; + OpCode op; + if (isSCnumber(e2, &im, &isfloat)) { + /* use immediate operand */ + r1 = luaK_exp2anyreg(fs, e1); + r2 = im; + op = binopr2op(opr, OPR_LT, OP_LTI); + } + else if (isSCnumber(e1, &im, &isfloat)) { + /* transform (A < B) to (B > A) and (A <= B) to (B >= A) */ + r1 = luaK_exp2anyreg(fs, e2); + r2 = im; + op = binopr2op(opr, OPR_LT, OP_GTI); + } + else { /* regular case, compare two registers */ + r1 = luaK_exp2anyreg(fs, e1); + r2 = luaK_exp2anyreg(fs, e2); + op = binopr2op(opr, OPR_LT, OP_LT); + } + freeexps(fs, e1, e2); + e1->u.info = condjump(fs, op, r1, r2, isfloat, 1); + e1->k = VJMP; +} + + +/* +** Emit code for equality comparisons ('==', '~='). +** 'e1' was already put as RK by 'luaK_infix'. +*/ +static void codeeq (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { + int r1, r2; + int im; + int isfloat = 0; /* not needed here, but kept for symmetry */ + OpCode op; + if (e1->k != VNONRELOC) { + lua_assert(e1->k == VK || e1->k == VKINT || e1->k == VKFLT); + swapexps(e1, e2); + } + r1 = luaK_exp2anyreg(fs, e1); /* 1st expression must be in register */ + if (isSCnumber(e2, &im, &isfloat)) { + op = OP_EQI; + r2 = im; /* immediate operand */ + } + else if (luaK_exp2RK(fs, e2)) { /* 2nd expression is constant? */ + op = OP_EQK; + r2 = e2->u.info; /* constant index */ + } + else { + op = OP_EQ; /* will compare two registers */ + r2 = luaK_exp2anyreg(fs, e2); + } + freeexps(fs, e1, e2); + e1->u.info = condjump(fs, op, r1, r2, isfloat, (opr == OPR_EQ)); + e1->k = VJMP; +} + + +/* +** Apply prefix operation 'op' to expression 'e'. +*/ +void luaK_prefix (FuncState *fs, UnOpr opr, expdesc *e, int line) { + static const expdesc ef = {VKINT, {0}, NO_JUMP, NO_JUMP}; + luaK_dischargevars(fs, e); + switch (opr) { + case OPR_MINUS: case OPR_BNOT: /* use 'ef' as fake 2nd operand */ + if (constfolding(fs, opr + LUA_OPUNM, e, &ef)) + break; + /* else */ /* FALLTHROUGH */ + case OPR_LEN: + codeunexpval(fs, unopr2op(opr), e, line); + break; + case OPR_NOT: codenot(fs, e); break; + default: lua_assert(0); + } +} + + +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ +void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { + luaK_dischargevars(fs, v); + switch (op) { + case OPR_AND: { + luaK_goiftrue(fs, v); /* go ahead only if 'v' is true */ + break; + } + case OPR_OR: { + luaK_goiffalse(fs, v); /* go ahead only if 'v' is false */ + break; + } + case OPR_CONCAT: { + luaK_exp2nextreg(fs, v); /* operand must be on the stack */ + break; + } + case OPR_ADD: case OPR_SUB: + case OPR_MUL: case OPR_DIV: case OPR_IDIV: + case OPR_MOD: case OPR_POW: + case OPR_BAND: case OPR_BOR: case OPR_BXOR: + case OPR_SHL: case OPR_SHR: { + if (!tonumeral(v, NULL)) + luaK_exp2anyreg(fs, v); + /* else keep numeral, which may be folded or used as an immediate + operand */ + break; + } + case OPR_EQ: case OPR_NE: { + if (!tonumeral(v, NULL)) + luaK_exp2RK(fs, v); + /* else keep numeral, which may be an immediate operand */ + break; + } + case OPR_LT: case OPR_LE: + case OPR_GT: case OPR_GE: { + int dummy, dummy2; + if (!isSCnumber(v, &dummy, &dummy2)) + luaK_exp2anyreg(fs, v); + /* else keep numeral, which may be an immediate operand */ + break; + } + default: lua_assert(0); + } +} + +/* +** Create code for '(e1 .. e2)'. +** For '(e1 .. e2.1 .. e2.2)' (which is '(e1 .. (e2.1 .. e2.2))', +** because concatenation is right associative), merge both CONCATs. +*/ +static void codeconcat (FuncState *fs, expdesc *e1, expdesc *e2, int line) { + Instruction *ie2 = previousinstruction(fs); + if (GET_OPCODE(*ie2) == OP_CONCAT) { /* is 'e2' a concatenation? */ + int n = GETARG_B(*ie2); /* # of elements concatenated in 'e2' */ + lua_assert(e1->u.info + 1 == GETARG_A(*ie2)); + freeexp(fs, e2); + SETARG_A(*ie2, e1->u.info); /* correct first element ('e1') */ + SETARG_B(*ie2, n + 1); /* will concatenate one more element */ + } + else { /* 'e2' is not a concatenation */ + luaK_codeABC(fs, OP_CONCAT, e1->u.info, 2, 0); /* new concat opcode */ + freeexp(fs, e2); + luaK_fixline(fs, line); + } +} + + +/* +** Finalize code for binary operation, after reading 2nd operand. +*/ +void luaK_posfix (FuncState *fs, BinOpr opr, + expdesc *e1, expdesc *e2, int line) { + luaK_dischargevars(fs, e2); + if (foldbinop(opr) && constfolding(fs, opr + LUA_OPADD, e1, e2)) + return; /* done by folding */ + switch (opr) { + case OPR_AND: { + lua_assert(e1->t == NO_JUMP); /* list closed by 'luaK_infix' */ + luaK_concat(fs, &e2->f, e1->f); + *e1 = *e2; + break; + } + case OPR_OR: { + lua_assert(e1->f == NO_JUMP); /* list closed by 'luaK_infix' */ + luaK_concat(fs, &e2->t, e1->t); + *e1 = *e2; + break; + } + case OPR_CONCAT: { /* e1 .. e2 */ + luaK_exp2nextreg(fs, e2); + codeconcat(fs, e1, e2, line); + break; + } + case OPR_ADD: case OPR_MUL: { + codecommutative(fs, opr, e1, e2, line); + break; + } + case OPR_SUB: { + if (finishbinexpneg(fs, e1, e2, OP_ADDI, line, TM_SUB)) + break; /* coded as (r1 + -I) */ + /* ELSE */ + } /* FALLTHROUGH */ + case OPR_DIV: case OPR_IDIV: case OPR_MOD: case OPR_POW: { + codearith(fs, opr, e1, e2, 0, line); + break; + } + case OPR_BAND: case OPR_BOR: case OPR_BXOR: { + codebitwise(fs, opr, e1, e2, line); + break; + } + case OPR_SHL: { + if (isSCint(e1)) { + swapexps(e1, e2); + codebini(fs, OP_SHLI, e1, e2, 1, line, TM_SHL); /* I << r2 */ + } + else if (finishbinexpneg(fs, e1, e2, OP_SHRI, line, TM_SHL)) { + /* coded as (r1 >> -I) */; + } + else /* regular case (two registers) */ + codebinexpval(fs, opr, e1, e2, line); + break; + } + case OPR_SHR: { + if (isSCint(e2)) + codebini(fs, OP_SHRI, e1, e2, 0, line, TM_SHR); /* r1 >> I */ + else /* regular case (two registers) */ + codebinexpval(fs, opr, e1, e2, line); + break; + } + case OPR_EQ: case OPR_NE: { + codeeq(fs, opr, e1, e2); + break; + } + case OPR_GT: case OPR_GE: { + /* '(a > b)' <=> '(b < a)'; '(a >= b)' <=> '(b <= a)' */ + swapexps(e1, e2); + opr = cast(BinOpr, (opr - OPR_GT) + OPR_LT); + } /* FALLTHROUGH */ + case OPR_LT: case OPR_LE: { + codeorder(fs, opr, e1, e2); + break; + } + default: lua_assert(0); + } +} + + +/* +** Change line information associated with current position, by removing +** previous info and adding it again with new line. +*/ +void luaK_fixline (FuncState *fs, int line) { + removelastlineinfo(fs); + savelineinfo(fs, fs->f, line); +} + + +void luaK_settablesize (FuncState *fs, int pc, int ra, int asize, int hsize) { + Instruction *inst = &fs->f->code[pc]; + int rb = (hsize != 0) ? luaO_ceillog2(hsize) + 1 : 0; /* hash size */ + int extra = asize / (MAXARG_C + 1); /* higher bits of array size */ + int rc = asize % (MAXARG_C + 1); /* lower bits of array size */ + int k = (extra > 0); /* true iff needs extra argument */ + *inst = CREATE_ABCk(OP_NEWTABLE, ra, rb, rc, k); + *(inst + 1) = CREATE_Ax(OP_EXTRAARG, extra); +} + + +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ +void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { + lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH); + if (tostore == LUA_MULTRET) + tostore = 0; + if (nelems <= MAXARG_C) + luaK_codeABC(fs, OP_SETLIST, base, tostore, nelems); + else { + int extra = nelems / (MAXARG_C + 1); + nelems %= (MAXARG_C + 1); + luaK_codeABCk(fs, OP_SETLIST, base, tostore, nelems, 1); + codeextraarg(fs, extra); + } + fs->freereg = base + 1; /* free registers with list values */ +} + + +/* +** return the final target of a jump (skipping jumps to jumps) +*/ +static int finaltarget (Instruction *code, int i) { + int count; + for (count = 0; count < 100; count++) { /* avoid infinite loops */ + Instruction pc = code[i]; + if (GET_OPCODE(pc) != OP_JMP) + break; + else + i += GETARG_sJ(pc) + 1; + } + return i; +} + + +/* +** Do a final pass over the code of a function, doing small peephole +** optimizations and adjustments. +*/ +void luaK_finish (FuncState *fs) { + int i; + Proto *p = fs->f; + for (i = 0; i < fs->pc; i++) { + Instruction *pc = &p->code[i]; + lua_assert(i == 0 || isOT(*(pc - 1)) == isIT(*pc)); + switch (GET_OPCODE(*pc)) { + case OP_RETURN0: case OP_RETURN1: { + if (!(fs->needclose || p->is_vararg)) + break; /* no extra work */ + /* else use OP_RETURN to do the extra work */ + SET_OPCODE(*pc, OP_RETURN); + } /* FALLTHROUGH */ + case OP_RETURN: case OP_TAILCALL: { + if (fs->needclose) + SETARG_k(*pc, 1); /* signal that it needs to close */ + if (p->is_vararg) + SETARG_C(*pc, p->numparams + 1); /* signal that it is vararg */ + break; + } + case OP_JMP: { + int target = finaltarget(p->code, i); + fixjump(fs, i, target); + break; + } + default: break; + } + } +} diff --git a/arm9/source/lua/lcode.h b/arm9/source/lua/lcode.h new file mode 100644 index 000000000..326582445 --- /dev/null +++ b/arm9/source/lua/lcode.h @@ -0,0 +1,104 @@ +/* +** $Id: lcode.h $ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef lcode_h +#define lcode_h + +#include "llex.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" + + +/* +** Marks the end of a patch list. It is an invalid value both as an absolute +** address, and as a list link (would link an element to itself). +*/ +#define NO_JUMP (-1) + + +/* +** grep "ORDER OPR" if you change these enums (ORDER OP) +*/ +typedef enum BinOpr { + /* arithmetic operators */ + OPR_ADD, OPR_SUB, OPR_MUL, OPR_MOD, OPR_POW, + OPR_DIV, OPR_IDIV, + /* bitwise operators */ + OPR_BAND, OPR_BOR, OPR_BXOR, + OPR_SHL, OPR_SHR, + /* string operator */ + OPR_CONCAT, + /* comparison operators */ + OPR_EQ, OPR_LT, OPR_LE, + OPR_NE, OPR_GT, OPR_GE, + /* logical operators */ + OPR_AND, OPR_OR, + OPR_NOBINOPR +} BinOpr; + + +/* true if operation is foldable (that is, it is arithmetic or bitwise) */ +#define foldbinop(op) ((op) <= OPR_SHR) + + +#define luaK_codeABC(fs,o,a,b,c) luaK_codeABCk(fs,o,a,b,c,0) + + +typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; + + +/* get (pointer to) instruction of given 'expdesc' */ +#define getinstruction(fs,e) ((fs)->f->code[(e)->u.info]) + + +#define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET) + +#define luaK_jumpto(fs,t) luaK_patchlist(fs, luaK_jump(fs), t) + +LUAI_FUNC int luaK_code (FuncState *fs, Instruction i); +LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); +LUAI_FUNC int luaK_codeAsBx (FuncState *fs, OpCode o, int A, int Bx); +LUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A, + int B, int C, int k); +LUAI_FUNC int luaK_isKint (expdesc *e); +LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v); +LUAI_FUNC void luaK_fixline (FuncState *fs, int line); +LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); +LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); +LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); +LUAI_FUNC void luaK_int (FuncState *fs, int reg, lua_Integer n); +LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); +LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); +LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_goiffalse (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e); +LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults); +LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_jump (FuncState *fs); +LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret); +LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target); +LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list); +LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2); +LUAI_FUNC int luaK_getlabel (FuncState *fs); +LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line); +LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v); +LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, + expdesc *v2, int line); +LUAI_FUNC void luaK_settablesize (FuncState *fs, int pc, + int ra, int asize, int hsize); +LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); +LUAI_FUNC void luaK_finish (FuncState *fs); +LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg); + + +#endif diff --git a/arm9/source/lua/lcorolib.c b/arm9/source/lua/lcorolib.c new file mode 100644 index 000000000..c64adf08a --- /dev/null +++ b/arm9/source/lua/lcorolib.c @@ -0,0 +1,210 @@ +/* +** $Id: lcorolib.c $ +** Coroutine Library +** See Copyright Notice in lua.h +*/ + +#define lcorolib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +static lua_State *getco (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argexpected(L, co, 1, "thread"); + return co; +} + + +/* +** Resumes a coroutine. Returns the number of results for non-error +** cases or -1 for errors. +*/ +static int auxresume (lua_State *L, lua_State *co, int narg) { + int status, nres; + if (l_unlikely(!lua_checkstack(co, narg))) { + lua_pushliteral(L, "too many arguments to resume"); + return -1; /* error flag */ + } + lua_xmove(L, co, narg); + status = lua_resume(co, L, narg, &nres); + if (l_likely(status == LUA_OK || status == LUA_YIELD)) { + if (l_unlikely(!lua_checkstack(L, nres + 1))) { + lua_pop(co, nres); /* remove results anyway */ + lua_pushliteral(L, "too many results to resume"); + return -1; /* error flag */ + } + lua_xmove(co, L, nres); /* move yielded values */ + return nres; + } + else { + lua_xmove(co, L, 1); /* move error message */ + return -1; /* error flag */ + } +} + + +static int luaB_coresume (lua_State *L) { + lua_State *co = getco(L); + int r; + r = auxresume(L, co, lua_gettop(L) - 1); + if (l_unlikely(r < 0)) { + lua_pushboolean(L, 0); + lua_insert(L, -2); + return 2; /* return false + error message */ + } + else { + lua_pushboolean(L, 1); + lua_insert(L, -(r + 1)); + return r + 1; /* return true + 'resume' returns */ + } +} + + +static int luaB_auxwrap (lua_State *L) { + lua_State *co = lua_tothread(L, lua_upvalueindex(1)); + int r = auxresume(L, co, lua_gettop(L)); + if (l_unlikely(r < 0)) { /* error? */ + int stat = lua_status(co); + if (stat != LUA_OK && stat != LUA_YIELD) { /* error in the coroutine? */ + stat = lua_closethread(co, L); /* close its tbc variables */ + lua_assert(stat != LUA_OK); + lua_xmove(co, L, 1); /* move error message to the caller */ + } + if (stat != LUA_ERRMEM && /* not a memory error and ... */ + lua_type(L, -1) == LUA_TSTRING) { /* ... error object is a string? */ + luaL_where(L, 1); /* add extra info, if available */ + lua_insert(L, -2); + lua_concat(L, 2); + } + return lua_error(L); /* propagate error */ + } + return r; +} + + +static int luaB_cocreate (lua_State *L) { + lua_State *NL; + luaL_checktype(L, 1, LUA_TFUNCTION); + NL = lua_newthread(L); + lua_pushvalue(L, 1); /* move function to top */ + lua_xmove(L, NL, 1); /* move function from L to NL */ + return 1; +} + + +static int luaB_cowrap (lua_State *L) { + luaB_cocreate(L); + lua_pushcclosure(L, luaB_auxwrap, 1); + return 1; +} + + +static int luaB_yield (lua_State *L) { + return lua_yield(L, lua_gettop(L)); +} + + +#define COS_RUN 0 +#define COS_DEAD 1 +#define COS_YIELD 2 +#define COS_NORM 3 + + +static const char *const statname[] = + {"running", "dead", "suspended", "normal"}; + + +static int auxstatus (lua_State *L, lua_State *co) { + if (L == co) return COS_RUN; + else { + switch (lua_status(co)) { + case LUA_YIELD: + return COS_YIELD; + case LUA_OK: { + lua_Debug ar; + if (lua_getstack(co, 0, &ar)) /* does it have frames? */ + return COS_NORM; /* it is running */ + else if (lua_gettop(co) == 0) + return COS_DEAD; + else + return COS_YIELD; /* initial state */ + } + default: /* some error occurred */ + return COS_DEAD; + } + } +} + + +static int luaB_costatus (lua_State *L) { + lua_State *co = getco(L); + lua_pushstring(L, statname[auxstatus(L, co)]); + return 1; +} + + +static int luaB_yieldable (lua_State *L) { + lua_State *co = lua_isnone(L, 1) ? L : getco(L); + lua_pushboolean(L, lua_isyieldable(co)); + return 1; +} + + +static int luaB_corunning (lua_State *L) { + int ismain = lua_pushthread(L); + lua_pushboolean(L, ismain); + return 2; +} + + +static int luaB_close (lua_State *L) { + lua_State *co = getco(L); + int status = auxstatus(L, co); + switch (status) { + case COS_DEAD: case COS_YIELD: { + status = lua_closethread(co, L); + if (status == LUA_OK) { + lua_pushboolean(L, 1); + return 1; + } + else { + lua_pushboolean(L, 0); + lua_xmove(co, L, 1); /* move error message */ + return 2; + } + } + default: /* normal or running coroutine */ + return luaL_error(L, "cannot close a %s coroutine", statname[status]); + } +} + + +static const luaL_Reg co_funcs[] = { + {"create", luaB_cocreate}, + {"resume", luaB_coresume}, + {"running", luaB_corunning}, + {"status", luaB_costatus}, + {"wrap", luaB_cowrap}, + {"yield", luaB_yield}, + {"isyieldable", luaB_yieldable}, + {"close", luaB_close}, + {NULL, NULL} +}; + + + +LUAMOD_API int luaopen_coroutine (lua_State *L) { + luaL_newlib(L, co_funcs); + return 1; +} + diff --git a/arm9/source/lua/lctype.c b/arm9/source/lua/lctype.c new file mode 100644 index 000000000..954228094 --- /dev/null +++ b/arm9/source/lua/lctype.c @@ -0,0 +1,64 @@ +/* +** $Id: lctype.c $ +** 'ctype' functions for Lua +** See Copyright Notice in lua.h +*/ + +#define lctype_c +#define LUA_CORE + +#include "lprefix.h" + + +#include "lctype.h" + +#if !LUA_USE_CTYPE /* { */ + +#include + + +#if defined (LUA_UCID) /* accept UniCode IDentifiers? */ +/* consider all non-ascii codepoints to be alphabetic */ +#define NONA 0x01 +#else +#define NONA 0x00 /* default */ +#endif + + +LUAI_DDEF const lu_byte luai_ctype_[UCHAR_MAX + 2] = { + 0x00, /* EOZ */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0. */ + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, /* 2. */ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, /* 3. */ + 0x16, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 4. */ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 5. */ + 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x05, + 0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 6. */ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 7. */ + 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x00, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 8. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 9. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* a. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* b. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + 0x00, 0x00, NONA, NONA, NONA, NONA, NONA, NONA, /* c. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* d. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* e. */ + NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, + NONA, NONA, NONA, NONA, NONA, 0x00, 0x00, 0x00, /* f. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +#endif /* } */ diff --git a/arm9/source/lua/lctype.h b/arm9/source/lua/lctype.h new file mode 100644 index 000000000..864e19018 --- /dev/null +++ b/arm9/source/lua/lctype.h @@ -0,0 +1,101 @@ +/* +** $Id: lctype.h $ +** 'ctype' functions for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef lctype_h +#define lctype_h + +#include "lua.h" + + +/* +** WARNING: the functions defined here do not necessarily correspond +** to the similar functions in the standard C ctype.h. They are +** optimized for the specific needs of Lua. +*/ + +#if !defined(LUA_USE_CTYPE) + +#if 'A' == 65 && '0' == 48 +/* ASCII case: can use its own tables; faster and fixed */ +#define LUA_USE_CTYPE 0 +#else +/* must use standard C ctype */ +#define LUA_USE_CTYPE 1 +#endif + +#endif + + +#if !LUA_USE_CTYPE /* { */ + +#include + +#include "llimits.h" + + +#define ALPHABIT 0 +#define DIGITBIT 1 +#define PRINTBIT 2 +#define SPACEBIT 3 +#define XDIGITBIT 4 + + +#define MASK(B) (1 << (B)) + + +/* +** add 1 to char to allow index -1 (EOZ) +*/ +#define testprop(c,p) (luai_ctype_[(c)+1] & (p)) + +/* +** 'lalpha' (Lua alphabetic) and 'lalnum' (Lua alphanumeric) both include '_' +*/ +#define lislalpha(c) testprop(c, MASK(ALPHABIT)) +#define lislalnum(c) testprop(c, (MASK(ALPHABIT) | MASK(DIGITBIT))) +#define lisdigit(c) testprop(c, MASK(DIGITBIT)) +#define lisspace(c) testprop(c, MASK(SPACEBIT)) +#define lisprint(c) testprop(c, MASK(PRINTBIT)) +#define lisxdigit(c) testprop(c, MASK(XDIGITBIT)) + + +/* +** In ASCII, this 'ltolower' is correct for alphabetic characters and +** for '.'. That is enough for Lua needs. ('check_exp' ensures that +** the character either is an upper-case letter or is unchanged by +** the transformation, which holds for lower-case letters and '.'.) +*/ +#define ltolower(c) \ + check_exp(('A' <= (c) && (c) <= 'Z') || (c) == ((c) | ('A' ^ 'a')), \ + (c) | ('A' ^ 'a')) + + +/* one entry for each character and for -1 (EOZ) */ +LUAI_DDEC(const lu_byte luai_ctype_[UCHAR_MAX + 2];) + + +#else /* }{ */ + +/* +** use standard C ctypes +*/ + +#include + + +#define lislalpha(c) (isalpha(c) || (c) == '_') +#define lislalnum(c) (isalnum(c) || (c) == '_') +#define lisdigit(c) (isdigit(c)) +#define lisspace(c) (isspace(c)) +#define lisprint(c) (isprint(c)) +#define lisxdigit(c) (isxdigit(c)) + +#define ltolower(c) (tolower(c)) + +#endif /* } */ + +#endif + diff --git a/arm9/source/lua/ldblib.c b/arm9/source/lua/ldblib.c new file mode 100644 index 000000000..6dcbaa982 --- /dev/null +++ b/arm9/source/lua/ldblib.c @@ -0,0 +1,483 @@ +/* +** $Id: ldblib.c $ +** Interface from Lua to its debug API +** See Copyright Notice in lua.h +*/ + +#define ldblib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** The hook table at registry[HOOKKEY] maps threads to their current +** hook function. +*/ +static const char *const HOOKKEY = "_HOOKKEY"; + + +/* +** If L1 != L, L1 can be in any state, and therefore there are no +** guarantees about its stack space; any push in L1 must be +** checked. +*/ +static void checkstack (lua_State *L, lua_State *L1, int n) { + if (l_unlikely(L != L1 && !lua_checkstack(L1, n))) + luaL_error(L, "stack overflow"); +} + + +static int db_getregistry (lua_State *L) { + lua_pushvalue(L, LUA_REGISTRYINDEX); + return 1; +} + + +static int db_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); /* no metatable */ + } + return 1; +} + + +static int db_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table"); + lua_settop(L, 2); + lua_setmetatable(L, 1); + return 1; /* return 1st argument */ +} + + +static int db_getuservalue (lua_State *L) { + int n = (int)luaL_optinteger(L, 2, 1); + if (lua_type(L, 1) != LUA_TUSERDATA) + luaL_pushfail(L); + else if (lua_getiuservalue(L, 1, n) != LUA_TNONE) { + lua_pushboolean(L, 1); + return 2; + } + return 1; +} + + +static int db_setuservalue (lua_State *L) { + int n = (int)luaL_optinteger(L, 3, 1); + luaL_checktype(L, 1, LUA_TUSERDATA); + luaL_checkany(L, 2); + lua_settop(L, 2); + if (!lua_setiuservalue(L, 1, n)) + luaL_pushfail(L); + return 1; +} + + +/* +** Auxiliary function used by several library functions: check for +** an optional thread as function's first argument and set 'arg' with +** 1 if this argument is present (so that functions can skip it to +** access their other arguments) +*/ +static lua_State *getthread (lua_State *L, int *arg) { + if (lua_isthread(L, 1)) { + *arg = 1; + return lua_tothread(L, 1); + } + else { + *arg = 0; + return L; /* function will operate over current thread */ + } +} + + +/* +** Variations of 'lua_settable', used by 'db_getinfo' to put results +** from 'lua_getinfo' into result table. Key is always a string; +** value can be a string, an int, or a boolean. +*/ +static void settabss (lua_State *L, const char *k, const char *v) { + lua_pushstring(L, v); + lua_setfield(L, -2, k); +} + +static void settabsi (lua_State *L, const char *k, int v) { + lua_pushinteger(L, v); + lua_setfield(L, -2, k); +} + +static void settabsb (lua_State *L, const char *k, int v) { + lua_pushboolean(L, v); + lua_setfield(L, -2, k); +} + + +/* +** In function 'db_getinfo', the call to 'lua_getinfo' may push +** results on the stack; later it creates the result table to put +** these objects. Function 'treatstackoption' puts the result from +** 'lua_getinfo' on top of the result table so that it can call +** 'lua_setfield'. +*/ +static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { + if (L == L1) + lua_rotate(L, -2, 1); /* exchange object and table */ + else + lua_xmove(L1, L, 1); /* move object to the "main" stack */ + lua_setfield(L, -2, fname); /* put object into table */ +} + + +/* +** Calls 'lua_getinfo' and collects all results in a new table. +** L1 needs stack space for an optional input (function) plus +** two optional outputs (function and line table) from function +** 'lua_getinfo'. +*/ +static int db_getinfo (lua_State *L) { + lua_Debug ar; + int arg; + lua_State *L1 = getthread(L, &arg); + const char *options = luaL_optstring(L, arg+2, "flnSrtu"); + checkstack(L, L1, 3); + luaL_argcheck(L, options[0] != '>', arg + 2, "invalid option '>'"); + if (lua_isfunction(L, arg + 1)) { /* info about a function? */ + options = lua_pushfstring(L, ">%s", options); /* add '>' to 'options' */ + lua_pushvalue(L, arg + 1); /* move function to 'L1' stack */ + lua_xmove(L, L1, 1); + } + else { /* stack level */ + if (!lua_getstack(L1, (int)luaL_checkinteger(L, arg + 1), &ar)) { + luaL_pushfail(L); /* level out of range */ + return 1; + } + } + if (!lua_getinfo(L1, options, &ar)) + return luaL_argerror(L, arg+2, "invalid option"); + lua_newtable(L); /* table to collect results */ + if (strchr(options, 'S')) { + lua_pushlstring(L, ar.source, ar.srclen); + lua_setfield(L, -2, "source"); + settabss(L, "short_src", ar.short_src); + settabsi(L, "linedefined", ar.linedefined); + settabsi(L, "lastlinedefined", ar.lastlinedefined); + settabss(L, "what", ar.what); + } + if (strchr(options, 'l')) + settabsi(L, "currentline", ar.currentline); + if (strchr(options, 'u')) { + settabsi(L, "nups", ar.nups); + settabsi(L, "nparams", ar.nparams); + settabsb(L, "isvararg", ar.isvararg); + } + if (strchr(options, 'n')) { + settabss(L, "name", ar.name); + settabss(L, "namewhat", ar.namewhat); + } + if (strchr(options, 'r')) { + settabsi(L, "ftransfer", ar.ftransfer); + settabsi(L, "ntransfer", ar.ntransfer); + } + if (strchr(options, 't')) + settabsb(L, "istailcall", ar.istailcall); + if (strchr(options, 'L')) + treatstackoption(L, L1, "activelines"); + if (strchr(options, 'f')) + treatstackoption(L, L1, "func"); + return 1; /* return table */ +} + + +static int db_getlocal (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + int nvar = (int)luaL_checkinteger(L, arg + 2); /* local-variable index */ + if (lua_isfunction(L, arg + 1)) { /* function argument? */ + lua_pushvalue(L, arg + 1); /* push function */ + lua_pushstring(L, lua_getlocal(L, NULL, nvar)); /* push local name */ + return 1; /* return only name (there is no value) */ + } + else { /* stack-level argument */ + lua_Debug ar; + const char *name; + int level = (int)luaL_checkinteger(L, arg + 1); + if (l_unlikely(!lua_getstack(L1, level, &ar))) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + checkstack(L, L1, 1); + name = lua_getlocal(L1, &ar, nvar); + if (name) { + lua_xmove(L1, L, 1); /* move local value */ + lua_pushstring(L, name); /* push name */ + lua_rotate(L, -2, 1); /* re-order */ + return 2; + } + else { + luaL_pushfail(L); /* no name (nor value) */ + return 1; + } + } +} + + +static int db_setlocal (lua_State *L) { + int arg; + const char *name; + lua_State *L1 = getthread(L, &arg); + lua_Debug ar; + int level = (int)luaL_checkinteger(L, arg + 1); + int nvar = (int)luaL_checkinteger(L, arg + 2); + if (l_unlikely(!lua_getstack(L1, level, &ar))) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + luaL_checkany(L, arg+3); + lua_settop(L, arg+3); + checkstack(L, L1, 1); + lua_xmove(L, L1, 1); + name = lua_setlocal(L1, &ar, nvar); + if (name == NULL) + lua_pop(L1, 1); /* pop value (if not popped by 'lua_setlocal') */ + lua_pushstring(L, name); + return 1; +} + + +/* +** get (if 'get' is true) or set an upvalue from a closure +*/ +static int auxupvalue (lua_State *L, int get) { + const char *name; + int n = (int)luaL_checkinteger(L, 2); /* upvalue index */ + luaL_checktype(L, 1, LUA_TFUNCTION); /* closure */ + name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); + if (name == NULL) return 0; + lua_pushstring(L, name); + lua_insert(L, -(get+1)); /* no-op if get is false */ + return get + 1; +} + + +static int db_getupvalue (lua_State *L) { + return auxupvalue(L, 1); +} + + +static int db_setupvalue (lua_State *L) { + luaL_checkany(L, 3); + return auxupvalue(L, 0); +} + + +/* +** Check whether a given upvalue from a given closure exists and +** returns its index +*/ +static void *checkupval (lua_State *L, int argf, int argnup, int *pnup) { + void *id; + int nup = (int)luaL_checkinteger(L, argnup); /* upvalue index */ + luaL_checktype(L, argf, LUA_TFUNCTION); /* closure */ + id = lua_upvalueid(L, argf, nup); + if (pnup) { + luaL_argcheck(L, id != NULL, argnup, "invalid upvalue index"); + *pnup = nup; + } + return id; +} + + +static int db_upvalueid (lua_State *L) { + void *id = checkupval(L, 1, 2, NULL); + if (id != NULL) + lua_pushlightuserdata(L, id); + else + luaL_pushfail(L); + return 1; +} + + +static int db_upvaluejoin (lua_State *L) { + int n1, n2; + checkupval(L, 1, 2, &n1); + checkupval(L, 3, 4, &n2); + luaL_argcheck(L, !lua_iscfunction(L, 1), 1, "Lua function expected"); + luaL_argcheck(L, !lua_iscfunction(L, 3), 3, "Lua function expected"); + lua_upvaluejoin(L, 1, n1, 3, n2); + return 0; +} + + +/* +** Call hook function registered at hook table for the current +** thread (if there is one) +*/ +static void hookf (lua_State *L, lua_Debug *ar) { + static const char *const hooknames[] = + {"call", "return", "line", "count", "tail call"}; + lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY); + lua_pushthread(L); + if (lua_rawget(L, -2) == LUA_TFUNCTION) { /* is there a hook function? */ + lua_pushstring(L, hooknames[(int)ar->event]); /* push event name */ + if (ar->currentline >= 0) + lua_pushinteger(L, ar->currentline); /* push current line */ + else lua_pushnil(L); + lua_assert(lua_getinfo(L, "lS", ar)); + lua_call(L, 2, 0); /* call hook function */ + } +} + + +/* +** Convert a string mask (for 'sethook') into a bit mask +*/ +static int makemask (const char *smask, int count) { + int mask = 0; + if (strchr(smask, 'c')) mask |= LUA_MASKCALL; + if (strchr(smask, 'r')) mask |= LUA_MASKRET; + if (strchr(smask, 'l')) mask |= LUA_MASKLINE; + if (count > 0) mask |= LUA_MASKCOUNT; + return mask; +} + + +/* +** Convert a bit mask (for 'gethook') into a string mask +*/ +static char *unmakemask (int mask, char *smask) { + int i = 0; + if (mask & LUA_MASKCALL) smask[i++] = 'c'; + if (mask & LUA_MASKRET) smask[i++] = 'r'; + if (mask & LUA_MASKLINE) smask[i++] = 'l'; + smask[i] = '\0'; + return smask; +} + + +static int db_sethook (lua_State *L) { + int arg, mask, count; + lua_Hook func; + lua_State *L1 = getthread(L, &arg); + if (lua_isnoneornil(L, arg+1)) { /* no hook? */ + lua_settop(L, arg+1); + func = NULL; mask = 0; count = 0; /* turn off hooks */ + } + else { + const char *smask = luaL_checkstring(L, arg+2); + luaL_checktype(L, arg+1, LUA_TFUNCTION); + count = (int)luaL_optinteger(L, arg + 3, 0); + func = hookf; mask = makemask(smask, count); + } + if (!luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY)) { + /* table just created; initialize it */ + lua_pushliteral(L, "k"); + lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */ + lua_pushvalue(L, -1); + lua_setmetatable(L, -2); /* metatable(hooktable) = hooktable */ + } + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); /* key (thread) */ + lua_pushvalue(L, arg + 1); /* value (hook function) */ + lua_rawset(L, -3); /* hooktable[L1] = new Lua hook */ + lua_sethook(L1, func, mask, count); + return 0; +} + + +static int db_gethook (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + char buff[5]; + int mask = lua_gethookmask(L1); + lua_Hook hook = lua_gethook(L1); + if (hook == NULL) { /* no hook? */ + luaL_pushfail(L); + return 1; + } + else if (hook != hookf) /* external hook? */ + lua_pushliteral(L, "external hook"); + else { /* hook table must exist */ + lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY); + checkstack(L, L1, 1); + lua_pushthread(L1); lua_xmove(L1, L, 1); + lua_rawget(L, -2); /* 1st result = hooktable[L1] */ + lua_remove(L, -2); /* remove hook table */ + } + lua_pushstring(L, unmakemask(mask, buff)); /* 2nd result = mask */ + lua_pushinteger(L, lua_gethookcount(L1)); /* 3rd result = count */ + return 3; +} + + +static int db_debug (lua_State *L) { + for (;;) { + char buffer[250]; + lua_writestringerror("%s", "lua_debug> "); + if (fgets(buffer, sizeof(buffer), stdin) == NULL || + strcmp(buffer, "cont\n") == 0) + return 0; + if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || + lua_pcall(L, 0, 0, 0)) + lua_writestringerror("%s\n", luaL_tolstring(L, -1, NULL)); + lua_settop(L, 0); /* remove eventual returns */ + } +} + + +static int db_traceback (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + const char *msg = lua_tostring(L, arg + 1); + if (msg == NULL && !lua_isnoneornil(L, arg + 1)) /* non-string 'msg'? */ + lua_pushvalue(L, arg + 1); /* return it untouched */ + else { + int level = (int)luaL_optinteger(L, arg + 2, (L == L1) ? 1 : 0); + luaL_traceback(L, L1, msg, level); + } + return 1; +} + + +static int db_setcstacklimit (lua_State *L) { + int limit = (int)luaL_checkinteger(L, 1); + int res = lua_setcstacklimit(L, limit); + lua_pushinteger(L, res); + return 1; +} + + +static const luaL_Reg dblib[] = { + {"debug", db_debug}, + {"getuservalue", db_getuservalue}, + {"gethook", db_gethook}, + {"getinfo", db_getinfo}, + {"getlocal", db_getlocal}, + {"getregistry", db_getregistry}, + {"getmetatable", db_getmetatable}, + {"getupvalue", db_getupvalue}, + {"upvaluejoin", db_upvaluejoin}, + {"upvalueid", db_upvalueid}, + {"setuservalue", db_setuservalue}, + {"sethook", db_sethook}, + {"setlocal", db_setlocal}, + {"setmetatable", db_setmetatable}, + {"setupvalue", db_setupvalue}, + {"traceback", db_traceback}, + {"setcstacklimit", db_setcstacklimit}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_debug (lua_State *L) { + luaL_newlib(L, dblib); + return 1; +} + diff --git a/arm9/source/lua/ldebug.c b/arm9/source/lua/ldebug.c new file mode 100644 index 000000000..28b1caabf --- /dev/null +++ b/arm9/source/lua/ldebug.c @@ -0,0 +1,924 @@ +/* +** $Id: ldebug.c $ +** Debug Interface +** See Copyright Notice in lua.h +*/ + +#define ldebug_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + + +#define noLuaClosure(f) ((f) == NULL || (f)->c.tt == LUA_VCCL) + + +static const char *funcnamefromcall (lua_State *L, CallInfo *ci, + const char **name); + + +static int currentpc (CallInfo *ci) { + lua_assert(isLua(ci)); + return pcRel(ci->u.l.savedpc, ci_func(ci)->p); +} + + +/* +** Get a "base line" to find the line corresponding to an instruction. +** Base lines are regularly placed at MAXIWTHABS intervals, so usually +** an integer division gets the right place. When the source file has +** large sequences of empty/comment lines, it may need extra entries, +** so the original estimate needs a correction. +** If the original estimate is -1, the initial 'if' ensures that the +** 'while' will run at least once. +** The assertion that the estimate is a lower bound for the correct base +** is valid as long as the debug info has been generated with the same +** value for MAXIWTHABS or smaller. (Previous releases use a little +** smaller value.) +*/ +static int getbaseline (const Proto *f, int pc, int *basepc) { + if (f->sizeabslineinfo == 0 || pc < f->abslineinfo[0].pc) { + *basepc = -1; /* start from the beginning */ + return f->linedefined; + } + else { + int i = cast_uint(pc) / MAXIWTHABS - 1; /* get an estimate */ + /* estimate must be a lower bound of the correct base */ + lua_assert(i < 0 || + (i < f->sizeabslineinfo && f->abslineinfo[i].pc <= pc)); + while (i + 1 < f->sizeabslineinfo && pc >= f->abslineinfo[i + 1].pc) + i++; /* low estimate; adjust it */ + *basepc = f->abslineinfo[i].pc; + return f->abslineinfo[i].line; + } +} + + +/* +** Get the line corresponding to instruction 'pc' in function 'f'; +** first gets a base line and from there does the increments until +** the desired instruction. +*/ +int luaG_getfuncline (const Proto *f, int pc) { + if (f->lineinfo == NULL) /* no debug information? */ + return -1; + else { + int basepc; + int baseline = getbaseline(f, pc, &basepc); + while (basepc++ < pc) { /* walk until given instruction */ + lua_assert(f->lineinfo[basepc] != ABSLINEINFO); + baseline += f->lineinfo[basepc]; /* correct line */ + } + return baseline; + } +} + + +static int getcurrentline (CallInfo *ci) { + return luaG_getfuncline(ci_func(ci)->p, currentpc(ci)); +} + + +/* +** Set 'trap' for all active Lua frames. +** This function can be called during a signal, under "reasonable" +** assumptions. A new 'ci' is completely linked in the list before it +** becomes part of the "active" list, and we assume that pointers are +** atomic; see comment in next function. +** (A compiler doing interprocedural optimizations could, theoretically, +** reorder memory writes in such a way that the list could be +** temporarily broken while inserting a new element. We simply assume it +** has no good reasons to do that.) +*/ +static void settraps (CallInfo *ci) { + for (; ci != NULL; ci = ci->previous) + if (isLua(ci)) + ci->u.l.trap = 1; +} + + +/* +** This function can be called during a signal, under "reasonable" +** assumptions. +** Fields 'basehookcount' and 'hookcount' (set by 'resethookcount') +** are for debug only, and it is no problem if they get arbitrary +** values (causes at most one wrong hook call). 'hookmask' is an atomic +** value. We assume that pointers are atomic too (e.g., gcc ensures that +** for all platforms where it runs). Moreover, 'hook' is always checked +** before being called (see 'luaD_hook'). +*/ +LUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { + if (func == NULL || mask == 0) { /* turn off hooks? */ + mask = 0; + func = NULL; + } + L->hook = func; + L->basehookcount = count; + resethookcount(L); + L->hookmask = cast_byte(mask); + if (mask) + settraps(L->ci); /* to trace inside 'luaV_execute' */ +} + + +LUA_API lua_Hook lua_gethook (lua_State *L) { + return L->hook; +} + + +LUA_API int lua_gethookmask (lua_State *L) { + return L->hookmask; +} + + +LUA_API int lua_gethookcount (lua_State *L) { + return L->basehookcount; +} + + +LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) { + int status; + CallInfo *ci; + if (level < 0) return 0; /* invalid (negative) level */ + lua_lock(L); + for (ci = L->ci; level > 0 && ci != &L->base_ci; ci = ci->previous) + level--; + if (level == 0 && ci != &L->base_ci) { /* level found? */ + status = 1; + ar->i_ci = ci; + } + else status = 0; /* no such level */ + lua_unlock(L); + return status; +} + + +static const char *upvalname (const Proto *p, int uv) { + TString *s = check_exp(uv < p->sizeupvalues, p->upvalues[uv].name); + if (s == NULL) return "?"; + else return getstr(s); +} + + +static const char *findvararg (CallInfo *ci, int n, StkId *pos) { + if (clLvalue(s2v(ci->func.p))->p->is_vararg) { + int nextra = ci->u.l.nextraargs; + if (n >= -nextra) { /* 'n' is negative */ + *pos = ci->func.p - nextra - (n + 1); + return "(vararg)"; /* generic name for any vararg */ + } + } + return NULL; /* no such vararg */ +} + + +const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, StkId *pos) { + StkId base = ci->func.p + 1; + const char *name = NULL; + if (isLua(ci)) { + if (n < 0) /* access to vararg values? */ + return findvararg(ci, n, pos); + else + name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci)); + } + if (name == NULL) { /* no 'standard' name? */ + StkId limit = (ci == L->ci) ? L->top.p : ci->next->func.p; + if (limit - base >= n && n > 0) { /* is 'n' inside 'ci' stack? */ + /* generic name for any valid slot */ + name = isLua(ci) ? "(temporary)" : "(C temporary)"; + } + else + return NULL; /* no name */ + } + if (pos) + *pos = base + (n - 1); + return name; +} + + +LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { + const char *name; + lua_lock(L); + if (ar == NULL) { /* information about non-active function? */ + if (!isLfunction(s2v(L->top.p - 1))) /* not a Lua function? */ + name = NULL; + else /* consider live variables at function start (parameters) */ + name = luaF_getlocalname(clLvalue(s2v(L->top.p - 1))->p, n, 0); + } + else { /* active function; get information through 'ar' */ + StkId pos = NULL; /* to avoid warnings */ + name = luaG_findlocal(L, ar->i_ci, n, &pos); + if (name) { + setobjs2s(L, L->top.p, pos); + api_incr_top(L); + } + } + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { + StkId pos = NULL; /* to avoid warnings */ + const char *name; + lua_lock(L); + name = luaG_findlocal(L, ar->i_ci, n, &pos); + if (name) { + setobjs2s(L, pos, L->top.p - 1); + L->top.p--; /* pop value */ + } + lua_unlock(L); + return name; +} + + +static void funcinfo (lua_Debug *ar, Closure *cl) { + if (noLuaClosure(cl)) { + ar->source = "=[C]"; + ar->srclen = LL("=[C]"); + ar->linedefined = -1; + ar->lastlinedefined = -1; + ar->what = "C"; + } + else { + const Proto *p = cl->l.p; + if (p->source) { + ar->source = getstr(p->source); + ar->srclen = tsslen(p->source); + } + else { + ar->source = "=?"; + ar->srclen = LL("=?"); + } + ar->linedefined = p->linedefined; + ar->lastlinedefined = p->lastlinedefined; + ar->what = (ar->linedefined == 0) ? "main" : "Lua"; + } + luaO_chunkid(ar->short_src, ar->source, ar->srclen); +} + + +static int nextline (const Proto *p, int currentline, int pc) { + if (p->lineinfo[pc] != ABSLINEINFO) + return currentline + p->lineinfo[pc]; + else + return luaG_getfuncline(p, pc); +} + + +static void collectvalidlines (lua_State *L, Closure *f) { + if (noLuaClosure(f)) { + setnilvalue(s2v(L->top.p)); + api_incr_top(L); + } + else { + int i; + TValue v; + const Proto *p = f->l.p; + int currentline = p->linedefined; + Table *t = luaH_new(L); /* new table to store active lines */ + sethvalue2s(L, L->top.p, t); /* push it on stack */ + api_incr_top(L); + setbtvalue(&v); /* boolean 'true' to be the value of all indices */ + if (!p->is_vararg) /* regular function? */ + i = 0; /* consider all instructions */ + else { /* vararg function */ + lua_assert(GET_OPCODE(p->code[0]) == OP_VARARGPREP); + currentline = nextline(p, currentline, 0); + i = 1; /* skip first instruction (OP_VARARGPREP) */ + } + for (; i < p->sizelineinfo; i++) { /* for each instruction */ + currentline = nextline(p, currentline, i); /* get its line */ + luaH_setint(L, t, currentline, &v); /* table[line] = true */ + } + } +} + + +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { + /* calling function is a known function? */ + if (ci != NULL && !(ci->callstatus & CIST_TAIL)) + return funcnamefromcall(L, ci->previous, name); + else return NULL; /* no way to find a name */ +} + + +static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, + Closure *f, CallInfo *ci) { + int status = 1; + for (; *what; what++) { + switch (*what) { + case 'S': { + funcinfo(ar, f); + break; + } + case 'l': { + ar->currentline = (ci && isLua(ci)) ? getcurrentline(ci) : -1; + break; + } + case 'u': { + ar->nups = (f == NULL) ? 0 : f->c.nupvalues; + if (noLuaClosure(f)) { + ar->isvararg = 1; + ar->nparams = 0; + } + else { + ar->isvararg = f->l.p->is_vararg; + ar->nparams = f->l.p->numparams; + } + break; + } + case 't': { + ar->istailcall = (ci) ? ci->callstatus & CIST_TAIL : 0; + break; + } + case 'n': { + ar->namewhat = getfuncname(L, ci, &ar->name); + if (ar->namewhat == NULL) { + ar->namewhat = ""; /* not found */ + ar->name = NULL; + } + break; + } + case 'r': { + if (ci == NULL || !(ci->callstatus & CIST_TRAN)) + ar->ftransfer = ar->ntransfer = 0; + else { + ar->ftransfer = ci->u2.transferinfo.ftransfer; + ar->ntransfer = ci->u2.transferinfo.ntransfer; + } + break; + } + case 'L': + case 'f': /* handled by lua_getinfo */ + break; + default: status = 0; /* invalid option */ + } + } + return status; +} + + +LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { + int status; + Closure *cl; + CallInfo *ci; + TValue *func; + lua_lock(L); + if (*what == '>') { + ci = NULL; + func = s2v(L->top.p - 1); + api_check(L, ttisfunction(func), "function expected"); + what++; /* skip the '>' */ + L->top.p--; /* pop function */ + } + else { + ci = ar->i_ci; + func = s2v(ci->func.p); + lua_assert(ttisfunction(func)); + } + cl = ttisclosure(func) ? clvalue(func) : NULL; + status = auxgetinfo(L, what, ar, cl, ci); + if (strchr(what, 'f')) { + setobj2s(L, L->top.p, func); + api_incr_top(L); + } + if (strchr(what, 'L')) + collectvalidlines(L, cl); + lua_unlock(L); + return status; +} + + +/* +** {====================================================== +** Symbolic Execution +** ======================================================= +*/ + +static const char *getobjname (const Proto *p, int lastpc, int reg, + const char **name); + + +/* +** Find a "name" for the constant 'c'. +*/ +static void kname (const Proto *p, int c, const char **name) { + TValue *kvalue = &p->k[c]; + *name = (ttisstring(kvalue)) ? svalue(kvalue) : "?"; +} + + +/* +** Find a "name" for the register 'c'. +*/ +static void rname (const Proto *p, int pc, int c, const char **name) { + const char *what = getobjname(p, pc, c, name); /* search for 'c' */ + if (!(what && *what == 'c')) /* did not find a constant name? */ + *name = "?"; +} + + +/* +** Find a "name" for a 'C' value in an RK instruction. +*/ +static void rkname (const Proto *p, int pc, Instruction i, const char **name) { + int c = GETARG_C(i); /* key index */ + if (GETARG_k(i)) /* is 'c' a constant? */ + kname(p, c, name); + else /* 'c' is a register */ + rname(p, pc, c, name); +} + + +static int filterpc (int pc, int jmptarget) { + if (pc < jmptarget) /* is code conditional (inside a jump)? */ + return -1; /* cannot know who sets that register */ + else return pc; /* current position sets that register */ +} + + +/* +** Try to find last instruction before 'lastpc' that modified register 'reg'. +*/ +static int findsetreg (const Proto *p, int lastpc, int reg) { + int pc; + int setreg = -1; /* keep last instruction that changed 'reg' */ + int jmptarget = 0; /* any code before this address is conditional */ + if (testMMMode(GET_OPCODE(p->code[lastpc]))) + lastpc--; /* previous instruction was not actually executed */ + for (pc = 0; pc < lastpc; pc++) { + Instruction i = p->code[pc]; + OpCode op = GET_OPCODE(i); + int a = GETARG_A(i); + int change; /* true if current instruction changed 'reg' */ + switch (op) { + case OP_LOADNIL: { /* set registers from 'a' to 'a+b' */ + int b = GETARG_B(i); + change = (a <= reg && reg <= a + b); + break; + } + case OP_TFORCALL: { /* affect all regs above its base */ + change = (reg >= a + 2); + break; + } + case OP_CALL: + case OP_TAILCALL: { /* affect all registers above base */ + change = (reg >= a); + break; + } + case OP_JMP: { /* doesn't change registers, but changes 'jmptarget' */ + int b = GETARG_sJ(i); + int dest = pc + 1 + b; + /* jump does not skip 'lastpc' and is larger than current one? */ + if (dest <= lastpc && dest > jmptarget) + jmptarget = dest; /* update 'jmptarget' */ + change = 0; + break; + } + default: /* any instruction that sets A */ + change = (testAMode(op) && reg == a); + break; + } + if (change) + setreg = filterpc(pc, jmptarget); + } + return setreg; +} + + +/* +** Check whether table being indexed by instruction 'i' is the +** environment '_ENV' +*/ +static const char *gxf (const Proto *p, int pc, Instruction i, int isup) { + int t = GETARG_B(i); /* table index */ + const char *name; /* name of indexed variable */ + if (isup) /* is an upvalue? */ + name = upvalname(p, t); + else + getobjname(p, pc, t, &name); + return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field"; +} + + +static const char *getobjname (const Proto *p, int lastpc, int reg, + const char **name) { + int pc; + *name = luaF_getlocalname(p, reg + 1, lastpc); + if (*name) /* is a local? */ + return "local"; + /* else try symbolic execution */ + pc = findsetreg(p, lastpc, reg); + if (pc != -1) { /* could find instruction? */ + Instruction i = p->code[pc]; + OpCode op = GET_OPCODE(i); + switch (op) { + case OP_MOVE: { + int b = GETARG_B(i); /* move from 'b' to 'a' */ + if (b < GETARG_A(i)) + return getobjname(p, pc, b, name); /* get name for 'b' */ + break; + } + case OP_GETTABUP: { + int k = GETARG_C(i); /* key index */ + kname(p, k, name); + return gxf(p, pc, i, 1); + } + case OP_GETTABLE: { + int k = GETARG_C(i); /* key index */ + rname(p, pc, k, name); + return gxf(p, pc, i, 0); + } + case OP_GETI: { + *name = "integer index"; + return "field"; + } + case OP_GETFIELD: { + int k = GETARG_C(i); /* key index */ + kname(p, k, name); + return gxf(p, pc, i, 0); + } + case OP_GETUPVAL: { + *name = upvalname(p, GETARG_B(i)); + return "upvalue"; + } + case OP_LOADK: + case OP_LOADKX: { + int b = (op == OP_LOADK) ? GETARG_Bx(i) + : GETARG_Ax(p->code[pc + 1]); + if (ttisstring(&p->k[b])) { + *name = svalue(&p->k[b]); + return "constant"; + } + break; + } + case OP_SELF: { + rkname(p, pc, i, name); + return "method"; + } + default: break; /* go through to return NULL */ + } + } + return NULL; /* could not find reasonable name */ +} + + +/* +** Try to find a name for a function based on the code that called it. +** (Only works when function was called by a Lua function.) +** Returns what the name is (e.g., "for iterator", "method", +** "metamethod") and sets '*name' to point to the name. +*/ +static const char *funcnamefromcode (lua_State *L, const Proto *p, + int pc, const char **name) { + TMS tm = (TMS)0; /* (initial value avoids warnings) */ + Instruction i = p->code[pc]; /* calling instruction */ + switch (GET_OPCODE(i)) { + case OP_CALL: + case OP_TAILCALL: + return getobjname(p, pc, GETARG_A(i), name); /* get function name */ + case OP_TFORCALL: { /* for iterator */ + *name = "for iterator"; + return "for iterator"; + } + /* other instructions can do calls through metamethods */ + case OP_SELF: case OP_GETTABUP: case OP_GETTABLE: + case OP_GETI: case OP_GETFIELD: + tm = TM_INDEX; + break; + case OP_SETTABUP: case OP_SETTABLE: case OP_SETI: case OP_SETFIELD: + tm = TM_NEWINDEX; + break; + case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: { + tm = cast(TMS, GETARG_C(i)); + break; + } + case OP_UNM: tm = TM_UNM; break; + case OP_BNOT: tm = TM_BNOT; break; + case OP_LEN: tm = TM_LEN; break; + case OP_CONCAT: tm = TM_CONCAT; break; + case OP_EQ: tm = TM_EQ; break; + /* no cases for OP_EQI and OP_EQK, as they don't call metamethods */ + case OP_LT: case OP_LTI: case OP_GTI: tm = TM_LT; break; + case OP_LE: case OP_LEI: case OP_GEI: tm = TM_LE; break; + case OP_CLOSE: case OP_RETURN: tm = TM_CLOSE; break; + default: + return NULL; /* cannot find a reasonable name */ + } + *name = getstr(G(L)->tmname[tm]) + 2; + return "metamethod"; +} + + +/* +** Try to find a name for a function based on how it was called. +*/ +static const char *funcnamefromcall (lua_State *L, CallInfo *ci, + const char **name) { + if (ci->callstatus & CIST_HOOKED) { /* was it called inside a hook? */ + *name = "?"; + return "hook"; + } + else if (ci->callstatus & CIST_FIN) { /* was it called as a finalizer? */ + *name = "__gc"; + return "metamethod"; /* report it as such */ + } + else if (isLua(ci)) + return funcnamefromcode(L, ci_func(ci)->p, currentpc(ci), name); + else + return NULL; +} + +/* }====================================================== */ + + + +/* +** Check whether pointer 'o' points to some value in the stack frame of +** the current function and, if so, returns its index. Because 'o' may +** not point to a value in this stack, we cannot compare it with the +** region boundaries (undefined behavior in ISO C). +*/ +static int instack (CallInfo *ci, const TValue *o) { + int pos; + StkId base = ci->func.p + 1; + for (pos = 0; base + pos < ci->top.p; pos++) { + if (o == s2v(base + pos)) + return pos; + } + return -1; /* not found */ +} + + +/* +** Checks whether value 'o' came from an upvalue. (That can only happen +** with instructions OP_GETTABUP/OP_SETTABUP, which operate directly on +** upvalues.) +*/ +static const char *getupvalname (CallInfo *ci, const TValue *o, + const char **name) { + LClosure *c = ci_func(ci); + int i; + for (i = 0; i < c->nupvalues; i++) { + if (c->upvals[i]->v.p == o) { + *name = upvalname(c->p, i); + return "upvalue"; + } + } + return NULL; +} + + +static const char *formatvarinfo (lua_State *L, const char *kind, + const char *name) { + if (kind == NULL) + return ""; /* no information */ + else + return luaO_pushfstring(L, " (%s '%s')", kind, name); +} + +/* +** Build a string with a "description" for the value 'o', such as +** "variable 'x'" or "upvalue 'y'". +*/ +static const char *varinfo (lua_State *L, const TValue *o) { + CallInfo *ci = L->ci; + const char *name = NULL; /* to avoid warnings */ + const char *kind = NULL; + if (isLua(ci)) { + kind = getupvalname(ci, o, &name); /* check whether 'o' is an upvalue */ + if (!kind) { /* not an upvalue? */ + int reg = instack(ci, o); /* try a register */ + if (reg >= 0) /* is 'o' a register? */ + kind = getobjname(ci_func(ci)->p, currentpc(ci), reg, &name); + } + } + return formatvarinfo(L, kind, name); +} + + +/* +** Raise a type error +*/ +static l_noret typeerror (lua_State *L, const TValue *o, const char *op, + const char *extra) { + const char *t = luaT_objtypename(L, o); + luaG_runerror(L, "attempt to %s a %s value%s", op, t, extra); +} + + +/* +** Raise a type error with "standard" information about the faulty +** object 'o' (using 'varinfo'). +*/ +l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { + typeerror(L, o, op, varinfo(L, o)); +} + + +/* +** Raise an error for calling a non-callable object. Try to find a name +** for the object based on how it was called ('funcnamefromcall'); if it +** cannot get a name there, try 'varinfo'. +*/ +l_noret luaG_callerror (lua_State *L, const TValue *o) { + CallInfo *ci = L->ci; + const char *name = NULL; /* to avoid warnings */ + const char *kind = funcnamefromcall(L, ci, &name); + const char *extra = kind ? formatvarinfo(L, kind, name) : varinfo(L, o); + typeerror(L, o, "call", extra); +} + + +l_noret luaG_forerror (lua_State *L, const TValue *o, const char *what) { + luaG_runerror(L, "bad 'for' %s (number expected, got %s)", + what, luaT_objtypename(L, o)); +} + + +l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) { + if (ttisstring(p1) || cvt2str(p1)) p1 = p2; + luaG_typeerror(L, p1, "concatenate"); +} + + +l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, const char *msg) { + if (!ttisnumber(p1)) /* first operand is wrong? */ + p2 = p1; /* now second is wrong */ + luaG_typeerror(L, p2, msg); +} + + +/* +** Error when both values are convertible to numbers, but not to integers +*/ +l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) { + lua_Integer temp; + if (!luaV_tointegerns(p1, &temp, LUA_FLOORN2I)) + p2 = p1; + luaG_runerror(L, "number%s has no integer representation", varinfo(L, p2)); +} + + +l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { + const char *t1 = luaT_objtypename(L, p1); + const char *t2 = luaT_objtypename(L, p2); + if (strcmp(t1, t2) == 0) + luaG_runerror(L, "attempt to compare two %s values", t1); + else + luaG_runerror(L, "attempt to compare %s with %s", t1, t2); +} + + +/* add src:line information to 'msg' */ +const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, + int line) { + char buff[LUA_IDSIZE]; + if (src) + luaO_chunkid(buff, getstr(src), tsslen(src)); + else { /* no source available; use "?" instead */ + buff[0] = '?'; buff[1] = '\0'; + } + return luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); +} + + +l_noret luaG_errormsg (lua_State *L) { + if (L->errfunc != 0) { /* is there an error handling function? */ + StkId errfunc = restorestack(L, L->errfunc); + lua_assert(ttisfunction(s2v(errfunc))); + setobjs2s(L, L->top.p, L->top.p - 1); /* move argument */ + setobjs2s(L, L->top.p - 1, errfunc); /* push function */ + L->top.p++; /* assume EXTRA_STACK */ + luaD_callnoyield(L, L->top.p - 2, 1); /* call it */ + } + luaD_throw(L, LUA_ERRRUN); +} + + +l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { + CallInfo *ci = L->ci; + const char *msg; + va_list argp; + luaC_checkGC(L); /* error message uses memory */ + va_start(argp, fmt); + msg = luaO_pushvfstring(L, fmt, argp); /* format message */ + va_end(argp); + if (isLua(ci)) { /* if Lua function, add source:line information */ + luaG_addinfo(L, msg, ci_func(ci)->p->source, getcurrentline(ci)); + setobjs2s(L, L->top.p - 2, L->top.p - 1); /* remove 'msg' */ + L->top.p--; + } + luaG_errormsg(L); +} + + +/* +** Check whether new instruction 'newpc' is in a different line from +** previous instruction 'oldpc'. More often than not, 'newpc' is only +** one or a few instructions after 'oldpc' (it must be after, see +** caller), so try to avoid calling 'luaG_getfuncline'. If they are +** too far apart, there is a good chance of a ABSLINEINFO in the way, +** so it goes directly to 'luaG_getfuncline'. +*/ +static int changedline (const Proto *p, int oldpc, int newpc) { + if (p->lineinfo == NULL) /* no debug information? */ + return 0; + if (newpc - oldpc < MAXIWTHABS / 2) { /* not too far apart? */ + int delta = 0; /* line difference */ + int pc = oldpc; + for (;;) { + int lineinfo = p->lineinfo[++pc]; + if (lineinfo == ABSLINEINFO) + break; /* cannot compute delta; fall through */ + delta += lineinfo; + if (pc == newpc) + return (delta != 0); /* delta computed successfully */ + } + } + /* either instructions are too far apart or there is an absolute line + info in the way; compute line difference explicitly */ + return (luaG_getfuncline(p, oldpc) != luaG_getfuncline(p, newpc)); +} + + +/* +** Traces the execution of a Lua function. Called before the execution +** of each opcode, when debug is on. 'L->oldpc' stores the last +** instruction traced, to detect line changes. When entering a new +** function, 'npci' will be zero and will test as a new line whatever +** the value of 'oldpc'. Some exceptional conditions may return to +** a function without setting 'oldpc'. In that case, 'oldpc' may be +** invalid; if so, use zero as a valid value. (A wrong but valid 'oldpc' +** at most causes an extra call to a line hook.) +** This function is not "Protected" when called, so it should correct +** 'L->top.p' before calling anything that can run the GC. +*/ +int luaG_traceexec (lua_State *L, const Instruction *pc) { + CallInfo *ci = L->ci; + lu_byte mask = L->hookmask; + const Proto *p = ci_func(ci)->p; + int counthook; + if (!(mask & (LUA_MASKLINE | LUA_MASKCOUNT))) { /* no hooks? */ + ci->u.l.trap = 0; /* don't need to stop again */ + return 0; /* turn off 'trap' */ + } + pc++; /* reference is always next instruction */ + ci->u.l.savedpc = pc; /* save 'pc' */ + counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT)); + if (counthook) + resethookcount(L); /* reset count */ + else if (!(mask & LUA_MASKLINE)) + return 1; /* no line hook and count != 0; nothing to be done now */ + if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ + ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ + return 1; /* do not call hook again (VM yielded, so it did not move) */ + } + if (!isIT(*(ci->u.l.savedpc - 1))) /* top not being used? */ + L->top.p = ci->top.p; /* correct top */ + if (counthook) + luaD_hook(L, LUA_HOOKCOUNT, -1, 0, 0); /* call count hook */ + if (mask & LUA_MASKLINE) { + /* 'L->oldpc' may be invalid; use zero in this case */ + int oldpc = (L->oldpc < p->sizecode) ? L->oldpc : 0; + int npci = pcRel(pc, p); + if (npci <= oldpc || /* call hook when jump back (loop), */ + changedline(p, oldpc, npci)) { /* or when enter new line */ + int newline = luaG_getfuncline(p, npci); + luaD_hook(L, LUA_HOOKLINE, newline, 0, 0); /* call line hook */ + } + L->oldpc = npci; /* 'pc' of last call to line hook */ + } + if (L->status == LUA_YIELD) { /* did hook yield? */ + if (counthook) + L->hookcount = 1; /* undo decrement to zero */ + ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ + ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ + luaD_throw(L, LUA_YIELD); + } + return 1; /* keep 'trap' on */ +} + diff --git a/arm9/source/lua/ldebug.h b/arm9/source/lua/ldebug.h new file mode 100644 index 000000000..2c3074c61 --- /dev/null +++ b/arm9/source/lua/ldebug.h @@ -0,0 +1,63 @@ +/* +** $Id: ldebug.h $ +** Auxiliary functions from Debug Interface module +** See Copyright Notice in lua.h +*/ + +#ifndef ldebug_h +#define ldebug_h + + +#include "lstate.h" + + +#define pcRel(pc, p) (cast_int((pc) - (p)->code) - 1) + + +/* Active Lua function (given call info) */ +#define ci_func(ci) (clLvalue(s2v((ci)->func.p))) + + +#define resethookcount(L) (L->hookcount = L->basehookcount) + +/* +** mark for entries in 'lineinfo' array that has absolute information in +** 'abslineinfo' array +*/ +#define ABSLINEINFO (-0x80) + + +/* +** MAXimum number of successive Instructions WiTHout ABSolute line +** information. (A power of two allows fast divisions.) +*/ +#if !defined(MAXIWTHABS) +#define MAXIWTHABS 128 +#endif + + +LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc); +LUAI_FUNC const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, + StkId *pos); +LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o, + const char *opname); +LUAI_FUNC l_noret luaG_callerror (lua_State *L, const TValue *o); +LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o, + const char *what); +LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1, + const TValue *p2, + const char *msg); +LUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...); +LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, + TString *src, int line); +LUAI_FUNC l_noret luaG_errormsg (lua_State *L); +LUAI_FUNC int luaG_traceexec (lua_State *L, const Instruction *pc); + + +#endif diff --git a/arm9/source/lua/ldo.c b/arm9/source/lua/ldo.c new file mode 100644 index 000000000..2a0017ca6 --- /dev/null +++ b/arm9/source/lua/ldo.c @@ -0,0 +1,1024 @@ +/* +** $Id: ldo.c $ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + +#define ldo_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" +#include "lzio.h" + + + +#define errorstatus(s) ((s) > LUA_YIELD) + + +/* +** {====================================================== +** Error-recovery functions +** ======================================================= +*/ + +/* +** LUAI_THROW/LUAI_TRY define how Lua does exception handling. By +** default, Lua handles errors with exceptions when compiling as +** C++ code, with _longjmp/_setjmp when asked to use them, and with +** longjmp/setjmp otherwise. +*/ +#if !defined(LUAI_THROW) /* { */ + +#if defined(__cplusplus) && !defined(LUA_USE_LONGJMP) /* { */ + +/* C++ exceptions */ +#define LUAI_THROW(L,c) throw(c) +#define LUAI_TRY(L,c,a) \ + try { a } catch(...) { if ((c)->status == 0) (c)->status = -1; } +#define luai_jmpbuf int /* dummy variable */ + +#elif defined(LUA_USE_POSIX) /* }{ */ + +/* in POSIX, try _longjmp/_setjmp (more efficient) */ +#define LUAI_THROW(L,c) _longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#else /* }{ */ + +/* ISO C handling with long jumps */ +#define LUAI_THROW(L,c) longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#endif /* } */ + +#endif /* } */ + + + +/* chain list of long jump buffers */ +struct lua_longjmp { + struct lua_longjmp *previous; + luai_jmpbuf b; + volatile int status; /* error code */ +}; + + +void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { + switch (errcode) { + case LUA_ERRMEM: { /* memory error? */ + setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ + break; + } + case LUA_ERRERR: { + setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); + break; + } + case LUA_OK: { /* special case only for closing upvalues */ + setnilvalue(s2v(oldtop)); /* no error message */ + break; + } + default: { + lua_assert(errorstatus(errcode)); /* real error */ + setobjs2s(L, oldtop, L->top.p - 1); /* error message on current top */ + break; + } + } + L->top.p = oldtop + 1; +} + + +l_noret luaD_throw (lua_State *L, int errcode) { + if (L->errorJmp) { /* thread has an error handler? */ + L->errorJmp->status = errcode; /* set status */ + LUAI_THROW(L, L->errorJmp); /* jump to it */ + } + else { /* thread has no error handler */ + global_State *g = G(L); + errcode = luaE_resetthread(L, errcode); /* close all upvalues */ + if (g->mainthread->errorJmp) { /* main thread has a handler? */ + setobjs2s(L, g->mainthread->top.p++, L->top.p - 1); /* copy error obj. */ + luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ + } + else { /* no handler at all; abort */ + if (g->panic) { /* panic function? */ + lua_unlock(L); + g->panic(L); /* call panic function (last chance to jump out) */ + } + abort(); + } + } +} + + +int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { + l_uint32 oldnCcalls = L->nCcalls; + struct lua_longjmp lj; + lj.status = LUA_OK; + lj.previous = L->errorJmp; /* chain new error handler */ + L->errorJmp = &lj; + LUAI_TRY(L, &lj, + (*f)(L, ud); + ); + L->errorJmp = lj.previous; /* restore old error handler */ + L->nCcalls = oldnCcalls; + return lj.status; +} + +/* }====================================================== */ + + +/* +** {================================================================== +** Stack reallocation +** =================================================================== +*/ + + +/* +** Change all pointers to the stack into offsets. +*/ +static void relstack (lua_State *L) { + CallInfo *ci; + UpVal *up; + L->top.offset = savestack(L, L->top.p); + L->tbclist.offset = savestack(L, L->tbclist.p); + for (up = L->openupval; up != NULL; up = up->u.open.next) + up->v.offset = savestack(L, uplevel(up)); + for (ci = L->ci; ci != NULL; ci = ci->previous) { + ci->top.offset = savestack(L, ci->top.p); + ci->func.offset = savestack(L, ci->func.p); + } +} + + +/* +** Change back all offsets into pointers. +*/ +static void correctstack (lua_State *L) { + CallInfo *ci; + UpVal *up; + L->top.p = restorestack(L, L->top.offset); + L->tbclist.p = restorestack(L, L->tbclist.offset); + for (up = L->openupval; up != NULL; up = up->u.open.next) + up->v.p = s2v(restorestack(L, up->v.offset)); + for (ci = L->ci; ci != NULL; ci = ci->previous) { + ci->top.p = restorestack(L, ci->top.offset); + ci->func.p = restorestack(L, ci->func.offset); + if (isLua(ci)) + ci->u.l.trap = 1; /* signal to update 'trap' in 'luaV_execute' */ + } +} + + +/* some space for error handling */ +#define ERRORSTACKSIZE (LUAI_MAXSTACK + 200) + +/* +** Reallocate the stack to a new size, correcting all pointers into it. +** In ISO C, any pointer use after the pointer has been deallocated is +** undefined behavior. So, before the reallocation, all pointers are +** changed to offsets, and after the reallocation they are changed back +** to pointers. As during the reallocation the pointers are invalid, the +** reallocation cannot run emergency collections. +** +** In case of allocation error, raise an error or return false according +** to 'raiseerror'. +*/ +int luaD_reallocstack (lua_State *L, int newsize, int raiseerror) { + int oldsize = stacksize(L); + int i; + StkId newstack; + int oldgcstop = G(L)->gcstopem; + lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE); + relstack(L); /* change pointers to offsets */ + G(L)->gcstopem = 1; /* stop emergency collection */ + newstack = luaM_reallocvector(L, L->stack.p, oldsize + EXTRA_STACK, + newsize + EXTRA_STACK, StackValue); + G(L)->gcstopem = oldgcstop; /* restore emergency collection */ + if (l_unlikely(newstack == NULL)) { /* reallocation failed? */ + correctstack(L); /* change offsets back to pointers */ + if (raiseerror) + luaM_error(L); + else return 0; /* do not raise an error */ + } + L->stack.p = newstack; + correctstack(L); /* change offsets back to pointers */ + L->stack_last.p = L->stack.p + newsize; + for (i = oldsize + EXTRA_STACK; i < newsize + EXTRA_STACK; i++) + setnilvalue(s2v(newstack + i)); /* erase new segment */ + return 1; +} + + +/* +** Try to grow the stack by at least 'n' elements. When 'raiseerror' +** is true, raises any error; otherwise, return 0 in case of errors. +*/ +int luaD_growstack (lua_State *L, int n, int raiseerror) { + int size = stacksize(L); + if (l_unlikely(size > LUAI_MAXSTACK)) { + /* if stack is larger than maximum, thread is already using the + extra space reserved for errors, that is, thread is handling + a stack error; cannot grow further than that. */ + lua_assert(stacksize(L) == ERRORSTACKSIZE); + if (raiseerror) + luaD_throw(L, LUA_ERRERR); /* error inside message handler */ + return 0; /* if not 'raiseerror', just signal it */ + } + else if (n < LUAI_MAXSTACK) { /* avoids arithmetic overflows */ + int newsize = 2 * size; /* tentative new size */ + int needed = cast_int(L->top.p - L->stack.p) + n; + if (newsize > LUAI_MAXSTACK) /* cannot cross the limit */ + newsize = LUAI_MAXSTACK; + if (newsize < needed) /* but must respect what was asked for */ + newsize = needed; + if (l_likely(newsize <= LUAI_MAXSTACK)) + return luaD_reallocstack(L, newsize, raiseerror); + } + /* else stack overflow */ + /* add extra size to be able to handle the error message */ + luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror); + if (raiseerror) + luaG_runerror(L, "stack overflow"); + return 0; +} + + +/* +** Compute how much of the stack is being used, by computing the +** maximum top of all call frames in the stack and the current top. +*/ +static int stackinuse (lua_State *L) { + CallInfo *ci; + int res; + StkId lim = L->top.p; + for (ci = L->ci; ci != NULL; ci = ci->previous) { + if (lim < ci->top.p) lim = ci->top.p; + } + lua_assert(lim <= L->stack_last.p + EXTRA_STACK); + res = cast_int(lim - L->stack.p) + 1; /* part of stack in use */ + if (res < LUA_MINSTACK) + res = LUA_MINSTACK; /* ensure a minimum size */ + return res; +} + + +/* +** If stack size is more than 3 times the current use, reduce that size +** to twice the current use. (So, the final stack size is at most 2/3 the +** previous size, and half of its entries are empty.) +** As a particular case, if stack was handling a stack overflow and now +** it is not, 'max' (limited by LUAI_MAXSTACK) will be smaller than +** stacksize (equal to ERRORSTACKSIZE in this case), and so the stack +** will be reduced to a "regular" size. +*/ +void luaD_shrinkstack (lua_State *L) { + int inuse = stackinuse(L); + int max = (inuse > LUAI_MAXSTACK / 3) ? LUAI_MAXSTACK : inuse * 3; + /* if thread is currently not handling a stack overflow and its + size is larger than maximum "reasonable" size, shrink it */ + if (inuse <= LUAI_MAXSTACK && stacksize(L) > max) { + int nsize = (inuse > LUAI_MAXSTACK / 2) ? LUAI_MAXSTACK : inuse * 2; + luaD_reallocstack(L, nsize, 0); /* ok if that fails */ + } + else /* don't change stack */ + condmovestack(L,{},{}); /* (change only for debugging) */ + luaE_shrinkCI(L); /* shrink CI list */ +} + + +void luaD_inctop (lua_State *L) { + luaD_checkstack(L, 1); + L->top.p++; +} + +/* }================================================================== */ + + +/* +** Call a hook for the given event. Make sure there is a hook to be +** called. (Both 'L->hook' and 'L->hookmask', which trigger this +** function, can be changed asynchronously by signals.) +*/ +void luaD_hook (lua_State *L, int event, int line, + int ftransfer, int ntransfer) { + lua_Hook hook = L->hook; + if (hook && L->allowhook) { /* make sure there is a hook */ + int mask = CIST_HOOKED; + CallInfo *ci = L->ci; + ptrdiff_t top = savestack(L, L->top.p); /* preserve original 'top' */ + ptrdiff_t ci_top = savestack(L, ci->top.p); /* idem for 'ci->top' */ + lua_Debug ar; + ar.event = event; + ar.currentline = line; + ar.i_ci = ci; + if (ntransfer != 0) { + mask |= CIST_TRAN; /* 'ci' has transfer information */ + ci->u2.transferinfo.ftransfer = ftransfer; + ci->u2.transferinfo.ntransfer = ntransfer; + } + if (isLua(ci) && L->top.p < ci->top.p) + L->top.p = ci->top.p; /* protect entire activation register */ + luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + if (ci->top.p < L->top.p + LUA_MINSTACK) + ci->top.p = L->top.p + LUA_MINSTACK; + L->allowhook = 0; /* cannot call hooks inside a hook */ + ci->callstatus |= mask; + lua_unlock(L); + (*hook)(L, &ar); + lua_lock(L); + lua_assert(!L->allowhook); + L->allowhook = 1; + ci->top.p = restorestack(L, ci_top); + L->top.p = restorestack(L, top); + ci->callstatus &= ~mask; + } +} + + +/* +** Executes a call hook for Lua functions. This function is called +** whenever 'hookmask' is not zero, so it checks whether call hooks are +** active. +*/ +void luaD_hookcall (lua_State *L, CallInfo *ci) { + L->oldpc = 0; /* set 'oldpc' for new function */ + if (L->hookmask & LUA_MASKCALL) { /* is call hook on? */ + int event = (ci->callstatus & CIST_TAIL) ? LUA_HOOKTAILCALL + : LUA_HOOKCALL; + Proto *p = ci_func(ci)->p; + ci->u.l.savedpc++; /* hooks assume 'pc' is already incremented */ + luaD_hook(L, event, -1, 1, p->numparams); + ci->u.l.savedpc--; /* correct 'pc' */ + } +} + + +/* +** Executes a return hook for Lua and C functions and sets/corrects +** 'oldpc'. (Note that this correction is needed by the line hook, so it +** is done even when return hooks are off.) +*/ +static void rethook (lua_State *L, CallInfo *ci, int nres) { + if (L->hookmask & LUA_MASKRET) { /* is return hook on? */ + StkId firstres = L->top.p - nres; /* index of first result */ + int delta = 0; /* correction for vararg functions */ + int ftransfer; + if (isLua(ci)) { + Proto *p = ci_func(ci)->p; + if (p->is_vararg) + delta = ci->u.l.nextraargs + p->numparams + 1; + } + ci->func.p += delta; /* if vararg, back to virtual 'func' */ + ftransfer = cast(unsigned short, firstres - ci->func.p); + luaD_hook(L, LUA_HOOKRET, -1, ftransfer, nres); /* call it */ + ci->func.p -= delta; + } + if (isLua(ci = ci->previous)) + L->oldpc = pcRel(ci->u.l.savedpc, ci_func(ci)->p); /* set 'oldpc' */ +} + + +/* +** Check whether 'func' has a '__call' metafield. If so, put it in the +** stack, below original 'func', so that 'luaD_precall' can call it. Raise +** an error if there is no '__call' metafield. +*/ +StkId luaD_tryfuncTM (lua_State *L, StkId func) { + const TValue *tm; + StkId p; + checkstackGCp(L, 1, func); /* space for metamethod */ + tm = luaT_gettmbyobj(L, s2v(func), TM_CALL); /* (after previous GC) */ + if (l_unlikely(ttisnil(tm))) + luaG_callerror(L, s2v(func)); /* nothing to call */ + for (p = L->top.p; p > func; p--) /* open space for metamethod */ + setobjs2s(L, p, p-1); + L->top.p++; /* stack space pre-allocated by the caller */ + setobj2s(L, func, tm); /* metamethod is the new function to be called */ + return func; +} + + +/* +** Given 'nres' results at 'firstResult', move 'wanted' of them to 'res'. +** Handle most typical cases (zero results for commands, one result for +** expressions, multiple results for tail calls/single parameters) +** separated. +*/ +l_sinline void moveresults (lua_State *L, StkId res, int nres, int wanted) { + StkId firstresult; + int i; + switch (wanted) { /* handle typical cases separately */ + case 0: /* no values needed */ + L->top.p = res; + return; + case 1: /* one value needed */ + if (nres == 0) /* no results? */ + setnilvalue(s2v(res)); /* adjust with nil */ + else /* at least one result */ + setobjs2s(L, res, L->top.p - nres); /* move it to proper place */ + L->top.p = res + 1; + return; + case LUA_MULTRET: + wanted = nres; /* we want all results */ + break; + default: /* two/more results and/or to-be-closed variables */ + if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */ + L->ci->callstatus |= CIST_CLSRET; /* in case of yields */ + L->ci->u2.nres = nres; + res = luaF_close(L, res, CLOSEKTOP, 1); + L->ci->callstatus &= ~CIST_CLSRET; + if (L->hookmask) { /* if needed, call hook after '__close's */ + ptrdiff_t savedres = savestack(L, res); + rethook(L, L->ci, nres); + res = restorestack(L, savedres); /* hook can move stack */ + } + wanted = decodeNresults(wanted); + if (wanted == LUA_MULTRET) + wanted = nres; /* we want all results */ + } + break; + } + /* generic case */ + firstresult = L->top.p - nres; /* index of first result */ + if (nres > wanted) /* extra results? */ + nres = wanted; /* don't need them */ + for (i = 0; i < nres; i++) /* move all results to correct place */ + setobjs2s(L, res + i, firstresult + i); + for (; i < wanted; i++) /* complete wanted number of results */ + setnilvalue(s2v(res + i)); + L->top.p = res + wanted; /* top points after the last result */ +} + + +/* +** Finishes a function call: calls hook if necessary, moves current +** number of results to proper place, and returns to previous call +** info. If function has to close variables, hook must be called after +** that. +*/ +void luaD_poscall (lua_State *L, CallInfo *ci, int nres) { + int wanted = ci->nresults; + if (l_unlikely(L->hookmask && !hastocloseCfunc(wanted))) + rethook(L, ci, nres); + /* move results to proper place */ + moveresults(L, ci->func.p, nres, wanted); + /* function cannot be in any of these cases when returning */ + lua_assert(!(ci->callstatus & + (CIST_HOOKED | CIST_YPCALL | CIST_FIN | CIST_TRAN | CIST_CLSRET))); + L->ci = ci->previous; /* back to caller (after closing variables) */ +} + + + +#define next_ci(L) (L->ci->next ? L->ci->next : luaE_extendCI(L)) + + +l_sinline CallInfo *prepCallInfo (lua_State *L, StkId func, int nret, + int mask, StkId top) { + CallInfo *ci = L->ci = next_ci(L); /* new frame */ + ci->func.p = func; + ci->nresults = nret; + ci->callstatus = mask; + ci->top.p = top; + return ci; +} + + +/* +** precall for C functions +*/ +l_sinline int precallC (lua_State *L, StkId func, int nresults, + lua_CFunction f) { + int n; /* number of returns */ + CallInfo *ci; + checkstackGCp(L, LUA_MINSTACK, func); /* ensure minimum stack size */ + L->ci = ci = prepCallInfo(L, func, nresults, CIST_C, + L->top.p + LUA_MINSTACK); + lua_assert(ci->top.p <= L->stack_last.p); + if (l_unlikely(L->hookmask & LUA_MASKCALL)) { + int narg = cast_int(L->top.p - func) - 1; + luaD_hook(L, LUA_HOOKCALL, -1, 1, narg); + } + lua_unlock(L); + n = (*f)(L); /* do the actual call */ + lua_lock(L); + api_checknelems(L, n); + luaD_poscall(L, ci, n); + return n; +} + + +/* +** Prepare a function for a tail call, building its call info on top +** of the current call info. 'narg1' is the number of arguments plus 1 +** (so that it includes the function itself). Return the number of +** results, if it was a C function, or -1 for a Lua function. +*/ +int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, + int narg1, int delta) { + retry: + switch (ttypetag(s2v(func))) { + case LUA_VCCL: /* C closure */ + return precallC(L, func, LUA_MULTRET, clCvalue(s2v(func))->f); + case LUA_VLCF: /* light C function */ + return precallC(L, func, LUA_MULTRET, fvalue(s2v(func))); + case LUA_VLCL: { /* Lua function */ + Proto *p = clLvalue(s2v(func))->p; + int fsize = p->maxstacksize; /* frame size */ + int nfixparams = p->numparams; + int i; + checkstackGCp(L, fsize - delta, func); + ci->func.p -= delta; /* restore 'func' (if vararg) */ + for (i = 0; i < narg1; i++) /* move down function and arguments */ + setobjs2s(L, ci->func.p + i, func + i); + func = ci->func.p; /* moved-down function */ + for (; narg1 <= nfixparams; narg1++) + setnilvalue(s2v(func + narg1)); /* complete missing arguments */ + ci->top.p = func + 1 + fsize; /* top for new function */ + lua_assert(ci->top.p <= L->stack_last.p); + ci->u.l.savedpc = p->code; /* starting point */ + ci->callstatus |= CIST_TAIL; + L->top.p = func + narg1; /* set top */ + return -1; + } + default: { /* not a function */ + func = luaD_tryfuncTM(L, func); /* try to get '__call' metamethod */ + /* return luaD_pretailcall(L, ci, func, narg1 + 1, delta); */ + narg1++; + goto retry; /* try again */ + } + } +} + + +/* +** Prepares the call to a function (C or Lua). For C functions, also do +** the call. The function to be called is at '*func'. The arguments +** are on the stack, right after the function. Returns the CallInfo +** to be executed, if it was a Lua function. Otherwise (a C function) +** returns NULL, with all the results on the stack, starting at the +** original function position. +*/ +CallInfo *luaD_precall (lua_State *L, StkId func, int nresults) { + retry: + switch (ttypetag(s2v(func))) { + case LUA_VCCL: /* C closure */ + precallC(L, func, nresults, clCvalue(s2v(func))->f); + return NULL; + case LUA_VLCF: /* light C function */ + precallC(L, func, nresults, fvalue(s2v(func))); + return NULL; + case LUA_VLCL: { /* Lua function */ + CallInfo *ci; + Proto *p = clLvalue(s2v(func))->p; + int narg = cast_int(L->top.p - func) - 1; /* number of real arguments */ + int nfixparams = p->numparams; + int fsize = p->maxstacksize; /* frame size */ + checkstackGCp(L, fsize, func); + L->ci = ci = prepCallInfo(L, func, nresults, 0, func + 1 + fsize); + ci->u.l.savedpc = p->code; /* starting point */ + for (; narg < nfixparams; narg++) + setnilvalue(s2v(L->top.p++)); /* complete missing arguments */ + lua_assert(ci->top.p <= L->stack_last.p); + return ci; + } + default: { /* not a function */ + func = luaD_tryfuncTM(L, func); /* try to get '__call' metamethod */ + /* return luaD_precall(L, func, nresults); */ + goto retry; /* try again with metamethod */ + } + } +} + + +/* +** Call a function (C or Lua) through C. 'inc' can be 1 (increment +** number of recursive invocations in the C stack) or nyci (the same +** plus increment number of non-yieldable calls). +** This function can be called with some use of EXTRA_STACK, so it should +** check the stack before doing anything else. 'luaD_precall' already +** does that. +*/ +l_sinline void ccall (lua_State *L, StkId func, int nResults, l_uint32 inc) { + CallInfo *ci; + L->nCcalls += inc; + if (l_unlikely(getCcalls(L) >= LUAI_MAXCCALLS)) { + checkstackp(L, 0, func); /* free any use of EXTRA_STACK */ + luaE_checkcstack(L); + } + if ((ci = luaD_precall(L, func, nResults)) != NULL) { /* Lua function? */ + ci->callstatus = CIST_FRESH; /* mark that it is a "fresh" execute */ + luaV_execute(L, ci); /* call it */ + } + L->nCcalls -= inc; +} + + +/* +** External interface for 'ccall' +*/ +void luaD_call (lua_State *L, StkId func, int nResults) { + ccall(L, func, nResults, 1); +} + + +/* +** Similar to 'luaD_call', but does not allow yields during the call. +*/ +void luaD_callnoyield (lua_State *L, StkId func, int nResults) { + ccall(L, func, nResults, nyci); +} + + +/* +** Finish the job of 'lua_pcallk' after it was interrupted by an yield. +** (The caller, 'finishCcall', does the final call to 'adjustresults'.) +** The main job is to complete the 'luaD_pcall' called by 'lua_pcallk'. +** If a '__close' method yields here, eventually control will be back +** to 'finishCcall' (when that '__close' method finally returns) and +** 'finishpcallk' will run again and close any still pending '__close' +** methods. Similarly, if a '__close' method errs, 'precover' calls +** 'unroll' which calls ''finishCcall' and we are back here again, to +** close any pending '__close' methods. +** Note that, up to the call to 'luaF_close', the corresponding +** 'CallInfo' is not modified, so that this repeated run works like the +** first one (except that it has at least one less '__close' to do). In +** particular, field CIST_RECST preserves the error status across these +** multiple runs, changing only if there is a new error. +*/ +static int finishpcallk (lua_State *L, CallInfo *ci) { + int status = getcistrecst(ci); /* get original status */ + if (l_likely(status == LUA_OK)) /* no error? */ + status = LUA_YIELD; /* was interrupted by an yield */ + else { /* error */ + StkId func = restorestack(L, ci->u2.funcidx); + L->allowhook = getoah(ci->callstatus); /* restore 'allowhook' */ + func = luaF_close(L, func, status, 1); /* can yield or raise an error */ + luaD_seterrorobj(L, status, func); + luaD_shrinkstack(L); /* restore stack size in case of overflow */ + setcistrecst(ci, LUA_OK); /* clear original status */ + } + ci->callstatus &= ~CIST_YPCALL; + L->errfunc = ci->u.c.old_errfunc; + /* if it is here, there were errors or yields; unlike 'lua_pcallk', + do not change status */ + return status; +} + + +/* +** Completes the execution of a C function interrupted by an yield. +** The interruption must have happened while the function was either +** closing its tbc variables in 'moveresults' or executing +** 'lua_callk'/'lua_pcallk'. In the first case, it just redoes +** 'luaD_poscall'. In the second case, the call to 'finishpcallk' +** finishes the interrupted execution of 'lua_pcallk'. After that, it +** calls the continuation of the interrupted function and finally it +** completes the job of the 'luaD_call' that called the function. In +** the call to 'adjustresults', we do not know the number of results +** of the function called by 'lua_callk'/'lua_pcallk', so we are +** conservative and use LUA_MULTRET (always adjust). +*/ +static void finishCcall (lua_State *L, CallInfo *ci) { + int n; /* actual number of results from C function */ + if (ci->callstatus & CIST_CLSRET) { /* was returning? */ + lua_assert(hastocloseCfunc(ci->nresults)); + n = ci->u2.nres; /* just redo 'luaD_poscall' */ + /* don't need to reset CIST_CLSRET, as it will be set again anyway */ + } + else { + int status = LUA_YIELD; /* default if there were no errors */ + /* must have a continuation and must be able to call it */ + lua_assert(ci->u.c.k != NULL && yieldable(L)); + if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */ + status = finishpcallk(L, ci); /* finish it */ + adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */ + lua_unlock(L); + n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */ + lua_lock(L); + api_checknelems(L, n); + } + luaD_poscall(L, ci, n); /* finish 'luaD_call' */ +} + + +/* +** Executes "full continuation" (everything in the stack) of a +** previously interrupted coroutine until the stack is empty (or another +** interruption long-jumps out of the loop). +*/ +static void unroll (lua_State *L, void *ud) { + CallInfo *ci; + UNUSED(ud); + while ((ci = L->ci) != &L->base_ci) { /* something in the stack */ + if (!isLua(ci)) /* C function? */ + finishCcall(L, ci); /* complete its execution */ + else { /* Lua function */ + luaV_finishOp(L); /* finish interrupted instruction */ + luaV_execute(L, ci); /* execute down to higher C 'boundary' */ + } + } +} + + +/* +** Try to find a suspended protected call (a "recover point") for the +** given thread. +*/ +static CallInfo *findpcall (lua_State *L) { + CallInfo *ci; + for (ci = L->ci; ci != NULL; ci = ci->previous) { /* search for a pcall */ + if (ci->callstatus & CIST_YPCALL) + return ci; + } + return NULL; /* no pending pcall */ +} + + +/* +** Signal an error in the call to 'lua_resume', not in the execution +** of the coroutine itself. (Such errors should not be handled by any +** coroutine error handler and should not kill the coroutine.) +*/ +static int resume_error (lua_State *L, const char *msg, int narg) { + L->top.p -= narg; /* remove args from the stack */ + setsvalue2s(L, L->top.p, luaS_new(L, msg)); /* push error message */ + api_incr_top(L); + lua_unlock(L); + return LUA_ERRRUN; +} + + +/* +** Do the work for 'lua_resume' in protected mode. Most of the work +** depends on the status of the coroutine: initial state, suspended +** inside a hook, or regularly suspended (optionally with a continuation +** function), plus erroneous cases: non-suspended coroutine or dead +** coroutine. +*/ +static void resume (lua_State *L, void *ud) { + int n = *(cast(int*, ud)); /* number of arguments */ + StkId firstArg = L->top.p - n; /* first argument */ + CallInfo *ci = L->ci; + if (L->status == LUA_OK) /* starting a coroutine? */ + ccall(L, firstArg - 1, LUA_MULTRET, 0); /* just call its body */ + else { /* resuming from previous yield */ + lua_assert(L->status == LUA_YIELD); + L->status = LUA_OK; /* mark that it is running (again) */ + if (isLua(ci)) { /* yielded inside a hook? */ + L->top.p = firstArg; /* discard arguments */ + luaV_execute(L, ci); /* just continue running Lua code */ + } + else { /* 'common' yield */ + if (ci->u.c.k != NULL) { /* does it have a continuation function? */ + lua_unlock(L); + n = (*ci->u.c.k)(L, LUA_YIELD, ci->u.c.ctx); /* call continuation */ + lua_lock(L); + api_checknelems(L, n); + } + luaD_poscall(L, ci, n); /* finish 'luaD_call' */ + } + unroll(L, NULL); /* run continuation */ + } +} + + +/* +** Unrolls a coroutine in protected mode while there are recoverable +** errors, that is, errors inside a protected call. (Any error +** interrupts 'unroll', and this loop protects it again so it can +** continue.) Stops with a normal end (status == LUA_OK), an yield +** (status == LUA_YIELD), or an unprotected error ('findpcall' doesn't +** find a recover point). +*/ +static int precover (lua_State *L, int status) { + CallInfo *ci; + while (errorstatus(status) && (ci = findpcall(L)) != NULL) { + L->ci = ci; /* go down to recovery functions */ + setcistrecst(ci, status); /* status to finish 'pcall' */ + status = luaD_rawrunprotected(L, unroll, NULL); + } + return status; +} + + +LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, + int *nresults) { + int status; + lua_lock(L); + if (L->status == LUA_OK) { /* may be starting a coroutine */ + if (L->ci != &L->base_ci) /* not in base level? */ + return resume_error(L, "cannot resume non-suspended coroutine", nargs); + else if (L->top.p - (L->ci->func.p + 1) == nargs) /* no function? */ + return resume_error(L, "cannot resume dead coroutine", nargs); + } + else if (L->status != LUA_YIELD) /* ended with errors? */ + return resume_error(L, "cannot resume dead coroutine", nargs); + L->nCcalls = (from) ? getCcalls(from) : 0; + if (getCcalls(L) >= LUAI_MAXCCALLS) + return resume_error(L, "C stack overflow", nargs); + L->nCcalls++; + luai_userstateresume(L, nargs); + api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); + status = luaD_rawrunprotected(L, resume, &nargs); + /* continue running after recoverable errors */ + status = precover(L, status); + if (l_likely(!errorstatus(status))) + lua_assert(status == L->status); /* normal end or yield */ + else { /* unrecoverable error */ + L->status = cast_byte(status); /* mark thread as 'dead' */ + luaD_seterrorobj(L, status, L->top.p); /* push error message */ + L->ci->top.p = L->top.p; + } + *nresults = (status == LUA_YIELD) ? L->ci->u2.nyield + : cast_int(L->top.p - (L->ci->func.p + 1)); + lua_unlock(L); + return status; +} + + +LUA_API int lua_isyieldable (lua_State *L) { + return yieldable(L); +} + + +LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k) { + CallInfo *ci; + luai_userstateyield(L, nresults); + lua_lock(L); + ci = L->ci; + api_checknelems(L, nresults); + if (l_unlikely(!yieldable(L))) { + if (L != G(L)->mainthread) + luaG_runerror(L, "attempt to yield across a C-call boundary"); + else + luaG_runerror(L, "attempt to yield from outside a coroutine"); + } + L->status = LUA_YIELD; + ci->u2.nyield = nresults; /* save number of results */ + if (isLua(ci)) { /* inside a hook? */ + lua_assert(!isLuacode(ci)); + api_check(L, nresults == 0, "hooks cannot yield values"); + api_check(L, k == NULL, "hooks cannot continue after yielding"); + } + else { + if ((ci->u.c.k = k) != NULL) /* is there a continuation? */ + ci->u.c.ctx = ctx; /* save context */ + luaD_throw(L, LUA_YIELD); + } + lua_assert(ci->callstatus & CIST_HOOKED); /* must be inside a hook */ + lua_unlock(L); + return 0; /* return to 'luaD_hook' */ +} + + +/* +** Auxiliary structure to call 'luaF_close' in protected mode. +*/ +struct CloseP { + StkId level; + int status; +}; + + +/* +** Auxiliary function to call 'luaF_close' in protected mode. +*/ +static void closepaux (lua_State *L, void *ud) { + struct CloseP *pcl = cast(struct CloseP *, ud); + luaF_close(L, pcl->level, pcl->status, 0); +} + + +/* +** Calls 'luaF_close' in protected mode. Return the original status +** or, in case of errors, the new status. +*/ +int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status) { + CallInfo *old_ci = L->ci; + lu_byte old_allowhooks = L->allowhook; + for (;;) { /* keep closing upvalues until no more errors */ + struct CloseP pcl; + pcl.level = restorestack(L, level); pcl.status = status; + status = luaD_rawrunprotected(L, &closepaux, &pcl); + if (l_likely(status == LUA_OK)) /* no more errors? */ + return pcl.status; + else { /* an error occurred; restore saved state and repeat */ + L->ci = old_ci; + L->allowhook = old_allowhooks; + } + } +} + + +/* +** Call the C function 'func' in protected mode, restoring basic +** thread information ('allowhook', etc.) and in particular +** its stack level in case of errors. +*/ +int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t old_top, ptrdiff_t ef) { + int status; + CallInfo *old_ci = L->ci; + lu_byte old_allowhooks = L->allowhook; + ptrdiff_t old_errfunc = L->errfunc; + L->errfunc = ef; + status = luaD_rawrunprotected(L, func, u); + if (l_unlikely(status != LUA_OK)) { /* an error occurred? */ + L->ci = old_ci; + L->allowhook = old_allowhooks; + status = luaD_closeprotected(L, old_top, status); + luaD_seterrorobj(L, status, restorestack(L, old_top)); + luaD_shrinkstack(L); /* restore stack size in case of overflow */ + } + L->errfunc = old_errfunc; + return status; +} + + + +/* +** Execute a protected parser. +*/ +struct SParser { /* data to 'f_parser' */ + ZIO *z; + Mbuffer buff; /* dynamic structure used by the scanner */ + Dyndata dyd; /* dynamic structures used by the parser */ + const char *mode; + const char *name; +}; + + +static void checkmode (lua_State *L, const char *mode, const char *x) { + if (mode && strchr(mode, x[0]) == NULL) { + luaO_pushfstring(L, + "attempt to load a %s chunk (mode is '%s')", x, mode); + luaD_throw(L, LUA_ERRSYNTAX); + } +} + + +static void f_parser (lua_State *L, void *ud) { + LClosure *cl; + struct SParser *p = cast(struct SParser *, ud); + int c = zgetc(p->z); /* read first character */ + if (c == LUA_SIGNATURE[0]) { + checkmode(L, p->mode, "binary"); + cl = luaU_undump(L, p->z, p->name); + } + else { + checkmode(L, p->mode, "text"); + cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c); + } + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luaF_initupvals(L, cl); +} + + +int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, + const char *mode) { + struct SParser p; + int status; + incnny(L); /* cannot yield during parsing */ + p.z = z; p.name = name; p.mode = mode; + p.dyd.actvar.arr = NULL; p.dyd.actvar.size = 0; + p.dyd.gt.arr = NULL; p.dyd.gt.size = 0; + p.dyd.label.arr = NULL; p.dyd.label.size = 0; + luaZ_initbuffer(L, &p.buff); + status = luaD_pcall(L, f_parser, &p, savestack(L, L->top.p), L->errfunc); + luaZ_freebuffer(L, &p.buff); + luaM_freearray(L, p.dyd.actvar.arr, p.dyd.actvar.size); + luaM_freearray(L, p.dyd.gt.arr, p.dyd.gt.size); + luaM_freearray(L, p.dyd.label.arr, p.dyd.label.size); + decnny(L); + return status; +} + + diff --git a/arm9/source/lua/ldo.h b/arm9/source/lua/ldo.h new file mode 100644 index 000000000..1aa446ad0 --- /dev/null +++ b/arm9/source/lua/ldo.h @@ -0,0 +1,88 @@ +/* +** $Id: ldo.h $ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + +#ifndef ldo_h +#define ldo_h + + +#include "llimits.h" +#include "lobject.h" +#include "lstate.h" +#include "lzio.h" + + +/* +** Macro to check stack size and grow stack if needed. Parameters +** 'pre'/'pos' allow the macro to preserve a pointer into the +** stack across reallocations, doing the work only when needed. +** It also allows the running of one GC step when the stack is +** reallocated. +** 'condmovestack' is used in heavy tests to force a stack reallocation +** at every check. +*/ +#define luaD_checkstackaux(L,n,pre,pos) \ + if (l_unlikely(L->stack_last.p - L->top.p <= (n))) \ + { pre; luaD_growstack(L, n, 1); pos; } \ + else { condmovestack(L,pre,pos); } + +/* In general, 'pre'/'pos' are empty (nothing to save) */ +#define luaD_checkstack(L,n) luaD_checkstackaux(L,n,(void)0,(void)0) + + + +#define savestack(L,pt) (cast_charp(pt) - cast_charp(L->stack.p)) +#define restorestack(L,n) cast(StkId, cast_charp(L->stack.p) + (n)) + + +/* macro to check stack size, preserving 'p' */ +#define checkstackp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p), /* save 'p' */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ + + +/* macro to check stack size and GC, preserving 'p' */ +#define checkstackGCp(L,n,p) \ + luaD_checkstackaux(L, n, \ + ptrdiff_t t__ = savestack(L, p); /* save 'p' */ \ + luaC_checkGC(L), /* stack grow uses memory */ \ + p = restorestack(L, t__)) /* 'pos' part: restore 'p' */ + + +/* macro to check stack size and GC */ +#define checkstackGC(L,fsize) \ + luaD_checkstackaux(L, (fsize), luaC_checkGC(L), (void)0) + + +/* type of protected functions, to be ran by 'runprotected' */ +typedef void (*Pfunc) (lua_State *L, void *ud); + +LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); +LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, + const char *mode); +LUAI_FUNC void luaD_hook (lua_State *L, int event, int line, + int fTransfer, int nTransfer); +LUAI_FUNC void luaD_hookcall (lua_State *L, CallInfo *ci); +LUAI_FUNC int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, + int narg1, int delta); +LUAI_FUNC CallInfo *luaD_precall (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); +LUAI_FUNC StkId luaD_tryfuncTM (lua_State *L, StkId func); +LUAI_FUNC int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status); +LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t oldtop, ptrdiff_t ef); +LUAI_FUNC void luaD_poscall (lua_State *L, CallInfo *ci, int nres); +LUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int raiseerror); +LUAI_FUNC int luaD_growstack (lua_State *L, int n, int raiseerror); +LUAI_FUNC void luaD_shrinkstack (lua_State *L); +LUAI_FUNC void luaD_inctop (lua_State *L); + +LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); +LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); + +#endif + diff --git a/arm9/source/lua/ldump.c b/arm9/source/lua/ldump.c new file mode 100644 index 000000000..f231691b7 --- /dev/null +++ b/arm9/source/lua/ldump.c @@ -0,0 +1,230 @@ +/* +** $Id: ldump.c $ +** save precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#define ldump_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "lobject.h" +#include "lstate.h" +#include "lundump.h" + + +typedef struct { + lua_State *L; + lua_Writer writer; + void *data; + int strip; + int status; +} DumpState; + + +/* +** All high-level dumps go through dumpVector; you can change it to +** change the endianness of the result +*/ +#define dumpVector(D,v,n) dumpBlock(D,v,(n)*sizeof((v)[0])) + +#define dumpLiteral(D, s) dumpBlock(D,s,sizeof(s) - sizeof(char)) + + +static void dumpBlock (DumpState *D, const void *b, size_t size) { + if (D->status == 0 && size > 0) { + lua_unlock(D->L); + D->status = (*D->writer)(D->L, b, size, D->data); + lua_lock(D->L); + } +} + + +#define dumpVar(D,x) dumpVector(D,&x,1) + + +static void dumpByte (DumpState *D, int y) { + lu_byte x = (lu_byte)y; + dumpVar(D, x); +} + + +/* +** 'dumpSize' buffer size: each byte can store up to 7 bits. (The "+6" +** rounds up the division.) +*/ +#define DIBS ((sizeof(size_t) * CHAR_BIT + 6) / 7) + +static void dumpSize (DumpState *D, size_t x) { + lu_byte buff[DIBS]; + int n = 0; + do { + buff[DIBS - (++n)] = x & 0x7f; /* fill buffer in reverse order */ + x >>= 7; + } while (x != 0); + buff[DIBS - 1] |= 0x80; /* mark last byte */ + dumpVector(D, buff + DIBS - n, n); +} + + +static void dumpInt (DumpState *D, int x) { + dumpSize(D, x); +} + + +static void dumpNumber (DumpState *D, lua_Number x) { + dumpVar(D, x); +} + + +static void dumpInteger (DumpState *D, lua_Integer x) { + dumpVar(D, x); +} + + +static void dumpString (DumpState *D, const TString *s) { + if (s == NULL) + dumpSize(D, 0); + else { + size_t size = tsslen(s); + const char *str = getstr(s); + dumpSize(D, size + 1); + dumpVector(D, str, size); + } +} + + +static void dumpCode (DumpState *D, const Proto *f) { + dumpInt(D, f->sizecode); + dumpVector(D, f->code, f->sizecode); +} + + +static void dumpFunction(DumpState *D, const Proto *f, TString *psource); + +static void dumpConstants (DumpState *D, const Proto *f) { + int i; + int n = f->sizek; + dumpInt(D, n); + for (i = 0; i < n; i++) { + const TValue *o = &f->k[i]; + int tt = ttypetag(o); + dumpByte(D, tt); + switch (tt) { + case LUA_VNUMFLT: + dumpNumber(D, fltvalue(o)); + break; + case LUA_VNUMINT: + dumpInteger(D, ivalue(o)); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + dumpString(D, tsvalue(o)); + break; + default: + lua_assert(tt == LUA_VNIL || tt == LUA_VFALSE || tt == LUA_VTRUE); + } + } +} + + +static void dumpProtos (DumpState *D, const Proto *f) { + int i; + int n = f->sizep; + dumpInt(D, n); + for (i = 0; i < n; i++) + dumpFunction(D, f->p[i], f->source); +} + + +static void dumpUpvalues (DumpState *D, const Proto *f) { + int i, n = f->sizeupvalues; + dumpInt(D, n); + for (i = 0; i < n; i++) { + dumpByte(D, f->upvalues[i].instack); + dumpByte(D, f->upvalues[i].idx); + dumpByte(D, f->upvalues[i].kind); + } +} + + +static void dumpDebug (DumpState *D, const Proto *f) { + int i, n; + n = (D->strip) ? 0 : f->sizelineinfo; + dumpInt(D, n); + dumpVector(D, f->lineinfo, n); + n = (D->strip) ? 0 : f->sizeabslineinfo; + dumpInt(D, n); + for (i = 0; i < n; i++) { + dumpInt(D, f->abslineinfo[i].pc); + dumpInt(D, f->abslineinfo[i].line); + } + n = (D->strip) ? 0 : f->sizelocvars; + dumpInt(D, n); + for (i = 0; i < n; i++) { + dumpString(D, f->locvars[i].varname); + dumpInt(D, f->locvars[i].startpc); + dumpInt(D, f->locvars[i].endpc); + } + n = (D->strip) ? 0 : f->sizeupvalues; + dumpInt(D, n); + for (i = 0; i < n; i++) + dumpString(D, f->upvalues[i].name); +} + + +static void dumpFunction (DumpState *D, const Proto *f, TString *psource) { + if (D->strip || f->source == psource) + dumpString(D, NULL); /* no debug info or same source as its parent */ + else + dumpString(D, f->source); + dumpInt(D, f->linedefined); + dumpInt(D, f->lastlinedefined); + dumpByte(D, f->numparams); + dumpByte(D, f->is_vararg); + dumpByte(D, f->maxstacksize); + dumpCode(D, f); + dumpConstants(D, f); + dumpUpvalues(D, f); + dumpProtos(D, f); + dumpDebug(D, f); +} + + +static void dumpHeader (DumpState *D) { + dumpLiteral(D, LUA_SIGNATURE); + dumpByte(D, LUAC_VERSION); + dumpByte(D, LUAC_FORMAT); + dumpLiteral(D, LUAC_DATA); + dumpByte(D, sizeof(Instruction)); + dumpByte(D, sizeof(lua_Integer)); + dumpByte(D, sizeof(lua_Number)); + dumpInteger(D, LUAC_INT); + dumpNumber(D, LUAC_NUM); +} + + +/* +** dump Lua function as precompiled chunk +*/ +int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data, + int strip) { + DumpState D; + D.L = L; + D.writer = w; + D.data = data; + D.strip = strip; + D.status = 0; + dumpHeader(&D); + dumpByte(&D, f->sizeupvalues); + dumpFunction(&D, f, NULL); + return D.status; +} + diff --git a/arm9/source/lua/lfunc.c b/arm9/source/lua/lfunc.c new file mode 100644 index 000000000..0945f241d --- /dev/null +++ b/arm9/source/lua/lfunc.c @@ -0,0 +1,294 @@ +/* +** $Id: lfunc.c $ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + +#define lfunc_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + + +CClosure *luaF_newCclosure (lua_State *L, int nupvals) { + GCObject *o = luaC_newobj(L, LUA_VCCL, sizeCclosure(nupvals)); + CClosure *c = gco2ccl(o); + c->nupvalues = cast_byte(nupvals); + return c; +} + + +LClosure *luaF_newLclosure (lua_State *L, int nupvals) { + GCObject *o = luaC_newobj(L, LUA_VLCL, sizeLclosure(nupvals)); + LClosure *c = gco2lcl(o); + c->p = NULL; + c->nupvalues = cast_byte(nupvals); + while (nupvals--) c->upvals[nupvals] = NULL; + return c; +} + + +/* +** fill a closure with new closed upvalues +*/ +void luaF_initupvals (lua_State *L, LClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) { + GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal)); + UpVal *uv = gco2upv(o); + uv->v.p = &uv->u.value; /* make it closed */ + setnilvalue(uv->v.p); + cl->upvals[i] = uv; + luaC_objbarrier(L, cl, uv); + } +} + + +/* +** Create a new upvalue at the given level, and link it to the list of +** open upvalues of 'L' after entry 'prev'. +**/ +static UpVal *newupval (lua_State *L, StkId level, UpVal **prev) { + GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal)); + UpVal *uv = gco2upv(o); + UpVal *next = *prev; + uv->v.p = s2v(level); /* current value lives in the stack */ + uv->u.open.next = next; /* link it to list of open upvalues */ + uv->u.open.previous = prev; + if (next) + next->u.open.previous = &uv->u.open.next; + *prev = uv; + if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ + L->twups = G(L)->twups; /* link it to the list */ + G(L)->twups = L; + } + return uv; +} + + +/* +** Find and reuse, or create if it does not exist, an upvalue +** at the given level. +*/ +UpVal *luaF_findupval (lua_State *L, StkId level) { + UpVal **pp = &L->openupval; + UpVal *p; + lua_assert(isintwups(L) || L->openupval == NULL); + while ((p = *pp) != NULL && uplevel(p) >= level) { /* search for it */ + lua_assert(!isdead(G(L), p)); + if (uplevel(p) == level) /* corresponding upvalue? */ + return p; /* return it */ + pp = &p->u.open.next; + } + /* not found: create a new upvalue after 'pp' */ + return newupval(L, level, pp); +} + + +/* +** Call closing method for object 'obj' with error message 'err'. The +** boolean 'yy' controls whether the call is yieldable. +** (This function assumes EXTRA_STACK.) +*/ +static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) { + StkId top = L->top.p; + const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); + setobj2s(L, top, tm); /* will call metamethod... */ + setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */ + setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */ + L->top.p = top + 3; /* add function and arguments */ + if (yy) + luaD_call(L, top, 0); + else + luaD_callnoyield(L, top, 0); +} + + +/* +** Check whether object at given level has a close metamethod and raise +** an error if not. +*/ +static void checkclosemth (lua_State *L, StkId level) { + const TValue *tm = luaT_gettmbyobj(L, s2v(level), TM_CLOSE); + if (ttisnil(tm)) { /* no metamethod? */ + int idx = cast_int(level - L->ci->func.p); /* variable index */ + const char *vname = luaG_findlocal(L, L->ci, idx, NULL); + if (vname == NULL) vname = "?"; + luaG_runerror(L, "variable '%s' got a non-closable value", vname); + } +} + + +/* +** Prepare and call a closing method. +** If status is CLOSEKTOP, the call to the closing method will be pushed +** at the top of the stack. Otherwise, values can be pushed right after +** the 'level' of the upvalue being closed, as everything after that +** won't be used again. +*/ +static void prepcallclosemth (lua_State *L, StkId level, int status, int yy) { + TValue *uv = s2v(level); /* value being closed */ + TValue *errobj; + if (status == CLOSEKTOP) + errobj = &G(L)->nilvalue; /* error object is nil */ + else { /* 'luaD_seterrorobj' will set top to level + 2 */ + errobj = s2v(level + 1); /* error object goes after 'uv' */ + luaD_seterrorobj(L, status, level + 1); /* set error object */ + } + callclosemethod(L, uv, errobj, yy); +} + + +/* +** Maximum value for deltas in 'tbclist', dependent on the type +** of delta. (This macro assumes that an 'L' is in scope where it +** is used.) +*/ +#define MAXDELTA \ + ((256ul << ((sizeof(L->stack.p->tbclist.delta) - 1) * 8)) - 1) + + +/* +** Insert a variable in the list of to-be-closed variables. +*/ +void luaF_newtbcupval (lua_State *L, StkId level) { + lua_assert(level > L->tbclist.p); + if (l_isfalse(s2v(level))) + return; /* false doesn't need to be closed */ + checkclosemth(L, level); /* value must have a close method */ + while (cast_uint(level - L->tbclist.p) > MAXDELTA) { + L->tbclist.p += MAXDELTA; /* create a dummy node at maximum delta */ + L->tbclist.p->tbclist.delta = 0; + } + level->tbclist.delta = cast(unsigned short, level - L->tbclist.p); + L->tbclist.p = level; +} + + +void luaF_unlinkupval (UpVal *uv) { + lua_assert(upisopen(uv)); + *uv->u.open.previous = uv->u.open.next; + if (uv->u.open.next) + uv->u.open.next->u.open.previous = uv->u.open.previous; +} + + +/* +** Close all upvalues up to the given stack level. +*/ +void luaF_closeupval (lua_State *L, StkId level) { + UpVal *uv; + StkId upl; /* stack index pointed by 'uv' */ + while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) { + TValue *slot = &uv->u.value; /* new position for value */ + lua_assert(uplevel(uv) < L->top.p); + luaF_unlinkupval(uv); /* remove upvalue from 'openupval' list */ + setobj(L, slot, uv->v.p); /* move value to upvalue slot */ + uv->v.p = slot; /* now current value lives here */ + if (!iswhite(uv)) { /* neither white nor dead? */ + nw2black(uv); /* closed upvalues cannot be gray */ + luaC_barrier(L, uv, slot); + } + } +} + + +/* +** Remove first element from the tbclist plus its dummy nodes. +*/ +static void poptbclist (lua_State *L) { + StkId tbc = L->tbclist.p; + lua_assert(tbc->tbclist.delta > 0); /* first element cannot be dummy */ + tbc -= tbc->tbclist.delta; + while (tbc > L->stack.p && tbc->tbclist.delta == 0) + tbc -= MAXDELTA; /* remove dummy nodes */ + L->tbclist.p = tbc; +} + + +/* +** Close all upvalues and to-be-closed variables up to the given stack +** level. Return restored 'level'. +*/ +StkId luaF_close (lua_State *L, StkId level, int status, int yy) { + ptrdiff_t levelrel = savestack(L, level); + luaF_closeupval(L, level); /* first, close the upvalues */ + while (L->tbclist.p >= level) { /* traverse tbc's down to that level */ + StkId tbc = L->tbclist.p; /* get variable index */ + poptbclist(L); /* remove it from list */ + prepcallclosemth(L, tbc, status, yy); /* close variable */ + level = restorestack(L, levelrel); + } + return level; +} + + +Proto *luaF_newproto (lua_State *L) { + GCObject *o = luaC_newobj(L, LUA_VPROTO, sizeof(Proto)); + Proto *f = gco2p(o); + f->k = NULL; + f->sizek = 0; + f->p = NULL; + f->sizep = 0; + f->code = NULL; + f->sizecode = 0; + f->lineinfo = NULL; + f->sizelineinfo = 0; + f->abslineinfo = NULL; + f->sizeabslineinfo = 0; + f->upvalues = NULL; + f->sizeupvalues = 0; + f->numparams = 0; + f->is_vararg = 0; + f->maxstacksize = 0; + f->locvars = NULL; + f->sizelocvars = 0; + f->linedefined = 0; + f->lastlinedefined = 0; + f->source = NULL; + return f; +} + + +void luaF_freeproto (lua_State *L, Proto *f) { + luaM_freearray(L, f->code, f->sizecode); + luaM_freearray(L, f->p, f->sizep); + luaM_freearray(L, f->k, f->sizek); + luaM_freearray(L, f->lineinfo, f->sizelineinfo); + luaM_freearray(L, f->abslineinfo, f->sizeabslineinfo); + luaM_freearray(L, f->locvars, f->sizelocvars); + luaM_freearray(L, f->upvalues, f->sizeupvalues); + luaM_free(L, f); +} + + +/* +** Look for n-th local variable at line 'line' in function 'func'. +** Returns NULL if not found. +*/ +const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { + int i; + for (i = 0; isizelocvars && f->locvars[i].startpc <= pc; i++) { + if (pc < f->locvars[i].endpc) { /* is variable active? */ + local_number--; + if (local_number == 0) + return getstr(f->locvars[i].varname); + } + } + return NULL; /* not found */ +} + diff --git a/arm9/source/lua/lfunc.h b/arm9/source/lua/lfunc.h new file mode 100644 index 000000000..3be265efb --- /dev/null +++ b/arm9/source/lua/lfunc.h @@ -0,0 +1,64 @@ +/* +** $Id: lfunc.h $ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + +#ifndef lfunc_h +#define lfunc_h + + +#include "lobject.h" + + +#define sizeCclosure(n) (cast_int(offsetof(CClosure, upvalue)) + \ + cast_int(sizeof(TValue)) * (n)) + +#define sizeLclosure(n) (cast_int(offsetof(LClosure, upvals)) + \ + cast_int(sizeof(TValue *)) * (n)) + + +/* test whether thread is in 'twups' list */ +#define isintwups(L) (L->twups != L) + + +/* +** maximum number of upvalues in a closure (both C and Lua). (Value +** must fit in a VM register.) +*/ +#define MAXUPVAL 255 + + +#define upisopen(up) ((up)->v.p != &(up)->u.value) + + +#define uplevel(up) check_exp(upisopen(up), cast(StkId, (up)->v.p)) + + +/* +** maximum number of misses before giving up the cache of closures +** in prototypes +*/ +#define MAXMISS 10 + + + +/* special status to close upvalues preserving the top of the stack */ +#define CLOSEKTOP (-1) + + +LUAI_FUNC Proto *luaF_newproto (lua_State *L); +LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nupvals); +LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nupvals); +LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); +LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_closeupval (lua_State *L, StkId level); +LUAI_FUNC StkId luaF_close (lua_State *L, StkId level, int status, int yy); +LUAI_FUNC void luaF_unlinkupval (UpVal *uv); +LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); +LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, + int pc); + + +#endif diff --git a/arm9/source/lua/lgc.c b/arm9/source/lua/lgc.c new file mode 100644 index 000000000..a3094ff57 --- /dev/null +++ b/arm9/source/lua/lgc.c @@ -0,0 +1,1739 @@ +/* +** $Id: lgc.c $ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#define lgc_c +#define LUA_CORE + +#include "lprefix.h" + +#include +#include + + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + +/* +** Maximum number of elements to sweep in each single step. +** (Large enough to dissipate fixed overheads but small enough +** to allow small steps for the collector.) +*/ +#define GCSWEEPMAX 100 + +/* +** Maximum number of finalizers to call in each single step. +*/ +#define GCFINMAX 10 + + +/* +** Cost of calling one finalizer. +*/ +#define GCFINALIZECOST 50 + + +/* +** The equivalent, in bytes, of one unit of "work" (visiting a slot, +** sweeping an object, etc.) +*/ +#define WORK2MEM sizeof(TValue) + + +/* +** macro to adjust 'pause': 'pause' is actually used like +** 'pause / PAUSEADJ' (value chosen by tests) +*/ +#define PAUSEADJ 100 + + +/* mask with all color bits */ +#define maskcolors (bitmask(BLACKBIT) | WHITEBITS) + +/* mask with all GC bits */ +#define maskgcbits (maskcolors | AGEBITS) + + +/* macro to erase all color bits then set only the current white bit */ +#define makewhite(g,x) \ + (x->marked = cast_byte((x->marked & ~maskcolors) | luaC_white(g))) + +/* make an object gray (neither white nor black) */ +#define set2gray(x) resetbits(x->marked, maskcolors) + + +/* make an object black (coming from any color) */ +#define set2black(x) \ + (x->marked = cast_byte((x->marked & ~WHITEBITS) | bitmask(BLACKBIT))) + + +#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) + +#define keyiswhite(n) (keyiscollectable(n) && iswhite(gckey(n))) + + +/* +** Protected access to objects in values +*/ +#define gcvalueN(o) (iscollectable(o) ? gcvalue(o) : NULL) + + +#define markvalue(g,o) { checkliveness(g->mainthread,o); \ + if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); } + +#define markkey(g, n) { if keyiswhite(n) reallymarkobject(g,gckey(n)); } + +#define markobject(g,t) { if (iswhite(t)) reallymarkobject(g, obj2gco(t)); } + +/* +** mark an object that can be NULL (either because it is really optional, +** or it was stripped as debug info, or inside an uncompleted structure) +*/ +#define markobjectN(g,t) { if (t) markobject(g,t); } + +static void reallymarkobject (global_State *g, GCObject *o); +static lu_mem atomic (lua_State *L); +static void entersweep (lua_State *L); + + +/* +** {====================================================== +** Generic functions +** ======================================================= +*/ + + +/* +** one after last element in a hash array +*/ +#define gnodelast(h) gnode(h, cast_sizet(sizenode(h))) + + +static GCObject **getgclist (GCObject *o) { + switch (o->tt) { + case LUA_VTABLE: return &gco2t(o)->gclist; + case LUA_VLCL: return &gco2lcl(o)->gclist; + case LUA_VCCL: return &gco2ccl(o)->gclist; + case LUA_VTHREAD: return &gco2th(o)->gclist; + case LUA_VPROTO: return &gco2p(o)->gclist; + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + lua_assert(u->nuvalue > 0); + return &u->gclist; + } + default: lua_assert(0); return 0; + } +} + + +/* +** Link a collectable object 'o' with a known type into the list 'p'. +** (Must be a macro to access the 'gclist' field in different types.) +*/ +#define linkgclist(o,p) linkgclist_(obj2gco(o), &(o)->gclist, &(p)) + +static void linkgclist_ (GCObject *o, GCObject **pnext, GCObject **list) { + lua_assert(!isgray(o)); /* cannot be in a gray list */ + *pnext = *list; + *list = o; + set2gray(o); /* now it is */ +} + + +/* +** Link a generic collectable object 'o' into the list 'p'. +*/ +#define linkobjgclist(o,p) linkgclist_(obj2gco(o), getgclist(o), &(p)) + + + +/* +** Clear keys for empty entries in tables. If entry is empty, mark its +** entry as dead. This allows the collection of the key, but keeps its +** entry in the table: its removal could break a chain and could break +** a table traversal. Other places never manipulate dead keys, because +** its associated empty value is enough to signal that the entry is +** logically empty. +*/ +static void clearkey (Node *n) { + lua_assert(isempty(gval(n))); + if (keyiscollectable(n)) + setdeadkey(n); /* unused key; remove it */ +} + + +/* +** tells whether a key or value can be cleared from a weak +** table. Non-collectable objects are never removed from weak +** tables. Strings behave as 'values', so are never removed too. for +** other objects: if really collected, cannot keep them; for objects +** being finalized, keep them in keys, but not in values +*/ +static int iscleared (global_State *g, const GCObject *o) { + if (o == NULL) return 0; /* non-collectable value */ + else if (novariant(o->tt) == LUA_TSTRING) { + markobject(g, o); /* strings are 'values', so are never weak */ + return 0; + } + else return iswhite(o); +} + + +/* +** Barrier that moves collector forward, that is, marks the white object +** 'v' being pointed by the black object 'o'. In the generational +** mode, 'v' must also become old, if 'o' is old; however, it cannot +** be changed directly to OLD, because it may still point to non-old +** objects. So, it is marked as OLD0. In the next cycle it will become +** OLD1, and in the next it will finally become OLD (regular old). By +** then, any object it points to will also be old. If called in the +** incremental sweep phase, it clears the black object to white (sweep +** it) to avoid other barrier calls for this same object. (That cannot +** be done is generational mode, as its sweep does not distinguish +** whites from deads.) +*/ +void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { + global_State *g = G(L); + lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); + if (keepinvariant(g)) { /* must keep invariant? */ + reallymarkobject(g, v); /* restore invariant */ + if (isold(o)) { + lua_assert(!isold(v)); /* white object could not be old */ + setage(v, G_OLD0); /* restore generational invariant */ + } + } + else { /* sweep phase */ + lua_assert(issweepphase(g)); + if (g->gckind == KGC_INC) /* incremental mode? */ + makewhite(g, o); /* mark 'o' as white to avoid other barriers */ + } +} + + +/* +** barrier that moves collector backward, that is, mark the black object +** pointing to a white object as gray again. +*/ +void luaC_barrierback_ (lua_State *L, GCObject *o) { + global_State *g = G(L); + lua_assert(isblack(o) && !isdead(g, o)); + lua_assert((g->gckind == KGC_GEN) == (isold(o) && getage(o) != G_TOUCHED1)); + if (getage(o) == G_TOUCHED2) /* already in gray list? */ + set2gray(o); /* make it gray to become touched1 */ + else /* link it in 'grayagain' and paint it gray */ + linkobjgclist(o, g->grayagain); + if (isold(o)) /* generational mode? */ + setage(o, G_TOUCHED1); /* touched in current cycle */ +} + + +void luaC_fix (lua_State *L, GCObject *o) { + global_State *g = G(L); + lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */ + set2gray(o); /* they will be gray forever */ + setage(o, G_OLD); /* and old forever */ + g->allgc = o->next; /* remove object from 'allgc' list */ + o->next = g->fixedgc; /* link it to 'fixedgc' list */ + g->fixedgc = o; +} + + +/* +** create a new collectable object (with given type, size, and offset) +** and link it to 'allgc' list. +*/ +GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz, size_t offset) { + global_State *g = G(L); + char *p = cast_charp(luaM_newobject(L, novariant(tt), sz)); + GCObject *o = cast(GCObject *, p + offset); + o->marked = luaC_white(g); + o->tt = tt; + o->next = g->allgc; + g->allgc = o; + return o; +} + + +GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { + return luaC_newobjdt(L, tt, sz, 0); +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Mark functions +** ======================================================= +*/ + + +/* +** Mark an object. Userdata with no user values, strings, and closed +** upvalues are visited and turned black here. Open upvalues are +** already indirectly linked through their respective threads in the +** 'twups' list, so they don't go to the gray list; nevertheless, they +** are kept gray to avoid barriers, as their values will be revisited +** by the thread or by 'remarkupvals'. Other objects are added to the +** gray list to be visited (and turned black) later. Both userdata and +** upvalues can call this function recursively, but this recursion goes +** for at most two levels: An upvalue cannot refer to another upvalue +** (only closures can), and a userdata's metatable must be a table. +*/ +static void reallymarkobject (global_State *g, GCObject *o) { + switch (o->tt) { + case LUA_VSHRSTR: + case LUA_VLNGSTR: { + set2black(o); /* nothing to visit */ + break; + } + case LUA_VUPVAL: { + UpVal *uv = gco2upv(o); + if (upisopen(uv)) + set2gray(uv); /* open upvalues are kept gray */ + else + set2black(uv); /* closed upvalues are visited here */ + markvalue(g, uv->v.p); /* mark its content */ + break; + } + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + if (u->nuvalue == 0) { /* no user values? */ + markobjectN(g, u->metatable); /* mark its metatable */ + set2black(u); /* nothing else to mark */ + break; + } + /* else... */ + } /* FALLTHROUGH */ + case LUA_VLCL: case LUA_VCCL: case LUA_VTABLE: + case LUA_VTHREAD: case LUA_VPROTO: { + linkobjgclist(o, g->gray); /* to be visited later */ + break; + } + default: lua_assert(0); break; + } +} + + +/* +** mark metamethods for basic types +*/ +static void markmt (global_State *g) { + int i; + for (i=0; i < LUA_NUMTAGS; i++) + markobjectN(g, g->mt[i]); +} + + +/* +** mark all objects in list of being-finalized +*/ +static lu_mem markbeingfnz (global_State *g) { + GCObject *o; + lu_mem count = 0; + for (o = g->tobefnz; o != NULL; o = o->next) { + count++; + markobject(g, o); + } + return count; +} + + +/* +** For each non-marked thread, simulates a barrier between each open +** upvalue and its value. (If the thread is collected, the value will be +** assigned to the upvalue, but then it can be too late for the barrier +** to act. The "barrier" does not need to check colors: A non-marked +** thread must be young; upvalues cannot be older than their threads; so +** any visited upvalue must be young too.) Also removes the thread from +** the list, as it was already visited. Removes also threads with no +** upvalues, as they have nothing to be checked. (If the thread gets an +** upvalue later, it will be linked in the list again.) +*/ +static int remarkupvals (global_State *g) { + lua_State *thread; + lua_State **p = &g->twups; + int work = 0; /* estimate of how much work was done here */ + while ((thread = *p) != NULL) { + work++; + if (!iswhite(thread) && thread->openupval != NULL) + p = &thread->twups; /* keep marked thread with upvalues in the list */ + else { /* thread is not marked or without upvalues */ + UpVal *uv; + lua_assert(!isold(thread) || thread->openupval == NULL); + *p = thread->twups; /* remove thread from the list */ + thread->twups = thread; /* mark that it is out of list */ + for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) { + lua_assert(getage(uv) <= getage(thread)); + work++; + if (!iswhite(uv)) { /* upvalue already visited? */ + lua_assert(upisopen(uv) && isgray(uv)); + markvalue(g, uv->v.p); /* mark its value */ + } + } + } + } + return work; +} + + +static void cleargraylists (global_State *g) { + g->gray = g->grayagain = NULL; + g->weak = g->allweak = g->ephemeron = NULL; +} + + +/* +** mark root set and reset all gray lists, to start a new collection +*/ +static void restartcollection (global_State *g) { + cleargraylists(g); + markobject(g, g->mainthread); + markvalue(g, &g->l_registry); + markmt(g); + markbeingfnz(g); /* mark any finalizing object left from previous cycle */ +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Traverse functions +** ======================================================= +*/ + + +/* +** Check whether object 'o' should be kept in the 'grayagain' list for +** post-processing by 'correctgraylist'. (It could put all old objects +** in the list and leave all the work to 'correctgraylist', but it is +** more efficient to avoid adding elements that will be removed.) Only +** TOUCHED1 objects need to be in the list. TOUCHED2 doesn't need to go +** back to a gray list, but then it must become OLD. (That is what +** 'correctgraylist' does when it finds a TOUCHED2 object.) +*/ +static void genlink (global_State *g, GCObject *o) { + lua_assert(isblack(o)); + if (getage(o) == G_TOUCHED1) { /* touched in this cycle? */ + linkobjgclist(o, g->grayagain); /* link it back in 'grayagain' */ + } /* everything else do not need to be linked back */ + else if (getage(o) == G_TOUCHED2) + changeage(o, G_TOUCHED2, G_OLD); /* advance age */ +} + + +/* +** Traverse a table with weak values and link it to proper list. During +** propagate phase, keep it in 'grayagain' list, to be revisited in the +** atomic phase. In the atomic phase, if table has any white value, +** put it in 'weak' list, to be cleared. +*/ +static void traverseweakvalue (global_State *g, Table *h) { + Node *n, *limit = gnodelast(h); + /* if there is array part, assume it may have white values (it is not + worth traversing it now just to check) */ + int hasclears = (h->alimit > 0); + for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ + else { + lua_assert(!keyisnil(n)); + markkey(g, n); + if (!hasclears && iscleared(g, gcvalueN(gval(n)))) /* a white value? */ + hasclears = 1; /* table will have to be cleared */ + } + } + if (g->gcstate == GCSatomic && hasclears) + linkgclist(h, g->weak); /* has to be cleared later */ + else + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ +} + + +/* +** Traverse an ephemeron table and link it to proper list. Returns true +** iff any object was marked during this traversal (which implies that +** convergence has to continue). During propagation phase, keep table +** in 'grayagain' list, to be visited again in the atomic phase. In +** the atomic phase, if table has any white->white entry, it has to +** be revisited during ephemeron convergence (as that key may turn +** black). Otherwise, if it has any white key, table has to be cleared +** (in the atomic phase). In generational mode, some tables +** must be kept in some gray list for post-processing; this is done +** by 'genlink'. +*/ +static int traverseephemeron (global_State *g, Table *h, int inv) { + int marked = 0; /* true if an object is marked in this traversal */ + int hasclears = 0; /* true if table has white keys */ + int hasww = 0; /* true if table has entry "white-key -> white-value" */ + unsigned int i; + unsigned int asize = luaH_realasize(h); + unsigned int nsize = sizenode(h); + /* traverse array part */ + for (i = 0; i < asize; i++) { + if (valiswhite(&h->array[i])) { + marked = 1; + reallymarkobject(g, gcvalue(&h->array[i])); + } + } + /* traverse hash part; if 'inv', traverse descending + (see 'convergeephemerons') */ + for (i = 0; i < nsize; i++) { + Node *n = inv ? gnode(h, nsize - 1 - i) : gnode(h, i); + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ + else if (iscleared(g, gckeyN(n))) { /* key is not marked (yet)? */ + hasclears = 1; /* table must be cleared */ + if (valiswhite(gval(n))) /* value not marked yet? */ + hasww = 1; /* white-white entry */ + } + else if (valiswhite(gval(n))) { /* value not marked yet? */ + marked = 1; + reallymarkobject(g, gcvalue(gval(n))); /* mark it now */ + } + } + /* link table into proper list */ + if (g->gcstate == GCSpropagate) + linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ + else if (hasww) /* table has white->white entries? */ + linkgclist(h, g->ephemeron); /* have to propagate again */ + else if (hasclears) /* table has white keys? */ + linkgclist(h, g->allweak); /* may have to clean white keys */ + else + genlink(g, obj2gco(h)); /* check whether collector still needs to see it */ + return marked; +} + + +static void traversestrongtable (global_State *g, Table *h) { + Node *n, *limit = gnodelast(h); + unsigned int i; + unsigned int asize = luaH_realasize(h); + for (i = 0; i < asize; i++) /* traverse array part */ + markvalue(g, &h->array[i]); + for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */ + if (isempty(gval(n))) /* entry is empty? */ + clearkey(n); /* clear its key */ + else { + lua_assert(!keyisnil(n)); + markkey(g, n); + markvalue(g, gval(n)); + } + } + genlink(g, obj2gco(h)); +} + + +static lu_mem traversetable (global_State *g, Table *h) { + const char *weakkey, *weakvalue; + const TValue *mode = gfasttm(g, h->metatable, TM_MODE); + markobjectN(g, h->metatable); + if (mode && ttisstring(mode) && /* is there a weak mode? */ + (cast_void(weakkey = strchr(svalue(mode), 'k')), + cast_void(weakvalue = strchr(svalue(mode), 'v')), + (weakkey || weakvalue))) { /* is really weak? */ + if (!weakkey) /* strong keys? */ + traverseweakvalue(g, h); + else if (!weakvalue) /* strong values? */ + traverseephemeron(g, h, 0); + else /* all weak */ + linkgclist(h, g->allweak); /* nothing to traverse now */ + } + else /* not weak */ + traversestrongtable(g, h); + return 1 + h->alimit + 2 * allocsizenode(h); +} + + +static int traverseudata (global_State *g, Udata *u) { + int i; + markobjectN(g, u->metatable); /* mark its metatable */ + for (i = 0; i < u->nuvalue; i++) + markvalue(g, &u->uv[i].uv); + genlink(g, obj2gco(u)); + return 1 + u->nuvalue; +} + + +/* +** Traverse a prototype. (While a prototype is being build, its +** arrays can be larger than needed; the extra slots are filled with +** NULL, so the use of 'markobjectN') +*/ +static int traverseproto (global_State *g, Proto *f) { + int i; + markobjectN(g, f->source); + for (i = 0; i < f->sizek; i++) /* mark literals */ + markvalue(g, &f->k[i]); + for (i = 0; i < f->sizeupvalues; i++) /* mark upvalue names */ + markobjectN(g, f->upvalues[i].name); + for (i = 0; i < f->sizep; i++) /* mark nested protos */ + markobjectN(g, f->p[i]); + for (i = 0; i < f->sizelocvars; i++) /* mark local-variable names */ + markobjectN(g, f->locvars[i].varname); + return 1 + f->sizek + f->sizeupvalues + f->sizep + f->sizelocvars; +} + + +static int traverseCclosure (global_State *g, CClosure *cl) { + int i; + for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */ + markvalue(g, &cl->upvalue[i]); + return 1 + cl->nupvalues; +} + +/* +** Traverse a Lua closure, marking its prototype and its upvalues. +** (Both can be NULL while closure is being created.) +*/ +static int traverseLclosure (global_State *g, LClosure *cl) { + int i; + markobjectN(g, cl->p); /* mark its prototype */ + for (i = 0; i < cl->nupvalues; i++) { /* visit its upvalues */ + UpVal *uv = cl->upvals[i]; + markobjectN(g, uv); /* mark upvalue */ + } + return 1 + cl->nupvalues; +} + + +/* +** Traverse a thread, marking the elements in the stack up to its top +** and cleaning the rest of the stack in the final traversal. That +** ensures that the entire stack have valid (non-dead) objects. +** Threads have no barriers. In gen. mode, old threads must be visited +** at every cycle, because they might point to young objects. In inc. +** mode, the thread can still be modified before the end of the cycle, +** and therefore it must be visited again in the atomic phase. To ensure +** these visits, threads must return to a gray list if they are not new +** (which can only happen in generational mode) or if the traverse is in +** the propagate phase (which can only happen in incremental mode). +*/ +static int traversethread (global_State *g, lua_State *th) { + UpVal *uv; + StkId o = th->stack.p; + if (isold(th) || g->gcstate == GCSpropagate) + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ + if (o == NULL) + return 1; /* stack not completely built yet */ + lua_assert(g->gcstate == GCSatomic || + th->openupval == NULL || isintwups(th)); + for (; o < th->top.p; o++) /* mark live elements in the stack */ + markvalue(g, s2v(o)); + for (uv = th->openupval; uv != NULL; uv = uv->u.open.next) + markobject(g, uv); /* open upvalues cannot be collected */ + if (g->gcstate == GCSatomic) { /* final traversal? */ + for (; o < th->stack_last.p + EXTRA_STACK; o++) + setnilvalue(s2v(o)); /* clear dead stack slice */ + /* 'remarkupvals' may have removed thread from 'twups' list */ + if (!isintwups(th) && th->openupval != NULL) { + th->twups = g->twups; /* link it back to the list */ + g->twups = th; + } + } + else if (!g->gcemergency) + luaD_shrinkstack(th); /* do not change stack in emergency cycle */ + return 1 + stacksize(th); +} + + +/* +** traverse one gray object, turning it to black. +*/ +static lu_mem propagatemark (global_State *g) { + GCObject *o = g->gray; + nw2black(o); + g->gray = *getgclist(o); /* remove from 'gray' list */ + switch (o->tt) { + case LUA_VTABLE: return traversetable(g, gco2t(o)); + case LUA_VUSERDATA: return traverseudata(g, gco2u(o)); + case LUA_VLCL: return traverseLclosure(g, gco2lcl(o)); + case LUA_VCCL: return traverseCclosure(g, gco2ccl(o)); + case LUA_VPROTO: return traverseproto(g, gco2p(o)); + case LUA_VTHREAD: return traversethread(g, gco2th(o)); + default: lua_assert(0); return 0; + } +} + + +static lu_mem propagateall (global_State *g) { + lu_mem tot = 0; + while (g->gray) + tot += propagatemark(g); + return tot; +} + + +/* +** Traverse all ephemeron tables propagating marks from keys to values. +** Repeat until it converges, that is, nothing new is marked. 'dir' +** inverts the direction of the traversals, trying to speed up +** convergence on chains in the same table. +** +*/ +static void convergeephemerons (global_State *g) { + int changed; + int dir = 0; + do { + GCObject *w; + GCObject *next = g->ephemeron; /* get ephemeron list */ + g->ephemeron = NULL; /* tables may return to this list when traversed */ + changed = 0; + while ((w = next) != NULL) { /* for each ephemeron table */ + Table *h = gco2t(w); + next = h->gclist; /* list is rebuilt during loop */ + nw2black(h); /* out of the list (for now) */ + if (traverseephemeron(g, h, dir)) { /* marked some value? */ + propagateall(g); /* propagate changes */ + changed = 1; /* will have to revisit all ephemeron tables */ + } + } + dir = !dir; /* invert direction next time */ + } while (changed); /* repeat until no more changes */ +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Sweep Functions +** ======================================================= +*/ + + +/* +** clear entries with unmarked keys from all weaktables in list 'l' +*/ +static void clearbykeys (global_State *g, GCObject *l) { + for (; l; l = gco2t(l)->gclist) { + Table *h = gco2t(l); + Node *limit = gnodelast(h); + Node *n; + for (n = gnode(h, 0); n < limit; n++) { + if (iscleared(g, gckeyN(n))) /* unmarked key? */ + setempty(gval(n)); /* remove entry */ + if (isempty(gval(n))) /* is entry empty? */ + clearkey(n); /* clear its key */ + } + } +} + + +/* +** clear entries with unmarked values from all weaktables in list 'l' up +** to element 'f' +*/ +static void clearbyvalues (global_State *g, GCObject *l, GCObject *f) { + for (; l != f; l = gco2t(l)->gclist) { + Table *h = gco2t(l); + Node *n, *limit = gnodelast(h); + unsigned int i; + unsigned int asize = luaH_realasize(h); + for (i = 0; i < asize; i++) { + TValue *o = &h->array[i]; + if (iscleared(g, gcvalueN(o))) /* value was collected? */ + setempty(o); /* remove entry */ + } + for (n = gnode(h, 0); n < limit; n++) { + if (iscleared(g, gcvalueN(gval(n)))) /* unmarked value? */ + setempty(gval(n)); /* remove entry */ + if (isempty(gval(n))) /* is entry empty? */ + clearkey(n); /* clear its key */ + } + } +} + + +static void freeupval (lua_State *L, UpVal *uv) { + if (upisopen(uv)) + luaF_unlinkupval(uv); + luaM_free(L, uv); +} + + +static void freeobj (lua_State *L, GCObject *o) { + switch (o->tt) { + case LUA_VPROTO: + luaF_freeproto(L, gco2p(o)); + break; + case LUA_VUPVAL: + freeupval(L, gco2upv(o)); + break; + case LUA_VLCL: { + LClosure *cl = gco2lcl(o); + luaM_freemem(L, cl, sizeLclosure(cl->nupvalues)); + break; + } + case LUA_VCCL: { + CClosure *cl = gco2ccl(o); + luaM_freemem(L, cl, sizeCclosure(cl->nupvalues)); + break; + } + case LUA_VTABLE: + luaH_free(L, gco2t(o)); + break; + case LUA_VTHREAD: + luaE_freethread(L, gco2th(o)); + break; + case LUA_VUSERDATA: { + Udata *u = gco2u(o); + luaM_freemem(L, o, sizeudata(u->nuvalue, u->len)); + break; + } + case LUA_VSHRSTR: { + TString *ts = gco2ts(o); + luaS_remove(L, ts); /* remove it from hash table */ + luaM_freemem(L, ts, sizelstring(ts->shrlen)); + break; + } + case LUA_VLNGSTR: { + TString *ts = gco2ts(o); + luaM_freemem(L, ts, sizelstring(ts->u.lnglen)); + break; + } + default: lua_assert(0); + } +} + + +/* +** sweep at most 'countin' elements from a list of GCObjects erasing dead +** objects, where a dead object is one marked with the old (non current) +** white; change all non-dead objects back to white, preparing for next +** collection cycle. Return where to continue the traversal or NULL if +** list is finished. ('*countout' gets the number of elements traversed.) +*/ +static GCObject **sweeplist (lua_State *L, GCObject **p, int countin, + int *countout) { + global_State *g = G(L); + int ow = otherwhite(g); + int i; + int white = luaC_white(g); /* current white */ + for (i = 0; *p != NULL && i < countin; i++) { + GCObject *curr = *p; + int marked = curr->marked; + if (isdeadm(ow, marked)) { /* is 'curr' dead? */ + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* change mark to 'white' */ + curr->marked = cast_byte((marked & ~maskgcbits) | white); + p = &curr->next; /* go to next element */ + } + } + if (countout) + *countout = i; /* number of elements traversed */ + return (*p == NULL) ? NULL : p; +} + + +/* +** sweep a list until a live object (or end of list) +*/ +static GCObject **sweeptolive (lua_State *L, GCObject **p) { + GCObject **old = p; + do { + p = sweeplist(L, p, 1, NULL); + } while (p == old); + return p; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Finalization +** ======================================================= +*/ + +/* +** If possible, shrink string table. +*/ +static void checkSizes (lua_State *L, global_State *g) { + if (!g->gcemergency) { + if (g->strt.nuse < g->strt.size / 4) { /* string table too big? */ + l_mem olddebt = g->GCdebt; + luaS_resize(L, g->strt.size / 2); + g->GCestimate += g->GCdebt - olddebt; /* correct estimate */ + } + } +} + + +/* +** Get the next udata to be finalized from the 'tobefnz' list, and +** link it back into the 'allgc' list. +*/ +static GCObject *udata2finalize (global_State *g) { + GCObject *o = g->tobefnz; /* get first element */ + lua_assert(tofinalize(o)); + g->tobefnz = o->next; /* remove it from 'tobefnz' list */ + o->next = g->allgc; /* return it to 'allgc' list */ + g->allgc = o; + resetbit(o->marked, FINALIZEDBIT); /* object is "normal" again */ + if (issweepphase(g)) + makewhite(g, o); /* "sweep" object */ + else if (getage(o) == G_OLD1) + g->firstold1 = o; /* it is the first OLD1 object in the list */ + return o; +} + + +static void dothecall (lua_State *L, void *ud) { + UNUSED(ud); + luaD_callnoyield(L, L->top.p - 2, 0); +} + + +static void GCTM (lua_State *L) { + global_State *g = G(L); + const TValue *tm; + TValue v; + lua_assert(!g->gcemergency); + setgcovalue(L, &v, udata2finalize(g)); + tm = luaT_gettmbyobj(L, &v, TM_GC); + if (!notm(tm)) { /* is there a finalizer? */ + int status; + lu_byte oldah = L->allowhook; + int oldgcstp = g->gcstp; + g->gcstp |= GCSTPGC; /* avoid GC steps */ + L->allowhook = 0; /* stop debug hooks during GC metamethod */ + setobj2s(L, L->top.p++, tm); /* push finalizer... */ + setobj2s(L, L->top.p++, &v); /* ... and its argument */ + L->ci->callstatus |= CIST_FIN; /* will run a finalizer */ + status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top.p - 2), 0); + L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ + L->allowhook = oldah; /* restore hooks */ + g->gcstp = oldgcstp; /* restore state */ + if (l_unlikely(status != LUA_OK)) { /* error while running __gc? */ + luaE_warnerror(L, "__gc"); + L->top.p--; /* pops error object */ + } + } +} + + +/* +** Call a few finalizers +*/ +static int runafewfinalizers (lua_State *L, int n) { + global_State *g = G(L); + int i; + for (i = 0; i < n && g->tobefnz; i++) + GCTM(L); /* call one finalizer */ + return i; +} + + +/* +** call all pending finalizers +*/ +static void callallpendingfinalizers (lua_State *L) { + global_State *g = G(L); + while (g->tobefnz) + GCTM(L); +} + + +/* +** find last 'next' field in list 'p' list (to add elements in its end) +*/ +static GCObject **findlast (GCObject **p) { + while (*p != NULL) + p = &(*p)->next; + return p; +} + + +/* +** Move all unreachable objects (or 'all' objects) that need +** finalization from list 'finobj' to list 'tobefnz' (to be finalized). +** (Note that objects after 'finobjold1' cannot be white, so they +** don't need to be traversed. In incremental mode, 'finobjold1' is NULL, +** so the whole list is traversed.) +*/ +static void separatetobefnz (global_State *g, int all) { + GCObject *curr; + GCObject **p = &g->finobj; + GCObject **lastnext = findlast(&g->tobefnz); + while ((curr = *p) != g->finobjold1) { /* traverse all finalizable objects */ + lua_assert(tofinalize(curr)); + if (!(iswhite(curr) || all)) /* not being collected? */ + p = &curr->next; /* don't bother with it */ + else { + if (curr == g->finobjsur) /* removing 'finobjsur'? */ + g->finobjsur = curr->next; /* correct it */ + *p = curr->next; /* remove 'curr' from 'finobj' list */ + curr->next = *lastnext; /* link at the end of 'tobefnz' list */ + *lastnext = curr; + lastnext = &curr->next; + } + } +} + + +/* +** If pointer 'p' points to 'o', move it to the next element. +*/ +static void checkpointer (GCObject **p, GCObject *o) { + if (o == *p) + *p = o->next; +} + + +/* +** Correct pointers to objects inside 'allgc' list when +** object 'o' is being removed from the list. +*/ +static void correctpointers (global_State *g, GCObject *o) { + checkpointer(&g->survival, o); + checkpointer(&g->old1, o); + checkpointer(&g->reallyold, o); + checkpointer(&g->firstold1, o); +} + + +/* +** if object 'o' has a finalizer, remove it from 'allgc' list (must +** search the list to find it) and link it in 'finobj' list. +*/ +void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { + global_State *g = G(L); + if (tofinalize(o) || /* obj. is already marked... */ + gfasttm(g, mt, TM_GC) == NULL || /* or has no finalizer... */ + (g->gcstp & GCSTPCLS)) /* or closing state? */ + return; /* nothing to be done */ + else { /* move 'o' to 'finobj' list */ + GCObject **p; + if (issweepphase(g)) { + makewhite(g, o); /* "sweep" object 'o' */ + if (g->sweepgc == &o->next) /* should not remove 'sweepgc' object */ + g->sweepgc = sweeptolive(L, g->sweepgc); /* change 'sweepgc' */ + } + else + correctpointers(g, o); + /* search for pointer pointing to 'o' */ + for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ } + *p = o->next; /* remove 'o' from 'allgc' list */ + o->next = g->finobj; /* link it in 'finobj' list */ + g->finobj = o; + l_setbit(o->marked, FINALIZEDBIT); /* mark it as such */ + } +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Generational Collector +** ======================================================= +*/ + + +/* +** Set the "time" to wait before starting a new GC cycle; cycle will +** start when memory use hits the threshold of ('estimate' * pause / +** PAUSEADJ). (Division by 'estimate' should be OK: it cannot be zero, +** because Lua cannot even start with less than PAUSEADJ bytes). +*/ +static void setpause (global_State *g) { + l_mem threshold, debt; + int pause = getgcparam(g->gcpause); + l_mem estimate = g->GCestimate / PAUSEADJ; /* adjust 'estimate' */ + lua_assert(estimate > 0); + threshold = (pause < MAX_LMEM / estimate) /* overflow? */ + ? estimate * pause /* no overflow */ + : MAX_LMEM; /* overflow; truncate to maximum */ + debt = gettotalbytes(g) - threshold; + if (debt > 0) debt = 0; + luaE_setdebt(g, debt); +} + + +/* +** Sweep a list of objects to enter generational mode. Deletes dead +** objects and turns the non dead to old. All non-dead threads---which +** are now old---must be in a gray list. Everything else is not in a +** gray list. Open upvalues are also kept gray. +*/ +static void sweep2old (lua_State *L, GCObject **p) { + GCObject *curr; + global_State *g = G(L); + while ((curr = *p) != NULL) { + if (iswhite(curr)) { /* is 'curr' dead? */ + lua_assert(isdead(g, curr)); + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* all surviving objects become old */ + setage(curr, G_OLD); + if (curr->tt == LUA_VTHREAD) { /* threads must be watched */ + lua_State *th = gco2th(curr); + linkgclist(th, g->grayagain); /* insert into 'grayagain' list */ + } + else if (curr->tt == LUA_VUPVAL && upisopen(gco2upv(curr))) + set2gray(curr); /* open upvalues are always gray */ + else /* everything else is black */ + nw2black(curr); + p = &curr->next; /* go to next element */ + } + } +} + + +/* +** Sweep for generational mode. Delete dead objects. (Because the +** collection is not incremental, there are no "new white" objects +** during the sweep. So, any white object must be dead.) For +** non-dead objects, advance their ages and clear the color of +** new objects. (Old objects keep their colors.) +** The ages of G_TOUCHED1 and G_TOUCHED2 objects cannot be advanced +** here, because these old-generation objects are usually not swept +** here. They will all be advanced in 'correctgraylist'. That function +** will also remove objects turned white here from any gray list. +*/ +static GCObject **sweepgen (lua_State *L, global_State *g, GCObject **p, + GCObject *limit, GCObject **pfirstold1) { + static const lu_byte nextage[] = { + G_SURVIVAL, /* from G_NEW */ + G_OLD1, /* from G_SURVIVAL */ + G_OLD1, /* from G_OLD0 */ + G_OLD, /* from G_OLD1 */ + G_OLD, /* from G_OLD (do not change) */ + G_TOUCHED1, /* from G_TOUCHED1 (do not change) */ + G_TOUCHED2 /* from G_TOUCHED2 (do not change) */ + }; + int white = luaC_white(g); + GCObject *curr; + while ((curr = *p) != limit) { + if (iswhite(curr)) { /* is 'curr' dead? */ + lua_assert(!isold(curr) && isdead(g, curr)); + *p = curr->next; /* remove 'curr' from list */ + freeobj(L, curr); /* erase 'curr' */ + } + else { /* correct mark and age */ + if (getage(curr) == G_NEW) { /* new objects go back to white */ + int marked = curr->marked & ~maskgcbits; /* erase GC bits */ + curr->marked = cast_byte(marked | G_SURVIVAL | white); + } + else { /* all other objects will be old, and so keep their color */ + setage(curr, nextage[getage(curr)]); + if (getage(curr) == G_OLD1 && *pfirstold1 == NULL) + *pfirstold1 = curr; /* first OLD1 object in the list */ + } + p = &curr->next; /* go to next element */ + } + } + return p; +} + + +/* +** Traverse a list making all its elements white and clearing their +** age. In incremental mode, all objects are 'new' all the time, +** except for fixed strings (which are always old). +*/ +static void whitelist (global_State *g, GCObject *p) { + int white = luaC_white(g); + for (; p != NULL; p = p->next) + p->marked = cast_byte((p->marked & ~maskgcbits) | white); +} + + +/* +** Correct a list of gray objects. Return pointer to where rest of the +** list should be linked. +** Because this correction is done after sweeping, young objects might +** be turned white and still be in the list. They are only removed. +** 'TOUCHED1' objects are advanced to 'TOUCHED2' and remain on the list; +** Non-white threads also remain on the list; 'TOUCHED2' objects become +** regular old; they and anything else are removed from the list. +*/ +static GCObject **correctgraylist (GCObject **p) { + GCObject *curr; + while ((curr = *p) != NULL) { + GCObject **next = getgclist(curr); + if (iswhite(curr)) + goto remove; /* remove all white objects */ + else if (getage(curr) == G_TOUCHED1) { /* touched in this cycle? */ + lua_assert(isgray(curr)); + nw2black(curr); /* make it black, for next barrier */ + changeage(curr, G_TOUCHED1, G_TOUCHED2); + goto remain; /* keep it in the list and go to next element */ + } + else if (curr->tt == LUA_VTHREAD) { + lua_assert(isgray(curr)); + goto remain; /* keep non-white threads on the list */ + } + else { /* everything else is removed */ + lua_assert(isold(curr)); /* young objects should be white here */ + if (getage(curr) == G_TOUCHED2) /* advance from TOUCHED2... */ + changeage(curr, G_TOUCHED2, G_OLD); /* ... to OLD */ + nw2black(curr); /* make object black (to be removed) */ + goto remove; + } + remove: *p = *next; continue; + remain: p = next; continue; + } + return p; +} + + +/* +** Correct all gray lists, coalescing them into 'grayagain'. +*/ +static void correctgraylists (global_State *g) { + GCObject **list = correctgraylist(&g->grayagain); + *list = g->weak; g->weak = NULL; + list = correctgraylist(list); + *list = g->allweak; g->allweak = NULL; + list = correctgraylist(list); + *list = g->ephemeron; g->ephemeron = NULL; + correctgraylist(list); +} + + +/* +** Mark black 'OLD1' objects when starting a new young collection. +** Gray objects are already in some gray list, and so will be visited +** in the atomic step. +*/ +static void markold (global_State *g, GCObject *from, GCObject *to) { + GCObject *p; + for (p = from; p != to; p = p->next) { + if (getage(p) == G_OLD1) { + lua_assert(!iswhite(p)); + changeage(p, G_OLD1, G_OLD); /* now they are old */ + if (isblack(p)) + reallymarkobject(g, p); + } + } +} + + +/* +** Finish a young-generation collection. +*/ +static void finishgencycle (lua_State *L, global_State *g) { + correctgraylists(g); + checkSizes(L, g); + g->gcstate = GCSpropagate; /* skip restart */ + if (!g->gcemergency) + callallpendingfinalizers(L); +} + + +/* +** Does a young collection. First, mark 'OLD1' objects. Then does the +** atomic step. Then, sweep all lists and advance pointers. Finally, +** finish the collection. +*/ +static void youngcollection (lua_State *L, global_State *g) { + GCObject **psurvival; /* to point to first non-dead survival object */ + GCObject *dummy; /* dummy out parameter to 'sweepgen' */ + lua_assert(g->gcstate == GCSpropagate); + if (g->firstold1) { /* are there regular OLD1 objects? */ + markold(g, g->firstold1, g->reallyold); /* mark them */ + g->firstold1 = NULL; /* no more OLD1 objects (for now) */ + } + markold(g, g->finobj, g->finobjrold); + markold(g, g->tobefnz, NULL); + atomic(L); + + /* sweep nursery and get a pointer to its last live element */ + g->gcstate = GCSswpallgc; + psurvival = sweepgen(L, g, &g->allgc, g->survival, &g->firstold1); + /* sweep 'survival' */ + sweepgen(L, g, psurvival, g->old1, &g->firstold1); + g->reallyold = g->old1; + g->old1 = *psurvival; /* 'survival' survivals are old now */ + g->survival = g->allgc; /* all news are survivals */ + + /* repeat for 'finobj' lists */ + dummy = NULL; /* no 'firstold1' optimization for 'finobj' lists */ + psurvival = sweepgen(L, g, &g->finobj, g->finobjsur, &dummy); + /* sweep 'survival' */ + sweepgen(L, g, psurvival, g->finobjold1, &dummy); + g->finobjrold = g->finobjold1; + g->finobjold1 = *psurvival; /* 'survival' survivals are old now */ + g->finobjsur = g->finobj; /* all news are survivals */ + + sweepgen(L, g, &g->tobefnz, NULL, &dummy); + finishgencycle(L, g); +} + + +/* +** Clears all gray lists, sweeps objects, and prepare sublists to enter +** generational mode. The sweeps remove dead objects and turn all +** surviving objects to old. Threads go back to 'grayagain'; everything +** else is turned black (not in any gray list). +*/ +static void atomic2gen (lua_State *L, global_State *g) { + cleargraylists(g); + /* sweep all elements making them old */ + g->gcstate = GCSswpallgc; + sweep2old(L, &g->allgc); + /* everything alive now is old */ + g->reallyold = g->old1 = g->survival = g->allgc; + g->firstold1 = NULL; /* there are no OLD1 objects anywhere */ + + /* repeat for 'finobj' lists */ + sweep2old(L, &g->finobj); + g->finobjrold = g->finobjold1 = g->finobjsur = g->finobj; + + sweep2old(L, &g->tobefnz); + + g->gckind = KGC_GEN; + g->lastatomic = 0; + g->GCestimate = gettotalbytes(g); /* base for memory control */ + finishgencycle(L, g); +} + + +/* +** Set debt for the next minor collection, which will happen when +** memory grows 'genminormul'%. +*/ +static void setminordebt (global_State *g) { + luaE_setdebt(g, -(cast(l_mem, (gettotalbytes(g) / 100)) * g->genminormul)); +} + + +/* +** Enter generational mode. Must go until the end of an atomic cycle +** to ensure that all objects are correctly marked and weak tables +** are cleared. Then, turn all objects into old and finishes the +** collection. +*/ +static lu_mem entergen (lua_State *L, global_State *g) { + lu_mem numobjs; + luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */ + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + numobjs = atomic(L); /* propagates all and then do the atomic stuff */ + atomic2gen(L, g); + setminordebt(g); /* set debt assuming next cycle will be minor */ + return numobjs; +} + + +/* +** Enter incremental mode. Turn all objects white, make all +** intermediate lists point to NULL (to avoid invalid pointers), +** and go to the pause state. +*/ +static void enterinc (global_State *g) { + whitelist(g, g->allgc); + g->reallyold = g->old1 = g->survival = NULL; + whitelist(g, g->finobj); + whitelist(g, g->tobefnz); + g->finobjrold = g->finobjold1 = g->finobjsur = NULL; + g->gcstate = GCSpause; + g->gckind = KGC_INC; + g->lastatomic = 0; +} + + +/* +** Change collector mode to 'newmode'. +*/ +void luaC_changemode (lua_State *L, int newmode) { + global_State *g = G(L); + if (newmode != g->gckind) { + if (newmode == KGC_GEN) /* entering generational mode? */ + entergen(L, g); + else + enterinc(g); /* entering incremental mode */ + } + g->lastatomic = 0; +} + + +/* +** Does a full collection in generational mode. +*/ +static lu_mem fullgen (lua_State *L, global_State *g) { + enterinc(g); + return entergen(L, g); +} + + +/* +** Does a major collection after last collection was a "bad collection". +** +** When the program is building a big structure, it allocates lots of +** memory but generates very little garbage. In those scenarios, +** the generational mode just wastes time doing small collections, and +** major collections are frequently what we call a "bad collection", a +** collection that frees too few objects. To avoid the cost of switching +** between generational mode and the incremental mode needed for full +** (major) collections, the collector tries to stay in incremental mode +** after a bad collection, and to switch back to generational mode only +** after a "good" collection (one that traverses less than 9/8 objects +** of the previous one). +** The collector must choose whether to stay in incremental mode or to +** switch back to generational mode before sweeping. At this point, it +** does not know the real memory in use, so it cannot use memory to +** decide whether to return to generational mode. Instead, it uses the +** number of objects traversed (returned by 'atomic') as a proxy. The +** field 'g->lastatomic' keeps this count from the last collection. +** ('g->lastatomic != 0' also means that the last collection was bad.) +*/ +static void stepgenfull (lua_State *L, global_State *g) { + lu_mem newatomic; /* count of traversed objects */ + lu_mem lastatomic = g->lastatomic; /* count from last collection */ + if (g->gckind == KGC_GEN) /* still in generational mode? */ + enterinc(g); /* enter incremental mode */ + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + newatomic = atomic(L); /* mark everybody */ + if (newatomic < lastatomic + (lastatomic >> 3)) { /* good collection? */ + atomic2gen(L, g); /* return to generational mode */ + setminordebt(g); + } + else { /* another bad collection; stay in incremental mode */ + g->GCestimate = gettotalbytes(g); /* first estimate */; + entersweep(L); + luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + setpause(g); + g->lastatomic = newatomic; + } +} + + +/* +** Does a generational "step". +** Usually, this means doing a minor collection and setting the debt to +** make another collection when memory grows 'genminormul'% larger. +** +** However, there are exceptions. If memory grows 'genmajormul'% +** larger than it was at the end of the last major collection (kept +** in 'g->GCestimate'), the function does a major collection. At the +** end, it checks whether the major collection was able to free a +** decent amount of memory (at least half the growth in memory since +** previous major collection). If so, the collector keeps its state, +** and the next collection will probably be minor again. Otherwise, +** we have what we call a "bad collection". In that case, set the field +** 'g->lastatomic' to signal that fact, so that the next collection will +** go to 'stepgenfull'. +** +** 'GCdebt <= 0' means an explicit call to GC step with "size" zero; +** in that case, do a minor collection. +*/ +static void genstep (lua_State *L, global_State *g) { + if (g->lastatomic != 0) /* last collection was a bad one? */ + stepgenfull(L, g); /* do a full step */ + else { + lu_mem majorbase = g->GCestimate; /* memory after last major collection */ + lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul); + if (g->GCdebt > 0 && gettotalbytes(g) > majorbase + majorinc) { + lu_mem numobjs = fullgen(L, g); /* do a major collection */ + if (gettotalbytes(g) < majorbase + (majorinc / 2)) { + /* collected at least half of memory growth since last major + collection; keep doing minor collections. */ + lua_assert(g->lastatomic == 0); + } + else { /* bad collection */ + g->lastatomic = numobjs; /* signal that last collection was bad */ + setpause(g); /* do a long wait for next (major) collection */ + } + } + else { /* regular case; do a minor collection */ + youngcollection(L, g); + setminordebt(g); + g->GCestimate = majorbase; /* preserve base value */ + } + } + lua_assert(isdecGCmodegen(g)); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** GC control +** ======================================================= +*/ + + +/* +** Enter first sweep phase. +** The call to 'sweeptolive' makes the pointer point to an object +** inside the list (instead of to the header), so that the real sweep do +** not need to skip objects created between "now" and the start of the +** real sweep. +*/ +static void entersweep (lua_State *L) { + global_State *g = G(L); + g->gcstate = GCSswpallgc; + lua_assert(g->sweepgc == NULL); + g->sweepgc = sweeptolive(L, &g->allgc); +} + + +/* +** Delete all objects in list 'p' until (but not including) object +** 'limit'. +*/ +static void deletelist (lua_State *L, GCObject *p, GCObject *limit) { + while (p != limit) { + GCObject *next = p->next; + freeobj(L, p); + p = next; + } +} + + +/* +** Call all finalizers of the objects in the given Lua state, and +** then free all objects, except for the main thread. +*/ +void luaC_freeallobjects (lua_State *L) { + global_State *g = G(L); + g->gcstp = GCSTPCLS; /* no extra finalizers after here */ + luaC_changemode(L, KGC_INC); + separatetobefnz(g, 1); /* separate all objects with finalizers */ + lua_assert(g->finobj == NULL); + callallpendingfinalizers(L); + deletelist(L, g->allgc, obj2gco(g->mainthread)); + lua_assert(g->finobj == NULL); /* no new finalizers */ + deletelist(L, g->fixedgc, NULL); /* collect fixed objects */ + lua_assert(g->strt.nuse == 0); +} + + +static lu_mem atomic (lua_State *L) { + global_State *g = G(L); + lu_mem work = 0; + GCObject *origweak, *origall; + GCObject *grayagain = g->grayagain; /* save original list */ + g->grayagain = NULL; + lua_assert(g->ephemeron == NULL && g->weak == NULL); + lua_assert(!iswhite(g->mainthread)); + g->gcstate = GCSatomic; + markobject(g, L); /* mark running thread */ + /* registry and global metatables may be changed by API */ + markvalue(g, &g->l_registry); + markmt(g); /* mark global metatables */ + work += propagateall(g); /* empties 'gray' list */ + /* remark occasional upvalues of (maybe) dead threads */ + work += remarkupvals(g); + work += propagateall(g); /* propagate changes */ + g->gray = grayagain; + work += propagateall(g); /* traverse 'grayagain' list */ + convergeephemerons(g); + /* at this point, all strongly accessible objects are marked. */ + /* Clear values from weak tables, before checking finalizers */ + clearbyvalues(g, g->weak, NULL); + clearbyvalues(g, g->allweak, NULL); + origweak = g->weak; origall = g->allweak; + separatetobefnz(g, 0); /* separate objects to be finalized */ + work += markbeingfnz(g); /* mark objects that will be finalized */ + work += propagateall(g); /* remark, to propagate 'resurrection' */ + convergeephemerons(g); + /* at this point, all resurrected objects are marked. */ + /* remove dead objects from weak tables */ + clearbykeys(g, g->ephemeron); /* clear keys from all ephemeron tables */ + clearbykeys(g, g->allweak); /* clear keys from all 'allweak' tables */ + /* clear values from resurrected weak tables */ + clearbyvalues(g, g->weak, origweak); + clearbyvalues(g, g->allweak, origall); + luaS_clearcache(g); + g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ + lua_assert(g->gray == NULL); + return work; /* estimate of slots marked by 'atomic' */ +} + + +static int sweepstep (lua_State *L, global_State *g, + int nextstate, GCObject **nextlist) { + if (g->sweepgc) { + l_mem olddebt = g->GCdebt; + int count; + g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX, &count); + g->GCestimate += g->GCdebt - olddebt; /* update estimate */ + return count; + } + else { /* enter next state */ + g->gcstate = nextstate; + g->sweepgc = nextlist; + return 0; /* no work done */ + } +} + + +static lu_mem singlestep (lua_State *L) { + global_State *g = G(L); + lu_mem work; + lua_assert(!g->gcstopem); /* collector is not reentrant */ + g->gcstopem = 1; /* no emergency collections while collecting */ + switch (g->gcstate) { + case GCSpause: { + restartcollection(g); + g->gcstate = GCSpropagate; + work = 1; + break; + } + case GCSpropagate: { + if (g->gray == NULL) { /* no more gray objects? */ + g->gcstate = GCSenteratomic; /* finish propagate phase */ + work = 0; + } + else + work = propagatemark(g); /* traverse one gray object */ + break; + } + case GCSenteratomic: { + work = atomic(L); /* work is what was traversed by 'atomic' */ + entersweep(L); + g->GCestimate = gettotalbytes(g); /* first estimate */; + break; + } + case GCSswpallgc: { /* sweep "regular" objects */ + work = sweepstep(L, g, GCSswpfinobj, &g->finobj); + break; + } + case GCSswpfinobj: { /* sweep objects with finalizers */ + work = sweepstep(L, g, GCSswptobefnz, &g->tobefnz); + break; + } + case GCSswptobefnz: { /* sweep objects to be finalized */ + work = sweepstep(L, g, GCSswpend, NULL); + break; + } + case GCSswpend: { /* finish sweeps */ + checkSizes(L, g); + g->gcstate = GCScallfin; + work = 0; + break; + } + case GCScallfin: { /* call remaining finalizers */ + if (g->tobefnz && !g->gcemergency) { + g->gcstopem = 0; /* ok collections during finalizers */ + work = runafewfinalizers(L, GCFINMAX) * GCFINALIZECOST; + } + else { /* emergency mode or no more finalizers */ + g->gcstate = GCSpause; /* finish collection */ + work = 0; + } + break; + } + default: lua_assert(0); return 0; + } + g->gcstopem = 0; + return work; +} + + +/* +** advances the garbage collector until it reaches a state allowed +** by 'statemask' +*/ +void luaC_runtilstate (lua_State *L, int statesmask) { + global_State *g = G(L); + while (!testbit(statesmask, g->gcstate)) + singlestep(L); +} + + + +/* +** Performs a basic incremental step. The debt and step size are +** converted from bytes to "units of work"; then the function loops +** running single steps until adding that many units of work or +** finishing a cycle (pause state). Finally, it sets the debt that +** controls when next step will be performed. +*/ +static void incstep (lua_State *L, global_State *g) { + int stepmul = (getgcparam(g->gcstepmul) | 1); /* avoid division by 0 */ + l_mem debt = (g->GCdebt / WORK2MEM) * stepmul; + l_mem stepsize = (g->gcstepsize <= log2maxs(l_mem)) + ? ((cast(l_mem, 1) << g->gcstepsize) / WORK2MEM) * stepmul + : MAX_LMEM; /* overflow; keep maximum value */ + do { /* repeat until pause or enough "credit" (negative debt) */ + lu_mem work = singlestep(L); /* perform one single step */ + debt -= work; + } while (debt > -stepsize && g->gcstate != GCSpause); + if (g->gcstate == GCSpause) + setpause(g); /* pause until next cycle */ + else { + debt = (debt / stepmul) * WORK2MEM; /* convert 'work units' to bytes */ + luaE_setdebt(g, debt); + } +} + +/* +** Performs a basic GC step if collector is running. (If collector is +** not running, set a reasonable debt to avoid it being called at +** every single check.) +*/ +void luaC_step (lua_State *L) { + global_State *g = G(L); + if (!gcrunning(g)) /* not running? */ + luaE_setdebt(g, -2000); + else { + if(isdecGCmodegen(g)) + genstep(L, g); + else + incstep(L, g); + } +} + + +/* +** Perform a full collection in incremental mode. +** Before running the collection, check 'keepinvariant'; if it is true, +** there may be some objects marked as black, so the collector has +** to sweep all objects to turn them back to white (as white has not +** changed, nothing will be collected). +*/ +static void fullinc (lua_State *L, global_State *g) { + if (keepinvariant(g)) /* black objects? */ + entersweep(L); /* sweep everything to turn them back to white */ + /* finish any pending sweep phase to start a new cycle */ + luaC_runtilstate(L, bitmask(GCSpause)); + luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ + /* estimate must be correct after a full GC cycle */ + lua_assert(g->GCestimate == gettotalbytes(g)); + luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + setpause(g); +} + + +/* +** Performs a full GC cycle; if 'isemergency', set a flag to avoid +** some operations which could change the interpreter state in some +** unexpected ways (running finalizers and shrinking some structures). +*/ +void luaC_fullgc (lua_State *L, int isemergency) { + global_State *g = G(L); + lua_assert(!g->gcemergency); + g->gcemergency = isemergency; /* set flag */ + if (g->gckind == KGC_INC) + fullinc(L, g); + else + fullgen(L, g); + g->gcemergency = 0; +} + +/* }====================================================== */ + + diff --git a/arm9/source/lua/lgc.h b/arm9/source/lua/lgc.h new file mode 100644 index 000000000..538f6edcc --- /dev/null +++ b/arm9/source/lua/lgc.h @@ -0,0 +1,202 @@ +/* +** $Id: lgc.h $ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#ifndef lgc_h +#define lgc_h + + +#include "lobject.h" +#include "lstate.h" + +/* +** Collectable objects may have one of three colors: white, which means +** the object is not marked; gray, which means the object is marked, but +** its references may be not marked; and black, which means that the +** object and all its references are marked. The main invariant of the +** garbage collector, while marking objects, is that a black object can +** never point to a white one. Moreover, any gray object must be in a +** "gray list" (gray, grayagain, weak, allweak, ephemeron) so that it +** can be visited again before finishing the collection cycle. (Open +** upvalues are an exception to this rule.) These lists have no meaning +** when the invariant is not being enforced (e.g., sweep phase). +*/ + + +/* +** Possible states of the Garbage Collector +*/ +#define GCSpropagate 0 +#define GCSenteratomic 1 +#define GCSatomic 2 +#define GCSswpallgc 3 +#define GCSswpfinobj 4 +#define GCSswptobefnz 5 +#define GCSswpend 6 +#define GCScallfin 7 +#define GCSpause 8 + + +#define issweepphase(g) \ + (GCSswpallgc <= (g)->gcstate && (g)->gcstate <= GCSswpend) + + +/* +** macro to tell when main invariant (white objects cannot point to black +** ones) must be kept. During a collection, the sweep +** phase may break the invariant, as objects turned white may point to +** still-black objects. The invariant is restored when sweep ends and +** all objects are white again. +*/ + +#define keepinvariant(g) ((g)->gcstate <= GCSatomic) + + +/* +** some useful bit tricks +*/ +#define resetbits(x,m) ((x) &= cast_byte(~(m))) +#define setbits(x,m) ((x) |= (m)) +#define testbits(x,m) ((x) & (m)) +#define bitmask(b) (1<<(b)) +#define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2)) +#define l_setbit(x,b) setbits(x, bitmask(b)) +#define resetbit(x,b) resetbits(x, bitmask(b)) +#define testbit(x,b) testbits(x, bitmask(b)) + + +/* +** Layout for bit use in 'marked' field. First three bits are +** used for object "age" in generational mode. Last bit is used +** by tests. +*/ +#define WHITE0BIT 3 /* object is white (type 0) */ +#define WHITE1BIT 4 /* object is white (type 1) */ +#define BLACKBIT 5 /* object is black */ +#define FINALIZEDBIT 6 /* object has been marked for finalization */ + +#define TESTBIT 7 + + + +#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) + + +#define iswhite(x) testbits((x)->marked, WHITEBITS) +#define isblack(x) testbit((x)->marked, BLACKBIT) +#define isgray(x) /* neither white nor black */ \ + (!testbits((x)->marked, WHITEBITS | bitmask(BLACKBIT))) + +#define tofinalize(x) testbit((x)->marked, FINALIZEDBIT) + +#define otherwhite(g) ((g)->currentwhite ^ WHITEBITS) +#define isdeadm(ow,m) ((m) & (ow)) +#define isdead(g,v) isdeadm(otherwhite(g), (v)->marked) + +#define changewhite(x) ((x)->marked ^= WHITEBITS) +#define nw2black(x) \ + check_exp(!iswhite(x), l_setbit((x)->marked, BLACKBIT)) + +#define luaC_white(g) cast_byte((g)->currentwhite & WHITEBITS) + + +/* object age in generational mode */ +#define G_NEW 0 /* created in current cycle */ +#define G_SURVIVAL 1 /* created in previous cycle */ +#define G_OLD0 2 /* marked old by frw. barrier in this cycle */ +#define G_OLD1 3 /* first full cycle as old */ +#define G_OLD 4 /* really old object (not to be visited) */ +#define G_TOUCHED1 5 /* old object touched this cycle */ +#define G_TOUCHED2 6 /* old object touched in previous cycle */ + +#define AGEBITS 7 /* all age bits (111) */ + +#define getage(o) ((o)->marked & AGEBITS) +#define setage(o,a) ((o)->marked = cast_byte(((o)->marked & (~AGEBITS)) | a)) +#define isold(o) (getage(o) > G_SURVIVAL) + +#define changeage(o,f,t) \ + check_exp(getage(o) == (f), (o)->marked ^= ((f)^(t))) + + +/* Default Values for GC parameters */ +#define LUAI_GENMAJORMUL 100 +#define LUAI_GENMINORMUL 20 + +/* wait memory to double before starting new cycle */ +#define LUAI_GCPAUSE 200 + +/* +** some gc parameters are stored divided by 4 to allow a maximum value +** up to 1023 in a 'lu_byte'. +*/ +#define getgcparam(p) ((p) * 4) +#define setgcparam(p,v) ((p) = (v) / 4) + +#define LUAI_GCMUL 100 + +/* how much to allocate before next GC step (log2) */ +#define LUAI_GCSTEPSIZE 13 /* 8 KB */ + + +/* +** Check whether the declared GC mode is generational. While in +** generational mode, the collector can go temporarily to incremental +** mode to improve performance. This is signaled by 'g->lastatomic != 0'. +*/ +#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0) + + +/* +** Control when GC is running: +*/ +#define GCSTPUSR 1 /* bit true when GC stopped by user */ +#define GCSTPGC 2 /* bit true when GC stopped by itself */ +#define GCSTPCLS 4 /* bit true when closing Lua state */ +#define gcrunning(g) ((g)->gcstp == 0) + + +/* +** Does one step of collection when debt becomes positive. 'pre'/'pos' +** allows some adjustments to be done only when needed. macro +** 'condchangemem' is used only for heavy tests (forcing a full +** GC cycle on every opportunity) +*/ +#define luaC_condGC(L,pre,pos) \ + { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \ + condchangemem(L,pre,pos); } + +/* more often than not, 'pre'/'pos' are empty */ +#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0) + + +#define luaC_objbarrier(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? \ + luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) + +#define luaC_barrier(L,p,v) ( \ + iscollectable(v) ? luaC_objbarrier(L,p,gcvalue(v)) : cast_void(0)) + +#define luaC_objbarrierback(L,p,o) ( \ + (isblack(p) && iswhite(o)) ? luaC_barrierback_(L,p) : cast_void(0)) + +#define luaC_barrierback(L,p,v) ( \ + iscollectable(v) ? luaC_objbarrierback(L, p, gcvalue(v)) : cast_void(0)) + +LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); +LUAI_FUNC void luaC_freeallobjects (lua_State *L); +LUAI_FUNC void luaC_step (lua_State *L); +LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask); +LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); +LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); +LUAI_FUNC GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz, + size_t offset); +LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); +LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); +LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); +LUAI_FUNC void luaC_changemode (lua_State *L, int newmode); + + +#endif diff --git a/arm9/source/lua/linit.c b/arm9/source/lua/linit.c new file mode 100644 index 000000000..69808f84f --- /dev/null +++ b/arm9/source/lua/linit.c @@ -0,0 +1,65 @@ +/* +** $Id: linit.c $ +** Initialization of libraries for lua.c and other clients +** See Copyright Notice in lua.h +*/ + + +#define linit_c +#define LUA_LIB + +/* +** If you embed Lua in your program and need to open the standard +** libraries, call luaL_openlibs in your program. If you need a +** different set of libraries, copy this file to your project and edit +** it to suit your needs. +** +** You can also *preload* libraries, so that a later 'require' can +** open the library, which is already linked to the application. +** For that, do the following code: +** +** luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); +** lua_pushcfunction(L, luaopen_modname); +** lua_setfield(L, -2, modname); +** lua_pop(L, 1); // remove PRELOAD table +*/ + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "lualib.h" +#include "lauxlib.h" + + +/* +** these libs are loaded by lua.c and are readily available to any Lua +** program +*/ +static const luaL_Reg loadedlibs[] = { + {LUA_GNAME, luaopen_base}, + {LUA_LOADLIBNAME, luaopen_package}, + {LUA_COLIBNAME, luaopen_coroutine}, + {LUA_TABLIBNAME, luaopen_table}, + {LUA_IOLIBNAME, luaopen_io}, + {LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_UTF8LIBNAME, luaopen_utf8}, + {LUA_DBLIBNAME, luaopen_debug}, + {NULL, NULL} +}; + + +LUALIB_API void luaL_openlibs (lua_State *L) { + const luaL_Reg *lib; + /* "require" functions from 'loadedlibs' and set results to global table */ + for (lib = loadedlibs; lib->func; lib++) { + luaL_requiref(L, lib->name, lib->func, 1); + lua_pop(L, 1); /* remove lib */ + } +} + diff --git a/arm9/source/lua/liolib.c b/arm9/source/lua/liolib.c new file mode 100644 index 000000000..b08397da4 --- /dev/null +++ b/arm9/source/lua/liolib.c @@ -0,0 +1,828 @@ +/* +** $Id: liolib.c $ +** Standard I/O (and system) library +** See Copyright Notice in lua.h +*/ + +#define liolib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + + + +/* +** Change this macro to accept other modes for 'fopen' besides +** the standard ones. +*/ +#if !defined(l_checkmode) + +/* accepted extensions to 'mode' in 'fopen' */ +#if !defined(L_MODEEXT) +#define L_MODEEXT "b" +#endif + +/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ +static int l_checkmode (const char *mode) { + return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && + (*mode != '+' || ((void)(++mode), 1)) && /* skip if char is '+' */ + (strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */ +} + +#endif + +/* +** {====================================================== +** l_popen spawns a new process connected to the current +** one through the file streams. +** ======================================================= +*/ + +#if !defined(l_popen) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#define l_popen(L,c,m) (fflush(NULL), popen(c,m)) +#define l_pclose(L,file) (pclose(file)) + +#elif defined(LUA_USE_WINDOWS) /* }{ */ + +#define l_popen(L,c,m) (_popen(c,m)) +#define l_pclose(L,file) (_pclose(file)) + +#if !defined(l_checkmodep) +/* Windows accepts "[rw][bt]?" as valid modes */ +#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && \ + (m[1] == '\0' || ((m[1] == 'b' || m[1] == 't') && m[2] == '\0'))) +#endif + +#else /* }{ */ + +/* ISO C definitions */ +#define l_popen(L,c,m) \ + ((void)c, (void)m, \ + luaL_error(L, "'popen' not supported"), \ + (FILE*)0) +#define l_pclose(L,file) ((void)L, (void)file, -1) + +#endif /* } */ + +#endif /* } */ + + +#if !defined(l_checkmodep) +/* By default, Lua accepts only "r" or "w" as valid modes */ +#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && m[1] == '\0') +#endif + +/* }====================================================== */ + + +#if !defined(l_getc) /* { */ + +#if defined(LUA_USE_POSIX) +#define l_getc(f) getc_unlocked(f) +#define l_lockfile(f) flockfile(f) +#define l_unlockfile(f) funlockfile(f) +#else +#define l_getc(f) getc(f) +#define l_lockfile(f) ((void)0) +#define l_unlockfile(f) ((void)0) +#endif + +#endif /* } */ + + +/* +** {====================================================== +** l_fseek: configuration for longer offsets +** ======================================================= +*/ + +#if !defined(l_fseek) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#include + +#define l_fseek(f,o,w) fseeko(f,o,w) +#define l_ftell(f) ftello(f) +#define l_seeknum off_t + +#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \ + && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ + +/* Windows (but not DDK) and Visual C++ 2005 or higher */ +#define l_fseek(f,o,w) _fseeki64(f,o,w) +#define l_ftell(f) _ftelli64(f) +#define l_seeknum __int64 + +#else /* }{ */ + +/* ISO C definitions */ +#define l_fseek(f,o,w) fseek(f,o,w) +#define l_ftell(f) ftell(f) +#define l_seeknum long + +#endif /* } */ + +#endif /* } */ + +/* }====================================================== */ + + + +#define IO_PREFIX "_IO_" +#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1) +#define IO_INPUT (IO_PREFIX "input") +#define IO_OUTPUT (IO_PREFIX "output") + + +typedef luaL_Stream LStream; + + +#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE)) + +#define isclosed(p) ((p)->closef == NULL) + + +static int io_type (lua_State *L) { + LStream *p; + luaL_checkany(L, 1); + p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE); + if (p == NULL) + luaL_pushfail(L); /* not a file */ + else if (isclosed(p)) + lua_pushliteral(L, "closed file"); + else + lua_pushliteral(L, "file"); + return 1; +} + + +static int f_tostring (lua_State *L) { + LStream *p = tolstream(L); + if (isclosed(p)) + lua_pushliteral(L, "file (closed)"); + else + lua_pushfstring(L, "file (%p)", p->f); + return 1; +} + + +static FILE *tofile (lua_State *L) { + LStream *p = tolstream(L); + if (l_unlikely(isclosed(p))) + luaL_error(L, "attempt to use a closed file"); + lua_assert(p->f); + return p->f; +} + + +/* +** When creating file handles, always creates a 'closed' file handle +** before opening the actual file; so, if there is a memory error, the +** handle is in a consistent state. +*/ +static LStream *newprefile (lua_State *L) { + LStream *p = (LStream *)lua_newuserdatauv(L, sizeof(LStream), 0); + p->closef = NULL; /* mark file handle as 'closed' */ + luaL_setmetatable(L, LUA_FILEHANDLE); + return p; +} + + +/* +** Calls the 'close' function from a file handle. The 'volatile' avoids +** a bug in some versions of the Clang compiler (e.g., clang 3.0 for +** 32 bits). +*/ +static int aux_close (lua_State *L) { + LStream *p = tolstream(L); + volatile lua_CFunction cf = p->closef; + p->closef = NULL; /* mark stream as closed */ + return (*cf)(L); /* close it */ +} + + +static int f_close (lua_State *L) { + tofile(L); /* make sure argument is an open stream */ + return aux_close(L); +} + + +static int io_close (lua_State *L) { + if (lua_isnone(L, 1)) /* no argument? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use default output */ + return f_close(L); +} + + +static int f_gc (lua_State *L) { + LStream *p = tolstream(L); + if (!isclosed(p) && p->f != NULL) + aux_close(L); /* ignore closed and incompletely open files */ + return 0; +} + + +/* +** function to close regular files +*/ +static int io_fclose (lua_State *L) { + LStream *p = tolstream(L); + int res = fclose(p->f); + return luaL_fileresult(L, (res == 0), NULL); +} + + +static LStream *newfile (lua_State *L) { + LStream *p = newprefile(L); + p->f = NULL; + p->closef = &io_fclose; + return p; +} + + +static void opencheck (lua_State *L, const char *fname, const char *mode) { + LStream *p = newfile(L); + p->f = fopen(fname, mode); + if (l_unlikely(p->f == NULL)) + luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno)); +} + + +static int io_open (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + LStream *p = newfile(L); + const char *md = mode; /* to traverse/check mode */ + luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); + p->f = fopen(filename, mode); + return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; +} + + +/* +** function to close 'popen' files +*/ +static int io_pclose (lua_State *L) { + LStream *p = tolstream(L); + errno = 0; + return luaL_execresult(L, l_pclose(L, p->f)); +} + + +static int io_popen (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + LStream *p = newprefile(L); + luaL_argcheck(L, l_checkmodep(mode), 2, "invalid mode"); + p->f = l_popen(L, filename, mode); + p->closef = &io_pclose; + return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; +} + + +static int io_tmpfile (lua_State *L) { + LStream *p = newfile(L); + p->f = tmpfile(); + return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1; +} + + +static FILE *getiofile (lua_State *L, const char *findex) { + LStream *p; + lua_getfield(L, LUA_REGISTRYINDEX, findex); + p = (LStream *)lua_touserdata(L, -1); + if (l_unlikely(isclosed(p))) + luaL_error(L, "default %s file is closed", findex + IOPREF_LEN); + return p->f; +} + + +static int g_iofile (lua_State *L, const char *f, const char *mode) { + if (!lua_isnoneornil(L, 1)) { + const char *filename = lua_tostring(L, 1); + if (filename) + opencheck(L, filename, mode); + else { + tofile(L); /* check that it's a valid file handle */ + lua_pushvalue(L, 1); + } + lua_setfield(L, LUA_REGISTRYINDEX, f); + } + /* return current value */ + lua_getfield(L, LUA_REGISTRYINDEX, f); + return 1; +} + + +static int io_input (lua_State *L) { + return g_iofile(L, IO_INPUT, "r"); +} + + +static int io_output (lua_State *L) { + return g_iofile(L, IO_OUTPUT, "w"); +} + + +static int io_readline (lua_State *L); + + +/* +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit +** in the limit for upvalues of a closure) +*/ +#define MAXARGLINE 250 + +/* +** Auxiliary function to create the iteration function for 'lines'. +** The iteration function is a closure over 'io_readline', with +** the following upvalues: +** 1) The file being read (first value in the stack) +** 2) the number of arguments to read +** 3) a boolean, true iff file has to be closed when finished ('toclose') +** *) a variable number of format arguments (rest of the stack) +*/ +static void aux_lines (lua_State *L, int toclose) { + int n = lua_gettop(L) - 1; /* number of arguments to read */ + luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); + lua_pushvalue(L, 1); /* file */ + lua_pushinteger(L, n); /* number of arguments to read */ + lua_pushboolean(L, toclose); /* close/not close file when finished */ + lua_rotate(L, 2, 3); /* move the three values to their positions */ + lua_pushcclosure(L, io_readline, 3 + n); +} + + +static int f_lines (lua_State *L) { + tofile(L); /* check that it's a valid file handle */ + aux_lines(L, 0); + return 1; +} + + +/* +** Return an iteration function for 'io.lines'. If file has to be +** closed, also returns the file itself as a second result (to be +** closed as the state at the exit of a generic for). +*/ +static int io_lines (lua_State *L) { + int toclose; + if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ + if (lua_isnil(L, 1)) { /* no file name? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT); /* get default input */ + lua_replace(L, 1); /* put it at index 1 */ + tofile(L); /* check that it's a valid file handle */ + toclose = 0; /* do not close it after iteration */ + } + else { /* open a new file */ + const char *filename = luaL_checkstring(L, 1); + opencheck(L, filename, "r"); + lua_replace(L, 1); /* put file at index 1 */ + toclose = 1; /* close it after iteration */ + } + aux_lines(L, toclose); /* push iteration function */ + if (toclose) { + lua_pushnil(L); /* state */ + lua_pushnil(L); /* control */ + lua_pushvalue(L, 1); /* file is the to-be-closed variable (4th result) */ + return 4; + } + else + return 1; +} + + +/* +** {====================================================== +** READ +** ======================================================= +*/ + + +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + + +/* auxiliary structure used by 'read_number' */ +typedef struct { + FILE *f; /* file being read */ + int c; /* current character (look ahead) */ + int n; /* number of elements in buffer 'buff' */ + char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ +} RN; + + +/* +** Add current char to buffer (if not out of space) and read next one +*/ +static int nextc (RN *rn) { + if (l_unlikely(rn->n >= L_MAXLENNUM)) { /* buffer overflow? */ + rn->buff[0] = '\0'; /* invalidate result */ + return 0; /* fail */ + } + else { + rn->buff[rn->n++] = rn->c; /* save current char */ + rn->c = l_getc(rn->f); /* read next one */ + return 1; + } +} + + +/* +** Accept current char if it is in 'set' (of size 2) +*/ +static int test2 (RN *rn, const char *set) { + if (rn->c == set[0] || rn->c == set[1]) + return nextc(rn); + else return 0; +} + + +/* +** Read a sequence of (hex)digits +*/ +static int readdigits (RN *rn, int hex) { + int count = 0; + while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn)) + count++; + return count; +} + + +/* +** Read a number: first reads a valid prefix of a numeral into a buffer. +** Then it calls 'lua_stringtonumber' to check whether the format is +** correct and to convert it to a Lua number. +*/ +static int read_number (lua_State *L, FILE *f) { + RN rn; + int count = 0; + int hex = 0; + char decp[2]; + rn.f = f; rn.n = 0; + decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ + decp[1] = '.'; /* always accept a dot */ + l_lockfile(rn.f); + do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ + test2(&rn, "-+"); /* optional sign */ + if (test2(&rn, "00")) { + if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ + else count = 1; /* count initial '0' as a valid digit */ + } + count += readdigits(&rn, hex); /* integral part */ + if (test2(&rn, decp)) /* decimal point? */ + count += readdigits(&rn, hex); /* fractional part */ + if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */ + test2(&rn, "-+"); /* exponent sign */ + readdigits(&rn, 0); /* exponent digits */ + } + ungetc(rn.c, rn.f); /* unread look-ahead char */ + l_unlockfile(rn.f); + rn.buff[rn.n] = '\0'; /* finish string */ + if (l_likely(lua_stringtonumber(L, rn.buff))) + return 1; /* ok, it is a valid number */ + else { /* invalid format */ + lua_pushnil(L); /* "result" to be removed */ + return 0; /* read fails */ + } +} + + +static int test_eof (lua_State *L, FILE *f) { + int c = getc(f); + ungetc(c, f); /* no-op when c == EOF */ + lua_pushliteral(L, ""); + return (c != EOF); +} + + +static int read_line (lua_State *L, FILE *f, int chop) { + luaL_Buffer b; + int c; + luaL_buffinit(L, &b); + do { /* may need to read several chunks to get whole line */ + char *buff = luaL_prepbuffer(&b); /* preallocate buffer space */ + int i = 0; + l_lockfile(f); /* no memory errors can happen inside the lock */ + while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') + buff[i++] = c; /* read up to end of line or buffer limit */ + l_unlockfile(f); + luaL_addsize(&b, i); + } while (c != EOF && c != '\n'); /* repeat until end of line */ + if (!chop && c == '\n') /* want a newline and have one? */ + luaL_addchar(&b, c); /* add ending newline to result */ + luaL_pushresult(&b); /* close buffer */ + /* return ok if read something (either a newline or something else) */ + return (c == '\n' || lua_rawlen(L, -1) > 0); +} + + +static void read_all (lua_State *L, FILE *f) { + size_t nr; + luaL_Buffer b; + luaL_buffinit(L, &b); + do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ + char *p = luaL_prepbuffer(&b); + nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f); + luaL_addsize(&b, nr); + } while (nr == LUAL_BUFFERSIZE); + luaL_pushresult(&b); /* close buffer */ +} + + +static int read_chars (lua_State *L, FILE *f, size_t n) { + size_t nr; /* number of chars actually read */ + char *p; + luaL_Buffer b; + luaL_buffinit(L, &b); + p = luaL_prepbuffsize(&b, n); /* prepare buffer to read whole block */ + nr = fread(p, sizeof(char), n, f); /* try to read 'n' chars */ + luaL_addsize(&b, nr); + luaL_pushresult(&b); /* close buffer */ + return (nr > 0); /* true iff read something */ +} + + +static int g_read (lua_State *L, FILE *f, int first) { + int nargs = lua_gettop(L) - 1; + int n, success; + clearerr(f); + if (nargs == 0) { /* no arguments? */ + success = read_line(L, f, 1); + n = first + 1; /* to return 1 result */ + } + else { + /* ensure stack space for all results and for auxlib's buffer */ + luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); + success = 1; + for (n = first; nargs-- && success; n++) { + if (lua_type(L, n) == LUA_TNUMBER) { + size_t l = (size_t)luaL_checkinteger(L, n); + success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); + } + else { + const char *p = luaL_checkstring(L, n); + if (*p == '*') p++; /* skip optional '*' (for compatibility) */ + switch (*p) { + case 'n': /* number */ + success = read_number(L, f); + break; + case 'l': /* line */ + success = read_line(L, f, 1); + break; + case 'L': /* line with end-of-line */ + success = read_line(L, f, 0); + break; + case 'a': /* file */ + read_all(L, f); /* read entire file */ + success = 1; /* always success */ + break; + default: + return luaL_argerror(L, n, "invalid format"); + } + } + } + } + if (ferror(f)) + return luaL_fileresult(L, 0, NULL); + if (!success) { + lua_pop(L, 1); /* remove last result */ + luaL_pushfail(L); /* push nil instead */ + } + return n - first; +} + + +static int io_read (lua_State *L) { + return g_read(L, getiofile(L, IO_INPUT), 1); +} + + +static int f_read (lua_State *L) { + return g_read(L, tofile(L), 2); +} + + +/* +** Iteration function for 'lines'. +*/ +static int io_readline (lua_State *L) { + LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); + int i; + int n = (int)lua_tointeger(L, lua_upvalueindex(2)); + if (isclosed(p)) /* file is already closed? */ + return luaL_error(L, "file is already closed"); + lua_settop(L , 1); + luaL_checkstack(L, n, "too many arguments"); + for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ + lua_pushvalue(L, lua_upvalueindex(3 + i)); + n = g_read(L, p->f, 2); /* 'n' is number of results */ + lua_assert(n > 0); /* should return at least a nil */ + if (lua_toboolean(L, -n)) /* read at least one value? */ + return n; /* return them */ + else { /* first result is false: EOF or error */ + if (n > 1) { /* is there error information? */ + /* 2nd result is error message */ + return luaL_error(L, "%s", lua_tostring(L, -n + 1)); + } + if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ + lua_settop(L, 0); /* clear stack */ + lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */ + aux_close(L); /* close it */ + } + return 0; + } +} + +/* }====================================================== */ + + +static int g_write (lua_State *L, FILE *f, int arg) { + int nargs = lua_gettop(L) - arg; + int status = 1; + for (; nargs--; arg++) { + if (lua_type(L, arg) == LUA_TNUMBER) { + /* optimization: could be done exactly as for strings */ + int len = lua_isinteger(L, arg) + ? fprintf(f, LUA_INTEGER_FMT, + (LUAI_UACINT)lua_tointeger(L, arg)) + : fprintf(f, LUA_NUMBER_FMT, + (LUAI_UACNUMBER)lua_tonumber(L, arg)); + status = status && (len > 0); + } + else { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + status = status && (fwrite(s, sizeof(char), l, f) == l); + } + } + if (l_likely(status)) + return 1; /* file handle already on stack top */ + else return luaL_fileresult(L, status, NULL); +} + + +static int io_write (lua_State *L) { + return g_write(L, getiofile(L, IO_OUTPUT), 1); +} + + +static int f_write (lua_State *L) { + FILE *f = tofile(L); + lua_pushvalue(L, 1); /* push file at the stack top (to be returned) */ + return g_write(L, f, 2); +} + + +static int f_seek (lua_State *L) { + static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; + static const char *const modenames[] = {"set", "cur", "end", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, "cur", modenames); + lua_Integer p3 = luaL_optinteger(L, 3, 0); + l_seeknum offset = (l_seeknum)p3; + luaL_argcheck(L, (lua_Integer)offset == p3, 3, + "not an integer in proper range"); + op = l_fseek(f, offset, mode[op]); + if (l_unlikely(op)) + return luaL_fileresult(L, 0, NULL); /* error */ + else { + lua_pushinteger(L, (lua_Integer)l_ftell(f)); + return 1; + } +} + + +static int f_setvbuf (lua_State *L) { + static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; + static const char *const modenames[] = {"no", "full", "line", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, NULL, modenames); + lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); + int res = setvbuf(f, NULL, mode[op], (size_t)sz); + return luaL_fileresult(L, res == 0, NULL); +} + + + +static int io_flush (lua_State *L) { + return luaL_fileresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL); +} + + +static int f_flush (lua_State *L) { + return luaL_fileresult(L, fflush(tofile(L)) == 0, NULL); +} + + +/* +** functions for 'io' library +*/ +static const luaL_Reg iolib[] = { + {"close", io_close}, + {"flush", io_flush}, + {"input", io_input}, + {"lines", io_lines}, + {"open", io_open}, + {"output", io_output}, + {"popen", io_popen}, + {"read", io_read}, + {"tmpfile", io_tmpfile}, + {"type", io_type}, + {"write", io_write}, + {NULL, NULL} +}; + + +/* +** methods for file handles +*/ +static const luaL_Reg meth[] = { + {"read", f_read}, + {"write", f_write}, + {"lines", f_lines}, + {"flush", f_flush}, + {"seek", f_seek}, + {"close", f_close}, + {"setvbuf", f_setvbuf}, + {NULL, NULL} +}; + + +/* +** metamethods for file handles +*/ +static const luaL_Reg metameth[] = { + {"__index", NULL}, /* place holder */ + {"__gc", f_gc}, + {"__close", f_gc}, + {"__tostring", f_tostring}, + {NULL, NULL} +}; + + +static void createmeta (lua_State *L) { + luaL_newmetatable(L, LUA_FILEHANDLE); /* metatable for file handles */ + luaL_setfuncs(L, metameth, 0); /* add metamethods to new metatable */ + luaL_newlibtable(L, meth); /* create method table */ + luaL_setfuncs(L, meth, 0); /* add file methods to method table */ + lua_setfield(L, -2, "__index"); /* metatable.__index = method table */ + lua_pop(L, 1); /* pop metatable */ +} + + +/* +** function to (not) close the standard files stdin, stdout, and stderr +*/ +static int io_noclose (lua_State *L) { + LStream *p = tolstream(L); + p->closef = &io_noclose; /* keep file opened */ + luaL_pushfail(L); + lua_pushliteral(L, "cannot close standard file"); + return 2; +} + + +static void createstdfile (lua_State *L, FILE *f, const char *k, + const char *fname) { + LStream *p = newprefile(L); + p->f = f; + p->closef = &io_noclose; + if (k != NULL) { + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */ + } + lua_setfield(L, -2, fname); /* add file to module */ +} + + +LUAMOD_API int luaopen_io (lua_State *L) { + luaL_newlib(L, iolib); /* new module */ + createmeta(L); + /* create (and set) default files */ + createstdfile(L, stdin, IO_INPUT, "stdin"); + createstdfile(L, stdout, IO_OUTPUT, "stdout"); + createstdfile(L, stderr, NULL, "stderr"); + return 1; +} + diff --git a/arm9/source/lua/ljumptab.h b/arm9/source/lua/ljumptab.h new file mode 100644 index 000000000..8306f250c --- /dev/null +++ b/arm9/source/lua/ljumptab.h @@ -0,0 +1,112 @@ +/* +** $Id: ljumptab.h $ +** Jump Table for the Lua interpreter +** See Copyright Notice in lua.h +*/ + + +#undef vmdispatch +#undef vmcase +#undef vmbreak + +#define vmdispatch(x) goto *disptab[x]; + +#define vmcase(l) L_##l: + +#define vmbreak vmfetch(); vmdispatch(GET_OPCODE(i)); + + +static const void *const disptab[NUM_OPCODES] = { + +#if 0 +** you can update the following list with this command: +** +** sed -n '/^OP_/\!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h +** +#endif + +&&L_OP_MOVE, +&&L_OP_LOADI, +&&L_OP_LOADF, +&&L_OP_LOADK, +&&L_OP_LOADKX, +&&L_OP_LOADFALSE, +&&L_OP_LFALSESKIP, +&&L_OP_LOADTRUE, +&&L_OP_LOADNIL, +&&L_OP_GETUPVAL, +&&L_OP_SETUPVAL, +&&L_OP_GETTABUP, +&&L_OP_GETTABLE, +&&L_OP_GETI, +&&L_OP_GETFIELD, +&&L_OP_SETTABUP, +&&L_OP_SETTABLE, +&&L_OP_SETI, +&&L_OP_SETFIELD, +&&L_OP_NEWTABLE, +&&L_OP_SELF, +&&L_OP_ADDI, +&&L_OP_ADDK, +&&L_OP_SUBK, +&&L_OP_MULK, +&&L_OP_MODK, +&&L_OP_POWK, +&&L_OP_DIVK, +&&L_OP_IDIVK, +&&L_OP_BANDK, +&&L_OP_BORK, +&&L_OP_BXORK, +&&L_OP_SHRI, +&&L_OP_SHLI, +&&L_OP_ADD, +&&L_OP_SUB, +&&L_OP_MUL, +&&L_OP_MOD, +&&L_OP_POW, +&&L_OP_DIV, +&&L_OP_IDIV, +&&L_OP_BAND, +&&L_OP_BOR, +&&L_OP_BXOR, +&&L_OP_SHL, +&&L_OP_SHR, +&&L_OP_MMBIN, +&&L_OP_MMBINI, +&&L_OP_MMBINK, +&&L_OP_UNM, +&&L_OP_BNOT, +&&L_OP_NOT, +&&L_OP_LEN, +&&L_OP_CONCAT, +&&L_OP_CLOSE, +&&L_OP_TBC, +&&L_OP_JMP, +&&L_OP_EQ, +&&L_OP_LT, +&&L_OP_LE, +&&L_OP_EQK, +&&L_OP_EQI, +&&L_OP_LTI, +&&L_OP_LEI, +&&L_OP_GTI, +&&L_OP_GEI, +&&L_OP_TEST, +&&L_OP_TESTSET, +&&L_OP_CALL, +&&L_OP_TAILCALL, +&&L_OP_RETURN, +&&L_OP_RETURN0, +&&L_OP_RETURN1, +&&L_OP_FORLOOP, +&&L_OP_FORPREP, +&&L_OP_TFORPREP, +&&L_OP_TFORCALL, +&&L_OP_TFORLOOP, +&&L_OP_SETLIST, +&&L_OP_CLOSURE, +&&L_OP_VARARG, +&&L_OP_VARARGPREP, +&&L_OP_EXTRAARG + +}; diff --git a/arm9/source/lua/llex.c b/arm9/source/lua/llex.c new file mode 100644 index 000000000..5fc39a5cd --- /dev/null +++ b/arm9/source/lua/llex.c @@ -0,0 +1,581 @@ +/* +** $Id: llex.c $ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + +#define llex_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "lctype.h" +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "llex.h" +#include "lobject.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lzio.h" + + + +#define next(ls) (ls->current = zgetc(ls->z)) + + + +#define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') + + +/* ORDER RESERVED */ +static const char *const luaX_tokens [] = { + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "goto", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "//", "..", "...", "==", ">=", "<=", "~=", + "<<", ">>", "::", "", + "", "", "", "" +}; + + +#define save_and_next(ls) (save(ls, ls->current), next(ls)) + + +static l_noret lexerror (LexState *ls, const char *msg, int token); + + +static void save (LexState *ls, int c) { + Mbuffer *b = ls->buff; + if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) { + size_t newsize; + if (luaZ_sizebuffer(b) >= MAX_SIZE/2) + lexerror(ls, "lexical element too long", 0); + newsize = luaZ_sizebuffer(b) * 2; + luaZ_resizebuffer(ls->L, b, newsize); + } + b->buffer[luaZ_bufflen(b)++] = cast_char(c); +} + + +void luaX_init (lua_State *L) { + int i; + TString *e = luaS_newliteral(L, LUA_ENV); /* create env name */ + luaC_fix(L, obj2gco(e)); /* never collect this name */ + for (i=0; iextra = cast_byte(i+1); /* reserved word */ + } +} + + +const char *luaX_token2str (LexState *ls, int token) { + if (token < FIRST_RESERVED) { /* single-byte symbols? */ + if (lisprint(token)) + return luaO_pushfstring(ls->L, "'%c'", token); + else /* control character */ + return luaO_pushfstring(ls->L, "'<\\%d>'", token); + } + else { + const char *s = luaX_tokens[token - FIRST_RESERVED]; + if (token < TK_EOS) /* fixed format (symbols and reserved words)? */ + return luaO_pushfstring(ls->L, "'%s'", s); + else /* names, strings, and numerals */ + return s; + } +} + + +static const char *txtToken (LexState *ls, int token) { + switch (token) { + case TK_NAME: case TK_STRING: + case TK_FLT: case TK_INT: + save(ls, '\0'); + return luaO_pushfstring(ls->L, "'%s'", luaZ_buffer(ls->buff)); + default: + return luaX_token2str(ls, token); + } +} + + +static l_noret lexerror (LexState *ls, const char *msg, int token) { + msg = luaG_addinfo(ls->L, msg, ls->source, ls->linenumber); + if (token) + luaO_pushfstring(ls->L, "%s near %s", msg, txtToken(ls, token)); + luaD_throw(ls->L, LUA_ERRSYNTAX); +} + + +l_noret luaX_syntaxerror (LexState *ls, const char *msg) { + lexerror(ls, msg, ls->t.token); +} + + +/* +** Creates a new string and anchors it in scanner's table so that it +** will not be collected until the end of the compilation; by that time +** it should be anchored somewhere. It also internalizes long strings, +** ensuring there is only one copy of each unique string. The table +** here is used as a set: the string enters as the key, while its value +** is irrelevant. We use the string itself as the value only because it +** is a TValue readily available. Later, the code generation can change +** this value. +*/ +TString *luaX_newstring (LexState *ls, const char *str, size_t l) { + lua_State *L = ls->L; + TString *ts = luaS_newlstr(L, str, l); /* create new string */ + const TValue *o = luaH_getstr(ls->h, ts); + if (!ttisnil(o)) /* string already present? */ + ts = keystrval(nodefromval(o)); /* get saved copy */ + else { /* not in use yet */ + TValue *stv = s2v(L->top.p++); /* reserve stack space for string */ + setsvalue(L, stv, ts); /* temporarily anchor the string */ + luaH_finishset(L, ls->h, stv, o, stv); /* t[string] = string */ + /* table is not a metatable, so it does not need to invalidate cache */ + luaC_checkGC(L); + L->top.p--; /* remove string from stack */ + } + return ts; +} + + +/* +** increment line number and skips newline sequence (any of +** \n, \r, \n\r, or \r\n) +*/ +static void inclinenumber (LexState *ls) { + int old = ls->current; + lua_assert(currIsNewline(ls)); + next(ls); /* skip '\n' or '\r' */ + if (currIsNewline(ls) && ls->current != old) + next(ls); /* skip '\n\r' or '\r\n' */ + if (++ls->linenumber >= MAX_INT) + lexerror(ls, "chunk has too many lines", 0); +} + + +void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, + int firstchar) { + ls->t.token = 0; + ls->L = L; + ls->current = firstchar; + ls->lookahead.token = TK_EOS; /* no look-ahead token */ + ls->z = z; + ls->fs = NULL; + ls->linenumber = 1; + ls->lastline = 1; + ls->source = source; + ls->envn = luaS_newliteral(L, LUA_ENV); /* get env name */ + luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ +} + + + +/* +** ======================================================= +** LEXICAL ANALYZER +** ======================================================= +*/ + + +static int check_next1 (LexState *ls, int c) { + if (ls->current == c) { + next(ls); + return 1; + } + else return 0; +} + + +/* +** Check whether current char is in set 'set' (with two chars) and +** saves it +*/ +static int check_next2 (LexState *ls, const char *set) { + lua_assert(set[2] == '\0'); + if (ls->current == set[0] || ls->current == set[1]) { + save_and_next(ls); + return 1; + } + else return 0; +} + + +/* LUA_NUMBER */ +/* +** This function is quite liberal in what it accepts, as 'luaO_str2num' +** will reject ill-formed numerals. Roughly, it accepts the following +** pattern: +** +** %d(%x|%.|([Ee][+-]?))* | 0[Xx](%x|%.|([Pp][+-]?))* +** +** The only tricky part is to accept [+-] only after a valid exponent +** mark, to avoid reading '3-4' or '0xe+1' as a single number. +** +** The caller might have already read an initial dot. +*/ +static int read_numeral (LexState *ls, SemInfo *seminfo) { + TValue obj; + const char *expo = "Ee"; + int first = ls->current; + lua_assert(lisdigit(ls->current)); + save_and_next(ls); + if (first == '0' && check_next2(ls, "xX")) /* hexadecimal? */ + expo = "Pp"; + for (;;) { + if (check_next2(ls, expo)) /* exponent mark? */ + check_next2(ls, "-+"); /* optional exponent sign */ + else if (lisxdigit(ls->current) || ls->current == '.') /* '%x|%.' */ + save_and_next(ls); + else break; + } + if (lislalpha(ls->current)) /* is numeral touching a letter? */ + save_and_next(ls); /* force an error */ + save(ls, '\0'); + if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */ + lexerror(ls, "malformed number", TK_FLT); + if (ttisinteger(&obj)) { + seminfo->i = ivalue(&obj); + return TK_INT; + } + else { + lua_assert(ttisfloat(&obj)); + seminfo->r = fltvalue(&obj); + return TK_FLT; + } +} + + +/* +** read a sequence '[=*[' or ']=*]', leaving the last bracket. If +** sequence is well formed, return its number of '='s + 2; otherwise, +** return 1 if it is a single bracket (no '='s and no 2nd bracket); +** otherwise (an unfinished '[==...') return 0. +*/ +static size_t skip_sep (LexState *ls) { + size_t count = 0; + int s = ls->current; + lua_assert(s == '[' || s == ']'); + save_and_next(ls); + while (ls->current == '=') { + save_and_next(ls); + count++; + } + return (ls->current == s) ? count + 2 + : (count == 0) ? 1 + : 0; +} + + +static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) { + int line = ls->linenumber; /* initial line (for error message) */ + save_and_next(ls); /* skip 2nd '[' */ + if (currIsNewline(ls)) /* string starts with a newline? */ + inclinenumber(ls); /* skip it */ + for (;;) { + switch (ls->current) { + case EOZ: { /* error */ + const char *what = (seminfo ? "string" : "comment"); + const char *msg = luaO_pushfstring(ls->L, + "unfinished long %s (starting at line %d)", what, line); + lexerror(ls, msg, TK_EOS); + break; /* to avoid warnings */ + } + case ']': { + if (skip_sep(ls) == sep) { + save_and_next(ls); /* skip 2nd ']' */ + goto endloop; + } + break; + } + case '\n': case '\r': { + save(ls, '\n'); + inclinenumber(ls); + if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */ + break; + } + default: { + if (seminfo) save_and_next(ls); + else next(ls); + } + } + } endloop: + if (seminfo) + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep, + luaZ_bufflen(ls->buff) - 2 * sep); +} + + +static void esccheck (LexState *ls, int c, const char *msg) { + if (!c) { + if (ls->current != EOZ) + save_and_next(ls); /* add current to buffer for error message */ + lexerror(ls, msg, TK_STRING); + } +} + + +static int gethexa (LexState *ls) { + save_and_next(ls); + esccheck (ls, lisxdigit(ls->current), "hexadecimal digit expected"); + return luaO_hexavalue(ls->current); +} + + +static int readhexaesc (LexState *ls) { + int r = gethexa(ls); + r = (r << 4) + gethexa(ls); + luaZ_buffremove(ls->buff, 2); /* remove saved chars from buffer */ + return r; +} + + +static unsigned long readutf8esc (LexState *ls) { + unsigned long r; + int i = 4; /* chars to be removed: '\', 'u', '{', and first digit */ + save_and_next(ls); /* skip 'u' */ + esccheck(ls, ls->current == '{', "missing '{'"); + r = gethexa(ls); /* must have at least one digit */ + while (cast_void(save_and_next(ls)), lisxdigit(ls->current)) { + i++; + esccheck(ls, r <= (0x7FFFFFFFu >> 4), "UTF-8 value too large"); + r = (r << 4) + luaO_hexavalue(ls->current); + } + esccheck(ls, ls->current == '}', "missing '}'"); + next(ls); /* skip '}' */ + luaZ_buffremove(ls->buff, i); /* remove saved chars from buffer */ + return r; +} + + +static void utf8esc (LexState *ls) { + char buff[UTF8BUFFSZ]; + int n = luaO_utf8esc(buff, readutf8esc(ls)); + for (; n > 0; n--) /* add 'buff' to string */ + save(ls, buff[UTF8BUFFSZ - n]); +} + + +static int readdecesc (LexState *ls) { + int i; + int r = 0; /* result accumulator */ + for (i = 0; i < 3 && lisdigit(ls->current); i++) { /* read up to 3 digits */ + r = 10*r + ls->current - '0'; + save_and_next(ls); + } + esccheck(ls, r <= UCHAR_MAX, "decimal escape too large"); + luaZ_buffremove(ls->buff, i); /* remove read digits from buffer */ + return r; +} + + +static void read_string (LexState *ls, int del, SemInfo *seminfo) { + save_and_next(ls); /* keep delimiter (for error messages) */ + while (ls->current != del) { + switch (ls->current) { + case EOZ: + lexerror(ls, "unfinished string", TK_EOS); + break; /* to avoid warnings */ + case '\n': + case '\r': + lexerror(ls, "unfinished string", TK_STRING); + break; /* to avoid warnings */ + case '\\': { /* escape sequences */ + int c; /* final character to be saved */ + save_and_next(ls); /* keep '\\' for error messages */ + switch (ls->current) { + case 'a': c = '\a'; goto read_save; + case 'b': c = '\b'; goto read_save; + case 'f': c = '\f'; goto read_save; + case 'n': c = '\n'; goto read_save; + case 'r': c = '\r'; goto read_save; + case 't': c = '\t'; goto read_save; + case 'v': c = '\v'; goto read_save; + case 'x': c = readhexaesc(ls); goto read_save; + case 'u': utf8esc(ls); goto no_save; + case '\n': case '\r': + inclinenumber(ls); c = '\n'; goto only_save; + case '\\': case '\"': case '\'': + c = ls->current; goto read_save; + case EOZ: goto no_save; /* will raise an error next loop */ + case 'z': { /* zap following span of spaces */ + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ + next(ls); /* skip the 'z' */ + while (lisspace(ls->current)) { + if (currIsNewline(ls)) inclinenumber(ls); + else next(ls); + } + goto no_save; + } + default: { + esccheck(ls, lisdigit(ls->current), "invalid escape sequence"); + c = readdecesc(ls); /* digital escape '\ddd' */ + goto only_save; + } + } + read_save: + next(ls); + /* go through */ + only_save: + luaZ_buffremove(ls->buff, 1); /* remove '\\' */ + save(ls, c); + /* go through */ + no_save: break; + } + default: + save_and_next(ls); + } + } + save_and_next(ls); /* skip delimiter */ + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1, + luaZ_bufflen(ls->buff) - 2); +} + + +static int llex (LexState *ls, SemInfo *seminfo) { + luaZ_resetbuffer(ls->buff); + for (;;) { + switch (ls->current) { + case '\n': case '\r': { /* line breaks */ + inclinenumber(ls); + break; + } + case ' ': case '\f': case '\t': case '\v': { /* spaces */ + next(ls); + break; + } + case '-': { /* '-' or '--' (comment) */ + next(ls); + if (ls->current != '-') return '-'; + /* else is a comment */ + next(ls); + if (ls->current == '[') { /* long comment? */ + size_t sep = skip_sep(ls); + luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */ + if (sep >= 2) { + read_long_string(ls, NULL, sep); /* skip long comment */ + luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */ + break; + } + } + /* else short comment */ + while (!currIsNewline(ls) && ls->current != EOZ) + next(ls); /* skip until end of line (or end of file) */ + break; + } + case '[': { /* long string or simply '[' */ + size_t sep = skip_sep(ls); + if (sep >= 2) { + read_long_string(ls, seminfo, sep); + return TK_STRING; + } + else if (sep == 0) /* '[=...' missing second bracket? */ + lexerror(ls, "invalid long string delimiter", TK_STRING); + return '['; + } + case '=': { + next(ls); + if (check_next1(ls, '=')) return TK_EQ; /* '==' */ + else return '='; + } + case '<': { + next(ls); + if (check_next1(ls, '=')) return TK_LE; /* '<=' */ + else if (check_next1(ls, '<')) return TK_SHL; /* '<<' */ + else return '<'; + } + case '>': { + next(ls); + if (check_next1(ls, '=')) return TK_GE; /* '>=' */ + else if (check_next1(ls, '>')) return TK_SHR; /* '>>' */ + else return '>'; + } + case '/': { + next(ls); + if (check_next1(ls, '/')) return TK_IDIV; /* '//' */ + else return '/'; + } + case '~': { + next(ls); + if (check_next1(ls, '=')) return TK_NE; /* '~=' */ + else return '~'; + } + case ':': { + next(ls); + if (check_next1(ls, ':')) return TK_DBCOLON; /* '::' */ + else return ':'; + } + case '"': case '\'': { /* short literal strings */ + read_string(ls, ls->current, seminfo); + return TK_STRING; + } + case '.': { /* '.', '..', '...', or number */ + save_and_next(ls); + if (check_next1(ls, '.')) { + if (check_next1(ls, '.')) + return TK_DOTS; /* '...' */ + else return TK_CONCAT; /* '..' */ + } + else if (!lisdigit(ls->current)) return '.'; + else return read_numeral(ls, seminfo); + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + return read_numeral(ls, seminfo); + } + case EOZ: { + return TK_EOS; + } + default: { + if (lislalpha(ls->current)) { /* identifier or reserved word? */ + TString *ts; + do { + save_and_next(ls); + } while (lislalnum(ls->current)); + ts = luaX_newstring(ls, luaZ_buffer(ls->buff), + luaZ_bufflen(ls->buff)); + seminfo->ts = ts; + if (isreserved(ts)) /* reserved word? */ + return ts->extra - 1 + FIRST_RESERVED; + else { + return TK_NAME; + } + } + else { /* single-char tokens ('+', '*', '%', '{', '}', ...) */ + int c = ls->current; + next(ls); + return c; + } + } + } + } +} + + +void luaX_next (LexState *ls) { + ls->lastline = ls->linenumber; + if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ + ls->t = ls->lookahead; /* use this one */ + ls->lookahead.token = TK_EOS; /* and discharge it */ + } + else + ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ +} + + +int luaX_lookahead (LexState *ls) { + lua_assert(ls->lookahead.token == TK_EOS); + ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); + return ls->lookahead.token; +} + diff --git a/arm9/source/lua/llex.h b/arm9/source/lua/llex.h new file mode 100644 index 000000000..389d2f863 --- /dev/null +++ b/arm9/source/lua/llex.h @@ -0,0 +1,91 @@ +/* +** $Id: llex.h $ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + +#ifndef llex_h +#define llex_h + +#include + +#include "lobject.h" +#include "lzio.h" + + +/* +** Single-char tokens (terminal symbols) are represented by their own +** numeric code. Other tokens start at the following value. +*/ +#define FIRST_RESERVED (UCHAR_MAX + 1) + + +#if !defined(LUA_ENV) +#define LUA_ENV "_ENV" +#endif + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER RESERVED" +*/ +enum RESERVED { + /* terminal symbols denoted by reserved words */ + TK_AND = FIRST_RESERVED, TK_BREAK, + TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, + TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, + TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, + /* other terminal symbols */ + TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, + TK_SHL, TK_SHR, + TK_DBCOLON, TK_EOS, + TK_FLT, TK_INT, TK_NAME, TK_STRING +}; + +/* number of reserved words */ +#define NUM_RESERVED (cast_int(TK_WHILE-FIRST_RESERVED + 1)) + + +typedef union { + lua_Number r; + lua_Integer i; + TString *ts; +} SemInfo; /* semantics information */ + + +typedef struct Token { + int token; + SemInfo seminfo; +} Token; + + +/* state of the lexer plus state of the parser when shared by all + functions */ +typedef struct LexState { + int current; /* current character (charint) */ + int linenumber; /* input line counter */ + int lastline; /* line of last token 'consumed' */ + Token t; /* current token */ + Token lookahead; /* look ahead token */ + struct FuncState *fs; /* current function (parser) */ + struct lua_State *L; + ZIO *z; /* input stream */ + Mbuffer *buff; /* buffer for tokens */ + Table *h; /* to avoid collection/reuse strings */ + struct Dyndata *dyd; /* dynamic structures used by the parser */ + TString *source; /* current source name */ + TString *envn; /* environment variable name */ +} LexState; + + +LUAI_FUNC void luaX_init (lua_State *L); +LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, + TString *source, int firstchar); +LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l); +LUAI_FUNC void luaX_next (LexState *ls); +LUAI_FUNC int luaX_lookahead (LexState *ls); +LUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s); +LUAI_FUNC const char *luaX_token2str (LexState *ls, int token); + + +#endif diff --git a/arm9/source/lua/llimits.h b/arm9/source/lua/llimits.h new file mode 100644 index 000000000..1c826f7be --- /dev/null +++ b/arm9/source/lua/llimits.h @@ -0,0 +1,380 @@ +/* +** $Id: llimits.h $ +** Limits, basic types, and some other 'installation-dependent' definitions +** See Copyright Notice in lua.h +*/ + +#ifndef llimits_h +#define llimits_h + + +#include +#include + + +#include "lua.h" + + +/* +** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count +** the total memory used by Lua (in bytes). Usually, 'size_t' and +** 'ptrdiff_t' should work, but we use 'long' for 16-bit machines. +*/ +#if defined(LUAI_MEM) /* { external definitions? */ +typedef LUAI_UMEM lu_mem; +typedef LUAI_MEM l_mem; +#elif LUAI_IS32INT /* }{ */ +typedef size_t lu_mem; +typedef ptrdiff_t l_mem; +#else /* 16-bit ints */ /* }{ */ +typedef unsigned long lu_mem; +typedef long l_mem; +#endif /* } */ + + +/* chars used as small naturals (so that 'char' is reserved for characters) */ +typedef unsigned char lu_byte; +typedef signed char ls_byte; + + +/* maximum value for size_t */ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +/* maximum size visible for Lua (must be representable in a lua_Integer) */ +#define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ + : (size_t)(LUA_MAXINTEGER)) + + +#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)) + +#define MAX_LMEM ((l_mem)(MAX_LUMEM >> 1)) + + +#define MAX_INT INT_MAX /* maximum value of an int */ + + +/* +** floor of the log2 of the maximum signed value for integral type 't'. +** (That is, maximum 'n' such that '2^n' fits in the given signed type.) +*/ +#define log2maxs(t) (sizeof(t) * 8 - 2) + + +/* +** test whether an unsigned value is a power of 2 (or zero) +*/ +#define ispow2(x) (((x) & ((x) - 1)) == 0) + + +/* number of chars of a literal string without the ending \0 */ +#define LL(x) (sizeof(x)/sizeof(char) - 1) + + +/* +** conversion of pointer to unsigned integer: this is for hashing only; +** there is no problem if the integer cannot hold the whole pointer +** value. (In strict ISO C this may cause undefined behavior, but no +** actual machine seems to bother.) +*/ +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(UINTPTR_MAX) /* even in C99 this type is optional */ +#define L_P2I uintptr_t +#else /* no 'intptr'? */ +#define L_P2I uintmax_t /* use the largest available integer */ +#endif +#else /* C89 option */ +#define L_P2I size_t +#endif + +#define point2uint(p) ((unsigned int)((L_P2I)(p) & UINT_MAX)) + + + +/* types of 'usual argument conversions' for lua_Number and lua_Integer */ +typedef LUAI_UACNUMBER l_uacNumber; +typedef LUAI_UACINT l_uacInt; + + +/* +** Internal assertions for in-house debugging +*/ +#if defined LUAI_ASSERT +#undef NDEBUG +#include +#define lua_assert(c) assert(c) +#endif + +#if defined(lua_assert) +#define check_exp(c,e) (lua_assert(c), (e)) +/* to avoid problems with conditions too long */ +#define lua_longassert(c) ((c) ? (void)0 : lua_assert(0)) +#else +#define lua_assert(c) ((void)0) +#define check_exp(c,e) (e) +#define lua_longassert(c) ((void)0) +#endif + +/* +** assertion for checking API calls +*/ +#if !defined(luai_apicheck) +#define luai_apicheck(l,e) ((void)l, lua_assert(e)) +#endif + +#define api_check(l,e,msg) luai_apicheck(l,(e) && msg) + + +/* macro to avoid warnings about unused variables */ +#if !defined(UNUSED) +#define UNUSED(x) ((void)(x)) +#endif + + +/* type casts (a macro highlights casts in the code) */ +#define cast(t, exp) ((t)(exp)) + +#define cast_void(i) cast(void, (i)) +#define cast_voidp(i) cast(void *, (i)) +#define cast_num(i) cast(lua_Number, (i)) +#define cast_int(i) cast(int, (i)) +#define cast_uint(i) cast(unsigned int, (i)) +#define cast_byte(i) cast(lu_byte, (i)) +#define cast_uchar(i) cast(unsigned char, (i)) +#define cast_char(i) cast(char, (i)) +#define cast_charp(i) cast(char *, (i)) +#define cast_sizet(i) cast(size_t, (i)) + + +/* cast a signed lua_Integer to lua_Unsigned */ +#if !defined(l_castS2U) +#define l_castS2U(i) ((lua_Unsigned)(i)) +#endif + +/* +** cast a lua_Unsigned to a signed lua_Integer; this cast is +** not strict ISO C, but two-complement architectures should +** work fine. +*/ +#if !defined(l_castU2S) +#define l_castU2S(i) ((lua_Integer)(i)) +#endif + + +/* +** non-return type +*/ +#if !defined(l_noret) + +#if defined(__GNUC__) +#define l_noret void __attribute__((noreturn)) +#elif defined(_MSC_VER) && _MSC_VER >= 1200 +#define l_noret void __declspec(noreturn) +#else +#define l_noret void +#endif + +#endif + + +/* +** Inline functions +*/ +#if !defined(LUA_USE_C89) +#define l_inline inline +#elif defined(__GNUC__) +#define l_inline __inline__ +#else +#define l_inline /* empty */ +#endif + +#define l_sinline static l_inline + + +/* +** type for virtual-machine instructions; +** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) +*/ +#if LUAI_IS32INT +typedef unsigned int l_uint32; +#else +typedef unsigned long l_uint32; +#endif + +typedef l_uint32 Instruction; + + + +/* +** Maximum length for short strings, that is, strings that are +** internalized. (Cannot be smaller than reserved words or tags for +** metamethods, as these strings must be internalized; +** #("function") = 8, #("__newindex") = 10.) +*/ +#if !defined(LUAI_MAXSHORTLEN) +#define LUAI_MAXSHORTLEN 40 +#endif + + +/* +** Initial size for the string table (must be power of 2). +** The Lua core alone registers ~50 strings (reserved words + +** metaevent keys + a few others). Libraries would typically add +** a few dozens more. +*/ +#if !defined(MINSTRTABSIZE) +#define MINSTRTABSIZE 128 +#endif + + +/* +** Size of cache for strings in the API. 'N' is the number of +** sets (better be a prime) and "M" is the size of each set (M == 1 +** makes a direct cache.) +*/ +#if !defined(STRCACHE_N) +#define STRCACHE_N 53 +#define STRCACHE_M 2 +#endif + + +/* minimum size for string buffer */ +#if !defined(LUA_MINBUFFER) +#define LUA_MINBUFFER 32 +#endif + + +/* +** Maximum depth for nested C calls, syntactical nested non-terminals, +** and other features implemented through recursion in C. (Value must +** fit in a 16-bit unsigned integer. It must also be compatible with +** the size of the C stack.) +*/ +#if !defined(LUAI_MAXCCALLS) +#define LUAI_MAXCCALLS 200 +#endif + + +/* +** macros that are executed whenever program enters the Lua core +** ('lua_lock') and leaves the core ('lua_unlock') +*/ +#if !defined(lua_lock) +#define lua_lock(L) ((void) 0) +#define lua_unlock(L) ((void) 0) +#endif + +/* +** macro executed during Lua functions at points where the +** function can yield. +*/ +#if !defined(luai_threadyield) +#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} +#endif + + +/* +** these macros allow user-specific actions when a thread is +** created/deleted/resumed/yielded. +*/ +#if !defined(luai_userstateopen) +#define luai_userstateopen(L) ((void)L) +#endif + +#if !defined(luai_userstateclose) +#define luai_userstateclose(L) ((void)L) +#endif + +#if !defined(luai_userstatethread) +#define luai_userstatethread(L,L1) ((void)L) +#endif + +#if !defined(luai_userstatefree) +#define luai_userstatefree(L,L1) ((void)L) +#endif + +#if !defined(luai_userstateresume) +#define luai_userstateresume(L,n) ((void)L) +#endif + +#if !defined(luai_userstateyield) +#define luai_userstateyield(L,n) ((void)L) +#endif + + + +/* +** The luai_num* macros define the primitive operations over numbers. +*/ + +/* floor division (defined as 'floor(a/b)') */ +#if !defined(luai_numidiv) +#define luai_numidiv(L,a,b) ((void)L, l_floor(luai_numdiv(L,a,b))) +#endif + +/* float division */ +#if !defined(luai_numdiv) +#define luai_numdiv(L,a,b) ((a)/(b)) +#endif + +/* +** modulo: defined as 'a - floor(a/b)*b'; the direct computation +** using this definition has several problems with rounding errors, +** so it is better to use 'fmod'. 'fmod' gives the result of +** 'a - trunc(a/b)*b', and therefore must be corrected when +** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a +** non-integer negative result: non-integer result is equivalent to +** a non-zero remainder 'm'; negative result is equivalent to 'a' and +** 'b' with different signs, or 'm' and 'b' with different signs +** (as the result 'm' of 'fmod' has the same sign of 'a'). +*/ +#if !defined(luai_nummod) +#define luai_nummod(L,a,b,m) \ + { (void)L; (m) = l_mathop(fmod)(a,b); \ + if (((m) > 0) ? (b) < 0 : ((m) < 0 && (b) > 0)) (m) += (b); } +#endif + +/* exponentiation */ +#if !defined(luai_numpow) +#define luai_numpow(L,a,b) \ + ((void)L, (b == 2) ? (a)*(a) : l_mathop(pow)(a,b)) +#endif + +/* the others are quite standard operations */ +#if !defined(luai_numadd) +#define luai_numadd(L,a,b) ((a)+(b)) +#define luai_numsub(L,a,b) ((a)-(b)) +#define luai_nummul(L,a,b) ((a)*(b)) +#define luai_numunm(L,a) (-(a)) +#define luai_numeq(a,b) ((a)==(b)) +#define luai_numlt(a,b) ((a)<(b)) +#define luai_numle(a,b) ((a)<=(b)) +#define luai_numgt(a,b) ((a)>(b)) +#define luai_numge(a,b) ((a)>=(b)) +#define luai_numisnan(a) (!luai_numeq((a), (a))) +#endif + + + + + +/* +** macro to control inclusion of some hard tests on stack reallocation +*/ +#if !defined(HARDSTACKTESTS) +#define condmovestack(L,pre,pos) ((void)0) +#else +/* realloc stack keeping its size */ +#define condmovestack(L,pre,pos) \ + { int sz_ = stacksize(L); pre; luaD_reallocstack((L), sz_, 0); pos; } +#endif + +#if !defined(HARDMEMTESTS) +#define condchangemem(L,pre,pos) ((void)0) +#else +#define condchangemem(L,pre,pos) \ + { if (gcrunning(G(L))) { pre; luaC_fullgc(L, 0); pos; } } +#endif + +#endif diff --git a/arm9/source/lua/lmathlib.c b/arm9/source/lua/lmathlib.c new file mode 100644 index 000000000..d0b1e1e5d --- /dev/null +++ b/arm9/source/lua/lmathlib.c @@ -0,0 +1,764 @@ +/* +** $Id: lmathlib.c $ +** Standard mathematical library +** See Copyright Notice in lua.h +*/ + +#define lmathlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#undef PI +#define PI (l_mathop(3.141592653589793238462643383279502884)) + + +static int math_abs (lua_State *L) { + if (lua_isinteger(L, 1)) { + lua_Integer n = lua_tointeger(L, 1); + if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n); + lua_pushinteger(L, n); + } + else + lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sin (lua_State *L) { + lua_pushnumber(L, l_mathop(sin)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_cos (lua_State *L) { + lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tan (lua_State *L) { + lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_asin (lua_State *L) { + lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_acos (lua_State *L) { + lua_pushnumber(L, l_mathop(acos)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_atan (lua_State *L) { + lua_Number y = luaL_checknumber(L, 1); + lua_Number x = luaL_optnumber(L, 2, 1); + lua_pushnumber(L, l_mathop(atan2)(y, x)); + return 1; +} + + +static int math_toint (lua_State *L) { + int valid; + lua_Integer n = lua_tointegerx(L, 1, &valid); + if (l_likely(valid)) + lua_pushinteger(L, n); + else { + luaL_checkany(L, 1); + luaL_pushfail(L); /* value is not convertible to integer */ + } + return 1; +} + + +static void pushnumint (lua_State *L, lua_Number d) { + lua_Integer n; + if (lua_numbertointeger(d, &n)) /* does 'd' fit in an integer? */ + lua_pushinteger(L, n); /* result is integer */ + else + lua_pushnumber(L, d); /* result is float */ +} + + +static int math_floor (lua_State *L) { + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own floor */ + else { + lua_Number d = l_mathop(floor)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } + return 1; +} + + +static int math_ceil (lua_State *L) { + if (lua_isinteger(L, 1)) + lua_settop(L, 1); /* integer is its own ceil */ + else { + lua_Number d = l_mathop(ceil)(luaL_checknumber(L, 1)); + pushnumint(L, d); + } + return 1; +} + + +static int math_fmod (lua_State *L) { + if (lua_isinteger(L, 1) && lua_isinteger(L, 2)) { + lua_Integer d = lua_tointeger(L, 2); + if ((lua_Unsigned)d + 1u <= 1u) { /* special cases: -1 or 0 */ + luaL_argcheck(L, d != 0, 2, "zero"); + lua_pushinteger(L, 0); /* avoid overflow with 0x80000... / -1 */ + } + else + lua_pushinteger(L, lua_tointeger(L, 1) % d); + } + else + lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1), + luaL_checknumber(L, 2))); + return 1; +} + + +/* +** next function does not use 'modf', avoiding problems with 'double*' +** (which is not compatible with 'float*') when lua_Number is not +** 'double'. +*/ +static int math_modf (lua_State *L) { + if (lua_isinteger(L ,1)) { + lua_settop(L, 1); /* number is its own integer part */ + lua_pushnumber(L, 0); /* no fractional part */ + } + else { + lua_Number n = luaL_checknumber(L, 1); + /* integer part (rounds toward zero) */ + lua_Number ip = (n < 0) ? l_mathop(ceil)(n) : l_mathop(floor)(n); + pushnumint(L, ip); + /* fractional part (test needed for inf/-inf) */ + lua_pushnumber(L, (n == ip) ? l_mathop(0.0) : (n - ip)); + } + return 2; +} + + +static int math_sqrt (lua_State *L) { + lua_pushnumber(L, l_mathop(sqrt)(luaL_checknumber(L, 1))); + return 1; +} + + +static int math_ult (lua_State *L) { + lua_Integer a = luaL_checkinteger(L, 1); + lua_Integer b = luaL_checkinteger(L, 2); + lua_pushboolean(L, (lua_Unsigned)a < (lua_Unsigned)b); + return 1; +} + +static int math_log (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number res; + if (lua_isnoneornil(L, 2)) + res = l_mathop(log)(x); + else { + lua_Number base = luaL_checknumber(L, 2); +#if !defined(LUA_USE_C89) + if (base == l_mathop(2.0)) + res = l_mathop(log2)(x); + else +#endif + if (base == l_mathop(10.0)) + res = l_mathop(log10)(x); + else + res = l_mathop(log)(x)/l_mathop(log)(base); + } + lua_pushnumber(L, res); + return 1; +} + +static int math_exp (lua_State *L) { + lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_deg (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1) * (l_mathop(180.0) / PI)); + return 1; +} + +static int math_rad (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / l_mathop(180.0))); + return 1; +} + + +static int math_min (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int imin = 1; /* index of current minimum value */ + int i; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, i, imin, LUA_OPLT)) + imin = i; + } + lua_pushvalue(L, imin); + return 1; +} + + +static int math_max (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int imax = 1; /* index of current maximum value */ + int i; + luaL_argcheck(L, n >= 1, 1, "value expected"); + for (i = 2; i <= n; i++) { + if (lua_compare(L, imax, i, LUA_OPLT)) + imax = i; + } + lua_pushvalue(L, imax); + return 1; +} + + +static int math_type (lua_State *L) { + if (lua_type(L, 1) == LUA_TNUMBER) + lua_pushstring(L, (lua_isinteger(L, 1)) ? "integer" : "float"); + else { + luaL_checkany(L, 1); + luaL_pushfail(L); + } + return 1; +} + + + +/* +** {================================================================== +** Pseudo-Random Number Generator based on 'xoshiro256**'. +** =================================================================== +*/ + +/* number of binary digits in the mantissa of a float */ +#define FIGS l_floatatt(MANT_DIG) + +#if FIGS > 64 +/* there are only 64 random bits; use them all */ +#undef FIGS +#define FIGS 64 +#endif + + +/* +** LUA_RAND32 forces the use of 32-bit integers in the implementation +** of the PRN generator (mainly for testing). +*/ +#if !defined(LUA_RAND32) && !defined(Rand64) + +/* try to find an integer type with at least 64 bits */ + +#if ((ULONG_MAX >> 31) >> 31) >= 3 + +/* 'long' has at least 64 bits */ +#define Rand64 unsigned long + +#elif !defined(LUA_USE_C89) && defined(LLONG_MAX) + +/* there is a 'long long' type (which must have at least 64 bits) */ +#define Rand64 unsigned long long + +#elif ((LUA_MAXUNSIGNED >> 31) >> 31) >= 3 + +/* 'lua_Unsigned' has at least 64 bits */ +#define Rand64 lua_Unsigned + +#endif + +#endif + + +#if defined(Rand64) /* { */ + +/* +** Standard implementation, using 64-bit integers. +** If 'Rand64' has more than 64 bits, the extra bits do not interfere +** with the 64 initial bits, except in a right shift. Moreover, the +** final result has to discard the extra bits. +*/ + +/* avoid using extra bits when needed */ +#define trim64(x) ((x) & 0xffffffffffffffffu) + + +/* rotate left 'x' by 'n' bits */ +static Rand64 rotl (Rand64 x, int n) { + return (x << n) | (trim64(x) >> (64 - n)); +} + +static Rand64 nextrand (Rand64 *state) { + Rand64 state0 = state[0]; + Rand64 state1 = state[1]; + Rand64 state2 = state[2] ^ state0; + Rand64 state3 = state[3] ^ state1; + Rand64 res = rotl(state1 * 5, 7) * 9; + state[0] = state0 ^ state3; + state[1] = state1 ^ state2; + state[2] = state2 ^ (state1 << 17); + state[3] = rotl(state3, 45); + return res; +} + + +/* must take care to not shift stuff by more than 63 slots */ + + +/* +** Convert bits from a random integer into a float in the +** interval [0,1), getting the higher FIG bits from the +** random unsigned integer and converting that to a float. +*/ + +/* must throw out the extra (64 - FIGS) bits */ +#define shift64_FIG (64 - FIGS) + +/* to scale to [0, 1), multiply by scaleFIG = 2^(-FIGS) */ +#define scaleFIG (l_mathop(0.5) / ((Rand64)1 << (FIGS - 1))) + +static lua_Number I2d (Rand64 x) { + return (lua_Number)(trim64(x) >> shift64_FIG) * scaleFIG; +} + +/* convert a 'Rand64' to a 'lua_Unsigned' */ +#define I2UInt(x) ((lua_Unsigned)trim64(x)) + +/* convert a 'lua_Unsigned' to a 'Rand64' */ +#define Int2I(x) ((Rand64)(x)) + + +#else /* no 'Rand64' }{ */ + +/* get an integer with at least 32 bits */ +#if LUAI_IS32INT +typedef unsigned int lu_int32; +#else +typedef unsigned long lu_int32; +#endif + + +/* +** Use two 32-bit integers to represent a 64-bit quantity. +*/ +typedef struct Rand64 { + lu_int32 h; /* higher half */ + lu_int32 l; /* lower half */ +} Rand64; + + +/* +** If 'lu_int32' has more than 32 bits, the extra bits do not interfere +** with the 32 initial bits, except in a right shift and comparisons. +** Moreover, the final result has to discard the extra bits. +*/ + +/* avoid using extra bits when needed */ +#define trim32(x) ((x) & 0xffffffffu) + + +/* +** basic operations on 'Rand64' values +*/ + +/* build a new Rand64 value */ +static Rand64 packI (lu_int32 h, lu_int32 l) { + Rand64 result; + result.h = h; + result.l = l; + return result; +} + +/* return i << n */ +static Rand64 Ishl (Rand64 i, int n) { + lua_assert(n > 0 && n < 32); + return packI((i.h << n) | (trim32(i.l) >> (32 - n)), i.l << n); +} + +/* i1 ^= i2 */ +static void Ixor (Rand64 *i1, Rand64 i2) { + i1->h ^= i2.h; + i1->l ^= i2.l; +} + +/* return i1 + i2 */ +static Rand64 Iadd (Rand64 i1, Rand64 i2) { + Rand64 result = packI(i1.h + i2.h, i1.l + i2.l); + if (trim32(result.l) < trim32(i1.l)) /* carry? */ + result.h++; + return result; +} + +/* return i * 5 */ +static Rand64 times5 (Rand64 i) { + return Iadd(Ishl(i, 2), i); /* i * 5 == (i << 2) + i */ +} + +/* return i * 9 */ +static Rand64 times9 (Rand64 i) { + return Iadd(Ishl(i, 3), i); /* i * 9 == (i << 3) + i */ +} + +/* return 'i' rotated left 'n' bits */ +static Rand64 rotl (Rand64 i, int n) { + lua_assert(n > 0 && n < 32); + return packI((i.h << n) | (trim32(i.l) >> (32 - n)), + (trim32(i.h) >> (32 - n)) | (i.l << n)); +} + +/* for offsets larger than 32, rotate right by 64 - offset */ +static Rand64 rotl1 (Rand64 i, int n) { + lua_assert(n > 32 && n < 64); + n = 64 - n; + return packI((trim32(i.h) >> n) | (i.l << (32 - n)), + (i.h << (32 - n)) | (trim32(i.l) >> n)); +} + +/* +** implementation of 'xoshiro256**' algorithm on 'Rand64' values +*/ +static Rand64 nextrand (Rand64 *state) { + Rand64 res = times9(rotl(times5(state[1]), 7)); + Rand64 t = Ishl(state[1], 17); + Ixor(&state[2], state[0]); + Ixor(&state[3], state[1]); + Ixor(&state[1], state[2]); + Ixor(&state[0], state[3]); + Ixor(&state[2], t); + state[3] = rotl1(state[3], 45); + return res; +} + + +/* +** Converts a 'Rand64' into a float. +*/ + +/* an unsigned 1 with proper type */ +#define UONE ((lu_int32)1) + + +#if FIGS <= 32 + +/* 2^(-FIGS) */ +#define scaleFIG (l_mathop(0.5) / (UONE << (FIGS - 1))) + +/* +** get up to 32 bits from higher half, shifting right to +** throw out the extra bits. +*/ +static lua_Number I2d (Rand64 x) { + lua_Number h = (lua_Number)(trim32(x.h) >> (32 - FIGS)); + return h * scaleFIG; +} + +#else /* 32 < FIGS <= 64 */ + +/* must take care to not shift stuff by more than 31 slots */ + +/* 2^(-FIGS) = 1.0 / 2^30 / 2^3 / 2^(FIGS-33) */ +#define scaleFIG \ + (l_mathop(1.0) / (UONE << 30) / l_mathop(8.0) / (UONE << (FIGS - 33))) + +/* +** use FIGS - 32 bits from lower half, throwing out the other +** (32 - (FIGS - 32)) = (64 - FIGS) bits +*/ +#define shiftLOW (64 - FIGS) + +/* +** higher 32 bits go after those (FIGS - 32) bits: shiftHI = 2^(FIGS - 32) +*/ +#define shiftHI ((lua_Number)(UONE << (FIGS - 33)) * l_mathop(2.0)) + + +static lua_Number I2d (Rand64 x) { + lua_Number h = (lua_Number)trim32(x.h) * shiftHI; + lua_Number l = (lua_Number)(trim32(x.l) >> shiftLOW); + return (h + l) * scaleFIG; +} + +#endif + + +/* convert a 'Rand64' to a 'lua_Unsigned' */ +static lua_Unsigned I2UInt (Rand64 x) { + return (((lua_Unsigned)trim32(x.h) << 31) << 1) | (lua_Unsigned)trim32(x.l); +} + +/* convert a 'lua_Unsigned' to a 'Rand64' */ +static Rand64 Int2I (lua_Unsigned n) { + return packI((lu_int32)((n >> 31) >> 1), (lu_int32)n); +} + +#endif /* } */ + + +/* +** A state uses four 'Rand64' values. +*/ +typedef struct { + Rand64 s[4]; +} RanState; + + +/* +** Project the random integer 'ran' into the interval [0, n]. +** Because 'ran' has 2^B possible values, the projection can only be +** uniform when the size of the interval is a power of 2 (exact +** division). Otherwise, to get a uniform projection into [0, n], we +** first compute 'lim', the smallest Mersenne number not smaller than +** 'n'. We then project 'ran' into the interval [0, lim]. If the result +** is inside [0, n], we are done. Otherwise, we try with another 'ran', +** until we have a result inside the interval. +*/ +static lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n, + RanState *state) { + if ((n & (n + 1)) == 0) /* is 'n + 1' a power of 2? */ + return ran & n; /* no bias */ + else { + lua_Unsigned lim = n; + /* compute the smallest (2^b - 1) not smaller than 'n' */ + lim |= (lim >> 1); + lim |= (lim >> 2); + lim |= (lim >> 4); + lim |= (lim >> 8); + lim |= (lim >> 16); +#if (LUA_MAXUNSIGNED >> 31) >= 3 + lim |= (lim >> 32); /* integer type has more than 32 bits */ +#endif + lua_assert((lim & (lim + 1)) == 0 /* 'lim + 1' is a power of 2, */ + && lim >= n /* not smaller than 'n', */ + && (lim >> 1) < n); /* and it is the smallest one */ + while ((ran &= lim) > n) /* project 'ran' into [0..lim] */ + ran = I2UInt(nextrand(state->s)); /* not inside [0..n]? try again */ + return ran; + } +} + + +static int math_random (lua_State *L) { + lua_Integer low, up; + lua_Unsigned p; + RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1)); + Rand64 rv = nextrand(state->s); /* next pseudo-random value */ + switch (lua_gettop(L)) { /* check number of arguments */ + case 0: { /* no arguments */ + lua_pushnumber(L, I2d(rv)); /* float between 0 and 1 */ + return 1; + } + case 1: { /* only upper limit */ + low = 1; + up = luaL_checkinteger(L, 1); + if (up == 0) { /* single 0 as argument? */ + lua_pushinteger(L, I2UInt(rv)); /* full random integer */ + return 1; + } + break; + } + case 2: { /* lower and upper limits */ + low = luaL_checkinteger(L, 1); + up = luaL_checkinteger(L, 2); + break; + } + default: return luaL_error(L, "wrong number of arguments"); + } + /* random integer in the interval [low, up] */ + luaL_argcheck(L, low <= up, 1, "interval is empty"); + /* project random integer into the interval [0, up - low] */ + p = project(I2UInt(rv), (lua_Unsigned)up - (lua_Unsigned)low, state); + lua_pushinteger(L, p + (lua_Unsigned)low); + return 1; +} + + +static void setseed (lua_State *L, Rand64 *state, + lua_Unsigned n1, lua_Unsigned n2) { + int i; + state[0] = Int2I(n1); + state[1] = Int2I(0xff); /* avoid a zero state */ + state[2] = Int2I(n2); + state[3] = Int2I(0); + for (i = 0; i < 16; i++) + nextrand(state); /* discard initial values to "spread" seed */ + lua_pushinteger(L, n1); + lua_pushinteger(L, n2); +} + + +/* +** Set a "random" seed. To get some randomness, use the current time +** and the address of 'L' (in case the machine does address space layout +** randomization). +*/ +static void randseed (lua_State *L, RanState *state) { + lua_Unsigned seed1 = (lua_Unsigned)time(NULL); + lua_Unsigned seed2 = (lua_Unsigned)(size_t)L; + setseed(L, state->s, seed1, seed2); +} + + +static int math_randomseed (lua_State *L) { + RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1)); + if (lua_isnone(L, 1)) { + randseed(L, state); + } + else { + lua_Integer n1 = luaL_checkinteger(L, 1); + lua_Integer n2 = luaL_optinteger(L, 2, 0); + setseed(L, state->s, n1, n2); + } + return 2; /* return seeds */ +} + + +static const luaL_Reg randfuncs[] = { + {"random", math_random}, + {"randomseed", math_randomseed}, + {NULL, NULL} +}; + + +/* +** Register the random functions and initialize their state. +*/ +static void setrandfunc (lua_State *L) { + RanState *state = (RanState *)lua_newuserdatauv(L, sizeof(RanState), 0); + randseed(L, state); /* initialize with a "random" seed */ + lua_pop(L, 2); /* remove pushed seeds */ + luaL_setfuncs(L, randfuncs, 1); +} + +/* }================================================================== */ + + +/* +** {================================================================== +** Deprecated functions (for compatibility only) +** =================================================================== +*/ +#if defined(LUA_COMPAT_MATHLIB) + +static int math_cosh (lua_State *L) { + lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sinh (lua_State *L) { + lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tanh (lua_State *L) { + lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1))); + return 1; +} + +static int math_pow (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + lua_Number y = luaL_checknumber(L, 2); + lua_pushnumber(L, l_mathop(pow)(x, y)); + return 1; +} + +static int math_frexp (lua_State *L) { + int e; + lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); + lua_pushinteger(L, e); + return 2; +} + +static int math_ldexp (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + int ep = (int)luaL_checkinteger(L, 2); + lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + return 1; +} + +static int math_log10 (lua_State *L) { + lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); + return 1; +} + +#endif +/* }================================================================== */ + + + +static const luaL_Reg mathlib[] = { + {"abs", math_abs}, + {"acos", math_acos}, + {"asin", math_asin}, + {"atan", math_atan}, + {"ceil", math_ceil}, + {"cos", math_cos}, + {"deg", math_deg}, + {"exp", math_exp}, + {"tointeger", math_toint}, + {"floor", math_floor}, + {"fmod", math_fmod}, + {"ult", math_ult}, + {"log", math_log}, + {"max", math_max}, + {"min", math_min}, + {"modf", math_modf}, + {"rad", math_rad}, + {"sin", math_sin}, + {"sqrt", math_sqrt}, + {"tan", math_tan}, + {"type", math_type}, +#if defined(LUA_COMPAT_MATHLIB) + {"atan2", math_atan}, + {"cosh", math_cosh}, + {"sinh", math_sinh}, + {"tanh", math_tanh}, + {"pow", math_pow}, + {"frexp", math_frexp}, + {"ldexp", math_ldexp}, + {"log10", math_log10}, +#endif + /* placeholders */ + {"random", NULL}, + {"randomseed", NULL}, + {"pi", NULL}, + {"huge", NULL}, + {"maxinteger", NULL}, + {"mininteger", NULL}, + {NULL, NULL} +}; + + +/* +** Open math library +*/ +LUAMOD_API int luaopen_math (lua_State *L) { + luaL_newlib(L, mathlib); + lua_pushnumber(L, PI); + lua_setfield(L, -2, "pi"); + lua_pushnumber(L, (lua_Number)HUGE_VAL); + lua_setfield(L, -2, "huge"); + lua_pushinteger(L, LUA_MAXINTEGER); + lua_setfield(L, -2, "maxinteger"); + lua_pushinteger(L, LUA_MININTEGER); + lua_setfield(L, -2, "mininteger"); + setrandfunc(L); + return 1; +} + diff --git a/arm9/source/lua/lmem.c b/arm9/source/lua/lmem.c new file mode 100644 index 000000000..9800a86fc --- /dev/null +++ b/arm9/source/lua/lmem.c @@ -0,0 +1,215 @@ +/* +** $Id: lmem.c $ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + +#define lmem_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + + +/* +** About the realloc function: +** void *frealloc (void *ud, void *ptr, size_t osize, size_t nsize); +** ('osize' is the old size, 'nsize' is the new size) +** +** - frealloc(ud, p, x, 0) frees the block 'p' and returns NULL. +** Particularly, frealloc(ud, NULL, 0, 0) does nothing, +** which is equivalent to free(NULL) in ISO C. +** +** - frealloc(ud, NULL, x, s) creates a new block of size 's' +** (no matter 'x'). Returns NULL if it cannot create the new block. +** +** - otherwise, frealloc(ud, b, x, y) reallocates the block 'b' from +** size 'x' to size 'y'. Returns NULL if it cannot reallocate the +** block to the new size. +*/ + + +/* +** Macro to call the allocation function. +*/ +#define callfrealloc(g,block,os,ns) ((*g->frealloc)(g->ud, block, os, ns)) + + +/* +** When an allocation fails, it will try again after an emergency +** collection, except when it cannot run a collection. The GC should +** not be called while the state is not fully built, as the collector +** is not yet fully initialized. Also, it should not be called when +** 'gcstopem' is true, because then the interpreter is in the middle of +** a collection step. +*/ +#define cantryagain(g) (completestate(g) && !g->gcstopem) + + + + +#if defined(EMERGENCYGCTESTS) +/* +** First allocation will fail except when freeing a block (frees never +** fail) and when it cannot try again; this fail will trigger 'tryagain' +** and a full GC cycle at every allocation. +*/ +static void *firsttry (global_State *g, void *block, size_t os, size_t ns) { + if (ns > 0 && cantryagain(g)) + return NULL; /* fail */ + else /* normal allocation */ + return callfrealloc(g, block, os, ns); +} +#else +#define firsttry(g,block,os,ns) callfrealloc(g, block, os, ns) +#endif + + + + + +/* +** {================================================================== +** Functions to allocate/deallocate arrays for the Parser +** =================================================================== +*/ + +/* +** Minimum size for arrays during parsing, to avoid overhead of +** reallocating to size 1, then 2, and then 4. All these arrays +** will be reallocated to exact sizes or erased when parsing ends. +*/ +#define MINSIZEARRAY 4 + + +void *luaM_growaux_ (lua_State *L, void *block, int nelems, int *psize, + int size_elems, int limit, const char *what) { + void *newblock; + int size = *psize; + if (nelems + 1 <= size) /* does one extra element still fit? */ + return block; /* nothing to be done */ + if (size >= limit / 2) { /* cannot double it? */ + if (l_unlikely(size >= limit)) /* cannot grow even a little? */ + luaG_runerror(L, "too many %s (limit is %d)", what, limit); + size = limit; /* still have at least one free place */ + } + else { + size *= 2; + if (size < MINSIZEARRAY) + size = MINSIZEARRAY; /* minimum size */ + } + lua_assert(nelems + 1 <= size && size <= limit); + /* 'limit' ensures that multiplication will not overflow */ + newblock = luaM_saferealloc_(L, block, cast_sizet(*psize) * size_elems, + cast_sizet(size) * size_elems); + *psize = size; /* update only when everything else is OK */ + return newblock; +} + + +/* +** In prototypes, the size of the array is also its number of +** elements (to save memory). So, if it cannot shrink an array +** to its number of elements, the only option is to raise an +** error. +*/ +void *luaM_shrinkvector_ (lua_State *L, void *block, int *size, + int final_n, int size_elem) { + void *newblock; + size_t oldsize = cast_sizet((*size) * size_elem); + size_t newsize = cast_sizet(final_n * size_elem); + lua_assert(newsize <= oldsize); + newblock = luaM_saferealloc_(L, block, oldsize, newsize); + *size = final_n; + return newblock; +} + +/* }================================================================== */ + + +l_noret luaM_toobig (lua_State *L) { + luaG_runerror(L, "memory allocation error: block too big"); +} + + +/* +** Free memory +*/ +void luaM_free_ (lua_State *L, void *block, size_t osize) { + global_State *g = G(L); + lua_assert((osize == 0) == (block == NULL)); + callfrealloc(g, block, osize, 0); + g->GCdebt -= osize; +} + + +/* +** In case of allocation fail, this function will do an emergency +** collection to free some memory and then try the allocation again. +*/ +static void *tryagain (lua_State *L, void *block, + size_t osize, size_t nsize) { + global_State *g = G(L); + if (cantryagain(g)) { + luaC_fullgc(L, 1); /* try to free some memory... */ + return callfrealloc(g, block, osize, nsize); /* try again */ + } + else return NULL; /* cannot run an emergency collection */ +} + + +/* +** Generic allocation routine. +*/ +void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { + void *newblock; + global_State *g = G(L); + lua_assert((osize == 0) == (block == NULL)); + newblock = firsttry(g, block, osize, nsize); + if (l_unlikely(newblock == NULL && nsize > 0)) { + newblock = tryagain(L, block, osize, nsize); + if (newblock == NULL) /* still no memory? */ + return NULL; /* do not update 'GCdebt' */ + } + lua_assert((nsize == 0) == (newblock == NULL)); + g->GCdebt = (g->GCdebt + nsize) - osize; + return newblock; +} + + +void *luaM_saferealloc_ (lua_State *L, void *block, size_t osize, + size_t nsize) { + void *newblock = luaM_realloc_(L, block, osize, nsize); + if (l_unlikely(newblock == NULL && nsize > 0)) /* allocation failed? */ + luaM_error(L); + return newblock; +} + + +void *luaM_malloc_ (lua_State *L, size_t size, int tag) { + if (size == 0) + return NULL; /* that's all */ + else { + global_State *g = G(L); + void *newblock = firsttry(g, NULL, tag, size); + if (l_unlikely(newblock == NULL)) { + newblock = tryagain(L, NULL, tag, size); + if (newblock == NULL) + luaM_error(L); + } + g->GCdebt += size; + return newblock; + } +} diff --git a/arm9/source/lua/lmem.h b/arm9/source/lua/lmem.h new file mode 100644 index 000000000..8c75a44be --- /dev/null +++ b/arm9/source/lua/lmem.h @@ -0,0 +1,93 @@ +/* +** $Id: lmem.h $ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + +#ifndef lmem_h +#define lmem_h + + +#include + +#include "llimits.h" +#include "lua.h" + + +#define luaM_error(L) luaD_throw(L, LUA_ERRMEM) + + +/* +** This macro tests whether it is safe to multiply 'n' by the size of +** type 't' without overflows. Because 'e' is always constant, it avoids +** the runtime division MAX_SIZET/(e). +** (The macro is somewhat complex to avoid warnings: The 'sizeof' +** comparison avoids a runtime comparison when overflow cannot occur. +** The compiler should be able to optimize the real test by itself, but +** when it does it, it may give a warning about "comparison is always +** false due to limited range of data type"; the +1 tricks the compiler, +** avoiding this warning but also this optimization.) +*/ +#define luaM_testsize(n,e) \ + (sizeof(n) >= sizeof(size_t) && cast_sizet((n)) + 1 > MAX_SIZET/(e)) + +#define luaM_checksize(L,n,e) \ + (luaM_testsize(n,e) ? luaM_toobig(L) : cast_void(0)) + + +/* +** Computes the minimum between 'n' and 'MAX_SIZET/sizeof(t)', so that +** the result is not larger than 'n' and cannot overflow a 'size_t' +** when multiplied by the size of type 't'. (Assumes that 'n' is an +** 'int' or 'unsigned int' and that 'int' is not larger than 'size_t'.) +*/ +#define luaM_limitN(n,t) \ + ((cast_sizet(n) <= MAX_SIZET/sizeof(t)) ? (n) : \ + cast_uint((MAX_SIZET/sizeof(t)))) + + +/* +** Arrays of chars do not need any test +*/ +#define luaM_reallocvchar(L,b,on,n) \ + cast_charp(luaM_saferealloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char))) + +#define luaM_freemem(L, b, s) luaM_free_(L, (b), (s)) +#define luaM_free(L, b) luaM_free_(L, (b), sizeof(*(b))) +#define luaM_freearray(L, b, n) luaM_free_(L, (b), (n)*sizeof(*(b))) + +#define luaM_new(L,t) cast(t*, luaM_malloc_(L, sizeof(t), 0)) +#define luaM_newvector(L,n,t) cast(t*, luaM_malloc_(L, (n)*sizeof(t), 0)) +#define luaM_newvectorchecked(L,n,t) \ + (luaM_checksize(L,n,sizeof(t)), luaM_newvector(L,n,t)) + +#define luaM_newobject(L,tag,s) luaM_malloc_(L, (s), tag) + +#define luaM_growvector(L,v,nelems,size,t,limit,e) \ + ((v)=cast(t *, luaM_growaux_(L,v,nelems,&(size),sizeof(t), \ + luaM_limitN(limit,t),e))) + +#define luaM_reallocvector(L, v,oldn,n,t) \ + (cast(t *, luaM_realloc_(L, v, cast_sizet(oldn) * sizeof(t), \ + cast_sizet(n) * sizeof(t)))) + +#define luaM_shrinkvector(L,v,size,fs,t) \ + ((v)=cast(t *, luaM_shrinkvector_(L, v, &(size), fs, sizeof(t)))) + +LUAI_FUNC l_noret luaM_toobig (lua_State *L); + +/* not to be called directly */ +LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize, + size_t size); +LUAI_FUNC void *luaM_saferealloc_ (lua_State *L, void *block, size_t oldsize, + size_t size); +LUAI_FUNC void luaM_free_ (lua_State *L, void *block, size_t osize); +LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int nelems, + int *size, int size_elem, int limit, + const char *what); +LUAI_FUNC void *luaM_shrinkvector_ (lua_State *L, void *block, int *nelem, + int final_n, int size_elem); +LUAI_FUNC void *luaM_malloc_ (lua_State *L, size_t size, int tag); + +#endif + diff --git a/arm9/source/lua/loadlib.c b/arm9/source/lua/loadlib.c new file mode 100644 index 000000000..d792dffaa --- /dev/null +++ b/arm9/source/lua/loadlib.c @@ -0,0 +1,767 @@ +/* +** $Id: loadlib.c $ +** Dynamic library loader for Lua +** See Copyright Notice in lua.h +** +** This module contains an implementation of loadlib for Unix systems +** that have dlfcn, an implementation for Windows, and a stub for other +** systems. +*/ + +#define loadlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** LUA_IGMARK is a mark to ignore all before it when building the +** luaopen_ function name. +*/ +#if !defined (LUA_IGMARK) +#define LUA_IGMARK "-" +#endif + + +/* +** LUA_CSUBSEP is the character that replaces dots in submodule names +** when searching for a C loader. +** LUA_LSUBSEP is the character that replaces dots in submodule names +** when searching for a Lua loader. +*/ +#if !defined(LUA_CSUBSEP) +#define LUA_CSUBSEP LUA_DIRSEP +#endif + +#if !defined(LUA_LSUBSEP) +#define LUA_LSUBSEP LUA_DIRSEP +#endif + + +/* prefix for open functions in C libraries */ +#define LUA_POF "luaopen_" + +/* separator for open functions in C libraries */ +#define LUA_OFSEP "_" + + +/* +** key for table in the registry that keeps handles +** for all loaded C libraries +*/ +static const char *const CLIBS = "_CLIBS"; + +#define LIB_FAIL "open" + + +#define setprogdir(L) ((void)0) + + +/* +** Special type equivalent to '(void*)' for functions in gcc +** (to suppress warnings when converting function pointers) +*/ +typedef void (*voidf)(void); + + +/* +** system-dependent functions +*/ + +/* +** unload library 'lib' +*/ +static void lsys_unloadlib (void *lib); + +/* +** load C library in file 'path'. If 'seeglb', load with all names in +** the library global. +** Returns the library; in case of error, returns NULL plus an +** error string in the stack. +*/ +static void *lsys_load (lua_State *L, const char *path, int seeglb); + +/* +** Try to find a function named 'sym' in library 'lib'. +** Returns the function; in case of error, returns NULL plus an +** error string in the stack. +*/ +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym); + + + + +#if defined(LUA_USE_DLOPEN) /* { */ +/* +** {======================================================================== +** This is an implementation of loadlib based on the dlfcn interface. +** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD, +** NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least +** as an emulation layer on top of native functions. +** ========================================================================= +*/ + +#include + +/* +** Macro to convert pointer-to-void* to pointer-to-function. This cast +** is undefined according to ISO C, but POSIX assumes that it works. +** (The '__extension__' in gnu compilers is only to avoid warnings.) +*/ +#if defined(__GNUC__) +#define cast_func(p) (__extension__ (lua_CFunction)(p)) +#else +#define cast_func(p) ((lua_CFunction)(p)) +#endif + + +static void lsys_unloadlib (void *lib) { + dlclose(lib); +} + + +static void *lsys_load (lua_State *L, const char *path, int seeglb) { + void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL)); + if (l_unlikely(lib == NULL)) + lua_pushstring(L, dlerror()); + return lib; +} + + +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = cast_func(dlsym(lib, sym)); + if (l_unlikely(f == NULL)) + lua_pushstring(L, dlerror()); + return f; +} + +/* }====================================================== */ + + + +#elif defined(LUA_DL_DLL) /* }{ */ +/* +** {====================================================================== +** This is an implementation of loadlib for Windows using native functions. +** ======================================================================= +*/ + +#include + + +/* +** optional flags for LoadLibraryEx +*/ +#if !defined(LUA_LLE_FLAGS) +#define LUA_LLE_FLAGS 0 +#endif + + +#undef setprogdir + + +/* +** Replace in the path (on the top of the stack) any occurrence +** of LUA_EXEC_DIR with the executable's path. +*/ +static void setprogdir (lua_State *L) { + char buff[MAX_PATH + 1]; + char *lb; + DWORD nsize = sizeof(buff)/sizeof(char); + DWORD n = GetModuleFileNameA(NULL, buff, nsize); /* get exec. name */ + if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL) + luaL_error(L, "unable to get ModuleFileName"); + else { + *lb = '\0'; /* cut name on the last '\\' to get the path */ + luaL_gsub(L, lua_tostring(L, -1), LUA_EXEC_DIR, buff); + lua_remove(L, -2); /* remove original string */ + } +} + + + + +static void pusherror (lua_State *L) { + int error = GetLastError(); + char buffer[128]; + if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error, 0, buffer, sizeof(buffer)/sizeof(char), NULL)) + lua_pushstring(L, buffer); + else + lua_pushfstring(L, "system error %d\n", error); +} + +static void lsys_unloadlib (void *lib) { + FreeLibrary((HMODULE)lib); +} + + +static void *lsys_load (lua_State *L, const char *path, int seeglb) { + HMODULE lib = LoadLibraryExA(path, NULL, LUA_LLE_FLAGS); + (void)(seeglb); /* not used: symbols are 'global' by default */ + if (lib == NULL) pusherror(L); + return lib; +} + + +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = (lua_CFunction)(voidf)GetProcAddress((HMODULE)lib, sym); + if (f == NULL) pusherror(L); + return f; +} + +/* }====================================================== */ + + +#else /* }{ */ +/* +** {====================================================== +** Fallback for other systems +** ======================================================= +*/ + +#undef LIB_FAIL +#define LIB_FAIL "absent" + + +#define DLMSG "dynamic libraries not enabled; check your Lua installation" + + +static void lsys_unloadlib (void *lib) { + (void)(lib); /* not used */ +} + + +static void *lsys_load (lua_State *L, const char *path, int seeglb) { + (void)(path); (void)(seeglb); /* not used */ + lua_pushliteral(L, DLMSG); + return NULL; +} + + +static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) { + (void)(lib); (void)(sym); /* not used */ + lua_pushliteral(L, DLMSG); + return NULL; +} + +/* }====================================================== */ +#endif /* } */ + + +/* +** {================================================================== +** Set Paths +** =================================================================== +*/ + +/* +** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment +** variables that Lua check to set its paths. +*/ +#if !defined(LUA_PATH_VAR) +#define LUA_PATH_VAR "LUA_PATH" +#endif + +#if !defined(LUA_CPATH_VAR) +#define LUA_CPATH_VAR "LUA_CPATH" +#endif + + + +/* +** return registry.LUA_NOENV as a boolean +*/ +static int noenv (lua_State *L) { + int b; + lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); + b = lua_toboolean(L, -1); + lua_pop(L, 1); /* remove value */ + return b; +} + + +/* +** Set a path +*/ +static void setpath (lua_State *L, const char *fieldname, + const char *envname, + const char *dft) { + const char *dftmark; + const char *nver = lua_pushfstring(L, "%s%s", envname, LUA_VERSUFFIX); + const char *path = getenv(nver); /* try versioned name */ + if (path == NULL) /* no versioned environment variable? */ + path = getenv(envname); /* try unversioned name */ + if (path == NULL || noenv(L)) /* no environment variable? */ + lua_pushstring(L, dft); /* use default */ + else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL) + lua_pushstring(L, path); /* nothing to change */ + else { /* path contains a ";;": insert default path in its place */ + size_t len = strlen(path); + luaL_Buffer b; + luaL_buffinit(L, &b); + if (path < dftmark) { /* is there a prefix before ';;'? */ + luaL_addlstring(&b, path, dftmark - path); /* add it */ + luaL_addchar(&b, *LUA_PATH_SEP); + } + luaL_addstring(&b, dft); /* add default */ + if (dftmark < path + len - 2) { /* is there a suffix after ';;'? */ + luaL_addchar(&b, *LUA_PATH_SEP); + luaL_addlstring(&b, dftmark + 2, (path + len - 2) - dftmark); + } + luaL_pushresult(&b); + } + setprogdir(L); + lua_setfield(L, -3, fieldname); /* package[fieldname] = path value */ + lua_pop(L, 1); /* pop versioned variable name ('nver') */ +} + +/* }================================================================== */ + + +/* +** return registry.CLIBS[path] +*/ +static void *checkclib (lua_State *L, const char *path) { + void *plib; + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_getfield(L, -1, path); + plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */ + lua_pop(L, 2); /* pop CLIBS table and 'plib' */ + return plib; +} + + +/* +** registry.CLIBS[path] = plib -- for queries +** registry.CLIBS[#CLIBS + 1] = plib -- also keep a list of all libraries +*/ +static void addtoclib (lua_State *L, const char *path, void *plib) { + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_pushlightuserdata(L, plib); + lua_pushvalue(L, -1); + lua_setfield(L, -3, path); /* CLIBS[path] = plib */ + lua_rawseti(L, -2, luaL_len(L, -2) + 1); /* CLIBS[#CLIBS + 1] = plib */ + lua_pop(L, 1); /* pop CLIBS table */ +} + + +/* +** __gc tag method for CLIBS table: calls 'lsys_unloadlib' for all lib +** handles in list CLIBS +*/ +static int gctm (lua_State *L) { + lua_Integer n = luaL_len(L, 1); + for (; n >= 1; n--) { /* for each handle, in reverse order */ + lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */ + lsys_unloadlib(lua_touserdata(L, -1)); + lua_pop(L, 1); /* pop handle */ + } + return 0; +} + + + +/* error codes for 'lookforfunc' */ +#define ERRLIB 1 +#define ERRFUNC 2 + +/* +** Look for a C function named 'sym' in a dynamically loaded library +** 'path'. +** First, check whether the library is already loaded; if not, try +** to load it. +** Then, if 'sym' is '*', return true (as library has been loaded). +** Otherwise, look for symbol 'sym' in the library and push a +** C function with that symbol. +** Return 0 and 'true' or a function in the stack; in case of +** errors, return an error code and an error message in the stack. +*/ +static int lookforfunc (lua_State *L, const char *path, const char *sym) { + void *reg = checkclib(L, path); /* check loaded C libraries */ + if (reg == NULL) { /* must load library? */ + reg = lsys_load(L, path, *sym == '*'); /* global symbols if 'sym'=='*' */ + if (reg == NULL) return ERRLIB; /* unable to load library */ + addtoclib(L, path, reg); + } + if (*sym == '*') { /* loading only library (no function)? */ + lua_pushboolean(L, 1); /* return 'true' */ + return 0; /* no errors */ + } + else { + lua_CFunction f = lsys_sym(L, reg, sym); + if (f == NULL) + return ERRFUNC; /* unable to find function */ + lua_pushcfunction(L, f); /* else create new function */ + return 0; /* no errors */ + } +} + + +static int ll_loadlib (lua_State *L) { + const char *path = luaL_checkstring(L, 1); + const char *init = luaL_checkstring(L, 2); + int stat = lookforfunc(L, path, init); + if (l_likely(stat == 0)) /* no errors? */ + return 1; /* return the loaded function */ + else { /* error; error message is on stack top */ + luaL_pushfail(L); + lua_insert(L, -2); + lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init"); + return 3; /* return fail, error message, and where */ + } +} + + + +/* +** {====================================================== +** 'require' function +** ======================================================= +*/ + + +static int readable (const char *filename) { + FILE *f = fopen(filename, "r"); /* try to open file */ + if (f == NULL) return 0; /* open failed */ + fclose(f); + return 1; +} + + +/* +** Get the next name in '*path' = 'name1;name2;name3;...', changing +** the ending ';' to '\0' to create a zero-terminated string. Return +** NULL when list ends. +*/ +static const char *getnextfilename (char **path, char *end) { + char *sep; + char *name = *path; + if (name == end) + return NULL; /* no more names */ + else if (*name == '\0') { /* from previous iteration? */ + *name = *LUA_PATH_SEP; /* restore separator */ + name++; /* skip it */ + } + sep = strchr(name, *LUA_PATH_SEP); /* find next separator */ + if (sep == NULL) /* separator not found? */ + sep = end; /* name goes until the end */ + *sep = '\0'; /* finish file name */ + *path = sep; /* will start next search from here */ + return name; +} + + +/* +** Given a path such as ";blabla.so;blublu.so", pushes the string +** +** no file 'blabla.so' +** no file 'blublu.so' +*/ +static void pusherrornotfound (lua_State *L, const char *path) { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addstring(&b, "no file '"); + luaL_addgsub(&b, path, LUA_PATH_SEP, "'\n\tno file '"); + luaL_addstring(&b, "'"); + luaL_pushresult(&b); +} + + +static const char *searchpath (lua_State *L, const char *name, + const char *path, + const char *sep, + const char *dirsep) { + luaL_Buffer buff; + char *pathname; /* path with name inserted */ + char *endpathname; /* its end */ + const char *filename; + /* separator is non-empty and appears in 'name'? */ + if (*sep != '\0' && strchr(name, *sep) != NULL) + name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */ + luaL_buffinit(L, &buff); + /* add path to the buffer, replacing marks ('?') with the file name */ + luaL_addgsub(&buff, path, LUA_PATH_MARK, name); + luaL_addchar(&buff, '\0'); + pathname = luaL_buffaddr(&buff); /* writable list of file names */ + endpathname = pathname + luaL_bufflen(&buff) - 1; + while ((filename = getnextfilename(&pathname, endpathname)) != NULL) { + if (readable(filename)) /* does file exist and is readable? */ + return lua_pushstring(L, filename); /* save and return name */ + } + luaL_pushresult(&buff); /* push path to create error message */ + pusherrornotfound(L, lua_tostring(L, -1)); /* create error message */ + return NULL; /* not found */ +} + + +static int ll_searchpath (lua_State *L) { + const char *f = searchpath(L, luaL_checkstring(L, 1), + luaL_checkstring(L, 2), + luaL_optstring(L, 3, "."), + luaL_optstring(L, 4, LUA_DIRSEP)); + if (f != NULL) return 1; + else { /* error message is on top of the stack */ + luaL_pushfail(L); + lua_insert(L, -2); + return 2; /* return fail + error message */ + } +} + + +static const char *findfile (lua_State *L, const char *name, + const char *pname, + const char *dirsep) { + const char *path; + lua_getfield(L, lua_upvalueindex(1), pname); + path = lua_tostring(L, -1); + if (l_unlikely(path == NULL)) + luaL_error(L, "'package.%s' must be a string", pname); + return searchpath(L, name, path, ".", dirsep); +} + + +static int checkload (lua_State *L, int stat, const char *filename) { + if (l_likely(stat)) { /* module loaded successfully? */ + lua_pushstring(L, filename); /* will be 2nd argument to module */ + return 2; /* return open function and file name */ + } + else + return luaL_error(L, "error loading module '%s' from file '%s':\n\t%s", + lua_tostring(L, 1), filename, lua_tostring(L, -1)); +} + + +static int searcher_Lua (lua_State *L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + filename = findfile(L, name, "path", LUA_LSUBSEP); + if (filename == NULL) return 1; /* module not found in this path */ + return checkload(L, (luaL_loadfile(L, filename) == LUA_OK), filename); +} + + +/* +** Try to find a load function for module 'modname' at file 'filename'. +** First, change '.' to '_' in 'modname'; then, if 'modname' has +** the form X-Y (that is, it has an "ignore mark"), build a function +** name "luaopen_X" and look for it. (For compatibility, if that +** fails, it also tries "luaopen_Y".) If there is no ignore mark, +** look for a function named "luaopen_modname". +*/ +static int loadfunc (lua_State *L, const char *filename, const char *modname) { + const char *openfunc; + const char *mark; + modname = luaL_gsub(L, modname, ".", LUA_OFSEP); + mark = strchr(modname, *LUA_IGMARK); + if (mark) { + int stat; + openfunc = lua_pushlstring(L, modname, mark - modname); + openfunc = lua_pushfstring(L, LUA_POF"%s", openfunc); + stat = lookforfunc(L, filename, openfunc); + if (stat != ERRFUNC) return stat; + modname = mark + 1; /* else go ahead and try old-style name */ + } + openfunc = lua_pushfstring(L, LUA_POF"%s", modname); + return lookforfunc(L, filename, openfunc); +} + + +static int searcher_C (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + const char *filename = findfile(L, name, "cpath", LUA_CSUBSEP); + if (filename == NULL) return 1; /* module not found in this path */ + return checkload(L, (loadfunc(L, filename, name) == 0), filename); +} + + +static int searcher_Croot (lua_State *L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + const char *p = strchr(name, '.'); + int stat; + if (p == NULL) return 0; /* is root */ + lua_pushlstring(L, name, p - name); + filename = findfile(L, lua_tostring(L, -1), "cpath", LUA_CSUBSEP); + if (filename == NULL) return 1; /* root not found */ + if ((stat = loadfunc(L, filename, name)) != 0) { + if (stat != ERRFUNC) + return checkload(L, 0, filename); /* real error */ + else { /* open function not found */ + lua_pushfstring(L, "no module '%s' in file '%s'", name, filename); + return 1; + } + } + lua_pushstring(L, filename); /* will be 2nd argument to module */ + return 2; +} + + +static int searcher_preload (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); + if (lua_getfield(L, -1, name) == LUA_TNIL) { /* not found? */ + lua_pushfstring(L, "no field package.preload['%s']", name); + return 1; + } + else { + lua_pushliteral(L, ":preload:"); + return 2; + } +} + + +static void findloader (lua_State *L, const char *name) { + int i; + luaL_Buffer msg; /* to build error message */ + /* push 'package.searchers' to index 3 in the stack */ + if (l_unlikely(lua_getfield(L, lua_upvalueindex(1), "searchers") + != LUA_TTABLE)) + luaL_error(L, "'package.searchers' must be a table"); + luaL_buffinit(L, &msg); + /* iterate over available searchers to find a loader */ + for (i = 1; ; i++) { + luaL_addstring(&msg, "\n\t"); /* error-message prefix */ + if (l_unlikely(lua_rawgeti(L, 3, i) == LUA_TNIL)) { /* no more searchers? */ + lua_pop(L, 1); /* remove nil */ + luaL_buffsub(&msg, 2); /* remove prefix */ + luaL_pushresult(&msg); /* create error message */ + luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1)); + } + lua_pushstring(L, name); + lua_call(L, 1, 2); /* call it */ + if (lua_isfunction(L, -2)) /* did it find a loader? */ + return; /* module loader found */ + else if (lua_isstring(L, -2)) { /* searcher returned error message? */ + lua_pop(L, 1); /* remove extra return */ + luaL_addvalue(&msg); /* concatenate error message */ + } + else { /* no error message */ + lua_pop(L, 2); /* remove both returns */ + luaL_buffsub(&msg, 2); /* remove prefix */ + } + } +} + + +static int ll_require (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + lua_settop(L, 1); /* LOADED table will be at index 2 */ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_getfield(L, 2, name); /* LOADED[name] */ + if (lua_toboolean(L, -1)) /* is it there? */ + return 1; /* package is already loaded */ + /* else must load package */ + lua_pop(L, 1); /* remove 'getfield' result */ + findloader(L, name); + lua_rotate(L, -2, 1); /* function <-> loader data */ + lua_pushvalue(L, 1); /* name is 1st argument to module loader */ + lua_pushvalue(L, -3); /* loader data is 2nd argument */ + /* stack: ...; loader data; loader function; mod. name; loader data */ + lua_call(L, 2, 1); /* run loader to load module */ + /* stack: ...; loader data; result from loader */ + if (!lua_isnil(L, -1)) /* non-nil return? */ + lua_setfield(L, 2, name); /* LOADED[name] = returned value */ + else + lua_pop(L, 1); /* pop nil */ + if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */ + lua_pushboolean(L, 1); /* use true as result */ + lua_copy(L, -1, -2); /* replace loader result */ + lua_setfield(L, 2, name); /* LOADED[name] = true */ + } + lua_rotate(L, -2, 1); /* loader data <-> module result */ + return 2; /* return module result and loader data */ +} + +/* }====================================================== */ + + + + +static const luaL_Reg pk_funcs[] = { + {"loadlib", ll_loadlib}, + {"searchpath", ll_searchpath}, + /* placeholders */ + {"preload", NULL}, + {"cpath", NULL}, + {"path", NULL}, + {"searchers", NULL}, + {"loaded", NULL}, + {NULL, NULL} +}; + + +static const luaL_Reg ll_funcs[] = { + {"require", ll_require}, + {NULL, NULL} +}; + + +static void createsearcherstable (lua_State *L) { + static const lua_CFunction searchers[] = { + searcher_preload, + searcher_Lua, + searcher_C, + searcher_Croot, + NULL + }; + int i; + /* create 'searchers' table */ + lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0); + /* fill it with predefined searchers */ + for (i=0; searchers[i] != NULL; i++) { + lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */ + lua_pushcclosure(L, searchers[i], 1); + lua_rawseti(L, -2, i+1); + } + lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */ +} + + +/* +** create table CLIBS to keep track of loaded C libraries, +** setting a finalizer to close all libraries when closing state. +*/ +static void createclibstable (lua_State *L) { + luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); /* create CLIBS table */ + lua_createtable(L, 0, 1); /* create metatable for CLIBS */ + lua_pushcfunction(L, gctm); + lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */ + lua_setmetatable(L, -2); +} + + +LUAMOD_API int luaopen_package (lua_State *L) { + createclibstable(L); + luaL_newlib(L, pk_funcs); /* create 'package' table */ + createsearcherstable(L); + /* set paths */ + setpath(L, "path", LUA_PATH_VAR, LUA_PATH_DEFAULT); + setpath(L, "cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT); + /* store config information */ + lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n" + LUA_EXEC_DIR "\n" LUA_IGMARK "\n"); + lua_setfield(L, -2, "config"); + /* set field 'loaded' */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + lua_setfield(L, -2, "loaded"); + /* set field 'preload' */ + luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE); + lua_setfield(L, -2, "preload"); + lua_pushglobaltable(L); + lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib */ + luaL_setfuncs(L, ll_funcs, 1); /* open lib into global table */ + lua_pop(L, 1); /* pop global table */ + return 1; /* return 'package' table */ +} + diff --git a/arm9/source/lua/lobject.c b/arm9/source/lua/lobject.c new file mode 100644 index 000000000..f73ffc6d9 --- /dev/null +++ b/arm9/source/lua/lobject.c @@ -0,0 +1,602 @@ +/* +** $Id: lobject.c $ +** Some generic functions over Lua objects +** See Copyright Notice in lua.h +*/ + +#define lobject_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lctype.h" +#include "ldebug.h" +#include "ldo.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "lvm.h" + + +/* +** Computes ceil(log2(x)) +*/ +int luaO_ceillog2 (unsigned int x) { + static const lu_byte log_2[256] = { /* log_2[i] = ceil(log2(i - 1)) */ + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + }; + int l = 0; + x--; + while (x >= 256) { l += 8; x >>= 8; } + return l + log_2[x]; +} + + +static lua_Integer intarith (lua_State *L, int op, lua_Integer v1, + lua_Integer v2) { + switch (op) { + case LUA_OPADD: return intop(+, v1, v2); + case LUA_OPSUB:return intop(-, v1, v2); + case LUA_OPMUL:return intop(*, v1, v2); + case LUA_OPMOD: return luaV_mod(L, v1, v2); + case LUA_OPIDIV: return luaV_idiv(L, v1, v2); + case LUA_OPBAND: return intop(&, v1, v2); + case LUA_OPBOR: return intop(|, v1, v2); + case LUA_OPBXOR: return intop(^, v1, v2); + case LUA_OPSHL: return luaV_shiftl(v1, v2); + case LUA_OPSHR: return luaV_shiftr(v1, v2); + case LUA_OPUNM: return intop(-, 0, v1); + case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1); + default: lua_assert(0); return 0; + } +} + + +static lua_Number numarith (lua_State *L, int op, lua_Number v1, + lua_Number v2) { + switch (op) { + case LUA_OPADD: return luai_numadd(L, v1, v2); + case LUA_OPSUB: return luai_numsub(L, v1, v2); + case LUA_OPMUL: return luai_nummul(L, v1, v2); + case LUA_OPDIV: return luai_numdiv(L, v1, v2); + case LUA_OPPOW: return luai_numpow(L, v1, v2); + case LUA_OPIDIV: return luai_numidiv(L, v1, v2); + case LUA_OPUNM: return luai_numunm(L, v1); + case LUA_OPMOD: return luaV_modf(L, v1, v2); + default: lua_assert(0); return 0; + } +} + + +int luaO_rawarith (lua_State *L, int op, const TValue *p1, const TValue *p2, + TValue *res) { + switch (op) { + case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: + case LUA_OPSHL: case LUA_OPSHR: + case LUA_OPBNOT: { /* operate only on integers */ + lua_Integer i1; lua_Integer i2; + if (tointegerns(p1, &i1) && tointegerns(p2, &i2)) { + setivalue(res, intarith(L, op, i1, i2)); + return 1; + } + else return 0; /* fail */ + } + case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */ + lua_Number n1; lua_Number n2; + if (tonumberns(p1, n1) && tonumberns(p2, n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return 1; + } + else return 0; /* fail */ + } + default: { /* other operations */ + lua_Number n1; lua_Number n2; + if (ttisinteger(p1) && ttisinteger(p2)) { + setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2))); + return 1; + } + else if (tonumberns(p1, n1) && tonumberns(p2, n2)) { + setfltvalue(res, numarith(L, op, n1, n2)); + return 1; + } + else return 0; /* fail */ + } + } +} + + +void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2, + StkId res) { + if (!luaO_rawarith(L, op, p1, p2, s2v(res))) { + /* could not perform raw operation; try metamethod */ + luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD)); + } +} + + +int luaO_hexavalue (int c) { + if (lisdigit(c)) return c - '0'; + else return (ltolower(c) - 'a') + 10; +} + + +static int isneg (const char **s) { + if (**s == '-') { (*s)++; return 1; } + else if (**s == '+') (*s)++; + return 0; +} + + + +/* +** {================================================================== +** Lua's implementation for 'lua_strx2number' +** =================================================================== +*/ + +#if !defined(lua_strx2number) + +/* maximum number of significant digits to read (to avoid overflows + even with single floats) */ +#define MAXSIGDIG 30 + +/* +** convert a hexadecimal numeric string to a number, following +** C99 specification for 'strtod' +*/ +static lua_Number lua_strx2number (const char *s, char **endptr) { + int dot = lua_getlocaledecpoint(); + lua_Number r = l_mathop(0.0); /* result (accumulator) */ + int sigdig = 0; /* number of significant digits */ + int nosigdig = 0; /* number of non-significant digits */ + int e = 0; /* exponent correction */ + int neg; /* 1 if number is negative */ + int hasdot = 0; /* true after seen a dot */ + *endptr = cast_charp(s); /* nothing is valid yet */ + while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ + neg = isneg(&s); /* check sign */ + if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */ + return l_mathop(0.0); /* invalid format (no '0x') */ + for (s += 2; ; s++) { /* skip '0x' and read numeral */ + if (*s == dot) { + if (hasdot) break; /* second dot? stop loop */ + else hasdot = 1; + } + else if (lisxdigit(cast_uchar(*s))) { + if (sigdig == 0 && *s == '0') /* non-significant digit (zero)? */ + nosigdig++; + else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */ + r = (r * l_mathop(16.0)) + luaO_hexavalue(*s); + else e++; /* too many digits; ignore, but still count for exponent */ + if (hasdot) e--; /* decimal digit? correct exponent */ + } + else break; /* neither a dot nor a digit */ + } + if (nosigdig + sigdig == 0) /* no digits? */ + return l_mathop(0.0); /* invalid format */ + *endptr = cast_charp(s); /* valid up to here */ + e *= 4; /* each digit multiplies/divides value by 2^4 */ + if (*s == 'p' || *s == 'P') { /* exponent part? */ + int exp1 = 0; /* exponent value */ + int neg1; /* exponent sign */ + s++; /* skip 'p' */ + neg1 = isneg(&s); /* sign */ + if (!lisdigit(cast_uchar(*s))) + return l_mathop(0.0); /* invalid; must have at least one digit */ + while (lisdigit(cast_uchar(*s))) /* read exponent */ + exp1 = exp1 * 10 + *(s++) - '0'; + if (neg1) exp1 = -exp1; + e += exp1; + *endptr = cast_charp(s); /* valid up to here */ + } + if (neg) r = -r; + return l_mathop(ldexp)(r, e); +} + +#endif +/* }====================================================== */ + + +/* maximum length of a numeral to be converted to a number */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + +/* +** Convert string 's' to a Lua number (put in 'result'). Return NULL on +** fail or the address of the ending '\0' on success. ('mode' == 'x') +** means a hexadecimal numeral. +*/ +static const char *l_str2dloc (const char *s, lua_Number *result, int mode) { + char *endptr; + *result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */ + : lua_str2number(s, &endptr); + if (endptr == s) return NULL; /* nothing recognized? */ + while (lisspace(cast_uchar(*endptr))) endptr++; /* skip trailing spaces */ + return (*endptr == '\0') ? endptr : NULL; /* OK iff no trailing chars */ +} + + +/* +** Convert string 's' to a Lua number (put in 'result') handling the +** current locale. +** This function accepts both the current locale or a dot as the radix +** mark. If the conversion fails, it may mean number has a dot but +** locale accepts something else. In that case, the code copies 's' +** to a buffer (because 's' is read-only), changes the dot to the +** current locale radix mark, and tries to convert again. +** The variable 'mode' checks for special characters in the string: +** - 'n' means 'inf' or 'nan' (which should be rejected) +** - 'x' means a hexadecimal numeral +** - '.' just optimizes the search for the common case (no special chars) +*/ +static const char *l_str2d (const char *s, lua_Number *result) { + const char *endptr; + const char *pmode = strpbrk(s, ".xXnN"); /* look for special chars */ + int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0; + if (mode == 'n') /* reject 'inf' and 'nan' */ + return NULL; + endptr = l_str2dloc(s, result, mode); /* try to convert */ + if (endptr == NULL) { /* failed? may be a different locale */ + char buff[L_MAXLENNUM + 1]; + const char *pdot = strchr(s, '.'); + if (pdot == NULL || strlen(s) > L_MAXLENNUM) + return NULL; /* string too long or no dot; fail */ + strcpy(buff, s); /* copy string to buffer */ + buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */ + endptr = l_str2dloc(buff, result, mode); /* try again */ + if (endptr != NULL) + endptr = s + (endptr - buff); /* make relative to 's' */ + } + return endptr; +} + + +#define MAXBY10 cast(lua_Unsigned, LUA_MAXINTEGER / 10) +#define MAXLASTD cast_int(LUA_MAXINTEGER % 10) + +static const char *l_str2int (const char *s, lua_Integer *result) { + lua_Unsigned a = 0; + int empty = 1; + int neg; + while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */ + neg = isneg(&s); + if (s[0] == '0' && + (s[1] == 'x' || s[1] == 'X')) { /* hex? */ + s += 2; /* skip '0x' */ + for (; lisxdigit(cast_uchar(*s)); s++) { + a = a * 16 + luaO_hexavalue(*s); + empty = 0; + } + } + else { /* decimal */ + for (; lisdigit(cast_uchar(*s)); s++) { + int d = *s - '0'; + if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ + return NULL; /* do not accept it (as integer) */ + a = a * 10 + d; + empty = 0; + } + } + while (lisspace(cast_uchar(*s))) s++; /* skip trailing spaces */ + if (empty || *s != '\0') return NULL; /* something wrong in the numeral */ + else { + *result = l_castU2S((neg) ? 0u - a : a); + return s; + } +} + + +size_t luaO_str2num (const char *s, TValue *o) { + lua_Integer i; lua_Number n; + const char *e; + if ((e = l_str2int(s, &i)) != NULL) { /* try as an integer */ + setivalue(o, i); + } + else if ((e = l_str2d(s, &n)) != NULL) { /* else try as a float */ + setfltvalue(o, n); + } + else + return 0; /* conversion failed */ + return (e - s) + 1; /* success; return string size */ +} + + +int luaO_utf8esc (char *buff, unsigned long x) { + int n = 1; /* number of bytes put in buffer (backwards) */ + lua_assert(x <= 0x7FFFFFFFu); + if (x < 0x80) /* ascii? */ + buff[UTF8BUFFSZ - 1] = cast_char(x); + else { /* need continuation bytes */ + unsigned int mfb = 0x3f; /* maximum that fits in first byte */ + do { /* add continuation bytes */ + buff[UTF8BUFFSZ - (n++)] = cast_char(0x80 | (x & 0x3f)); + x >>= 6; /* remove added bits */ + mfb >>= 1; /* now there is one less bit available in first byte */ + } while (x > mfb); /* still needs continuation byte? */ + buff[UTF8BUFFSZ - n] = cast_char((~mfb << 1) | x); /* add first byte */ + } + return n; +} + + +/* +** Maximum length of the conversion of a number to a string. Must be +** enough to accommodate both LUA_INTEGER_FMT and LUA_NUMBER_FMT. +** (For a long long int, this is 19 digits plus a sign and a final '\0', +** adding to 21. For a long double, it can go to a sign, 33 digits, +** the dot, an exponent letter, an exponent sign, 5 exponent digits, +** and a final '\0', adding to 43.) +*/ +#define MAXNUMBER2STR 44 + + +/* +** Convert a number object to a string, adding it to a buffer +*/ +static int tostringbuff (TValue *obj, char *buff) { + int len; + lua_assert(ttisnumber(obj)); + if (ttisinteger(obj)) + len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj)); + else { + len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj)); + if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */ + buff[len++] = lua_getlocaledecpoint(); + buff[len++] = '0'; /* adds '.0' to result */ + } + } + return len; +} + + +/* +** Convert a number object to a Lua string, replacing the value at 'obj' +*/ +void luaO_tostring (lua_State *L, TValue *obj) { + char buff[MAXNUMBER2STR]; + int len = tostringbuff(obj, buff); + setsvalue(L, obj, luaS_newlstr(L, buff, len)); +} + + + + +/* +** {================================================================== +** 'luaO_pushvfstring' +** =================================================================== +*/ + +/* +** Size for buffer space used by 'luaO_pushvfstring'. It should be +** (LUA_IDSIZE + MAXNUMBER2STR) + a minimal space for basic messages, +** so that 'luaG_addinfo' can work directly on the buffer. +*/ +#define BUFVFS (LUA_IDSIZE + MAXNUMBER2STR + 95) + +/* buffer used by 'luaO_pushvfstring' */ +typedef struct BuffFS { + lua_State *L; + int pushed; /* true if there is a part of the result on the stack */ + int blen; /* length of partial string in 'space' */ + char space[BUFVFS]; /* holds last part of the result */ +} BuffFS; + + +/* +** Push given string to the stack, as part of the result, and +** join it to previous partial result if there is one. +** It may call 'luaV_concat' while using one slot from EXTRA_STACK. +** This call cannot invoke metamethods, as both operands must be +** strings. It can, however, raise an error if the result is too +** long. In that case, 'luaV_concat' frees the extra slot before +** raising the error. +*/ +static void pushstr (BuffFS *buff, const char *str, size_t lstr) { + lua_State *L = buff->L; + setsvalue2s(L, L->top.p, luaS_newlstr(L, str, lstr)); + L->top.p++; /* may use one slot from EXTRA_STACK */ + if (!buff->pushed) /* no previous string on the stack? */ + buff->pushed = 1; /* now there is one */ + else /* join previous string with new one */ + luaV_concat(L, 2); +} + + +/* +** empty the buffer space into the stack +*/ +static void clearbuff (BuffFS *buff) { + pushstr(buff, buff->space, buff->blen); /* push buffer contents */ + buff->blen = 0; /* space now is empty */ +} + + +/* +** Get a space of size 'sz' in the buffer. If buffer has not enough +** space, empty it. 'sz' must fit in an empty buffer. +*/ +static char *getbuff (BuffFS *buff, int sz) { + lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS); + if (sz > BUFVFS - buff->blen) /* not enough space? */ + clearbuff(buff); + return buff->space + buff->blen; +} + + +#define addsize(b,sz) ((b)->blen += (sz)) + + +/* +** Add 'str' to the buffer. If string is larger than the buffer space, +** push the string directly to the stack. +*/ +static void addstr2buff (BuffFS *buff, const char *str, size_t slen) { + if (slen <= BUFVFS) { /* does string fit into buffer? */ + char *bf = getbuff(buff, cast_int(slen)); + memcpy(bf, str, slen); /* add string to buffer */ + addsize(buff, cast_int(slen)); + } + else { /* string larger than buffer */ + clearbuff(buff); /* string comes after buffer's content */ + pushstr(buff, str, slen); /* push string */ + } +} + + +/* +** Add a numeral to the buffer. +*/ +static void addnum2buff (BuffFS *buff, TValue *num) { + char *numbuff = getbuff(buff, MAXNUMBER2STR); + int len = tostringbuff(num, numbuff); /* format number into 'numbuff' */ + addsize(buff, len); +} + + +/* +** this function handles only '%d', '%c', '%f', '%p', '%s', and '%%' + conventional formats, plus Lua-specific '%I' and '%U' +*/ +const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { + BuffFS buff; /* holds last part of the result */ + const char *e; /* points to next '%' */ + buff.pushed = buff.blen = 0; + buff.L = L; + while ((e = strchr(fmt, '%')) != NULL) { + addstr2buff(&buff, fmt, e - fmt); /* add 'fmt' up to '%' */ + switch (*(e + 1)) { /* conversion specifier */ + case 's': { /* zero-terminated string */ + const char *s = va_arg(argp, char *); + if (s == NULL) s = "(null)"; + addstr2buff(&buff, s, strlen(s)); + break; + } + case 'c': { /* an 'int' as a character */ + char c = cast_uchar(va_arg(argp, int)); + addstr2buff(&buff, &c, sizeof(char)); + break; + } + case 'd': { /* an 'int' */ + TValue num; + setivalue(&num, va_arg(argp, int)); + addnum2buff(&buff, &num); + break; + } + case 'I': { /* a 'lua_Integer' */ + TValue num; + setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt))); + addnum2buff(&buff, &num); + break; + } + case 'f': { /* a 'lua_Number' */ + TValue num; + setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber))); + addnum2buff(&buff, &num); + break; + } + case 'p': { /* a pointer */ + const int sz = 3 * sizeof(void*) + 8; /* enough space for '%p' */ + char *bf = getbuff(&buff, sz); + void *p = va_arg(argp, void *); + int len = lua_pointer2str(bf, sz, p); + addsize(&buff, len); + break; + } + case 'U': { /* a 'long' as a UTF-8 sequence */ + char bf[UTF8BUFFSZ]; + int len = luaO_utf8esc(bf, va_arg(argp, long)); + addstr2buff(&buff, bf + UTF8BUFFSZ - len, len); + break; + } + case '%': { + addstr2buff(&buff, "%", 1); + break; + } + default: { + luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'", + *(e + 1)); + } + } + fmt = e + 2; /* skip '%' and the specifier */ + } + addstr2buff(&buff, fmt, strlen(fmt)); /* rest of 'fmt' */ + clearbuff(&buff); /* empty buffer into the stack */ + lua_assert(buff.pushed == 1); + return svalue(s2v(L->top.p - 1)); +} + + +const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { + const char *msg; + va_list argp; + va_start(argp, fmt); + msg = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + return msg; +} + +/* }================================================================== */ + + +#define RETS "..." +#define PRE "[string \"" +#define POS "\"]" + +#define addstr(a,b,l) ( memcpy(a,b,(l) * sizeof(char)), a += (l) ) + +void luaO_chunkid (char *out, const char *source, size_t srclen) { + size_t bufflen = LUA_IDSIZE; /* free space in buffer */ + if (*source == '=') { /* 'literal' source */ + if (srclen <= bufflen) /* small enough? */ + memcpy(out, source + 1, srclen * sizeof(char)); + else { /* truncate it */ + addstr(out, source + 1, bufflen - 1); + *out = '\0'; + } + } + else if (*source == '@') { /* file name */ + if (srclen <= bufflen) /* small enough? */ + memcpy(out, source + 1, srclen * sizeof(char)); + else { /* add '...' before rest of name */ + addstr(out, RETS, LL(RETS)); + bufflen -= LL(RETS); + memcpy(out, source + 1 + srclen - bufflen, bufflen * sizeof(char)); + } + } + else { /* string; format as [string "source"] */ + const char *nl = strchr(source, '\n'); /* find first new line (if any) */ + addstr(out, PRE, LL(PRE)); /* add prefix */ + bufflen -= LL(PRE RETS POS) + 1; /* save space for prefix+suffix+'\0' */ + if (srclen < bufflen && nl == NULL) { /* small one-line source? */ + addstr(out, source, srclen); /* keep it */ + } + else { + if (nl != NULL) srclen = nl - source; /* stop at first newline */ + if (srclen > bufflen) srclen = bufflen; + addstr(out, source, srclen); + addstr(out, RETS, LL(RETS)); + } + memcpy(out, POS, (LL(POS) + 1) * sizeof(char)); + } +} + diff --git a/arm9/source/lua/lobject.h b/arm9/source/lua/lobject.h new file mode 100644 index 000000000..556608e4a --- /dev/null +++ b/arm9/source/lua/lobject.h @@ -0,0 +1,815 @@ +/* +** $Id: lobject.h $ +** Type definitions for Lua objects +** See Copyright Notice in lua.h +*/ + + +#ifndef lobject_h +#define lobject_h + + +#include + + +#include "llimits.h" +#include "lua.h" + + +/* +** Extra types for collectable non-values +*/ +#define LUA_TUPVAL LUA_NUMTYPES /* upvalues */ +#define LUA_TPROTO (LUA_NUMTYPES+1) /* function prototypes */ +#define LUA_TDEADKEY (LUA_NUMTYPES+2) /* removed keys in tables */ + + + +/* +** number of all possible types (including LUA_TNONE but excluding DEADKEY) +*/ +#define LUA_TOTALTYPES (LUA_TPROTO + 2) + + +/* +** tags for Tagged Values have the following use of bits: +** bits 0-3: actual tag (a LUA_T* constant) +** bits 4-5: variant bits +** bit 6: whether value is collectable +*/ + +/* add variant bits to a type */ +#define makevariant(t,v) ((t) | ((v) << 4)) + + + +/* +** Union of all Lua values +*/ +typedef union Value { + struct GCObject *gc; /* collectable objects */ + void *p; /* light userdata */ + lua_CFunction f; /* light C functions */ + lua_Integer i; /* integer numbers */ + lua_Number n; /* float numbers */ + /* not used, but may avoid warnings for uninitialized value */ + lu_byte ub; +} Value; + + +/* +** Tagged Values. This is the basic representation of values in Lua: +** an actual value plus a tag with its type. +*/ + +#define TValuefields Value value_; lu_byte tt_ + +typedef struct TValue { + TValuefields; +} TValue; + + +#define val_(o) ((o)->value_) +#define valraw(o) (val_(o)) + + +/* raw type tag of a TValue */ +#define rawtt(o) ((o)->tt_) + +/* tag with no variants (bits 0-3) */ +#define novariant(t) ((t) & 0x0F) + +/* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */ +#define withvariant(t) ((t) & 0x3F) +#define ttypetag(o) withvariant(rawtt(o)) + +/* type of a TValue */ +#define ttype(o) (novariant(rawtt(o))) + + +/* Macros to test type */ +#define checktag(o,t) (rawtt(o) == (t)) +#define checktype(o,t) (ttype(o) == (t)) + + +/* Macros for internal tests */ + +/* collectable object has the same tag as the original value */ +#define righttt(obj) (ttypetag(obj) == gcvalue(obj)->tt) + +/* +** Any value being manipulated by the program either is non +** collectable, or the collectable object has the right tag +** and it is not dead. The option 'L == NULL' allows other +** macros using this one to be used where L is not available. +*/ +#define checkliveness(L,obj) \ + ((void)L, lua_longassert(!iscollectable(obj) || \ + (righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj)))))) + + +/* Macros to set values */ + +/* set a value's tag */ +#define settt_(o,t) ((o)->tt_=(t)) + + +/* main macro to copy values (from 'obj2' to 'obj1') */ +#define setobj(L,obj1,obj2) \ + { TValue *io1=(obj1); const TValue *io2=(obj2); \ + io1->value_ = io2->value_; settt_(io1, io2->tt_); \ + checkliveness(L,io1); lua_assert(!isnonstrictnil(io1)); } + +/* +** Different types of assignments, according to source and destination. +** (They are mostly equal now, but may be different in the future.) +*/ + +/* from stack to stack */ +#define setobjs2s(L,o1,o2) setobj(L,s2v(o1),s2v(o2)) +/* to stack (not from same stack) */ +#define setobj2s(L,o1,o2) setobj(L,s2v(o1),o2) +/* from table to same table */ +#define setobjt2t setobj +/* to new object */ +#define setobj2n setobj +/* to table */ +#define setobj2t setobj + + +/* +** Entries in a Lua stack. Field 'tbclist' forms a list of all +** to-be-closed variables active in this stack. Dummy entries are +** used when the distance between two tbc variables does not fit +** in an unsigned short. They are represented by delta==0, and +** their real delta is always the maximum value that fits in +** that field. +*/ +typedef union StackValue { + TValue val; + struct { + TValuefields; + unsigned short delta; + } tbclist; +} StackValue; + + +/* index to stack elements */ +typedef StackValue *StkId; + + +/* +** When reallocating the stack, change all pointers to the stack into +** proper offsets. +*/ +typedef union { + StkId p; /* actual pointer */ + ptrdiff_t offset; /* used while the stack is being reallocated */ +} StkIdRel; + + +/* convert a 'StackValue' to a 'TValue' */ +#define s2v(o) (&(o)->val) + + + +/* +** {================================================================== +** Nil +** =================================================================== +*/ + +/* Standard nil */ +#define LUA_VNIL makevariant(LUA_TNIL, 0) + +/* Empty slot (which might be different from a slot containing nil) */ +#define LUA_VEMPTY makevariant(LUA_TNIL, 1) + +/* Value returned for a key not found in a table (absent key) */ +#define LUA_VABSTKEY makevariant(LUA_TNIL, 2) + + +/* macro to test for (any kind of) nil */ +#define ttisnil(v) checktype((v), LUA_TNIL) + + +/* macro to test for a standard nil */ +#define ttisstrictnil(o) checktag((o), LUA_VNIL) + + +#define setnilvalue(obj) settt_(obj, LUA_VNIL) + + +#define isabstkey(v) checktag((v), LUA_VABSTKEY) + + +/* +** macro to detect non-standard nils (used only in assertions) +*/ +#define isnonstrictnil(v) (ttisnil(v) && !ttisstrictnil(v)) + + +/* +** By default, entries with any kind of nil are considered empty. +** (In any definition, values associated with absent keys must also +** be accepted as empty.) +*/ +#define isempty(v) ttisnil(v) + + +/* macro defining a value corresponding to an absent key */ +#define ABSTKEYCONSTANT {NULL}, LUA_VABSTKEY + + +/* mark an entry as empty */ +#define setempty(v) settt_(v, LUA_VEMPTY) + + + +/* }================================================================== */ + + +/* +** {================================================================== +** Booleans +** =================================================================== +*/ + + +#define LUA_VFALSE makevariant(LUA_TBOOLEAN, 0) +#define LUA_VTRUE makevariant(LUA_TBOOLEAN, 1) + +#define ttisboolean(o) checktype((o), LUA_TBOOLEAN) +#define ttisfalse(o) checktag((o), LUA_VFALSE) +#define ttistrue(o) checktag((o), LUA_VTRUE) + + +#define l_isfalse(o) (ttisfalse(o) || ttisnil(o)) + + +#define setbfvalue(obj) settt_(obj, LUA_VFALSE) +#define setbtvalue(obj) settt_(obj, LUA_VTRUE) + +/* }================================================================== */ + + +/* +** {================================================================== +** Threads +** =================================================================== +*/ + +#define LUA_VTHREAD makevariant(LUA_TTHREAD, 0) + +#define ttisthread(o) checktag((o), ctb(LUA_VTHREAD)) + +#define thvalue(o) check_exp(ttisthread(o), gco2th(val_(o).gc)) + +#define setthvalue(L,obj,x) \ + { TValue *io = (obj); lua_State *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTHREAD)); \ + checkliveness(L,io); } + +#define setthvalue2s(L,o,t) setthvalue(L,s2v(o),t) + +/* }================================================================== */ + + +/* +** {================================================================== +** Collectable Objects +** =================================================================== +*/ + +/* +** Common Header for all collectable objects (in macro form, to be +** included in other objects) +*/ +#define CommonHeader struct GCObject *next; lu_byte tt; lu_byte marked + + +/* Common type for all collectable objects */ +typedef struct GCObject { + CommonHeader; +} GCObject; + + +/* Bit mark for collectable types */ +#define BIT_ISCOLLECTABLE (1 << 6) + +#define iscollectable(o) (rawtt(o) & BIT_ISCOLLECTABLE) + +/* mark a tag as collectable */ +#define ctb(t) ((t) | BIT_ISCOLLECTABLE) + +#define gcvalue(o) check_exp(iscollectable(o), val_(o).gc) + +#define gcvalueraw(v) ((v).gc) + +#define setgcovalue(L,obj,x) \ + { TValue *io = (obj); GCObject *i_g=(x); \ + val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); } + +/* }================================================================== */ + + +/* +** {================================================================== +** Numbers +** =================================================================== +*/ + +/* Variant tags for numbers */ +#define LUA_VNUMINT makevariant(LUA_TNUMBER, 0) /* integer numbers */ +#define LUA_VNUMFLT makevariant(LUA_TNUMBER, 1) /* float numbers */ + +#define ttisnumber(o) checktype((o), LUA_TNUMBER) +#define ttisfloat(o) checktag((o), LUA_VNUMFLT) +#define ttisinteger(o) checktag((o), LUA_VNUMINT) + +#define nvalue(o) check_exp(ttisnumber(o), \ + (ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o))) +#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n) +#define ivalue(o) check_exp(ttisinteger(o), val_(o).i) + +#define fltvalueraw(v) ((v).n) +#define ivalueraw(v) ((v).i) + +#define setfltvalue(obj,x) \ + { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_VNUMFLT); } + +#define chgfltvalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); } + +#define setivalue(obj,x) \ + { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_VNUMINT); } + +#define chgivalue(obj,x) \ + { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); } + +/* }================================================================== */ + + +/* +** {================================================================== +** Strings +** =================================================================== +*/ + +/* Variant tags for strings */ +#define LUA_VSHRSTR makevariant(LUA_TSTRING, 0) /* short strings */ +#define LUA_VLNGSTR makevariant(LUA_TSTRING, 1) /* long strings */ + +#define ttisstring(o) checktype((o), LUA_TSTRING) +#define ttisshrstring(o) checktag((o), ctb(LUA_VSHRSTR)) +#define ttislngstring(o) checktag((o), ctb(LUA_VLNGSTR)) + +#define tsvalueraw(v) (gco2ts((v).gc)) + +#define tsvalue(o) check_exp(ttisstring(o), gco2ts(val_(o).gc)) + +#define setsvalue(L,obj,x) \ + { TValue *io = (obj); TString *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \ + checkliveness(L,io); } + +/* set a string to the stack */ +#define setsvalue2s(L,o,s) setsvalue(L,s2v(o),s) + +/* set a string to a new object */ +#define setsvalue2n setsvalue + + +/* +** Header for a string value. +*/ +typedef struct TString { + CommonHeader; + lu_byte extra; /* reserved words for short strings; "has hash" for longs */ + lu_byte shrlen; /* length for short strings */ + unsigned int hash; + union { + size_t lnglen; /* length for long strings */ + struct TString *hnext; /* linked list for hash table */ + } u; + char contents[1]; +} TString; + + + +/* +** Get the actual string (array of bytes) from a 'TString'. +*/ +#define getstr(ts) ((ts)->contents) + + +/* get the actual string (array of bytes) from a Lua value */ +#define svalue(o) getstr(tsvalue(o)) + +/* get string length from 'TString *s' */ +#define tsslen(s) ((s)->tt == LUA_VSHRSTR ? (s)->shrlen : (s)->u.lnglen) + +/* get string length from 'TValue *o' */ +#define vslen(o) tsslen(tsvalue(o)) + +/* }================================================================== */ + + +/* +** {================================================================== +** Userdata +** =================================================================== +*/ + + +/* +** Light userdata should be a variant of userdata, but for compatibility +** reasons they are also different types. +*/ +#define LUA_VLIGHTUSERDATA makevariant(LUA_TLIGHTUSERDATA, 0) + +#define LUA_VUSERDATA makevariant(LUA_TUSERDATA, 0) + +#define ttislightuserdata(o) checktag((o), LUA_VLIGHTUSERDATA) +#define ttisfulluserdata(o) checktag((o), ctb(LUA_VUSERDATA)) + +#define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p) +#define uvalue(o) check_exp(ttisfulluserdata(o), gco2u(val_(o).gc)) + +#define pvalueraw(v) ((v).p) + +#define setpvalue(obj,x) \ + { TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_VLIGHTUSERDATA); } + +#define setuvalue(L,obj,x) \ + { TValue *io = (obj); Udata *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VUSERDATA)); \ + checkliveness(L,io); } + + +/* Ensures that addresses after this type are always fully aligned. */ +typedef union UValue { + TValue uv; + LUAI_MAXALIGN; /* ensures maximum alignment for udata bytes */ +} UValue; + + +/* +** Header for userdata with user values; +** memory area follows the end of this structure. +*/ +typedef struct Udata { + CommonHeader; + unsigned short nuvalue; /* number of user values */ + size_t len; /* number of bytes */ + struct Table *metatable; + GCObject *gclist; + UValue uv[1]; /* user values */ +} Udata; + + +/* +** Header for userdata with no user values. These userdata do not need +** to be gray during GC, and therefore do not need a 'gclist' field. +** To simplify, the code always use 'Udata' for both kinds of userdata, +** making sure it never accesses 'gclist' on userdata with no user values. +** This structure here is used only to compute the correct size for +** this representation. (The 'bindata' field in its end ensures correct +** alignment for binary data following this header.) +*/ +typedef struct Udata0 { + CommonHeader; + unsigned short nuvalue; /* number of user values */ + size_t len; /* number of bytes */ + struct Table *metatable; + union {LUAI_MAXALIGN;} bindata; +} Udata0; + + +/* compute the offset of the memory area of a userdata */ +#define udatamemoffset(nuv) \ + ((nuv) == 0 ? offsetof(Udata0, bindata) \ + : offsetof(Udata, uv) + (sizeof(UValue) * (nuv))) + +/* get the address of the memory block inside 'Udata' */ +#define getudatamem(u) (cast_charp(u) + udatamemoffset((u)->nuvalue)) + +/* compute the size of a userdata */ +#define sizeudata(nuv,nb) (udatamemoffset(nuv) + (nb)) + +/* }================================================================== */ + + +/* +** {================================================================== +** Prototypes +** =================================================================== +*/ + +#define LUA_VPROTO makevariant(LUA_TPROTO, 0) + + +/* +** Description of an upvalue for function prototypes +*/ +typedef struct Upvaldesc { + TString *name; /* upvalue name (for debug information) */ + lu_byte instack; /* whether it is in stack (register) */ + lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ + lu_byte kind; /* kind of corresponding variable */ +} Upvaldesc; + + +/* +** Description of a local variable for function prototypes +** (used for debug information) +*/ +typedef struct LocVar { + TString *varname; + int startpc; /* first point where variable is active */ + int endpc; /* first point where variable is dead */ +} LocVar; + + +/* +** Associates the absolute line source for a given instruction ('pc'). +** The array 'lineinfo' gives, for each instruction, the difference in +** lines from the previous instruction. When that difference does not +** fit into a byte, Lua saves the absolute line for that instruction. +** (Lua also saves the absolute line periodically, to speed up the +** computation of a line number: we can use binary search in the +** absolute-line array, but we must traverse the 'lineinfo' array +** linearly to compute a line.) +*/ +typedef struct AbsLineInfo { + int pc; + int line; +} AbsLineInfo; + +/* +** Function Prototypes +*/ +typedef struct Proto { + CommonHeader; + lu_byte numparams; /* number of fixed (named) parameters */ + lu_byte is_vararg; + lu_byte maxstacksize; /* number of registers needed by this function */ + int sizeupvalues; /* size of 'upvalues' */ + int sizek; /* size of 'k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of 'p' */ + int sizelocvars; + int sizeabslineinfo; /* size of 'abslineinfo' */ + int linedefined; /* debug information */ + int lastlinedefined; /* debug information */ + TValue *k; /* constants used by the function */ + Instruction *code; /* opcodes */ + struct Proto **p; /* functions defined inside the function */ + Upvaldesc *upvalues; /* upvalue information */ + ls_byte *lineinfo; /* information about source lines (debug information) */ + AbsLineInfo *abslineinfo; /* idem */ + LocVar *locvars; /* information about local variables (debug information) */ + TString *source; /* used for debug information */ + GCObject *gclist; +} Proto; + +/* }================================================================== */ + + +/* +** {================================================================== +** Functions +** =================================================================== +*/ + +#define LUA_VUPVAL makevariant(LUA_TUPVAL, 0) + + +/* Variant tags for functions */ +#define LUA_VLCL makevariant(LUA_TFUNCTION, 0) /* Lua closure */ +#define LUA_VLCF makevariant(LUA_TFUNCTION, 1) /* light C function */ +#define LUA_VCCL makevariant(LUA_TFUNCTION, 2) /* C closure */ + +#define ttisfunction(o) checktype(o, LUA_TFUNCTION) +#define ttisLclosure(o) checktag((o), ctb(LUA_VLCL)) +#define ttislcf(o) checktag((o), LUA_VLCF) +#define ttisCclosure(o) checktag((o), ctb(LUA_VCCL)) +#define ttisclosure(o) (ttisLclosure(o) || ttisCclosure(o)) + + +#define isLfunction(o) ttisLclosure(o) + +#define clvalue(o) check_exp(ttisclosure(o), gco2cl(val_(o).gc)) +#define clLvalue(o) check_exp(ttisLclosure(o), gco2lcl(val_(o).gc)) +#define fvalue(o) check_exp(ttislcf(o), val_(o).f) +#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc)) + +#define fvalueraw(v) ((v).f) + +#define setclLvalue(L,obj,x) \ + { TValue *io = (obj); LClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VLCL)); \ + checkliveness(L,io); } + +#define setclLvalue2s(L,o,cl) setclLvalue(L,s2v(o),cl) + +#define setfvalue(obj,x) \ + { TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_VLCF); } + +#define setclCvalue(L,obj,x) \ + { TValue *io = (obj); CClosure *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VCCL)); \ + checkliveness(L,io); } + + +/* +** Upvalues for Lua closures +*/ +typedef struct UpVal { + CommonHeader; + union { + TValue *p; /* points to stack or to its own value */ + ptrdiff_t offset; /* used while the stack is being reallocated */ + } v; + union { + struct { /* (when open) */ + struct UpVal *next; /* linked list */ + struct UpVal **previous; + } open; + TValue value; /* the value (when closed) */ + } u; +} UpVal; + + + +#define ClosureHeader \ + CommonHeader; lu_byte nupvalues; GCObject *gclist + +typedef struct CClosure { + ClosureHeader; + lua_CFunction f; + TValue upvalue[1]; /* list of upvalues */ +} CClosure; + + +typedef struct LClosure { + ClosureHeader; + struct Proto *p; + UpVal *upvals[1]; /* list of upvalues */ +} LClosure; + + +typedef union Closure { + CClosure c; + LClosure l; +} Closure; + + +#define getproto(o) (clLvalue(o)->p) + +/* }================================================================== */ + + +/* +** {================================================================== +** Tables +** =================================================================== +*/ + +#define LUA_VTABLE makevariant(LUA_TTABLE, 0) + +#define ttistable(o) checktag((o), ctb(LUA_VTABLE)) + +#define hvalue(o) check_exp(ttistable(o), gco2t(val_(o).gc)) + +#define sethvalue(L,obj,x) \ + { TValue *io = (obj); Table *x_ = (x); \ + val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTABLE)); \ + checkliveness(L,io); } + +#define sethvalue2s(L,o,h) sethvalue(L,s2v(o),h) + + +/* +** Nodes for Hash tables: A pack of two TValue's (key-value pairs) +** plus a 'next' field to link colliding entries. The distribution +** of the key's fields ('key_tt' and 'key_val') not forming a proper +** 'TValue' allows for a smaller size for 'Node' both in 4-byte +** and 8-byte alignments. +*/ +typedef union Node { + struct NodeKey { + TValuefields; /* fields for value */ + lu_byte key_tt; /* key type */ + int next; /* for chaining */ + Value key_val; /* key value */ + } u; + TValue i_val; /* direct access to node's value as a proper 'TValue' */ +} Node; + + +/* copy a value into a key */ +#define setnodekey(L,node,obj) \ + { Node *n_=(node); const TValue *io_=(obj); \ + n_->u.key_val = io_->value_; n_->u.key_tt = io_->tt_; \ + checkliveness(L,io_); } + + +/* copy a value from a key */ +#define getnodekey(L,obj,node) \ + { TValue *io_=(obj); const Node *n_=(node); \ + io_->value_ = n_->u.key_val; io_->tt_ = n_->u.key_tt; \ + checkliveness(L,io_); } + + +/* +** About 'alimit': if 'isrealasize(t)' is true, then 'alimit' is the +** real size of 'array'. Otherwise, the real size of 'array' is the +** smallest power of two not smaller than 'alimit' (or zero iff 'alimit' +** is zero); 'alimit' is then used as a hint for #t. +*/ + +#define BITRAS (1 << 7) +#define isrealasize(t) (!((t)->flags & BITRAS)) +#define setrealasize(t) ((t)->flags &= cast_byte(~BITRAS)) +#define setnorealasize(t) ((t)->flags |= BITRAS) + + +typedef struct Table { + CommonHeader; + lu_byte flags; /* 1<

u.key_tt) +#define keyval(node) ((node)->u.key_val) + +#define keyisnil(node) (keytt(node) == LUA_TNIL) +#define keyisinteger(node) (keytt(node) == LUA_VNUMINT) +#define keyival(node) (keyval(node).i) +#define keyisshrstr(node) (keytt(node) == ctb(LUA_VSHRSTR)) +#define keystrval(node) (gco2ts(keyval(node).gc)) + +#define setnilkey(node) (keytt(node) = LUA_TNIL) + +#define keyiscollectable(n) (keytt(n) & BIT_ISCOLLECTABLE) + +#define gckey(n) (keyval(n).gc) +#define gckeyN(n) (keyiscollectable(n) ? gckey(n) : NULL) + + +/* +** Dead keys in tables have the tag DEADKEY but keep their original +** gcvalue. This distinguishes them from regular keys but allows them to +** be found when searched in a special way. ('next' needs that to find +** keys removed from a table during a traversal.) +*/ +#define setdeadkey(node) (keytt(node) = LUA_TDEADKEY) +#define keyisdead(node) (keytt(node) == LUA_TDEADKEY) + +/* }================================================================== */ + + + +/* +** 'module' operation for hashing (size is always a power of 2) +*/ +#define lmod(s,size) \ + (check_exp((size&(size-1))==0, (cast_int((s) & ((size)-1))))) + + +#define twoto(x) (1<<(x)) +#define sizenode(t) (twoto((t)->lsizenode)) + + +/* size of buffer for 'luaO_utf8esc' function */ +#define UTF8BUFFSZ 8 + +LUAI_FUNC int luaO_utf8esc (char *buff, unsigned long x); +LUAI_FUNC int luaO_ceillog2 (unsigned int x); +LUAI_FUNC int luaO_rawarith (lua_State *L, int op, const TValue *p1, + const TValue *p2, TValue *res); +LUAI_FUNC void luaO_arith (lua_State *L, int op, const TValue *p1, + const TValue *p2, StkId res); +LUAI_FUNC size_t luaO_str2num (const char *s, TValue *o); +LUAI_FUNC int luaO_hexavalue (int c); +LUAI_FUNC void luaO_tostring (lua_State *L, TValue *obj); +LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, + va_list argp); +LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); +LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t srclen); + + +#endif + diff --git a/arm9/source/lua/lopcodes.c b/arm9/source/lua/lopcodes.c new file mode 100644 index 000000000..c67aa227c --- /dev/null +++ b/arm9/source/lua/lopcodes.c @@ -0,0 +1,104 @@ +/* +** $Id: lopcodes.c $ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#define lopcodes_c +#define LUA_CORE + +#include "lprefix.h" + + +#include "lopcodes.h" + + +/* ORDER OP */ + +LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { +/* MM OT IT T A mode opcode */ + opmode(0, 0, 0, 0, 1, iABC) /* OP_MOVE */ + ,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADI */ + ,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADF */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADK */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADKX */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADFALSE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LFALSESKIP */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADTRUE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADNIL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETUPVAL */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETUPVAL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABUP */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABLE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETFIELD */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABUP */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABLE */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETI */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETFIELD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_NEWTABLE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SELF */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUBK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MULK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MODK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_POWK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIVK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIVK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BANDK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BORK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXORK */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHRI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHLI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUB */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MUL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MOD */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_POW */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIV */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIV */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BAND */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BOR */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXOR */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHL */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHR */ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBIN */ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINI*/ + ,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINK*/ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_UNM */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BNOT */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_NOT */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_LEN */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_CONCAT */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_CLOSE */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_TBC */ + ,opmode(0, 0, 0, 0, 0, isJ) /* OP_JMP */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQ */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LT */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LE */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQK */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LTI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_LEI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_GTI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_GEI */ + ,opmode(0, 0, 0, 1, 0, iABC) /* OP_TEST */ + ,opmode(0, 0, 0, 1, 1, iABC) /* OP_TESTSET */ + ,opmode(0, 1, 1, 0, 1, iABC) /* OP_CALL */ + ,opmode(0, 1, 1, 0, 1, iABC) /* OP_TAILCALL */ + ,opmode(0, 0, 1, 0, 0, iABC) /* OP_RETURN */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN0 */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN1 */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORLOOP */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORPREP */ + ,opmode(0, 0, 0, 0, 0, iABx) /* OP_TFORPREP */ + ,opmode(0, 0, 0, 0, 0, iABC) /* OP_TFORCALL */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_TFORLOOP */ + ,opmode(0, 0, 1, 0, 0, iABC) /* OP_SETLIST */ + ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ + ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ + ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ + ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ +}; + diff --git a/arm9/source/lua/lopcodes.h b/arm9/source/lua/lopcodes.h new file mode 100644 index 000000000..4c5514539 --- /dev/null +++ b/arm9/source/lua/lopcodes.h @@ -0,0 +1,405 @@ +/* +** $Id: lopcodes.h $ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lopcodes_h +#define lopcodes_h + +#include "llimits.h" + + +/*=========================================================================== + We assume that instructions are unsigned 32-bit integers. + All instructions have an opcode in the first 7 bits. + Instructions can have the following formats: + + 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 + 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +iABC C(8) | B(8) |k| A(8) | Op(7) | +iABx Bx(17) | A(8) | Op(7) | +iAsBx sBx (signed)(17) | A(8) | Op(7) | +iAx Ax(25) | Op(7) | +isJ sJ (signed)(25) | Op(7) | + + A signed argument is represented in excess K: the represented value is + the written unsigned value minus K, where K is half the maximum for the + corresponding unsigned argument. +===========================================================================*/ + + +enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */ + + +/* +** size and position of opcode arguments. +*/ +#define SIZE_C 8 +#define SIZE_B 8 +#define SIZE_Bx (SIZE_C + SIZE_B + 1) +#define SIZE_A 8 +#define SIZE_Ax (SIZE_Bx + SIZE_A) +#define SIZE_sJ (SIZE_Bx + SIZE_A) + +#define SIZE_OP 7 + +#define POS_OP 0 + +#define POS_A (POS_OP + SIZE_OP) +#define POS_k (POS_A + SIZE_A) +#define POS_B (POS_k + 1) +#define POS_C (POS_B + SIZE_B) + +#define POS_Bx POS_k + +#define POS_Ax POS_A + +#define POS_sJ POS_A + + +/* +** limits for opcode arguments. +** we use (signed) 'int' to manipulate most arguments, +** so they must fit in ints. +*/ + +/* Check whether type 'int' has at least 'b' bits ('b' < 32) */ +#define L_INTHASBITS(b) ((UINT_MAX >> ((b) - 1)) >= 1) + + +#if L_INTHASBITS(SIZE_Bx) +#define MAXARG_Bx ((1<>1) /* 'sBx' is signed */ + + +#if L_INTHASBITS(SIZE_Ax) +#define MAXARG_Ax ((1<> 1) + + +#define MAXARG_A ((1<> 1) + +#define int2sC(i) ((i) + OFFSET_sC) +#define sC2int(i) ((i) - OFFSET_sC) + + +/* creates a mask with 'n' 1 bits at position 'p' */ +#define MASK1(n,p) ((~((~(Instruction)0)<<(n)))<<(p)) + +/* creates a mask with 'n' 0 bits at position 'p' */ +#define MASK0(n,p) (~MASK1(n,p)) + +/* +** the following macros help to manipulate instructions +*/ + +#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))) +#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ + ((cast(Instruction, o)<>(pos)) & MASK1(size,0))) +#define setarg(i,v,pos,size) ((i) = (((i)&MASK0(size,pos)) | \ + ((cast(Instruction, v)<> sC */ +OP_SHLI,/* A B sC R[A] := sC << R[B] */ + +OP_ADD,/* A B C R[A] := R[B] + R[C] */ +OP_SUB,/* A B C R[A] := R[B] - R[C] */ +OP_MUL,/* A B C R[A] := R[B] * R[C] */ +OP_MOD,/* A B C R[A] := R[B] % R[C] */ +OP_POW,/* A B C R[A] := R[B] ^ R[C] */ +OP_DIV,/* A B C R[A] := R[B] / R[C] */ +OP_IDIV,/* A B C R[A] := R[B] // R[C] */ + +OP_BAND,/* A B C R[A] := R[B] & R[C] */ +OP_BOR,/* A B C R[A] := R[B] | R[C] */ +OP_BXOR,/* A B C R[A] := R[B] ~ R[C] */ +OP_SHL,/* A B C R[A] := R[B] << R[C] */ +OP_SHR,/* A B C R[A] := R[B] >> R[C] */ + +OP_MMBIN,/* A B C call C metamethod over R[A] and R[B] (*) */ +OP_MMBINI,/* A sB C k call C metamethod over R[A] and sB */ +OP_MMBINK,/* A B C k call C metamethod over R[A] and K[B] */ + +OP_UNM,/* A B R[A] := -R[B] */ +OP_BNOT,/* A B R[A] := ~R[B] */ +OP_NOT,/* A B R[A] := not R[B] */ +OP_LEN,/* A B R[A] := #R[B] (length operator) */ + +OP_CONCAT,/* A B R[A] := R[A].. ... ..R[A + B - 1] */ + +OP_CLOSE,/* A close all upvalues >= R[A] */ +OP_TBC,/* A mark variable A "to be closed" */ +OP_JMP,/* sJ pc += sJ */ +OP_EQ,/* A B k if ((R[A] == R[B]) ~= k) then pc++ */ +OP_LT,/* A B k if ((R[A] < R[B]) ~= k) then pc++ */ +OP_LE,/* A B k if ((R[A] <= R[B]) ~= k) then pc++ */ + +OP_EQK,/* A B k if ((R[A] == K[B]) ~= k) then pc++ */ +OP_EQI,/* A sB k if ((R[A] == sB) ~= k) then pc++ */ +OP_LTI,/* A sB k if ((R[A] < sB) ~= k) then pc++ */ +OP_LEI,/* A sB k if ((R[A] <= sB) ~= k) then pc++ */ +OP_GTI,/* A sB k if ((R[A] > sB) ~= k) then pc++ */ +OP_GEI,/* A sB k if ((R[A] >= sB) ~= k) then pc++ */ + +OP_TEST,/* A k if (not R[A] == k) then pc++ */ +OP_TESTSET,/* A B k if (not R[B] == k) then pc++ else R[A] := R[B] (*) */ + +OP_CALL,/* A B C R[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1]) */ +OP_TAILCALL,/* A B C k return R[A](R[A+1], ... ,R[A+B-1]) */ + +OP_RETURN,/* A B C k return R[A], ... ,R[A+B-2] (see note) */ +OP_RETURN0,/* return */ +OP_RETURN1,/* A return R[A] */ + +OP_FORLOOP,/* A Bx update counters; if loop continues then pc-=Bx; */ +OP_FORPREP,/* A Bx ; + if not to run then pc+=Bx+1; */ + +OP_TFORPREP,/* A Bx create upvalue for R[A + 3]; pc+=Bx */ +OP_TFORCALL,/* A C R[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2]); */ +OP_TFORLOOP,/* A Bx if R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx } */ + +OP_SETLIST,/* A B C k R[A][C+i] := R[A+i], 1 <= i <= B */ + +OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ + +OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ + +OP_VARARGPREP,/*A (adjust vararg parameters) */ + +OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ +} OpCode; + + +#define NUM_OPCODES ((int)(OP_EXTRAARG) + 1) + + + +/*=========================================================================== + Notes: + + (*) Opcode OP_LFALSESKIP is used to convert a condition to a boolean + value, in a code equivalent to (not cond ? false : true). (It + produces false and skips the next instruction producing true.) + + (*) Opcodes OP_MMBIN and variants follow each arithmetic and + bitwise opcode. If the operation succeeds, it skips this next + opcode. Otherwise, this opcode calls the corresponding metamethod. + + (*) Opcode OP_TESTSET is used in short-circuit expressions that need + both to jump and to produce a value, such as (a = b or c). + + (*) In OP_CALL, if (B == 0) then B = top - A. If (C == 0), then + 'top' is set to last_result+1, so next open instruction (OP_CALL, + OP_RETURN*, OP_SETLIST) may use 'top'. + + (*) In OP_VARARG, if (C == 0) then use actual number of varargs and + set top (like in OP_CALL with C == 0). + + (*) In OP_RETURN, if (B == 0) then return up to 'top'. + + (*) In OP_LOADKX and OP_NEWTABLE, the next instruction is always + OP_EXTRAARG. + + (*) In OP_SETLIST, if (B == 0) then real B = 'top'; if k, then + real C = EXTRAARG _ C (the bits of EXTRAARG concatenated with the + bits of C). + + (*) In OP_NEWTABLE, B is log2 of the hash size (which is always a + power of 2) plus 1, or zero for size zero. If not k, the array size + is C. Otherwise, the array size is EXTRAARG _ C. + + (*) For comparisons, k specifies what condition the test should accept + (true or false). + + (*) In OP_MMBINI/OP_MMBINK, k means the arguments were flipped + (the constant is the first operand). + + (*) All 'skips' (pc++) assume that next instruction is a jump. + + (*) In instructions OP_RETURN/OP_TAILCALL, 'k' specifies that the + function builds upvalues, which may need to be closed. C > 0 means + the function is vararg, so that its 'func' must be corrected before + returning; in this case, (C - 1) is its number of fixed parameters. + + (*) In comparisons with an immediate operand, C signals whether the + original operand was a float. (It must be corrected in case of + metamethods.) + +===========================================================================*/ + + +/* +** masks for instruction properties. The format is: +** bits 0-2: op mode +** bit 3: instruction set register A +** bit 4: operator is a test (next instruction must be a jump) +** bit 5: instruction uses 'L->top' set by previous instruction (when B == 0) +** bit 6: instruction sets 'L->top' for next instruction (when C == 0) +** bit 7: instruction is an MM instruction (call a metamethod) +*/ + +LUAI_DDEC(const lu_byte luaP_opmodes[NUM_OPCODES];) + +#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 7)) +#define testAMode(m) (luaP_opmodes[m] & (1 << 3)) +#define testTMode(m) (luaP_opmodes[m] & (1 << 4)) +#define testITMode(m) (luaP_opmodes[m] & (1 << 5)) +#define testOTMode(m) (luaP_opmodes[m] & (1 << 6)) +#define testMMMode(m) (luaP_opmodes[m] & (1 << 7)) + +/* "out top" (set top for next instruction) */ +#define isOT(i) \ + ((testOTMode(GET_OPCODE(i)) && GETARG_C(i) == 0) || \ + GET_OPCODE(i) == OP_TAILCALL) + +/* "in top" (uses top from previous instruction) */ +#define isIT(i) (testITMode(GET_OPCODE(i)) && GETARG_B(i) == 0) + +#define opmode(mm,ot,it,t,a,m) \ + (((mm) << 7) | ((ot) << 6) | ((it) << 5) | ((t) << 4) | ((a) << 3) | (m)) + + +/* number of list items to accumulate before a SETLIST instruction */ +#define LFIELDS_PER_FLUSH 50 + +#endif diff --git a/arm9/source/lua/lopnames.h b/arm9/source/lua/lopnames.h new file mode 100644 index 000000000..965cec9bf --- /dev/null +++ b/arm9/source/lua/lopnames.h @@ -0,0 +1,103 @@ +/* +** $Id: lopnames.h $ +** Opcode names +** See Copyright Notice in lua.h +*/ + +#if !defined(lopnames_h) +#define lopnames_h + +#include + + +/* ORDER OP */ + +static const char *const opnames[] = { + "MOVE", + "LOADI", + "LOADF", + "LOADK", + "LOADKX", + "LOADFALSE", + "LFALSESKIP", + "LOADTRUE", + "LOADNIL", + "GETUPVAL", + "SETUPVAL", + "GETTABUP", + "GETTABLE", + "GETI", + "GETFIELD", + "SETTABUP", + "SETTABLE", + "SETI", + "SETFIELD", + "NEWTABLE", + "SELF", + "ADDI", + "ADDK", + "SUBK", + "MULK", + "MODK", + "POWK", + "DIVK", + "IDIVK", + "BANDK", + "BORK", + "BXORK", + "SHRI", + "SHLI", + "ADD", + "SUB", + "MUL", + "MOD", + "POW", + "DIV", + "IDIV", + "BAND", + "BOR", + "BXOR", + "SHL", + "SHR", + "MMBIN", + "MMBINI", + "MMBINK", + "UNM", + "BNOT", + "NOT", + "LEN", + "CONCAT", + "CLOSE", + "TBC", + "JMP", + "EQ", + "LT", + "LE", + "EQK", + "EQI", + "LTI", + "LEI", + "GTI", + "GEI", + "TEST", + "TESTSET", + "CALL", + "TAILCALL", + "RETURN", + "RETURN0", + "RETURN1", + "FORLOOP", + "FORPREP", + "TFORPREP", + "TFORCALL", + "TFORLOOP", + "SETLIST", + "CLOSURE", + "VARARG", + "VARARGPREP", + "EXTRAARG", + NULL +}; + +#endif + diff --git a/arm9/source/lua/loslib.c b/arm9/source/lua/loslib.c new file mode 100644 index 000000000..ad5a92768 --- /dev/null +++ b/arm9/source/lua/loslib.c @@ -0,0 +1,428 @@ +/* +** $Id: loslib.c $ +** Standard Operating System library +** See Copyright Notice in lua.h +*/ + +#define loslib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** {================================================================== +** List of valid conversion specifiers for the 'strftime' function; +** options are grouped by length; group of length 2 start with '||'. +** =================================================================== +*/ +#if !defined(LUA_STRFTIMEOPTIONS) /* { */ + +#if defined(LUA_USE_WINDOWS) +#define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYzZ%" \ + "||" "#c#x#d#H#I#j#m#M#S#U#w#W#y#Y" /* two-char options */ +#elif defined(LUA_USE_C89) /* ANSI C 89 (only 1-char options) */ +#define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYZ%" +#else /* C99 specification */ +#define LUA_STRFTIMEOPTIONS "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%" \ + "||" "EcECExEXEyEY" "OdOeOHOIOmOMOSOuOUOVOwOWOy" /* two-char options */ +#endif + +#endif /* } */ +/* }================================================================== */ + + +/* +** {================================================================== +** Configuration for time-related stuff +** =================================================================== +*/ + +/* +** type to represent time_t in Lua +*/ +#if !defined(LUA_NUMTIME) /* { */ + +#define l_timet lua_Integer +#define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) +#define l_gettime(L,arg) luaL_checkinteger(L, arg) + +#else /* }{ */ + +#define l_timet lua_Number +#define l_pushtime(L,t) lua_pushnumber(L,(lua_Number)(t)) +#define l_gettime(L,arg) luaL_checknumber(L, arg) + +#endif /* } */ + + +#if !defined(l_gmtime) /* { */ +/* +** By default, Lua uses gmtime/localtime, except when POSIX is available, +** where it uses gmtime_r/localtime_r +*/ + +#if defined(LUA_USE_POSIX) /* { */ + +#define l_gmtime(t,r) gmtime_r(t,r) +#define l_localtime(t,r) localtime_r(t,r) + +#else /* }{ */ + +/* ISO C definitions */ +#define l_gmtime(t,r) ((void)(r)->tm_sec, gmtime(t)) +#define l_localtime(t,r) ((void)(r)->tm_sec, localtime(t)) + +#endif /* } */ + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Configuration for 'tmpnam': +** By default, Lua uses tmpnam except when POSIX is available, where +** it uses mkstemp. +** =================================================================== +*/ +#if !defined(lua_tmpnam) /* { */ + +#if defined(LUA_USE_POSIX) /* { */ + +#include + +#define LUA_TMPNAMBUFSIZE 32 + +#if !defined(LUA_TMPNAMTEMPLATE) +#define LUA_TMPNAMTEMPLATE "/tmp/lua_XXXXXX" +#endif + +#define lua_tmpnam(b,e) { \ + strcpy(b, LUA_TMPNAMTEMPLATE); \ + e = mkstemp(b); \ + if (e != -1) close(e); \ + e = (e == -1); } + +#else /* }{ */ + +/* ISO C definitions */ +#define LUA_TMPNAMBUFSIZE L_tmpnam +#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } + +#endif /* } */ + +#endif /* } */ +/* }================================================================== */ + + +#if !defined(l_system) +#if defined(LUA_USE_IOS) +/* Despite claiming to be ISO C, iOS does not implement 'system'. */ +#define l_system(cmd) ((cmd) == NULL ? 0 : -1) +#else +#define l_system(cmd) system(cmd) /* default definition */ +#endif +#endif + + +static int os_execute (lua_State *L) { + const char *cmd = luaL_optstring(L, 1, NULL); + int stat; + errno = 0; + stat = l_system(cmd); + if (cmd != NULL) + return luaL_execresult(L, stat); + else { + lua_pushboolean(L, stat); /* true if there is a shell */ + return 1; + } +} + + +static int os_remove (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + return luaL_fileresult(L, remove(filename) == 0, filename); +} + + +static int os_rename (lua_State *L) { + const char *fromname = luaL_checkstring(L, 1); + const char *toname = luaL_checkstring(L, 2); + return luaL_fileresult(L, rename(fromname, toname) == 0, NULL); +} + + +static int os_tmpname (lua_State *L) { + char buff[LUA_TMPNAMBUFSIZE]; + int err; + lua_tmpnam(buff, err); + if (l_unlikely(err)) + return luaL_error(L, "unable to generate a unique filename"); + lua_pushstring(L, buff); + return 1; +} + + +static int os_getenv (lua_State *L) { + lua_pushstring(L, getenv(luaL_checkstring(L, 1))); /* if NULL push nil */ + return 1; +} + + +static int os_clock (lua_State *L) { + lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC); + return 1; +} + + +/* +** {====================================================== +** Time/Date operations +** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S, +** wday=%w+1, yday=%j, isdst=? } +** ======================================================= +*/ + +/* +** About the overflow check: an overflow cannot occur when time +** is represented by a lua_Integer, because either lua_Integer is +** large enough to represent all int fields or it is not large enough +** to represent a time that cause a field to overflow. However, if +** times are represented as doubles and lua_Integer is int, then the +** time 0x1.e1853b0d184f6p+55 would cause an overflow when adding 1900 +** to compute the year. +*/ +static void setfield (lua_State *L, const char *key, int value, int delta) { + #if (defined(LUA_NUMTIME) && LUA_MAXINTEGER <= INT_MAX) + if (l_unlikely(value > LUA_MAXINTEGER - delta)) + luaL_error(L, "field '%s' is out-of-bound", key); + #endif + lua_pushinteger(L, (lua_Integer)value + delta); + lua_setfield(L, -2, key); +} + + +static void setboolfield (lua_State *L, const char *key, int value) { + if (value < 0) /* undefined? */ + return; /* does not set field */ + lua_pushboolean(L, value); + lua_setfield(L, -2, key); +} + + +/* +** Set all fields from structure 'tm' in the table on top of the stack +*/ +static void setallfields (lua_State *L, struct tm *stm) { + setfield(L, "year", stm->tm_year, 1900); + setfield(L, "month", stm->tm_mon, 1); + setfield(L, "day", stm->tm_mday, 0); + setfield(L, "hour", stm->tm_hour, 0); + setfield(L, "min", stm->tm_min, 0); + setfield(L, "sec", stm->tm_sec, 0); + setfield(L, "yday", stm->tm_yday, 1); + setfield(L, "wday", stm->tm_wday, 1); + setboolfield(L, "isdst", stm->tm_isdst); +} + + +static int getboolfield (lua_State *L, const char *key) { + int res; + res = (lua_getfield(L, -1, key) == LUA_TNIL) ? -1 : lua_toboolean(L, -1); + lua_pop(L, 1); + return res; +} + + +static int getfield (lua_State *L, const char *key, int d, int delta) { + int isnum; + int t = lua_getfield(L, -1, key); /* get field and its type */ + lua_Integer res = lua_tointegerx(L, -1, &isnum); + if (!isnum) { /* field is not an integer? */ + if (l_unlikely(t != LUA_TNIL)) /* some other value? */ + return luaL_error(L, "field '%s' is not an integer", key); + else if (l_unlikely(d < 0)) /* absent field; no default? */ + return luaL_error(L, "field '%s' missing in date table", key); + res = d; + } + else { + if (!(res >= 0 ? res - delta <= INT_MAX : INT_MIN + delta <= res)) + return luaL_error(L, "field '%s' is out-of-bound", key); + res -= delta; + } + lua_pop(L, 1); + return (int)res; +} + + +static const char *checkoption (lua_State *L, const char *conv, + ptrdiff_t convlen, char *buff) { + const char *option = LUA_STRFTIMEOPTIONS; + int oplen = 1; /* length of options being checked */ + for (; *option != '\0' && oplen <= convlen; option += oplen) { + if (*option == '|') /* next block? */ + oplen++; /* will check options with next length (+1) */ + else if (memcmp(conv, option, oplen) == 0) { /* match? */ + memcpy(buff, conv, oplen); /* copy valid option to buffer */ + buff[oplen] = '\0'; + return conv + oplen; /* return next item */ + } + } + luaL_argerror(L, 1, + lua_pushfstring(L, "invalid conversion specifier '%%%s'", conv)); + return conv; /* to avoid warnings */ +} + + +static time_t l_checktime (lua_State *L, int arg) { + l_timet t = l_gettime(L, arg); + luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); + return (time_t)t; +} + + +/* maximum size for an individual 'strftime' item */ +#define SIZETIMEFMT 250 + + +static int os_date (lua_State *L) { + size_t slen; + const char *s = luaL_optlstring(L, 1, "%c", &slen); + time_t t = luaL_opt(L, l_checktime, 2, time(NULL)); + const char *se = s + slen; /* 's' end */ + struct tm tmr, *stm; + if (*s == '!') { /* UTC? */ + stm = l_gmtime(&t, &tmr); + s++; /* skip '!' */ + } + else + stm = l_localtime(&t, &tmr); + if (stm == NULL) /* invalid date? */ + return luaL_error(L, + "date result cannot be represented in this installation"); + if (strcmp(s, "*t") == 0) { + lua_createtable(L, 0, 9); /* 9 = number of fields */ + setallfields(L, stm); + } + else { + char cc[4]; /* buffer for individual conversion specifiers */ + luaL_Buffer b; + cc[0] = '%'; + luaL_buffinit(L, &b); + while (s < se) { + if (*s != '%') /* not a conversion specifier? */ + luaL_addchar(&b, *s++); + else { + size_t reslen; + char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT); + s++; /* skip '%' */ + s = checkoption(L, s, se - s, cc + 1); /* copy specifier to 'cc' */ + reslen = strftime(buff, SIZETIMEFMT, cc, stm); + luaL_addsize(&b, reslen); + } + } + luaL_pushresult(&b); + } + return 1; +} + + +static int os_time (lua_State *L) { + time_t t; + if (lua_isnoneornil(L, 1)) /* called without args? */ + t = time(NULL); /* get current time */ + else { + struct tm ts; + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); /* make sure table is at the top */ + ts.tm_year = getfield(L, "year", -1, 1900); + ts.tm_mon = getfield(L, "month", -1, 1); + ts.tm_mday = getfield(L, "day", -1, 0); + ts.tm_hour = getfield(L, "hour", 12, 0); + ts.tm_min = getfield(L, "min", 0, 0); + ts.tm_sec = getfield(L, "sec", 0, 0); + ts.tm_isdst = getboolfield(L, "isdst"); + t = mktime(&ts); + setallfields(L, &ts); /* update fields with normalized values */ + } + if (t != (time_t)(l_timet)t || t == (time_t)(-1)) + return luaL_error(L, + "time result cannot be represented in this installation"); + l_pushtime(L, t); + return 1; +} + + +static int os_difftime (lua_State *L) { + time_t t1 = l_checktime(L, 1); + time_t t2 = l_checktime(L, 2); + lua_pushnumber(L, (lua_Number)difftime(t1, t2)); + return 1; +} + +/* }====================================================== */ + + +static int os_setlocale (lua_State *L) { + static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, + LC_NUMERIC, LC_TIME}; + static const char *const catnames[] = {"all", "collate", "ctype", "monetary", + "numeric", "time", NULL}; + const char *l = luaL_optstring(L, 1, NULL); + int op = luaL_checkoption(L, 2, "all", catnames); + lua_pushstring(L, setlocale(cat[op], l)); + return 1; +} + + +static int os_exit (lua_State *L) { + int status; + if (lua_isboolean(L, 1)) + status = (lua_toboolean(L, 1) ? EXIT_SUCCESS : EXIT_FAILURE); + else + status = (int)luaL_optinteger(L, 1, EXIT_SUCCESS); + if (lua_toboolean(L, 2)) + lua_close(L); + if (L) exit(status); /* 'if' to avoid warnings for unreachable 'return' */ + return 0; +} + + +static const luaL_Reg syslib[] = { + {"clock", os_clock}, + {"date", os_date}, + {"difftime", os_difftime}, + {"execute", os_execute}, + {"exit", os_exit}, + {"getenv", os_getenv}, + {"remove", os_remove}, + {"rename", os_rename}, + {"setlocale", os_setlocale}, + {"time", os_time}, + {"tmpname", os_tmpname}, + {NULL, NULL} +}; + +/* }====================================================== */ + + + +LUAMOD_API int luaopen_os (lua_State *L) { + luaL_newlib(L, syslib); + return 1; +} + diff --git a/arm9/source/lua/lparser.c b/arm9/source/lua/lparser.c new file mode 100644 index 000000000..b745f236f --- /dev/null +++ b/arm9/source/lua/lparser.c @@ -0,0 +1,1967 @@ +/* +** $Id: lparser.c $ +** Lua Parser +** See Copyright Notice in lua.h +*/ + +#define lparser_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" + + + +/* maximum number of local variables per function (must be smaller + than 250, due to the bytecode format) */ +#define MAXVARS 200 + + +#define hasmultret(k) ((k) == VCALL || (k) == VVARARG) + + +/* because all strings are unified by the scanner, the parser + can use pointer equality for string equality */ +#define eqstr(a,b) ((a) == (b)) + + +/* +** nodes for block list (list of active blocks) +*/ +typedef struct BlockCnt { + struct BlockCnt *previous; /* chain */ + int firstlabel; /* index of first label in this block */ + int firstgoto; /* index of first pending goto in this block */ + lu_byte nactvar; /* # active locals outside the block */ + lu_byte upval; /* true if some variable in the block is an upvalue */ + lu_byte isloop; /* true if 'block' is a loop */ + lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ +} BlockCnt; + + + +/* +** prototypes for recursive non-terminal functions +*/ +static void statement (LexState *ls); +static void expr (LexState *ls, expdesc *v); + + +static l_noret error_expected (LexState *ls, int token) { + luaX_syntaxerror(ls, + luaO_pushfstring(ls->L, "%s expected", luaX_token2str(ls, token))); +} + + +static l_noret errorlimit (FuncState *fs, int limit, const char *what) { + lua_State *L = fs->ls->L; + const char *msg; + int line = fs->f->linedefined; + const char *where = (line == 0) + ? "main function" + : luaO_pushfstring(L, "function at line %d", line); + msg = luaO_pushfstring(L, "too many %s (limit is %d) in %s", + what, limit, where); + luaX_syntaxerror(fs->ls, msg); +} + + +static void checklimit (FuncState *fs, int v, int l, const char *what) { + if (v > l) errorlimit(fs, l, what); +} + + +/* +** Test whether next token is 'c'; if so, skip it. +*/ +static int testnext (LexState *ls, int c) { + if (ls->t.token == c) { + luaX_next(ls); + return 1; + } + else return 0; +} + + +/* +** Check that next token is 'c'. +*/ +static void check (LexState *ls, int c) { + if (ls->t.token != c) + error_expected(ls, c); +} + + +/* +** Check that next token is 'c' and skip it. +*/ +static void checknext (LexState *ls, int c) { + check(ls, c); + luaX_next(ls); +} + + +#define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); } + + +/* +** Check that next token is 'what' and skip it. In case of error, +** raise an error that the expected 'what' should match a 'who' +** in line 'where' (if that is not the current line). +*/ +static void check_match (LexState *ls, int what, int who, int where) { + if (l_unlikely(!testnext(ls, what))) { + if (where == ls->linenumber) /* all in the same line? */ + error_expected(ls, what); /* do not need a complex message */ + else { + luaX_syntaxerror(ls, luaO_pushfstring(ls->L, + "%s expected (to close %s at line %d)", + luaX_token2str(ls, what), luaX_token2str(ls, who), where)); + } + } +} + + +static TString *str_checkname (LexState *ls) { + TString *ts; + check(ls, TK_NAME); + ts = ls->t.seminfo.ts; + luaX_next(ls); + return ts; +} + + +static void init_exp (expdesc *e, expkind k, int i) { + e->f = e->t = NO_JUMP; + e->k = k; + e->u.info = i; +} + + +static void codestring (expdesc *e, TString *s) { + e->f = e->t = NO_JUMP; + e->k = VKSTR; + e->u.strval = s; +} + + +static void codename (LexState *ls, expdesc *e) { + codestring(e, str_checkname(ls)); +} + + +/* +** Register a new local variable in the active 'Proto' (for debug +** information). +*/ +static int registerlocalvar (LexState *ls, FuncState *fs, TString *varname) { + Proto *f = fs->f; + int oldsize = f->sizelocvars; + luaM_growvector(ls->L, f->locvars, fs->ndebugvars, f->sizelocvars, + LocVar, SHRT_MAX, "local variables"); + while (oldsize < f->sizelocvars) + f->locvars[oldsize++].varname = NULL; + f->locvars[fs->ndebugvars].varname = varname; + f->locvars[fs->ndebugvars].startpc = fs->pc; + luaC_objbarrier(ls->L, f, varname); + return fs->ndebugvars++; +} + + +/* +** Create a new local variable with the given 'name'. Return its index +** in the function. +*/ +static int new_localvar (LexState *ls, TString *name) { + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Dyndata *dyd = ls->dyd; + Vardesc *var; + checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, + MAXVARS, "local variables"); + luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, + dyd->actvar.size, Vardesc, USHRT_MAX, "local variables"); + var = &dyd->actvar.arr[dyd->actvar.n++]; + var->vd.kind = VDKREG; /* default */ + var->vd.name = name; + return dyd->actvar.n - 1 - fs->firstlocal; +} + +#define new_localvarliteral(ls,v) \ + new_localvar(ls, \ + luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1)); + + + +/* +** Return the "variable description" (Vardesc) of a given variable. +** (Unless noted otherwise, all variables are referred to by their +** compiler indices.) +*/ +static Vardesc *getlocalvardesc (FuncState *fs, int vidx) { + return &fs->ls->dyd->actvar.arr[fs->firstlocal + vidx]; +} + + +/* +** Convert 'nvar', a compiler index level, to its corresponding +** register. For that, search for the highest variable below that level +** that is in a register and uses its register index ('ridx') plus one. +*/ +static int reglevel (FuncState *fs, int nvar) { + while (nvar-- > 0) { + Vardesc *vd = getlocalvardesc(fs, nvar); /* get previous variable */ + if (vd->vd.kind != RDKCTC) /* is in a register? */ + return vd->vd.ridx + 1; + } + return 0; /* no variables in registers */ +} + + +/* +** Return the number of variables in the register stack for the given +** function. +*/ +int luaY_nvarstack (FuncState *fs) { + return reglevel(fs, fs->nactvar); +} + + +/* +** Get the debug-information entry for current variable 'vidx'. +*/ +static LocVar *localdebuginfo (FuncState *fs, int vidx) { + Vardesc *vd = getlocalvardesc(fs, vidx); + if (vd->vd.kind == RDKCTC) + return NULL; /* no debug info. for constants */ + else { + int idx = vd->vd.pidx; + lua_assert(idx < fs->ndebugvars); + return &fs->f->locvars[idx]; + } +} + + +/* +** Create an expression representing variable 'vidx' +*/ +static void init_var (FuncState *fs, expdesc *e, int vidx) { + e->f = e->t = NO_JUMP; + e->k = VLOCAL; + e->u.var.vidx = vidx; + e->u.var.ridx = getlocalvardesc(fs, vidx)->vd.ridx; +} + + +/* +** Raises an error if variable described by 'e' is read only +*/ +static void check_readonly (LexState *ls, expdesc *e) { + FuncState *fs = ls->fs; + TString *varname = NULL; /* to be set if variable is const */ + switch (e->k) { + case VCONST: { + varname = ls->dyd->actvar.arr[e->u.info].vd.name; + break; + } + case VLOCAL: { + Vardesc *vardesc = getlocalvardesc(fs, e->u.var.vidx); + if (vardesc->vd.kind != VDKREG) /* not a regular variable? */ + varname = vardesc->vd.name; + break; + } + case VUPVAL: { + Upvaldesc *up = &fs->f->upvalues[e->u.info]; + if (up->kind != VDKREG) + varname = up->name; + break; + } + default: + return; /* other cases cannot be read-only */ + } + if (varname) { + const char *msg = luaO_pushfstring(ls->L, + "attempt to assign to const variable '%s'", getstr(varname)); + luaK_semerror(ls, msg); /* error */ + } +} + + +/* +** Start the scope for the last 'nvars' created variables. +*/ +static void adjustlocalvars (LexState *ls, int nvars) { + FuncState *fs = ls->fs; + int reglevel = luaY_nvarstack(fs); + int i; + for (i = 0; i < nvars; i++) { + int vidx = fs->nactvar++; + Vardesc *var = getlocalvardesc(fs, vidx); + var->vd.ridx = reglevel++; + var->vd.pidx = registerlocalvar(ls, fs, var->vd.name); + } +} + + +/* +** Close the scope for all variables up to level 'tolevel'. +** (debug info.) +*/ +static void removevars (FuncState *fs, int tolevel) { + fs->ls->dyd->actvar.n -= (fs->nactvar - tolevel); + while (fs->nactvar > tolevel) { + LocVar *var = localdebuginfo(fs, --fs->nactvar); + if (var) /* does it have debug information? */ + var->endpc = fs->pc; + } +} + + +/* +** Search the upvalues of the function 'fs' for one +** with the given 'name'. +*/ +static int searchupvalue (FuncState *fs, TString *name) { + int i; + Upvaldesc *up = fs->f->upvalues; + for (i = 0; i < fs->nups; i++) { + if (eqstr(up[i].name, name)) return i; + } + return -1; /* not found */ +} + + +static Upvaldesc *allocupvalue (FuncState *fs) { + Proto *f = fs->f; + int oldsize = f->sizeupvalues; + checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues"); + luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues, + Upvaldesc, MAXUPVAL, "upvalues"); + while (oldsize < f->sizeupvalues) + f->upvalues[oldsize++].name = NULL; + return &f->upvalues[fs->nups++]; +} + + +static int newupvalue (FuncState *fs, TString *name, expdesc *v) { + Upvaldesc *up = allocupvalue(fs); + FuncState *prev = fs->prev; + if (v->k == VLOCAL) { + up->instack = 1; + up->idx = v->u.var.ridx; + up->kind = getlocalvardesc(prev, v->u.var.vidx)->vd.kind; + lua_assert(eqstr(name, getlocalvardesc(prev, v->u.var.vidx)->vd.name)); + } + else { + up->instack = 0; + up->idx = cast_byte(v->u.info); + up->kind = prev->f->upvalues[v->u.info].kind; + lua_assert(eqstr(name, prev->f->upvalues[v->u.info].name)); + } + up->name = name; + luaC_objbarrier(fs->ls->L, fs->f, name); + return fs->nups - 1; +} + + +/* +** Look for an active local variable with the name 'n' in the +** function 'fs'. If found, initialize 'var' with it and return +** its expression kind; otherwise return -1. +*/ +static int searchvar (FuncState *fs, TString *n, expdesc *var) { + int i; + for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { + Vardesc *vd = getlocalvardesc(fs, i); + if (eqstr(n, vd->vd.name)) { /* found? */ + if (vd->vd.kind == RDKCTC) /* compile-time constant? */ + init_exp(var, VCONST, fs->firstlocal + i); + else /* real variable */ + init_var(fs, var, i); + return var->k; + } + } + return -1; /* not found */ +} + + +/* +** Mark block where variable at given level was defined +** (to emit close instructions later). +*/ +static void markupval (FuncState *fs, int level) { + BlockCnt *bl = fs->bl; + while (bl->nactvar > level) + bl = bl->previous; + bl->upval = 1; + fs->needclose = 1; +} + + +/* +** Mark that current block has a to-be-closed variable. +*/ +static void marktobeclosed (FuncState *fs) { + BlockCnt *bl = fs->bl; + bl->upval = 1; + bl->insidetbc = 1; + fs->needclose = 1; +} + + +/* +** Find a variable with the given name 'n'. If it is an upvalue, add +** this upvalue into all intermediate functions. If it is a global, set +** 'var' as 'void' as a flag. +*/ +static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { + if (fs == NULL) /* no more levels? */ + init_exp(var, VVOID, 0); /* default is global */ + else { + int v = searchvar(fs, n, var); /* look up locals at current level */ + if (v >= 0) { /* found? */ + if (v == VLOCAL && !base) + markupval(fs, var->u.var.vidx); /* local will be used as an upval */ + } + else { /* not found as local at current level; try upvalues */ + int idx = searchupvalue(fs, n); /* try existing upvalues */ + if (idx < 0) { /* not found? */ + singlevaraux(fs->prev, n, var, 0); /* try upper levels */ + if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */ + idx = newupvalue(fs, n, var); /* will be a new upvalue */ + else /* it is a global or a constant */ + return; /* don't need to do anything at this level */ + } + init_exp(var, VUPVAL, idx); /* new or old upvalue */ + } + } +} + + +/* +** Find a variable with the given name 'n', handling global variables +** too. +*/ +static void singlevar (LexState *ls, expdesc *var) { + TString *varname = str_checkname(ls); + FuncState *fs = ls->fs; + singlevaraux(fs, varname, var, 1); + if (var->k == VVOID) { /* global name? */ + expdesc key; + singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ + lua_assert(var->k != VVOID); /* this one must exist */ + luaK_exp2anyregup(fs, var); /* but could be a constant */ + codestring(&key, varname); /* key is variable name */ + luaK_indexed(fs, var, &key); /* env[varname] */ + } +} + + +/* +** Adjust the number of results from an expression list 'e' with 'nexps' +** expressions to 'nvars' values. +*/ +static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { + FuncState *fs = ls->fs; + int needed = nvars - nexps; /* extra values needed */ + if (hasmultret(e->k)) { /* last expression has multiple returns? */ + int extra = needed + 1; /* discount last expression itself */ + if (extra < 0) + extra = 0; + luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ + } + else { + if (e->k != VVOID) /* at least one expression? */ + luaK_exp2nextreg(fs, e); /* close last expression */ + if (needed > 0) /* missing values? */ + luaK_nil(fs, fs->freereg, needed); /* complete with nils */ + } + if (needed > 0) + luaK_reserveregs(fs, needed); /* registers for extra values */ + else /* adding 'needed' is actually a subtraction */ + fs->freereg += needed; /* remove extra values */ +} + + +#define enterlevel(ls) luaE_incCstack(ls->L) + + +#define leavelevel(ls) ((ls)->L->nCcalls--) + + +/* +** Generates an error that a goto jumps into the scope of some +** local variable. +*/ +static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) { + const char *varname = getstr(getlocalvardesc(ls->fs, gt->nactvar)->vd.name); + const char *msg = " at line %d jumps into the scope of local '%s'"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line, varname); + luaK_semerror(ls, msg); /* raise the error */ +} + + +/* +** Solves the goto at index 'g' to given 'label' and removes it +** from the list of pending gotos. +** If it jumps into the scope of some variable, raises an error. +*/ +static void solvegoto (LexState *ls, int g, Labeldesc *label) { + int i; + Labellist *gl = &ls->dyd->gt; /* list of gotos */ + Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */ + lua_assert(eqstr(gt->name, label->name)); + if (l_unlikely(gt->nactvar < label->nactvar)) /* enter some scope? */ + jumpscopeerror(ls, gt); + luaK_patchlist(ls->fs, gt->pc, label->pc); + for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */ + gl->arr[i] = gl->arr[i + 1]; + gl->n--; +} + + +/* +** Search for an active label with the given name. +*/ +static Labeldesc *findlabel (LexState *ls, TString *name) { + int i; + Dyndata *dyd = ls->dyd; + /* check labels in current function for a match */ + for (i = ls->fs->firstlabel; i < dyd->label.n; i++) { + Labeldesc *lb = &dyd->label.arr[i]; + if (eqstr(lb->name, name)) /* correct label? */ + return lb; + } + return NULL; /* label not found */ +} + + +/* +** Adds a new label/goto in the corresponding list. +*/ +static int newlabelentry (LexState *ls, Labellist *l, TString *name, + int line, int pc) { + int n = l->n; + luaM_growvector(ls->L, l->arr, n, l->size, + Labeldesc, SHRT_MAX, "labels/gotos"); + l->arr[n].name = name; + l->arr[n].line = line; + l->arr[n].nactvar = ls->fs->nactvar; + l->arr[n].close = 0; + l->arr[n].pc = pc; + l->n = n + 1; + return n; +} + + +static int newgotoentry (LexState *ls, TString *name, int line, int pc) { + return newlabelentry(ls, &ls->dyd->gt, name, line, pc); +} + + +/* +** Solves forward jumps. Check whether new label 'lb' matches any +** pending gotos in current block and solves them. Return true +** if any of the gotos need to close upvalues. +*/ +static int solvegotos (LexState *ls, Labeldesc *lb) { + Labellist *gl = &ls->dyd->gt; + int i = ls->fs->bl->firstgoto; + int needsclose = 0; + while (i < gl->n) { + if (eqstr(gl->arr[i].name, lb->name)) { + needsclose |= gl->arr[i].close; + solvegoto(ls, i, lb); /* will remove 'i' from the list */ + } + else + i++; + } + return needsclose; +} + + +/* +** Create a new label with the given 'name' at the given 'line'. +** 'last' tells whether label is the last non-op statement in its +** block. Solves all pending gotos to this new label and adds +** a close instruction if necessary. +** Returns true iff it added a close instruction. +*/ +static int createlabel (LexState *ls, TString *name, int line, + int last) { + FuncState *fs = ls->fs; + Labellist *ll = &ls->dyd->label; + int l = newlabelentry(ls, ll, name, line, luaK_getlabel(fs)); + if (last) { /* label is last no-op statement in the block? */ + /* assume that locals are already out of scope */ + ll->arr[l].nactvar = fs->bl->nactvar; + } + if (solvegotos(ls, &ll->arr[l])) { /* need close? */ + luaK_codeABC(fs, OP_CLOSE, luaY_nvarstack(fs), 0, 0); + return 1; + } + return 0; +} + + +/* +** Adjust pending gotos to outer level of a block. +*/ +static void movegotosout (FuncState *fs, BlockCnt *bl) { + int i; + Labellist *gl = &fs->ls->dyd->gt; + /* correct pending gotos to current block */ + for (i = bl->firstgoto; i < gl->n; i++) { /* for each pending goto */ + Labeldesc *gt = &gl->arr[i]; + /* leaving a variable scope? */ + if (reglevel(fs, gt->nactvar) > reglevel(fs, bl->nactvar)) + gt->close |= bl->upval; /* jump may need a close */ + gt->nactvar = bl->nactvar; /* update goto level */ + } +} + + +static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { + bl->isloop = isloop; + bl->nactvar = fs->nactvar; + bl->firstlabel = fs->ls->dyd->label.n; + bl->firstgoto = fs->ls->dyd->gt.n; + bl->upval = 0; + bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); + bl->previous = fs->bl; + fs->bl = bl; + lua_assert(fs->freereg == luaY_nvarstack(fs)); +} + + +/* +** generates an error for an undefined 'goto'. +*/ +static l_noret undefgoto (LexState *ls, Labeldesc *gt) { + const char *msg; + if (eqstr(gt->name, luaS_newliteral(ls->L, "break"))) { + msg = "break outside loop at line %d"; + msg = luaO_pushfstring(ls->L, msg, gt->line); + } + else { + msg = "no visible label '%s' for at line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); + } + luaK_semerror(ls, msg); +} + + +static void leaveblock (FuncState *fs) { + BlockCnt *bl = fs->bl; + LexState *ls = fs->ls; + int hasclose = 0; + int stklevel = reglevel(fs, bl->nactvar); /* level outside the block */ + removevars(fs, bl->nactvar); /* remove block locals */ + lua_assert(bl->nactvar == fs->nactvar); /* back to level on entry */ + if (bl->isloop) /* has to fix pending breaks? */ + hasclose = createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); + if (!hasclose && bl->previous && bl->upval) /* still need a 'close'? */ + luaK_codeABC(fs, OP_CLOSE, stklevel, 0, 0); + fs->freereg = stklevel; /* free registers */ + ls->dyd->label.n = bl->firstlabel; /* remove local labels */ + fs->bl = bl->previous; /* current block now is previous one */ + if (bl->previous) /* was it a nested block? */ + movegotosout(fs, bl); /* update pending gotos to enclosing block */ + else { + if (bl->firstgoto < ls->dyd->gt.n) /* still pending gotos? */ + undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ + } +} + + +/* +** adds a new prototype into list of prototypes +*/ +static Proto *addprototype (LexState *ls) { + Proto *clp; + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Proto *f = fs->f; /* prototype of current function */ + if (fs->np >= f->sizep) { + int oldsize = f->sizep; + luaM_growvector(L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "functions"); + while (oldsize < f->sizep) + f->p[oldsize++] = NULL; + } + f->p[fs->np++] = clp = luaF_newproto(L); + luaC_objbarrier(L, f, clp); + return clp; +} + + +/* +** codes instruction to create new closure in parent function. +** The OP_CLOSURE instruction uses the last available register, +** so that, if it invokes the GC, the GC knows which registers +** are in use at that time. + +*/ +static void codeclosure (LexState *ls, expdesc *v) { + FuncState *fs = ls->fs->prev; + init_exp(v, VRELOC, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1)); + luaK_exp2nextreg(fs, v); /* fix it at the last register */ +} + + +static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { + Proto *f = fs->f; + fs->prev = ls->fs; /* linked list of funcstates */ + fs->ls = ls; + ls->fs = fs; + fs->pc = 0; + fs->previousline = f->linedefined; + fs->iwthabs = 0; + fs->lasttarget = 0; + fs->freereg = 0; + fs->nk = 0; + fs->nabslineinfo = 0; + fs->np = 0; + fs->nups = 0; + fs->ndebugvars = 0; + fs->nactvar = 0; + fs->needclose = 0; + fs->firstlocal = ls->dyd->actvar.n; + fs->firstlabel = ls->dyd->label.n; + fs->bl = NULL; + f->source = ls->source; + luaC_objbarrier(ls->L, f, f->source); + f->maxstacksize = 2; /* registers 0/1 are always valid */ + enterblock(fs, bl, 0); +} + + +static void close_func (LexState *ls) { + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Proto *f = fs->f; + luaK_ret(fs, luaY_nvarstack(fs), 0); /* final return */ + leaveblock(fs); + lua_assert(fs->bl == NULL); + luaK_finish(fs); + luaM_shrinkvector(L, f->code, f->sizecode, fs->pc, Instruction); + luaM_shrinkvector(L, f->lineinfo, f->sizelineinfo, fs->pc, ls_byte); + luaM_shrinkvector(L, f->abslineinfo, f->sizeabslineinfo, + fs->nabslineinfo, AbsLineInfo); + luaM_shrinkvector(L, f->k, f->sizek, fs->nk, TValue); + luaM_shrinkvector(L, f->p, f->sizep, fs->np, Proto *); + luaM_shrinkvector(L, f->locvars, f->sizelocvars, fs->ndebugvars, LocVar); + luaM_shrinkvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc); + ls->fs = fs->prev; + luaC_checkGC(L); +} + + + +/*============================================================*/ +/* GRAMMAR RULES */ +/*============================================================*/ + + +/* +** check whether current token is in the follow set of a block. +** 'until' closes syntactical blocks, but do not close scope, +** so it is handled in separate. +*/ +static int block_follow (LexState *ls, int withuntil) { + switch (ls->t.token) { + case TK_ELSE: case TK_ELSEIF: + case TK_END: case TK_EOS: + return 1; + case TK_UNTIL: return withuntil; + default: return 0; + } +} + + +static void statlist (LexState *ls) { + /* statlist -> { stat [';'] } */ + while (!block_follow(ls, 1)) { + if (ls->t.token == TK_RETURN) { + statement(ls); + return; /* 'return' must be last statement */ + } + statement(ls); + } +} + + +static void fieldsel (LexState *ls, expdesc *v) { + /* fieldsel -> ['.' | ':'] NAME */ + FuncState *fs = ls->fs; + expdesc key; + luaK_exp2anyregup(fs, v); + luaX_next(ls); /* skip the dot or colon */ + codename(ls, &key); + luaK_indexed(fs, v, &key); +} + + +static void yindex (LexState *ls, expdesc *v) { + /* index -> '[' expr ']' */ + luaX_next(ls); /* skip the '[' */ + expr(ls, v); + luaK_exp2val(ls->fs, v); + checknext(ls, ']'); +} + + +/* +** {====================================================================== +** Rules for Constructors +** ======================================================================= +*/ + + +typedef struct ConsControl { + expdesc v; /* last list item read */ + expdesc *t; /* table descriptor */ + int nh; /* total number of 'record' elements */ + int na; /* number of array elements already stored */ + int tostore; /* number of array elements pending to be stored */ +} ConsControl; + + +static void recfield (LexState *ls, ConsControl *cc) { + /* recfield -> (NAME | '['exp']') = exp */ + FuncState *fs = ls->fs; + int reg = ls->fs->freereg; + expdesc tab, key, val; + if (ls->t.token == TK_NAME) { + checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); + codename(ls, &key); + } + else /* ls->t.token == '[' */ + yindex(ls, &key); + cc->nh++; + checknext(ls, '='); + tab = *cc->t; + luaK_indexed(fs, &tab, &key); + expr(ls, &val); + luaK_storevar(fs, &tab, &val); + fs->freereg = reg; /* free registers */ +} + + +static void closelistfield (FuncState *fs, ConsControl *cc) { + if (cc->v.k == VVOID) return; /* there is no list item */ + luaK_exp2nextreg(fs, &cc->v); + cc->v.k = VVOID; + if (cc->tostore == LFIELDS_PER_FLUSH) { + luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */ + cc->na += cc->tostore; + cc->tostore = 0; /* no more items pending */ + } +} + + +static void lastlistfield (FuncState *fs, ConsControl *cc) { + if (cc->tostore == 0) return; + if (hasmultret(cc->v.k)) { + luaK_setmultret(fs, &cc->v); + luaK_setlist(fs, cc->t->u.info, cc->na, LUA_MULTRET); + cc->na--; /* do not count last expression (unknown number of elements) */ + } + else { + if (cc->v.k != VVOID) + luaK_exp2nextreg(fs, &cc->v); + luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); + } + cc->na += cc->tostore; +} + + +static void listfield (LexState *ls, ConsControl *cc) { + /* listfield -> exp */ + expr(ls, &cc->v); + cc->tostore++; +} + + +static void field (LexState *ls, ConsControl *cc) { + /* field -> listfield | recfield */ + switch(ls->t.token) { + case TK_NAME: { /* may be 'listfield' or 'recfield' */ + if (luaX_lookahead(ls) != '=') /* expression? */ + listfield(ls, cc); + else + recfield(ls, cc); + break; + } + case '[': { + recfield(ls, cc); + break; + } + default: { + listfield(ls, cc); + break; + } + } +} + + +static void constructor (LexState *ls, expdesc *t) { + /* constructor -> '{' [ field { sep field } [sep] ] '}' + sep -> ',' | ';' */ + FuncState *fs = ls->fs; + int line = ls->linenumber; + int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); + ConsControl cc; + luaK_code(fs, 0); /* space for extra arg. */ + cc.na = cc.nh = cc.tostore = 0; + cc.t = t; + init_exp(t, VNONRELOC, fs->freereg); /* table will be at stack top */ + luaK_reserveregs(fs, 1); + init_exp(&cc.v, VVOID, 0); /* no value (yet) */ + checknext(ls, '{'); + do { + lua_assert(cc.v.k == VVOID || cc.tostore > 0); + if (ls->t.token == '}') break; + closelistfield(fs, &cc); + field(ls, &cc); + } while (testnext(ls, ',') || testnext(ls, ';')); + check_match(ls, '}', '{', line); + lastlistfield(fs, &cc); + luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh); +} + +/* }====================================================================== */ + + +static void setvararg (FuncState *fs, int nparams) { + fs->f->is_vararg = 1; + luaK_codeABC(fs, OP_VARARGPREP, nparams, 0, 0); +} + + +static void parlist (LexState *ls) { + /* parlist -> [ {NAME ','} (NAME | '...') ] */ + FuncState *fs = ls->fs; + Proto *f = fs->f; + int nparams = 0; + int isvararg = 0; + if (ls->t.token != ')') { /* is 'parlist' not empty? */ + do { + switch (ls->t.token) { + case TK_NAME: { + new_localvar(ls, str_checkname(ls)); + nparams++; + break; + } + case TK_DOTS: { + luaX_next(ls); + isvararg = 1; + break; + } + default: luaX_syntaxerror(ls, " or '...' expected"); + } + } while (!isvararg && testnext(ls, ',')); + } + adjustlocalvars(ls, nparams); + f->numparams = cast_byte(fs->nactvar); + if (isvararg) + setvararg(fs, f->numparams); /* declared vararg */ + luaK_reserveregs(fs, fs->nactvar); /* reserve registers for parameters */ +} + + +static void body (LexState *ls, expdesc *e, int ismethod, int line) { + /* body -> '(' parlist ')' block END */ + FuncState new_fs; + BlockCnt bl; + new_fs.f = addprototype(ls); + new_fs.f->linedefined = line; + open_func(ls, &new_fs, &bl); + checknext(ls, '('); + if (ismethod) { + new_localvarliteral(ls, "self"); /* create 'self' parameter */ + adjustlocalvars(ls, 1); + } + parlist(ls); + checknext(ls, ')'); + statlist(ls); + new_fs.f->lastlinedefined = ls->linenumber; + check_match(ls, TK_END, TK_FUNCTION, line); + codeclosure(ls, e); + close_func(ls); +} + + +static int explist (LexState *ls, expdesc *v) { + /* explist -> expr { ',' expr } */ + int n = 1; /* at least one expression */ + expr(ls, v); + while (testnext(ls, ',')) { + luaK_exp2nextreg(ls->fs, v); + expr(ls, v); + n++; + } + return n; +} + + +static void funcargs (LexState *ls, expdesc *f, int line) { + FuncState *fs = ls->fs; + expdesc args; + int base, nparams; + switch (ls->t.token) { + case '(': { /* funcargs -> '(' [ explist ] ')' */ + luaX_next(ls); + if (ls->t.token == ')') /* arg list is empty? */ + args.k = VVOID; + else { + explist(ls, &args); + if (hasmultret(args.k)) + luaK_setmultret(fs, &args); + } + check_match(ls, ')', '(', line); + break; + } + case '{': { /* funcargs -> constructor */ + constructor(ls, &args); + break; + } + case TK_STRING: { /* funcargs -> STRING */ + codestring(&args, ls->t.seminfo.ts); + luaX_next(ls); /* must use 'seminfo' before 'next' */ + break; + } + default: { + luaX_syntaxerror(ls, "function arguments expected"); + } + } + lua_assert(f->k == VNONRELOC); + base = f->u.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = LUA_MULTRET; /* open call */ + else { + if (args.k != VVOID) + luaK_exp2nextreg(fs, &args); /* close last argument */ + nparams = fs->freereg - (base+1); + } + init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); + luaK_fixline(fs, line); + fs->freereg = base+1; /* call remove function and arguments and leaves + (unless changed) one result */ +} + + + + +/* +** {====================================================================== +** Expression parsing +** ======================================================================= +*/ + + +static void primaryexp (LexState *ls, expdesc *v) { + /* primaryexp -> NAME | '(' expr ')' */ + switch (ls->t.token) { + case '(': { + int line = ls->linenumber; + luaX_next(ls); + expr(ls, v); + check_match(ls, ')', '(', line); + luaK_dischargevars(ls->fs, v); + return; + } + case TK_NAME: { + singlevar(ls, v); + return; + } + default: { + luaX_syntaxerror(ls, "unexpected symbol"); + } + } +} + + +static void suffixedexp (LexState *ls, expdesc *v) { + /* suffixedexp -> + primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ + FuncState *fs = ls->fs; + int line = ls->linenumber; + primaryexp(ls, v); + for (;;) { + switch (ls->t.token) { + case '.': { /* fieldsel */ + fieldsel(ls, v); + break; + } + case '[': { /* '[' exp ']' */ + expdesc key; + luaK_exp2anyregup(fs, v); + yindex(ls, &key); + luaK_indexed(fs, v, &key); + break; + } + case ':': { /* ':' NAME funcargs */ + expdesc key; + luaX_next(ls); + codename(ls, &key); + luaK_self(fs, v, &key); + funcargs(ls, v, line); + break; + } + case '(': case TK_STRING: case '{': { /* funcargs */ + luaK_exp2nextreg(fs, v); + funcargs(ls, v, line); + break; + } + default: return; + } + } +} + + +static void simpleexp (LexState *ls, expdesc *v) { + /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... | + constructor | FUNCTION body | suffixedexp */ + switch (ls->t.token) { + case TK_FLT: { + init_exp(v, VKFLT, 0); + v->u.nval = ls->t.seminfo.r; + break; + } + case TK_INT: { + init_exp(v, VKINT, 0); + v->u.ival = ls->t.seminfo.i; + break; + } + case TK_STRING: { + codestring(v, ls->t.seminfo.ts); + break; + } + case TK_NIL: { + init_exp(v, VNIL, 0); + break; + } + case TK_TRUE: { + init_exp(v, VTRUE, 0); + break; + } + case TK_FALSE: { + init_exp(v, VFALSE, 0); + break; + } + case TK_DOTS: { /* vararg */ + FuncState *fs = ls->fs; + check_condition(ls, fs->f->is_vararg, + "cannot use '...' outside a vararg function"); + init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 0, 1)); + break; + } + case '{': { /* constructor */ + constructor(ls, v); + return; + } + case TK_FUNCTION: { + luaX_next(ls); + body(ls, v, 0, ls->linenumber); + return; + } + default: { + suffixedexp(ls, v); + return; + } + } + luaX_next(ls); +} + + +static UnOpr getunopr (int op) { + switch (op) { + case TK_NOT: return OPR_NOT; + case '-': return OPR_MINUS; + case '~': return OPR_BNOT; + case '#': return OPR_LEN; + default: return OPR_NOUNOPR; + } +} + + +static BinOpr getbinopr (int op) { + switch (op) { + case '+': return OPR_ADD; + case '-': return OPR_SUB; + case '*': return OPR_MUL; + case '%': return OPR_MOD; + case '^': return OPR_POW; + case '/': return OPR_DIV; + case TK_IDIV: return OPR_IDIV; + case '&': return OPR_BAND; + case '|': return OPR_BOR; + case '~': return OPR_BXOR; + case TK_SHL: return OPR_SHL; + case TK_SHR: return OPR_SHR; + case TK_CONCAT: return OPR_CONCAT; + case TK_NE: return OPR_NE; + case TK_EQ: return OPR_EQ; + case '<': return OPR_LT; + case TK_LE: return OPR_LE; + case '>': return OPR_GT; + case TK_GE: return OPR_GE; + case TK_AND: return OPR_AND; + case TK_OR: return OPR_OR; + default: return OPR_NOBINOPR; + } +} + + +/* +** Priority table for binary operators. +*/ +static const struct { + lu_byte left; /* left priority for each binary operator */ + lu_byte right; /* right priority */ +} priority[] = { /* ORDER OPR */ + {10, 10}, {10, 10}, /* '+' '-' */ + {11, 11}, {11, 11}, /* '*' '%' */ + {14, 13}, /* '^' (right associative) */ + {11, 11}, {11, 11}, /* '/' '//' */ + {6, 6}, {4, 4}, {5, 5}, /* '&' '|' '~' */ + {7, 7}, {7, 7}, /* '<<' '>>' */ + {9, 8}, /* '..' (right associative) */ + {3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */ + {3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */ + {2, 2}, {1, 1} /* and, or */ +}; + +#define UNARY_PRIORITY 12 /* priority for unary operators */ + + +/* +** subexpr -> (simpleexp | unop subexpr) { binop subexpr } +** where 'binop' is any binary operator with a priority higher than 'limit' +*/ +static BinOpr subexpr (LexState *ls, expdesc *v, int limit) { + BinOpr op; + UnOpr uop; + enterlevel(ls); + uop = getunopr(ls->t.token); + if (uop != OPR_NOUNOPR) { /* prefix (unary) operator? */ + int line = ls->linenumber; + luaX_next(ls); /* skip operator */ + subexpr(ls, v, UNARY_PRIORITY); + luaK_prefix(ls->fs, uop, v, line); + } + else simpleexp(ls, v); + /* expand while operators have priorities higher than 'limit' */ + op = getbinopr(ls->t.token); + while (op != OPR_NOBINOPR && priority[op].left > limit) { + expdesc v2; + BinOpr nextop; + int line = ls->linenumber; + luaX_next(ls); /* skip operator */ + luaK_infix(ls->fs, op, v); + /* read sub-expression with higher priority */ + nextop = subexpr(ls, &v2, priority[op].right); + luaK_posfix(ls->fs, op, v, &v2, line); + op = nextop; + } + leavelevel(ls); + return op; /* return first untreated operator */ +} + + +static void expr (LexState *ls, expdesc *v) { + subexpr(ls, v, 0); +} + +/* }==================================================================== */ + + + +/* +** {====================================================================== +** Rules for Statements +** ======================================================================= +*/ + + +static void block (LexState *ls) { + /* block -> statlist */ + FuncState *fs = ls->fs; + BlockCnt bl; + enterblock(fs, &bl, 0); + statlist(ls); + leaveblock(fs); +} + + +/* +** structure to chain all variables in the left-hand side of an +** assignment +*/ +struct LHS_assign { + struct LHS_assign *prev; + expdesc v; /* variable (global, local, upvalue, or indexed) */ +}; + + +/* +** check whether, in an assignment to an upvalue/local variable, the +** upvalue/local variable is begin used in a previous assignment to a +** table. If so, save original upvalue/local value in a safe place and +** use this safe copy in the previous assignment. +*/ +static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { + FuncState *fs = ls->fs; + int extra = fs->freereg; /* eventual position to save local variable */ + int conflict = 0; + for (; lh; lh = lh->prev) { /* check all previous assignments */ + if (vkisindexed(lh->v.k)) { /* assignment to table field? */ + if (lh->v.k == VINDEXUP) { /* is table an upvalue? */ + if (v->k == VUPVAL && lh->v.u.ind.t == v->u.info) { + conflict = 1; /* table is the upvalue being assigned now */ + lh->v.k = VINDEXSTR; + lh->v.u.ind.t = extra; /* assignment will use safe copy */ + } + } + else { /* table is a register */ + if (v->k == VLOCAL && lh->v.u.ind.t == v->u.var.ridx) { + conflict = 1; /* table is the local being assigned now */ + lh->v.u.ind.t = extra; /* assignment will use safe copy */ + } + /* is index the local being assigned? */ + if (lh->v.k == VINDEXED && v->k == VLOCAL && + lh->v.u.ind.idx == v->u.var.ridx) { + conflict = 1; + lh->v.u.ind.idx = extra; /* previous assignment will use safe copy */ + } + } + } + } + if (conflict) { + /* copy upvalue/local value to a temporary (in position 'extra') */ + if (v->k == VLOCAL) + luaK_codeABC(fs, OP_MOVE, extra, v->u.var.ridx, 0); + else + luaK_codeABC(fs, OP_GETUPVAL, extra, v->u.info, 0); + luaK_reserveregs(fs, 1); + } +} + +/* +** Parse and compile a multiple assignment. The first "variable" +** (a 'suffixedexp') was already read by the caller. +** +** assignment -> suffixedexp restassign +** restassign -> ',' suffixedexp restassign | '=' explist +*/ +static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) { + expdesc e; + check_condition(ls, vkisvar(lh->v.k), "syntax error"); + check_readonly(ls, &lh->v); + if (testnext(ls, ',')) { /* restassign -> ',' suffixedexp restassign */ + struct LHS_assign nv; + nv.prev = lh; + suffixedexp(ls, &nv.v); + if (!vkisindexed(nv.v.k)) + check_conflict(ls, lh, &nv.v); + enterlevel(ls); /* control recursion depth */ + restassign(ls, &nv, nvars+1); + leavelevel(ls); + } + else { /* restassign -> '=' explist */ + int nexps; + checknext(ls, '='); + nexps = explist(ls, &e); + if (nexps != nvars) + adjust_assign(ls, nvars, nexps, &e); + else { + luaK_setoneret(ls->fs, &e); /* close last expression */ + luaK_storevar(ls->fs, &lh->v, &e); + return; /* avoid default */ + } + } + init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ + luaK_storevar(ls->fs, &lh->v, &e); +} + + +static int cond (LexState *ls) { + /* cond -> exp */ + expdesc v; + expr(ls, &v); /* read condition */ + if (v.k == VNIL) v.k = VFALSE; /* 'falses' are all equal here */ + luaK_goiftrue(ls->fs, &v); + return v.f; +} + + +static void gotostat (LexState *ls) { + FuncState *fs = ls->fs; + int line = ls->linenumber; + TString *name = str_checkname(ls); /* label's name */ + Labeldesc *lb = findlabel(ls, name); + if (lb == NULL) /* no label? */ + /* forward jump; will be resolved when the label is declared */ + newgotoentry(ls, name, line, luaK_jump(fs)); + else { /* found a label */ + /* backward jump; will be resolved here */ + int lblevel = reglevel(fs, lb->nactvar); /* label level */ + if (luaY_nvarstack(fs) > lblevel) /* leaving the scope of a variable? */ + luaK_codeABC(fs, OP_CLOSE, lblevel, 0, 0); + /* create jump and link it to the label */ + luaK_patchlist(fs, luaK_jump(fs), lb->pc); + } +} + + +/* +** Break statement. Semantically equivalent to "goto break". +*/ +static void breakstat (LexState *ls) { + int line = ls->linenumber; + luaX_next(ls); /* skip break */ + newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, luaK_jump(ls->fs)); +} + + +/* +** Check whether there is already a label with the given 'name'. +*/ +static void checkrepeated (LexState *ls, TString *name) { + Labeldesc *lb = findlabel(ls, name); + if (l_unlikely(lb != NULL)) { /* already defined? */ + const char *msg = "label '%s' already defined on line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); + luaK_semerror(ls, msg); /* error */ + } +} + + +static void labelstat (LexState *ls, TString *name, int line) { + /* label -> '::' NAME '::' */ + checknext(ls, TK_DBCOLON); /* skip double colon */ + while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) + statement(ls); /* skip other no-op statements */ + checkrepeated(ls, name); /* check for repeated labels */ + createlabel(ls, name, line, block_follow(ls, 0)); +} + + +static void whilestat (LexState *ls, int line) { + /* whilestat -> WHILE cond DO block END */ + FuncState *fs = ls->fs; + int whileinit; + int condexit; + BlockCnt bl; + luaX_next(ls); /* skip WHILE */ + whileinit = luaK_getlabel(fs); + condexit = cond(ls); + enterblock(fs, &bl, 1); + checknext(ls, TK_DO); + block(ls); + luaK_jumpto(fs, whileinit); + check_match(ls, TK_END, TK_WHILE, line); + leaveblock(fs); + luaK_patchtohere(fs, condexit); /* false conditions finish the loop */ +} + + +static void repeatstat (LexState *ls, int line) { + /* repeatstat -> REPEAT block UNTIL cond */ + int condexit; + FuncState *fs = ls->fs; + int repeat_init = luaK_getlabel(fs); + BlockCnt bl1, bl2; + enterblock(fs, &bl1, 1); /* loop block */ + enterblock(fs, &bl2, 0); /* scope block */ + luaX_next(ls); /* skip REPEAT */ + statlist(ls); + check_match(ls, TK_UNTIL, TK_REPEAT, line); + condexit = cond(ls); /* read condition (inside scope block) */ + leaveblock(fs); /* finish scope */ + if (bl2.upval) { /* upvalues? */ + int exit = luaK_jump(fs); /* normal exit must jump over fix */ + luaK_patchtohere(fs, condexit); /* repetition must close upvalues */ + luaK_codeABC(fs, OP_CLOSE, reglevel(fs, bl2.nactvar), 0, 0); + condexit = luaK_jump(fs); /* repeat after closing upvalues */ + luaK_patchtohere(fs, exit); /* normal exit comes to here */ + } + luaK_patchlist(fs, condexit, repeat_init); /* close the loop */ + leaveblock(fs); /* finish loop */ +} + + +/* +** Read an expression and generate code to put its results in next +** stack slot. +** +*/ +static void exp1 (LexState *ls) { + expdesc e; + expr(ls, &e); + luaK_exp2nextreg(ls->fs, &e); + lua_assert(e.k == VNONRELOC); +} + + +/* +** Fix for instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua). 'back' true means +** a back jump. +*/ +static void fixforjump (FuncState *fs, int pc, int dest, int back) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest - (pc + 1); + if (back) + offset = -offset; + if (l_unlikely(offset > MAXARG_Bx)) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_Bx(*jmp, offset); +} + + +/* +** Generate code for a 'for' loop. +*/ +static void forbody (LexState *ls, int base, int line, int nvars, int isgen) { + /* forbody -> DO block */ + static const OpCode forprep[2] = {OP_FORPREP, OP_TFORPREP}; + static const OpCode forloop[2] = {OP_FORLOOP, OP_TFORLOOP}; + BlockCnt bl; + FuncState *fs = ls->fs; + int prep, endfor; + checknext(ls, TK_DO); + prep = luaK_codeABx(fs, forprep[isgen], base, 0); + enterblock(fs, &bl, 0); /* scope for declared variables */ + adjustlocalvars(ls, nvars); + luaK_reserveregs(fs, nvars); + block(ls); + leaveblock(fs); /* end of scope for declared variables */ + fixforjump(fs, prep, luaK_getlabel(fs), 0); + if (isgen) { /* generic for? */ + luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars); + luaK_fixline(fs, line); + } + endfor = luaK_codeABx(fs, forloop[isgen], base, 0); + fixforjump(fs, endfor, prep + 1, 1); + luaK_fixline(fs, line); +} + + +static void fornum (LexState *ls, TString *varname, int line) { + /* fornum -> NAME = exp,exp[,exp] forbody */ + FuncState *fs = ls->fs; + int base = fs->freereg; + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvar(ls, varname); + checknext(ls, '='); + exp1(ls); /* initial value */ + checknext(ls, ','); + exp1(ls); /* limit */ + if (testnext(ls, ',')) + exp1(ls); /* optional step */ + else { /* default step = 1 */ + luaK_int(fs, fs->freereg, 1); + luaK_reserveregs(fs, 1); + } + adjustlocalvars(ls, 3); /* control variables */ + forbody(ls, base, line, 1, 0); +} + + +static void forlist (LexState *ls, TString *indexname) { + /* forlist -> NAME {,NAME} IN explist forbody */ + FuncState *fs = ls->fs; + expdesc e; + int nvars = 5; /* gen, state, control, toclose, 'indexname' */ + int line; + int base = fs->freereg; + /* create control variables */ + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + new_localvarliteral(ls, "(for state)"); + /* create declared variables */ + new_localvar(ls, indexname); + while (testnext(ls, ',')) { + new_localvar(ls, str_checkname(ls)); + nvars++; + } + checknext(ls, TK_IN); + line = ls->linenumber; + adjust_assign(ls, 4, explist(ls, &e), &e); + adjustlocalvars(ls, 4); /* control variables */ + marktobeclosed(fs); /* last control var. must be closed */ + luaK_checkstack(fs, 3); /* extra space to call generator */ + forbody(ls, base, line, nvars - 4, 1); +} + + +static void forstat (LexState *ls, int line) { + /* forstat -> FOR (fornum | forlist) END */ + FuncState *fs = ls->fs; + TString *varname; + BlockCnt bl; + enterblock(fs, &bl, 1); /* scope for loop and control variables */ + luaX_next(ls); /* skip 'for' */ + varname = str_checkname(ls); /* first variable name */ + switch (ls->t.token) { + case '=': fornum(ls, varname, line); break; + case ',': case TK_IN: forlist(ls, varname); break; + default: luaX_syntaxerror(ls, "'=' or 'in' expected"); + } + check_match(ls, TK_END, TK_FOR, line); + leaveblock(fs); /* loop scope ('break' jumps to this point) */ +} + + +static void test_then_block (LexState *ls, int *escapelist) { + /* test_then_block -> [IF | ELSEIF] cond THEN block */ + BlockCnt bl; + FuncState *fs = ls->fs; + expdesc v; + int jf; /* instruction to skip 'then' code (if condition is false) */ + luaX_next(ls); /* skip IF or ELSEIF */ + expr(ls, &v); /* read condition */ + checknext(ls, TK_THEN); + if (ls->t.token == TK_BREAK) { /* 'if x then break' ? */ + int line = ls->linenumber; + luaK_goiffalse(ls->fs, &v); /* will jump if condition is true */ + luaX_next(ls); /* skip 'break' */ + enterblock(fs, &bl, 0); /* must enter block before 'goto' */ + newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, v.t); + while (testnext(ls, ';')) {} /* skip semicolons */ + if (block_follow(ls, 0)) { /* jump is the entire block? */ + leaveblock(fs); + return; /* and that is it */ + } + else /* must skip over 'then' part if condition is false */ + jf = luaK_jump(fs); + } + else { /* regular case (not a break) */ + luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */ + enterblock(fs, &bl, 0); + jf = v.f; + } + statlist(ls); /* 'then' part */ + leaveblock(fs); + if (ls->t.token == TK_ELSE || + ls->t.token == TK_ELSEIF) /* followed by 'else'/'elseif'? */ + luaK_concat(fs, escapelist, luaK_jump(fs)); /* must jump over it */ + luaK_patchtohere(fs, jf); +} + + +static void ifstat (LexState *ls, int line) { + /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ + FuncState *fs = ls->fs; + int escapelist = NO_JUMP; /* exit list for finished parts */ + test_then_block(ls, &escapelist); /* IF cond THEN block */ + while (ls->t.token == TK_ELSEIF) + test_then_block(ls, &escapelist); /* ELSEIF cond THEN block */ + if (testnext(ls, TK_ELSE)) + block(ls); /* 'else' part */ + check_match(ls, TK_END, TK_IF, line); + luaK_patchtohere(fs, escapelist); /* patch escape list to 'if' end */ +} + + +static void localfunc (LexState *ls) { + expdesc b; + FuncState *fs = ls->fs; + int fvar = fs->nactvar; /* function's variable index */ + new_localvar(ls, str_checkname(ls)); /* new local variable */ + adjustlocalvars(ls, 1); /* enter its scope */ + body(ls, &b, 0, ls->linenumber); /* function created in next register */ + /* debug information will only see the variable after this point! */ + localdebuginfo(fs, fvar)->startpc = fs->pc; +} + + +static int getlocalattribute (LexState *ls) { + /* ATTRIB -> ['<' Name '>'] */ + if (testnext(ls, '<')) { + const char *attr = getstr(str_checkname(ls)); + checknext(ls, '>'); + if (strcmp(attr, "const") == 0) + return RDKCONST; /* read-only variable */ + else if (strcmp(attr, "close") == 0) + return RDKTOCLOSE; /* to-be-closed variable */ + else + luaK_semerror(ls, + luaO_pushfstring(ls->L, "unknown attribute '%s'", attr)); + } + return VDKREG; /* regular variable */ +} + + +static void checktoclose (FuncState *fs, int level) { + if (level != -1) { /* is there a to-be-closed variable? */ + marktobeclosed(fs); + luaK_codeABC(fs, OP_TBC, reglevel(fs, level), 0, 0); + } +} + + +static void localstat (LexState *ls) { + /* stat -> LOCAL NAME ATTRIB { ',' NAME ATTRIB } ['=' explist] */ + FuncState *fs = ls->fs; + int toclose = -1; /* index of to-be-closed variable (if any) */ + Vardesc *var; /* last variable */ + int vidx, kind; /* index and kind of last variable */ + int nvars = 0; + int nexps; + expdesc e; + do { + vidx = new_localvar(ls, str_checkname(ls)); + kind = getlocalattribute(ls); + getlocalvardesc(fs, vidx)->vd.kind = kind; + if (kind == RDKTOCLOSE) { /* to-be-closed? */ + if (toclose != -1) /* one already present? */ + luaK_semerror(ls, "multiple to-be-closed variables in local list"); + toclose = fs->nactvar + nvars; + } + nvars++; + } while (testnext(ls, ',')); + if (testnext(ls, '=')) + nexps = explist(ls, &e); + else { + e.k = VVOID; + nexps = 0; + } + var = getlocalvardesc(fs, vidx); /* get last variable */ + if (nvars == nexps && /* no adjustments? */ + var->vd.kind == RDKCONST && /* last variable is const? */ + luaK_exp2const(fs, &e, &var->k)) { /* compile-time constant? */ + var->vd.kind = RDKCTC; /* variable is a compile-time constant */ + adjustlocalvars(ls, nvars - 1); /* exclude last variable */ + fs->nactvar++; /* but count it */ + } + else { + adjust_assign(ls, nvars, nexps, &e); + adjustlocalvars(ls, nvars); + } + checktoclose(fs, toclose); +} + + +static int funcname (LexState *ls, expdesc *v) { + /* funcname -> NAME {fieldsel} [':' NAME] */ + int ismethod = 0; + singlevar(ls, v); + while (ls->t.token == '.') + fieldsel(ls, v); + if (ls->t.token == ':') { + ismethod = 1; + fieldsel(ls, v); + } + return ismethod; +} + + +static void funcstat (LexState *ls, int line) { + /* funcstat -> FUNCTION funcname body */ + int ismethod; + expdesc v, b; + luaX_next(ls); /* skip FUNCTION */ + ismethod = funcname(ls, &v); + body(ls, &b, ismethod, line); + check_readonly(ls, &v); + luaK_storevar(ls->fs, &v, &b); + luaK_fixline(ls->fs, line); /* definition "happens" in the first line */ +} + + +static void exprstat (LexState *ls) { + /* stat -> func | assignment */ + FuncState *fs = ls->fs; + struct LHS_assign v; + suffixedexp(ls, &v.v); + if (ls->t.token == '=' || ls->t.token == ',') { /* stat -> assignment ? */ + v.prev = NULL; + restassign(ls, &v, 1); + } + else { /* stat -> func */ + Instruction *inst; + check_condition(ls, v.v.k == VCALL, "syntax error"); + inst = &getinstruction(fs, &v.v); + SETARG_C(*inst, 1); /* call statement uses no results */ + } +} + + +static void retstat (LexState *ls) { + /* stat -> RETURN [explist] [';'] */ + FuncState *fs = ls->fs; + expdesc e; + int nret; /* number of values being returned */ + int first = luaY_nvarstack(fs); /* first slot to be returned */ + if (block_follow(ls, 1) || ls->t.token == ';') + nret = 0; /* return no values */ + else { + nret = explist(ls, &e); /* optional return values */ + if (hasmultret(e.k)) { + luaK_setmultret(fs, &e); + if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */ + SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getinstruction(fs,&e)) == luaY_nvarstack(fs)); + } + nret = LUA_MULTRET; /* return all values */ + } + else { + if (nret == 1) /* only one single value? */ + first = luaK_exp2anyreg(fs, &e); /* can use original slot */ + else { /* values must go to the top of the stack */ + luaK_exp2nextreg(fs, &e); + lua_assert(nret == fs->freereg - first); + } + } + } + luaK_ret(fs, first, nret); + testnext(ls, ';'); /* skip optional semicolon */ +} + + +static void statement (LexState *ls) { + int line = ls->linenumber; /* may be needed for error messages */ + enterlevel(ls); + switch (ls->t.token) { + case ';': { /* stat -> ';' (empty statement) */ + luaX_next(ls); /* skip ';' */ + break; + } + case TK_IF: { /* stat -> ifstat */ + ifstat(ls, line); + break; + } + case TK_WHILE: { /* stat -> whilestat */ + whilestat(ls, line); + break; + } + case TK_DO: { /* stat -> DO block END */ + luaX_next(ls); /* skip DO */ + block(ls); + check_match(ls, TK_END, TK_DO, line); + break; + } + case TK_FOR: { /* stat -> forstat */ + forstat(ls, line); + break; + } + case TK_REPEAT: { /* stat -> repeatstat */ + repeatstat(ls, line); + break; + } + case TK_FUNCTION: { /* stat -> funcstat */ + funcstat(ls, line); + break; + } + case TK_LOCAL: { /* stat -> localstat */ + luaX_next(ls); /* skip LOCAL */ + if (testnext(ls, TK_FUNCTION)) /* local function? */ + localfunc(ls); + else + localstat(ls); + break; + } + case TK_DBCOLON: { /* stat -> label */ + luaX_next(ls); /* skip double colon */ + labelstat(ls, str_checkname(ls), line); + break; + } + case TK_RETURN: { /* stat -> retstat */ + luaX_next(ls); /* skip RETURN */ + retstat(ls); + break; + } + case TK_BREAK: { /* stat -> breakstat */ + breakstat(ls); + break; + } + case TK_GOTO: { /* stat -> 'goto' NAME */ + luaX_next(ls); /* skip 'goto' */ + gotostat(ls); + break; + } + default: { /* stat -> func | assignment */ + exprstat(ls); + break; + } + } + lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && + ls->fs->freereg >= luaY_nvarstack(ls->fs)); + ls->fs->freereg = luaY_nvarstack(ls->fs); /* free registers */ + leavelevel(ls); +} + +/* }====================================================================== */ + + +/* +** compiles the main function, which is a regular vararg function with an +** upvalue named LUA_ENV +*/ +static void mainfunc (LexState *ls, FuncState *fs) { + BlockCnt bl; + Upvaldesc *env; + open_func(ls, fs, &bl); + setvararg(fs, 0); /* main function is always declared vararg */ + env = allocupvalue(fs); /* ...set environment upvalue */ + env->instack = 1; + env->idx = 0; + env->kind = VDKREG; + env->name = ls->envn; + luaC_objbarrier(ls->L, fs->f, env->name); + luaX_next(ls); /* read first token */ + statlist(ls); /* parse main body */ + check(ls, TK_EOS); + close_func(ls); +} + + +LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar) { + LexState lexstate; + FuncState funcstate; + LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */ + setclLvalue2s(L, L->top.p, cl); /* anchor it (to avoid being collected) */ + luaD_inctop(L); + lexstate.h = luaH_new(L); /* create table for scanner */ + sethvalue2s(L, L->top.p, lexstate.h); /* anchor it */ + luaD_inctop(L); + funcstate.f = cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); + funcstate.f->source = luaS_new(L, name); /* create and anchor TString */ + luaC_objbarrier(L, funcstate.f, funcstate.f->source); + lexstate.buff = buff; + lexstate.dyd = dyd; + dyd->actvar.n = dyd->gt.n = dyd->label.n = 0; + luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar); + mainfunc(&lexstate, &funcstate); + lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs); + /* all scopes should be correctly finished */ + lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0); + L->top.p--; /* remove scanner's table */ + return cl; /* closure is on the stack, too */ +} + diff --git a/arm9/source/lua/lparser.h b/arm9/source/lua/lparser.h new file mode 100644 index 000000000..5e4500f18 --- /dev/null +++ b/arm9/source/lua/lparser.h @@ -0,0 +1,171 @@ +/* +** $Id: lparser.h $ +** Lua Parser +** See Copyright Notice in lua.h +*/ + +#ifndef lparser_h +#define lparser_h + +#include "llimits.h" +#include "lobject.h" +#include "lzio.h" + + +/* +** Expression and variable descriptor. +** Code generation for variables and expressions can be delayed to allow +** optimizations; An 'expdesc' structure describes a potentially-delayed +** variable/expression. It has a description of its "main" value plus a +** list of conditional jumps that can also produce its value (generated +** by short-circuit operators 'and'/'or'). +*/ + +/* kinds of variables/expressions */ +typedef enum { + VVOID, /* when 'expdesc' describes the last expression of a list, + this kind means an empty list (so, no expression) */ + VNIL, /* constant nil */ + VTRUE, /* constant true */ + VFALSE, /* constant false */ + VK, /* constant in 'k'; info = index of constant in 'k' */ + VKFLT, /* floating constant; nval = numerical float value */ + VKINT, /* integer constant; ival = numerical integer value */ + VKSTR, /* string constant; strval = TString address; + (string is fixed by the lexer) */ + VNONRELOC, /* expression has its value in a fixed register; + info = result register */ + VLOCAL, /* local variable; var.ridx = register index; + var.vidx = relative index in 'actvar.arr' */ + VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ + VCONST, /* compile-time variable; + info = absolute index in 'actvar.arr' */ + VINDEXED, /* indexed variable; + ind.t = table register; + ind.idx = key's R index */ + VINDEXUP, /* indexed upvalue; + ind.t = table upvalue; + ind.idx = key's K index */ + VINDEXI, /* indexed variable with constant integer; + ind.t = table register; + ind.idx = key's value */ + VINDEXSTR, /* indexed variable with literal string; + ind.t = table register; + ind.idx = key's K index */ + VJMP, /* expression is a test/comparison; + info = pc of corresponding jump instruction */ + VRELOC, /* expression can put result in any register; + info = instruction pc */ + VCALL, /* expression is a function call; info = instruction pc */ + VVARARG /* vararg expression; info = instruction pc */ +} expkind; + + +#define vkisvar(k) (VLOCAL <= (k) && (k) <= VINDEXSTR) +#define vkisindexed(k) (VINDEXED <= (k) && (k) <= VINDEXSTR) + + +typedef struct expdesc { + expkind k; + union { + lua_Integer ival; /* for VKINT */ + lua_Number nval; /* for VKFLT */ + TString *strval; /* for VKSTR */ + int info; /* for generic use */ + struct { /* for indexed variables */ + short idx; /* index (R or "long" K) */ + lu_byte t; /* table (register or upvalue) */ + } ind; + struct { /* for local variables */ + lu_byte ridx; /* register holding the variable */ + unsigned short vidx; /* compiler index (in 'actvar.arr') */ + } var; + } u; + int t; /* patch list of 'exit when true' */ + int f; /* patch list of 'exit when false' */ +} expdesc; + + +/* kinds of variables */ +#define VDKREG 0 /* regular */ +#define RDKCONST 1 /* constant */ +#define RDKTOCLOSE 2 /* to-be-closed */ +#define RDKCTC 3 /* compile-time constant */ + +/* description of an active local variable */ +typedef union Vardesc { + struct { + TValuefields; /* constant value (if it is a compile-time constant) */ + lu_byte kind; + lu_byte ridx; /* register holding the variable */ + short pidx; /* index of the variable in the Proto's 'locvars' array */ + TString *name; /* variable name */ + } vd; + TValue k; /* constant value (if any) */ +} Vardesc; + + + +/* description of pending goto statements and label statements */ +typedef struct Labeldesc { + TString *name; /* label identifier */ + int pc; /* position in code */ + int line; /* line where it appeared */ + lu_byte nactvar; /* number of active variables in that position */ + lu_byte close; /* goto that escapes upvalues */ +} Labeldesc; + + +/* list of labels or gotos */ +typedef struct Labellist { + Labeldesc *arr; /* array */ + int n; /* number of entries in use */ + int size; /* array size */ +} Labellist; + + +/* dynamic structures used by the parser */ +typedef struct Dyndata { + struct { /* list of all active local variables */ + Vardesc *arr; + int n; + int size; + } actvar; + Labellist gt; /* list of pending gotos */ + Labellist label; /* list of active labels */ +} Dyndata; + + +/* control of blocks */ +struct BlockCnt; /* defined in lparser.c */ + + +/* state needed to generate code for a given function */ +typedef struct FuncState { + Proto *f; /* current function header */ + struct FuncState *prev; /* enclosing function */ + struct LexState *ls; /* lexical state */ + struct BlockCnt *bl; /* chain of current blocks */ + int pc; /* next position to code (equivalent to 'ncode') */ + int lasttarget; /* 'label' of last 'jump label' */ + int previousline; /* last line that was saved in 'lineinfo' */ + int nk; /* number of elements in 'k' */ + int np; /* number of elements in 'p' */ + int nabslineinfo; /* number of elements in 'abslineinfo' */ + int firstlocal; /* index of first local var (in Dyndata array) */ + int firstlabel; /* index of first label (in 'dyd->label->arr') */ + short ndebugvars; /* number of elements in 'f->locvars' */ + lu_byte nactvar; /* number of active local variables */ + lu_byte nups; /* number of upvalues */ + lu_byte freereg; /* first free register */ + lu_byte iwthabs; /* instructions issued since last absolute line info */ + lu_byte needclose; /* function needs to close upvalues when returning */ +} FuncState; + + +LUAI_FUNC int luaY_nvarstack (FuncState *fs); +LUAI_FUNC LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + Dyndata *dyd, const char *name, int firstchar); + + +#endif diff --git a/arm9/source/lua/lprefix.h b/arm9/source/lua/lprefix.h new file mode 100644 index 000000000..484f2ad6f --- /dev/null +++ b/arm9/source/lua/lprefix.h @@ -0,0 +1,45 @@ +/* +** $Id: lprefix.h $ +** Definitions for Lua code that must come before any other header file +** See Copyright Notice in lua.h +*/ + +#ifndef lprefix_h +#define lprefix_h + + +/* +** Allows POSIX/XSI stuff +*/ +#if !defined(LUA_USE_C89) /* { */ + +#if !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 600 +#elif _XOPEN_SOURCE == 0 +#undef _XOPEN_SOURCE /* use -D_XOPEN_SOURCE=0 to undefine it */ +#endif + +/* +** Allows manipulation of large files in gcc and some other compilers +*/ +#if !defined(LUA_32BITS) && !defined(_FILE_OFFSET_BITS) +#define _LARGEFILE_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#endif + +#endif /* } */ + + +/* +** Windows stuff +*/ +#if defined(_WIN32) /* { */ + +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS /* avoid warnings about ISO C functions */ +#endif + +#endif /* } */ + +#endif + diff --git a/arm9/source/lua/lstate.c b/arm9/source/lua/lstate.c new file mode 100644 index 000000000..1e925e5ad --- /dev/null +++ b/arm9/source/lua/lstate.c @@ -0,0 +1,445 @@ +/* +** $Id: lstate.c $ +** Global State +** See Copyright Notice in lua.h +*/ + +#define lstate_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + + +/* +** thread state + extra space +*/ +typedef struct LX { + lu_byte extra_[LUA_EXTRASPACE]; + lua_State l; +} LX; + + +/* +** Main thread combines a thread state and the global state +*/ +typedef struct LG { + LX l; + global_State g; +} LG; + + + +#define fromstate(L) (cast(LX *, cast(lu_byte *, (L)) - offsetof(LX, l))) + + +/* +** A macro to create a "random" seed when a state is created; +** the seed is used to randomize string hashes. +*/ +#if !defined(luai_makeseed) + +#include + +/* +** Compute an initial seed with some level of randomness. +** Rely on Address Space Layout Randomization (if present) and +** current time. +*/ +#define addbuff(b,p,e) \ + { size_t t = cast_sizet(e); \ + memcpy(b + p, &t, sizeof(t)); p += sizeof(t); } + +static unsigned int luai_makeseed (lua_State *L) { + char buff[3 * sizeof(size_t)]; + unsigned int h = cast_uint(time(NULL)); + int p = 0; + addbuff(buff, p, L); /* heap variable */ + addbuff(buff, p, &h); /* local variable */ + addbuff(buff, p, &lua_newstate); /* public function */ + lua_assert(p == sizeof(buff)); + return luaS_hash(buff, p, h); +} + +#endif + + +/* +** set GCdebt to a new value keeping the value (totalbytes + GCdebt) +** invariant (and avoiding underflows in 'totalbytes') +*/ +void luaE_setdebt (global_State *g, l_mem debt) { + l_mem tb = gettotalbytes(g); + lua_assert(tb > 0); + if (debt < tb - MAX_LMEM) + debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */ + g->totalbytes = tb - debt; + g->GCdebt = debt; +} + + +LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) { + UNUSED(L); UNUSED(limit); + return LUAI_MAXCCALLS; /* warning?? */ +} + + +CallInfo *luaE_extendCI (lua_State *L) { + CallInfo *ci; + lua_assert(L->ci->next == NULL); + ci = luaM_new(L, CallInfo); + lua_assert(L->ci->next == NULL); + L->ci->next = ci; + ci->previous = L->ci; + ci->next = NULL; + ci->u.l.trap = 0; + L->nci++; + return ci; +} + + +/* +** free all CallInfo structures not in use by a thread +*/ +void luaE_freeCI (lua_State *L) { + CallInfo *ci = L->ci; + CallInfo *next = ci->next; + ci->next = NULL; + while ((ci = next) != NULL) { + next = ci->next; + luaM_free(L, ci); + L->nci--; + } +} + + +/* +** free half of the CallInfo structures not in use by a thread, +** keeping the first one. +*/ +void luaE_shrinkCI (lua_State *L) { + CallInfo *ci = L->ci->next; /* first free CallInfo */ + CallInfo *next; + if (ci == NULL) + return; /* no extra elements */ + while ((next = ci->next) != NULL) { /* two extra elements? */ + CallInfo *next2 = next->next; /* next's next */ + ci->next = next2; /* remove next from the list */ + L->nci--; + luaM_free(L, next); /* free next */ + if (next2 == NULL) + break; /* no more elements */ + else { + next2->previous = ci; + ci = next2; /* continue */ + } + } +} + + +/* +** Called when 'getCcalls(L)' larger or equal to LUAI_MAXCCALLS. +** If equal, raises an overflow error. If value is larger than +** LUAI_MAXCCALLS (which means it is handling an overflow) but +** not much larger, does not report an error (to allow overflow +** handling to work). +*/ +void luaE_checkcstack (lua_State *L) { + if (getCcalls(L) == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11)) + luaD_throw(L, LUA_ERRERR); /* error while handling stack error */ +} + + +LUAI_FUNC void luaE_incCstack (lua_State *L) { + L->nCcalls++; + if (l_unlikely(getCcalls(L) >= LUAI_MAXCCALLS)) + luaE_checkcstack(L); +} + + +static void stack_init (lua_State *L1, lua_State *L) { + int i; CallInfo *ci; + /* initialize stack array */ + L1->stack.p = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue); + L1->tbclist.p = L1->stack.p; + for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) + setnilvalue(s2v(L1->stack.p + i)); /* erase new stack */ + L1->top.p = L1->stack.p; + L1->stack_last.p = L1->stack.p + BASIC_STACK_SIZE; + /* initialize first ci */ + ci = &L1->base_ci; + ci->next = ci->previous = NULL; + ci->callstatus = CIST_C; + ci->func.p = L1->top.p; + ci->u.c.k = NULL; + ci->nresults = 0; + setnilvalue(s2v(L1->top.p)); /* 'function' entry for this 'ci' */ + L1->top.p++; + ci->top.p = L1->top.p + LUA_MINSTACK; + L1->ci = ci; +} + + +static void freestack (lua_State *L) { + if (L->stack.p == NULL) + return; /* stack not completely built yet */ + L->ci = &L->base_ci; /* free the entire 'ci' list */ + luaE_freeCI(L); + lua_assert(L->nci == 0); + luaM_freearray(L, L->stack.p, stacksize(L) + EXTRA_STACK); /* free stack */ +} + + +/* +** Create registry table and its predefined values +*/ +static void init_registry (lua_State *L, global_State *g) { + /* create registry */ + Table *registry = luaH_new(L); + sethvalue(L, &g->l_registry, registry); + luaH_resize(L, registry, LUA_RIDX_LAST, 0); + /* registry[LUA_RIDX_MAINTHREAD] = L */ + setthvalue(L, ®istry->array[LUA_RIDX_MAINTHREAD - 1], L); + /* registry[LUA_RIDX_GLOBALS] = new table (table of globals) */ + sethvalue(L, ®istry->array[LUA_RIDX_GLOBALS - 1], luaH_new(L)); +} + + +/* +** open parts of the state that may cause memory-allocation errors. +*/ +static void f_luaopen (lua_State *L, void *ud) { + global_State *g = G(L); + UNUSED(ud); + stack_init(L, L); /* init stack */ + init_registry(L, g); + luaS_init(L); + luaT_init(L); + luaX_init(L); + g->gcstp = 0; /* allow gc */ + setnilvalue(&g->nilvalue); /* now state is complete */ + luai_userstateopen(L); +} + + +/* +** preinitialize a thread with consistent values without allocating +** any memory (to avoid errors) +*/ +static void preinit_thread (lua_State *L, global_State *g) { + G(L) = g; + L->stack.p = NULL; + L->ci = NULL; + L->nci = 0; + L->twups = L; /* thread has no upvalues */ + L->nCcalls = 0; + L->errorJmp = NULL; + L->hook = NULL; + L->hookmask = 0; + L->basehookcount = 0; + L->allowhook = 1; + resethookcount(L); + L->openupval = NULL; + L->status = LUA_OK; + L->errfunc = 0; + L->oldpc = 0; +} + + +static void close_state (lua_State *L) { + global_State *g = G(L); + if (!completestate(g)) /* closing a partially built state? */ + luaC_freeallobjects(L); /* just collect its objects */ + else { /* closing a fully built state */ + L->ci = &L->base_ci; /* unwind CallInfo list */ + luaD_closeprotected(L, 1, LUA_OK); /* close all upvalues */ + luaC_freeallobjects(L); /* collect all objects */ + luai_userstateclose(L); + } + luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); + freestack(L); + lua_assert(gettotalbytes(g) == sizeof(LG)); + (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ +} + + +LUA_API lua_State *lua_newthread (lua_State *L) { + global_State *g = G(L); + GCObject *o; + lua_State *L1; + lua_lock(L); + luaC_checkGC(L); + /* create new thread */ + o = luaC_newobjdt(L, LUA_TTHREAD, sizeof(LX), offsetof(LX, l)); + L1 = gco2th(o); + /* anchor it on L stack */ + setthvalue2s(L, L->top.p, L1); + api_incr_top(L); + preinit_thread(L1, g); + L1->hookmask = L->hookmask; + L1->basehookcount = L->basehookcount; + L1->hook = L->hook; + resethookcount(L1); + /* initialize L1 extra space */ + memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread), + LUA_EXTRASPACE); + luai_userstatethread(L, L1); + stack_init(L1, L); /* init stack */ + lua_unlock(L); + return L1; +} + + +void luaE_freethread (lua_State *L, lua_State *L1) { + LX *l = fromstate(L1); + luaF_closeupval(L1, L1->stack.p); /* close all upvalues */ + lua_assert(L1->openupval == NULL); + luai_userstatefree(L, L1); + freestack(L1); + luaM_free(L, l); +} + + +int luaE_resetthread (lua_State *L, int status) { + CallInfo *ci = L->ci = &L->base_ci; /* unwind CallInfo list */ + setnilvalue(s2v(L->stack.p)); /* 'function' entry for basic 'ci' */ + ci->func.p = L->stack.p; + ci->callstatus = CIST_C; + if (status == LUA_YIELD) + status = LUA_OK; + L->status = LUA_OK; /* so it can run __close metamethods */ + status = luaD_closeprotected(L, 1, status); + if (status != LUA_OK) /* errors? */ + luaD_seterrorobj(L, status, L->stack.p + 1); + else + L->top.p = L->stack.p + 1; + ci->top.p = L->top.p + LUA_MINSTACK; + luaD_reallocstack(L, cast_int(ci->top.p - L->stack.p), 0); + return status; +} + + +LUA_API int lua_closethread (lua_State *L, lua_State *from) { + int status; + lua_lock(L); + L->nCcalls = (from) ? getCcalls(from) : 0; + status = luaE_resetthread(L, L->status); + lua_unlock(L); + return status; +} + + +/* +** Deprecated! Use 'lua_closethread' instead. +*/ +LUA_API int lua_resetthread (lua_State *L) { + return lua_closethread(L, NULL); +} + + +LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { + int i; + lua_State *L; + global_State *g; + LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); + if (l == NULL) return NULL; + L = &l->l.l; + g = &l->g; + L->tt = LUA_VTHREAD; + g->currentwhite = bitmask(WHITE0BIT); + L->marked = luaC_white(g); + preinit_thread(L, g); + g->allgc = obj2gco(L); /* by now, only object is the main thread */ + L->next = NULL; + incnny(L); /* main thread is always non yieldable */ + g->frealloc = f; + g->ud = ud; + g->warnf = NULL; + g->ud_warn = NULL; + g->mainthread = L; + g->seed = luai_makeseed(L); + g->gcstp = GCSTPGC; /* no GC while building state */ + g->strt.size = g->strt.nuse = 0; + g->strt.hash = NULL; + setnilvalue(&g->l_registry); + g->panic = NULL; + g->gcstate = GCSpause; + g->gckind = KGC_INC; + g->gcstopem = 0; + g->gcemergency = 0; + g->finobj = g->tobefnz = g->fixedgc = NULL; + g->firstold1 = g->survival = g->old1 = g->reallyold = NULL; + g->finobjsur = g->finobjold1 = g->finobjrold = NULL; + g->sweepgc = NULL; + g->gray = g->grayagain = NULL; + g->weak = g->ephemeron = g->allweak = NULL; + g->twups = NULL; + g->totalbytes = sizeof(LG); + g->GCdebt = 0; + g->lastatomic = 0; + setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */ + setgcparam(g->gcpause, LUAI_GCPAUSE); + setgcparam(g->gcstepmul, LUAI_GCMUL); + g->gcstepsize = LUAI_GCSTEPSIZE; + setgcparam(g->genmajormul, LUAI_GENMAJORMUL); + g->genminormul = LUAI_GENMINORMUL; + for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; + if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { + /* memory allocation error: free partial state */ + close_state(L); + L = NULL; + } + return L; +} + + +LUA_API void lua_close (lua_State *L) { + lua_lock(L); + L = G(L)->mainthread; /* only the main thread can be closed */ + close_state(L); +} + + +void luaE_warning (lua_State *L, const char *msg, int tocont) { + lua_WarnFunction wf = G(L)->warnf; + if (wf != NULL) + wf(G(L)->ud_warn, msg, tocont); +} + + +/* +** Generate a warning from an error message +*/ +void luaE_warnerror (lua_State *L, const char *where) { + TValue *errobj = s2v(L->top.p - 1); /* error object */ + const char *msg = (ttisstring(errobj)) + ? svalue(errobj) + : "error object is not a string"; + /* produce warning "error in %s (%s)" (where, msg) */ + luaE_warning(L, "error in ", 1); + luaE_warning(L, where, 1); + luaE_warning(L, " (", 1); + luaE_warning(L, msg, 1); + luaE_warning(L, ")", 0); +} + diff --git a/arm9/source/lua/lstate.h b/arm9/source/lua/lstate.h new file mode 100644 index 000000000..8bf6600e3 --- /dev/null +++ b/arm9/source/lua/lstate.h @@ -0,0 +1,409 @@ +/* +** $Id: lstate.h $ +** Global State +** See Copyright Notice in lua.h +*/ + +#ifndef lstate_h +#define lstate_h + +#include "lua.h" + + +/* Some header files included here need this definition */ +typedef struct CallInfo CallInfo; + + +#include "lobject.h" +#include "ltm.h" +#include "lzio.h" + + +/* +** Some notes about garbage-collected objects: All objects in Lua must +** be kept somehow accessible until being freed, so all objects always +** belong to one (and only one) of these lists, using field 'next' of +** the 'CommonHeader' for the link: +** +** 'allgc': all objects not marked for finalization; +** 'finobj': all objects marked for finalization; +** 'tobefnz': all objects ready to be finalized; +** 'fixedgc': all objects that are not to be collected (currently +** only small strings, such as reserved words). +** +** For the generational collector, some of these lists have marks for +** generations. Each mark points to the first element in the list for +** that particular generation; that generation goes until the next mark. +** +** 'allgc' -> 'survival': new objects; +** 'survival' -> 'old': objects that survived one collection; +** 'old1' -> 'reallyold': objects that became old in last collection; +** 'reallyold' -> NULL: objects old for more than one cycle. +** +** 'finobj' -> 'finobjsur': new objects marked for finalization; +** 'finobjsur' -> 'finobjold1': survived """"; +** 'finobjold1' -> 'finobjrold': just old """"; +** 'finobjrold' -> NULL: really old """". +** +** All lists can contain elements older than their main ages, due +** to 'luaC_checkfinalizer' and 'udata2finalize', which move +** objects between the normal lists and the "marked for finalization" +** lists. Moreover, barriers can age young objects in young lists as +** OLD0, which then become OLD1. However, a list never contains +** elements younger than their main ages. +** +** The generational collector also uses a pointer 'firstold1', which +** points to the first OLD1 object in the list. It is used to optimize +** 'markold'. (Potentially OLD1 objects can be anywhere between 'allgc' +** and 'reallyold', but often the list has no OLD1 objects or they are +** after 'old1'.) Note the difference between it and 'old1': +** 'firstold1': no OLD1 objects before this point; there can be all +** ages after it. +** 'old1': no objects younger than OLD1 after this point. +*/ + +/* +** Moreover, there is another set of lists that control gray objects. +** These lists are linked by fields 'gclist'. (All objects that +** can become gray have such a field. The field is not the same +** in all objects, but it always has this name.) Any gray object +** must belong to one of these lists, and all objects in these lists +** must be gray (with two exceptions explained below): +** +** 'gray': regular gray objects, still waiting to be visited. +** 'grayagain': objects that must be revisited at the atomic phase. +** That includes +** - black objects got in a write barrier; +** - all kinds of weak tables during propagation phase; +** - all threads. +** 'weak': tables with weak values to be cleared; +** 'ephemeron': ephemeron tables with white->white entries; +** 'allweak': tables with weak keys and/or weak values to be cleared. +** +** The exceptions to that "gray rule" are: +** - TOUCHED2 objects in generational mode stay in a gray list (because +** they must be visited again at the end of the cycle), but they are +** marked black because assignments to them must activate barriers (to +** move them back to TOUCHED1). +** - Open upvales are kept gray to avoid barriers, but they stay out +** of gray lists. (They don't even have a 'gclist' field.) +*/ + + + +/* +** About 'nCcalls': This count has two parts: the lower 16 bits counts +** the number of recursive invocations in the C stack; the higher +** 16 bits counts the number of non-yieldable calls in the stack. +** (They are together so that we can change and save both with one +** instruction.) +*/ + + +/* true if this thread does not have non-yieldable calls in the stack */ +#define yieldable(L) (((L)->nCcalls & 0xffff0000) == 0) + +/* real number of C calls */ +#define getCcalls(L) ((L)->nCcalls & 0xffff) + + +/* Increment the number of non-yieldable calls */ +#define incnny(L) ((L)->nCcalls += 0x10000) + +/* Decrement the number of non-yieldable calls */ +#define decnny(L) ((L)->nCcalls -= 0x10000) + +/* Non-yieldable call increment */ +#define nyci (0x10000 | 1) + + + + +struct lua_longjmp; /* defined in ldo.c */ + + +/* +** Atomic type (relative to signals) to better ensure that 'lua_sethook' +** is thread safe +*/ +#if !defined(l_signalT) +#include +#define l_signalT sig_atomic_t +#endif + + +/* +** Extra stack space to handle TM calls and some other extras. This +** space is not included in 'stack_last'. It is used only to avoid stack +** checks, either because the element will be promptly popped or because +** there will be a stack check soon after the push. Function frames +** never use this extra space, so it does not need to be kept clean. +*/ +#define EXTRA_STACK 5 + + +#define BASIC_STACK_SIZE (2*LUA_MINSTACK) + +#define stacksize(th) cast_int((th)->stack_last.p - (th)->stack.p) + + +/* kinds of Garbage Collection */ +#define KGC_INC 0 /* incremental gc */ +#define KGC_GEN 1 /* generational gc */ + + +typedef struct stringtable { + TString **hash; + int nuse; /* number of elements */ + int size; +} stringtable; + + +/* +** Information about a call. +** About union 'u': +** - field 'l' is used only for Lua functions; +** - field 'c' is used only for C functions. +** About union 'u2': +** - field 'funcidx' is used only by C functions while doing a +** protected call; +** - field 'nyield' is used only while a function is "doing" an +** yield (from the yield until the next resume); +** - field 'nres' is used only while closing tbc variables when +** returning from a function; +** - field 'transferinfo' is used only during call/returnhooks, +** before the function starts or after it ends. +*/ +struct CallInfo { + StkIdRel func; /* function index in the stack */ + StkIdRel top; /* top for this function */ + struct CallInfo *previous, *next; /* dynamic call link */ + union { + struct { /* only for Lua functions */ + const Instruction *savedpc; + volatile l_signalT trap; + int nextraargs; /* # of extra arguments in vararg functions */ + } l; + struct { /* only for C functions */ + lua_KFunction k; /* continuation in case of yields */ + ptrdiff_t old_errfunc; + lua_KContext ctx; /* context info. in case of yields */ + } c; + } u; + union { + int funcidx; /* called-function index */ + int nyield; /* number of values yielded */ + int nres; /* number of values returned */ + struct { /* info about transferred values (for call/return hooks) */ + unsigned short ftransfer; /* offset of first value transferred */ + unsigned short ntransfer; /* number of values transferred */ + } transferinfo; + } u2; + short nresults; /* expected number of results from this function */ + unsigned short callstatus; +}; + + +/* +** Bits in CallInfo status +*/ +#define CIST_OAH (1<<0) /* original value of 'allowhook' */ +#define CIST_C (1<<1) /* call is running a C function */ +#define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ +#define CIST_HOOKED (1<<3) /* call is running a debug hook */ +#define CIST_YPCALL (1<<4) /* doing a yieldable protected call */ +#define CIST_TAIL (1<<5) /* call was tail called */ +#define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ +#define CIST_FIN (1<<7) /* function "called" a finalizer */ +#define CIST_TRAN (1<<8) /* 'ci' has transfer information */ +#define CIST_CLSRET (1<<9) /* function is closing tbc variables */ +/* Bits 10-12 are used for CIST_RECST (see below) */ +#define CIST_RECST 10 +#if defined(LUA_COMPAT_LT_LE) +#define CIST_LEQ (1<<13) /* using __lt for __le */ +#endif + + +/* +** Field CIST_RECST stores the "recover status", used to keep the error +** status while closing to-be-closed variables in coroutines, so that +** Lua can correctly resume after an yield from a __close method called +** because of an error. (Three bits are enough for error status.) +*/ +#define getcistrecst(ci) (((ci)->callstatus >> CIST_RECST) & 7) +#define setcistrecst(ci,st) \ + check_exp(((st) & 7) == (st), /* status must fit in three bits */ \ + ((ci)->callstatus = ((ci)->callstatus & ~(7 << CIST_RECST)) \ + | ((st) << CIST_RECST))) + + +/* active function is a Lua function */ +#define isLua(ci) (!((ci)->callstatus & CIST_C)) + +/* call is running Lua code (not a hook) */ +#define isLuacode(ci) (!((ci)->callstatus & (CIST_C | CIST_HOOKED))) + +/* assume that CIST_OAH has offset 0 and that 'v' is strictly 0/1 */ +#define setoah(st,v) ((st) = ((st) & ~CIST_OAH) | (v)) +#define getoah(st) ((st) & CIST_OAH) + + +/* +** 'global state', shared by all threads of this state +*/ +typedef struct global_State { + lua_Alloc frealloc; /* function to reallocate memory */ + void *ud; /* auxiliary data to 'frealloc' */ + l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ + l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ + lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ + lu_mem lastatomic; /* see function 'genstep' in file 'lgc.c' */ + stringtable strt; /* hash table for strings */ + TValue l_registry; + TValue nilvalue; /* a nil value */ + unsigned int seed; /* randomized seed for hashes */ + lu_byte currentwhite; + lu_byte gcstate; /* state of garbage collector */ + lu_byte gckind; /* kind of GC running */ + lu_byte gcstopem; /* stops emergency collections */ + lu_byte genminormul; /* control for minor generational collections */ + lu_byte genmajormul; /* control for major generational collections */ + lu_byte gcstp; /* control whether GC is running */ + lu_byte gcemergency; /* true if this is an emergency collection */ + lu_byte gcpause; /* size of pause between successive GCs */ + lu_byte gcstepmul; /* GC "speed" */ + lu_byte gcstepsize; /* (log2 of) GC granularity */ + GCObject *allgc; /* list of all collectable objects */ + GCObject **sweepgc; /* current position of sweep in list */ + GCObject *finobj; /* list of collectable objects with finalizers */ + GCObject *gray; /* list of gray objects */ + GCObject *grayagain; /* list of objects to be traversed atomically */ + GCObject *weak; /* list of tables with weak values */ + GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ + GCObject *allweak; /* list of all-weak tables */ + GCObject *tobefnz; /* list of userdata to be GC */ + GCObject *fixedgc; /* list of objects not to be collected */ + /* fields for generational collector */ + GCObject *survival; /* start of objects that survived one GC cycle */ + GCObject *old1; /* start of old1 objects */ + GCObject *reallyold; /* objects more than one cycle old ("really old") */ + GCObject *firstold1; /* first OLD1 object in the list (if any) */ + GCObject *finobjsur; /* list of survival objects with finalizers */ + GCObject *finobjold1; /* list of old1 objects with finalizers */ + GCObject *finobjrold; /* list of really old objects with finalizers */ + struct lua_State *twups; /* list of threads with open upvalues */ + lua_CFunction panic; /* to be called in unprotected errors */ + struct lua_State *mainthread; + TString *memerrmsg; /* message for memory-allocation errors */ + TString *tmname[TM_N]; /* array with tag-method names */ + struct Table *mt[LUA_NUMTYPES]; /* metatables for basic types */ + TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ + lua_WarnFunction warnf; /* warning function */ + void *ud_warn; /* auxiliary data to 'warnf' */ +} global_State; + + +/* +** 'per thread' state +*/ +struct lua_State { + CommonHeader; + lu_byte status; + lu_byte allowhook; + unsigned short nci; /* number of items in 'ci' list */ + StkIdRel top; /* first free slot in the stack */ + global_State *l_G; + CallInfo *ci; /* call info for current function */ + StkIdRel stack_last; /* end of stack (last element + 1) */ + StkIdRel stack; /* stack base */ + UpVal *openupval; /* list of open upvalues in this stack */ + StkIdRel tbclist; /* list of to-be-closed variables */ + GCObject *gclist; + struct lua_State *twups; /* list of threads with open upvalues */ + struct lua_longjmp *errorJmp; /* current error recover point */ + CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ + volatile lua_Hook hook; + ptrdiff_t errfunc; /* current error handling function (stack index) */ + l_uint32 nCcalls; /* number of nested (non-yieldable | C) calls */ + int oldpc; /* last pc traced */ + int basehookcount; + int hookcount; + volatile l_signalT hookmask; +}; + + +#define G(L) (L->l_G) + +/* +** 'g->nilvalue' being a nil value flags that the state was completely +** build. +*/ +#define completestate(g) ttisnil(&g->nilvalue) + + +/* +** Union of all collectable objects (only for conversions) +** ISO C99, 6.5.2.3 p.5: +** "if a union contains several structures that share a common initial +** sequence [...], and if the union object currently contains one +** of these structures, it is permitted to inspect the common initial +** part of any of them anywhere that a declaration of the complete type +** of the union is visible." +*/ +union GCUnion { + GCObject gc; /* common header */ + struct TString ts; + struct Udata u; + union Closure cl; + struct Table h; + struct Proto p; + struct lua_State th; /* thread */ + struct UpVal upv; +}; + + +/* +** ISO C99, 6.7.2.1 p.14: +** "A pointer to a union object, suitably converted, points to each of +** its members [...], and vice versa." +*/ +#define cast_u(o) cast(union GCUnion *, (o)) + +/* macros to convert a GCObject into a specific value */ +#define gco2ts(o) \ + check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts)) +#define gco2u(o) check_exp((o)->tt == LUA_VUSERDATA, &((cast_u(o))->u)) +#define gco2lcl(o) check_exp((o)->tt == LUA_VLCL, &((cast_u(o))->cl.l)) +#define gco2ccl(o) check_exp((o)->tt == LUA_VCCL, &((cast_u(o))->cl.c)) +#define gco2cl(o) \ + check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl)) +#define gco2t(o) check_exp((o)->tt == LUA_VTABLE, &((cast_u(o))->h)) +#define gco2p(o) check_exp((o)->tt == LUA_VPROTO, &((cast_u(o))->p)) +#define gco2th(o) check_exp((o)->tt == LUA_VTHREAD, &((cast_u(o))->th)) +#define gco2upv(o) check_exp((o)->tt == LUA_VUPVAL, &((cast_u(o))->upv)) + + +/* +** macro to convert a Lua object into a GCObject +** (The access to 'tt' tries to ensure that 'v' is actually a Lua object.) +*/ +#define obj2gco(v) check_exp((v)->tt >= LUA_TSTRING, &(cast_u(v)->gc)) + + +/* actual number of total bytes allocated */ +#define gettotalbytes(g) cast(lu_mem, (g)->totalbytes + (g)->GCdebt) + +LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); +LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); +LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); +LUAI_FUNC void luaE_freeCI (lua_State *L); +LUAI_FUNC void luaE_shrinkCI (lua_State *L); +LUAI_FUNC void luaE_checkcstack (lua_State *L); +LUAI_FUNC void luaE_incCstack (lua_State *L); +LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont); +LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where); +LUAI_FUNC int luaE_resetthread (lua_State *L, int status); + + +#endif + diff --git a/arm9/source/lua/lstring.c b/arm9/source/lua/lstring.c new file mode 100644 index 000000000..13dcaf425 --- /dev/null +++ b/arm9/source/lua/lstring.c @@ -0,0 +1,273 @@ +/* +** $Id: lstring.c $ +** String table (keeps all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + +#define lstring_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" + + +/* +** Maximum size for string table. +*/ +#define MAXSTRTB cast_int(luaM_limitN(MAX_INT, TString*)) + + +/* +** equality for long strings +*/ +int luaS_eqlngstr (TString *a, TString *b) { + size_t len = a->u.lnglen; + lua_assert(a->tt == LUA_VLNGSTR && b->tt == LUA_VLNGSTR); + return (a == b) || /* same instance or... */ + ((len == b->u.lnglen) && /* equal length and ... */ + (memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */ +} + + +unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) { + unsigned int h = seed ^ cast_uint(l); + for (; l > 0; l--) + h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1])); + return h; +} + + +unsigned int luaS_hashlongstr (TString *ts) { + lua_assert(ts->tt == LUA_VLNGSTR); + if (ts->extra == 0) { /* no hash? */ + size_t len = ts->u.lnglen; + ts->hash = luaS_hash(getstr(ts), len, ts->hash); + ts->extra = 1; /* now it has its hash */ + } + return ts->hash; +} + + +static void tablerehash (TString **vect, int osize, int nsize) { + int i; + for (i = osize; i < nsize; i++) /* clear new elements */ + vect[i] = NULL; + for (i = 0; i < osize; i++) { /* rehash old part of the array */ + TString *p = vect[i]; + vect[i] = NULL; + while (p) { /* for each string in the list */ + TString *hnext = p->u.hnext; /* save next */ + unsigned int h = lmod(p->hash, nsize); /* new position */ + p->u.hnext = vect[h]; /* chain it into array */ + vect[h] = p; + p = hnext; + } + } +} + + +/* +** Resize the string table. If allocation fails, keep the current size. +** (This can degrade performance, but any non-zero size should work +** correctly.) +*/ +void luaS_resize (lua_State *L, int nsize) { + stringtable *tb = &G(L)->strt; + int osize = tb->size; + TString **newvect; + if (nsize < osize) /* shrinking table? */ + tablerehash(tb->hash, osize, nsize); /* depopulate shrinking part */ + newvect = luaM_reallocvector(L, tb->hash, osize, nsize, TString*); + if (l_unlikely(newvect == NULL)) { /* reallocation failed? */ + if (nsize < osize) /* was it shrinking table? */ + tablerehash(tb->hash, nsize, osize); /* restore to original size */ + /* leave table as it was */ + } + else { /* allocation succeeded */ + tb->hash = newvect; + tb->size = nsize; + if (nsize > osize) + tablerehash(newvect, osize, nsize); /* rehash for new size */ + } +} + + +/* +** Clear API string cache. (Entries cannot be empty, so fill them with +** a non-collectable string.) +*/ +void luaS_clearcache (global_State *g) { + int i, j; + for (i = 0; i < STRCACHE_N; i++) + for (j = 0; j < STRCACHE_M; j++) { + if (iswhite(g->strcache[i][j])) /* will entry be collected? */ + g->strcache[i][j] = g->memerrmsg; /* replace it with something fixed */ + } +} + + +/* +** Initialize the string table and the string cache +*/ +void luaS_init (lua_State *L) { + global_State *g = G(L); + int i, j; + stringtable *tb = &G(L)->strt; + tb->hash = luaM_newvector(L, MINSTRTABSIZE, TString*); + tablerehash(tb->hash, 0, MINSTRTABSIZE); /* clear array */ + tb->size = MINSTRTABSIZE; + /* pre-create memory-error message */ + g->memerrmsg = luaS_newliteral(L, MEMERRMSG); + luaC_fix(L, obj2gco(g->memerrmsg)); /* it should never be collected */ + for (i = 0; i < STRCACHE_N; i++) /* fill cache with valid strings */ + for (j = 0; j < STRCACHE_M; j++) + g->strcache[i][j] = g->memerrmsg; +} + + + +/* +** creates a new string object +*/ +static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { + TString *ts; + GCObject *o; + size_t totalsize; /* total size of TString object */ + totalsize = sizelstring(l); + o = luaC_newobj(L, tag, totalsize); + ts = gco2ts(o); + ts->hash = h; + ts->extra = 0; + getstr(ts)[l] = '\0'; /* ending 0 */ + return ts; +} + + +TString *luaS_createlngstrobj (lua_State *L, size_t l) { + TString *ts = createstrobj(L, l, LUA_VLNGSTR, G(L)->seed); + ts->u.lnglen = l; + return ts; +} + + +void luaS_remove (lua_State *L, TString *ts) { + stringtable *tb = &G(L)->strt; + TString **p = &tb->hash[lmod(ts->hash, tb->size)]; + while (*p != ts) /* find previous element */ + p = &(*p)->u.hnext; + *p = (*p)->u.hnext; /* remove element from its list */ + tb->nuse--; +} + + +static void growstrtab (lua_State *L, stringtable *tb) { + if (l_unlikely(tb->nuse == MAX_INT)) { /* too many strings? */ + luaC_fullgc(L, 1); /* try to free some... */ + if (tb->nuse == MAX_INT) /* still too many? */ + luaM_error(L); /* cannot even create a message... */ + } + if (tb->size <= MAXSTRTB / 2) /* can grow string table? */ + luaS_resize(L, tb->size * 2); +} + + +/* +** Checks whether short string exists and reuses it or creates a new one. +*/ +static TString *internshrstr (lua_State *L, const char *str, size_t l) { + TString *ts; + global_State *g = G(L); + stringtable *tb = &g->strt; + unsigned int h = luaS_hash(str, l, g->seed); + TString **list = &tb->hash[lmod(h, tb->size)]; + lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ + for (ts = *list; ts != NULL; ts = ts->u.hnext) { + if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { + /* found! */ + if (isdead(g, ts)) /* dead (but not collected yet)? */ + changewhite(ts); /* resurrect it */ + return ts; + } + } + /* else must create a new string */ + if (tb->nuse >= tb->size) { /* need to grow string table? */ + growstrtab(L, tb); + list = &tb->hash[lmod(h, tb->size)]; /* rehash with new size */ + } + ts = createstrobj(L, l, LUA_VSHRSTR, h); + memcpy(getstr(ts), str, l * sizeof(char)); + ts->shrlen = cast_byte(l); + ts->u.hnext = *list; + *list = ts; + tb->nuse++; + return ts; +} + + +/* +** new string (with explicit length) +*/ +TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { + if (l <= LUAI_MAXSHORTLEN) /* short string? */ + return internshrstr(L, str, l); + else { + TString *ts; + if (l_unlikely(l >= (MAX_SIZE - sizeof(TString))/sizeof(char))) + luaM_toobig(L); + ts = luaS_createlngstrobj(L, l); + memcpy(getstr(ts), str, l * sizeof(char)); + return ts; + } +} + + +/* +** Create or reuse a zero-terminated string, first checking in the +** cache (using the string address as a key). The cache can contain +** only zero-terminated strings, so it is safe to use 'strcmp' to +** check hits. +*/ +TString *luaS_new (lua_State *L, const char *str) { + unsigned int i = point2uint(str) % STRCACHE_N; /* hash */ + int j; + TString **p = G(L)->strcache[i]; + for (j = 0; j < STRCACHE_M; j++) { + if (strcmp(str, getstr(p[j])) == 0) /* hit? */ + return p[j]; /* that is it */ + } + /* normal route */ + for (j = STRCACHE_M - 1; j > 0; j--) + p[j] = p[j - 1]; /* move out last element */ + /* new element is first in the list */ + p[0] = luaS_newlstr(L, str, strlen(str)); + return p[0]; +} + + +Udata *luaS_newudata (lua_State *L, size_t s, int nuvalue) { + Udata *u; + int i; + GCObject *o; + if (l_unlikely(s > MAX_SIZE - udatamemoffset(nuvalue))) + luaM_toobig(L); + o = luaC_newobj(L, LUA_VUSERDATA, sizeudata(nuvalue, s)); + u = gco2u(o); + u->len = s; + u->nuvalue = nuvalue; + u->metatable = NULL; + for (i = 0; i < nuvalue; i++) + setnilvalue(&u->uv[i].uv); + return u; +} + diff --git a/arm9/source/lua/lstring.h b/arm9/source/lua/lstring.h new file mode 100644 index 000000000..450c2390d --- /dev/null +++ b/arm9/source/lua/lstring.h @@ -0,0 +1,57 @@ +/* +** $Id: lstring.h $ +** String table (keep all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + +#ifndef lstring_h +#define lstring_h + +#include "lgc.h" +#include "lobject.h" +#include "lstate.h" + + +/* +** Memory-allocation error message must be preallocated (it cannot +** be created after memory is exhausted) +*/ +#define MEMERRMSG "not enough memory" + + +/* +** Size of a TString: Size of the header plus space for the string +** itself (including final '\0'). +*/ +#define sizelstring(l) (offsetof(TString, contents) + ((l) + 1) * sizeof(char)) + +#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ + (sizeof(s)/sizeof(char))-1)) + + +/* +** test whether a string is a reserved word +*/ +#define isreserved(s) ((s)->tt == LUA_VSHRSTR && (s)->extra > 0) + + +/* +** equality for short strings, which are always internalized +*/ +#define eqshrstr(a,b) check_exp((a)->tt == LUA_VSHRSTR, (a) == (b)) + + +LUAI_FUNC unsigned int luaS_hash (const char *str, size_t l, unsigned int seed); +LUAI_FUNC unsigned int luaS_hashlongstr (TString *ts); +LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); +LUAI_FUNC void luaS_resize (lua_State *L, int newsize); +LUAI_FUNC void luaS_clearcache (global_State *g); +LUAI_FUNC void luaS_init (lua_State *L); +LUAI_FUNC void luaS_remove (lua_State *L, TString *ts); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, int nuvalue); +LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); +LUAI_FUNC TString *luaS_new (lua_State *L, const char *str); +LUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l); + + +#endif diff --git a/arm9/source/lua/lstrlib.c b/arm9/source/lua/lstrlib.c new file mode 100644 index 000000000..03167161d --- /dev/null +++ b/arm9/source/lua/lstrlib.c @@ -0,0 +1,1874 @@ +/* +** $Id: lstrlib.c $ +** Standard library for string operations and pattern-matching +** See Copyright Notice in lua.h +*/ + +#define lstrlib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** maximum number of captures that a pattern can do during +** pattern-matching. This limit is arbitrary, but must fit in +** an unsigned char. +*/ +#if !defined(LUA_MAXCAPTURES) +#define LUA_MAXCAPTURES 32 +#endif + + +/* macro to 'unsign' a character */ +#define uchar(c) ((unsigned char)(c)) + + +/* +** Some sizes are better limited to fit in 'int', but must also fit in +** 'size_t'. (We assume that 'lua_Integer' cannot be smaller than 'int'.) +*/ +#define MAX_SIZET ((size_t)(~(size_t)0)) + +#define MAXSIZE \ + (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) + + + + +static int str_len (lua_State *L) { + size_t l; + luaL_checklstring(L, 1, &l); + lua_pushinteger(L, (lua_Integer)l); + return 1; +} + + +/* +** translate a relative initial string position +** (negative means back from end): clip result to [1, inf). +** The length of any string in Lua must fit in a lua_Integer, +** so there are no overflows in the casts. +** The inverted comparison avoids a possible overflow +** computing '-pos'. +*/ +static size_t posrelatI (lua_Integer pos, size_t len) { + if (pos > 0) + return (size_t)pos; + else if (pos == 0) + return 1; + else if (pos < -(lua_Integer)len) /* inverted comparison */ + return 1; /* clip to 1 */ + else return len + (size_t)pos + 1; +} + + +/* +** Gets an optional ending string position from argument 'arg', +** with default value 'def'. +** Negative means back from end: clip result to [0, len] +*/ +static size_t getendpos (lua_State *L, int arg, lua_Integer def, + size_t len) { + lua_Integer pos = luaL_optinteger(L, arg, def); + if (pos > (lua_Integer)len) + return len; + else if (pos >= 0) + return (size_t)pos; + else if (pos < -(lua_Integer)len) + return 0; + else return len + (size_t)pos + 1; +} + + +static int str_sub (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + size_t start = posrelatI(luaL_checkinteger(L, 2), l); + size_t end = getendpos(L, 3, -1, l); + if (start <= end) + lua_pushlstring(L, s + start - 1, (end - start) + 1); + else lua_pushliteral(L, ""); + return 1; +} + + +static int str_reverse (lua_State *L) { + size_t l, i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + char *p = luaL_buffinitsize(L, &b, l); + for (i = 0; i < l; i++) + p[i] = s[l - i - 1]; + luaL_pushresultsize(&b, l); + return 1; +} + + +static int str_lower (lua_State *L) { + size_t l; + size_t i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + char *p = luaL_buffinitsize(L, &b, l); + for (i=0; i MAXSIZE / n)) + return luaL_error(L, "resulting string too large"); + else { + size_t totallen = (size_t)n * l + (size_t)(n - 1) * lsep; + luaL_Buffer b; + char *p = luaL_buffinitsize(L, &b, totallen); + while (n-- > 1) { /* first n-1 copies (followed by separator) */ + memcpy(p, s, l * sizeof(char)); p += l; + if (lsep > 0) { /* empty 'memcpy' is not that cheap */ + memcpy(p, sep, lsep * sizeof(char)); + p += lsep; + } + } + memcpy(p, s, l * sizeof(char)); /* last copy (not followed by separator) */ + luaL_pushresultsize(&b, totallen); + } + return 1; +} + + +static int str_byte (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + lua_Integer pi = luaL_optinteger(L, 2, 1); + size_t posi = posrelatI(pi, l); + size_t pose = getendpos(L, 3, pi, l); + int n, i; + if (posi > pose) return 0; /* empty interval; return no values */ + if (l_unlikely(pose - posi >= (size_t)INT_MAX)) /* arithmetic overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; + luaL_checkstack(L, n, "string slice too long"); + for (i=0; iinit) { + state->init = 1; + luaL_buffinit(L, &state->B); + } + luaL_addlstring(&state->B, (const char *)b, size); + return 0; +} + + +static int str_dump (lua_State *L) { + struct str_Writer state; + int strip = lua_toboolean(L, 2); + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, 1); /* ensure function is on the top of the stack */ + state.init = 0; + if (l_unlikely(lua_dump(L, writer, &state, strip) != 0)) + return luaL_error(L, "unable to dump given function"); + luaL_pushresult(&state.B); + return 1; +} + + + +/* +** {====================================================== +** METAMETHODS +** ======================================================= +*/ + +#if defined(LUA_NOCVTS2N) /* { */ + +/* no coercion from strings to numbers */ + +static const luaL_Reg stringmetamethods[] = { + {"__index", NULL}, /* placeholder */ + {NULL, NULL} +}; + +#else /* }{ */ + +static int tonum (lua_State *L, int arg) { + if (lua_type(L, arg) == LUA_TNUMBER) { /* already a number? */ + lua_pushvalue(L, arg); + return 1; + } + else { /* check whether it is a numerical string */ + size_t len; + const char *s = lua_tolstring(L, arg, &len); + return (s != NULL && lua_stringtonumber(L, s) == len + 1); + } +} + + +static void trymt (lua_State *L, const char *mtname) { + lua_settop(L, 2); /* back to the original arguments */ + if (l_unlikely(lua_type(L, 2) == LUA_TSTRING || + !luaL_getmetafield(L, 2, mtname))) + luaL_error(L, "attempt to %s a '%s' with a '%s'", mtname + 2, + luaL_typename(L, -2), luaL_typename(L, -1)); + lua_insert(L, -3); /* put metamethod before arguments */ + lua_call(L, 2, 1); /* call metamethod */ +} + + +static int arith (lua_State *L, int op, const char *mtname) { + if (tonum(L, 1) && tonum(L, 2)) + lua_arith(L, op); /* result will be on the top */ + else + trymt(L, mtname); + return 1; +} + + +static int arith_add (lua_State *L) { + return arith(L, LUA_OPADD, "__add"); +} + +static int arith_sub (lua_State *L) { + return arith(L, LUA_OPSUB, "__sub"); +} + +static int arith_mul (lua_State *L) { + return arith(L, LUA_OPMUL, "__mul"); +} + +static int arith_mod (lua_State *L) { + return arith(L, LUA_OPMOD, "__mod"); +} + +static int arith_pow (lua_State *L) { + return arith(L, LUA_OPPOW, "__pow"); +} + +static int arith_div (lua_State *L) { + return arith(L, LUA_OPDIV, "__div"); +} + +static int arith_idiv (lua_State *L) { + return arith(L, LUA_OPIDIV, "__idiv"); +} + +static int arith_unm (lua_State *L) { + return arith(L, LUA_OPUNM, "__unm"); +} + + +static const luaL_Reg stringmetamethods[] = { + {"__add", arith_add}, + {"__sub", arith_sub}, + {"__mul", arith_mul}, + {"__mod", arith_mod}, + {"__pow", arith_pow}, + {"__div", arith_div}, + {"__idiv", arith_idiv}, + {"__unm", arith_unm}, + {"__index", NULL}, /* placeholder */ + {NULL, NULL} +}; + +#endif /* } */ + +/* }====================================================== */ + +/* +** {====================================================== +** PATTERN MATCHING +** ======================================================= +*/ + + +#define CAP_UNFINISHED (-1) +#define CAP_POSITION (-2) + + +typedef struct MatchState { + const char *src_init; /* init of source string */ + const char *src_end; /* end ('\0') of source string */ + const char *p_end; /* end ('\0') of pattern */ + lua_State *L; + int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ + unsigned char level; /* total number of captures (finished or unfinished) */ + struct { + const char *init; + ptrdiff_t len; + } capture[LUA_MAXCAPTURES]; +} MatchState; + + +/* recursive function */ +static const char *match (MatchState *ms, const char *s, const char *p); + + +/* maximum recursion depth for 'match' */ +#if !defined(MAXCCALLS) +#define MAXCCALLS 200 +#endif + + +#define L_ESC '%' +#define SPECIALS "^$*+?.([%-" + + +static int check_capture (MatchState *ms, int l) { + l -= '1'; + if (l_unlikely(l < 0 || l >= ms->level || + ms->capture[l].len == CAP_UNFINISHED)) + return luaL_error(ms->L, "invalid capture index %%%d", l + 1); + return l; +} + + +static int capture_to_close (MatchState *ms) { + int level = ms->level; + for (level--; level>=0; level--) + if (ms->capture[level].len == CAP_UNFINISHED) return level; + return luaL_error(ms->L, "invalid pattern capture"); +} + + +static const char *classend (MatchState *ms, const char *p) { + switch (*p++) { + case L_ESC: { + if (l_unlikely(p == ms->p_end)) + luaL_error(ms->L, "malformed pattern (ends with '%%')"); + return p+1; + } + case '[': { + if (*p == '^') p++; + do { /* look for a ']' */ + if (l_unlikely(p == ms->p_end)) + luaL_error(ms->L, "malformed pattern (missing ']')"); + if (*(p++) == L_ESC && p < ms->p_end) + p++; /* skip escapes (e.g. '%]') */ + } while (*p != ']'); + return p+1; + } + default: { + return p; + } + } +} + + +static int match_class (int c, int cl) { + int res; + switch (tolower(cl)) { + case 'a' : res = isalpha(c); break; + case 'c' : res = iscntrl(c); break; + case 'd' : res = isdigit(c); break; + case 'g' : res = isgraph(c); break; + case 'l' : res = islower(c); break; + case 'p' : res = ispunct(c); break; + case 's' : res = isspace(c); break; + case 'u' : res = isupper(c); break; + case 'w' : res = isalnum(c); break; + case 'x' : res = isxdigit(c); break; + case 'z' : res = (c == 0); break; /* deprecated option */ + default: return (cl == c); + } + return (islower(cl) ? res : !res); +} + + +static int matchbracketclass (int c, const char *p, const char *ec) { + int sig = 1; + if (*(p+1) == '^') { + sig = 0; + p++; /* skip the '^' */ + } + while (++p < ec) { + if (*p == L_ESC) { + p++; + if (match_class(c, uchar(*p))) + return sig; + } + else if ((*(p+1) == '-') && (p+2 < ec)) { + p+=2; + if (uchar(*(p-2)) <= c && c <= uchar(*p)) + return sig; + } + else if (uchar(*p) == c) return sig; + } + return !sig; +} + + +static int singlematch (MatchState *ms, const char *s, const char *p, + const char *ep) { + if (s >= ms->src_end) + return 0; + else { + int c = uchar(*s); + switch (*p) { + case '.': return 1; /* matches any char */ + case L_ESC: return match_class(c, uchar(*(p+1))); + case '[': return matchbracketclass(c, p, ep-1); + default: return (uchar(*p) == c); + } + } +} + + +static const char *matchbalance (MatchState *ms, const char *s, + const char *p) { + if (l_unlikely(p >= ms->p_end - 1)) + luaL_error(ms->L, "malformed pattern (missing arguments to '%%b')"); + if (*s != *p) return NULL; + else { + int b = *p; + int e = *(p+1); + int cont = 1; + while (++s < ms->src_end) { + if (*s == e) { + if (--cont == 0) return s+1; + } + else if (*s == b) cont++; + } + } + return NULL; /* string ends out of balance */ +} + + +static const char *max_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + ptrdiff_t i = 0; /* counts maximum expand for item */ + while (singlematch(ms, s + i, p, ep)) + i++; + /* keeps trying to match with the maximum repetitions */ + while (i>=0) { + const char *res = match(ms, (s+i), ep+1); + if (res) return res; + i--; /* else didn't match; reduce 1 repetition to try again */ + } + return NULL; +} + + +static const char *min_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + for (;;) { + const char *res = match(ms, s, ep+1); + if (res != NULL) + return res; + else if (singlematch(ms, s, p, ep)) + s++; /* try with one more repetition */ + else return NULL; + } +} + + +static const char *start_capture (MatchState *ms, const char *s, + const char *p, int what) { + const char *res; + int level = ms->level; + if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); + ms->capture[level].init = s; + ms->capture[level].len = what; + ms->level = level+1; + if ((res=match(ms, s, p)) == NULL) /* match failed? */ + ms->level--; /* undo capture */ + return res; +} + + +static const char *end_capture (MatchState *ms, const char *s, + const char *p) { + int l = capture_to_close(ms); + const char *res; + ms->capture[l].len = s - ms->capture[l].init; /* close capture */ + if ((res = match(ms, s, p)) == NULL) /* match failed? */ + ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +} + + +static const char *match_capture (MatchState *ms, const char *s, int l) { + size_t len; + l = check_capture(ms, l); + len = ms->capture[l].len; + if ((size_t)(ms->src_end-s) >= len && + memcmp(ms->capture[l].init, s, len) == 0) + return s+len; + else return NULL; +} + + +static const char *match (MatchState *ms, const char *s, const char *p) { + if (l_unlikely(ms->matchdepth-- == 0)) + luaL_error(ms->L, "pattern too complex"); + init: /* using goto to optimize tail recursion */ + if (p != ms->p_end) { /* end of pattern? */ + switch (*p) { + case '(': { /* start capture */ + if (*(p + 1) == ')') /* position capture? */ + s = start_capture(ms, s, p + 2, CAP_POSITION); + else + s = start_capture(ms, s, p + 1, CAP_UNFINISHED); + break; + } + case ')': { /* end capture */ + s = end_capture(ms, s, p + 1); + break; + } + case '$': { + if ((p + 1) != ms->p_end) /* is the '$' the last char in pattern? */ + goto dflt; /* no; go to default */ + s = (s == ms->src_end) ? s : NULL; /* check end of string */ + break; + } + case L_ESC: { /* escaped sequences not in the format class[*+?-]? */ + switch (*(p + 1)) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, p + 2); + if (s != NULL) { + p += 4; goto init; /* return match(ms, s, p + 4); */ + } /* else fail (s == NULL) */ + break; + } + case 'f': { /* frontier? */ + const char *ep; char previous; + p += 2; + if (l_unlikely(*p != '[')) + luaL_error(ms->L, "missing '[' after '%%f' in pattern"); + ep = classend(ms, p); /* points to what is next */ + previous = (s == ms->src_init) ? '\0' : *(s - 1); + if (!matchbracketclass(uchar(previous), p, ep - 1) && + matchbracketclass(uchar(*s), p, ep - 1)) { + p = ep; goto init; /* return match(ms, s, ep); */ + } + s = NULL; /* match failed */ + break; + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': { /* capture results (%0-%9)? */ + s = match_capture(ms, s, uchar(*(p + 1))); + if (s != NULL) { + p += 2; goto init; /* return match(ms, s, p + 2) */ + } + break; + } + default: goto dflt; + } + break; + } + default: dflt: { /* pattern class plus optional suffix */ + const char *ep = classend(ms, p); /* points to optional suffix */ + /* does not match at least once? */ + if (!singlematch(ms, s, p, ep)) { + if (*ep == '*' || *ep == '?' || *ep == '-') { /* accept empty? */ + p = ep + 1; goto init; /* return match(ms, s, ep + 1); */ + } + else /* '+' or no suffix */ + s = NULL; /* fail */ + } + else { /* matched once */ + switch (*ep) { /* handle optional suffix */ + case '?': { /* optional */ + const char *res; + if ((res = match(ms, s + 1, ep + 1)) != NULL) + s = res; + else { + p = ep + 1; goto init; /* else return match(ms, s, ep + 1); */ + } + break; + } + case '+': /* 1 or more repetitions */ + s++; /* 1 match already done */ + /* FALLTHROUGH */ + case '*': /* 0 or more repetitions */ + s = max_expand(ms, s, p, ep); + break; + case '-': /* 0 or more repetitions (minimum) */ + s = min_expand(ms, s, p, ep); + break; + default: /* no suffix */ + s++; p = ep; goto init; /* return match(ms, s + 1, ep); */ + } + } + break; + } + } + } + ms->matchdepth++; + return s; +} + + + +static const char *lmemfind (const char *s1, size_t l1, + const char *s2, size_t l2) { + if (l2 == 0) return s1; /* empty strings are everywhere */ + else if (l2 > l1) return NULL; /* avoids a negative 'l1' */ + else { + const char *init; /* to search for a '*s2' inside 's1' */ + l2--; /* 1st char will be checked by 'memchr' */ + l1 = l1-l2; /* 's2' cannot be found after that */ + while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { + init++; /* 1st char is already checked */ + if (memcmp(init, s2+1, l2) == 0) + return init-1; + else { /* correct 'l1' and 's1' to try again */ + l1 -= init-s1; + s1 = init; + } + } + return NULL; /* not found */ + } +} + + +/* +** get information about the i-th capture. If there are no captures +** and 'i==0', return information about the whole match, which +** is the range 's'..'e'. If the capture is a string, return +** its length and put its address in '*cap'. If it is an integer +** (a position), push it on the stack and return CAP_POSITION. +*/ +static size_t get_onecapture (MatchState *ms, int i, const char *s, + const char *e, const char **cap) { + if (i >= ms->level) { + if (l_unlikely(i != 0)) + luaL_error(ms->L, "invalid capture index %%%d", i + 1); + *cap = s; + return e - s; + } + else { + ptrdiff_t capl = ms->capture[i].len; + *cap = ms->capture[i].init; + if (l_unlikely(capl == CAP_UNFINISHED)) + luaL_error(ms->L, "unfinished capture"); + else if (capl == CAP_POSITION) + lua_pushinteger(ms->L, (ms->capture[i].init - ms->src_init) + 1); + return capl; + } +} + + +/* +** Push the i-th capture on the stack. +*/ +static void push_onecapture (MatchState *ms, int i, const char *s, + const char *e) { + const char *cap; + ptrdiff_t l = get_onecapture(ms, i, s, e, &cap); + if (l != CAP_POSITION) + lua_pushlstring(ms->L, cap, l); + /* else position was already pushed */ +} + + +static int push_captures (MatchState *ms, const char *s, const char *e) { + int i; + int nlevels = (ms->level == 0 && s) ? 1 : ms->level; + luaL_checkstack(ms->L, nlevels, "too many captures"); + for (i = 0; i < nlevels; i++) + push_onecapture(ms, i, s, e); + return nlevels; /* number of strings pushed */ +} + + +/* check whether pattern has no special characters */ +static int nospecials (const char *p, size_t l) { + size_t upto = 0; + do { + if (strpbrk(p + upto, SPECIALS)) + return 0; /* pattern has a special character */ + upto += strlen(p + upto) + 1; /* may have more after \0 */ + } while (upto <= l); + return 1; /* no special chars found */ +} + + +static void prepstate (MatchState *ms, lua_State *L, + const char *s, size_t ls, const char *p, size_t lp) { + ms->L = L; + ms->matchdepth = MAXCCALLS; + ms->src_init = s; + ms->src_end = s + ls; + ms->p_end = p + lp; +} + + +static void reprepstate (MatchState *ms) { + ms->level = 0; + lua_assert(ms->matchdepth == MAXCCALLS); +} + + +static int str_find_aux (lua_State *L, int find) { + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1; + if (init > ls) { /* start after string's end? */ + luaL_pushfail(L); /* cannot find anything */ + return 1; + } + /* explicit request or no special characters? */ + if (find && (lua_toboolean(L, 4) || nospecials(p, lp))) { + /* do a plain search */ + const char *s2 = lmemfind(s + init, ls - init, p, lp); + if (s2) { + lua_pushinteger(L, (s2 - s) + 1); + lua_pushinteger(L, (s2 - s) + lp); + return 2; + } + } + else { + MatchState ms; + const char *s1 = s + init; + int anchor = (*p == '^'); + if (anchor) { + p++; lp--; /* skip anchor character */ + } + prepstate(&ms, L, s, ls, p, lp); + do { + const char *res; + reprepstate(&ms); + if ((res=match(&ms, s1, p)) != NULL) { + if (find) { + lua_pushinteger(L, (s1 - s) + 1); /* start */ + lua_pushinteger(L, res - s); /* end */ + return push_captures(&ms, NULL, 0) + 2; + } + else + return push_captures(&ms, s1, res); + } + } while (s1++ < ms.src_end && !anchor); + } + luaL_pushfail(L); /* not found */ + return 1; +} + + +static int str_find (lua_State *L) { + return str_find_aux(L, 1); +} + + +static int str_match (lua_State *L) { + return str_find_aux(L, 0); +} + + +/* state for 'gmatch' */ +typedef struct GMatchState { + const char *src; /* current position */ + const char *p; /* pattern */ + const char *lastmatch; /* end of last match */ + MatchState ms; /* match state */ +} GMatchState; + + +static int gmatch_aux (lua_State *L) { + GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3)); + const char *src; + gm->ms.L = L; + for (src = gm->src; src <= gm->ms.src_end; src++) { + const char *e; + reprepstate(&gm->ms); + if ((e = match(&gm->ms, src, gm->p)) != NULL && e != gm->lastmatch) { + gm->src = gm->lastmatch = e; + return push_captures(&gm->ms, src, e); + } + } + return 0; /* not found */ +} + + +static int gmatch (lua_State *L) { + size_t ls, lp; + const char *s = luaL_checklstring(L, 1, &ls); + const char *p = luaL_checklstring(L, 2, &lp); + size_t init = posrelatI(luaL_optinteger(L, 3, 1), ls) - 1; + GMatchState *gm; + lua_settop(L, 2); /* keep strings on closure to avoid being collected */ + gm = (GMatchState *)lua_newuserdatauv(L, sizeof(GMatchState), 0); + if (init > ls) /* start after string's end? */ + init = ls + 1; /* avoid overflows in 's + init' */ + prepstate(&gm->ms, L, s, ls, p, lp); + gm->src = s + init; gm->p = p; gm->lastmatch = NULL; + lua_pushcclosure(L, gmatch_aux, 3); + return 1; +} + + +static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e) { + size_t l; + lua_State *L = ms->L; + const char *news = lua_tolstring(L, 3, &l); + const char *p; + while ((p = (char *)memchr(news, L_ESC, l)) != NULL) { + luaL_addlstring(b, news, p - news); + p++; /* skip ESC */ + if (*p == L_ESC) /* '%%' */ + luaL_addchar(b, *p); + else if (*p == '0') /* '%0' */ + luaL_addlstring(b, s, e - s); + else if (isdigit(uchar(*p))) { /* '%n' */ + const char *cap; + ptrdiff_t resl = get_onecapture(ms, *p - '1', s, e, &cap); + if (resl == CAP_POSITION) + luaL_addvalue(b); /* add position to accumulated result */ + else + luaL_addlstring(b, cap, resl); + } + else + luaL_error(L, "invalid use of '%c' in replacement string", L_ESC); + l -= p + 1 - news; + news = p + 1; + } + luaL_addlstring(b, news, l); +} + + +/* +** Add the replacement value to the string buffer 'b'. +** Return true if the original string was changed. (Function calls and +** table indexing resulting in nil or false do not change the subject.) +*/ +static int add_value (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e, int tr) { + lua_State *L = ms->L; + switch (tr) { + case LUA_TFUNCTION: { /* call the function */ + int n; + lua_pushvalue(L, 3); /* push the function */ + n = push_captures(ms, s, e); /* all captures as arguments */ + lua_call(L, n, 1); /* call it */ + break; + } + case LUA_TTABLE: { /* index the table */ + push_onecapture(ms, 0, s, e); /* first capture is the index */ + lua_gettable(L, 3); + break; + } + default: { /* LUA_TNUMBER or LUA_TSTRING */ + add_s(ms, b, s, e); /* add value to the buffer */ + return 1; /* something changed */ + } + } + if (!lua_toboolean(L, -1)) { /* nil or false? */ + lua_pop(L, 1); /* remove value */ + luaL_addlstring(b, s, e - s); /* keep original text */ + return 0; /* no changes */ + } + else if (l_unlikely(!lua_isstring(L, -1))) + return luaL_error(L, "invalid replacement value (a %s)", + luaL_typename(L, -1)); + else { + luaL_addvalue(b); /* add result to accumulator */ + return 1; /* something changed */ + } +} + + +static int str_gsub (lua_State *L) { + size_t srcl, lp; + const char *src = luaL_checklstring(L, 1, &srcl); /* subject */ + const char *p = luaL_checklstring(L, 2, &lp); /* pattern */ + const char *lastmatch = NULL; /* end of last match */ + int tr = lua_type(L, 3); /* replacement type */ + lua_Integer max_s = luaL_optinteger(L, 4, srcl + 1); /* max replacements */ + int anchor = (*p == '^'); + lua_Integer n = 0; /* replacement count */ + int changed = 0; /* change flag */ + MatchState ms; + luaL_Buffer b; + luaL_argexpected(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || + tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, + "string/function/table"); + luaL_buffinit(L, &b); + if (anchor) { + p++; lp--; /* skip anchor character */ + } + prepstate(&ms, L, src, srcl, p, lp); + while (n < max_s) { + const char *e; + reprepstate(&ms); /* (re)prepare state for new match */ + if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */ + n++; + changed = add_value(&ms, &b, src, e, tr) | changed; + src = lastmatch = e; + } + else if (src < ms.src_end) /* otherwise, skip one character */ + luaL_addchar(&b, *src++); + else break; /* end of subject */ + if (anchor) break; + } + if (!changed) /* no changes? */ + lua_pushvalue(L, 1); /* return original string */ + else { /* something changed */ + luaL_addlstring(&b, src, ms.src_end-src); + luaL_pushresult(&b); /* create and return new string */ + } + lua_pushinteger(L, n); /* number of substitutions */ + return 2; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** STRING FORMAT +** ======================================================= +*/ + +#if !defined(lua_number2strx) /* { */ + +/* +** Hexadecimal floating-point formatter +*/ + +#define SIZELENMOD (sizeof(LUA_NUMBER_FRMLEN)/sizeof(char)) + + +/* +** Number of bits that goes into the first digit. It can be any value +** between 1 and 4; the following definition tries to align the number +** to nibble boundaries by making what is left after that first digit a +** multiple of 4. +*/ +#define L_NBFD ((l_floatatt(MANT_DIG) - 1)%4 + 1) + + +/* +** Add integer part of 'x' to buffer and return new 'x' +*/ +static lua_Number adddigit (char *buff, int n, lua_Number x) { + lua_Number dd = l_mathop(floor)(x); /* get integer part from 'x' */ + int d = (int)dd; + buff[n] = (d < 10 ? d + '0' : d - 10 + 'a'); /* add to buffer */ + return x - dd; /* return what is left */ +} + + +static int num2straux (char *buff, int sz, lua_Number x) { + /* if 'inf' or 'NaN', format it like '%g' */ + if (x != x || x == (lua_Number)HUGE_VAL || x == -(lua_Number)HUGE_VAL) + return l_sprintf(buff, sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)x); + else if (x == 0) { /* can be -0... */ + /* create "0" or "-0" followed by exponent */ + return l_sprintf(buff, sz, LUA_NUMBER_FMT "x0p+0", (LUAI_UACNUMBER)x); + } + else { + int e; + lua_Number m = l_mathop(frexp)(x, &e); /* 'x' fraction and exponent */ + int n = 0; /* character count */ + if (m < 0) { /* is number negative? */ + buff[n++] = '-'; /* add sign */ + m = -m; /* make it positive */ + } + buff[n++] = '0'; buff[n++] = 'x'; /* add "0x" */ + m = adddigit(buff, n++, m * (1 << L_NBFD)); /* add first digit */ + e -= L_NBFD; /* this digit goes before the radix point */ + if (m > 0) { /* more digits? */ + buff[n++] = lua_getlocaledecpoint(); /* add radix point */ + do { /* add as many digits as needed */ + m = adddigit(buff, n++, m * 16); + } while (m > 0); + } + n += l_sprintf(buff + n, sz - n, "p%+d", e); /* add exponent */ + lua_assert(n < sz); + return n; + } +} + + +static int lua_number2strx (lua_State *L, char *buff, int sz, + const char *fmt, lua_Number x) { + int n = num2straux(buff, sz, x); + if (fmt[SIZELENMOD] == 'A') { + int i; + for (i = 0; i < n; i++) + buff[i] = toupper(uchar(buff[i])); + } + else if (l_unlikely(fmt[SIZELENMOD] != 'a')) + return luaL_error(L, "modifiers for format '%%a'/'%%A' not implemented"); + return n; +} + +#endif /* } */ + + +/* +** Maximum size for items formatted with '%f'. This size is produced +** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.', +** and '\0') + number of decimal digits to represent maxfloat (which +** is maximum exponent + 1). (99+3+1, adding some extra, 110) +*/ +#define MAX_ITEMF (110 + l_floatatt(MAX_10_EXP)) + + +/* +** All formats except '%f' do not need that large limit. The other +** float formats use exponents, so that they fit in the 99 limit for +** significant digits; 's' for large strings and 'q' add items directly +** to the buffer; all integer formats also fit in the 99 limit. The +** worst case are floats: they may need 99 significant digits, plus +** '0x', '-', '.', 'e+XXXX', and '\0'. Adding some extra, 120. +*/ +#define MAX_ITEM 120 + + +/* valid flags in a format specification */ +#if !defined(L_FMTFLAGSF) + +/* valid flags for a, A, e, E, f, F, g, and G conversions */ +#define L_FMTFLAGSF "-+#0 " + +/* valid flags for o, x, and X conversions */ +#define L_FMTFLAGSX "-#0" + +/* valid flags for d and i conversions */ +#define L_FMTFLAGSI "-+0 " + +/* valid flags for u conversions */ +#define L_FMTFLAGSU "-0" + +/* valid flags for c, p, and s conversions */ +#define L_FMTFLAGSC "-" + +#endif + + +/* +** Maximum size of each format specification (such as "%-099.99d"): +** Initial '%', flags (up to 5), width (2), period, precision (2), +** length modifier (8), conversion specifier, and final '\0', plus some +** extra. +*/ +#define MAX_FORMAT 32 + + +static void addquoted (luaL_Buffer *b, const char *s, size_t len) { + luaL_addchar(b, '"'); + while (len--) { + if (*s == '"' || *s == '\\' || *s == '\n') { + luaL_addchar(b, '\\'); + luaL_addchar(b, *s); + } + else if (iscntrl(uchar(*s))) { + char buff[10]; + if (!isdigit(uchar(*(s+1)))) + l_sprintf(buff, sizeof(buff), "\\%d", (int)uchar(*s)); + else + l_sprintf(buff, sizeof(buff), "\\%03d", (int)uchar(*s)); + luaL_addstring(b, buff); + } + else + luaL_addchar(b, *s); + s++; + } + luaL_addchar(b, '"'); +} + + +/* +** Serialize a floating-point number in such a way that it can be +** scanned back by Lua. Use hexadecimal format for "common" numbers +** (to preserve precision); inf, -inf, and NaN are handled separately. +** (NaN cannot be expressed as a numeral, so we write '(0/0)' for it.) +*/ +static int quotefloat (lua_State *L, char *buff, lua_Number n) { + const char *s; /* for the fixed representations */ + if (n == (lua_Number)HUGE_VAL) /* inf? */ + s = "1e9999"; + else if (n == -(lua_Number)HUGE_VAL) /* -inf? */ + s = "-1e9999"; + else if (n != n) /* NaN? */ + s = "(0/0)"; + else { /* format number as hexadecimal */ + int nb = lua_number2strx(L, buff, MAX_ITEM, + "%" LUA_NUMBER_FRMLEN "a", n); + /* ensures that 'buff' string uses a dot as the radix character */ + if (memchr(buff, '.', nb) == NULL) { /* no dot? */ + char point = lua_getlocaledecpoint(); /* try locale point */ + char *ppoint = (char *)memchr(buff, point, nb); + if (ppoint) *ppoint = '.'; /* change it to a dot */ + } + return nb; + } + /* for the fixed representations */ + return l_sprintf(buff, MAX_ITEM, "%s", s); +} + + +static void addliteral (lua_State *L, luaL_Buffer *b, int arg) { + switch (lua_type(L, arg)) { + case LUA_TSTRING: { + size_t len; + const char *s = lua_tolstring(L, arg, &len); + addquoted(b, s, len); + break; + } + case LUA_TNUMBER: { + char *buff = luaL_prepbuffsize(b, MAX_ITEM); + int nb; + if (!lua_isinteger(L, arg)) /* float? */ + nb = quotefloat(L, buff, lua_tonumber(L, arg)); + else { /* integers */ + lua_Integer n = lua_tointeger(L, arg); + const char *format = (n == LUA_MININTEGER) /* corner case? */ + ? "0x%" LUA_INTEGER_FRMLEN "x" /* use hex */ + : LUA_INTEGER_FMT; /* else use default format */ + nb = l_sprintf(buff, MAX_ITEM, format, (LUAI_UACINT)n); + } + luaL_addsize(b, nb); + break; + } + case LUA_TNIL: case LUA_TBOOLEAN: { + luaL_tolstring(L, arg, NULL); + luaL_addvalue(b); + break; + } + default: { + luaL_argerror(L, arg, "value has no literal form"); + } + } +} + + +static const char *get2digits (const char *s) { + if (isdigit(uchar(*s))) { + s++; + if (isdigit(uchar(*s))) s++; /* (2 digits at most) */ + } + return s; +} + + +/* +** Check whether a conversion specification is valid. When called, +** first character in 'form' must be '%' and last character must +** be a valid conversion specifier. 'flags' are the accepted flags; +** 'precision' signals whether to accept a precision. +*/ +static void checkformat (lua_State *L, const char *form, const char *flags, + int precision) { + const char *spec = form + 1; /* skip '%' */ + spec += strspn(spec, flags); /* skip flags */ + if (*spec != '0') { /* a width cannot start with '0' */ + spec = get2digits(spec); /* skip width */ + if (*spec == '.' && precision) { + spec++; + spec = get2digits(spec); /* skip precision */ + } + } + if (!isalpha(uchar(*spec))) /* did not go to the end? */ + luaL_error(L, "invalid conversion specification: '%s'", form); +} + + +/* +** Get a conversion specification and copy it to 'form'. +** Return the address of its last character. +*/ +static const char *getformat (lua_State *L, const char *strfrmt, + char *form) { + /* spans flags, width, and precision ('0' is included as a flag) */ + size_t len = strspn(strfrmt, L_FMTFLAGSF "123456789."); + len++; /* adds following character (should be the specifier) */ + /* still needs space for '%', '\0', plus a length modifier */ + if (len >= MAX_FORMAT - 10) + luaL_error(L, "invalid format (too long)"); + *(form++) = '%'; + memcpy(form, strfrmt, len * sizeof(char)); + *(form + len) = '\0'; + return strfrmt + len - 1; +} + + +/* +** add length modifier into formats +*/ +static void addlenmod (char *form, const char *lenmod) { + size_t l = strlen(form); + size_t lm = strlen(lenmod); + char spec = form[l - 1]; + strcpy(form + l - 1, lenmod); + form[l + lm - 1] = spec; + form[l + lm] = '\0'; +} + + +static int str_format (lua_State *L) { + int top = lua_gettop(L); + int arg = 1; + size_t sfl; + const char *strfrmt = luaL_checklstring(L, arg, &sfl); + const char *strfrmt_end = strfrmt+sfl; + const char *flags; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (strfrmt < strfrmt_end) { + if (*strfrmt != L_ESC) + luaL_addchar(&b, *strfrmt++); + else if (*++strfrmt == L_ESC) + luaL_addchar(&b, *strfrmt++); /* %% */ + else { /* format item */ + char form[MAX_FORMAT]; /* to store the format ('%...') */ + int maxitem = MAX_ITEM; /* maximum length for the result */ + char *buff = luaL_prepbuffsize(&b, maxitem); /* to put result */ + int nb = 0; /* number of bytes in result */ + if (++arg > top) + return luaL_argerror(L, arg, "no value"); + strfrmt = getformat(L, strfrmt, form); + switch (*strfrmt++) { + case 'c': { + checkformat(L, form, L_FMTFLAGSC, 0); + nb = l_sprintf(buff, maxitem, form, (int)luaL_checkinteger(L, arg)); + break; + } + case 'd': case 'i': + flags = L_FMTFLAGSI; + goto intcase; + case 'u': + flags = L_FMTFLAGSU; + goto intcase; + case 'o': case 'x': case 'X': + flags = L_FMTFLAGSX; + intcase: { + lua_Integer n = luaL_checkinteger(L, arg); + checkformat(L, form, flags, 1); + addlenmod(form, LUA_INTEGER_FRMLEN); + nb = l_sprintf(buff, maxitem, form, (LUAI_UACINT)n); + break; + } + case 'a': case 'A': + checkformat(L, form, L_FMTFLAGSF, 1); + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = lua_number2strx(L, buff, maxitem, form, + luaL_checknumber(L, arg)); + break; + case 'f': + maxitem = MAX_ITEMF; /* extra space for '%f' */ + buff = luaL_prepbuffsize(&b, maxitem); + /* FALLTHROUGH */ + case 'e': case 'E': case 'g': case 'G': { + lua_Number n = luaL_checknumber(L, arg); + checkformat(L, form, L_FMTFLAGSF, 1); + addlenmod(form, LUA_NUMBER_FRMLEN); + nb = l_sprintf(buff, maxitem, form, (LUAI_UACNUMBER)n); + break; + } + case 'p': { + const void *p = lua_topointer(L, arg); + checkformat(L, form, L_FMTFLAGSC, 0); + if (p == NULL) { /* avoid calling 'printf' with argument NULL */ + p = "(null)"; /* result */ + form[strlen(form) - 1] = 's'; /* format it as a string */ + } + nb = l_sprintf(buff, maxitem, form, p); + break; + } + case 'q': { + if (form[2] != '\0') /* modifiers? */ + return luaL_error(L, "specifier '%%q' cannot have modifiers"); + addliteral(L, &b, arg); + break; + } + case 's': { + size_t l; + const char *s = luaL_tolstring(L, arg, &l); + if (form[2] == '\0') /* no modifiers? */ + luaL_addvalue(&b); /* keep entire string */ + else { + luaL_argcheck(L, l == strlen(s), arg, "string contains zeros"); + checkformat(L, form, L_FMTFLAGSC, 1); + if (strchr(form, '.') == NULL && l >= 100) { + /* no precision and string is too long to be formatted */ + luaL_addvalue(&b); /* keep entire string */ + } + else { /* format the string into 'buff' */ + nb = l_sprintf(buff, maxitem, form, s); + lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ + } + } + break; + } + default: { /* also treat cases 'pnLlh' */ + return luaL_error(L, "invalid conversion '%s' to 'format'", form); + } + } + lua_assert(nb < maxitem); + luaL_addsize(&b, nb); + } + } + luaL_pushresult(&b); + return 1; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** PACK/UNPACK +** ======================================================= +*/ + + +/* value used for padding */ +#if !defined(LUAL_PACKPADBYTE) +#define LUAL_PACKPADBYTE 0x00 +#endif + +/* maximum size for the binary representation of an integer */ +#define MAXINTSIZE 16 + +/* number of bits in a character */ +#define NB CHAR_BIT + +/* mask for one character (NB 1's) */ +#define MC ((1 << NB) - 1) + +/* size of a lua_Integer */ +#define SZINT ((int)sizeof(lua_Integer)) + + +/* dummy union to get native endianness */ +static const union { + int dummy; + char little; /* true iff machine is little endian */ +} nativeendian = {1}; + + +/* +** information to pack/unpack stuff +*/ +typedef struct Header { + lua_State *L; + int islittle; + int maxalign; +} Header; + + +/* +** options for pack/unpack +*/ +typedef enum KOption { + Kint, /* signed integers */ + Kuint, /* unsigned integers */ + Kfloat, /* single-precision floating-point numbers */ + Knumber, /* Lua "native" floating-point numbers */ + Kdouble, /* double-precision floating-point numbers */ + Kchar, /* fixed-length strings */ + Kstring, /* strings with prefixed length */ + Kzstr, /* zero-terminated strings */ + Kpadding, /* padding */ + Kpaddalign, /* padding for alignment */ + Knop /* no-op (configuration or spaces) */ +} KOption; + + +/* +** Read an integer numeral from string 'fmt' or return 'df' if +** there is no numeral +*/ +static int digit (int c) { return '0' <= c && c <= '9'; } + +static int getnum (const char **fmt, int df) { + if (!digit(**fmt)) /* no number? */ + return df; /* return default value */ + else { + int a = 0; + do { + a = a*10 + (*((*fmt)++) - '0'); + } while (digit(**fmt) && a <= ((int)MAXSIZE - 9)/10); + return a; + } +} + + +/* +** Read an integer numeral and raises an error if it is larger +** than the maximum size for integers. +*/ +static int getnumlimit (Header *h, const char **fmt, int df) { + int sz = getnum(fmt, df); + if (l_unlikely(sz > MAXINTSIZE || sz <= 0)) + return luaL_error(h->L, "integral size (%d) out of limits [1,%d]", + sz, MAXINTSIZE); + return sz; +} + + +/* +** Initialize Header +*/ +static void initheader (lua_State *L, Header *h) { + h->L = L; + h->islittle = nativeendian.little; + h->maxalign = 1; +} + + +/* +** Read and classify next option. 'size' is filled with option's size. +*/ +static KOption getoption (Header *h, const char **fmt, int *size) { + /* dummy structure to get native alignment requirements */ + struct cD { char c; union { LUAI_MAXALIGN; } u; }; + int opt = *((*fmt)++); + *size = 0; /* default */ + switch (opt) { + case 'b': *size = sizeof(char); return Kint; + case 'B': *size = sizeof(char); return Kuint; + case 'h': *size = sizeof(short); return Kint; + case 'H': *size = sizeof(short); return Kuint; + case 'l': *size = sizeof(long); return Kint; + case 'L': *size = sizeof(long); return Kuint; + case 'j': *size = sizeof(lua_Integer); return Kint; + case 'J': *size = sizeof(lua_Integer); return Kuint; + case 'T': *size = sizeof(size_t); return Kuint; + case 'f': *size = sizeof(float); return Kfloat; + case 'n': *size = sizeof(lua_Number); return Knumber; + case 'd': *size = sizeof(double); return Kdouble; + case 'i': *size = getnumlimit(h, fmt, sizeof(int)); return Kint; + case 'I': *size = getnumlimit(h, fmt, sizeof(int)); return Kuint; + case 's': *size = getnumlimit(h, fmt, sizeof(size_t)); return Kstring; + case 'c': + *size = getnum(fmt, -1); + if (l_unlikely(*size == -1)) + luaL_error(h->L, "missing size for format option 'c'"); + return Kchar; + case 'z': return Kzstr; + case 'x': *size = 1; return Kpadding; + case 'X': return Kpaddalign; + case ' ': break; + case '<': h->islittle = 1; break; + case '>': h->islittle = 0; break; + case '=': h->islittle = nativeendian.little; break; + case '!': { + const int maxalign = offsetof(struct cD, u); + h->maxalign = getnumlimit(h, fmt, maxalign); + break; + } + default: luaL_error(h->L, "invalid format option '%c'", opt); + } + return Knop; +} + + +/* +** Read, classify, and fill other details about the next option. +** 'psize' is filled with option's size, 'notoalign' with its +** alignment requirements. +** Local variable 'size' gets the size to be aligned. (Kpadal option +** always gets its full alignment, other options are limited by +** the maximum alignment ('maxalign'). Kchar option needs no alignment +** despite its size. +*/ +static KOption getdetails (Header *h, size_t totalsize, + const char **fmt, int *psize, int *ntoalign) { + KOption opt = getoption(h, fmt, psize); + int align = *psize; /* usually, alignment follows size */ + if (opt == Kpaddalign) { /* 'X' gets alignment from following option */ + if (**fmt == '\0' || getoption(h, fmt, &align) == Kchar || align == 0) + luaL_argerror(h->L, 1, "invalid next option for option 'X'"); + } + if (align <= 1 || opt == Kchar) /* need no alignment? */ + *ntoalign = 0; + else { + if (align > h->maxalign) /* enforce maximum alignment */ + align = h->maxalign; + if (l_unlikely((align & (align - 1)) != 0)) /* not a power of 2? */ + luaL_argerror(h->L, 1, "format asks for alignment not power of 2"); + *ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1); + } + return opt; +} + + +/* +** Pack integer 'n' with 'size' bytes and 'islittle' endianness. +** The final 'if' handles the case when 'size' is larger than +** the size of a Lua integer, correcting the extra sign-extension +** bytes if necessary (by default they would be zeros). +*/ +static void packint (luaL_Buffer *b, lua_Unsigned n, + int islittle, int size, int neg) { + char *buff = luaL_prepbuffsize(b, size); + int i; + buff[islittle ? 0 : size - 1] = (char)(n & MC); /* first byte */ + for (i = 1; i < size; i++) { + n >>= NB; + buff[islittle ? i : size - 1 - i] = (char)(n & MC); + } + if (neg && size > SZINT) { /* negative number need sign extension? */ + for (i = SZINT; i < size; i++) /* correct extra bytes */ + buff[islittle ? i : size - 1 - i] = (char)MC; + } + luaL_addsize(b, size); /* add result to buffer */ +} + + +/* +** Copy 'size' bytes from 'src' to 'dest', correcting endianness if +** given 'islittle' is different from native endianness. +*/ +static void copywithendian (char *dest, const char *src, + int size, int islittle) { + if (islittle == nativeendian.little) + memcpy(dest, src, size); + else { + dest += size - 1; + while (size-- != 0) + *(dest--) = *(src++); + } +} + + +static int str_pack (lua_State *L) { + luaL_Buffer b; + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + int arg = 1; /* current argument to pack */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + lua_pushnil(L); /* mark to separate arguments from string buffer */ + luaL_buffinit(L, &b); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + totalsize += ntoalign + size; + while (ntoalign-- > 0) + luaL_addchar(&b, LUAL_PACKPADBYTE); /* fill alignment */ + arg++; + switch (opt) { + case Kint: { /* signed integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) { /* need overflow check? */ + lua_Integer lim = (lua_Integer)1 << ((size * NB) - 1); + luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow"); + } + packint(&b, (lua_Unsigned)n, h.islittle, size, (n < 0)); + break; + } + case Kuint: { /* unsigned integers */ + lua_Integer n = luaL_checkinteger(L, arg); + if (size < SZINT) /* need overflow check? */ + luaL_argcheck(L, (lua_Unsigned)n < ((lua_Unsigned)1 << (size * NB)), + arg, "unsigned overflow"); + packint(&b, (lua_Unsigned)n, h.islittle, size, 0); + break; + } + case Kfloat: { /* C float */ + float f = (float)luaL_checknumber(L, arg); /* get argument */ + char *buff = luaL_prepbuffsize(&b, sizeof(f)); + /* move 'f' to final result, correcting endianness if needed */ + copywithendian(buff, (char *)&f, sizeof(f), h.islittle); + luaL_addsize(&b, size); + break; + } + case Knumber: { /* Lua float */ + lua_Number f = luaL_checknumber(L, arg); /* get argument */ + char *buff = luaL_prepbuffsize(&b, sizeof(f)); + /* move 'f' to final result, correcting endianness if needed */ + copywithendian(buff, (char *)&f, sizeof(f), h.islittle); + luaL_addsize(&b, size); + break; + } + case Kdouble: { /* C double */ + double f = (double)luaL_checknumber(L, arg); /* get argument */ + char *buff = luaL_prepbuffsize(&b, sizeof(f)); + /* move 'f' to final result, correcting endianness if needed */ + copywithendian(buff, (char *)&f, sizeof(f), h.islittle); + luaL_addsize(&b, size); + break; + } + case Kchar: { /* fixed-size string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, len <= (size_t)size, arg, + "string longer than given size"); + luaL_addlstring(&b, s, len); /* add string */ + while (len++ < (size_t)size) /* pad extra space */ + luaL_addchar(&b, LUAL_PACKPADBYTE); + break; + } + case Kstring: { /* strings with length count */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, size >= (int)sizeof(size_t) || + len < ((size_t)1 << (size * NB)), + arg, "string length does not fit in given size"); + packint(&b, (lua_Unsigned)len, h.islittle, size, 0); /* pack length */ + luaL_addlstring(&b, s, len); + totalsize += len; + break; + } + case Kzstr: { /* zero-terminated string */ + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + luaL_argcheck(L, strlen(s) == len, arg, "string contains zeros"); + luaL_addlstring(&b, s, len); + luaL_addchar(&b, '\0'); /* add zero at the end */ + totalsize += len + 1; + break; + } + case Kpadding: luaL_addchar(&b, LUAL_PACKPADBYTE); /* FALLTHROUGH */ + case Kpaddalign: case Knop: + arg--; /* undo increment */ + break; + } + } + luaL_pushresult(&b); + return 1; +} + + +static int str_packsize (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); /* format string */ + size_t totalsize = 0; /* accumulate total size of result */ + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign); + luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1, + "variable-length format"); + size += ntoalign; /* total space used by option */ + luaL_argcheck(L, totalsize <= MAXSIZE - size, 1, + "format result too large"); + totalsize += size; + } + lua_pushinteger(L, (lua_Integer)totalsize); + return 1; +} + + +/* +** Unpack an integer with 'size' bytes and 'islittle' endianness. +** If size is smaller than the size of a Lua integer and integer +** is signed, must do sign extension (propagating the sign to the +** higher bits); if size is larger than the size of a Lua integer, +** it must check the unread bytes to see whether they do not cause an +** overflow. +*/ +static lua_Integer unpackint (lua_State *L, const char *str, + int islittle, int size, int issigned) { + lua_Unsigned res = 0; + int i; + int limit = (size <= SZINT) ? size : SZINT; + for (i = limit - 1; i >= 0; i--) { + res <<= NB; + res |= (lua_Unsigned)(unsigned char)str[islittle ? i : size - 1 - i]; + } + if (size < SZINT) { /* real size smaller than lua_Integer? */ + if (issigned) { /* needs sign extension? */ + lua_Unsigned mask = (lua_Unsigned)1 << (size*NB - 1); + res = ((res ^ mask) - mask); /* do sign extension */ + } + } + else if (size > SZINT) { /* must check unread bytes */ + int mask = (!issigned || (lua_Integer)res >= 0) ? 0 : MC; + for (i = limit; i < size; i++) { + if (l_unlikely((unsigned char)str[islittle ? i : size - 1 - i] != mask)) + luaL_error(L, "%d-byte integer does not fit into Lua Integer", size); + } + } + return (lua_Integer)res; +} + + +static int str_unpack (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); + size_t ld; + const char *data = luaL_checklstring(L, 2, &ld); + size_t pos = posrelatI(luaL_optinteger(L, 3, 1), ld) - 1; + int n = 0; /* number of results */ + luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); + initheader(L, &h); + while (*fmt != '\0') { + int size, ntoalign; + KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign); + luaL_argcheck(L, (size_t)ntoalign + size <= ld - pos, 2, + "data string too short"); + pos += ntoalign; /* skip alignment */ + /* stack space for item + next position */ + luaL_checkstack(L, 2, "too many results"); + n++; + switch (opt) { + case Kint: + case Kuint: { + lua_Integer res = unpackint(L, data + pos, h.islittle, size, + (opt == Kint)); + lua_pushinteger(L, res); + break; + } + case Kfloat: { + float f; + copywithendian((char *)&f, data + pos, sizeof(f), h.islittle); + lua_pushnumber(L, (lua_Number)f); + break; + } + case Knumber: { + lua_Number f; + copywithendian((char *)&f, data + pos, sizeof(f), h.islittle); + lua_pushnumber(L, f); + break; + } + case Kdouble: { + double f; + copywithendian((char *)&f, data + pos, sizeof(f), h.islittle); + lua_pushnumber(L, (lua_Number)f); + break; + } + case Kchar: { + lua_pushlstring(L, data + pos, size); + break; + } + case Kstring: { + size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0); + luaL_argcheck(L, len <= ld - pos - size, 2, "data string too short"); + lua_pushlstring(L, data + pos + size, len); + pos += len; /* skip string */ + break; + } + case Kzstr: { + size_t len = strlen(data + pos); + luaL_argcheck(L, pos + len < ld, 2, + "unfinished string for format 'z'"); + lua_pushlstring(L, data + pos, len); + pos += len + 1; /* skip string plus final '\0' */ + break; + } + case Kpaddalign: case Kpadding: case Knop: + n--; /* undo increment */ + break; + } + pos += size; + } + lua_pushinteger(L, pos + 1); /* next position */ + return n + 1; +} + +/* }====================================================== */ + + +static const luaL_Reg strlib[] = { + {"byte", str_byte}, + {"char", str_char}, + {"dump", str_dump}, + {"find", str_find}, + {"format", str_format}, + {"gmatch", gmatch}, + {"gsub", str_gsub}, + {"len", str_len}, + {"lower", str_lower}, + {"match", str_match}, + {"rep", str_rep}, + {"reverse", str_reverse}, + {"sub", str_sub}, + {"upper", str_upper}, + {"pack", str_pack}, + {"packsize", str_packsize}, + {"unpack", str_unpack}, + {NULL, NULL} +}; + + +static void createmetatable (lua_State *L) { + /* table to be metatable for strings */ + luaL_newlibtable(L, stringmetamethods); + luaL_setfuncs(L, stringmetamethods, 0); + lua_pushliteral(L, ""); /* dummy string */ + lua_pushvalue(L, -2); /* copy table */ + lua_setmetatable(L, -2); /* set table as metatable for strings */ + lua_pop(L, 1); /* pop dummy string */ + lua_pushvalue(L, -2); /* get string library */ + lua_setfield(L, -2, "__index"); /* metatable.__index = string */ + lua_pop(L, 1); /* pop metatable */ +} + + +/* +** Open string library +*/ +LUAMOD_API int luaopen_string (lua_State *L) { + luaL_newlib(L, strlib); + createmetatable(L); + return 1; +} + diff --git a/arm9/source/lua/ltable.c b/arm9/source/lua/ltable.c new file mode 100644 index 000000000..3c690c5f1 --- /dev/null +++ b/arm9/source/lua/ltable.c @@ -0,0 +1,980 @@ +/* +** $Id: ltable.c $ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + +#define ltable_c +#define LUA_CORE + +#include "lprefix.h" + + +/* +** Implementation of tables (aka arrays, objects, or hash tables). +** Tables keep its elements in two parts: an array part and a hash part. +** Non-negative integer keys are all candidates to be kept in the array +** part. The actual size of the array is the largest 'n' such that +** more than half the slots between 1 and n are in use. +** Hash uses a mix of chained scatter table with Brent's variation. +** A main invariant of these tables is that, if an element is not +** in its main position (i.e. the 'original' position that its hash gives +** to it), then the colliding element is in its own main position. +** Hence even when the load factor reaches 100%, performance remains good. +*/ + +#include +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lvm.h" + + +/* +** MAXABITS is the largest integer such that MAXASIZE fits in an +** unsigned int. +*/ +#define MAXABITS cast_int(sizeof(int) * CHAR_BIT - 1) + + +/* +** MAXASIZE is the maximum size of the array part. It is the minimum +** between 2^MAXABITS and the maximum size that, measured in bytes, +** fits in a 'size_t'. +*/ +#define MAXASIZE luaM_limitN(1u << MAXABITS, TValue) + +/* +** MAXHBITS is the largest integer such that 2^MAXHBITS fits in a +** signed int. +*/ +#define MAXHBITS (MAXABITS - 1) + + +/* +** MAXHSIZE is the maximum size of the hash part. It is the minimum +** between 2^MAXHBITS and the maximum size such that, measured in bytes, +** it fits in a 'size_t'. +*/ +#define MAXHSIZE luaM_limitN(1u << MAXHBITS, Node) + + +/* +** When the original hash value is good, hashing by a power of 2 +** avoids the cost of '%'. +*/ +#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) + +/* +** for other types, it is better to avoid modulo by power of 2, as +** they can have many 2 factors. +*/ +#define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) + + +#define hashstr(t,str) hashpow2(t, (str)->hash) +#define hashboolean(t,p) hashpow2(t, p) + + +#define hashpointer(t,p) hashmod(t, point2uint(p)) + + +#define dummynode (&dummynode_) + +static const Node dummynode_ = { + {{NULL}, LUA_VEMPTY, /* value's value and type */ + LUA_VNIL, 0, {NULL}} /* key type, next, and key value */ +}; + + +static const TValue absentkey = {ABSTKEYCONSTANT}; + + +/* +** Hash for integers. To allow a good hash, use the remainder operator +** ('%'). If integer fits as a non-negative int, compute an int +** remainder, which is faster. Otherwise, use an unsigned-integer +** remainder, which uses all bits and ensures a non-negative result. +*/ +static Node *hashint (const Table *t, lua_Integer i) { + lua_Unsigned ui = l_castS2U(i); + if (ui <= cast_uint(INT_MAX)) + return hashmod(t, cast_int(ui)); + else + return hashmod(t, ui); +} + + +/* +** Hash for floating-point numbers. +** The main computation should be just +** n = frexp(n, &i); return (n * INT_MAX) + i +** but there are some numerical subtleties. +** In a two-complement representation, INT_MAX does not has an exact +** representation as a float, but INT_MIN does; because the absolute +** value of 'frexp' is smaller than 1 (unless 'n' is inf/NaN), the +** absolute value of the product 'frexp * -INT_MIN' is smaller or equal +** to INT_MAX. Next, the use of 'unsigned int' avoids overflows when +** adding 'i'; the use of '~u' (instead of '-u') avoids problems with +** INT_MIN. +*/ +#if !defined(l_hashfloat) +static int l_hashfloat (lua_Number n) { + int i; + lua_Integer ni; + n = l_mathop(frexp)(n, &i) * -cast_num(INT_MIN); + if (!lua_numbertointeger(n, &ni)) { /* is 'n' inf/-inf/NaN? */ + lua_assert(luai_numisnan(n) || l_mathop(fabs)(n) == cast_num(HUGE_VAL)); + return 0; + } + else { /* normal case */ + unsigned int u = cast_uint(i) + cast_uint(ni); + return cast_int(u <= cast_uint(INT_MAX) ? u : ~u); + } +} +#endif + + +/* +** returns the 'main' position of an element in a table (that is, +** the index of its hash value). +*/ +static Node *mainpositionTV (const Table *t, const TValue *key) { + switch (ttypetag(key)) { + case LUA_VNUMINT: { + lua_Integer i = ivalue(key); + return hashint(t, i); + } + case LUA_VNUMFLT: { + lua_Number n = fltvalue(key); + return hashmod(t, l_hashfloat(n)); + } + case LUA_VSHRSTR: { + TString *ts = tsvalue(key); + return hashstr(t, ts); + } + case LUA_VLNGSTR: { + TString *ts = tsvalue(key); + return hashpow2(t, luaS_hashlongstr(ts)); + } + case LUA_VFALSE: + return hashboolean(t, 0); + case LUA_VTRUE: + return hashboolean(t, 1); + case LUA_VLIGHTUSERDATA: { + void *p = pvalue(key); + return hashpointer(t, p); + } + case LUA_VLCF: { + lua_CFunction f = fvalue(key); + return hashpointer(t, f); + } + default: { + GCObject *o = gcvalue(key); + return hashpointer(t, o); + } + } +} + + +l_sinline Node *mainpositionfromnode (const Table *t, Node *nd) { + TValue key; + getnodekey(cast(lua_State *, NULL), &key, nd); + return mainpositionTV(t, &key); +} + + +/* +** Check whether key 'k1' is equal to the key in node 'n2'. This +** equality is raw, so there are no metamethods. Floats with integer +** values have been normalized, so integers cannot be equal to +** floats. It is assumed that 'eqshrstr' is simply pointer equality, so +** that short strings are handled in the default case. +** A true 'deadok' means to accept dead keys as equal to their original +** values. All dead keys are compared in the default case, by pointer +** identity. (Only collectable objects can produce dead keys.) Note that +** dead long strings are also compared by identity. +** Once a key is dead, its corresponding value may be collected, and +** then another value can be created with the same address. If this +** other value is given to 'next', 'equalkey' will signal a false +** positive. In a regular traversal, this situation should never happen, +** as all keys given to 'next' came from the table itself, and therefore +** could not have been collected. Outside a regular traversal, we +** have garbage in, garbage out. What is relevant is that this false +** positive does not break anything. (In particular, 'next' will return +** some other valid item on the table or nil.) +*/ +static int equalkey (const TValue *k1, const Node *n2, int deadok) { + if ((rawtt(k1) != keytt(n2)) && /* not the same variants? */ + !(deadok && keyisdead(n2) && iscollectable(k1))) + return 0; /* cannot be same key */ + switch (keytt(n2)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: + return 1; + case LUA_VNUMINT: + return (ivalue(k1) == keyival(n2)); + case LUA_VNUMFLT: + return luai_numeq(fltvalue(k1), fltvalueraw(keyval(n2))); + case LUA_VLIGHTUSERDATA: + return pvalue(k1) == pvalueraw(keyval(n2)); + case LUA_VLCF: + return fvalue(k1) == fvalueraw(keyval(n2)); + case ctb(LUA_VLNGSTR): + return luaS_eqlngstr(tsvalue(k1), keystrval(n2)); + default: + return gcvalue(k1) == gcvalueraw(keyval(n2)); + } +} + + +/* +** True if value of 'alimit' is equal to the real size of the array +** part of table 't'. (Otherwise, the array part must be larger than +** 'alimit'.) +*/ +#define limitequalsasize(t) (isrealasize(t) || ispow2((t)->alimit)) + + +/* +** Returns the real size of the 'array' array +*/ +LUAI_FUNC unsigned int luaH_realasize (const Table *t) { + if (limitequalsasize(t)) + return t->alimit; /* this is the size */ + else { + unsigned int size = t->alimit; + /* compute the smallest power of 2 not smaller than 'n' */ + size |= (size >> 1); + size |= (size >> 2); + size |= (size >> 4); + size |= (size >> 8); +#if (UINT_MAX >> 14) > 3 /* unsigned int has more than 16 bits */ + size |= (size >> 16); +#if (UINT_MAX >> 30) > 3 + size |= (size >> 32); /* unsigned int has more than 32 bits */ +#endif +#endif + size++; + lua_assert(ispow2(size) && size/2 < t->alimit && t->alimit < size); + return size; + } +} + + +/* +** Check whether real size of the array is a power of 2. +** (If it is not, 'alimit' cannot be changed to any other value +** without changing the real size.) +*/ +static int ispow2realasize (const Table *t) { + return (!isrealasize(t) || ispow2(t->alimit)); +} + + +static unsigned int setlimittosize (Table *t) { + t->alimit = luaH_realasize(t); + setrealasize(t); + return t->alimit; +} + + +#define limitasasize(t) check_exp(isrealasize(t), t->alimit) + + + +/* +** "Generic" get version. (Not that generic: not valid for integers, +** which may be in array part, nor for floats with integral values.) +** See explanation about 'deadok' in function 'equalkey'. +*/ +static const TValue *getgeneric (Table *t, const TValue *key, int deadok) { + Node *n = mainpositionTV(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (equalkey(key, n, deadok)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return &absentkey; /* not found */ + n += nx; + } + } +} + + +/* +** returns the index for 'k' if 'k' is an appropriate key to live in +** the array part of a table, 0 otherwise. +*/ +static unsigned int arrayindex (lua_Integer k) { + if (l_castS2U(k) - 1u < MAXASIZE) /* 'k' in [1, MAXASIZE]? */ + return cast_uint(k); /* 'key' is an appropriate array index */ + else + return 0; +} + + +/* +** returns the index of a 'key' for table traversals. First goes all +** elements in the array part, then elements in the hash part. The +** beginning of a traversal is signaled by 0. +*/ +static unsigned int findindex (lua_State *L, Table *t, TValue *key, + unsigned int asize) { + unsigned int i; + if (ttisnil(key)) return 0; /* first iteration */ + i = ttisinteger(key) ? arrayindex(ivalue(key)) : 0; + if (i - 1u < asize) /* is 'key' inside array part? */ + return i; /* yes; that's the index */ + else { + const TValue *n = getgeneric(t, key, 1); + if (l_unlikely(isabstkey(n))) + luaG_runerror(L, "invalid key to 'next'"); /* key not found */ + i = cast_int(nodefromval(n) - gnode(t, 0)); /* key index in hash table */ + /* hash elements are numbered after array ones */ + return (i + 1) + asize; + } +} + + +int luaH_next (lua_State *L, Table *t, StkId key) { + unsigned int asize = luaH_realasize(t); + unsigned int i = findindex(L, t, s2v(key), asize); /* find original key */ + for (; i < asize; i++) { /* try first array part */ + if (!isempty(&t->array[i])) { /* a non-empty entry? */ + setivalue(s2v(key), i + 1); + setobj2s(L, key + 1, &t->array[i]); + return 1; + } + } + for (i -= asize; cast_int(i) < sizenode(t); i++) { /* hash part */ + if (!isempty(gval(gnode(t, i)))) { /* a non-empty entry? */ + Node *n = gnode(t, i); + getnodekey(L, s2v(key), n); + setobj2s(L, key + 1, gval(n)); + return 1; + } + } + return 0; /* no more elements */ +} + + +static void freehash (lua_State *L, Table *t) { + if (!isdummy(t)) + luaM_freearray(L, t->node, cast_sizet(sizenode(t))); +} + + +/* +** {============================================================= +** Rehash +** ============================================================== +*/ + +/* +** Compute the optimal size for the array part of table 't'. 'nums' is a +** "count array" where 'nums[i]' is the number of integers in the table +** between 2^(i - 1) + 1 and 2^i. 'pna' enters with the total number of +** integer keys in the table and leaves with the number of keys that +** will go to the array part; return the optimal size. (The condition +** 'twotoi > 0' in the for loop stops the loop if 'twotoi' overflows.) +*/ +static unsigned int computesizes (unsigned int nums[], unsigned int *pna) { + int i; + unsigned int twotoi; /* 2^i (candidate for optimal size) */ + unsigned int a = 0; /* number of elements smaller than 2^i */ + unsigned int na = 0; /* number of elements to go to array part */ + unsigned int optimal = 0; /* optimal size for array part */ + /* loop while keys can fill more than half of total size */ + for (i = 0, twotoi = 1; + twotoi > 0 && *pna > twotoi / 2; + i++, twotoi *= 2) { + a += nums[i]; + if (a > twotoi/2) { /* more than half elements present? */ + optimal = twotoi; /* optimal size (till now) */ + na = a; /* all elements up to 'optimal' will go to array part */ + } + } + lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal); + *pna = na; + return optimal; +} + + +static int countint (lua_Integer key, unsigned int *nums) { + unsigned int k = arrayindex(key); + if (k != 0) { /* is 'key' an appropriate array index? */ + nums[luaO_ceillog2(k)]++; /* count as such */ + return 1; + } + else + return 0; +} + + +/* +** Count keys in array part of table 't': Fill 'nums[i]' with +** number of keys that will go into corresponding slice and return +** total number of non-nil keys. +*/ +static unsigned int numusearray (const Table *t, unsigned int *nums) { + int lg; + unsigned int ttlg; /* 2^lg */ + unsigned int ause = 0; /* summation of 'nums' */ + unsigned int i = 1; /* count to traverse all array keys */ + unsigned int asize = limitasasize(t); /* real array size */ + /* traverse each slice */ + for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) { + unsigned int lc = 0; /* counter */ + unsigned int lim = ttlg; + if (lim > asize) { + lim = asize; /* adjust upper limit */ + if (i > lim) + break; /* no more elements to count */ + } + /* count elements in range (2^(lg - 1), 2^lg] */ + for (; i <= lim; i++) { + if (!isempty(&t->array[i-1])) + lc++; + } + nums[lg] += lc; + ause += lc; + } + return ause; +} + + +static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) { + int totaluse = 0; /* total number of elements */ + int ause = 0; /* elements added to 'nums' (can go to array part) */ + int i = sizenode(t); + while (i--) { + Node *n = &t->node[i]; + if (!isempty(gval(n))) { + if (keyisinteger(n)) + ause += countint(keyival(n), nums); + totaluse++; + } + } + *pna += ause; + return totaluse; +} + + +/* +** Creates an array for the hash part of a table with the given +** size, or reuses the dummy node if size is zero. +** The computation for size overflow is in two steps: the first +** comparison ensures that the shift in the second one does not +** overflow. +*/ +static void setnodevector (lua_State *L, Table *t, unsigned int size) { + if (size == 0) { /* no elements to hash part? */ + t->node = cast(Node *, dummynode); /* use common 'dummynode' */ + t->lsizenode = 0; + t->lastfree = NULL; /* signal that it is using dummy node */ + } + else { + int i; + int lsize = luaO_ceillog2(size); + if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE) + luaG_runerror(L, "table overflow"); + size = twoto(lsize); + t->node = luaM_newvector(L, size, Node); + for (i = 0; i < cast_int(size); i++) { + Node *n = gnode(t, i); + gnext(n) = 0; + setnilkey(n); + setempty(gval(n)); + } + t->lsizenode = cast_byte(lsize); + t->lastfree = gnode(t, size); /* all positions are free */ + } +} + + +/* +** (Re)insert all elements from the hash part of 'ot' into table 't'. +*/ +static void reinsert (lua_State *L, Table *ot, Table *t) { + int j; + int size = sizenode(ot); + for (j = 0; j < size; j++) { + Node *old = gnode(ot, j); + if (!isempty(gval(old))) { + /* doesn't need barrier/invalidate cache, as entry was + already present in the table */ + TValue k; + getnodekey(L, &k, old); + luaH_set(L, t, &k, gval(old)); + } + } +} + + +/* +** Exchange the hash part of 't1' and 't2'. +*/ +static void exchangehashpart (Table *t1, Table *t2) { + lu_byte lsizenode = t1->lsizenode; + Node *node = t1->node; + Node *lastfree = t1->lastfree; + t1->lsizenode = t2->lsizenode; + t1->node = t2->node; + t1->lastfree = t2->lastfree; + t2->lsizenode = lsizenode; + t2->node = node; + t2->lastfree = lastfree; +} + + +/* +** Resize table 't' for the new given sizes. Both allocations (for +** the hash part and for the array part) can fail, which creates some +** subtleties. If the first allocation, for the hash part, fails, an +** error is raised and that is it. Otherwise, it copies the elements from +** the shrinking part of the array (if it is shrinking) into the new +** hash. Then it reallocates the array part. If that fails, the table +** is in its original state; the function frees the new hash part and then +** raises the allocation error. Otherwise, it sets the new hash part +** into the table, initializes the new part of the array (if any) with +** nils and reinserts the elements of the old hash back into the new +** parts of the table. +*/ +void luaH_resize (lua_State *L, Table *t, unsigned int newasize, + unsigned int nhsize) { + unsigned int i; + Table newt; /* to keep the new hash part */ + unsigned int oldasize = setlimittosize(t); + TValue *newarray; + /* create new hash part with appropriate size into 'newt' */ + setnodevector(L, &newt, nhsize); + if (newasize < oldasize) { /* will array shrink? */ + t->alimit = newasize; /* pretend array has new size... */ + exchangehashpart(t, &newt); /* and new hash */ + /* re-insert into the new hash the elements from vanishing slice */ + for (i = newasize; i < oldasize; i++) { + if (!isempty(&t->array[i])) + luaH_setint(L, t, i + 1, &t->array[i]); + } + t->alimit = oldasize; /* restore current size... */ + exchangehashpart(t, &newt); /* and hash (in case of errors) */ + } + /* allocate new array */ + newarray = luaM_reallocvector(L, t->array, oldasize, newasize, TValue); + if (l_unlikely(newarray == NULL && newasize > 0)) { /* allocation failed? */ + freehash(L, &newt); /* release new hash part */ + luaM_error(L); /* raise error (with array unchanged) */ + } + /* allocation ok; initialize new part of the array */ + exchangehashpart(t, &newt); /* 't' has the new hash ('newt' has the old) */ + t->array = newarray; /* set new array part */ + t->alimit = newasize; + for (i = oldasize; i < newasize; i++) /* clear new slice of the array */ + setempty(&t->array[i]); + /* re-insert elements from old hash part into new parts */ + reinsert(L, &newt, t); /* 'newt' now has the old hash */ + freehash(L, &newt); /* free old hash part */ +} + + +void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize) { + int nsize = allocsizenode(t); + luaH_resize(L, t, nasize, nsize); +} + +/* +** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i +*/ +static void rehash (lua_State *L, Table *t, const TValue *ek) { + unsigned int asize; /* optimal size for array part */ + unsigned int na; /* number of keys in the array part */ + unsigned int nums[MAXABITS + 1]; + int i; + int totaluse; + for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */ + setlimittosize(t); + na = numusearray(t, nums); /* count keys in array part */ + totaluse = na; /* all those keys are integer keys */ + totaluse += numusehash(t, nums, &na); /* count keys in hash part */ + /* count extra key */ + if (ttisinteger(ek)) + na += countint(ivalue(ek), nums); + totaluse++; + /* compute new size for array part */ + asize = computesizes(nums, &na); + /* resize the table to new computed sizes */ + luaH_resize(L, t, asize, totaluse - na); +} + + + +/* +** }============================================================= +*/ + + +Table *luaH_new (lua_State *L) { + GCObject *o = luaC_newobj(L, LUA_VTABLE, sizeof(Table)); + Table *t = gco2t(o); + t->metatable = NULL; + t->flags = cast_byte(maskflags); /* table has no metamethod fields */ + t->array = NULL; + t->alimit = 0; + setnodevector(L, t, 0); + return t; +} + + +void luaH_free (lua_State *L, Table *t) { + freehash(L, t); + luaM_freearray(L, t->array, luaH_realasize(t)); + luaM_free(L, t); +} + + +static Node *getfreepos (Table *t) { + if (!isdummy(t)) { + while (t->lastfree > t->node) { + t->lastfree--; + if (keyisnil(t->lastfree)) + return t->lastfree; + } + } + return NULL; /* could not find a free place */ +} + + + +/* +** inserts a new key into a hash table; first, check whether key's main +** position is free. If not, check whether colliding node is in its main +** position or not: if it is not, move colliding node to an empty place and +** put new key in its main position; otherwise (colliding node is in its main +** position), new key goes to an empty position. +*/ +void luaH_newkey (lua_State *L, Table *t, const TValue *key, TValue *value) { + Node *mp; + TValue aux; + if (l_unlikely(ttisnil(key))) + luaG_runerror(L, "table index is nil"); + else if (ttisfloat(key)) { + lua_Number f = fltvalue(key); + lua_Integer k; + if (luaV_flttointeger(f, &k, F2Ieq)) { /* does key fit in an integer? */ + setivalue(&aux, k); + key = &aux; /* insert it as an integer */ + } + else if (l_unlikely(luai_numisnan(f))) + luaG_runerror(L, "table index is NaN"); + } + if (ttisnil(value)) + return; /* do not insert nil values */ + mp = mainpositionTV(t, key); + if (!isempty(gval(mp)) || isdummy(t)) { /* main position is taken? */ + Node *othern; + Node *f = getfreepos(t); /* get a free place */ + if (f == NULL) { /* cannot find a free place? */ + rehash(L, t, key); /* grow table */ + /* whatever called 'newkey' takes care of TM cache */ + luaH_set(L, t, key, value); /* insert key into grown table */ + return; + } + lua_assert(!isdummy(t)); + othern = mainpositionfromnode(t, mp); + if (othern != mp) { /* is colliding node out of its main position? */ + /* yes; move colliding node into free position */ + while (othern + gnext(othern) != mp) /* find previous */ + othern += gnext(othern); + gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */ + *f = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + if (gnext(mp) != 0) { + gnext(f) += cast_int(mp - f); /* correct 'next' */ + gnext(mp) = 0; /* now 'mp' is free */ + } + setempty(gval(mp)); + } + else { /* colliding node is in its own main position */ + /* new node will go into free position */ + if (gnext(mp) != 0) + gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */ + else lua_assert(gnext(f) == 0); + gnext(mp) = cast_int(f - mp); + mp = f; + } + } + setnodekey(L, mp, key); + luaC_barrierback(L, obj2gco(t), key); + lua_assert(isempty(gval(mp))); + setobj2t(L, gval(mp), value); +} + + +/* +** Search function for integers. If integer is inside 'alimit', get it +** directly from the array part. Otherwise, if 'alimit' is not equal to +** the real size of the array, key still can be in the array part. In +** this case, try to avoid a call to 'luaH_realasize' when key is just +** one more than the limit (so that it can be incremented without +** changing the real size of the array). +*/ +const TValue *luaH_getint (Table *t, lua_Integer key) { + if (l_castS2U(key) - 1u < t->alimit) /* 'key' in [1, t->alimit]? */ + return &t->array[key - 1]; + else if (!limitequalsasize(t) && /* key still may be in the array part? */ + (l_castS2U(key) == t->alimit + 1 || + l_castS2U(key) - 1u < luaH_realasize(t))) { + t->alimit = cast_uint(key); /* probably '#t' is here now */ + return &t->array[key - 1]; + } + else { + Node *n = hashint(t, key); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (keyisinteger(n) && keyival(n) == key) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) break; + n += nx; + } + } + return &absentkey; + } +} + + +/* +** search function for short strings +*/ +const TValue *luaH_getshortstr (Table *t, TString *key) { + Node *n = hashstr(t, key); + lua_assert(key->tt == LUA_VSHRSTR); + for (;;) { /* check whether 'key' is somewhere in the chain */ + if (keyisshrstr(n) && eqshrstr(keystrval(n), key)) + return gval(n); /* that's it */ + else { + int nx = gnext(n); + if (nx == 0) + return &absentkey; /* not found */ + n += nx; + } + } +} + + +const TValue *luaH_getstr (Table *t, TString *key) { + if (key->tt == LUA_VSHRSTR) + return luaH_getshortstr(t, key); + else { /* for long strings, use generic case */ + TValue ko; + setsvalue(cast(lua_State *, NULL), &ko, key); + return getgeneric(t, &ko, 0); + } +} + + +/* +** main search function +*/ +const TValue *luaH_get (Table *t, const TValue *key) { + switch (ttypetag(key)) { + case LUA_VSHRSTR: return luaH_getshortstr(t, tsvalue(key)); + case LUA_VNUMINT: return luaH_getint(t, ivalue(key)); + case LUA_VNIL: return &absentkey; + case LUA_VNUMFLT: { + lua_Integer k; + if (luaV_flttointeger(fltvalue(key), &k, F2Ieq)) /* integral index? */ + return luaH_getint(t, k); /* use specialized version */ + /* else... */ + } /* FALLTHROUGH */ + default: + return getgeneric(t, key, 0); + } +} + + +/* +** Finish a raw "set table" operation, where 'slot' is where the value +** should have been (the result of a previous "get table"). +** Beware: when using this function you probably need to check a GC +** barrier and invalidate the TM cache. +*/ +void luaH_finishset (lua_State *L, Table *t, const TValue *key, + const TValue *slot, TValue *value) { + if (isabstkey(slot)) + luaH_newkey(L, t, key, value); + else + setobj2t(L, cast(TValue *, slot), value); +} + + +/* +** beware: when using this function you probably need to check a GC +** barrier and invalidate the TM cache. +*/ +void luaH_set (lua_State *L, Table *t, const TValue *key, TValue *value) { + const TValue *slot = luaH_get(t, key); + luaH_finishset(L, t, key, slot, value); +} + + +void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) { + const TValue *p = luaH_getint(t, key); + if (isabstkey(p)) { + TValue k; + setivalue(&k, key); + luaH_newkey(L, t, &k, value); + } + else + setobj2t(L, cast(TValue *, p), value); +} + + +/* +** Try to find a boundary in the hash part of table 't'. From the +** caller, we know that 'j' is zero or present and that 'j + 1' is +** present. We want to find a larger key that is absent from the +** table, so that we can do a binary search between the two keys to +** find a boundary. We keep doubling 'j' until we get an absent index. +** If the doubling would overflow, we try LUA_MAXINTEGER. If it is +** absent, we are ready for the binary search. ('j', being max integer, +** is larger or equal to 'i', but it cannot be equal because it is +** absent while 'i' is present; so 'j > i'.) Otherwise, 'j' is a +** boundary. ('j + 1' cannot be a present integer key because it is +** not a valid integer in Lua.) +*/ +static lua_Unsigned hash_search (Table *t, lua_Unsigned j) { + lua_Unsigned i; + if (j == 0) j++; /* the caller ensures 'j + 1' is present */ + do { + i = j; /* 'i' is a present index */ + if (j <= l_castS2U(LUA_MAXINTEGER) / 2) + j *= 2; + else { + j = LUA_MAXINTEGER; + if (isempty(luaH_getint(t, j))) /* t[j] not present? */ + break; /* 'j' now is an absent index */ + else /* weird case */ + return j; /* well, max integer is a boundary... */ + } + } while (!isempty(luaH_getint(t, j))); /* repeat until an absent t[j] */ + /* i < j && t[i] present && t[j] absent */ + while (j - i > 1u) { /* do a binary search between them */ + lua_Unsigned m = (i + j) / 2; + if (isempty(luaH_getint(t, m))) j = m; + else i = m; + } + return i; +} + + +static unsigned int binsearch (const TValue *array, unsigned int i, + unsigned int j) { + while (j - i > 1u) { /* binary search */ + unsigned int m = (i + j) / 2; + if (isempty(&array[m - 1])) j = m; + else i = m; + } + return i; +} + + +/* +** Try to find a boundary in table 't'. (A 'boundary' is an integer index +** such that t[i] is present and t[i+1] is absent, or 0 if t[1] is absent +** and 'maxinteger' if t[maxinteger] is present.) +** (In the next explanation, we use Lua indices, that is, with base 1. +** The code itself uses base 0 when indexing the array part of the table.) +** The code starts with 'limit = t->alimit', a position in the array +** part that may be a boundary. +** +** (1) If 't[limit]' is empty, there must be a boundary before it. +** As a common case (e.g., after 't[#t]=nil'), check whether 'limit-1' +** is present. If so, it is a boundary. Otherwise, do a binary search +** between 0 and limit to find a boundary. In both cases, try to +** use this boundary as the new 'alimit', as a hint for the next call. +** +** (2) If 't[limit]' is not empty and the array has more elements +** after 'limit', try to find a boundary there. Again, try first +** the special case (which should be quite frequent) where 'limit+1' +** is empty, so that 'limit' is a boundary. Otherwise, check the +** last element of the array part. If it is empty, there must be a +** boundary between the old limit (present) and the last element +** (absent), which is found with a binary search. (This boundary always +** can be a new limit.) +** +** (3) The last case is when there are no elements in the array part +** (limit == 0) or its last element (the new limit) is present. +** In this case, must check the hash part. If there is no hash part +** or 'limit+1' is absent, 'limit' is a boundary. Otherwise, call +** 'hash_search' to find a boundary in the hash part of the table. +** (In those cases, the boundary is not inside the array part, and +** therefore cannot be used as a new limit.) +*/ +lua_Unsigned luaH_getn (Table *t) { + unsigned int limit = t->alimit; + if (limit > 0 && isempty(&t->array[limit - 1])) { /* (1)? */ + /* there must be a boundary before 'limit' */ + if (limit >= 2 && !isempty(&t->array[limit - 2])) { + /* 'limit - 1' is a boundary; can it be a new limit? */ + if (ispow2realasize(t) && !ispow2(limit - 1)) { + t->alimit = limit - 1; + setnorealasize(t); /* now 'alimit' is not the real size */ + } + return limit - 1; + } + else { /* must search for a boundary in [0, limit] */ + unsigned int boundary = binsearch(t->array, 0, limit); + /* can this boundary represent the real size of the array? */ + if (ispow2realasize(t) && boundary > luaH_realasize(t) / 2) { + t->alimit = boundary; /* use it as the new limit */ + setnorealasize(t); + } + return boundary; + } + } + /* 'limit' is zero or present in table */ + if (!limitequalsasize(t)) { /* (2)? */ + /* 'limit' > 0 and array has more elements after 'limit' */ + if (isempty(&t->array[limit])) /* 'limit + 1' is empty? */ + return limit; /* this is the boundary */ + /* else, try last element in the array */ + limit = luaH_realasize(t); + if (isempty(&t->array[limit - 1])) { /* empty? */ + /* there must be a boundary in the array after old limit, + and it must be a valid new limit */ + unsigned int boundary = binsearch(t->array, t->alimit, limit); + t->alimit = boundary; + return boundary; + } + /* else, new limit is present in the table; check the hash part */ + } + /* (3) 'limit' is the last element and either is zero or present in table */ + lua_assert(limit == luaH_realasize(t) && + (limit == 0 || !isempty(&t->array[limit - 1]))); + if (isdummy(t) || isempty(luaH_getint(t, cast(lua_Integer, limit + 1)))) + return limit; /* 'limit + 1' is absent */ + else /* 'limit + 1' is also present */ + return hash_search(t, limit); +} + + + +#if defined(LUA_DEBUG) + +/* export these functions for the test library */ + +Node *luaH_mainposition (const Table *t, const TValue *key) { + return mainpositionTV(t, key); +} + +#endif diff --git a/arm9/source/lua/ltable.h b/arm9/source/lua/ltable.h new file mode 100644 index 000000000..75dd9e26e --- /dev/null +++ b/arm9/source/lua/ltable.h @@ -0,0 +1,65 @@ +/* +** $Id: ltable.h $ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + +#ifndef ltable_h +#define ltable_h + +#include "lobject.h" + + +#define gnode(t,i) (&(t)->node[i]) +#define gval(n) (&(n)->i_val) +#define gnext(n) ((n)->u.next) + + +/* +** Clear all bits of fast-access metamethods, which means that the table +** may have any of these metamethods. (First access that fails after the +** clearing will set the bit again.) +*/ +#define invalidateTMcache(t) ((t)->flags &= ~maskflags) + + +/* true when 't' is using 'dummynode' as its hash part */ +#define isdummy(t) ((t)->lastfree == NULL) + + +/* allocated size for hash nodes */ +#define allocsizenode(t) (isdummy(t) ? 0 : sizenode(t)) + + +/* returns the Node, given the value of a table entry */ +#define nodefromval(v) cast(Node *, (v)) + + +LUAI_FUNC const TValue *luaH_getint (Table *t, lua_Integer key); +LUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key, + TValue *value); +LUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key); +LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); +LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); +LUAI_FUNC void luaH_newkey (lua_State *L, Table *t, const TValue *key, + TValue *value); +LUAI_FUNC void luaH_set (lua_State *L, Table *t, const TValue *key, + TValue *value); +LUAI_FUNC void luaH_finishset (lua_State *L, Table *t, const TValue *key, + const TValue *slot, TValue *value); +LUAI_FUNC Table *luaH_new (lua_State *L); +LUAI_FUNC void luaH_resize (lua_State *L, Table *t, unsigned int nasize, + unsigned int nhsize); +LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned int nasize); +LUAI_FUNC void luaH_free (lua_State *L, Table *t); +LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); +LUAI_FUNC lua_Unsigned luaH_getn (Table *t); +LUAI_FUNC unsigned int luaH_realasize (const Table *t); + + +#if defined(LUA_DEBUG) +LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); +#endif + + +#endif diff --git a/arm9/source/lua/ltablib.c b/arm9/source/lua/ltablib.c new file mode 100644 index 000000000..e6bc4d04a --- /dev/null +++ b/arm9/source/lua/ltablib.c @@ -0,0 +1,430 @@ +/* +** $Id: ltablib.c $ +** Library for Table Manipulation +** See Copyright Notice in lua.h +*/ + +#define ltablib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* +** Operations that an object must define to mimic a table +** (some functions only need some of them) +*/ +#define TAB_R 1 /* read */ +#define TAB_W 2 /* write */ +#define TAB_L 4 /* length */ +#define TAB_RW (TAB_R | TAB_W) /* read/write */ + + +#define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n)) + + +static int checkfield (lua_State *L, const char *key, int n) { + lua_pushstring(L, key); + return (lua_rawget(L, -n) != LUA_TNIL); +} + + +/* +** Check that 'arg' either is a table or can behave like one (that is, +** has a metatable with the required metamethods) +*/ +static void checktab (lua_State *L, int arg, int what) { + if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ + int n = 1; /* number of elements to pop */ + if (lua_getmetatable(L, arg) && /* must have metatable */ + (!(what & TAB_R) || checkfield(L, "__index", ++n)) && + (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && + (!(what & TAB_L) || checkfield(L, "__len", ++n))) { + lua_pop(L, n); /* pop metatable and tested metamethods */ + } + else + luaL_checktype(L, arg, LUA_TTABLE); /* force an error */ + } +} + + +static int tinsert (lua_State *L) { + lua_Integer pos; /* where to insert new element */ + lua_Integer e = aux_getn(L, 1, TAB_RW); + e = luaL_intop(+, e, 1); /* first empty element */ + switch (lua_gettop(L)) { + case 2: { /* called with only 2 arguments */ + pos = e; /* insert new element at the end */ + break; + } + case 3: { + lua_Integer i; + pos = luaL_checkinteger(L, 2); /* 2nd argument is the position */ + /* check whether 'pos' is in [1, e] */ + luaL_argcheck(L, (lua_Unsigned)pos - 1u < (lua_Unsigned)e, 2, + "position out of bounds"); + for (i = e; i > pos; i--) { /* move up elements */ + lua_geti(L, 1, i - 1); + lua_seti(L, 1, i); /* t[i] = t[i - 1] */ + } + break; + } + default: { + return luaL_error(L, "wrong number of arguments to 'insert'"); + } + } + lua_seti(L, 1, pos); /* t[pos] = v */ + return 0; +} + + +static int tremove (lua_State *L) { + lua_Integer size = aux_getn(L, 1, TAB_RW); + lua_Integer pos = luaL_optinteger(L, 2, size); + if (pos != size) /* validate 'pos' if given */ + /* check whether 'pos' is in [1, size + 1] */ + luaL_argcheck(L, (lua_Unsigned)pos - 1u <= (lua_Unsigned)size, 2, + "position out of bounds"); + lua_geti(L, 1, pos); /* result = t[pos] */ + for ( ; pos < size; pos++) { + lua_geti(L, 1, pos + 1); + lua_seti(L, 1, pos); /* t[pos] = t[pos + 1] */ + } + lua_pushnil(L); + lua_seti(L, 1, pos); /* remove entry t[pos] */ + return 1; +} + + +/* +** Copy elements (1[f], ..., 1[e]) into (tt[t], tt[t+1], ...). Whenever +** possible, copy in increasing order, which is better for rehashing. +** "possible" means destination after original range, or smaller +** than origin, or copying to another table. +*/ +static int tmove (lua_State *L) { + lua_Integer f = luaL_checkinteger(L, 2); + lua_Integer e = luaL_checkinteger(L, 3); + lua_Integer t = luaL_checkinteger(L, 4); + int tt = !lua_isnoneornil(L, 5) ? 5 : 1; /* destination table */ + checktab(L, 1, TAB_R); + checktab(L, tt, TAB_W); + if (e >= f) { /* otherwise, nothing to move */ + lua_Integer n, i; + luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3, + "too many elements to move"); + n = e - f + 1; /* number of elements to move */ + luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4, + "destination wrap around"); + if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) { + for (i = 0; i < n; i++) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + else { + for (i = n - 1; i >= 0; i--) { + lua_geti(L, 1, f + i); + lua_seti(L, tt, t + i); + } + } + } + lua_pushvalue(L, tt); /* return destination table */ + return 1; +} + + +static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) { + lua_geti(L, 1, i); + if (l_unlikely(!lua_isstring(L, -1))) + luaL_error(L, "invalid value (%s) at index %I in table for 'concat'", + luaL_typename(L, -1), (LUAI_UACINT)i); + luaL_addvalue(b); +} + + +static int tconcat (lua_State *L) { + luaL_Buffer b; + lua_Integer last = aux_getn(L, 1, TAB_R); + size_t lsep; + const char *sep = luaL_optlstring(L, 2, "", &lsep); + lua_Integer i = luaL_optinteger(L, 3, 1); + last = luaL_optinteger(L, 4, last); + luaL_buffinit(L, &b); + for (; i < last; i++) { + addfield(L, &b, i); + luaL_addlstring(&b, sep, lsep); + } + if (i == last) /* add last value (if interval was not empty) */ + addfield(L, &b, i); + luaL_pushresult(&b); + return 1; +} + + +/* +** {====================================================== +** Pack/unpack +** ======================================================= +*/ + +static int tpack (lua_State *L) { + int i; + int n = lua_gettop(L); /* number of elements to pack */ + lua_createtable(L, n, 1); /* create result table */ + lua_insert(L, 1); /* put it at index 1 */ + for (i = n; i >= 1; i--) /* assign elements */ + lua_seti(L, 1, i); + lua_pushinteger(L, n); + lua_setfield(L, 1, "n"); /* t.n = number of elements */ + return 1; /* return table */ +} + + +static int tunpack (lua_State *L) { + lua_Unsigned n; + lua_Integer i = luaL_optinteger(L, 2, 1); + lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); + if (i > e) return 0; /* empty range */ + n = (lua_Unsigned)e - i; /* number of elements minus 1 (avoid overflows) */ + if (l_unlikely(n >= (unsigned int)INT_MAX || + !lua_checkstack(L, (int)(++n)))) + return luaL_error(L, "too many results to unpack"); + for (; i < e; i++) { /* push arg[i..e - 1] (to avoid overflows) */ + lua_geti(L, 1, i); + } + lua_geti(L, 1, e); /* push last element */ + return (int)n; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Quicksort +** (based on 'Algorithms in MODULA-3', Robert Sedgewick; +** Addison-Wesley, 1993.) +** ======================================================= +*/ + + +/* type for array indices */ +typedef unsigned int IdxT; + + +/* +** Produce a "random" 'unsigned int' to randomize pivot choice. This +** macro is used only when 'sort' detects a big imbalance in the result +** of a partition. (If you don't want/need this "randomness", ~0 is a +** good choice.) +*/ +#if !defined(l_randomizePivot) /* { */ + +#include + +/* size of 'e' measured in number of 'unsigned int's */ +#define sof(e) (sizeof(e) / sizeof(unsigned int)) + +/* +** Use 'time' and 'clock' as sources of "randomness". Because we don't +** know the types 'clock_t' and 'time_t', we cannot cast them to +** anything without risking overflows. A safe way to use their values +** is to copy them to an array of a known type and use the array values. +*/ +static unsigned int l_randomizePivot (void) { + clock_t c = clock(); + time_t t = time(NULL); + unsigned int buff[sof(c) + sof(t)]; + unsigned int i, rnd = 0; + memcpy(buff, &c, sof(c) * sizeof(unsigned int)); + memcpy(buff + sof(c), &t, sof(t) * sizeof(unsigned int)); + for (i = 0; i < sof(buff); i++) + rnd += buff[i]; + return rnd; +} + +#endif /* } */ + + +/* arrays larger than 'RANLIMIT' may use randomized pivots */ +#define RANLIMIT 100u + + +static void set2 (lua_State *L, IdxT i, IdxT j) { + lua_seti(L, 1, i); + lua_seti(L, 1, j); +} + + +/* +** Return true iff value at stack index 'a' is less than the value at +** index 'b' (according to the order of the sort). +*/ +static int sort_comp (lua_State *L, int a, int b) { + if (lua_isnil(L, 2)) /* no function? */ + return lua_compare(L, a, b, LUA_OPLT); /* a < b */ + else { /* function */ + int res; + lua_pushvalue(L, 2); /* push function */ + lua_pushvalue(L, a-1); /* -1 to compensate function */ + lua_pushvalue(L, b-2); /* -2 to compensate function and 'a' */ + lua_call(L, 2, 1); /* call function */ + res = lua_toboolean(L, -1); /* get result */ + lua_pop(L, 1); /* pop result */ + return res; + } +} + + +/* +** Does the partition: Pivot P is at the top of the stack. +** precondition: a[lo] <= P == a[up-1] <= a[up], +** so it only needs to do the partition from lo + 1 to up - 2. +** Pos-condition: a[lo .. i - 1] <= a[i] == P <= a[i + 1 .. up] +** returns 'i'. +*/ +static IdxT partition (lua_State *L, IdxT lo, IdxT up) { + IdxT i = lo; /* will be incremented before first use */ + IdxT j = up - 1; /* will be decremented before first use */ + /* loop invariant: a[lo .. i] <= P <= a[j .. up] */ + for (;;) { + /* next loop: repeat ++i while a[i] < P */ + while ((void)lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (l_unlikely(i == up - 1)) /* a[i] < P but a[up - 1] == P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* after the loop, a[i] >= P and a[lo .. i - 1] < P */ + /* next loop: repeat --j while P < a[j] */ + while ((void)lua_geti(L, 1, --j), sort_comp(L, -3, -1)) { + if (l_unlikely(j < i)) /* j < i but a[j] > P ?? */ + luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + /* after the loop, a[j] <= P and a[j + 1 .. up] >= P */ + if (j < i) { /* no elements out of place? */ + /* a[lo .. i - 1] <= P <= a[j + 1 .. i .. up] */ + lua_pop(L, 1); /* pop a[j] */ + /* swap pivot (a[up - 1]) with a[i] to satisfy pos-condition */ + set2(L, up - 1, i); + return i; + } + /* otherwise, swap a[i] - a[j] to restore invariant and repeat */ + set2(L, i, j); + } +} + + +/* +** Choose an element in the middle (2nd-3th quarters) of [lo,up] +** "randomized" by 'rnd' +*/ +static IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) { + IdxT r4 = (up - lo) / 4; /* range/4 */ + IdxT p = rnd % (r4 * 2) + (lo + r4); + lua_assert(lo + r4 <= p && p <= up - r4); + return p; +} + + +/* +** Quicksort algorithm (recursive function) +*/ +static void auxsort (lua_State *L, IdxT lo, IdxT up, + unsigned int rnd) { + while (lo < up) { /* loop for tail recursion */ + IdxT p; /* Pivot index */ + IdxT n; /* to be used later */ + /* sort elements 'lo', 'p', and 'up' */ + lua_geti(L, 1, lo); + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[lo]? */ + set2(L, lo, up); /* swap a[lo] - a[up] */ + else + lua_pop(L, 2); /* remove both values */ + if (up - lo == 1) /* only 2 elements? */ + return; /* already sorted */ + if (up - lo < RANLIMIT || rnd == 0) /* small interval or no randomize? */ + p = (lo + up)/2; /* middle element is a good pivot */ + else /* for larger intervals, it is worth a random pivot */ + p = choosePivot(lo, up, rnd); + lua_geti(L, 1, p); + lua_geti(L, 1, lo); + if (sort_comp(L, -2, -1)) /* a[p] < a[lo]? */ + set2(L, p, lo); /* swap a[p] - a[lo] */ + else { + lua_pop(L, 1); /* remove a[lo] */ + lua_geti(L, 1, up); + if (sort_comp(L, -1, -2)) /* a[up] < a[p]? */ + set2(L, p, up); /* swap a[up] - a[p] */ + else + lua_pop(L, 2); + } + if (up - lo == 2) /* only 3 elements? */ + return; /* already sorted */ + lua_geti(L, 1, p); /* get middle element (Pivot) */ + lua_pushvalue(L, -1); /* push Pivot */ + lua_geti(L, 1, up - 1); /* push a[up - 1] */ + set2(L, p, up - 1); /* swap Pivot (a[p]) with a[up - 1] */ + p = partition(L, lo, up); + /* a[lo .. p - 1] <= a[p] == P <= a[p + 1 .. up] */ + if (p - lo < up - p) { /* lower interval is smaller? */ + auxsort(L, lo, p - 1, rnd); /* call recursively for lower interval */ + n = p - lo; /* size of smaller interval */ + lo = p + 1; /* tail call for [p + 1 .. up] (upper interval) */ + } + else { + auxsort(L, p + 1, up, rnd); /* call recursively for upper interval */ + n = up - p; /* size of smaller interval */ + up = p - 1; /* tail call for [lo .. p - 1] (lower interval) */ + } + if ((up - lo) / 128 > n) /* partition too imbalanced? */ + rnd = l_randomizePivot(); /* try a new randomization */ + } /* tail call auxsort(L, lo, up, rnd) */ +} + + +static int sort (lua_State *L) { + lua_Integer n = aux_getn(L, 1, TAB_RW); + if (n > 1) { /* non-trivial interval? */ + luaL_argcheck(L, n < INT_MAX, 1, "array too big"); + if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checktype(L, 2, LUA_TFUNCTION); /* must be a function */ + lua_settop(L, 2); /* make sure there are two arguments */ + auxsort(L, 1, (IdxT)n, 0); + } + return 0; +} + +/* }====================================================== */ + + +static const luaL_Reg tab_funcs[] = { + {"concat", tconcat}, + {"insert", tinsert}, + {"pack", tpack}, + {"unpack", tunpack}, + {"remove", tremove}, + {"move", tmove}, + {"sort", sort}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_table (lua_State *L) { + luaL_newlib(L, tab_funcs); + return 1; +} + diff --git a/arm9/source/lua/ltm.c b/arm9/source/lua/ltm.c new file mode 100644 index 000000000..07a060811 --- /dev/null +++ b/arm9/source/lua/ltm.c @@ -0,0 +1,271 @@ +/* +** $Id: ltm.c $ +** Tag methods +** See Copyright Notice in lua.h +*/ + +#define ltm_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + +static const char udatatypename[] = "userdata"; + +LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTYPES] = { + "no value", + "nil", "boolean", udatatypename, "number", + "string", "table", "function", udatatypename, "thread", + "upvalue", "proto" /* these last cases are used for tests only */ +}; + + +void luaT_init (lua_State *L) { + static const char *const luaT_eventname[] = { /* ORDER TM */ + "__index", "__newindex", + "__gc", "__mode", "__len", "__eq", + "__add", "__sub", "__mul", "__mod", "__pow", + "__div", "__idiv", + "__band", "__bor", "__bxor", "__shl", "__shr", + "__unm", "__bnot", "__lt", "__le", + "__concat", "__call", "__close" + }; + int i; + for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); + luaC_fix(L, obj2gco(G(L)->tmname[i])); /* never collect these names */ + } +} + + +/* +** function to be used with macro "fasttm": optimized for absence of +** tag methods +*/ +const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { + const TValue *tm = luaH_getshortstr(events, ename); + lua_assert(event <= TM_EQ); + if (notm(tm)) { /* no tag method? */ + events->flags |= cast_byte(1u<metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(o)->metatable; + break; + default: + mt = G(L)->mt[ttype(o)]; + } + return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : &G(L)->nilvalue); +} + + +/* +** Return the name of the type of an object. For tables and userdata +** with metatable, use their '__name' metafield, if present. +*/ +const char *luaT_objtypename (lua_State *L, const TValue *o) { + Table *mt; + if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) || + (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL)) { + const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name")); + if (ttisstring(name)) /* is '__name' a string? */ + return getstr(tsvalue(name)); /* use it as type name */ + } + return ttypename(ttype(o)); /* else use standard type name */ +} + + +void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, const TValue *p3) { + StkId func = L->top.p; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + setobj2s(L, func + 3, p3); /* 3rd argument */ + L->top.p = func + 4; + /* metamethod may yield only when called from Lua code */ + if (isLuacode(L->ci)) + luaD_call(L, func, 0); + else + luaD_callnoyield(L, func, 0); +} + + +void luaT_callTMres (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, StkId res) { + ptrdiff_t result = savestack(L, res); + StkId func = L->top.p; + setobj2s(L, func, f); /* push function (assume EXTRA_STACK) */ + setobj2s(L, func + 1, p1); /* 1st argument */ + setobj2s(L, func + 2, p2); /* 2nd argument */ + L->top.p += 3; + /* metamethod may yield only when called from Lua code */ + if (isLuacode(L->ci)) + luaD_call(L, func, 1); + else + luaD_callnoyield(L, func, 1); + res = restorestack(L, result); + setobjs2s(L, res, --L->top.p); /* move result to its place */ +} + + +static int callbinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ + if (notm(tm)) + tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ + if (notm(tm)) return 0; + luaT_callTMres(L, tm, p1, p2, res); + return 1; +} + + +void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + if (l_unlikely(!callbinTM(L, p1, p2, res, event))) { + switch (event) { + case TM_BAND: case TM_BOR: case TM_BXOR: + case TM_SHL: case TM_SHR: case TM_BNOT: { + if (ttisnumber(p1) && ttisnumber(p2)) + luaG_tointerror(L, p1, p2); + else + luaG_opinterror(L, p1, p2, "perform bitwise operation on"); + } + /* calls never return, but to avoid warnings: *//* FALLTHROUGH */ + default: + luaG_opinterror(L, p1, p2, "perform arithmetic on"); + } + } +} + + +void luaT_tryconcatTM (lua_State *L) { + StkId top = L->top.p; + if (l_unlikely(!callbinTM(L, s2v(top - 2), s2v(top - 1), top - 2, + TM_CONCAT))) + luaG_concaterror(L, s2v(top - 2), s2v(top - 1)); +} + + +void luaT_trybinassocTM (lua_State *L, const TValue *p1, const TValue *p2, + int flip, StkId res, TMS event) { + if (flip) + luaT_trybinTM(L, p2, p1, res, event); + else + luaT_trybinTM(L, p1, p2, res, event); +} + + +void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2, + int flip, StkId res, TMS event) { + TValue aux; + setivalue(&aux, i2); + luaT_trybinassocTM(L, p1, &aux, flip, res, event); +} + + +/* +** Calls an order tag method. +** For lessequal, LUA_COMPAT_LT_LE keeps compatibility with old +** behavior: if there is no '__le', try '__lt', based on l <= r iff +** !(r < l) (assuming a total order). If the metamethod yields during +** this substitution, the continuation has to know about it (to negate +** the result of rtop.p, event)) /* try original event */ + return !l_isfalse(s2v(L->top.p)); +#if defined(LUA_COMPAT_LT_LE) + else if (event == TM_LE) { + /* try '!(p2 < p1)' for '(p1 <= p2)' */ + L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ + if (callbinTM(L, p2, p1, L->top.p, TM_LT)) { + L->ci->callstatus ^= CIST_LEQ; /* clear mark */ + return l_isfalse(s2v(L->top.p)); + } + /* else error will remove this 'ci'; no need to clear mark */ + } +#endif + luaG_ordererror(L, p1, p2); /* no metamethod found */ + return 0; /* to avoid warnings */ +} + + +int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, + int flip, int isfloat, TMS event) { + TValue aux; const TValue *p2; + if (isfloat) { + setfltvalue(&aux, cast_num(v2)); + } + else + setivalue(&aux, v2); + if (flip) { /* arguments were exchanged? */ + p2 = p1; p1 = &aux; /* correct them */ + } + else + p2 = &aux; + return luaT_callorderTM(L, p1, p2, event); +} + + +void luaT_adjustvarargs (lua_State *L, int nfixparams, CallInfo *ci, + const Proto *p) { + int i; + int actual = cast_int(L->top.p - ci->func.p) - 1; /* number of arguments */ + int nextra = actual - nfixparams; /* number of extra arguments */ + ci->u.l.nextraargs = nextra; + luaD_checkstack(L, p->maxstacksize + 1); + /* copy function to the top of the stack */ + setobjs2s(L, L->top.p++, ci->func.p); + /* move fixed parameters to the top of the stack */ + for (i = 1; i <= nfixparams; i++) { + setobjs2s(L, L->top.p++, ci->func.p + i); + setnilvalue(s2v(ci->func.p + i)); /* erase original parameter (for GC) */ + } + ci->func.p += actual + 1; + ci->top.p += actual + 1; + lua_assert(L->top.p <= ci->top.p && ci->top.p <= L->stack_last.p); +} + + +void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) { + int i; + int nextra = ci->u.l.nextraargs; + if (wanted < 0) { + wanted = nextra; /* get all extra arguments available */ + checkstackGCp(L, nextra, where); /* ensure stack space */ + L->top.p = where + nextra; /* next instruction will need top */ + } + for (i = 0; i < wanted && i < nextra; i++) + setobjs2s(L, where + i, ci->func.p - nextra + i); + for (; i < wanted; i++) /* complete required results with nil */ + setnilvalue(s2v(where + i)); +} + diff --git a/arm9/source/lua/ltm.h b/arm9/source/lua/ltm.h new file mode 100644 index 000000000..c309e2ae1 --- /dev/null +++ b/arm9/source/lua/ltm.h @@ -0,0 +1,104 @@ +/* +** $Id: ltm.h $ +** Tag methods +** See Copyright Notice in lua.h +*/ + +#ifndef ltm_h +#define ltm_h + + +#include "lobject.h" +#include "lstate.h" + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER TM" and "ORDER OP" +*/ +typedef enum { + TM_INDEX, + TM_NEWINDEX, + TM_GC, + TM_MODE, + TM_LEN, + TM_EQ, /* last tag method with fast access */ + TM_ADD, + TM_SUB, + TM_MUL, + TM_MOD, + TM_POW, + TM_DIV, + TM_IDIV, + TM_BAND, + TM_BOR, + TM_BXOR, + TM_SHL, + TM_SHR, + TM_UNM, + TM_BNOT, + TM_LT, + TM_LE, + TM_CONCAT, + TM_CALL, + TM_CLOSE, + TM_N /* number of elements in the enum */ +} TMS; + + +/* +** Mask with 1 in all fast-access methods. A 1 in any of these bits +** in the flag of a (meta)table means the metatable does not have the +** corresponding metamethod field. (Bit 7 of the flag is used for +** 'isrealasize'.) +*/ +#define maskflags (~(~0u << (TM_EQ + 1))) + + +/* +** Test whether there is no tagmethod. +** (Because tagmethods use raw accesses, the result may be an "empty" nil.) +*/ +#define notm(tm) ttisnil(tm) + + +#define gfasttm(g,et,e) ((et) == NULL ? NULL : \ + ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) + +#define fasttm(l,et,e) gfasttm(G(l), et, e) + +#define ttypename(x) luaT_typenames_[(x) + 1] + +LUAI_DDEC(const char *const luaT_typenames_[LUA_TOTALTYPES];) + + +LUAI_FUNC const char *luaT_objtypename (lua_State *L, const TValue *o); + +LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); +LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, + TMS event); +LUAI_FUNC void luaT_init (lua_State *L); + +LUAI_FUNC void luaT_callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, const TValue *p3); +LUAI_FUNC void luaT_callTMres (lua_State *L, const TValue *f, + const TValue *p1, const TValue *p2, StkId p3); +LUAI_FUNC void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event); +LUAI_FUNC void luaT_tryconcatTM (lua_State *L); +LUAI_FUNC void luaT_trybinassocTM (lua_State *L, const TValue *p1, + const TValue *p2, int inv, StkId res, TMS event); +LUAI_FUNC void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2, + int inv, StkId res, TMS event); +LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1, + const TValue *p2, TMS event); +LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, + int inv, int isfloat, TMS event); + +LUAI_FUNC void luaT_adjustvarargs (lua_State *L, int nfixparams, + CallInfo *ci, const Proto *p); +LUAI_FUNC void luaT_getvarargs (lua_State *L, CallInfo *ci, + StkId where, int wanted); + + +#endif diff --git a/arm9/source/lua/lua.h b/arm9/source/lua/lua.h new file mode 100644 index 000000000..fd16cf805 --- /dev/null +++ b/arm9/source/lua/lua.h @@ -0,0 +1,523 @@ +/* +** $Id: lua.h $ +** Lua - A Scripting Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include +#include + + +#include "luaconf.h" + + +#define LUA_VERSION_MAJOR "5" +#define LUA_VERSION_MINOR "4" +#define LUA_VERSION_RELEASE "6" + +#define LUA_VERSION_NUM 504 +#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 6) + +#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2023 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" + + +/* mark for precompiled code ('Lua') */ +#define LUA_SIGNATURE "\x1bLua" + +/* option for multiple returns in 'lua_pcall' and 'lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) +*/ +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) +#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) + + +/* thread status */ +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRERR 5 + + +typedef struct lua_State lua_State; + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTYPES 9 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* predefined values in the registry */ +#define LUA_RIDX_MAINTHREAD 1 +#define LUA_RIDX_GLOBALS 2 +#define LUA_RIDX_LAST LUA_RIDX_GLOBALS + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction) (lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); + + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); + + +/* +** Type for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + +/* +** Type for warning functions +*/ +typedef void (*lua_WarnFunction) (void *ud, const char *msg, int tocont); + + +/* +** Type used by the debug API to collect debug information +*/ +typedef struct lua_Debug lua_Debug; + + +/* +** Functions to be called by the debugger in specific events +*/ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* +** RCS ident string +*/ +extern const char lua_ident[]; + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); +LUA_API int (lua_closethread) (lua_State *L, lua_State *from); +LUA_API int (lua_resetthread) (lua_State *L); /* Deprecated! */ + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +LUA_API lua_Number (lua_version) (lua_State *L); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_absindex) (lua_State *L, int idx); +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_rotate) (lua_State *L, int idx, int n); +LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); +LUA_API int (lua_checkstack) (lua_State *L, int n); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isinteger) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); +LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API lua_Unsigned (lua_rawlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + + +/* +** Comparison and arithmetic functions +*/ + +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ +#define LUA_OPSUB 1 +#define LUA_OPMUL 2 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 + +LUA_API void (lua_arith) (lua_State *L, int op); + +#define LUA_OPEQ 0 +#define LUA_OPLT 1 +#define LUA_OPLE 2 + +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); + + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); +LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API int (lua_getglobal) (lua_State *L, const char *name); +LUA_API int (lua_gettable) (lua_State *L, int idx); +LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawget) (lua_State *L, int idx); +LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); + +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API int (lua_getiuservalue) (lua_State *L, int idx, int n); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_setglobal) (lua_State *L, const char *name); +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API int (lua_setiuservalue) (lua_State *L, int idx, int n); + + +/* +** 'load' and 'call' functions (load and run Lua code) +*/ +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k); +#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) + +LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, + lua_KContext ctx, lua_KFunction k); +#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) + +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname, const char *mode); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg, + int *nres); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + +#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) + + +/* +** Warning-related functions +*/ +LUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud); +LUA_API void (lua_warning) (lua_State *L, const char *msg, int tocont); + + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCISRUNNING 9 +#define LUA_GCGEN 10 +#define LUA_GCINC 11 + +LUA_API int (lua_gc) (lua_State *L, int what, ...); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); +LUA_API void (lua_len) (lua_State *L, int idx); + +LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); + +LUA_API void (lua_toclose) (lua_State *L, int idx); +LUA_API void (lua_closeslot) (lua_State *L, int idx); + + +/* +** {============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL) + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) + +#define lua_pushglobaltable(L) \ + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + +#define lua_insert(L,idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + + +/* +** {============================================================== +** compatibility macros +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) +#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) + +#endif + +#define lua_newuserdata(L,s) lua_newuserdatauv(L,s,1) +#define lua_getuservalue(L,idx) lua_getiuservalue(L,idx,1) +#define lua_setuservalue(L,idx) lua_setiuservalue(L,idx,1) + +#define LUA_NUMTAGS LUA_NUMTYPES + +/* }============================================================== */ + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILCALL 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + + +LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar); +LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n); +LUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n); + +LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); +LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, + int fidx2, int n2); + +LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook (lua_gethook) (lua_State *L); +LUA_API int (lua_gethookmask) (lua_State *L); +LUA_API int (lua_gethookcount) (lua_State *L); + +LUA_API int (lua_setcstacklimit) (lua_State *L, unsigned int limit); + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + const char *source; /* (S) */ + size_t srclen; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + unsigned char nups; /* (u) number of upvalues */ + unsigned char nparams;/* (u) number of parameters */ + char isvararg; /* (u) */ + char istailcall; /* (t) */ + unsigned short ftransfer; /* (r) index of first value transferred */ + unsigned short ntransfer; /* (r) number of transferred values */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + struct CallInfo *i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2023 Lua.org, PUC-Rio. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + + +#endif diff --git a/arm9/source/lua/lua.hpp b/arm9/source/lua/lua.hpp new file mode 100644 index 000000000..ec417f594 --- /dev/null +++ b/arm9/source/lua/lua.hpp @@ -0,0 +1,9 @@ +// lua.hpp +// Lua header files for C++ +// <> not supplied automatically because Lua also compiles as C++ + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} diff --git a/arm9/source/lua/luaconf.h b/arm9/source/lua/luaconf.h new file mode 100644 index 000000000..137103ede --- /dev/null +++ b/arm9/source/lua/luaconf.h @@ -0,0 +1,793 @@ +/* +** $Id: luaconf.h $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef luaconf_h +#define luaconf_h + +#include +#include + + +/* +** =================================================================== +** General Configuration File for Lua +** +** Some definitions here can be changed externally, through the compiler +** (e.g., with '-D' options): They are commented out or protected +** by '#if !defined' guards. However, several other definitions +** should be changed directly here, either because they affect the +** Lua ABI (by making the changes here, you ensure that all software +** connected to Lua, such as C libraries, will be compiled with the same +** configuration); or because they are seldom changed. +** +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + + +/* +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance restricting it to C89. +** ===================================================================== +*/ + +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ + + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ +#endif + + +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#endif + + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ +#endif + + +#if defined(LUA_USE_IOS) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN +#endif + + +/* +@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits. +*/ +#define LUAI_IS32INT ((UINT_MAX >> 30) >= 3) + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Number types. These options should not be +** set externally, because any other code connected to Lua must +** use the same configuration. +** =================================================================== +*/ + +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options supported +** by your C compiler. The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + + +/* Default configuration ('long long' and 'double', for 64-bit Lua) */ +#define LUA_INT_DEFAULT LUA_INT_LONGLONG +#define LUA_FLOAT_DEFAULT LUA_FLOAT_DOUBLE + + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. +*/ +#define LUA_32BITS 0 + + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS 1 +#else +#define LUA_C89_NUMBERS 0 +#endif + + +#if LUA_32BITS /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_IS32INT /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT + +#elif LUA_C89_NUMBERS /* }{ */ +/* +** largest types available for C89 ('long' and 'double') +*/ +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#else /* }{ */ +/* use defaults */ + +#define LUA_INT_TYPE LUA_INT_DEFAULT +#define LUA_FLOAT_TYPE LUA_FLOAT_DEFAULT + +#endif /* } */ + + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ + +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +*/ +#define LUA_PATH_SEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXEC_DIR "!" + + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +** Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +** C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ + +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + ".\\?.lua;" ".\\?\\init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.dll;" \ + LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR"loadall.dll;" ".\\?.dll" +#endif + +#else /* }{ */ + +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + "./?.lua;" "./?/init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" +#endif + +#endif /* } */ + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if !defined(LUA_DIRSEP) + +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== +*/ + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + + +/* +** More often than not the libs go together with the core. +*/ +#define LUALIB_API LUA_API +#define LUAMOD_API LUA_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables, +** none of which to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("internal"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC(dec) LUAI_FUNC dec +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_5_3) /* { */ + +/* +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. +** (These functions were already officially removed in 5.3; +** nevertheless they are still available here.) +*/ +#define LUA_COMPAT_MATHLIB + +/* +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) +** (These macros were also officially removed in 5.3, but they are still +** available here.) +*/ +#define LUA_COMPAT_APIINTCASTS + + +/* +@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod +** using '__lt'. +*/ +#define LUA_COMPAT_LT_LE + + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +** (Once more, these macros were officially removed in 5.3, but they are +** still available here.) +*/ +#define lua_strlen(L,i) lua_rawlen(L, (i)) + +#define lua_objlen(L,i) lua_rawlen(L, (i)) + +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) + +#endif /* } */ + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Numbers (low-level part). +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== +*/ + +/* +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' +@@ over a floating number. +@@ l_floatatt(x) corrects float attribute 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeral to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) \ + l_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n)) + +/* +@@ lua_numbertointeger converts a float number with an integral value +** to an integer, or returns 0 if float is not within the range of +** a lua_Integer. (The range comparisons are tricky because of +** rounding. The tests here assume a two-complement representation, +** where MININTEGER always has an exact representation as a float; +** MAXINTEGER may not have one, and therefore its conversion to float +** may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_floatatt(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_floatatt(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s,p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_floatatt(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s,p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + + + +/* +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +@@ LUAI_UACINT is the result of a 'default argument promotion' +@@ over a LUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ LUA_MAXUNSIGNED is the maximum value for a LUA_UNSIGNED. +@@ lua_integer2str converts an integer to a string. +*/ + + +/* The following definitions are good for most cases here */ + +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" + +#define LUAI_UACINT LUA_INTEGER + +#define lua_integer2str(s,sz,n) \ + l_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n)) + +/* +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) +*/ +#define LUA_UNSIGNED unsigned LUAI_UACINT + + +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#define LUA_MAXUNSIGNED UINT_MAX + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" + +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN + +#define LUA_MAXUNSIGNED ULONG_MAX + +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#define LUA_MAXUNSIGNED ULLONG_MAX + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#define LUA_MAXUNSIGNED _UI64_MAX + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Dependencies with C99 and other C details +** =================================================================== +*/ + +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif + + +/* +@@ lua_strx2number converts a hexadecimal numeral to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif + + +/* +@@ lua_pointer2str converts a pointer to a readable string in a +** non-specified way. +*/ +#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p) + + +/* +@@ lua_number2strx converts a float to a hexadecimal numeral. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) \ + ((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n))) +#endif + + +/* +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) +*/ +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) +#endif + + +/* +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) +*/ +#define LUA_KCONTEXT ptrdiff_t + +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t +#endif +#endif + + +/* +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include the header 'locale.h'.) +*/ +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + + +/* +** macros to improve jump prediction, used mostly for error handling +** and debug facilities. (Some macros in the Lua API use these macros. +** Define LUA_NOBUILTIN if you do not want '__builtin_expect' in your +** code.) +*/ +#if !defined(luai_likely) + +#if defined(__GNUC__) && !defined(LUA_NOBUILTIN) +#define luai_likely(x) (__builtin_expect(((x) != 0), 1)) +#define luai_unlikely(x) (__builtin_expect(((x) != 0), 0)) +#else +#define luai_likely(x) (x) +#define luai_unlikely(x) (x) +#endif + +#endif + + +#if defined(LUA_CORE) || defined(LUA_LIB) +/* shorter names for Lua's own use */ +#define l_likely(x) luai_likely(x) +#define l_unlikely(x) luai_unlikely(x) +#endif + + + +/* }================================================================== */ + + +/* +** {================================================================== +** Language Variations +** ===================================================================== +*/ + +/* +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. +*/ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ + + +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). +** ===================================================================== +*/ + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +** (It must fit into max(size_t)/32 and max(int)/2.) +*/ +#if LUAI_IS32INT +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + + +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +** of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +@@ LUAL_BUFFERSIZE is the initial buffer size used by the lauxlib +** buffer system. +*/ +#define LUAL_BUFFERSIZE ((int)(16 * sizeof(void*) * sizeof(lua_Number))) + + +/* +@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure +** maximum alignment for the other items in that union. +*/ +#define LUAI_MAXALIGN lua_Number n; double u; void *s; lua_Integer i; long l + +/* }================================================================== */ + + + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + + + +#endif + diff --git a/arm9/source/lua/lualib.h b/arm9/source/lua/lualib.h new file mode 100644 index 000000000..262552907 --- /dev/null +++ b/arm9/source/lua/lualib.h @@ -0,0 +1,52 @@ +/* +** $Id: lualib.h $ +** Lua standard libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lualib_h +#define lualib_h + +#include "lua.h" + + +/* version suffix for environment variable names */ +#define LUA_VERSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + + +LUAMOD_API int (luaopen_base) (lua_State *L); + +#define LUA_COLIBNAME "coroutine" +LUAMOD_API int (luaopen_coroutine) (lua_State *L); + +#define LUA_TABLIBNAME "table" +LUAMOD_API int (luaopen_table) (lua_State *L); + +#define LUA_IOLIBNAME "io" +LUAMOD_API int (luaopen_io) (lua_State *L); + +#define LUA_OSLIBNAME "os" +LUAMOD_API int (luaopen_os) (lua_State *L); + +#define LUA_STRLIBNAME "string" +LUAMOD_API int (luaopen_string) (lua_State *L); + +#define LUA_UTF8LIBNAME "utf8" +LUAMOD_API int (luaopen_utf8) (lua_State *L); + +#define LUA_MATHLIBNAME "math" +LUAMOD_API int (luaopen_math) (lua_State *L); + +#define LUA_DBLIBNAME "debug" +LUAMOD_API int (luaopen_debug) (lua_State *L); + +#define LUA_LOADLIBNAME "package" +LUAMOD_API int (luaopen_package) (lua_State *L); + + +/* open all previous libraries */ +LUALIB_API void (luaL_openlibs) (lua_State *L); + + +#endif diff --git a/arm9/source/lua/lundump.c b/arm9/source/lua/lundump.c new file mode 100644 index 000000000..02aed64fb --- /dev/null +++ b/arm9/source/lua/lundump.c @@ -0,0 +1,335 @@ +/* +** $Id: lundump.c $ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#define lundump_c +#define LUA_CORE + +#include "lprefix.h" + + +#include +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstring.h" +#include "lundump.h" +#include "lzio.h" + + +#if !defined(luai_verifycode) +#define luai_verifycode(L,f) /* empty */ +#endif + + +typedef struct { + lua_State *L; + ZIO *Z; + const char *name; +} LoadState; + + +static l_noret error (LoadState *S, const char *why) { + luaO_pushfstring(S->L, "%s: bad binary format (%s)", S->name, why); + luaD_throw(S->L, LUA_ERRSYNTAX); +} + + +/* +** All high-level loads go through loadVector; you can change it to +** adapt to the endianness of the input +*/ +#define loadVector(S,b,n) loadBlock(S,b,(n)*sizeof((b)[0])) + +static void loadBlock (LoadState *S, void *b, size_t size) { + if (luaZ_read(S->Z, b, size) != 0) + error(S, "truncated chunk"); +} + + +#define loadVar(S,x) loadVector(S,&x,1) + + +static lu_byte loadByte (LoadState *S) { + int b = zgetc(S->Z); + if (b == EOZ) + error(S, "truncated chunk"); + return cast_byte(b); +} + + +static size_t loadUnsigned (LoadState *S, size_t limit) { + size_t x = 0; + int b; + limit >>= 7; + do { + b = loadByte(S); + if (x >= limit) + error(S, "integer overflow"); + x = (x << 7) | (b & 0x7f); + } while ((b & 0x80) == 0); + return x; +} + + +static size_t loadSize (LoadState *S) { + return loadUnsigned(S, ~(size_t)0); +} + + +static int loadInt (LoadState *S) { + return cast_int(loadUnsigned(S, INT_MAX)); +} + + +static lua_Number loadNumber (LoadState *S) { + lua_Number x; + loadVar(S, x); + return x; +} + + +static lua_Integer loadInteger (LoadState *S) { + lua_Integer x; + loadVar(S, x); + return x; +} + + +/* +** Load a nullable string into prototype 'p'. +*/ +static TString *loadStringN (LoadState *S, Proto *p) { + lua_State *L = S->L; + TString *ts; + size_t size = loadSize(S); + if (size == 0) /* no string? */ + return NULL; + else if (--size <= LUAI_MAXSHORTLEN) { /* short string? */ + char buff[LUAI_MAXSHORTLEN]; + loadVector(S, buff, size); /* load string into buffer */ + ts = luaS_newlstr(L, buff, size); /* create string */ + } + else { /* long string */ + ts = luaS_createlngstrobj(L, size); /* create string */ + setsvalue2s(L, L->top.p, ts); /* anchor it ('loadVector' can GC) */ + luaD_inctop(L); + loadVector(S, getstr(ts), size); /* load directly in final place */ + L->top.p--; /* pop string */ + } + luaC_objbarrier(L, p, ts); + return ts; +} + + +/* +** Load a non-nullable string into prototype 'p'. +*/ +static TString *loadString (LoadState *S, Proto *p) { + TString *st = loadStringN(S, p); + if (st == NULL) + error(S, "bad format for constant string"); + return st; +} + + +static void loadCode (LoadState *S, Proto *f) { + int n = loadInt(S); + f->code = luaM_newvectorchecked(S->L, n, Instruction); + f->sizecode = n; + loadVector(S, f->code, n); +} + + +static void loadFunction(LoadState *S, Proto *f, TString *psource); + + +static void loadConstants (LoadState *S, Proto *f) { + int i; + int n = loadInt(S); + f->k = luaM_newvectorchecked(S->L, n, TValue); + f->sizek = n; + for (i = 0; i < n; i++) + setnilvalue(&f->k[i]); + for (i = 0; i < n; i++) { + TValue *o = &f->k[i]; + int t = loadByte(S); + switch (t) { + case LUA_VNIL: + setnilvalue(o); + break; + case LUA_VFALSE: + setbfvalue(o); + break; + case LUA_VTRUE: + setbtvalue(o); + break; + case LUA_VNUMFLT: + setfltvalue(o, loadNumber(S)); + break; + case LUA_VNUMINT: + setivalue(o, loadInteger(S)); + break; + case LUA_VSHRSTR: + case LUA_VLNGSTR: + setsvalue2n(S->L, o, loadString(S, f)); + break; + default: lua_assert(0); + } + } +} + + +static void loadProtos (LoadState *S, Proto *f) { + int i; + int n = loadInt(S); + f->p = luaM_newvectorchecked(S->L, n, Proto *); + f->sizep = n; + for (i = 0; i < n; i++) + f->p[i] = NULL; + for (i = 0; i < n; i++) { + f->p[i] = luaF_newproto(S->L); + luaC_objbarrier(S->L, f, f->p[i]); + loadFunction(S, f->p[i], f->source); + } +} + + +/* +** Load the upvalues for a function. The names must be filled first, +** because the filling of the other fields can raise read errors and +** the creation of the error message can call an emergency collection; +** in that case all prototypes must be consistent for the GC. +*/ +static void loadUpvalues (LoadState *S, Proto *f) { + int i, n; + n = loadInt(S); + f->upvalues = luaM_newvectorchecked(S->L, n, Upvaldesc); + f->sizeupvalues = n; + for (i = 0; i < n; i++) /* make array valid for GC */ + f->upvalues[i].name = NULL; + for (i = 0; i < n; i++) { /* following calls can raise errors */ + f->upvalues[i].instack = loadByte(S); + f->upvalues[i].idx = loadByte(S); + f->upvalues[i].kind = loadByte(S); + } +} + + +static void loadDebug (LoadState *S, Proto *f) { + int i, n; + n = loadInt(S); + f->lineinfo = luaM_newvectorchecked(S->L, n, ls_byte); + f->sizelineinfo = n; + loadVector(S, f->lineinfo, n); + n = loadInt(S); + f->abslineinfo = luaM_newvectorchecked(S->L, n, AbsLineInfo); + f->sizeabslineinfo = n; + for (i = 0; i < n; i++) { + f->abslineinfo[i].pc = loadInt(S); + f->abslineinfo[i].line = loadInt(S); + } + n = loadInt(S); + f->locvars = luaM_newvectorchecked(S->L, n, LocVar); + f->sizelocvars = n; + for (i = 0; i < n; i++) + f->locvars[i].varname = NULL; + for (i = 0; i < n; i++) { + f->locvars[i].varname = loadStringN(S, f); + f->locvars[i].startpc = loadInt(S); + f->locvars[i].endpc = loadInt(S); + } + n = loadInt(S); + if (n != 0) /* does it have debug information? */ + n = f->sizeupvalues; /* must be this many */ + for (i = 0; i < n; i++) + f->upvalues[i].name = loadStringN(S, f); +} + + +static void loadFunction (LoadState *S, Proto *f, TString *psource) { + f->source = loadStringN(S, f); + if (f->source == NULL) /* no source in dump? */ + f->source = psource; /* reuse parent's source */ + f->linedefined = loadInt(S); + f->lastlinedefined = loadInt(S); + f->numparams = loadByte(S); + f->is_vararg = loadByte(S); + f->maxstacksize = loadByte(S); + loadCode(S, f); + loadConstants(S, f); + loadUpvalues(S, f); + loadProtos(S, f); + loadDebug(S, f); +} + + +static void checkliteral (LoadState *S, const char *s, const char *msg) { + char buff[sizeof(LUA_SIGNATURE) + sizeof(LUAC_DATA)]; /* larger than both */ + size_t len = strlen(s); + loadVector(S, buff, len); + if (memcmp(s, buff, len) != 0) + error(S, msg); +} + + +static void fchecksize (LoadState *S, size_t size, const char *tname) { + if (loadByte(S) != size) + error(S, luaO_pushfstring(S->L, "%s size mismatch", tname)); +} + + +#define checksize(S,t) fchecksize(S,sizeof(t),#t) + +static void checkHeader (LoadState *S) { + /* skip 1st char (already read and checked) */ + checkliteral(S, &LUA_SIGNATURE[1], "not a binary chunk"); + if (loadByte(S) != LUAC_VERSION) + error(S, "version mismatch"); + if (loadByte(S) != LUAC_FORMAT) + error(S, "format mismatch"); + checkliteral(S, LUAC_DATA, "corrupted chunk"); + checksize(S, Instruction); + checksize(S, lua_Integer); + checksize(S, lua_Number); + if (loadInteger(S) != LUAC_INT) + error(S, "integer format mismatch"); + if (loadNumber(S) != LUAC_NUM) + error(S, "float format mismatch"); +} + + +/* +** Load precompiled chunk. +*/ +LClosure *luaU_undump(lua_State *L, ZIO *Z, const char *name) { + LoadState S; + LClosure *cl; + if (*name == '@' || *name == '=') + S.name = name + 1; + else if (*name == LUA_SIGNATURE[0]) + S.name = "binary string"; + else + S.name = name; + S.L = L; + S.Z = Z; + checkHeader(&S); + cl = luaF_newLclosure(L, loadByte(&S)); + setclLvalue2s(L, L->top.p, cl); + luaD_inctop(L); + cl->p = luaF_newproto(L); + luaC_objbarrier(L, cl, cl->p); + loadFunction(&S, cl->p, NULL); + lua_assert(cl->nupvalues == cl->p->sizeupvalues); + luai_verifycode(L, cl->p); + return cl; +} + diff --git a/arm9/source/lua/lundump.h b/arm9/source/lua/lundump.h new file mode 100644 index 000000000..f3748a998 --- /dev/null +++ b/arm9/source/lua/lundump.h @@ -0,0 +1,36 @@ +/* +** $Id: lundump.h $ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#ifndef lundump_h +#define lundump_h + +#include "llimits.h" +#include "lobject.h" +#include "lzio.h" + + +/* data to catch conversion errors */ +#define LUAC_DATA "\x19\x93\r\n\x1a\n" + +#define LUAC_INT 0x5678 +#define LUAC_NUM cast_num(370.5) + +/* +** Encode major-minor version in one byte, one nibble for each +*/ +#define MYINT(s) (s[0]-'0') /* assume one-digit numerals */ +#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR)) + +#define LUAC_FORMAT 0 /* this is the official format */ + +/* load one chunk; from lundump.c */ +LUAI_FUNC LClosure* luaU_undump (lua_State* L, ZIO* Z, const char* name); + +/* dump one chunk; from ldump.c */ +LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, + void* data, int strip); + +#endif diff --git a/arm9/source/lua/lutf8lib.c b/arm9/source/lua/lutf8lib.c new file mode 100644 index 000000000..3a5b9bc38 --- /dev/null +++ b/arm9/source/lua/lutf8lib.c @@ -0,0 +1,291 @@ +/* +** $Id: lutf8lib.c $ +** Standard library for UTF-8 manipulation +** See Copyright Notice in lua.h +*/ + +#define lutf8lib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#define MAXUNICODE 0x10FFFFu + +#define MAXUTF 0x7FFFFFFFu + + +#define MSGInvalid "invalid UTF-8 code" + +/* +** Integer type for decoded UTF-8 values; MAXUTF needs 31 bits. +*/ +#if (UINT_MAX >> 30) >= 1 +typedef unsigned int utfint; +#else +typedef unsigned long utfint; +#endif + + +#define iscont(c) (((c) & 0xC0) == 0x80) +#define iscontp(p) iscont(*(p)) + + +/* from strlib */ +/* translate a relative string position: negative means back from end */ +static lua_Integer u_posrelat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; + else if (0u - (size_t)pos > len) return 0; + else return (lua_Integer)len + pos + 1; +} + + +/* +** Decode one UTF-8 sequence, returning NULL if byte sequence is +** invalid. The array 'limits' stores the minimum value for each +** sequence length, to check for overlong representations. Its first +** entry forces an error for non-ascii bytes with no continuation +** bytes (count == 0). +*/ +static const char *utf8_decode (const char *s, utfint *val, int strict) { + static const utfint limits[] = + {~(utfint)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u}; + unsigned int c = (unsigned char)s[0]; + utfint res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */ + unsigned int cc = (unsigned char)s[++count]; /* read next byte */ + if (!iscont(cc)) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + } + res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */ + if (count > 5 || res > MAXUTF || res < limits[count]) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + if (strict) { + /* check for invalid code points; too large or surrogates */ + if (res > MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu)) + return NULL; + } + if (val) *val = res; + return s + 1; /* +1 to include first byte */ +} + + +/* +** utf8len(s [, i [, j [, lax]]]) --> number of characters that +** start in the range [i,j], or nil + current position if 's' is not +** well formed in that interval +*/ +static int utflen (lua_State *L) { + lua_Integer n = 0; /* counter for the number of characters */ + size_t len; /* string length in bytes */ + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len); + int lax = lua_toboolean(L, 4); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2, + "initial position out of bounds"); + luaL_argcheck(L, --posj < (lua_Integer)len, 3, + "final position out of bounds"); + while (posi <= posj) { + const char *s1 = utf8_decode(s + posi, NULL, !lax); + if (s1 == NULL) { /* conversion error? */ + luaL_pushfail(L); /* return fail ... */ + lua_pushinteger(L, posi + 1); /* ... and current position */ + return 2; + } + posi = s1 - s; + n++; + } + lua_pushinteger(L, n); + return 1; +} + + +/* +** codepoint(s, [i, [j [, lax]]]) -> returns codepoints for all +** characters that start in the range [i,j] +*/ +static int codepoint (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len); + int lax = lua_toboolean(L, 4); + int n; + const char *se; + luaL_argcheck(L, posi >= 1, 2, "out of bounds"); + luaL_argcheck(L, pose <= (lua_Integer)len, 3, "out of bounds"); + if (posi > pose) return 0; /* empty interval; return no values */ + if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi) + 1; /* upper bound for number of returns */ + luaL_checkstack(L, n, "string slice too long"); + n = 0; /* count the number of returns */ + se = s + pose; /* string end */ + for (s += posi - 1; s < se;) { + utfint code; + s = utf8_decode(s, &code, !lax); + if (s == NULL) + return luaL_error(L, MSGInvalid); + lua_pushinteger(L, code); + n++; + } + return n; +} + + +static void pushutfchar (lua_State *L, int arg) { + lua_Unsigned code = (lua_Unsigned)luaL_checkinteger(L, arg); + luaL_argcheck(L, code <= MAXUTF, arg, "value out of range"); + lua_pushfstring(L, "%U", (long)code); +} + + +/* +** utfchar(n1, n2, ...) -> char(n1)..char(n2)... +*/ +static int utfchar (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + if (n == 1) /* optimize common case of single char */ + pushutfchar(L, 1); + else { + int i; + luaL_Buffer b; + luaL_buffinit(L, &b); + for (i = 1; i <= n; i++) { + pushutfchar(L, i); + luaL_addvalue(&b); + } + luaL_pushresult(&b); + } + return 1; +} + + +/* +** offset(s, n, [i]) -> index where n-th character counting from +** position 'i' starts; 0 means character at 'i'. +*/ +static int byteoffset (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = luaL_checkinteger(L, 2); + lua_Integer posi = (n >= 0) ? 1 : len + 1; + posi = u_posrelat(luaL_optinteger(L, 3, posi), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 3, + "position out of bounds"); + if (n == 0) { + /* find beginning of current byte sequence */ + while (posi > 0 && iscontp(s + posi)) posi--; + } + else { + if (iscontp(s + posi)) + return luaL_error(L, "initial position is a continuation byte"); + if (n < 0) { + while (n < 0 && posi > 0) { /* move back */ + do { /* find beginning of previous character */ + posi--; + } while (posi > 0 && iscontp(s + posi)); + n++; + } + } + else { + n--; /* do not move for 1st character */ + while (n > 0 && posi < (lua_Integer)len) { + do { /* find beginning of next character */ + posi++; + } while (iscontp(s + posi)); /* (cannot pass final '\0') */ + n--; + } + } + } + if (n == 0) /* did it find given character? */ + lua_pushinteger(L, posi + 1); + else /* no such character */ + luaL_pushfail(L); + return 1; +} + + +static int iter_aux (lua_State *L, int strict) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Unsigned n = (lua_Unsigned)lua_tointeger(L, 2); + if (n < len) { + while (iscontp(s + n)) n++; /* go to next character */ + } + if (n >= len) /* (also handles original 'n' being negative) */ + return 0; /* no more codepoints */ + else { + utfint code; + const char *next = utf8_decode(s + n, &code, strict); + if (next == NULL || iscontp(next)) + return luaL_error(L, MSGInvalid); + lua_pushinteger(L, n + 1); + lua_pushinteger(L, code); + return 2; + } +} + + +static int iter_auxstrict (lua_State *L) { + return iter_aux(L, 1); +} + +static int iter_auxlax (lua_State *L) { + return iter_aux(L, 0); +} + + +static int iter_codes (lua_State *L) { + int lax = lua_toboolean(L, 2); + const char *s = luaL_checkstring(L, 1); + luaL_argcheck(L, !iscontp(s), 1, MSGInvalid); + lua_pushcfunction(L, lax ? iter_auxlax : iter_auxstrict); + lua_pushvalue(L, 1); + lua_pushinteger(L, 0); + return 3; +} + + +/* pattern to match a single UTF-8 character */ +#define UTF8PATT "[\0-\x7F\xC2-\xFD][\x80-\xBF]*" + + +static const luaL_Reg funcs[] = { + {"offset", byteoffset}, + {"codepoint", codepoint}, + {"char", utfchar}, + {"len", utflen}, + {"codes", iter_codes}, + /* placeholders */ + {"charpattern", NULL}, + {NULL, NULL} +}; + + +LUAMOD_API int luaopen_utf8 (lua_State *L) { + luaL_newlib(L, funcs); + lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT)/sizeof(char) - 1); + lua_setfield(L, -2, "charpattern"); + return 1; +} + diff --git a/arm9/source/lua/lvm.c b/arm9/source/lua/lvm.c new file mode 100644 index 000000000..8493a770c --- /dev/null +++ b/arm9/source/lua/lvm.c @@ -0,0 +1,1901 @@ +/* +** $Id: lvm.c $ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#define lvm_c +#define LUA_CORE + +#include "lprefix.h" + +#include +#include +#include +#include +#include +#include + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + +/* +** By default, use jump tables in the main interpreter loop on gcc +** and compatible compilers. +*/ +#if !defined(LUA_USE_JUMPTABLE) +#if defined(__GNUC__) +#define LUA_USE_JUMPTABLE 1 +#else +#define LUA_USE_JUMPTABLE 0 +#endif +#endif + + + +/* limit for table tag-method chains (to avoid infinite loops) */ +#define MAXTAGLOOP 2000 + + +/* +** 'l_intfitsf' checks whether a given integer is in the range that +** can be converted to a float without rounding. Used in comparisons. +*/ + +/* number of bits in the mantissa of a float */ +#define NBM (l_floatatt(MANT_DIG)) + +/* +** Check whether some integers may not fit in a float, testing whether +** (maxinteger >> NBM) > 0. (That implies (1 << NBM) <= maxinteger.) +** (The shifts are done in parts, to avoid shifting by more than the size +** of an integer. In a worst case, NBM == 113 for long double and +** sizeof(long) == 32.) +*/ +#if ((((LUA_MAXINTEGER >> (NBM / 4)) >> (NBM / 4)) >> (NBM / 4)) \ + >> (NBM - (3 * (NBM / 4)))) > 0 + +/* limit for integers that fit in a float */ +#define MAXINTFITSF ((lua_Unsigned)1 << NBM) + +/* check whether 'i' is in the interval [-MAXINTFITSF, MAXINTFITSF] */ +#define l_intfitsf(i) ((MAXINTFITSF + l_castS2U(i)) <= (2 * MAXINTFITSF)) + +#else /* all integers fit in a float precisely */ + +#define l_intfitsf(i) 1 + +#endif + + +/* +** Try to convert a value from string to a number value. +** If the value is not a string or is a string not representing +** a valid numeral (or if coercions from strings to numbers +** are disabled via macro 'cvt2num'), do not modify 'result' +** and return 0. +*/ +static int l_strton (const TValue *obj, TValue *result) { + lua_assert(obj != result); + if (!cvt2num(obj)) /* is object not a string? */ + return 0; + else + return (luaO_str2num(svalue(obj), result) == vslen(obj) + 1); +} + + +/* +** Try to convert a value to a float. The float case is already handled +** by the macro 'tonumber'. +*/ +int luaV_tonumber_ (const TValue *obj, lua_Number *n) { + TValue v; + if (ttisinteger(obj)) { + *n = cast_num(ivalue(obj)); + return 1; + } + else if (l_strton(obj, &v)) { /* string coercible to number? */ + *n = nvalue(&v); /* convert result of 'luaO_str2num' to a float */ + return 1; + } + else + return 0; /* conversion failed */ +} + + +/* +** try to convert a float to an integer, rounding according to 'mode'. +*/ +int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode) { + lua_Number f = l_floor(n); + if (n != f) { /* not an integral value? */ + if (mode == F2Ieq) return 0; /* fails if mode demands integral value */ + else if (mode == F2Iceil) /* needs ceil? */ + f += 1; /* convert floor to ceil (remember: n != f) */ + } + return lua_numbertointeger(f, p); +} + + +/* +** try to convert a value to an integer, rounding according to 'mode', +** without string coercion. +** ("Fast track" handled by macro 'tointegerns'.) +*/ +int luaV_tointegerns (const TValue *obj, lua_Integer *p, F2Imod mode) { + if (ttisfloat(obj)) + return luaV_flttointeger(fltvalue(obj), p, mode); + else if (ttisinteger(obj)) { + *p = ivalue(obj); + return 1; + } + else + return 0; +} + + +/* +** try to convert a value to an integer. +*/ +int luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode) { + TValue v; + if (l_strton(obj, &v)) /* does 'obj' point to a numerical string? */ + obj = &v; /* change it to point to its corresponding number */ + return luaV_tointegerns(obj, p, mode); +} + + +/* +** Try to convert a 'for' limit to an integer, preserving the semantics +** of the loop. Return true if the loop must not run; otherwise, '*p' +** gets the integer limit. +** (The following explanation assumes a positive step; it is valid for +** negative steps mutatis mutandis.) +** If the limit is an integer or can be converted to an integer, +** rounding down, that is the limit. +** Otherwise, check whether the limit can be converted to a float. If +** the float is too large, clip it to LUA_MAXINTEGER. If the float +** is too negative, the loop should not run, because any initial +** integer value is greater than such limit; so, the function returns +** true to signal that. (For this latter case, no integer limit would be +** correct; even a limit of LUA_MININTEGER would run the loop once for +** an initial value equal to LUA_MININTEGER.) +*/ +static int forlimit (lua_State *L, lua_Integer init, const TValue *lim, + lua_Integer *p, lua_Integer step) { + if (!luaV_tointeger(lim, p, (step < 0 ? F2Iceil : F2Ifloor))) { + /* not coercible to in integer */ + lua_Number flim; /* try to convert to float */ + if (!tonumber(lim, &flim)) /* cannot convert to float? */ + luaG_forerror(L, lim, "limit"); + /* else 'flim' is a float out of integer bounds */ + if (luai_numlt(0, flim)) { /* if it is positive, it is too large */ + if (step < 0) return 1; /* initial value must be less than it */ + *p = LUA_MAXINTEGER; /* truncate */ + } + else { /* it is less than min integer */ + if (step > 0) return 1; /* initial value must be greater than it */ + *p = LUA_MININTEGER; /* truncate */ + } + } + return (step > 0 ? init > *p : init < *p); /* not to run? */ +} + + +/* +** Prepare a numerical for loop (opcode OP_FORPREP). +** Return true to skip the loop. Otherwise, +** after preparation, stack will be as follows: +** ra : internal index (safe copy of the control variable) +** ra + 1 : loop counter (integer loops) or limit (float loops) +** ra + 2 : step +** ra + 3 : control variable +*/ +static int forprep (lua_State *L, StkId ra) { + TValue *pinit = s2v(ra); + TValue *plimit = s2v(ra + 1); + TValue *pstep = s2v(ra + 2); + if (ttisinteger(pinit) && ttisinteger(pstep)) { /* integer loop? */ + lua_Integer init = ivalue(pinit); + lua_Integer step = ivalue(pstep); + lua_Integer limit; + if (step == 0) + luaG_runerror(L, "'for' step is zero"); + setivalue(s2v(ra + 3), init); /* control variable */ + if (forlimit(L, init, plimit, &limit, step)) + return 1; /* skip the loop */ + else { /* prepare loop counter */ + lua_Unsigned count; + if (step > 0) { /* ascending loop? */ + count = l_castS2U(limit) - l_castS2U(init); + if (step != 1) /* avoid division in the too common case */ + count /= l_castS2U(step); + } + else { /* step < 0; descending loop */ + count = l_castS2U(init) - l_castS2U(limit); + /* 'step+1' avoids negating 'mininteger' */ + count /= l_castS2U(-(step + 1)) + 1u; + } + /* store the counter in place of the limit (which won't be + needed anymore) */ + setivalue(plimit, l_castU2S(count)); + } + } + else { /* try making all values floats */ + lua_Number init; lua_Number limit; lua_Number step; + if (l_unlikely(!tonumber(plimit, &limit))) + luaG_forerror(L, plimit, "limit"); + if (l_unlikely(!tonumber(pstep, &step))) + luaG_forerror(L, pstep, "step"); + if (l_unlikely(!tonumber(pinit, &init))) + luaG_forerror(L, pinit, "initial value"); + if (step == 0) + luaG_runerror(L, "'for' step is zero"); + if (luai_numlt(0, step) ? luai_numlt(limit, init) + : luai_numlt(init, limit)) + return 1; /* skip the loop */ + else { + /* make sure internal values are all floats */ + setfltvalue(plimit, limit); + setfltvalue(pstep, step); + setfltvalue(s2v(ra), init); /* internal index */ + setfltvalue(s2v(ra + 3), init); /* control variable */ + } + } + return 0; +} + + +/* +** Execute a step of a float numerical for loop, returning +** true iff the loop must continue. (The integer case is +** written online with opcode OP_FORLOOP, for performance.) +*/ +static int floatforloop (StkId ra) { + lua_Number step = fltvalue(s2v(ra + 2)); + lua_Number limit = fltvalue(s2v(ra + 1)); + lua_Number idx = fltvalue(s2v(ra)); /* internal index */ + idx = luai_numadd(L, idx, step); /* increment index */ + if (luai_numlt(0, step) ? luai_numle(idx, limit) + : luai_numle(limit, idx)) { + chgfltvalue(s2v(ra), idx); /* update internal index */ + setfltvalue(s2v(ra + 3), idx); /* and control variable */ + return 1; /* jump back */ + } + else + return 0; /* finish the loop */ +} + + +/* +** Finish the table access 'val = t[key]'. +** if 'slot' is NULL, 't' is not a table; otherwise, 'slot' points to +** t[k] entry (which must be empty). +*/ +void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, + const TValue *slot) { + int loop; /* counter to avoid infinite loops */ + const TValue *tm; /* metamethod */ + for (loop = 0; loop < MAXTAGLOOP; loop++) { + if (slot == NULL) { /* 't' is not a table? */ + lua_assert(!ttistable(t)); + tm = luaT_gettmbyobj(L, t, TM_INDEX); + if (l_unlikely(notm(tm))) + luaG_typeerror(L, t, "index"); /* no metamethod */ + /* else will try the metamethod */ + } + else { /* 't' is a table */ + lua_assert(isempty(slot)); + tm = fasttm(L, hvalue(t)->metatable, TM_INDEX); /* table's metamethod */ + if (tm == NULL) { /* no metamethod? */ + setnilvalue(s2v(val)); /* result is nil */ + return; + } + /* else will try the metamethod */ + } + if (ttisfunction(tm)) { /* is metamethod a function? */ + luaT_callTMres(L, tm, t, key, val); /* call it */ + return; + } + t = tm; /* else try to access 'tm[key]' */ + if (luaV_fastget(L, t, key, slot, luaH_get)) { /* fast track? */ + setobj2s(L, val, slot); /* done */ + return; + } + /* else repeat (tail call 'luaV_finishget') */ + } + luaG_runerror(L, "'__index' chain too long; possible loop"); +} + + +/* +** Finish a table assignment 't[key] = val'. +** If 'slot' is NULL, 't' is not a table. Otherwise, 'slot' points +** to the entry 't[key]', or to a value with an absent key if there +** is no such entry. (The value at 'slot' must be empty, otherwise +** 'luaV_fastget' would have done the job.) +*/ +void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + TValue *val, const TValue *slot) { + int loop; /* counter to avoid infinite loops */ + for (loop = 0; loop < MAXTAGLOOP; loop++) { + const TValue *tm; /* '__newindex' metamethod */ + if (slot != NULL) { /* is 't' a table? */ + Table *h = hvalue(t); /* save 't' table */ + lua_assert(isempty(slot)); /* slot must be empty */ + tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */ + if (tm == NULL) { /* no metamethod? */ + luaH_finishset(L, h, key, slot, val); /* set new value */ + invalidateTMcache(h); + luaC_barrierback(L, obj2gco(h), val); + return; + } + /* else will try the metamethod */ + } + else { /* not a table; check metamethod */ + tm = luaT_gettmbyobj(L, t, TM_NEWINDEX); + if (l_unlikely(notm(tm))) + luaG_typeerror(L, t, "index"); + } + /* try the metamethod */ + if (ttisfunction(tm)) { + luaT_callTM(L, tm, t, key, val); + return; + } + t = tm; /* else repeat assignment over 'tm' */ + if (luaV_fastget(L, t, key, slot, luaH_get)) { + luaV_finishfastset(L, t, slot, val); + return; /* done */ + } + /* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */ + } + luaG_runerror(L, "'__newindex' chain too long; possible loop"); +} + + +/* +** Compare two strings 'ls' x 'rs', returning an integer less-equal- +** -greater than zero if 'ls' is less-equal-greater than 'rs'. +** The code is a little tricky because it allows '\0' in the strings +** and it uses 'strcoll' (to respect locales) for each segments +** of the strings. +*/ +static int l_strcmp (const TString *ls, const TString *rs) { + const char *l = getstr(ls); + size_t ll = tsslen(ls); + const char *r = getstr(rs); + size_t lr = tsslen(rs); + for (;;) { /* for each segment */ + int temp = strcoll(l, r); + if (temp != 0) /* not equal? */ + return temp; /* done */ + else { /* strings are equal up to a '\0' */ + size_t len = strlen(l); /* index of first '\0' in both strings */ + if (len == lr) /* 'rs' is finished? */ + return (len == ll) ? 0 : 1; /* check 'ls' */ + else if (len == ll) /* 'ls' is finished? */ + return -1; /* 'ls' is less than 'rs' ('rs' is not finished) */ + /* both strings longer than 'len'; go on comparing after the '\0' */ + len++; + l += len; ll -= len; r += len; lr -= len; + } + } +} + + +/* +** Check whether integer 'i' is less than float 'f'. If 'i' has an +** exact representation as a float ('l_intfitsf'), compare numbers as +** floats. Otherwise, use the equivalence 'i < f <=> i < ceil(f)'. +** If 'ceil(f)' is out of integer range, either 'f' is greater than +** all integers or less than all integers. +** (The test with 'l_intfitsf' is only for performance; the else +** case is correct for all values, but it is slow due to the conversion +** from float to int.) +** When 'f' is NaN, comparisons must result in false. +*/ +l_sinline int LTintfloat (lua_Integer i, lua_Number f) { + if (l_intfitsf(i)) + return luai_numlt(cast_num(i), f); /* compare them as floats */ + else { /* i < f <=> i < ceil(f) */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Iceil)) /* fi = ceil(f) */ + return i < fi; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f > 0; /* greater? */ + } +} + + +/* +** Check whether integer 'i' is less than or equal to float 'f'. +** See comments on previous function. +*/ +l_sinline int LEintfloat (lua_Integer i, lua_Number f) { + if (l_intfitsf(i)) + return luai_numle(cast_num(i), f); /* compare them as floats */ + else { /* i <= f <=> i <= floor(f) */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ifloor)) /* fi = floor(f) */ + return i <= fi; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f > 0; /* greater? */ + } +} + + +/* +** Check whether float 'f' is less than integer 'i'. +** See comments on previous function. +*/ +l_sinline int LTfloatint (lua_Number f, lua_Integer i) { + if (l_intfitsf(i)) + return luai_numlt(f, cast_num(i)); /* compare them as floats */ + else { /* f < i <=> floor(f) < i */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Ifloor)) /* fi = floor(f) */ + return fi < i; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f < 0; /* less? */ + } +} + + +/* +** Check whether float 'f' is less than or equal to integer 'i'. +** See comments on previous function. +*/ +l_sinline int LEfloatint (lua_Number f, lua_Integer i) { + if (l_intfitsf(i)) + return luai_numle(f, cast_num(i)); /* compare them as floats */ + else { /* f <= i <=> ceil(f) <= i */ + lua_Integer fi; + if (luaV_flttointeger(f, &fi, F2Iceil)) /* fi = ceil(f) */ + return fi <= i; /* compare them as integers */ + else /* 'f' is either greater or less than all integers */ + return f < 0; /* less? */ + } +} + + +/* +** Return 'l < r', for numbers. +*/ +l_sinline int LTnum (const TValue *l, const TValue *r) { + lua_assert(ttisnumber(l) && ttisnumber(r)); + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li < ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LTintfloat(li, fltvalue(r)); /* l < r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numlt(lf, fltvalue(r)); /* both are float */ + else /* 'l' is float and 'r' is int */ + return LTfloatint(lf, ivalue(r)); + } +} + + +/* +** Return 'l <= r', for numbers. +*/ +l_sinline int LEnum (const TValue *l, const TValue *r) { + lua_assert(ttisnumber(l) && ttisnumber(r)); + if (ttisinteger(l)) { + lua_Integer li = ivalue(l); + if (ttisinteger(r)) + return li <= ivalue(r); /* both are integers */ + else /* 'l' is int and 'r' is float */ + return LEintfloat(li, fltvalue(r)); /* l <= r ? */ + } + else { + lua_Number lf = fltvalue(l); /* 'l' must be float */ + if (ttisfloat(r)) + return luai_numle(lf, fltvalue(r)); /* both are float */ + else /* 'l' is float and 'r' is int */ + return LEfloatint(lf, ivalue(r)); + } +} + + +/* +** return 'l < r' for non-numbers. +*/ +static int lessthanothers (lua_State *L, const TValue *l, const TValue *r) { + lua_assert(!ttisnumber(l) || !ttisnumber(r)); + if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) < 0; + else + return luaT_callorderTM(L, l, r, TM_LT); +} + + +/* +** Main operation less than; return 'l < r'. +*/ +int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LTnum(l, r); + else return lessthanothers(L, l, r); +} + + +/* +** return 'l <= r' for non-numbers. +*/ +static int lessequalothers (lua_State *L, const TValue *l, const TValue *r) { + lua_assert(!ttisnumber(l) || !ttisnumber(r)); + if (ttisstring(l) && ttisstring(r)) /* both are strings? */ + return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; + else + return luaT_callorderTM(L, l, r, TM_LE); +} + + +/* +** Main operation less than or equal to; return 'l <= r'. +*/ +int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { + if (ttisnumber(l) && ttisnumber(r)) /* both operands are numbers? */ + return LEnum(l, r); + else return lessequalothers(L, l, r); +} + + +/* +** Main operation for equality of Lua values; return 't1 == t2'. +** L == NULL means raw equality (no metamethods) +*/ +int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { + const TValue *tm; + if (ttypetag(t1) != ttypetag(t2)) { /* not the same variant? */ + if (ttype(t1) != ttype(t2) || ttype(t1) != LUA_TNUMBER) + return 0; /* only numbers can be equal with different variants */ + else { /* two numbers with different variants */ + /* One of them is an integer. If the other does not have an + integer value, they cannot be equal; otherwise, compare their + integer values. */ + lua_Integer i1, i2; + return (luaV_tointegerns(t1, &i1, F2Ieq) && + luaV_tointegerns(t2, &i2, F2Ieq) && + i1 == i2); + } + } + /* values have same type and same variant */ + switch (ttypetag(t1)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: return 1; + case LUA_VNUMINT: return (ivalue(t1) == ivalue(t2)); + case LUA_VNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); + case LUA_VLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); + case LUA_VLCF: return fvalue(t1) == fvalue(t2); + case LUA_VSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2)); + case LUA_VLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2)); + case LUA_VUSERDATA: { + if (uvalue(t1) == uvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + case LUA_VTABLE: { + if (hvalue(t1) == hvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, hvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + default: + return gcvalue(t1) == gcvalue(t2); + } + if (tm == NULL) /* no TM? */ + return 0; /* objects are different */ + else { + luaT_callTMres(L, tm, t1, t2, L->top.p); /* call TM */ + return !l_isfalse(s2v(L->top.p)); + } +} + + +/* macro used by 'luaV_concat' to ensure that element at 'o' is a string */ +#define tostring(L,o) \ + (ttisstring(o) || (cvt2str(o) && (luaO_tostring(L, o), 1))) + +#define isemptystr(o) (ttisshrstring(o) && tsvalue(o)->shrlen == 0) + +/* copy strings in stack from top - n up to top - 1 to buffer */ +static void copy2buff (StkId top, int n, char *buff) { + size_t tl = 0; /* size already copied */ + do { + size_t l = vslen(s2v(top - n)); /* length of string being copied */ + memcpy(buff + tl, svalue(s2v(top - n)), l * sizeof(char)); + tl += l; + } while (--n > 0); +} + + +/* +** Main operation for concatenation: concat 'total' values in the stack, +** from 'L->top.p - total' up to 'L->top.p - 1'. +*/ +void luaV_concat (lua_State *L, int total) { + if (total == 1) + return; /* "all" values already concatenated */ + do { + StkId top = L->top.p; + int n = 2; /* number of elements handled in this pass (at least 2) */ + if (!(ttisstring(s2v(top - 2)) || cvt2str(s2v(top - 2))) || + !tostring(L, s2v(top - 1))) + luaT_tryconcatTM(L); /* may invalidate 'top' */ + else if (isemptystr(s2v(top - 1))) /* second operand is empty? */ + cast_void(tostring(L, s2v(top - 2))); /* result is first operand */ + else if (isemptystr(s2v(top - 2))) { /* first operand is empty string? */ + setobjs2s(L, top - 2, top - 1); /* result is second op. */ + } + else { + /* at least two non-empty string values; get as many as possible */ + size_t tl = vslen(s2v(top - 1)); + TString *ts; + /* collect total length and number of strings */ + for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++) { + size_t l = vslen(s2v(top - n - 1)); + if (l_unlikely(l >= (MAX_SIZE/sizeof(char)) - tl)) { + L->top.p = top - total; /* pop strings to avoid wasting stack */ + luaG_runerror(L, "string length overflow"); + } + tl += l; + } + if (tl <= LUAI_MAXSHORTLEN) { /* is result a short string? */ + char buff[LUAI_MAXSHORTLEN]; + copy2buff(top, n, buff); /* copy strings to buffer */ + ts = luaS_newlstr(L, buff, tl); + } + else { /* long string; copy strings directly to final result */ + ts = luaS_createlngstrobj(L, tl); + copy2buff(top, n, getstr(ts)); + } + setsvalue2s(L, top - n, ts); /* create result */ + } + total -= n - 1; /* got 'n' strings to create one new */ + L->top.p -= n - 1; /* popped 'n' strings and pushed one */ + } while (total > 1); /* repeat until only 1 result left */ +} + + +/* +** Main operation 'ra = #rb'. +*/ +void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { + const TValue *tm; + switch (ttypetag(rb)) { + case LUA_VTABLE: { + Table *h = hvalue(rb); + tm = fasttm(L, h->metatable, TM_LEN); + if (tm) break; /* metamethod? break switch to call it */ + setivalue(s2v(ra), luaH_getn(h)); /* else primitive len */ + return; + } + case LUA_VSHRSTR: { + setivalue(s2v(ra), tsvalue(rb)->shrlen); + return; + } + case LUA_VLNGSTR: { + setivalue(s2v(ra), tsvalue(rb)->u.lnglen); + return; + } + default: { /* try metamethod */ + tm = luaT_gettmbyobj(L, rb, TM_LEN); + if (l_unlikely(notm(tm))) /* no metamethod? */ + luaG_typeerror(L, rb, "get length of"); + break; + } + } + luaT_callTMres(L, tm, rb, rb, ra); +} + + +/* +** Integer division; return 'm // n', that is, floor(m/n). +** C division truncates its result (rounds towards zero). +** 'floor(q) == trunc(q)' when 'q >= 0' or when 'q' is integer, +** otherwise 'floor(q) == trunc(q) - 1'. +*/ +lua_Integer luaV_idiv (lua_State *L, lua_Integer m, lua_Integer n) { + if (l_unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to divide by zero"); + return intop(-, 0, m); /* n==-1; avoid overflow with 0x80000...//-1 */ + } + else { + lua_Integer q = m / n; /* perform C division */ + if ((m ^ n) < 0 && m % n != 0) /* 'm/n' would be negative non-integer? */ + q -= 1; /* correct result for different rounding */ + return q; + } +} + + +/* +** Integer modulus; return 'm % n'. (Assume that C '%' with +** negative operands follows C99 behavior. See previous comment +** about luaV_idiv.) +*/ +lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) { + if (l_unlikely(l_castS2U(n) + 1u <= 1u)) { /* special cases: -1 or 0 */ + if (n == 0) + luaG_runerror(L, "attempt to perform 'n%%0'"); + return 0; /* m % -1 == 0; avoid overflow with 0x80000...%-1 */ + } + else { + lua_Integer r = m % n; + if (r != 0 && (r ^ n) < 0) /* 'm/n' would be non-integer negative? */ + r += n; /* correct result for different rounding */ + return r; + } +} + + +/* +** Float modulus +*/ +lua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) { + lua_Number r; + luai_nummod(L, m, n, r); + return r; +} + + +/* number of bits in an integer */ +#define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT) + + +/* +** Shift left operation. (Shift right just negates 'y'.) +*/ +lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) { + if (y < 0) { /* shift right? */ + if (y <= -NBITS) return 0; + else return intop(>>, x, -y); + } + else { /* shift left */ + if (y >= NBITS) return 0; + else return intop(<<, x, y); + } +} + + +/* +** create a new Lua closure, push it in the stack, and initialize +** its upvalues. +*/ +static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, + StkId ra) { + int nup = p->sizeupvalues; + Upvaldesc *uv = p->upvalues; + int i; + LClosure *ncl = luaF_newLclosure(L, nup); + ncl->p = p; + setclLvalue2s(L, ra, ncl); /* anchor new closure in stack */ + for (i = 0; i < nup; i++) { /* fill in its upvalues */ + if (uv[i].instack) /* upvalue refers to local variable? */ + ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx); + else /* get upvalue from enclosing function */ + ncl->upvals[i] = encup[uv[i].idx]; + luaC_objbarrier(L, ncl, ncl->upvals[i]); + } +} + + +/* +** finish execution of an opcode interrupted by a yield +*/ +void luaV_finishOp (lua_State *L) { + CallInfo *ci = L->ci; + StkId base = ci->func.p + 1; + Instruction inst = *(ci->u.l.savedpc - 1); /* interrupted instruction */ + OpCode op = GET_OPCODE(inst); + switch (op) { /* finish its execution */ + case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: { + setobjs2s(L, base + GETARG_A(*(ci->u.l.savedpc - 2)), --L->top.p); + break; + } + case OP_UNM: case OP_BNOT: case OP_LEN: + case OP_GETTABUP: case OP_GETTABLE: case OP_GETI: + case OP_GETFIELD: case OP_SELF: { + setobjs2s(L, base + GETARG_A(inst), --L->top.p); + break; + } + case OP_LT: case OP_LE: + case OP_LTI: case OP_LEI: + case OP_GTI: case OP_GEI: + case OP_EQ: { /* note that 'OP_EQI'/'OP_EQK' cannot yield */ + int res = !l_isfalse(s2v(L->top.p - 1)); + L->top.p--; +#if defined(LUA_COMPAT_LT_LE) + if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ + ci->callstatus ^= CIST_LEQ; /* clear mark */ + res = !res; /* negate result */ + } +#endif + lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); + if (res != GETARG_k(inst)) /* condition failed? */ + ci->u.l.savedpc++; /* skip jump instruction */ + break; + } + case OP_CONCAT: { + StkId top = L->top.p - 1; /* top when 'luaT_tryconcatTM' was called */ + int a = GETARG_A(inst); /* first element to concatenate */ + int total = cast_int(top - 1 - (base + a)); /* yet to concatenate */ + setobjs2s(L, top - 2, top); /* put TM result in proper position */ + L->top.p = top - 1; /* top is one after last element (at top-2) */ + luaV_concat(L, total); /* concat them (may yield again) */ + break; + } + case OP_CLOSE: { /* yielded closing variables */ + ci->u.l.savedpc--; /* repeat instruction to close other vars. */ + break; + } + case OP_RETURN: { /* yielded closing variables */ + StkId ra = base + GETARG_A(inst); + /* adjust top to signal correct number of returns, in case the + return is "up to top" ('isIT') */ + L->top.p = ra + ci->u2.nres; + /* repeat instruction to close other vars. and complete the return */ + ci->u.l.savedpc--; + break; + } + default: { + /* only these other opcodes can yield */ + lua_assert(op == OP_TFORCALL || op == OP_CALL || + op == OP_TAILCALL || op == OP_SETTABUP || op == OP_SETTABLE || + op == OP_SETI || op == OP_SETFIELD); + break; + } + } +} + + + + +/* +** {================================================================== +** Macros for arithmetic/bitwise/comparison opcodes in 'luaV_execute' +** =================================================================== +*/ + +#define l_addi(L,a,b) intop(+, a, b) +#define l_subi(L,a,b) intop(-, a, b) +#define l_muli(L,a,b) intop(*, a, b) +#define l_band(a,b) intop(&, a, b) +#define l_bor(a,b) intop(|, a, b) +#define l_bxor(a,b) intop(^, a, b) + +#define l_lti(a,b) (a < b) +#define l_lei(a,b) (a <= b) +#define l_gti(a,b) (a > b) +#define l_gei(a,b) (a >= b) + + +/* +** Arithmetic operations with immediate operands. 'iop' is the integer +** operation, 'fop' is the float operation. +*/ +#define op_arithI(L,iop,fop) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + int imm = GETARG_sC(i); \ + if (ttisinteger(v1)) { \ + lua_Integer iv1 = ivalue(v1); \ + pc++; setivalue(s2v(ra), iop(L, iv1, imm)); \ + } \ + else if (ttisfloat(v1)) { \ + lua_Number nb = fltvalue(v1); \ + lua_Number fimm = cast_num(imm); \ + pc++; setfltvalue(s2v(ra), fop(L, nb, fimm)); \ + }} + + +/* +** Auxiliary function for arithmetic operations over floats and others +** with two register operands. +*/ +#define op_arithf_aux(L,v1,v2,fop) { \ + lua_Number n1; lua_Number n2; \ + if (tonumberns(v1, n1) && tonumberns(v2, n2)) { \ + pc++; setfltvalue(s2v(ra), fop(L, n1, n2)); \ + }} + + +/* +** Arithmetic operations over floats and others with register operands. +*/ +#define op_arithf(L,fop) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations with K operands for floats. +*/ +#define op_arithfK(L,fop) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); lua_assert(ttisnumber(v2)); \ + op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations over integers and floats. +*/ +#define op_arith_aux(L,v1,v2,iop,fop) { \ + StkId ra = RA(i); \ + if (ttisinteger(v1) && ttisinteger(v2)) { \ + lua_Integer i1 = ivalue(v1); lua_Integer i2 = ivalue(v2); \ + pc++; setivalue(s2v(ra), iop(L, i1, i2)); \ + } \ + else op_arithf_aux(L, v1, v2, fop); } + + +/* +** Arithmetic operations with register operands. +*/ +#define op_arith(L,iop,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + op_arith_aux(L, v1, v2, iop, fop); } + + +/* +** Arithmetic operations with K operands. +*/ +#define op_arithK(L,iop,fop) { \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); lua_assert(ttisnumber(v2)); \ + op_arith_aux(L, v1, v2, iop, fop); } + + +/* +** Bitwise operations with constant operand. +*/ +#define op_bitwiseK(L,op) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = KC(i); \ + lua_Integer i1; \ + lua_Integer i2 = ivalue(v2); \ + if (tointegerns(v1, &i1)) { \ + pc++; setivalue(s2v(ra), op(i1, i2)); \ + }} + + +/* +** Bitwise operations with register operands. +*/ +#define op_bitwise(L,op) { \ + StkId ra = RA(i); \ + TValue *v1 = vRB(i); \ + TValue *v2 = vRC(i); \ + lua_Integer i1; lua_Integer i2; \ + if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) { \ + pc++; setivalue(s2v(ra), op(i1, i2)); \ + }} + + +/* +** Order operations with register operands. 'opn' actually works +** for all numbers, but the fast track improves performance for +** integers. +*/ +#define op_order(L,opi,opn,other) { \ + StkId ra = RA(i); \ + int cond; \ + TValue *rb = vRB(i); \ + if (ttisinteger(s2v(ra)) && ttisinteger(rb)) { \ + lua_Integer ia = ivalue(s2v(ra)); \ + lua_Integer ib = ivalue(rb); \ + cond = opi(ia, ib); \ + } \ + else if (ttisnumber(s2v(ra)) && ttisnumber(rb)) \ + cond = opn(s2v(ra), rb); \ + else \ + Protect(cond = other(L, s2v(ra), rb)); \ + docondjump(); } + + +/* +** Order operations with immediate operand. (Immediate operand is +** always small enough to have an exact representation as a float.) +*/ +#define op_orderI(L,opi,opf,inv,tm) { \ + StkId ra = RA(i); \ + int cond; \ + int im = GETARG_sB(i); \ + if (ttisinteger(s2v(ra))) \ + cond = opi(ivalue(s2v(ra)), im); \ + else if (ttisfloat(s2v(ra))) { \ + lua_Number fa = fltvalue(s2v(ra)); \ + lua_Number fim = cast_num(im); \ + cond = opf(fa, fim); \ + } \ + else { \ + int isf = GETARG_C(i); \ + Protect(cond = luaT_callorderiTM(L, s2v(ra), im, inv, isf, tm)); \ + } \ + docondjump(); } + +/* }================================================================== */ + + +/* +** {================================================================== +** Function 'luaV_execute': main interpreter loop +** =================================================================== +*/ + +/* +** some macros for common tasks in 'luaV_execute' +*/ + + +#define RA(i) (base+GETARG_A(i)) +#define RB(i) (base+GETARG_B(i)) +#define vRB(i) s2v(RB(i)) +#define KB(i) (k+GETARG_B(i)) +#define RC(i) (base+GETARG_C(i)) +#define vRC(i) s2v(RC(i)) +#define KC(i) (k+GETARG_C(i)) +#define RKC(i) ((TESTARG_k(i)) ? k + GETARG_C(i) : s2v(base + GETARG_C(i))) + + + +#define updatetrap(ci) (trap = ci->u.l.trap) + +#define updatebase(ci) (base = ci->func.p + 1) + + +#define updatestack(ci) \ + { if (l_unlikely(trap)) { updatebase(ci); ra = RA(i); } } + + +/* +** Execute a jump instruction. The 'updatetrap' allows signals to stop +** tight loops. (Without it, the local copy of 'trap' could never change.) +*/ +#define dojump(ci,i,e) { pc += GETARG_sJ(i) + e; updatetrap(ci); } + + +/* for test instructions, execute the jump instruction that follows it */ +#define donextjump(ci) { Instruction ni = *pc; dojump(ci, ni, 1); } + +/* +** do a conditional jump: skip next instruction if 'cond' is not what +** was expected (parameter 'k'), else do next instruction, which must +** be a jump. +*/ +#define docondjump() if (cond != GETARG_k(i)) pc++; else donextjump(ci); + + +/* +** Correct global 'pc'. +*/ +#define savepc(L) (ci->u.l.savedpc = pc) + + +/* +** Whenever code can raise errors, the global 'pc' and the global +** 'top' must be correct to report occasional errors. +*/ +#define savestate(L,ci) (savepc(L), L->top.p = ci->top.p) + + +/* +** Protect code that, in general, can raise errors, reallocate the +** stack, and change the hooks. +*/ +#define Protect(exp) (savestate(L,ci), (exp), updatetrap(ci)) + +/* special version that does not change the top */ +#define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci)) + +/* +** Protect code that can only raise errors. (That is, it cannot change +** the stack or hooks.) +*/ +#define halfProtect(exp) (savestate(L,ci), (exp)) + +/* 'c' is the limit of live values in the stack */ +#define checkGC(L,c) \ + { luaC_condGC(L, (savepc(L), L->top.p = (c)), \ + updatetrap(ci)); \ + luai_threadyield(L); } + + +/* fetch an instruction and prepare its execution */ +#define vmfetch() { \ + if (l_unlikely(trap)) { /* stack reallocation or hooks? */ \ + trap = luaG_traceexec(L, pc); /* handle hooks */ \ + updatebase(ci); /* correct stack */ \ + } \ + i = *(pc++); \ +} + +#define vmdispatch(o) switch(o) +#define vmcase(l) case l: +#define vmbreak break + + +void luaV_execute (lua_State *L, CallInfo *ci) { + LClosure *cl; + TValue *k; + StkId base; + const Instruction *pc; + int trap; +#if LUA_USE_JUMPTABLE +#include "ljumptab.h" +#endif + startfunc: + trap = L->hookmask; + returning: /* trap already set */ + cl = clLvalue(s2v(ci->func.p)); + k = cl->p->k; + pc = ci->u.l.savedpc; + if (l_unlikely(trap)) { + if (pc == cl->p->code) { /* first instruction (not resuming)? */ + if (cl->p->is_vararg) + trap = 0; /* hooks will start after VARARGPREP instruction */ + else /* check 'call' hook */ + luaD_hookcall(L, ci); + } + ci->u.l.trap = 1; /* assume trap is on, for now */ + } + base = ci->func.p + 1; + /* main loop of interpreter */ + for (;;) { + Instruction i; /* instruction being executed */ + vmfetch(); + #if 0 + /* low-level line tracing for debugging Lua */ + printf("line: %d\n", luaG_getfuncline(cl->p, pcRel(pc, cl->p))); + #endif + lua_assert(base == ci->func.p + 1); + lua_assert(base <= L->top.p && L->top.p <= L->stack_last.p); + /* invalidate top for instructions not expecting it */ + lua_assert(isIT(i) || (cast_void(L->top.p = base), 1)); + vmdispatch (GET_OPCODE(i)) { + vmcase(OP_MOVE) { + StkId ra = RA(i); + setobjs2s(L, ra, RB(i)); + vmbreak; + } + vmcase(OP_LOADI) { + StkId ra = RA(i); + lua_Integer b = GETARG_sBx(i); + setivalue(s2v(ra), b); + vmbreak; + } + vmcase(OP_LOADF) { + StkId ra = RA(i); + int b = GETARG_sBx(i); + setfltvalue(s2v(ra), cast_num(b)); + vmbreak; + } + vmcase(OP_LOADK) { + StkId ra = RA(i); + TValue *rb = k + GETARG_Bx(i); + setobj2s(L, ra, rb); + vmbreak; + } + vmcase(OP_LOADKX) { + StkId ra = RA(i); + TValue *rb; + rb = k + GETARG_Ax(*pc); pc++; + setobj2s(L, ra, rb); + vmbreak; + } + vmcase(OP_LOADFALSE) { + StkId ra = RA(i); + setbfvalue(s2v(ra)); + vmbreak; + } + vmcase(OP_LFALSESKIP) { + StkId ra = RA(i); + setbfvalue(s2v(ra)); + pc++; /* skip next instruction */ + vmbreak; + } + vmcase(OP_LOADTRUE) { + StkId ra = RA(i); + setbtvalue(s2v(ra)); + vmbreak; + } + vmcase(OP_LOADNIL) { + StkId ra = RA(i); + int b = GETARG_B(i); + do { + setnilvalue(s2v(ra++)); + } while (b--); + vmbreak; + } + vmcase(OP_GETUPVAL) { + StkId ra = RA(i); + int b = GETARG_B(i); + setobj2s(L, ra, cl->upvals[b]->v.p); + vmbreak; + } + vmcase(OP_SETUPVAL) { + StkId ra = RA(i); + UpVal *uv = cl->upvals[GETARG_B(i)]; + setobj(L, uv->v.p, s2v(ra)); + luaC_barrier(L, uv, s2v(ra)); + vmbreak; + } + vmcase(OP_GETTABUP) { + StkId ra = RA(i); + const TValue *slot; + TValue *upval = cl->upvals[GETARG_B(i)]->v.p; + TValue *rc = KC(i); + TString *key = tsvalue(rc); /* key must be a string */ + if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, upval, rc, ra, slot)); + vmbreak; + } + vmcase(OP_GETTABLE) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = vRC(i); + lua_Unsigned n; + if (ttisinteger(rc) /* fast track for integers? */ + ? (cast_void(n = ivalue(rc)), luaV_fastgeti(L, rb, n, slot)) + : luaV_fastget(L, rb, rc, slot, luaH_get)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); + vmbreak; + } + vmcase(OP_GETI) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + int c = GETARG_C(i); + if (luaV_fastgeti(L, rb, c, slot)) { + setobj2s(L, ra, slot); + } + else { + TValue key; + setivalue(&key, c); + Protect(luaV_finishget(L, rb, &key, ra, slot)); + } + vmbreak; + } + vmcase(OP_GETFIELD) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = KC(i); + TString *key = tsvalue(rc); /* key must be a string */ + if (luaV_fastget(L, rb, key, slot, luaH_getshortstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); + vmbreak; + } + vmcase(OP_SETTABUP) { + const TValue *slot; + TValue *upval = cl->upvals[GETARG_A(i)]->v.p; + TValue *rb = KB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rb); /* key must be a string */ + if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { + luaV_finishfastset(L, upval, slot, rc); + } + else + Protect(luaV_finishset(L, upval, rb, rc, slot)); + vmbreak; + } + vmcase(OP_SETTABLE) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); /* key (table is in 'ra') */ + TValue *rc = RKC(i); /* value */ + lua_Unsigned n; + if (ttisinteger(rb) /* fast track for integers? */ + ? (cast_void(n = ivalue(rb)), luaV_fastgeti(L, s2v(ra), n, slot)) + : luaV_fastget(L, s2v(ra), rb, slot, luaH_get)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else + Protect(luaV_finishset(L, s2v(ra), rb, rc, slot)); + vmbreak; + } + vmcase(OP_SETI) { + StkId ra = RA(i); + const TValue *slot; + int c = GETARG_B(i); + TValue *rc = RKC(i); + if (luaV_fastgeti(L, s2v(ra), c, slot)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else { + TValue key; + setivalue(&key, c); + Protect(luaV_finishset(L, s2v(ra), &key, rc, slot)); + } + vmbreak; + } + vmcase(OP_SETFIELD) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = KB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rb); /* key must be a string */ + if (luaV_fastget(L, s2v(ra), key, slot, luaH_getshortstr)) { + luaV_finishfastset(L, s2v(ra), slot, rc); + } + else + Protect(luaV_finishset(L, s2v(ra), rb, rc, slot)); + vmbreak; + } + vmcase(OP_NEWTABLE) { + StkId ra = RA(i); + int b = GETARG_B(i); /* log2(hash size) + 1 */ + int c = GETARG_C(i); /* array size */ + Table *t; + if (b > 0) + b = 1 << (b - 1); /* size is 2^(b - 1) */ + lua_assert((!TESTARG_k(i)) == (GETARG_Ax(*pc) == 0)); + if (TESTARG_k(i)) /* non-zero extra argument? */ + c += GETARG_Ax(*pc) * (MAXARG_C + 1); /* add it to size */ + pc++; /* skip extra argument */ + L->top.p = ra + 1; /* correct top in case of emergency GC */ + t = luaH_new(L); /* memory allocation */ + sethvalue2s(L, ra, t); + if (b != 0 || c != 0) + luaH_resize(L, t, c, b); /* idem */ + checkGC(L, ra + 1); + vmbreak; + } + vmcase(OP_SELF) { + StkId ra = RA(i); + const TValue *slot; + TValue *rb = vRB(i); + TValue *rc = RKC(i); + TString *key = tsvalue(rc); /* key must be a string */ + setobj2s(L, ra + 1, rb); + if (luaV_fastget(L, rb, key, slot, luaH_getstr)) { + setobj2s(L, ra, slot); + } + else + Protect(luaV_finishget(L, rb, rc, ra, slot)); + vmbreak; + } + vmcase(OP_ADDI) { + op_arithI(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_ADDK) { + op_arithK(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_SUBK) { + op_arithK(L, l_subi, luai_numsub); + vmbreak; + } + vmcase(OP_MULK) { + op_arithK(L, l_muli, luai_nummul); + vmbreak; + } + vmcase(OP_MODK) { + savestate(L, ci); /* in case of division by 0 */ + op_arithK(L, luaV_mod, luaV_modf); + vmbreak; + } + vmcase(OP_POWK) { + op_arithfK(L, luai_numpow); + vmbreak; + } + vmcase(OP_DIVK) { + op_arithfK(L, luai_numdiv); + vmbreak; + } + vmcase(OP_IDIVK) { + savestate(L, ci); /* in case of division by 0 */ + op_arithK(L, luaV_idiv, luai_numidiv); + vmbreak; + } + vmcase(OP_BANDK) { + op_bitwiseK(L, l_band); + vmbreak; + } + vmcase(OP_BORK) { + op_bitwiseK(L, l_bor); + vmbreak; + } + vmcase(OP_BXORK) { + op_bitwiseK(L, l_bxor); + vmbreak; + } + vmcase(OP_SHRI) { + StkId ra = RA(i); + TValue *rb = vRB(i); + int ic = GETARG_sC(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + pc++; setivalue(s2v(ra), luaV_shiftl(ib, -ic)); + } + vmbreak; + } + vmcase(OP_SHLI) { + StkId ra = RA(i); + TValue *rb = vRB(i); + int ic = GETARG_sC(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + pc++; setivalue(s2v(ra), luaV_shiftl(ic, ib)); + } + vmbreak; + } + vmcase(OP_ADD) { + op_arith(L, l_addi, luai_numadd); + vmbreak; + } + vmcase(OP_SUB) { + op_arith(L, l_subi, luai_numsub); + vmbreak; + } + vmcase(OP_MUL) { + op_arith(L, l_muli, luai_nummul); + vmbreak; + } + vmcase(OP_MOD) { + savestate(L, ci); /* in case of division by 0 */ + op_arith(L, luaV_mod, luaV_modf); + vmbreak; + } + vmcase(OP_POW) { + op_arithf(L, luai_numpow); + vmbreak; + } + vmcase(OP_DIV) { /* float division (always with floats) */ + op_arithf(L, luai_numdiv); + vmbreak; + } + vmcase(OP_IDIV) { /* floor division */ + savestate(L, ci); /* in case of division by 0 */ + op_arith(L, luaV_idiv, luai_numidiv); + vmbreak; + } + vmcase(OP_BAND) { + op_bitwise(L, l_band); + vmbreak; + } + vmcase(OP_BOR) { + op_bitwise(L, l_bor); + vmbreak; + } + vmcase(OP_BXOR) { + op_bitwise(L, l_bxor); + vmbreak; + } + vmcase(OP_SHR) { + op_bitwise(L, luaV_shiftr); + vmbreak; + } + vmcase(OP_SHL) { + op_bitwise(L, luaV_shiftl); + vmbreak; + } + vmcase(OP_MMBIN) { + StkId ra = RA(i); + Instruction pi = *(pc - 2); /* original arith. expression */ + TValue *rb = vRB(i); + TMS tm = (TMS)GETARG_C(i); + StkId result = RA(pi); + lua_assert(OP_ADD <= GET_OPCODE(pi) && GET_OPCODE(pi) <= OP_SHR); + Protect(luaT_trybinTM(L, s2v(ra), rb, result, tm)); + vmbreak; + } + vmcase(OP_MMBINI) { + StkId ra = RA(i); + Instruction pi = *(pc - 2); /* original arith. expression */ + int imm = GETARG_sB(i); + TMS tm = (TMS)GETARG_C(i); + int flip = GETARG_k(i); + StkId result = RA(pi); + Protect(luaT_trybiniTM(L, s2v(ra), imm, flip, result, tm)); + vmbreak; + } + vmcase(OP_MMBINK) { + StkId ra = RA(i); + Instruction pi = *(pc - 2); /* original arith. expression */ + TValue *imm = KB(i); + TMS tm = (TMS)GETARG_C(i); + int flip = GETARG_k(i); + StkId result = RA(pi); + Protect(luaT_trybinassocTM(L, s2v(ra), imm, flip, result, tm)); + vmbreak; + } + vmcase(OP_UNM) { + StkId ra = RA(i); + TValue *rb = vRB(i); + lua_Number nb; + if (ttisinteger(rb)) { + lua_Integer ib = ivalue(rb); + setivalue(s2v(ra), intop(-, 0, ib)); + } + else if (tonumberns(rb, nb)) { + setfltvalue(s2v(ra), luai_numunm(L, nb)); + } + else + Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM)); + vmbreak; + } + vmcase(OP_BNOT) { + StkId ra = RA(i); + TValue *rb = vRB(i); + lua_Integer ib; + if (tointegerns(rb, &ib)) { + setivalue(s2v(ra), intop(^, ~l_castS2U(0), ib)); + } + else + Protect(luaT_trybinTM(L, rb, rb, ra, TM_BNOT)); + vmbreak; + } + vmcase(OP_NOT) { + StkId ra = RA(i); + TValue *rb = vRB(i); + if (l_isfalse(rb)) + setbtvalue(s2v(ra)); + else + setbfvalue(s2v(ra)); + vmbreak; + } + vmcase(OP_LEN) { + StkId ra = RA(i); + Protect(luaV_objlen(L, ra, vRB(i))); + vmbreak; + } + vmcase(OP_CONCAT) { + StkId ra = RA(i); + int n = GETARG_B(i); /* number of elements to concatenate */ + L->top.p = ra + n; /* mark the end of concat operands */ + ProtectNT(luaV_concat(L, n)); + checkGC(L, L->top.p); /* 'luaV_concat' ensures correct top */ + vmbreak; + } + vmcase(OP_CLOSE) { + StkId ra = RA(i); + Protect(luaF_close(L, ra, LUA_OK, 1)); + vmbreak; + } + vmcase(OP_TBC) { + StkId ra = RA(i); + /* create new to-be-closed upvalue */ + halfProtect(luaF_newtbcupval(L, ra)); + vmbreak; + } + vmcase(OP_JMP) { + dojump(ci, i, 0); + vmbreak; + } + vmcase(OP_EQ) { + StkId ra = RA(i); + int cond; + TValue *rb = vRB(i); + Protect(cond = luaV_equalobj(L, s2v(ra), rb)); + docondjump(); + vmbreak; + } + vmcase(OP_LT) { + op_order(L, l_lti, LTnum, lessthanothers); + vmbreak; + } + vmcase(OP_LE) { + op_order(L, l_lei, LEnum, lessequalothers); + vmbreak; + } + vmcase(OP_EQK) { + StkId ra = RA(i); + TValue *rb = KB(i); + /* basic types do not use '__eq'; we can use raw equality */ + int cond = luaV_rawequalobj(s2v(ra), rb); + docondjump(); + vmbreak; + } + vmcase(OP_EQI) { + StkId ra = RA(i); + int cond; + int im = GETARG_sB(i); + if (ttisinteger(s2v(ra))) + cond = (ivalue(s2v(ra)) == im); + else if (ttisfloat(s2v(ra))) + cond = luai_numeq(fltvalue(s2v(ra)), cast_num(im)); + else + cond = 0; /* other types cannot be equal to a number */ + docondjump(); + vmbreak; + } + vmcase(OP_LTI) { + op_orderI(L, l_lti, luai_numlt, 0, TM_LT); + vmbreak; + } + vmcase(OP_LEI) { + op_orderI(L, l_lei, luai_numle, 0, TM_LE); + vmbreak; + } + vmcase(OP_GTI) { + op_orderI(L, l_gti, luai_numgt, 1, TM_LT); + vmbreak; + } + vmcase(OP_GEI) { + op_orderI(L, l_gei, luai_numge, 1, TM_LE); + vmbreak; + } + vmcase(OP_TEST) { + StkId ra = RA(i); + int cond = !l_isfalse(s2v(ra)); + docondjump(); + vmbreak; + } + vmcase(OP_TESTSET) { + StkId ra = RA(i); + TValue *rb = vRB(i); + if (l_isfalse(rb) == GETARG_k(i)) + pc++; + else { + setobj2s(L, ra, rb); + donextjump(ci); + } + vmbreak; + } + vmcase(OP_CALL) { + StkId ra = RA(i); + CallInfo *newci; + int b = GETARG_B(i); + int nresults = GETARG_C(i) - 1; + if (b != 0) /* fixed number of arguments? */ + L->top.p = ra + b; /* top signals number of arguments */ + /* else previous instruction set top */ + savepc(L); /* in case of errors */ + if ((newci = luaD_precall(L, ra, nresults)) == NULL) + updatetrap(ci); /* C call; nothing else to be done */ + else { /* Lua call: run function in this same C frame */ + ci = newci; + goto startfunc; + } + vmbreak; + } + vmcase(OP_TAILCALL) { + StkId ra = RA(i); + int b = GETARG_B(i); /* number of arguments + 1 (function) */ + int n; /* number of results when calling a C function */ + int nparams1 = GETARG_C(i); + /* delta is virtual 'func' - real 'func' (vararg functions) */ + int delta = (nparams1) ? ci->u.l.nextraargs + nparams1 : 0; + if (b != 0) + L->top.p = ra + b; + else /* previous instruction set top */ + b = cast_int(L->top.p - ra); + savepc(ci); /* several calls here can raise errors */ + if (TESTARG_k(i)) { + luaF_closeupval(L, base); /* close upvalues from current call */ + lua_assert(L->tbclist.p < base); /* no pending tbc variables */ + lua_assert(base == ci->func.p + 1); + } + if ((n = luaD_pretailcall(L, ci, ra, b, delta)) < 0) /* Lua function? */ + goto startfunc; /* execute the callee */ + else { /* C function? */ + ci->func.p -= delta; /* restore 'func' (if vararg) */ + luaD_poscall(L, ci, n); /* finish caller */ + updatetrap(ci); /* 'luaD_poscall' can change hooks */ + goto ret; /* caller returns after the tail call */ + } + } + vmcase(OP_RETURN) { + StkId ra = RA(i); + int n = GETARG_B(i) - 1; /* number of results */ + int nparams1 = GETARG_C(i); + if (n < 0) /* not fixed? */ + n = cast_int(L->top.p - ra); /* get what is available */ + savepc(ci); + if (TESTARG_k(i)) { /* may there be open upvalues? */ + ci->u2.nres = n; /* save number of returns */ + if (L->top.p < ci->top.p) + L->top.p = ci->top.p; + luaF_close(L, base, CLOSEKTOP, 1); + updatetrap(ci); + updatestack(ci); + } + if (nparams1) /* vararg function? */ + ci->func.p -= ci->u.l.nextraargs + nparams1; + L->top.p = ra + n; /* set call for 'luaD_poscall' */ + luaD_poscall(L, ci, n); + updatetrap(ci); /* 'luaD_poscall' can change hooks */ + goto ret; + } + vmcase(OP_RETURN0) { + if (l_unlikely(L->hookmask)) { + StkId ra = RA(i); + L->top.p = ra; + savepc(ci); + luaD_poscall(L, ci, 0); /* no hurry... */ + trap = 1; + } + else { /* do the 'poscall' here */ + int nres; + L->ci = ci->previous; /* back to caller */ + L->top.p = base - 1; + for (nres = ci->nresults; l_unlikely(nres > 0); nres--) + setnilvalue(s2v(L->top.p++)); /* all results are nil */ + } + goto ret; + } + vmcase(OP_RETURN1) { + if (l_unlikely(L->hookmask)) { + StkId ra = RA(i); + L->top.p = ra + 1; + savepc(ci); + luaD_poscall(L, ci, 1); /* no hurry... */ + trap = 1; + } + else { /* do the 'poscall' here */ + int nres = ci->nresults; + L->ci = ci->previous; /* back to caller */ + if (nres == 0) + L->top.p = base - 1; /* asked for no results */ + else { + StkId ra = RA(i); + setobjs2s(L, base - 1, ra); /* at least this result */ + L->top.p = base; + for (; l_unlikely(nres > 1); nres--) + setnilvalue(s2v(L->top.p++)); /* complete missing results */ + } + } + ret: /* return from a Lua function */ + if (ci->callstatus & CIST_FRESH) + return; /* end this frame */ + else { + ci = ci->previous; + goto returning; /* continue running caller in this frame */ + } + } + vmcase(OP_FORLOOP) { + StkId ra = RA(i); + if (ttisinteger(s2v(ra + 2))) { /* integer loop? */ + lua_Unsigned count = l_castS2U(ivalue(s2v(ra + 1))); + if (count > 0) { /* still more iterations? */ + lua_Integer step = ivalue(s2v(ra + 2)); + lua_Integer idx = ivalue(s2v(ra)); /* internal index */ + chgivalue(s2v(ra + 1), count - 1); /* update counter */ + idx = intop(+, idx, step); /* add step to index */ + chgivalue(s2v(ra), idx); /* update internal index */ + setivalue(s2v(ra + 3), idx); /* and control variable */ + pc -= GETARG_Bx(i); /* jump back */ + } + } + else if (floatforloop(ra)) /* float loop */ + pc -= GETARG_Bx(i); /* jump back */ + updatetrap(ci); /* allows a signal to break the loop */ + vmbreak; + } + vmcase(OP_FORPREP) { + StkId ra = RA(i); + savestate(L, ci); /* in case of errors */ + if (forprep(L, ra)) + pc += GETARG_Bx(i) + 1; /* skip the loop */ + vmbreak; + } + vmcase(OP_TFORPREP) { + StkId ra = RA(i); + /* create to-be-closed upvalue (if needed) */ + halfProtect(luaF_newtbcupval(L, ra + 3)); + pc += GETARG_Bx(i); + i = *(pc++); /* go to next instruction */ + lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i)); + goto l_tforcall; + } + vmcase(OP_TFORCALL) { + l_tforcall: { + StkId ra = RA(i); + /* 'ra' has the iterator function, 'ra + 1' has the state, + 'ra + 2' has the control variable, and 'ra + 3' has the + to-be-closed variable. The call will use the stack after + these values (starting at 'ra + 4') + */ + /* push function, state, and control variable */ + memcpy(ra + 4, ra, 3 * sizeof(*ra)); + L->top.p = ra + 4 + 3; + ProtectNT(luaD_call(L, ra + 4, GETARG_C(i))); /* do the call */ + updatestack(ci); /* stack may have changed */ + i = *(pc++); /* go to next instruction */ + lua_assert(GET_OPCODE(i) == OP_TFORLOOP && ra == RA(i)); + goto l_tforloop; + }} + vmcase(OP_TFORLOOP) { + l_tforloop: { + StkId ra = RA(i); + if (!ttisnil(s2v(ra + 4))) { /* continue loop? */ + setobjs2s(L, ra + 2, ra + 4); /* save control variable */ + pc -= GETARG_Bx(i); /* jump back */ + } + vmbreak; + }} + vmcase(OP_SETLIST) { + StkId ra = RA(i); + int n = GETARG_B(i); + unsigned int last = GETARG_C(i); + Table *h = hvalue(s2v(ra)); + if (n == 0) + n = cast_int(L->top.p - ra) - 1; /* get up to the top */ + else + L->top.p = ci->top.p; /* correct top in case of emergency GC */ + last += n; + if (TESTARG_k(i)) { + last += GETARG_Ax(*pc) * (MAXARG_C + 1); + pc++; + } + if (last > luaH_realasize(h)) /* needs more space? */ + luaH_resizearray(L, h, last); /* preallocate it at once */ + for (; n > 0; n--) { + TValue *val = s2v(ra + n); + setobj2t(L, &h->array[last - 1], val); + last--; + luaC_barrierback(L, obj2gco(h), val); + } + vmbreak; + } + vmcase(OP_CLOSURE) { + StkId ra = RA(i); + Proto *p = cl->p->p[GETARG_Bx(i)]; + halfProtect(pushclosure(L, p, cl->upvals, base, ra)); + checkGC(L, ra + 1); + vmbreak; + } + vmcase(OP_VARARG) { + StkId ra = RA(i); + int n = GETARG_C(i) - 1; /* required results */ + Protect(luaT_getvarargs(L, ci, ra, n)); + vmbreak; + } + vmcase(OP_VARARGPREP) { + ProtectNT(luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p)); + if (l_unlikely(trap)) { /* previous "Protect" updated trap */ + luaD_hookcall(L, ci); + L->oldpc = 1; /* next opcode will be seen as a "new" line */ + } + updatebase(ci); /* function has new base after adjustment */ + vmbreak; + } + vmcase(OP_EXTRAARG) { + lua_assert(0); + vmbreak; + } + } + } +} + +/* }================================================================== */ diff --git a/arm9/source/lua/lvm.h b/arm9/source/lua/lvm.h new file mode 100644 index 000000000..dba1ad277 --- /dev/null +++ b/arm9/source/lua/lvm.h @@ -0,0 +1,141 @@ +/* +** $Id: lvm.h $ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lvm_h +#define lvm_h + + +#include "ldo.h" +#include "lobject.h" +#include "ltm.h" + + +#if !defined(LUA_NOCVTN2S) +#define cvt2str(o) ttisnumber(o) +#else +#define cvt2str(o) 0 /* no conversion from numbers to strings */ +#endif + + +#if !defined(LUA_NOCVTS2N) +#define cvt2num(o) ttisstring(o) +#else +#define cvt2num(o) 0 /* no conversion from strings to numbers */ +#endif + + +/* +** You can define LUA_FLOORN2I if you want to convert floats to integers +** by flooring them (instead of raising an error if they are not +** integral values) +*/ +#if !defined(LUA_FLOORN2I) +#define LUA_FLOORN2I F2Ieq +#endif + + +/* +** Rounding modes for float->integer coercion + */ +typedef enum { + F2Ieq, /* no rounding; accepts only integral values */ + F2Ifloor, /* takes the floor of the number */ + F2Iceil /* takes the ceil of the number */ +} F2Imod; + + +/* convert an object to a float (including string coercion) */ +#define tonumber(o,n) \ + (ttisfloat(o) ? (*(n) = fltvalue(o), 1) : luaV_tonumber_(o,n)) + + +/* convert an object to a float (without string coercion) */ +#define tonumberns(o,n) \ + (ttisfloat(o) ? ((n) = fltvalue(o), 1) : \ + (ttisinteger(o) ? ((n) = cast_num(ivalue(o)), 1) : 0)) + + +/* convert an object to an integer (including string coercion) */ +#define tointeger(o,i) \ + (l_likely(ttisinteger(o)) ? (*(i) = ivalue(o), 1) \ + : luaV_tointeger(o,i,LUA_FLOORN2I)) + + +/* convert an object to an integer (without string coercion) */ +#define tointegerns(o,i) \ + (l_likely(ttisinteger(o)) ? (*(i) = ivalue(o), 1) \ + : luaV_tointegerns(o,i,LUA_FLOORN2I)) + + +#define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2)) + +#define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) + + +/* +** fast track for 'gettable': if 't' is a table and 't[k]' is present, +** return 1 with 'slot' pointing to 't[k]' (position of final result). +** Otherwise, return 0 (meaning it will have to check metamethod) +** with 'slot' pointing to an empty 't[k]' (if 't' is a table) or NULL +** (otherwise). 'f' is the raw get function to use. +*/ +#define luaV_fastget(L,t,k,slot,f) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = f(hvalue(t), k), /* else, do raw access */ \ + !isempty(slot))) /* result not empty? */ + + +/* +** Special case of 'luaV_fastget' for integers, inlining the fast case +** of 'luaH_getint'. +*/ +#define luaV_fastgeti(L,t,k,slot) \ + (!ttistable(t) \ + ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ + : (slot = (l_castS2U(k) - 1u < hvalue(t)->alimit) \ + ? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \ + !isempty(slot))) /* result not empty? */ + + +/* +** Finish a fast set operation (when fast get succeeds). In that case, +** 'slot' points to the place to put the value. +*/ +#define luaV_finishfastset(L,t,slot,v) \ + { setobj2t(L, cast(TValue *,slot), v); \ + luaC_barrierback(L, gcvalue(t), v); } + + +/* +** Shift right is the same as shift left with a negative 'y' +*/ +#define luaV_shiftr(x,y) luaV_shiftl(x,intop(-, 0, y)) + + + +LUAI_FUNC int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2); +LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); +LUAI_FUNC int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r); +LUAI_FUNC int luaV_tonumber_ (const TValue *obj, lua_Number *n); +LUAI_FUNC int luaV_tointeger (const TValue *obj, lua_Integer *p, F2Imod mode); +LUAI_FUNC int luaV_tointegerns (const TValue *obj, lua_Integer *p, + F2Imod mode); +LUAI_FUNC int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode); +LUAI_FUNC void luaV_finishget (lua_State *L, const TValue *t, TValue *key, + StkId val, const TValue *slot); +LUAI_FUNC void luaV_finishset (lua_State *L, const TValue *t, TValue *key, + TValue *val, const TValue *slot); +LUAI_FUNC void luaV_finishOp (lua_State *L); +LUAI_FUNC void luaV_execute (lua_State *L, CallInfo *ci); +LUAI_FUNC void luaV_concat (lua_State *L, int total); +LUAI_FUNC lua_Integer luaV_idiv (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y); +LUAI_FUNC lua_Number luaV_modf (lua_State *L, lua_Number x, lua_Number y); +LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y); +LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb); + +#endif diff --git a/arm9/source/lua/lzio.c b/arm9/source/lua/lzio.c new file mode 100644 index 000000000..cd0a02d5f --- /dev/null +++ b/arm9/source/lua/lzio.c @@ -0,0 +1,68 @@ +/* +** $Id: lzio.c $ +** Buffered streams +** See Copyright Notice in lua.h +*/ + +#define lzio_c +#define LUA_CORE + +#include "lprefix.h" + + +#include + +#include "lua.h" + +#include "llimits.h" +#include "lmem.h" +#include "lstate.h" +#include "lzio.h" + + +int luaZ_fill (ZIO *z) { + size_t size; + lua_State *L = z->L; + const char *buff; + lua_unlock(L); + buff = z->reader(L, z->data, &size); + lua_lock(L); + if (buff == NULL || size == 0) + return EOZ; + z->n = size - 1; /* discount char being returned */ + z->p = buff; + return cast_uchar(*(z->p++)); +} + + +void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) { + z->L = L; + z->reader = reader; + z->data = data; + z->n = 0; + z->p = NULL; +} + + +/* --------------------------------------------------------------- read --- */ +size_t luaZ_read (ZIO *z, void *b, size_t n) { + while (n) { + size_t m; + if (z->n == 0) { /* no bytes in buffer? */ + if (luaZ_fill(z) == EOZ) /* try to read more */ + return n; /* no more input; return number of missing bytes */ + else { + z->n++; /* luaZ_fill consumed first byte; put it back */ + z->p--; + } + } + m = (n <= z->n) ? n : z->n; /* min. between n and z->n */ + memcpy(b, z->p, m); + z->n -= m; + z->p += m; + b = (char *)b + m; + n -= m; + } + return 0; +} + diff --git a/arm9/source/lua/lzio.h b/arm9/source/lua/lzio.h new file mode 100644 index 000000000..38f397fd2 --- /dev/null +++ b/arm9/source/lua/lzio.h @@ -0,0 +1,66 @@ +/* +** $Id: lzio.h $ +** Buffered streams +** See Copyright Notice in lua.h +*/ + + +#ifndef lzio_h +#define lzio_h + +#include "lua.h" + +#include "lmem.h" + + +#define EOZ (-1) /* end of stream */ + +typedef struct Zio ZIO; + +#define zgetc(z) (((z)->n--)>0 ? cast_uchar(*(z)->p++) : luaZ_fill(z)) + + +typedef struct Mbuffer { + char *buffer; + size_t n; + size_t buffsize; +} Mbuffer; + +#define luaZ_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) + +#define luaZ_buffer(buff) ((buff)->buffer) +#define luaZ_sizebuffer(buff) ((buff)->buffsize) +#define luaZ_bufflen(buff) ((buff)->n) + +#define luaZ_buffremove(buff,i) ((buff)->n -= (i)) +#define luaZ_resetbuffer(buff) ((buff)->n = 0) + + +#define luaZ_resizebuffer(L, buff, size) \ + ((buff)->buffer = luaM_reallocvchar(L, (buff)->buffer, \ + (buff)->buffsize, size), \ + (buff)->buffsize = size) + +#define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) + + +LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, + void *data); +LUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n); /* read next n bytes */ + + + +/* --------- Private Part ------------------ */ + +struct Zio { + size_t n; /* bytes still unread */ + const char *p; /* current position in buffer */ + lua_Reader reader; /* reader function */ + void *data; /* additional data */ + lua_State *L; /* Lua state (for reader) */ +}; + + +LUAI_FUNC int luaZ_fill (ZIO *z); + +#endif diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index 41ebe07a1..fafbfcaff 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -18,6 +18,9 @@ #include "ips.h" #include "bps.h" #include "pxi.h" +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" #define _MAX_ARGS 4 @@ -127,7 +130,8 @@ typedef enum { CMD_ID_NEXTEMU, CMD_ID_REBOOT, CMD_ID_POWEROFF, - CMD_ID_BKPT + CMD_ID_BKPT, + CMD_ID_LUARUN } cmd_id; typedef struct { @@ -202,7 +206,8 @@ static const Gm9ScriptCmd cmd_list[] = { { CMD_ID_NEXTEMU , "nextemu" , 0, 0 }, { CMD_ID_REBOOT , "reboot" , 0, 0 }, { CMD_ID_POWEROFF, "poweroff", 0, 0 }, - { CMD_ID_BKPT , "bkpt" , 0, 0 } + { CMD_ID_BKPT , "bkpt" , 0, 0 }, + { CMD_ID_LUARUN , "luarun" , 1, 0 } }; // global vars for preview @@ -859,6 +864,14 @@ bool parse_line(const char* line_start, const char* line_end, cmd_id* cmdid, u32 return false; } +int Thingy(lua_State *L) { + const char* mystr = luaL_checkstring(L, 1); + set_var("PREVIEW_MODE", mystr); + set_preview("PREVIEW_MODE", mystr); + ShowPrompt(false, "thingy called: %s\n", mystr); + return 0; +} + bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { bool ret = true; // true unless some cmd messes up @@ -1541,6 +1554,59 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { bkpt; while(1); } + else if (id == CMD_ID_LUARUN) { + luaL_Reg loadedlibs[] = { + {LUA_GNAME, luaopen_base}, + //{LUA_LOADLIBNAME, luaopen_package}, + //{LUA_COLIBNAME, luaopen_coroutine}, + //{LUA_TABLIBNAME, luaopen_table}, + //{LUA_IOLIBNAME, luaopen_io}, + //{LUA_OSLIBNAME, luaopen_os}, + //{LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + //{LUA_UTF8LIBNAME, luaopen_utf8}, + //{LUA_DBLIBNAME, luaopen_debug}, + {NULL, NULL} + }; + // this code is awful + ShowPrompt(false, "Make lua state"); + lua_State *L = luaL_newstate(); + // NOTE most libs will most likely not work right away because GM9 is a very weird environment + //luaL_openlibs(L); + + // this will require a bunch of other funcs defined + const luaL_Reg *lib; + /* "require" functions from 'loadedlibs' and set results to global table */ + for (lib = loadedlibs; lib->func; lib++) { + ShowPrompt(false, "Loading %s", lib->name); + luaL_requiref(L, lib->name, lib->func, 1); + lua_pop(L, 1); /* remove lib */ + } + + lua_pushcfunction(L, Thingy); + lua_setglobal(L, "thingy"); + + char* text = calloc(1, STD_BUFFER_SIZE); + if (!text) return false; + + ShowPrompt(false, "Reading data..."); + + size_t flen = FileGetData(argv[0], text, STD_BUFFER_SIZE - 1, 0); + + ShowPrompt(false, "Read %s size %zu", argv[0], flen); + + if (luaL_dostring(L, text) == LUA_OK) { + lua_pop(L, lua_gettop(L)); + ShowPrompt(false, "Success"); + } else { + ShowPrompt(false, "%s", lua_tostring(L, -1)); + } + + ShowPrompt(false, "Closing lua"); + + lua_close(L); + free(text); + } else { // command not recognized / bad number of arguments ret = false; if (err_str) snprintf(err_str, _ERR_STR_LEN, "%s", STR_SCRIPTERR_UNKNOWN_ERROR); diff --git a/init.lua b/init.lua new file mode 100755 index 000000000..573ae6afc --- /dev/null +++ b/init.lua @@ -0,0 +1,9 @@ +thingy(tostring("a")) +thingy(tostring(math)) +index = 1 +thingy(tostring(math.sin(3))) +thingy(tostring(3 ^ 2)) +for k, v in pairs(math) do + thingy("math func "..tostring(index).."/"..tostring(#math)..": \n- "..tostring(k).."\n "..tostring(v)) + index = index + 1 +end diff --git a/lua.gm9 b/lua.gm9 new file mode 100755 index 000000000..a667473c2 --- /dev/null +++ b/lua.gm9 @@ -0,0 +1 @@ +luarun 0:/init.lua From 9905b939b26aae3422c906c7858d8852764fa279 Mon Sep 17 00:00:00 2001 From: luigoalma Date: Sun, 23 Jul 2023 12:21:30 +0100 Subject: [PATCH 002/124] Trust that lua knows what its doing with this Silence warnings --- arm9/source/lua/ldo.h | 2 +- arm9/source/lua/lgc.c | 2 +- arm9/source/lua/llimits.h | 5 +++-- arm9/source/lua/lstate.c | 2 +- arm9/source/lua/lstate.h | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/arm9/source/lua/ldo.h b/arm9/source/lua/ldo.h index 1aa446ad0..b61cd1494 100644 --- a/arm9/source/lua/ldo.h +++ b/arm9/source/lua/ldo.h @@ -34,7 +34,7 @@ #define savestack(L,pt) (cast_charp(pt) - cast_charp(L->stack.p)) -#define restorestack(L,n) cast(StkId, cast_charp(L->stack.p) + (n)) +#define restorestack(L,n) castp(StkId, cast_charp(L->stack.p) + (n)) /* macro to check stack size, preserving 'p' */ diff --git a/arm9/source/lua/lgc.c b/arm9/source/lua/lgc.c index a3094ff57..a1d09d2d4 100644 --- a/arm9/source/lua/lgc.c +++ b/arm9/source/lua/lgc.c @@ -258,7 +258,7 @@ void luaC_fix (lua_State *L, GCObject *o) { GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz, size_t offset) { global_State *g = G(L); char *p = cast_charp(luaM_newobject(L, novariant(tt), sz)); - GCObject *o = cast(GCObject *, p + offset); + GCObject *o = castp(GCObject *, p + offset); o->marked = luaC_white(g); o->tt = tt; o->next = g->allgc; diff --git a/arm9/source/lua/llimits.h b/arm9/source/lua/llimits.h index 1c826f7be..d0575ea26 100644 --- a/arm9/source/lua/llimits.h +++ b/arm9/source/lua/llimits.h @@ -133,7 +133,8 @@ typedef LUAI_UACINT l_uacInt; /* type casts (a macro highlights casts in the code) */ -#define cast(t, exp) ((t)(exp)) +#define cast(t, exp) ((t)(exp)) +#define castp(t, exp) ((t)(void *)(exp)) #define cast_void(i) cast(void, (i)) #define cast_voidp(i) cast(void *, (i)) @@ -143,7 +144,7 @@ typedef LUAI_UACINT l_uacInt; #define cast_byte(i) cast(lu_byte, (i)) #define cast_uchar(i) cast(unsigned char, (i)) #define cast_char(i) cast(char, (i)) -#define cast_charp(i) cast(char *, (i)) +#define cast_charp(i) castp(char *, (i)) #define cast_sizet(i) cast(size_t, (i)) diff --git a/arm9/source/lua/lstate.c b/arm9/source/lua/lstate.c index 1e925e5ad..cf8d1bc0f 100644 --- a/arm9/source/lua/lstate.c +++ b/arm9/source/lua/lstate.c @@ -48,7 +48,7 @@ typedef struct LG { -#define fromstate(L) (cast(LX *, cast(lu_byte *, (L)) - offsetof(LX, l))) +#define fromstate(L) (castp(LX *, cast(lu_byte *, (L)) - offsetof(LX, l))) /* diff --git a/arm9/source/lua/lstate.h b/arm9/source/lua/lstate.h index 8bf6600e3..6a8a20233 100644 --- a/arm9/source/lua/lstate.h +++ b/arm9/source/lua/lstate.h @@ -367,7 +367,7 @@ union GCUnion { ** "A pointer to a union object, suitably converted, points to each of ** its members [...], and vice versa." */ -#define cast_u(o) cast(union GCUnion *, (o)) +#define cast_u(o) castp(union GCUnion *, (o)) /* macros to convert a GCObject into a specific value */ #define gco2ts(o) \ From d4d2a08fd0adc74b1e9b99a102a3617b62ad5236 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 04:24:22 -0700 Subject: [PATCH 003/124] actually update top screen when Thingy is called, disable unnecessary ShowPrompt calls --- arm9/source/utils/scripting.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index fafbfcaff..df8c40a3b 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -866,8 +866,10 @@ bool parse_line(const char* line_start, const char* line_end, cmd_id* cmdid, u32 int Thingy(lua_State *L) { const char* mystr = luaL_checkstring(L, 1); - set_var("PREVIEW_MODE", mystr); - set_preview("PREVIEW_MODE", mystr); + // so this would normally be done when a loop in ExecuteGM9Script continues, + // but because of how i made this luarun command, i need to manually update the top screen + ClearScreen(TOP_SCREEN, COLOR_STD_BG); + DrawStringCenter(TOP_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", mystr); ShowPrompt(false, "thingy called: %s\n", mystr); return 0; } @@ -1569,7 +1571,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { {NULL, NULL} }; // this code is awful - ShowPrompt(false, "Make lua state"); + //ShowPrompt(false, "Make lua state"); lua_State *L = luaL_newstate(); // NOTE most libs will most likely not work right away because GM9 is a very weird environment //luaL_openlibs(L); @@ -1578,7 +1580,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { const luaL_Reg *lib; /* "require" functions from 'loadedlibs' and set results to global table */ for (lib = loadedlibs; lib->func; lib++) { - ShowPrompt(false, "Loading %s", lib->name); + //ShowPrompt(false, "Loading %s", lib->name); luaL_requiref(L, lib->name, lib->func, 1); lua_pop(L, 1); /* remove lib */ } @@ -1589,20 +1591,20 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { char* text = calloc(1, STD_BUFFER_SIZE); if (!text) return false; - ShowPrompt(false, "Reading data..."); + //ShowPrompt(false, "Reading data..."); size_t flen = FileGetData(argv[0], text, STD_BUFFER_SIZE - 1, 0); - ShowPrompt(false, "Read %s size %zu", argv[0], flen); + //ShowPrompt(false, "Read %s size %zu", argv[0], flen); if (luaL_dostring(L, text) == LUA_OK) { lua_pop(L, lua_gettop(L)); - ShowPrompt(false, "Success"); + //ShowPrompt(false, "Success"); } else { ShowPrompt(false, "%s", lua_tostring(L, -1)); } - ShowPrompt(false, "Closing lua"); + //ShowPrompt(false, "Closing lua"); lua_close(L); free(text); From a7597d0951e857a3dac387902d7d9a6b81f7e460 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 04:24:33 -0700 Subject: [PATCH 004/124] readme --- README.md | 210 ++---------------------------------------------------- 1 file changed, 4 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index ea9462e7f..1928d0ffd 100644 --- a/README.md +++ b/README.md @@ -1,209 +1,7 @@ -# ![GodMode9](https://github.com/d0k3/GodMode9/blob/master/resources/logo.png) -_A full access file browser for the 3DS console_ :godmode: +# GodMode9 but with lua -GodMode9 is a full access file browser for the Nintendo 3DS console, giving you access to your SD card, to the FAT partitions inside your SysNAND and EmuNAND and to basically anything else. Among other functionality (see below), you can copy, delete, rename files and create folders. +A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings +copy init.lua to the root and then run "run.gm9" to use it -## Warning -__This is powerful stuff__, it provides you with the means to do basically any thinkable modification to any system data available on the 3DS console. However, precautions are taken so you don't accidentally damage the data of your console. The write permissions system protects you by providing warnings and forces you to enter an unlock sequence for enabling write permissions. It is not possible to overwrite or modify any important stuff without such unlock sequences and it is not possible to accidentally unlock something. - -__As always, be smart, keep backups, just to be safe__. - - -## Quick start guide -The recommended bootloader for use with GodMode9 is [fastboot3DS](https://github.com/derrekr/fastboot3DS). There are [known issues for some users](https://github.com/d0k3/GodMode9/issues/466) when using the standard setup based on [boot9strap](https://github.com/SciresM/boot9strap) and [Luma3DS](https://github.com/AuroraWright/Luma3DS). If you insist on using that setup follow the instructions found in a [certain guide](https://3ds.hacks.guide). Here's how to set up GodMode9 (and fastboot3DS) up quickly: -* Download [OpenFirmInstaller](https://github.com/d0k3/OpenFirmInstaller/releases/tag/v0.0.9) and follow the quick setup instructions found there. -* Copy the `gm9` folder from the release archive to your SD card. Then, get good versions of `seeddb.bin` and `encTitleKeys.bin` from somewhere (don't ask me!) and put these two files into `sd:/gm9/support` (optional but recommended for full functionality). -* It is also recommended you setup the RTC clock if you're running GodMode9 for the first time. Find the option via HOME button -> `More...`. Also keep in mind that you should fix your system OS clock afterwards. While you're in the `More...` menu, you may also set screen brightness to a fixed value of your choosing and manually calibrate the touch screen (*not recommended* - try the automatic configuration first). -* Helpful hint #1: Go [here](https://3ds.hacks.guide/godmode9-usage) for step by steps on doing some common tasks in GodMode9. Especially users coming from Decrypt9WIP or Hourglass9 may find this to be helpful. -* Helpful hint #2: __Never unlock the red write permission level unless you know exactly what you're doing__. You will notice that prompt when it comes up, it features a completely red screen. It is recommended you stay on the yellow permission level or below at all times to be completely safe. Also read more on the write permissions system below. - -You may now run GodMode9 via holding the X Button (or any other button you chose) at startup. See below for a list of stuff you can do with it. - - -## Buttons in GodMode9 -GodMode9 is designed to be intuitive, buttons leading to the results you'd expect. However, some stuff may not be obvious at first glance. So, here's a quick, incomplete rundown of what each button / button combo does. -* __\ button__: The \ button is the 'confirm' / 'choose' button. It confirms prompts and selects entries in menus. In the main file view, it pulls up a submenu for files and opens directories (use \ on directories for a submenu, also including the invaluable title search). In the hexviewer, \ switches into edit mode. -* __\ button__: The \ button is the 'cancel' / 'return' button. Use it to leave menus without action, hold it on file operations to cancel said file operations. -* __\ button__: In the main file view, the \ button deletes (marked) files. With \ files are renamed. -* __\ button__: In the main file view, the \ button copies and pastes files. With \ you can create folders and dummy files. -* __\ button__: The \ button is the 'mark' button. Use it with \ / \ to mark / unmark all files in a folder, hold it and use \ / \ to select multiple files. -* __\ button__: The \ button is the 'switch' button. It switches buttons to their secondary function. Notable exceptions are \ for a screenshot (works almost anywhere), \ / \ to switch panes and \ to reload the file listing. -* __\ button__: Use the \ button to reboot from GodMode9. Use \ to poweroff your 3DS. -* __\ button clears or restores the clipboard (depending on if it's empty or not). -* __\ button__: The \ button enters the HOME menu, including the scripts / payloads submenus, options for formatting the SD, setting the RTC, and more. The \ button is an alternative way of entering the HOME menu. -* __\ combo__: This little known keycombo, when held at startup, pauses the GodMode9 boot so that you can stare at the splash screen for a little longer. -* __\ combo__: If you have installed GodMode9 as your bootloader, this keycombo enters the bootmenu. Hold on startup! If you built GodMode9 as SALTMODE and have it as a bootloader, the keycombo is simply the \ button. - - -## How to build this / developer info -Build `GodMode9.firm` via `make firm`. This requires [firmtool](https://github.com/TuxSH/firmtool), [Python 3.5+](https://www.python.org/downloads/) and [devkitARM](https://sourceforge.net/projects/devkitpro/) installed). - -You may run `make release` to get a nice, release-ready package of all required files. To build __SafeMode9__ (a bricksafe variant of GodMode9, with limited write permissions) instead of GodMode9, compile with `make FLAVOR=SafeMode9`. To switch screens, compile with `make SWITCH_SCREENS=1`. For additional customization, you may choose the internal font by replacing `font_default.frf` inside the `data` directory. You may also hardcode the brightness via `make FIXED_BRIGHTNESS=x`, whereas `x` is a value between 0...15. - -Further customization is possible by hardcoding `aeskeydb.bin` (just put the file into the `data` folder when compiling). All files put into the `data` folder will turn up in the `V:` drive, but keep in mind there's a hard 223.5KiB limit for all files inside, including overhead. A standalone script runner is compiled by providing `autorun.gm9` (again, in the `data` folder) and building with `make SCRIPT_RUNNER=1`. There's more possibility for customization, read the Makefiles to learn more. - -To build a .firm signed with SPI boot keys (for ntrboot and the like), run `make NTRBOOT=1`. You may need to rename the output files if the ntrboot installer you use uses hardcoded filenames. Some features such as boot9 / boot11 access are not currently available from the ntrboot environment. - - -## Bootloader mode -Same as [boot9strap](https://github.com/SciresM/boot9strap) or [fastboot3ds](https://github.com/derrekr/fastboot3DS), GodMode9 can be installed to the system FIRM partition ('FIRM0'). When executed from a FIRM partition, GodMode9 will default to bootloader mode and try to boot, in order, `FIRM from FCRAM` (see [A9NC](https://github.com/d0k3/A9NC/releases)), `0:/bootonce.firm` (will be deleted on a successful boot), `0:/boot.firm`, `1:/boot.firm`. In bootloader mode, hold R+LEFT on boot to enter the boot menu. *Installing GodMode9 to a FIRM partition is only recommended for developers and will overwrite [boot9strap](https://github.com/SciresM/boot9strap) or any other bootloader you have installed in there*. - - -## Write permissions system -GodMode9 provides a write permissions system, which will protect you from accidentally damaging your system, losing data and/or modifying important system data. To unlock a write permission, an unlock sequence must be entered. This is not possible by accident. The write permission system is based on colors and the top bar on the top screen will change color according to the current write permission level. No permission above the yellow level can be unlocked on SafeMode9. -* __Green:__ Modification to system files is not possible on this permission level. You can't edit or delete savegames and installed data. However, keep in mind that any non-system related or custom stuff on your SD card is not protected. -* __Yellow:__ You can modify system files on this permission level. Data that is unique to your console and cannot be gotten from anywhere else is still not modifiable. Any damages you introduce can be fixed in software, but loss of savegames and installed data is possible if you are not careful. __A NAND backup is highly recommended starting at this level.__ -* __Orange:__ This is similar to the yellow permission level, but, in addition, allows edits to console unique data. Any damages you introduce are still fixable in software, but if you play around with this, __having a NAND backup is an absolute requirement__. -* __Red:__ The highest regular permission level. There are no limits to system file edits, and if you are not careful enough, you can brick your console and/or remove your A9LH/B9S installation. Bricks on this level may only be fixable in hardware. __If you don't have a NAND backup at this point, you seem to have a deathwish for your console__. -* __Blue:__ This permission level is reserved for edits to system memory. While, most likely, nothing bad at all will happen, consequences of edits can be unforeseen. There is even a (albeit very small) chance of bricking your console, maybe even permanently. __Tread with caution on this level__. - - -## Support files -For certain functionality, GodMode9 may need 'support files'. Support files should be placed into either `0:/gm9/support` or `1:/gm9/support`. Support files contain additional information that is required in decryption operations. A list of support files, and what they do, is found below. Please don't ask for support files - find them yourself. -* __`aeskeydb.bin`__: This should contain 0x25keyX, 0x18keyX and 0x1BkeyX to enable decryption of 7x / Secure3 / Secure4 encrypted NCCH files, 0x11key95 / 0x11key96 for FIRM decrypt support and 0x11keyOTP / 0x11keyIVOTP for 'secret' sector 0x96 crypto support. Entrypoints other than [boot9strap](https://github.com/SciresM/boot9strap) or [fastboot3ds](https://github.com/derrekr/fastboot3DS) may require a aeskeydb.bin file. This is now included in standard releases of GM9. No need to hunt down the file! -* __`seeddb.bin`__: This file is optional and required to decrypt and mount seed-encrypted NCCHs and CIAs (if the seed in question is not installed to your NAND). Note that your seeddb.bin must also contain the seed for the specific game you need to decrypt. -* __`encTitleKeys.bin`__ / __`decTitleKeys.bin`__: These files are optional and provide titlekeys, which are required to decrypt and install contents downloaded from CDN (for DSi and 3DS content). - -### Fonts and translations -GodMode9 also supports custom fonts and translations as support files. These both use custom formats, fonts use FRF (Font RIFF) files which can be created using the `fontriff.py` Python script in the 'utils' folder. Translations use TRF (Translation RIFF) files from the `transriff.py` script. Examples of the inputs to these scripts can be found in the 'fonts' and 'languages' folders of the 'resources' folder respectively. - -TRF files can be placed in `0:/gm9/languages` to show in the language menu accessible from the HOME menu and shown on first load. Official translations are provided from the community via the [GodMode9 Crowdin](https://crowdin.com/project/GodMode9). Languages can use a special font by having an FRF with the same name, for example `en.trf` and `en.frf`. - -## Drives in GodMode9 -GodMode9 provides access to system data via drives, a listing of what each drive contains and additional info follows below. Some of these drives are removable (such as drive `7:`), some will only turn up if they are available (drive `8:` and everything associated with EmuNAND, f.e.). Information on the 3DS console file system is also found on [3Dbrew.org](https://3dbrew.org/wiki/Flash_Filesystem). -* __`0: SDCARD`__: The SD card currently inserted into the SD card slot. The `0:/Nintendo 3DS` folder contains software installs and extdata and is specially protected via the write permission system. The SD card can be unmounted from the root directory via the R+B buttons, otherwise the SD card is always available. -* __`1: SYSNAND CTRNAND`__: The CTRNAND partition on SysNAND. This contains your 3DS console's operating system and system software installs. Data in here is protected by the write permissions system. -* __`2: SYSNAND TWLN`__: The TWLN partition on SysNAND. This contains your 3DS console's TWL mode operating system and system software installs. Data in here is protected by the write permissions system. -* __`3: SYSNAND TWLP`__: The TWLP partition on SysNAND. This contains photos taken while in TWL mode. -* __`A: SYSNAND SD`__: This drive is used for special access to data on your SD card. It actually links to a subfolder inside `0:/Nintendo 3DS` and contains software and extdata installed to SD from SysNAND. Crypto in this folder is handled only when accessed via the `A:` drive (not from `0:`). This is protected by the write permissions system. -* __`S: SYSNAND VIRTUAL`__: This drive provides access to all partitions of the SysNAND, some of them critical for base system functionality. This is protected by the write permissions system, but, when unlocked, modifications can brick the system. -* __`4: EMUNAND CTRNAND`__: Same as `1:`, but handles the CTRNAND on EmuNAND. For multi EmuNAND setups, the currently active EmuNAND partition can be switched via the HOME menu. -* __`5: EMUNAND TWLN`__: Same as `2`, but handles TWLN on EmuNAND. No write protection here, cause this partition is never used on EmuNAND. -* __`6: EMUNAND TWLP`__: Same as `3`, but handles TWLP on EmuNAND. -* __`B: EMUNAND SD`__: Same as `A:`, but handles the `0:/Nintendo 3DS` subfolder associated with EmuNAND. In case of linked NANDs, this is identical with `A:`. This is also protected by the write permissions system. -* __`E: EMUNAND VIRTUAL`__: Same as `S:`, but handles system partitions on EmuNAND. No bricking risk here as EmuNAND is never critical to system functionality. -* __`7: FAT IMAGE / IMGNAND CTRNAND`__: This provides access to mounted FAT images. When a NAND image is mounted, it provides access to the mounted NAND image's CTRNAND. -* __`8: BONUS DRIVE / IMGNAND TWLN`__: This provides access to the bonus drive on SysNAND. The bonus drive can be setup via the HOME menu on 3DS consoles that provide the space for it. When a NAND image is mounted, this provides access to the mounted NAND image's TWLN. -* __`9: RAM DRIVE / IMGNAND TWLP`__: This provides access to the RAM drive. All data stored inside the RAM drive is temporary and will be wiped after a reboot. When a NAND image is mounted, this provides access to the mounted NAND image's TWLP. -* __`I: IMGNAND VIRTUAL`__: When a NAND image is mounted, this provides access to the partitions inside the NAND image. -* __`C: GAMECART`__: This is read-only and provides access to the game cartridge currently inserted into the cart slot. This can be used for dumps of CTR and TWL mode cartridges. Flash cards are supported only to a limited extent. -* __`G: GAME IMAGE`__: CIA/NCSD/NCCH/EXEFS/ROMFS/FIRM images can be accessed via this drive when mounted. This is read-only. -* __`K: AESKEYDB IMAGE`__: An `aeskeydb.bin` image can be mounted and accessed via this drive. The drive shows all keys inside the aeskeydb.bin. This is read-only. -* __`T: TICKET.DB IMAGE / BDRI IMAGE`__: Ticket database files can be mounted and accessed via this drive. This provides easy and quick access to all tickets inside the `ticket.db`. This drive also provides access to other BDRI images, such as the Title database (`title.db`). -* __`M: MEMORY VIRTUAL`__: This provides access to various memory regions. This is protected by a special write permission, and caution is advised when doing modifications inside this drive. This drive also gives access to `boot9.bin`, `boot11.bin` (boot9strap only) and `otp.mem` (sighaxed systems only). -* __`V: VRAM VIRTUAL`__: This drive resides in part of ARM9 internal memory and contains files essential to GodMode9. The font (in FRF format), the splash logo (in PNG format) and the readme file are found there, as well as any file that is provided inside the `data` folder at build time. This is read-only. -* __`Y: TITLE MANAGER`__: The title manager is accessed via the HOME menu and provides easy access to all installed titles. -* __`Z: LAST SEARCH`__: After a search operation, search results are found inside this drive. The drive can be accessed at a later point to return to the former search results. - - -## Digital preservation -GodMode9 is one of the most important tools for digital preservation of 3DS content data. Here's some stuff you should know: -* __Dumping game cartridges (size < 4GiB)__: Game cartridges turn up inside the `C:` drive (see above). For most carts all you need to do is copy the `.3DS` game image to some place of your choice. Game images dumped by GodMode9 contain no identifying info such as private headers or savegames. Private headers can be dumped in a separate image. -* __Dumping game cartridges (size = 4GiB)__: Everything written above applies here as well. However, the FAT32 file system (which is what the 3DS uses) is limited to _4GiB - 1byte_. Take note that the `.3DS` game image, as provided by GodMode9 actually misses the last byte in these cases. That byte is 0xFF and unused in all known cases. It is not required for playing the image. If you need to check, we also provide split files (`.000`, `.001)`, which contain all the data. If you need a valid checksum for the `.3DS` game image, append a 0xFF byte before checking. -* __Building CIAs (all types)__: You may convert compatible file types (game images, installed content, CDN content) to the CIA installable format using the A button menu. To get a list of installed content, press HOME, select `Title manager` and choose a drive. Take note that `standard` built CIAs are decrypted by default (decryption allows better compression by ZIP and 7Z). If you should need an encrypted CIA for some reason, apply the encryption to the CIA afterwards. -* __Building CIAs (legit type)__: Installed content can be built as `legit` or `standard` CIA. Legit CIAs preserve more of the original data and are thus recommended for preservation purposes. When building legit CIAs, GodMode9 keeps the original crypto and tries to find a genuine, signature-valid ticket. If it doesn't find one on your system, it will use a generic ticket instead. If it only finds a personalized one, it still offers to use a generic ticket. It is not recommended to use personalized tickets - only choose this if you know what you're doing. -* __Checking CIAs__: You may also check your CIA files with the builtin `CIA checker tool`. Legit CIAs with generic tickets are identified as `Universal Pirate Legit`, which is the recommended preservation format where `Universal Legit` is not available. Note: apart from system titles, `Universal Legit` is only available for a handful of preinstalled games from special edition 3DS consoles. - - -## What you can do with GodMode9 -With the possibilites GodMode9 provides, not everything may be obvious at first glance. In short, __GodMode9 includes improved versions of basically everything that Decrypt9 has, and more__. Any kind of dumps and injections are handled via standard copy operations and more specific operations are found inside the A button menu. The A button menu also works for batch operations when multiple files are selected. For your convenience a (incomplete!) list of what GodMode9 can do follows below. - -### Basic functionality -* __Manage files on all your data storages__: You wouldn't have expected this, right? Included are all standard file operations such as copy, delete, rename files and create folders. Use the L button to mark multiple files and apply file operations to multiple files at once. -* __Make screenshots__: Press R+L anywhere. Screenshots are stored in PNG format. -* __Use multiple panes__: Press R+left|right. This enables you to stay in one location in the first pane and open another in the second pane. -* __Search drives and folders__: Just press R+A on the drive / folder you want to search. -* __Compare and verify files__: Press the A button on the first file, select `Calculate SHA-256`. Do the same for the second file. If the two files are identical, you will get a message about them being identical. On the SDCARD drive (`0:`) you can also write an SHA file, so you can check for any modifications at a later point. -* __Hexview and hexedit any file__: Press the A button on a file and select `Show in Hexeditor`. A button again enables edit mode, hold the A button and press arrow buttons to edit bytes. You will get an additional confirmation prompt to take over changes. Take note that for certain files, write permissions can't be enabled. -* __View text files in a text viewer__: Press the A button on a file and select `Show in Textviewer` (only shows up for actual text files). You can enable wordwrapped mode via R+Y, and navigate around the file via R+X and the dpad. -* __Chainload FIRM payloads__: Press the A button on a FIRM file, select `FIRM options` -> `Boot FIRM`. Keep in mind you should not run FIRMs from dubious sources and that the write permissions system is no longer in place after booting a payload. -* __Chainload FIRM payloads from a neat menu__: The `payloads` menu is found inside the HOME button menu. It provides any FIRM found in `0:/gm9/payloads` for quick chainloading. -* __Inject a file to another file__: Put exactly one file (the file to be injected from) into the clipboard (via the Y button). Press A on the file to be injected to. There will be an option to inject the first file into it. - -### Scripting functionality -* __Run .gm9 scripts from anywhere on your SD card__: You can run scripts in .gm9 format via the A button menu. .gm9 scripts use a shell-like language and can be edited in any text editor. For an overview of usable commands have a look into the sample scripts included in the release archive. *Don't run scripts from untrusted sources.* -* __Run .gm9 scripts via a neat menu__: Press the HOME button, select `More...` -> `Scripts...`. Any script you put into `0:/gm9/scripts` (subdirs included) will be found here. Scripts ran via this method won't have the confirmation at the beginning either. - -### SD card handling -* __Format your SD card / setup an EmuNAND__: Press the HOME button, select `More...` -> `SD format menu`. This also allows to setup a RedNAND (single/multi) or GW type EmuNAND on your SD card. You will get a warning prompt and an unlock sequence before any operation starts. -* __Handle multiple EmuNANDs__: Press the HOME button, select `More...` -> `Switch EmuNAND` to switch between EmuNANDs / RedNANDs. (Only available on multi EmuNAND / RedNAND systems.) -* __Run it without an SD card / unmount the SD card__: If no SD card is found, you will be offered to run without the SD card. You can also unmount and remount your SD card from the file system root at any point. -* __Direct access to SD installed contents__: Just take a look inside the `A:`/`B:` drives. On-the-fly-crypto is taken care for, you can access this the same as any other content. -* __Set (and use) the RTC clock__: For correct modification / creation dates in your file system, you need to setup the RTC clock first. Press the HOME Button and select `More...` to find the option. Keep in mind that modifying the RTC clock means you should also fix system OS time afterwards. - -### Game file handling -* __List titles installed on your system__: Press HOME and select `Title manager`. This will also work via R+A for `CTRNAND` and `A:`/`B:` drives. This will list all titles installed in the selected location. -* __Install titles to your system__: Just press A on any file you want installed and select `Install game image` from the submenu. Works with NCCH / NCSD / CIA / DSiWare SRLs / 3DS CDN TMDs / DSi CDN TMDs / NUS TMDs. -* __(Batch) Uninstall titles from your system__: Most easily done via the HOME menu `Title manager`. Just select one or more titles and find the option inside the `Manage title...` submenu. -* __Build CIAs from NCCH / NCSD (.3DS) / SRL / TMD__: Press A on the file you want converted and the option will be shown. Installed contents are found most easily via the HOME menu `Title manager`. Where applicable, you will also be able to generate legit CIAs. Note: this works also from a file search and title listing. -* __Dump CXIs / NDS from TMD (installed contents)__: This works the same as building CIAs, but dumps decrypted CXIs or NDS rom dumps instead. Note: this works also from a file search and title listing. -* __Decrypt, encrypt and verify NCCH / NCSD / CIA / BOSS / FIRM images__: Options are found inside the A button menu. You will be able to decrypt/encrypt to the standard output directory or (where applicable) in place. -* __Decrypt content downloaded from CDN / NUS__: Press A on the file you want decrypted. For this to work, you need at least a TMD file (`encTitlekeys.bin` / `decTitlekeys.bin` also required, see _Support files_ below) or a CETK file. Either keep the names provided by CDN / NUS, or rename the downloaded content to `(anything).nus` or `(anything).cdn` and the CETK to `(anything).cetk`. -* __Batch mode for the above operations__: Just select multiple files of the same type via the L button, then press the A button on one of the selected files. -* __Access any file inside NCCH / NCSD / CIA / FIRM / NDS images__: Just mount the file via the A button menu and browse to the file you want. For CDN / NUS content, prior decryption is required for full access. -* __Rename your NCCH / NCSD / CIA / NDS / GBA files to proper names__: Find this feature inside the A button menu. Proper names include title id, game name, product code and region. -* __Trim NCCH / NCSD / NDS / GBA / FIRM / NAND images__: This feature is found inside the A button menu. It allows you to trim excess data from supported file types. *Warning: Excess data may not be empty, bonus drives are stored there for NAND images, NCSD card2 images store savedata there, for FIRMs parts of the A9LH exploit may be stored there*. -* __Dump 3DS / NDS / DSi type retail game cartridges__: Insert the cartridge and take a look inside the `C:` drive. You may also dump private headers from 3DS game cartridges. The `C:` drive also gives you read/write access to the saves on the cartridges. Note: For 4GiB cartridges, the last byte is not included in the .3ds file dump. This is due to restrictrions of the FAT32 file system. - -### NAND handling -* __Directly mount and access NAND dumps or standard FAT images__: Just press the A button on these files to get the option. You can only mount NAND dumps from the same console. -* __Restore NAND dumps while keeping your A9LH / sighax installation intact__: Select `Restore SysNAND (safe)` from inside the A button menu for NAND dumps. -* __Restore / dump NAND partitions or even full NANDs__: Just take a look into the `S:` (or `E:`/ `I:`) drive. This is done the same as any other file operation. -* __Transfer CTRNAND images between systems__: Transfer the file located at `S:/ctrnand_full.bin` (or `E:`/ `I:`). On the receiving system, press A, select `CTRNAND Options...`, then `Transfer to NAND`. -* __Embed an essential backup right into a NAND dump__: This is available in the A button menu for NAND dumps. Essential backups contain NAND header, `movable.sed`, `LocalFriendCodeSeed_B`, `SecureInfo_A`, NAND CID and OTP. If your local SysNAND does not contain an embedded backup, you will be asked to do one at startup. To update the essential SysNAND backup at a later point in time, press A on `S:/nand.bin` and select `NAND image options...` -> `Update embedded backup`. -* __Install an AES key database to your NAND__: For `aeskeydb.bin` files the option is found in `aeskeydb.bin options` -> `Install aeskeydb.bin`. Only the recommended key database can be installed (see above). With an installed key database, it is possible to run the GodMode9 bootloader completely from NAND. -* __Install FIRM files to your NAND__: Found inside the A button menu for FIRM files, select `FIRM options` -> `Install FIRM`. __Use this with caution__ - installing an incompatible FIRM file will lead to a __brick__. The FIRMs signature will automagically be replaced with a sighax signature to ensure compatibility. -* __Actually use that extra NAND space__: You can set up a __bonus drive__ via the HOME menu, which will be available via drive letter `8:`. (Only available on systems that have the extra space.) -* __Fix certain problems on your NANDs__: You can fix CMACs for a whole drive (works on `A:`, `B:`, `S:` and `E:`) via an entry in the R+A button menu, or even restore borked NAND headers back to a functional state (inside the A button menu of borked NANDs and available for `S:/nand_hdr.bin`). Recommended only for advanced users! - -### System file handling -* __Check and fix CMACs (for any file that has them)__: The option will turn up in the A button menu if it is available for a given file (f.e. system savegames, `ticket.db`, etc...). This can also be done for multiple files at once if they are marked. -* __Mount ticket.db files and dump tickets__: Mount the file via the A button menu. Tickets are sorted into `eshop` (stuff from eshop), `system` (system tickets), `unknown` (typically empty) and `hidden` (hidden tickets, found via a deeper scan) categories. All tickets displayed are legit, fake tickets are ignored -* __Inject any NCCH CXI file into Health & Safety__: The option is found inside the A button menu for any NCCH CXI file. NCCH CXIs are found, f.e. inside of CIAs. Keep in mind there is a (system internal) size restriction on H&S injectable apps. -* __Inject and dump GBA VC saves__: Find the options to do this inside the A button menu for `agbsave.bin` in the `S:` drive. Keep in mind that you need to start the specific GBA game on your console before dumping / injecting the save. _To inject a save it needs to be in the clipboard_. -* __Dump a copy of boot9, boot11 & your OTP__: This works on sighax, via boot9strap only. These files are found inside the `M:` drive and can be copied from there to any other place. - -### Support file handling -* __Build `decTitleKeys.bin` / `encTitleKeys.bin` / `seeddb.bin`__: Press the HOME button, select `More...` -> `Build support files`. `decTitleKeys.bin` / `encTitleKeys.bin` can also be created / merged from tickets, `ticket.db` and merged from other `decTitleKeys.bin` / `encTitleKeys.bin` files via the A button menu. -* __Build, mount, decrypt and encrypt `aeskeydb.bin`__: AES key databases can be merged from other `aeskeydb.bin` or build from legacy `slot??Key?.bin` files. Just select one or more files, press A on one of them and then select `Build aeskeydb.bin`. Options for mounting, decrypting and encrypting are also found in the A button menu. - - -## License -You may use this under the terms of the GNU General Public License GPL v2 or under the terms of any later revisions of the GPL. Refer to the provided `LICENSE.txt` file for further information. - -## Contact info -You can chat directly with us via IRC @ #GodMode9 on [libera.chat](https://web.libera.chat/#GodMode9) or [Discord](https://discord.gg/BRcbvtFxX4)! - -## Credits -This tool would not have been possible without the help of numerous people. Thanks go to (in no particular order)... -* **Archshift**, for providing the base project infrastructure -* **Normmatt**, for sdmmc.c / sdmmc.h and gamecart code, and for being of great help on countless other occasions -* **Cha(N)**, **Kane49**, and all other FatFS contributors for [FatFS](http://elm-chan.org/fsw/ff/00index_e.html) -* **Wolfvak** for ARM11 code, FIRM binary launcher, exception handlers, PCX code, Makefile and for help on countless other occasions -* **SciresM** for helping me figure out RomFS and for boot9strap -* **SciresM**, **Myria**, **Normmatt**, **TuxSH** and **hedgeberg** for figuring out sighax and giving us access to bootrom -* **ihaveamac** for first developing the simple CIA generation method and for being of great help in porting it -* **wwylele** and **aspargas2** for documenting and implementing the DISA, DIFF, and BDRI formats -* **dratini0** for savefile management, based on [TWLSaveTool](https://github.com/TuxSH/TWLSaveTool/) -* **Pk11** for unicode and translation support -* **b1l1s** for helping me figure out A9LH compatibility -* **Gelex** and **AuroraWright** for helping me figure out various things -* **stuckpixel** for the new 6x10 font and help on various things -* **Al3x_10m** for help with countless hours of testing and useful advice -* **WinterMute** for helping me with his vast knowledge on everything gamecart related -* **profi200** for always useful advice and helpful hints on various things -* **windows-server-2003** for the initial implementation of if-else-goto in .gm9 scripts -* **Kazuma77** for pushing forward scripting, for testing and for always useful advice -* **TurdPooCharger** for being one of the most meticulous software testers around -* **JaySea**, **YodaDaCoda**, **liomajor**, **Supster131**, **imanoob**, **Kasher_CS** and countless others from freenode #Cakey and the GBAtemp forums for testing, feedback and helpful hints -* **Shadowhand** for being awesome and [hosting my nightlies](https://d0k3.secretalgorithm.com/) -* **Plailect** for putting his trust in my tools and recommending this in [The Guide](https://3ds.guide/) -* **SirNapkin1334** for testing, bug reports and for hosting the original GodMode9 Discord server -* **Lilith Valentine** for testing and helpful advice -* **Project Nayuki** for [qrcodegen](https://github.com/nayuki/QR-Code-generator) -* **Amazingmax fonts** for the Amazdoom font -* The fine folks on **the official GodMode9 IRC channel and Discord server** -* The fine folks on **freenode #Cakey** -* All **[3dbrew.org](https://www.3dbrew.org/wiki/Main_Page) editors** -* Everyone I possibly forgot, if you think you deserve to be mentioned, just contact us! +Before you think about using this: it doesn't even have an API or a proper way to run the code. This is currenetly ONLY a test to see if Lua can be used at all. From 107fb48241ae5cbf83a8f922dc66516c72f36f83 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 04:51:21 -0700 Subject: [PATCH 005/124] change init for a simple test, print error on top screen too --- arm9/source/utils/scripting.c | 6 ++++-- init.lua | 14 +++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index df8c40a3b..f3991dff0 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -1557,7 +1557,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { while(1); } else if (id == CMD_ID_LUARUN) { - luaL_Reg loadedlibs[] = { + luaL_Reg loadedlibs[] = { {LUA_GNAME, luaopen_base}, //{LUA_LOADLIBNAME, luaopen_package}, //{LUA_COLIBNAME, luaopen_coroutine}, @@ -1575,7 +1575,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { lua_State *L = luaL_newstate(); // NOTE most libs will most likely not work right away because GM9 is a very weird environment //luaL_openlibs(L); - + // this will require a bunch of other funcs defined const luaL_Reg *lib; /* "require" functions from 'loadedlibs' and set results to global table */ @@ -1601,6 +1601,8 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { lua_pop(L, lua_gettop(L)); //ShowPrompt(false, "Success"); } else { + ClearScreen(TOP_SCREEN, COLOR_STD_BG); + DrawStringCenter(TOP_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", lua_tostring(L, -1)); ShowPrompt(false, "%s", lua_tostring(L, -1)); } diff --git a/init.lua b/init.lua index 573ae6afc..f3f6462a6 100755 --- a/init.lua +++ b/init.lua @@ -1,9 +1,5 @@ -thingy(tostring("a")) -thingy(tostring(math)) -index = 1 -thingy(tostring(math.sin(3))) -thingy(tostring(3 ^ 2)) -for k, v in pairs(math) do - thingy("math func "..tostring(index).."/"..tostring(#math)..": \n- "..tostring(k).."\n "..tostring(v)) - index = index + 1 -end +thingy("A test of your reflexes!") +thingy("math.sin(3): "..tostring(math.sin(3))) +thingy("3 ^ 2: "..tostring(3 ^ 2)) + +-- this is about it... can't even do io yet From 201cbc40ee274506aad78588f87c1b9283cdee49 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 04:24:33 -0700 Subject: [PATCH 006/124] Readme --- README.md | 210 ++---------------------------------------------------- 1 file changed, 7 insertions(+), 203 deletions(-) diff --git a/README.md b/README.md index ea9462e7f..dc24f0796 100644 --- a/README.md +++ b/README.md @@ -1,209 +1,13 @@ -# ![GodMode9](https://github.com/d0k3/GodMode9/blob/master/resources/logo.png) -_A full access file browser for the 3DS console_ :godmode: +# GodMode9 but with lua -GodMode9 is a full access file browser for the Nintendo 3DS console, giving you access to your SD card, to the FAT partitions inside your SysNAND and EmuNAND and to basically anything else. Among other functionality (see below), you can copy, delete, rename files and create folders. +A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings +Makefile.build edited to support a LIBS variable on build -## Warning -__This is powerful stuff__, it provides you with the means to do basically any thinkable modification to any system data available on the 3DS console. However, precautions are taken so you don't accidentally damage the data of your console. The write permissions system protects you by providing warnings and forces you to enter an unlock sequence for enabling write permissions. It is not possible to overwrite or modify any important stuff without such unlock sequences and it is not possible to accidentally unlock something. +Math lib (-lm) now needed for lua -__As always, be smart, keep backups, just to be safe__. +link.ld on arm9 edited to include .ARM.exidx into .rodata, caused by lua's code. Limit of AHBWRAM max size was increased until address 0x80C0000, 744KiB since 0x8006000 +copy init.lua to the root and then run "run.gm9" to use it -## Quick start guide -The recommended bootloader for use with GodMode9 is [fastboot3DS](https://github.com/derrekr/fastboot3DS). There are [known issues for some users](https://github.com/d0k3/GodMode9/issues/466) when using the standard setup based on [boot9strap](https://github.com/SciresM/boot9strap) and [Luma3DS](https://github.com/AuroraWright/Luma3DS). If you insist on using that setup follow the instructions found in a [certain guide](https://3ds.hacks.guide). Here's how to set up GodMode9 (and fastboot3DS) up quickly: -* Download [OpenFirmInstaller](https://github.com/d0k3/OpenFirmInstaller/releases/tag/v0.0.9) and follow the quick setup instructions found there. -* Copy the `gm9` folder from the release archive to your SD card. Then, get good versions of `seeddb.bin` and `encTitleKeys.bin` from somewhere (don't ask me!) and put these two files into `sd:/gm9/support` (optional but recommended for full functionality). -* It is also recommended you setup the RTC clock if you're running GodMode9 for the first time. Find the option via HOME button -> `More...`. Also keep in mind that you should fix your system OS clock afterwards. While you're in the `More...` menu, you may also set screen brightness to a fixed value of your choosing and manually calibrate the touch screen (*not recommended* - try the automatic configuration first). -* Helpful hint #1: Go [here](https://3ds.hacks.guide/godmode9-usage) for step by steps on doing some common tasks in GodMode9. Especially users coming from Decrypt9WIP or Hourglass9 may find this to be helpful. -* Helpful hint #2: __Never unlock the red write permission level unless you know exactly what you're doing__. You will notice that prompt when it comes up, it features a completely red screen. It is recommended you stay on the yellow permission level or below at all times to be completely safe. Also read more on the write permissions system below. - -You may now run GodMode9 via holding the X Button (or any other button you chose) at startup. See below for a list of stuff you can do with it. - - -## Buttons in GodMode9 -GodMode9 is designed to be intuitive, buttons leading to the results you'd expect. However, some stuff may not be obvious at first glance. So, here's a quick, incomplete rundown of what each button / button combo does. -* __\ button__: The \ button is the 'confirm' / 'choose' button. It confirms prompts and selects entries in menus. In the main file view, it pulls up a submenu for files and opens directories (use \ on directories for a submenu, also including the invaluable title search). In the hexviewer, \ switches into edit mode. -* __\ button__: The \ button is the 'cancel' / 'return' button. Use it to leave menus without action, hold it on file operations to cancel said file operations. -* __\ button__: In the main file view, the \ button deletes (marked) files. With \ files are renamed. -* __\ button__: In the main file view, the \ button copies and pastes files. With \ you can create folders and dummy files. -* __\ button__: The \ button is the 'mark' button. Use it with \ / \ to mark / unmark all files in a folder, hold it and use \ / \ to select multiple files. -* __\ button__: The \ button is the 'switch' button. It switches buttons to their secondary function. Notable exceptions are \ for a screenshot (works almost anywhere), \ / \ to switch panes and \ to reload the file listing. -* __\ button__: Use the \ button to reboot from GodMode9. Use \ to poweroff your 3DS. -* __\ button clears or restores the clipboard (depending on if it's empty or not). -* __\ button__: The \ button enters the HOME menu, including the scripts / payloads submenus, options for formatting the SD, setting the RTC, and more. The \ button is an alternative way of entering the HOME menu. -* __\ combo__: This little known keycombo, when held at startup, pauses the GodMode9 boot so that you can stare at the splash screen for a little longer. -* __\ combo__: If you have installed GodMode9 as your bootloader, this keycombo enters the bootmenu. Hold on startup! If you built GodMode9 as SALTMODE and have it as a bootloader, the keycombo is simply the \ button. - - -## How to build this / developer info -Build `GodMode9.firm` via `make firm`. This requires [firmtool](https://github.com/TuxSH/firmtool), [Python 3.5+](https://www.python.org/downloads/) and [devkitARM](https://sourceforge.net/projects/devkitpro/) installed). - -You may run `make release` to get a nice, release-ready package of all required files. To build __SafeMode9__ (a bricksafe variant of GodMode9, with limited write permissions) instead of GodMode9, compile with `make FLAVOR=SafeMode9`. To switch screens, compile with `make SWITCH_SCREENS=1`. For additional customization, you may choose the internal font by replacing `font_default.frf` inside the `data` directory. You may also hardcode the brightness via `make FIXED_BRIGHTNESS=x`, whereas `x` is a value between 0...15. - -Further customization is possible by hardcoding `aeskeydb.bin` (just put the file into the `data` folder when compiling). All files put into the `data` folder will turn up in the `V:` drive, but keep in mind there's a hard 223.5KiB limit for all files inside, including overhead. A standalone script runner is compiled by providing `autorun.gm9` (again, in the `data` folder) and building with `make SCRIPT_RUNNER=1`. There's more possibility for customization, read the Makefiles to learn more. - -To build a .firm signed with SPI boot keys (for ntrboot and the like), run `make NTRBOOT=1`. You may need to rename the output files if the ntrboot installer you use uses hardcoded filenames. Some features such as boot9 / boot11 access are not currently available from the ntrboot environment. - - -## Bootloader mode -Same as [boot9strap](https://github.com/SciresM/boot9strap) or [fastboot3ds](https://github.com/derrekr/fastboot3DS), GodMode9 can be installed to the system FIRM partition ('FIRM0'). When executed from a FIRM partition, GodMode9 will default to bootloader mode and try to boot, in order, `FIRM from FCRAM` (see [A9NC](https://github.com/d0k3/A9NC/releases)), `0:/bootonce.firm` (will be deleted on a successful boot), `0:/boot.firm`, `1:/boot.firm`. In bootloader mode, hold R+LEFT on boot to enter the boot menu. *Installing GodMode9 to a FIRM partition is only recommended for developers and will overwrite [boot9strap](https://github.com/SciresM/boot9strap) or any other bootloader you have installed in there*. - - -## Write permissions system -GodMode9 provides a write permissions system, which will protect you from accidentally damaging your system, losing data and/or modifying important system data. To unlock a write permission, an unlock sequence must be entered. This is not possible by accident. The write permission system is based on colors and the top bar on the top screen will change color according to the current write permission level. No permission above the yellow level can be unlocked on SafeMode9. -* __Green:__ Modification to system files is not possible on this permission level. You can't edit or delete savegames and installed data. However, keep in mind that any non-system related or custom stuff on your SD card is not protected. -* __Yellow:__ You can modify system files on this permission level. Data that is unique to your console and cannot be gotten from anywhere else is still not modifiable. Any damages you introduce can be fixed in software, but loss of savegames and installed data is possible if you are not careful. __A NAND backup is highly recommended starting at this level.__ -* __Orange:__ This is similar to the yellow permission level, but, in addition, allows edits to console unique data. Any damages you introduce are still fixable in software, but if you play around with this, __having a NAND backup is an absolute requirement__. -* __Red:__ The highest regular permission level. There are no limits to system file edits, and if you are not careful enough, you can brick your console and/or remove your A9LH/B9S installation. Bricks on this level may only be fixable in hardware. __If you don't have a NAND backup at this point, you seem to have a deathwish for your console__. -* __Blue:__ This permission level is reserved for edits to system memory. While, most likely, nothing bad at all will happen, consequences of edits can be unforeseen. There is even a (albeit very small) chance of bricking your console, maybe even permanently. __Tread with caution on this level__. - - -## Support files -For certain functionality, GodMode9 may need 'support files'. Support files should be placed into either `0:/gm9/support` or `1:/gm9/support`. Support files contain additional information that is required in decryption operations. A list of support files, and what they do, is found below. Please don't ask for support files - find them yourself. -* __`aeskeydb.bin`__: This should contain 0x25keyX, 0x18keyX and 0x1BkeyX to enable decryption of 7x / Secure3 / Secure4 encrypted NCCH files, 0x11key95 / 0x11key96 for FIRM decrypt support and 0x11keyOTP / 0x11keyIVOTP for 'secret' sector 0x96 crypto support. Entrypoints other than [boot9strap](https://github.com/SciresM/boot9strap) or [fastboot3ds](https://github.com/derrekr/fastboot3DS) may require a aeskeydb.bin file. This is now included in standard releases of GM9. No need to hunt down the file! -* __`seeddb.bin`__: This file is optional and required to decrypt and mount seed-encrypted NCCHs and CIAs (if the seed in question is not installed to your NAND). Note that your seeddb.bin must also contain the seed for the specific game you need to decrypt. -* __`encTitleKeys.bin`__ / __`decTitleKeys.bin`__: These files are optional and provide titlekeys, which are required to decrypt and install contents downloaded from CDN (for DSi and 3DS content). - -### Fonts and translations -GodMode9 also supports custom fonts and translations as support files. These both use custom formats, fonts use FRF (Font RIFF) files which can be created using the `fontriff.py` Python script in the 'utils' folder. Translations use TRF (Translation RIFF) files from the `transriff.py` script. Examples of the inputs to these scripts can be found in the 'fonts' and 'languages' folders of the 'resources' folder respectively. - -TRF files can be placed in `0:/gm9/languages` to show in the language menu accessible from the HOME menu and shown on first load. Official translations are provided from the community via the [GodMode9 Crowdin](https://crowdin.com/project/GodMode9). Languages can use a special font by having an FRF with the same name, for example `en.trf` and `en.frf`. - -## Drives in GodMode9 -GodMode9 provides access to system data via drives, a listing of what each drive contains and additional info follows below. Some of these drives are removable (such as drive `7:`), some will only turn up if they are available (drive `8:` and everything associated with EmuNAND, f.e.). Information on the 3DS console file system is also found on [3Dbrew.org](https://3dbrew.org/wiki/Flash_Filesystem). -* __`0: SDCARD`__: The SD card currently inserted into the SD card slot. The `0:/Nintendo 3DS` folder contains software installs and extdata and is specially protected via the write permission system. The SD card can be unmounted from the root directory via the R+B buttons, otherwise the SD card is always available. -* __`1: SYSNAND CTRNAND`__: The CTRNAND partition on SysNAND. This contains your 3DS console's operating system and system software installs. Data in here is protected by the write permissions system. -* __`2: SYSNAND TWLN`__: The TWLN partition on SysNAND. This contains your 3DS console's TWL mode operating system and system software installs. Data in here is protected by the write permissions system. -* __`3: SYSNAND TWLP`__: The TWLP partition on SysNAND. This contains photos taken while in TWL mode. -* __`A: SYSNAND SD`__: This drive is used for special access to data on your SD card. It actually links to a subfolder inside `0:/Nintendo 3DS` and contains software and extdata installed to SD from SysNAND. Crypto in this folder is handled only when accessed via the `A:` drive (not from `0:`). This is protected by the write permissions system. -* __`S: SYSNAND VIRTUAL`__: This drive provides access to all partitions of the SysNAND, some of them critical for base system functionality. This is protected by the write permissions system, but, when unlocked, modifications can brick the system. -* __`4: EMUNAND CTRNAND`__: Same as `1:`, but handles the CTRNAND on EmuNAND. For multi EmuNAND setups, the currently active EmuNAND partition can be switched via the HOME menu. -* __`5: EMUNAND TWLN`__: Same as `2`, but handles TWLN on EmuNAND. No write protection here, cause this partition is never used on EmuNAND. -* __`6: EMUNAND TWLP`__: Same as `3`, but handles TWLP on EmuNAND. -* __`B: EMUNAND SD`__: Same as `A:`, but handles the `0:/Nintendo 3DS` subfolder associated with EmuNAND. In case of linked NANDs, this is identical with `A:`. This is also protected by the write permissions system. -* __`E: EMUNAND VIRTUAL`__: Same as `S:`, but handles system partitions on EmuNAND. No bricking risk here as EmuNAND is never critical to system functionality. -* __`7: FAT IMAGE / IMGNAND CTRNAND`__: This provides access to mounted FAT images. When a NAND image is mounted, it provides access to the mounted NAND image's CTRNAND. -* __`8: BONUS DRIVE / IMGNAND TWLN`__: This provides access to the bonus drive on SysNAND. The bonus drive can be setup via the HOME menu on 3DS consoles that provide the space for it. When a NAND image is mounted, this provides access to the mounted NAND image's TWLN. -* __`9: RAM DRIVE / IMGNAND TWLP`__: This provides access to the RAM drive. All data stored inside the RAM drive is temporary and will be wiped after a reboot. When a NAND image is mounted, this provides access to the mounted NAND image's TWLP. -* __`I: IMGNAND VIRTUAL`__: When a NAND image is mounted, this provides access to the partitions inside the NAND image. -* __`C: GAMECART`__: This is read-only and provides access to the game cartridge currently inserted into the cart slot. This can be used for dumps of CTR and TWL mode cartridges. Flash cards are supported only to a limited extent. -* __`G: GAME IMAGE`__: CIA/NCSD/NCCH/EXEFS/ROMFS/FIRM images can be accessed via this drive when mounted. This is read-only. -* __`K: AESKEYDB IMAGE`__: An `aeskeydb.bin` image can be mounted and accessed via this drive. The drive shows all keys inside the aeskeydb.bin. This is read-only. -* __`T: TICKET.DB IMAGE / BDRI IMAGE`__: Ticket database files can be mounted and accessed via this drive. This provides easy and quick access to all tickets inside the `ticket.db`. This drive also provides access to other BDRI images, such as the Title database (`title.db`). -* __`M: MEMORY VIRTUAL`__: This provides access to various memory regions. This is protected by a special write permission, and caution is advised when doing modifications inside this drive. This drive also gives access to `boot9.bin`, `boot11.bin` (boot9strap only) and `otp.mem` (sighaxed systems only). -* __`V: VRAM VIRTUAL`__: This drive resides in part of ARM9 internal memory and contains files essential to GodMode9. The font (in FRF format), the splash logo (in PNG format) and the readme file are found there, as well as any file that is provided inside the `data` folder at build time. This is read-only. -* __`Y: TITLE MANAGER`__: The title manager is accessed via the HOME menu and provides easy access to all installed titles. -* __`Z: LAST SEARCH`__: After a search operation, search results are found inside this drive. The drive can be accessed at a later point to return to the former search results. - - -## Digital preservation -GodMode9 is one of the most important tools for digital preservation of 3DS content data. Here's some stuff you should know: -* __Dumping game cartridges (size < 4GiB)__: Game cartridges turn up inside the `C:` drive (see above). For most carts all you need to do is copy the `.3DS` game image to some place of your choice. Game images dumped by GodMode9 contain no identifying info such as private headers or savegames. Private headers can be dumped in a separate image. -* __Dumping game cartridges (size = 4GiB)__: Everything written above applies here as well. However, the FAT32 file system (which is what the 3DS uses) is limited to _4GiB - 1byte_. Take note that the `.3DS` game image, as provided by GodMode9 actually misses the last byte in these cases. That byte is 0xFF and unused in all known cases. It is not required for playing the image. If you need to check, we also provide split files (`.000`, `.001)`, which contain all the data. If you need a valid checksum for the `.3DS` game image, append a 0xFF byte before checking. -* __Building CIAs (all types)__: You may convert compatible file types (game images, installed content, CDN content) to the CIA installable format using the A button menu. To get a list of installed content, press HOME, select `Title manager` and choose a drive. Take note that `standard` built CIAs are decrypted by default (decryption allows better compression by ZIP and 7Z). If you should need an encrypted CIA for some reason, apply the encryption to the CIA afterwards. -* __Building CIAs (legit type)__: Installed content can be built as `legit` or `standard` CIA. Legit CIAs preserve more of the original data and are thus recommended for preservation purposes. When building legit CIAs, GodMode9 keeps the original crypto and tries to find a genuine, signature-valid ticket. If it doesn't find one on your system, it will use a generic ticket instead. If it only finds a personalized one, it still offers to use a generic ticket. It is not recommended to use personalized tickets - only choose this if you know what you're doing. -* __Checking CIAs__: You may also check your CIA files with the builtin `CIA checker tool`. Legit CIAs with generic tickets are identified as `Universal Pirate Legit`, which is the recommended preservation format where `Universal Legit` is not available. Note: apart from system titles, `Universal Legit` is only available for a handful of preinstalled games from special edition 3DS consoles. - - -## What you can do with GodMode9 -With the possibilites GodMode9 provides, not everything may be obvious at first glance. In short, __GodMode9 includes improved versions of basically everything that Decrypt9 has, and more__. Any kind of dumps and injections are handled via standard copy operations and more specific operations are found inside the A button menu. The A button menu also works for batch operations when multiple files are selected. For your convenience a (incomplete!) list of what GodMode9 can do follows below. - -### Basic functionality -* __Manage files on all your data storages__: You wouldn't have expected this, right? Included are all standard file operations such as copy, delete, rename files and create folders. Use the L button to mark multiple files and apply file operations to multiple files at once. -* __Make screenshots__: Press R+L anywhere. Screenshots are stored in PNG format. -* __Use multiple panes__: Press R+left|right. This enables you to stay in one location in the first pane and open another in the second pane. -* __Search drives and folders__: Just press R+A on the drive / folder you want to search. -* __Compare and verify files__: Press the A button on the first file, select `Calculate SHA-256`. Do the same for the second file. If the two files are identical, you will get a message about them being identical. On the SDCARD drive (`0:`) you can also write an SHA file, so you can check for any modifications at a later point. -* __Hexview and hexedit any file__: Press the A button on a file and select `Show in Hexeditor`. A button again enables edit mode, hold the A button and press arrow buttons to edit bytes. You will get an additional confirmation prompt to take over changes. Take note that for certain files, write permissions can't be enabled. -* __View text files in a text viewer__: Press the A button on a file and select `Show in Textviewer` (only shows up for actual text files). You can enable wordwrapped mode via R+Y, and navigate around the file via R+X and the dpad. -* __Chainload FIRM payloads__: Press the A button on a FIRM file, select `FIRM options` -> `Boot FIRM`. Keep in mind you should not run FIRMs from dubious sources and that the write permissions system is no longer in place after booting a payload. -* __Chainload FIRM payloads from a neat menu__: The `payloads` menu is found inside the HOME button menu. It provides any FIRM found in `0:/gm9/payloads` for quick chainloading. -* __Inject a file to another file__: Put exactly one file (the file to be injected from) into the clipboard (via the Y button). Press A on the file to be injected to. There will be an option to inject the first file into it. - -### Scripting functionality -* __Run .gm9 scripts from anywhere on your SD card__: You can run scripts in .gm9 format via the A button menu. .gm9 scripts use a shell-like language and can be edited in any text editor. For an overview of usable commands have a look into the sample scripts included in the release archive. *Don't run scripts from untrusted sources.* -* __Run .gm9 scripts via a neat menu__: Press the HOME button, select `More...` -> `Scripts...`. Any script you put into `0:/gm9/scripts` (subdirs included) will be found here. Scripts ran via this method won't have the confirmation at the beginning either. - -### SD card handling -* __Format your SD card / setup an EmuNAND__: Press the HOME button, select `More...` -> `SD format menu`. This also allows to setup a RedNAND (single/multi) or GW type EmuNAND on your SD card. You will get a warning prompt and an unlock sequence before any operation starts. -* __Handle multiple EmuNANDs__: Press the HOME button, select `More...` -> `Switch EmuNAND` to switch between EmuNANDs / RedNANDs. (Only available on multi EmuNAND / RedNAND systems.) -* __Run it without an SD card / unmount the SD card__: If no SD card is found, you will be offered to run without the SD card. You can also unmount and remount your SD card from the file system root at any point. -* __Direct access to SD installed contents__: Just take a look inside the `A:`/`B:` drives. On-the-fly-crypto is taken care for, you can access this the same as any other content. -* __Set (and use) the RTC clock__: For correct modification / creation dates in your file system, you need to setup the RTC clock first. Press the HOME Button and select `More...` to find the option. Keep in mind that modifying the RTC clock means you should also fix system OS time afterwards. - -### Game file handling -* __List titles installed on your system__: Press HOME and select `Title manager`. This will also work via R+A for `CTRNAND` and `A:`/`B:` drives. This will list all titles installed in the selected location. -* __Install titles to your system__: Just press A on any file you want installed and select `Install game image` from the submenu. Works with NCCH / NCSD / CIA / DSiWare SRLs / 3DS CDN TMDs / DSi CDN TMDs / NUS TMDs. -* __(Batch) Uninstall titles from your system__: Most easily done via the HOME menu `Title manager`. Just select one or more titles and find the option inside the `Manage title...` submenu. -* __Build CIAs from NCCH / NCSD (.3DS) / SRL / TMD__: Press A on the file you want converted and the option will be shown. Installed contents are found most easily via the HOME menu `Title manager`. Where applicable, you will also be able to generate legit CIAs. Note: this works also from a file search and title listing. -* __Dump CXIs / NDS from TMD (installed contents)__: This works the same as building CIAs, but dumps decrypted CXIs or NDS rom dumps instead. Note: this works also from a file search and title listing. -* __Decrypt, encrypt and verify NCCH / NCSD / CIA / BOSS / FIRM images__: Options are found inside the A button menu. You will be able to decrypt/encrypt to the standard output directory or (where applicable) in place. -* __Decrypt content downloaded from CDN / NUS__: Press A on the file you want decrypted. For this to work, you need at least a TMD file (`encTitlekeys.bin` / `decTitlekeys.bin` also required, see _Support files_ below) or a CETK file. Either keep the names provided by CDN / NUS, or rename the downloaded content to `(anything).nus` or `(anything).cdn` and the CETK to `(anything).cetk`. -* __Batch mode for the above operations__: Just select multiple files of the same type via the L button, then press the A button on one of the selected files. -* __Access any file inside NCCH / NCSD / CIA / FIRM / NDS images__: Just mount the file via the A button menu and browse to the file you want. For CDN / NUS content, prior decryption is required for full access. -* __Rename your NCCH / NCSD / CIA / NDS / GBA files to proper names__: Find this feature inside the A button menu. Proper names include title id, game name, product code and region. -* __Trim NCCH / NCSD / NDS / GBA / FIRM / NAND images__: This feature is found inside the A button menu. It allows you to trim excess data from supported file types. *Warning: Excess data may not be empty, bonus drives are stored there for NAND images, NCSD card2 images store savedata there, for FIRMs parts of the A9LH exploit may be stored there*. -* __Dump 3DS / NDS / DSi type retail game cartridges__: Insert the cartridge and take a look inside the `C:` drive. You may also dump private headers from 3DS game cartridges. The `C:` drive also gives you read/write access to the saves on the cartridges. Note: For 4GiB cartridges, the last byte is not included in the .3ds file dump. This is due to restrictrions of the FAT32 file system. - -### NAND handling -* __Directly mount and access NAND dumps or standard FAT images__: Just press the A button on these files to get the option. You can only mount NAND dumps from the same console. -* __Restore NAND dumps while keeping your A9LH / sighax installation intact__: Select `Restore SysNAND (safe)` from inside the A button menu for NAND dumps. -* __Restore / dump NAND partitions or even full NANDs__: Just take a look into the `S:` (or `E:`/ `I:`) drive. This is done the same as any other file operation. -* __Transfer CTRNAND images between systems__: Transfer the file located at `S:/ctrnand_full.bin` (or `E:`/ `I:`). On the receiving system, press A, select `CTRNAND Options...`, then `Transfer to NAND`. -* __Embed an essential backup right into a NAND dump__: This is available in the A button menu for NAND dumps. Essential backups contain NAND header, `movable.sed`, `LocalFriendCodeSeed_B`, `SecureInfo_A`, NAND CID and OTP. If your local SysNAND does not contain an embedded backup, you will be asked to do one at startup. To update the essential SysNAND backup at a later point in time, press A on `S:/nand.bin` and select `NAND image options...` -> `Update embedded backup`. -* __Install an AES key database to your NAND__: For `aeskeydb.bin` files the option is found in `aeskeydb.bin options` -> `Install aeskeydb.bin`. Only the recommended key database can be installed (see above). With an installed key database, it is possible to run the GodMode9 bootloader completely from NAND. -* __Install FIRM files to your NAND__: Found inside the A button menu for FIRM files, select `FIRM options` -> `Install FIRM`. __Use this with caution__ - installing an incompatible FIRM file will lead to a __brick__. The FIRMs signature will automagically be replaced with a sighax signature to ensure compatibility. -* __Actually use that extra NAND space__: You can set up a __bonus drive__ via the HOME menu, which will be available via drive letter `8:`. (Only available on systems that have the extra space.) -* __Fix certain problems on your NANDs__: You can fix CMACs for a whole drive (works on `A:`, `B:`, `S:` and `E:`) via an entry in the R+A button menu, or even restore borked NAND headers back to a functional state (inside the A button menu of borked NANDs and available for `S:/nand_hdr.bin`). Recommended only for advanced users! - -### System file handling -* __Check and fix CMACs (for any file that has them)__: The option will turn up in the A button menu if it is available for a given file (f.e. system savegames, `ticket.db`, etc...). This can also be done for multiple files at once if they are marked. -* __Mount ticket.db files and dump tickets__: Mount the file via the A button menu. Tickets are sorted into `eshop` (stuff from eshop), `system` (system tickets), `unknown` (typically empty) and `hidden` (hidden tickets, found via a deeper scan) categories. All tickets displayed are legit, fake tickets are ignored -* __Inject any NCCH CXI file into Health & Safety__: The option is found inside the A button menu for any NCCH CXI file. NCCH CXIs are found, f.e. inside of CIAs. Keep in mind there is a (system internal) size restriction on H&S injectable apps. -* __Inject and dump GBA VC saves__: Find the options to do this inside the A button menu for `agbsave.bin` in the `S:` drive. Keep in mind that you need to start the specific GBA game on your console before dumping / injecting the save. _To inject a save it needs to be in the clipboard_. -* __Dump a copy of boot9, boot11 & your OTP__: This works on sighax, via boot9strap only. These files are found inside the `M:` drive and can be copied from there to any other place. - -### Support file handling -* __Build `decTitleKeys.bin` / `encTitleKeys.bin` / `seeddb.bin`__: Press the HOME button, select `More...` -> `Build support files`. `decTitleKeys.bin` / `encTitleKeys.bin` can also be created / merged from tickets, `ticket.db` and merged from other `decTitleKeys.bin` / `encTitleKeys.bin` files via the A button menu. -* __Build, mount, decrypt and encrypt `aeskeydb.bin`__: AES key databases can be merged from other `aeskeydb.bin` or build from legacy `slot??Key?.bin` files. Just select one or more files, press A on one of them and then select `Build aeskeydb.bin`. Options for mounting, decrypting and encrypting are also found in the A button menu. - - -## License -You may use this under the terms of the GNU General Public License GPL v2 or under the terms of any later revisions of the GPL. Refer to the provided `LICENSE.txt` file for further information. - -## Contact info -You can chat directly with us via IRC @ #GodMode9 on [libera.chat](https://web.libera.chat/#GodMode9) or [Discord](https://discord.gg/BRcbvtFxX4)! - -## Credits -This tool would not have been possible without the help of numerous people. Thanks go to (in no particular order)... -* **Archshift**, for providing the base project infrastructure -* **Normmatt**, for sdmmc.c / sdmmc.h and gamecart code, and for being of great help on countless other occasions -* **Cha(N)**, **Kane49**, and all other FatFS contributors for [FatFS](http://elm-chan.org/fsw/ff/00index_e.html) -* **Wolfvak** for ARM11 code, FIRM binary launcher, exception handlers, PCX code, Makefile and for help on countless other occasions -* **SciresM** for helping me figure out RomFS and for boot9strap -* **SciresM**, **Myria**, **Normmatt**, **TuxSH** and **hedgeberg** for figuring out sighax and giving us access to bootrom -* **ihaveamac** for first developing the simple CIA generation method and for being of great help in porting it -* **wwylele** and **aspargas2** for documenting and implementing the DISA, DIFF, and BDRI formats -* **dratini0** for savefile management, based on [TWLSaveTool](https://github.com/TuxSH/TWLSaveTool/) -* **Pk11** for unicode and translation support -* **b1l1s** for helping me figure out A9LH compatibility -* **Gelex** and **AuroraWright** for helping me figure out various things -* **stuckpixel** for the new 6x10 font and help on various things -* **Al3x_10m** for help with countless hours of testing and useful advice -* **WinterMute** for helping me with his vast knowledge on everything gamecart related -* **profi200** for always useful advice and helpful hints on various things -* **windows-server-2003** for the initial implementation of if-else-goto in .gm9 scripts -* **Kazuma77** for pushing forward scripting, for testing and for always useful advice -* **TurdPooCharger** for being one of the most meticulous software testers around -* **JaySea**, **YodaDaCoda**, **liomajor**, **Supster131**, **imanoob**, **Kasher_CS** and countless others from freenode #Cakey and the GBAtemp forums for testing, feedback and helpful hints -* **Shadowhand** for being awesome and [hosting my nightlies](https://d0k3.secretalgorithm.com/) -* **Plailect** for putting his trust in my tools and recommending this in [The Guide](https://3ds.guide/) -* **SirNapkin1334** for testing, bug reports and for hosting the original GodMode9 Discord server -* **Lilith Valentine** for testing and helpful advice -* **Project Nayuki** for [qrcodegen](https://github.com/nayuki/QR-Code-generator) -* **Amazingmax fonts** for the Amazdoom font -* The fine folks on **the official GodMode9 IRC channel and Discord server** -* The fine folks on **freenode #Cakey** -* All **[3dbrew.org](https://www.3dbrew.org/wiki/Main_Page) editors** -* Everyone I possibly forgot, if you think you deserve to be mentioned, just contact us! +Before you think about using this: it doesn't even have an API or a proper way to run the code. This is currenetly ONLY a test to see if Lua can be used at all. From 20a2cc7bd00a2317b76f2d9fa196a1da2d74530b Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 04:51:21 -0700 Subject: [PATCH 007/124] change init for a simple test, print error on top screen too --- arm9/source/utils/scripting.c | 6 ++++-- init.lua | 14 +++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index df8c40a3b..f3991dff0 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -1557,7 +1557,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { while(1); } else if (id == CMD_ID_LUARUN) { - luaL_Reg loadedlibs[] = { + luaL_Reg loadedlibs[] = { {LUA_GNAME, luaopen_base}, //{LUA_LOADLIBNAME, luaopen_package}, //{LUA_COLIBNAME, luaopen_coroutine}, @@ -1575,7 +1575,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { lua_State *L = luaL_newstate(); // NOTE most libs will most likely not work right away because GM9 is a very weird environment //luaL_openlibs(L); - + // this will require a bunch of other funcs defined const luaL_Reg *lib; /* "require" functions from 'loadedlibs' and set results to global table */ @@ -1601,6 +1601,8 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { lua_pop(L, lua_gettop(L)); //ShowPrompt(false, "Success"); } else { + ClearScreen(TOP_SCREEN, COLOR_STD_BG); + DrawStringCenter(TOP_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", lua_tostring(L, -1)); ShowPrompt(false, "%s", lua_tostring(L, -1)); } diff --git a/init.lua b/init.lua index 573ae6afc..f3f6462a6 100755 --- a/init.lua +++ b/init.lua @@ -1,9 +1,5 @@ -thingy(tostring("a")) -thingy(tostring(math)) -index = 1 -thingy(tostring(math.sin(3))) -thingy(tostring(3 ^ 2)) -for k, v in pairs(math) do - thingy("math func "..tostring(index).."/"..tostring(#math)..": \n- "..tostring(k).."\n "..tostring(v)) - index = index + 1 -end +thingy("A test of your reflexes!") +thingy("math.sin(3): "..tostring(math.sin(3))) +thingy("3 ^ 2: "..tostring(3 ^ 2)) + +-- this is about it... can't even do io yet From 725fcd165cc7647df88c208429b4f25e55c07d6d Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 05:26:35 -0700 Subject: [PATCH 008/124] enable more lua libs, edit init.lua with string examples --- arm9/source/utils/scripting.c | 8 ++++---- init.lua | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index f3991dff0..d62b9680e 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -1560,13 +1560,13 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { luaL_Reg loadedlibs[] = { {LUA_GNAME, luaopen_base}, //{LUA_LOADLIBNAME, luaopen_package}, - //{LUA_COLIBNAME, luaopen_coroutine}, - //{LUA_TABLIBNAME, luaopen_table}, + {LUA_COLIBNAME, luaopen_coroutine}, + {LUA_TABLIBNAME, luaopen_table}, //{LUA_IOLIBNAME, luaopen_io}, //{LUA_OSLIBNAME, luaopen_os}, - //{LUA_STRLIBNAME, luaopen_string}, + {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, - //{LUA_UTF8LIBNAME, luaopen_utf8}, + {LUA_UTF8LIBNAME, luaopen_utf8}, //{LUA_DBLIBNAME, luaopen_debug}, {NULL, NULL} }; diff --git a/init.lua b/init.lua index f3f6462a6..fc3b2f1d6 100755 --- a/init.lua +++ b/init.lua @@ -2,4 +2,11 @@ thingy("A test of your reflexes!") thingy("math.sin(3): "..tostring(math.sin(3))) thingy("3 ^ 2: "..tostring(3 ^ 2)) +thingy(string.sub("freeman", 5)) + +words = 'black,mesa' +for word in string.gmatch(words, '([^,]+)') do + thingy("gmatch test: "..word) +end + -- this is about it... can't even do io yet From fa46fc25769d06a2b9a87277d5717d9947f04c08 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 05:31:49 -0700 Subject: [PATCH 009/124] one more readme edit before bed --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc24f0796..8ff8d8edd 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,14 @@ A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings -Makefile.build edited to support a LIBS variable on build +* Makefile.build edited to support a LIBS variable on build -Math lib (-lm) now needed for lua +* Math lib (-lm) now needed for lua -link.ld on arm9 edited to include .ARM.exidx into .rodata, caused by lua's code. Limit of AHBWRAM max size was increased until address 0x80C0000, 744KiB since 0x8006000 +* link.ld on arm9 edited to include .ARM.exidx into .rodata, caused by lua's code. Limit of AHBWRAM max size was increased until address 0x80C0000, 744KiB since 0x8006000 copy init.lua to the root and then run "run.gm9" to use it +This exposes one function "thingy" which will display text on both screens, similar to "echo" and "set PREVIEW\_MODE". + Before you think about using this: it doesn't even have an API or a proper way to run the code. This is currenetly ONLY a test to see if Lua can be used at all. From 85eaca739ff28951878705220189d05a8d474640 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 04:24:33 -0700 Subject: [PATCH 010/124] Readme --- README.md | 210 +++--------------------------------------------------- 1 file changed, 8 insertions(+), 202 deletions(-) diff --git a/README.md b/README.md index ea9462e7f..8ff8d8edd 100644 --- a/README.md +++ b/README.md @@ -1,209 +1,15 @@ -# ![GodMode9](https://github.com/d0k3/GodMode9/blob/master/resources/logo.png) -_A full access file browser for the 3DS console_ :godmode: +# GodMode9 but with lua -GodMode9 is a full access file browser for the Nintendo 3DS console, giving you access to your SD card, to the FAT partitions inside your SysNAND and EmuNAND and to basically anything else. Among other functionality (see below), you can copy, delete, rename files and create folders. +A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings +* Makefile.build edited to support a LIBS variable on build -## Warning -__This is powerful stuff__, it provides you with the means to do basically any thinkable modification to any system data available on the 3DS console. However, precautions are taken so you don't accidentally damage the data of your console. The write permissions system protects you by providing warnings and forces you to enter an unlock sequence for enabling write permissions. It is not possible to overwrite or modify any important stuff without such unlock sequences and it is not possible to accidentally unlock something. +* Math lib (-lm) now needed for lua -__As always, be smart, keep backups, just to be safe__. +* link.ld on arm9 edited to include .ARM.exidx into .rodata, caused by lua's code. Limit of AHBWRAM max size was increased until address 0x80C0000, 744KiB since 0x8006000 +copy init.lua to the root and then run "run.gm9" to use it -## Quick start guide -The recommended bootloader for use with GodMode9 is [fastboot3DS](https://github.com/derrekr/fastboot3DS). There are [known issues for some users](https://github.com/d0k3/GodMode9/issues/466) when using the standard setup based on [boot9strap](https://github.com/SciresM/boot9strap) and [Luma3DS](https://github.com/AuroraWright/Luma3DS). If you insist on using that setup follow the instructions found in a [certain guide](https://3ds.hacks.guide). Here's how to set up GodMode9 (and fastboot3DS) up quickly: -* Download [OpenFirmInstaller](https://github.com/d0k3/OpenFirmInstaller/releases/tag/v0.0.9) and follow the quick setup instructions found there. -* Copy the `gm9` folder from the release archive to your SD card. Then, get good versions of `seeddb.bin` and `encTitleKeys.bin` from somewhere (don't ask me!) and put these two files into `sd:/gm9/support` (optional but recommended for full functionality). -* It is also recommended you setup the RTC clock if you're running GodMode9 for the first time. Find the option via HOME button -> `More...`. Also keep in mind that you should fix your system OS clock afterwards. While you're in the `More...` menu, you may also set screen brightness to a fixed value of your choosing and manually calibrate the touch screen (*not recommended* - try the automatic configuration first). -* Helpful hint #1: Go [here](https://3ds.hacks.guide/godmode9-usage) for step by steps on doing some common tasks in GodMode9. Especially users coming from Decrypt9WIP or Hourglass9 may find this to be helpful. -* Helpful hint #2: __Never unlock the red write permission level unless you know exactly what you're doing__. You will notice that prompt when it comes up, it features a completely red screen. It is recommended you stay on the yellow permission level or below at all times to be completely safe. Also read more on the write permissions system below. +This exposes one function "thingy" which will display text on both screens, similar to "echo" and "set PREVIEW\_MODE". -You may now run GodMode9 via holding the X Button (or any other button you chose) at startup. See below for a list of stuff you can do with it. - - -## Buttons in GodMode9 -GodMode9 is designed to be intuitive, buttons leading to the results you'd expect. However, some stuff may not be obvious at first glance. So, here's a quick, incomplete rundown of what each button / button combo does. -* __\ button__: The \ button is the 'confirm' / 'choose' button. It confirms prompts and selects entries in menus. In the main file view, it pulls up a submenu for files and opens directories (use \ on directories for a submenu, also including the invaluable title search). In the hexviewer, \ switches into edit mode. -* __\ button__: The \ button is the 'cancel' / 'return' button. Use it to leave menus without action, hold it on file operations to cancel said file operations. -* __\ button__: In the main file view, the \ button deletes (marked) files. With \ files are renamed. -* __\ button__: In the main file view, the \ button copies and pastes files. With \ you can create folders and dummy files. -* __\ button__: The \ button is the 'mark' button. Use it with \ / \ to mark / unmark all files in a folder, hold it and use \ / \ to select multiple files. -* __\ button__: The \ button is the 'switch' button. It switches buttons to their secondary function. Notable exceptions are \ for a screenshot (works almost anywhere), \ / \ to switch panes and \ to reload the file listing. -* __\ button__: Use the \ button to reboot from GodMode9. Use \ to poweroff your 3DS. -* __\ button clears or restores the clipboard (depending on if it's empty or not). -* __\ button__: The \ button enters the HOME menu, including the scripts / payloads submenus, options for formatting the SD, setting the RTC, and more. The \ button is an alternative way of entering the HOME menu. -* __\ combo__: This little known keycombo, when held at startup, pauses the GodMode9 boot so that you can stare at the splash screen for a little longer. -* __\ combo__: If you have installed GodMode9 as your bootloader, this keycombo enters the bootmenu. Hold on startup! If you built GodMode9 as SALTMODE and have it as a bootloader, the keycombo is simply the \ button. - - -## How to build this / developer info -Build `GodMode9.firm` via `make firm`. This requires [firmtool](https://github.com/TuxSH/firmtool), [Python 3.5+](https://www.python.org/downloads/) and [devkitARM](https://sourceforge.net/projects/devkitpro/) installed). - -You may run `make release` to get a nice, release-ready package of all required files. To build __SafeMode9__ (a bricksafe variant of GodMode9, with limited write permissions) instead of GodMode9, compile with `make FLAVOR=SafeMode9`. To switch screens, compile with `make SWITCH_SCREENS=1`. For additional customization, you may choose the internal font by replacing `font_default.frf` inside the `data` directory. You may also hardcode the brightness via `make FIXED_BRIGHTNESS=x`, whereas `x` is a value between 0...15. - -Further customization is possible by hardcoding `aeskeydb.bin` (just put the file into the `data` folder when compiling). All files put into the `data` folder will turn up in the `V:` drive, but keep in mind there's a hard 223.5KiB limit for all files inside, including overhead. A standalone script runner is compiled by providing `autorun.gm9` (again, in the `data` folder) and building with `make SCRIPT_RUNNER=1`. There's more possibility for customization, read the Makefiles to learn more. - -To build a .firm signed with SPI boot keys (for ntrboot and the like), run `make NTRBOOT=1`. You may need to rename the output files if the ntrboot installer you use uses hardcoded filenames. Some features such as boot9 / boot11 access are not currently available from the ntrboot environment. - - -## Bootloader mode -Same as [boot9strap](https://github.com/SciresM/boot9strap) or [fastboot3ds](https://github.com/derrekr/fastboot3DS), GodMode9 can be installed to the system FIRM partition ('FIRM0'). When executed from a FIRM partition, GodMode9 will default to bootloader mode and try to boot, in order, `FIRM from FCRAM` (see [A9NC](https://github.com/d0k3/A9NC/releases)), `0:/bootonce.firm` (will be deleted on a successful boot), `0:/boot.firm`, `1:/boot.firm`. In bootloader mode, hold R+LEFT on boot to enter the boot menu. *Installing GodMode9 to a FIRM partition is only recommended for developers and will overwrite [boot9strap](https://github.com/SciresM/boot9strap) or any other bootloader you have installed in there*. - - -## Write permissions system -GodMode9 provides a write permissions system, which will protect you from accidentally damaging your system, losing data and/or modifying important system data. To unlock a write permission, an unlock sequence must be entered. This is not possible by accident. The write permission system is based on colors and the top bar on the top screen will change color according to the current write permission level. No permission above the yellow level can be unlocked on SafeMode9. -* __Green:__ Modification to system files is not possible on this permission level. You can't edit or delete savegames and installed data. However, keep in mind that any non-system related or custom stuff on your SD card is not protected. -* __Yellow:__ You can modify system files on this permission level. Data that is unique to your console and cannot be gotten from anywhere else is still not modifiable. Any damages you introduce can be fixed in software, but loss of savegames and installed data is possible if you are not careful. __A NAND backup is highly recommended starting at this level.__ -* __Orange:__ This is similar to the yellow permission level, but, in addition, allows edits to console unique data. Any damages you introduce are still fixable in software, but if you play around with this, __having a NAND backup is an absolute requirement__. -* __Red:__ The highest regular permission level. There are no limits to system file edits, and if you are not careful enough, you can brick your console and/or remove your A9LH/B9S installation. Bricks on this level may only be fixable in hardware. __If you don't have a NAND backup at this point, you seem to have a deathwish for your console__. -* __Blue:__ This permission level is reserved for edits to system memory. While, most likely, nothing bad at all will happen, consequences of edits can be unforeseen. There is even a (albeit very small) chance of bricking your console, maybe even permanently. __Tread with caution on this level__. - - -## Support files -For certain functionality, GodMode9 may need 'support files'. Support files should be placed into either `0:/gm9/support` or `1:/gm9/support`. Support files contain additional information that is required in decryption operations. A list of support files, and what they do, is found below. Please don't ask for support files - find them yourself. -* __`aeskeydb.bin`__: This should contain 0x25keyX, 0x18keyX and 0x1BkeyX to enable decryption of 7x / Secure3 / Secure4 encrypted NCCH files, 0x11key95 / 0x11key96 for FIRM decrypt support and 0x11keyOTP / 0x11keyIVOTP for 'secret' sector 0x96 crypto support. Entrypoints other than [boot9strap](https://github.com/SciresM/boot9strap) or [fastboot3ds](https://github.com/derrekr/fastboot3DS) may require a aeskeydb.bin file. This is now included in standard releases of GM9. No need to hunt down the file! -* __`seeddb.bin`__: This file is optional and required to decrypt and mount seed-encrypted NCCHs and CIAs (if the seed in question is not installed to your NAND). Note that your seeddb.bin must also contain the seed for the specific game you need to decrypt. -* __`encTitleKeys.bin`__ / __`decTitleKeys.bin`__: These files are optional and provide titlekeys, which are required to decrypt and install contents downloaded from CDN (for DSi and 3DS content). - -### Fonts and translations -GodMode9 also supports custom fonts and translations as support files. These both use custom formats, fonts use FRF (Font RIFF) files which can be created using the `fontriff.py` Python script in the 'utils' folder. Translations use TRF (Translation RIFF) files from the `transriff.py` script. Examples of the inputs to these scripts can be found in the 'fonts' and 'languages' folders of the 'resources' folder respectively. - -TRF files can be placed in `0:/gm9/languages` to show in the language menu accessible from the HOME menu and shown on first load. Official translations are provided from the community via the [GodMode9 Crowdin](https://crowdin.com/project/GodMode9). Languages can use a special font by having an FRF with the same name, for example `en.trf` and `en.frf`. - -## Drives in GodMode9 -GodMode9 provides access to system data via drives, a listing of what each drive contains and additional info follows below. Some of these drives are removable (such as drive `7:`), some will only turn up if they are available (drive `8:` and everything associated with EmuNAND, f.e.). Information on the 3DS console file system is also found on [3Dbrew.org](https://3dbrew.org/wiki/Flash_Filesystem). -* __`0: SDCARD`__: The SD card currently inserted into the SD card slot. The `0:/Nintendo 3DS` folder contains software installs and extdata and is specially protected via the write permission system. The SD card can be unmounted from the root directory via the R+B buttons, otherwise the SD card is always available. -* __`1: SYSNAND CTRNAND`__: The CTRNAND partition on SysNAND. This contains your 3DS console's operating system and system software installs. Data in here is protected by the write permissions system. -* __`2: SYSNAND TWLN`__: The TWLN partition on SysNAND. This contains your 3DS console's TWL mode operating system and system software installs. Data in here is protected by the write permissions system. -* __`3: SYSNAND TWLP`__: The TWLP partition on SysNAND. This contains photos taken while in TWL mode. -* __`A: SYSNAND SD`__: This drive is used for special access to data on your SD card. It actually links to a subfolder inside `0:/Nintendo 3DS` and contains software and extdata installed to SD from SysNAND. Crypto in this folder is handled only when accessed via the `A:` drive (not from `0:`). This is protected by the write permissions system. -* __`S: SYSNAND VIRTUAL`__: This drive provides access to all partitions of the SysNAND, some of them critical for base system functionality. This is protected by the write permissions system, but, when unlocked, modifications can brick the system. -* __`4: EMUNAND CTRNAND`__: Same as `1:`, but handles the CTRNAND on EmuNAND. For multi EmuNAND setups, the currently active EmuNAND partition can be switched via the HOME menu. -* __`5: EMUNAND TWLN`__: Same as `2`, but handles TWLN on EmuNAND. No write protection here, cause this partition is never used on EmuNAND. -* __`6: EMUNAND TWLP`__: Same as `3`, but handles TWLP on EmuNAND. -* __`B: EMUNAND SD`__: Same as `A:`, but handles the `0:/Nintendo 3DS` subfolder associated with EmuNAND. In case of linked NANDs, this is identical with `A:`. This is also protected by the write permissions system. -* __`E: EMUNAND VIRTUAL`__: Same as `S:`, but handles system partitions on EmuNAND. No bricking risk here as EmuNAND is never critical to system functionality. -* __`7: FAT IMAGE / IMGNAND CTRNAND`__: This provides access to mounted FAT images. When a NAND image is mounted, it provides access to the mounted NAND image's CTRNAND. -* __`8: BONUS DRIVE / IMGNAND TWLN`__: This provides access to the bonus drive on SysNAND. The bonus drive can be setup via the HOME menu on 3DS consoles that provide the space for it. When a NAND image is mounted, this provides access to the mounted NAND image's TWLN. -* __`9: RAM DRIVE / IMGNAND TWLP`__: This provides access to the RAM drive. All data stored inside the RAM drive is temporary and will be wiped after a reboot. When a NAND image is mounted, this provides access to the mounted NAND image's TWLP. -* __`I: IMGNAND VIRTUAL`__: When a NAND image is mounted, this provides access to the partitions inside the NAND image. -* __`C: GAMECART`__: This is read-only and provides access to the game cartridge currently inserted into the cart slot. This can be used for dumps of CTR and TWL mode cartridges. Flash cards are supported only to a limited extent. -* __`G: GAME IMAGE`__: CIA/NCSD/NCCH/EXEFS/ROMFS/FIRM images can be accessed via this drive when mounted. This is read-only. -* __`K: AESKEYDB IMAGE`__: An `aeskeydb.bin` image can be mounted and accessed via this drive. The drive shows all keys inside the aeskeydb.bin. This is read-only. -* __`T: TICKET.DB IMAGE / BDRI IMAGE`__: Ticket database files can be mounted and accessed via this drive. This provides easy and quick access to all tickets inside the `ticket.db`. This drive also provides access to other BDRI images, such as the Title database (`title.db`). -* __`M: MEMORY VIRTUAL`__: This provides access to various memory regions. This is protected by a special write permission, and caution is advised when doing modifications inside this drive. This drive also gives access to `boot9.bin`, `boot11.bin` (boot9strap only) and `otp.mem` (sighaxed systems only). -* __`V: VRAM VIRTUAL`__: This drive resides in part of ARM9 internal memory and contains files essential to GodMode9. The font (in FRF format), the splash logo (in PNG format) and the readme file are found there, as well as any file that is provided inside the `data` folder at build time. This is read-only. -* __`Y: TITLE MANAGER`__: The title manager is accessed via the HOME menu and provides easy access to all installed titles. -* __`Z: LAST SEARCH`__: After a search operation, search results are found inside this drive. The drive can be accessed at a later point to return to the former search results. - - -## Digital preservation -GodMode9 is one of the most important tools for digital preservation of 3DS content data. Here's some stuff you should know: -* __Dumping game cartridges (size < 4GiB)__: Game cartridges turn up inside the `C:` drive (see above). For most carts all you need to do is copy the `.3DS` game image to some place of your choice. Game images dumped by GodMode9 contain no identifying info such as private headers or savegames. Private headers can be dumped in a separate image. -* __Dumping game cartridges (size = 4GiB)__: Everything written above applies here as well. However, the FAT32 file system (which is what the 3DS uses) is limited to _4GiB - 1byte_. Take note that the `.3DS` game image, as provided by GodMode9 actually misses the last byte in these cases. That byte is 0xFF and unused in all known cases. It is not required for playing the image. If you need to check, we also provide split files (`.000`, `.001)`, which contain all the data. If you need a valid checksum for the `.3DS` game image, append a 0xFF byte before checking. -* __Building CIAs (all types)__: You may convert compatible file types (game images, installed content, CDN content) to the CIA installable format using the A button menu. To get a list of installed content, press HOME, select `Title manager` and choose a drive. Take note that `standard` built CIAs are decrypted by default (decryption allows better compression by ZIP and 7Z). If you should need an encrypted CIA for some reason, apply the encryption to the CIA afterwards. -* __Building CIAs (legit type)__: Installed content can be built as `legit` or `standard` CIA. Legit CIAs preserve more of the original data and are thus recommended for preservation purposes. When building legit CIAs, GodMode9 keeps the original crypto and tries to find a genuine, signature-valid ticket. If it doesn't find one on your system, it will use a generic ticket instead. If it only finds a personalized one, it still offers to use a generic ticket. It is not recommended to use personalized tickets - only choose this if you know what you're doing. -* __Checking CIAs__: You may also check your CIA files with the builtin `CIA checker tool`. Legit CIAs with generic tickets are identified as `Universal Pirate Legit`, which is the recommended preservation format where `Universal Legit` is not available. Note: apart from system titles, `Universal Legit` is only available for a handful of preinstalled games from special edition 3DS consoles. - - -## What you can do with GodMode9 -With the possibilites GodMode9 provides, not everything may be obvious at first glance. In short, __GodMode9 includes improved versions of basically everything that Decrypt9 has, and more__. Any kind of dumps and injections are handled via standard copy operations and more specific operations are found inside the A button menu. The A button menu also works for batch operations when multiple files are selected. For your convenience a (incomplete!) list of what GodMode9 can do follows below. - -### Basic functionality -* __Manage files on all your data storages__: You wouldn't have expected this, right? Included are all standard file operations such as copy, delete, rename files and create folders. Use the L button to mark multiple files and apply file operations to multiple files at once. -* __Make screenshots__: Press R+L anywhere. Screenshots are stored in PNG format. -* __Use multiple panes__: Press R+left|right. This enables you to stay in one location in the first pane and open another in the second pane. -* __Search drives and folders__: Just press R+A on the drive / folder you want to search. -* __Compare and verify files__: Press the A button on the first file, select `Calculate SHA-256`. Do the same for the second file. If the two files are identical, you will get a message about them being identical. On the SDCARD drive (`0:`) you can also write an SHA file, so you can check for any modifications at a later point. -* __Hexview and hexedit any file__: Press the A button on a file and select `Show in Hexeditor`. A button again enables edit mode, hold the A button and press arrow buttons to edit bytes. You will get an additional confirmation prompt to take over changes. Take note that for certain files, write permissions can't be enabled. -* __View text files in a text viewer__: Press the A button on a file and select `Show in Textviewer` (only shows up for actual text files). You can enable wordwrapped mode via R+Y, and navigate around the file via R+X and the dpad. -* __Chainload FIRM payloads__: Press the A button on a FIRM file, select `FIRM options` -> `Boot FIRM`. Keep in mind you should not run FIRMs from dubious sources and that the write permissions system is no longer in place after booting a payload. -* __Chainload FIRM payloads from a neat menu__: The `payloads` menu is found inside the HOME button menu. It provides any FIRM found in `0:/gm9/payloads` for quick chainloading. -* __Inject a file to another file__: Put exactly one file (the file to be injected from) into the clipboard (via the Y button). Press A on the file to be injected to. There will be an option to inject the first file into it. - -### Scripting functionality -* __Run .gm9 scripts from anywhere on your SD card__: You can run scripts in .gm9 format via the A button menu. .gm9 scripts use a shell-like language and can be edited in any text editor. For an overview of usable commands have a look into the sample scripts included in the release archive. *Don't run scripts from untrusted sources.* -* __Run .gm9 scripts via a neat menu__: Press the HOME button, select `More...` -> `Scripts...`. Any script you put into `0:/gm9/scripts` (subdirs included) will be found here. Scripts ran via this method won't have the confirmation at the beginning either. - -### SD card handling -* __Format your SD card / setup an EmuNAND__: Press the HOME button, select `More...` -> `SD format menu`. This also allows to setup a RedNAND (single/multi) or GW type EmuNAND on your SD card. You will get a warning prompt and an unlock sequence before any operation starts. -* __Handle multiple EmuNANDs__: Press the HOME button, select `More...` -> `Switch EmuNAND` to switch between EmuNANDs / RedNANDs. (Only available on multi EmuNAND / RedNAND systems.) -* __Run it without an SD card / unmount the SD card__: If no SD card is found, you will be offered to run without the SD card. You can also unmount and remount your SD card from the file system root at any point. -* __Direct access to SD installed contents__: Just take a look inside the `A:`/`B:` drives. On-the-fly-crypto is taken care for, you can access this the same as any other content. -* __Set (and use) the RTC clock__: For correct modification / creation dates in your file system, you need to setup the RTC clock first. Press the HOME Button and select `More...` to find the option. Keep in mind that modifying the RTC clock means you should also fix system OS time afterwards. - -### Game file handling -* __List titles installed on your system__: Press HOME and select `Title manager`. This will also work via R+A for `CTRNAND` and `A:`/`B:` drives. This will list all titles installed in the selected location. -* __Install titles to your system__: Just press A on any file you want installed and select `Install game image` from the submenu. Works with NCCH / NCSD / CIA / DSiWare SRLs / 3DS CDN TMDs / DSi CDN TMDs / NUS TMDs. -* __(Batch) Uninstall titles from your system__: Most easily done via the HOME menu `Title manager`. Just select one or more titles and find the option inside the `Manage title...` submenu. -* __Build CIAs from NCCH / NCSD (.3DS) / SRL / TMD__: Press A on the file you want converted and the option will be shown. Installed contents are found most easily via the HOME menu `Title manager`. Where applicable, you will also be able to generate legit CIAs. Note: this works also from a file search and title listing. -* __Dump CXIs / NDS from TMD (installed contents)__: This works the same as building CIAs, but dumps decrypted CXIs or NDS rom dumps instead. Note: this works also from a file search and title listing. -* __Decrypt, encrypt and verify NCCH / NCSD / CIA / BOSS / FIRM images__: Options are found inside the A button menu. You will be able to decrypt/encrypt to the standard output directory or (where applicable) in place. -* __Decrypt content downloaded from CDN / NUS__: Press A on the file you want decrypted. For this to work, you need at least a TMD file (`encTitlekeys.bin` / `decTitlekeys.bin` also required, see _Support files_ below) or a CETK file. Either keep the names provided by CDN / NUS, or rename the downloaded content to `(anything).nus` or `(anything).cdn` and the CETK to `(anything).cetk`. -* __Batch mode for the above operations__: Just select multiple files of the same type via the L button, then press the A button on one of the selected files. -* __Access any file inside NCCH / NCSD / CIA / FIRM / NDS images__: Just mount the file via the A button menu and browse to the file you want. For CDN / NUS content, prior decryption is required for full access. -* __Rename your NCCH / NCSD / CIA / NDS / GBA files to proper names__: Find this feature inside the A button menu. Proper names include title id, game name, product code and region. -* __Trim NCCH / NCSD / NDS / GBA / FIRM / NAND images__: This feature is found inside the A button menu. It allows you to trim excess data from supported file types. *Warning: Excess data may not be empty, bonus drives are stored there for NAND images, NCSD card2 images store savedata there, for FIRMs parts of the A9LH exploit may be stored there*. -* __Dump 3DS / NDS / DSi type retail game cartridges__: Insert the cartridge and take a look inside the `C:` drive. You may also dump private headers from 3DS game cartridges. The `C:` drive also gives you read/write access to the saves on the cartridges. Note: For 4GiB cartridges, the last byte is not included in the .3ds file dump. This is due to restrictrions of the FAT32 file system. - -### NAND handling -* __Directly mount and access NAND dumps or standard FAT images__: Just press the A button on these files to get the option. You can only mount NAND dumps from the same console. -* __Restore NAND dumps while keeping your A9LH / sighax installation intact__: Select `Restore SysNAND (safe)` from inside the A button menu for NAND dumps. -* __Restore / dump NAND partitions or even full NANDs__: Just take a look into the `S:` (or `E:`/ `I:`) drive. This is done the same as any other file operation. -* __Transfer CTRNAND images between systems__: Transfer the file located at `S:/ctrnand_full.bin` (or `E:`/ `I:`). On the receiving system, press A, select `CTRNAND Options...`, then `Transfer to NAND`. -* __Embed an essential backup right into a NAND dump__: This is available in the A button menu for NAND dumps. Essential backups contain NAND header, `movable.sed`, `LocalFriendCodeSeed_B`, `SecureInfo_A`, NAND CID and OTP. If your local SysNAND does not contain an embedded backup, you will be asked to do one at startup. To update the essential SysNAND backup at a later point in time, press A on `S:/nand.bin` and select `NAND image options...` -> `Update embedded backup`. -* __Install an AES key database to your NAND__: For `aeskeydb.bin` files the option is found in `aeskeydb.bin options` -> `Install aeskeydb.bin`. Only the recommended key database can be installed (see above). With an installed key database, it is possible to run the GodMode9 bootloader completely from NAND. -* __Install FIRM files to your NAND__: Found inside the A button menu for FIRM files, select `FIRM options` -> `Install FIRM`. __Use this with caution__ - installing an incompatible FIRM file will lead to a __brick__. The FIRMs signature will automagically be replaced with a sighax signature to ensure compatibility. -* __Actually use that extra NAND space__: You can set up a __bonus drive__ via the HOME menu, which will be available via drive letter `8:`. (Only available on systems that have the extra space.) -* __Fix certain problems on your NANDs__: You can fix CMACs for a whole drive (works on `A:`, `B:`, `S:` and `E:`) via an entry in the R+A button menu, or even restore borked NAND headers back to a functional state (inside the A button menu of borked NANDs and available for `S:/nand_hdr.bin`). Recommended only for advanced users! - -### System file handling -* __Check and fix CMACs (for any file that has them)__: The option will turn up in the A button menu if it is available for a given file (f.e. system savegames, `ticket.db`, etc...). This can also be done for multiple files at once if they are marked. -* __Mount ticket.db files and dump tickets__: Mount the file via the A button menu. Tickets are sorted into `eshop` (stuff from eshop), `system` (system tickets), `unknown` (typically empty) and `hidden` (hidden tickets, found via a deeper scan) categories. All tickets displayed are legit, fake tickets are ignored -* __Inject any NCCH CXI file into Health & Safety__: The option is found inside the A button menu for any NCCH CXI file. NCCH CXIs are found, f.e. inside of CIAs. Keep in mind there is a (system internal) size restriction on H&S injectable apps. -* __Inject and dump GBA VC saves__: Find the options to do this inside the A button menu for `agbsave.bin` in the `S:` drive. Keep in mind that you need to start the specific GBA game on your console before dumping / injecting the save. _To inject a save it needs to be in the clipboard_. -* __Dump a copy of boot9, boot11 & your OTP__: This works on sighax, via boot9strap only. These files are found inside the `M:` drive and can be copied from there to any other place. - -### Support file handling -* __Build `decTitleKeys.bin` / `encTitleKeys.bin` / `seeddb.bin`__: Press the HOME button, select `More...` -> `Build support files`. `decTitleKeys.bin` / `encTitleKeys.bin` can also be created / merged from tickets, `ticket.db` and merged from other `decTitleKeys.bin` / `encTitleKeys.bin` files via the A button menu. -* __Build, mount, decrypt and encrypt `aeskeydb.bin`__: AES key databases can be merged from other `aeskeydb.bin` or build from legacy `slot??Key?.bin` files. Just select one or more files, press A on one of them and then select `Build aeskeydb.bin`. Options for mounting, decrypting and encrypting are also found in the A button menu. - - -## License -You may use this under the terms of the GNU General Public License GPL v2 or under the terms of any later revisions of the GPL. Refer to the provided `LICENSE.txt` file for further information. - -## Contact info -You can chat directly with us via IRC @ #GodMode9 on [libera.chat](https://web.libera.chat/#GodMode9) or [Discord](https://discord.gg/BRcbvtFxX4)! - -## Credits -This tool would not have been possible without the help of numerous people. Thanks go to (in no particular order)... -* **Archshift**, for providing the base project infrastructure -* **Normmatt**, for sdmmc.c / sdmmc.h and gamecart code, and for being of great help on countless other occasions -* **Cha(N)**, **Kane49**, and all other FatFS contributors for [FatFS](http://elm-chan.org/fsw/ff/00index_e.html) -* **Wolfvak** for ARM11 code, FIRM binary launcher, exception handlers, PCX code, Makefile and for help on countless other occasions -* **SciresM** for helping me figure out RomFS and for boot9strap -* **SciresM**, **Myria**, **Normmatt**, **TuxSH** and **hedgeberg** for figuring out sighax and giving us access to bootrom -* **ihaveamac** for first developing the simple CIA generation method and for being of great help in porting it -* **wwylele** and **aspargas2** for documenting and implementing the DISA, DIFF, and BDRI formats -* **dratini0** for savefile management, based on [TWLSaveTool](https://github.com/TuxSH/TWLSaveTool/) -* **Pk11** for unicode and translation support -* **b1l1s** for helping me figure out A9LH compatibility -* **Gelex** and **AuroraWright** for helping me figure out various things -* **stuckpixel** for the new 6x10 font and help on various things -* **Al3x_10m** for help with countless hours of testing and useful advice -* **WinterMute** for helping me with his vast knowledge on everything gamecart related -* **profi200** for always useful advice and helpful hints on various things -* **windows-server-2003** for the initial implementation of if-else-goto in .gm9 scripts -* **Kazuma77** for pushing forward scripting, for testing and for always useful advice -* **TurdPooCharger** for being one of the most meticulous software testers around -* **JaySea**, **YodaDaCoda**, **liomajor**, **Supster131**, **imanoob**, **Kasher_CS** and countless others from freenode #Cakey and the GBAtemp forums for testing, feedback and helpful hints -* **Shadowhand** for being awesome and [hosting my nightlies](https://d0k3.secretalgorithm.com/) -* **Plailect** for putting his trust in my tools and recommending this in [The Guide](https://3ds.guide/) -* **SirNapkin1334** for testing, bug reports and for hosting the original GodMode9 Discord server -* **Lilith Valentine** for testing and helpful advice -* **Project Nayuki** for [qrcodegen](https://github.com/nayuki/QR-Code-generator) -* **Amazingmax fonts** for the Amazdoom font -* The fine folks on **the official GodMode9 IRC channel and Discord server** -* The fine folks on **freenode #Cakey** -* All **[3dbrew.org](https://www.3dbrew.org/wiki/Main_Page) editors** -* Everyone I possibly forgot, if you think you deserve to be mentioned, just contact us! +Before you think about using this: it doesn't even have an API or a proper way to run the code. This is currenetly ONLY a test to see if Lua can be used at all. From f2317d6bcdfe1301d2daa1f055fb4ee6af2e272e Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 04:51:21 -0700 Subject: [PATCH 011/124] change init for a simple test, print error on top screen too --- arm9/source/utils/scripting.c | 6 ++++-- init.lua | 14 +++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index df8c40a3b..f3991dff0 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -1557,7 +1557,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { while(1); } else if (id == CMD_ID_LUARUN) { - luaL_Reg loadedlibs[] = { + luaL_Reg loadedlibs[] = { {LUA_GNAME, luaopen_base}, //{LUA_LOADLIBNAME, luaopen_package}, //{LUA_COLIBNAME, luaopen_coroutine}, @@ -1575,7 +1575,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { lua_State *L = luaL_newstate(); // NOTE most libs will most likely not work right away because GM9 is a very weird environment //luaL_openlibs(L); - + // this will require a bunch of other funcs defined const luaL_Reg *lib; /* "require" functions from 'loadedlibs' and set results to global table */ @@ -1601,6 +1601,8 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { lua_pop(L, lua_gettop(L)); //ShowPrompt(false, "Success"); } else { + ClearScreen(TOP_SCREEN, COLOR_STD_BG); + DrawStringCenter(TOP_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", lua_tostring(L, -1)); ShowPrompt(false, "%s", lua_tostring(L, -1)); } diff --git a/init.lua b/init.lua index 573ae6afc..f3f6462a6 100755 --- a/init.lua +++ b/init.lua @@ -1,9 +1,5 @@ -thingy(tostring("a")) -thingy(tostring(math)) -index = 1 -thingy(tostring(math.sin(3))) -thingy(tostring(3 ^ 2)) -for k, v in pairs(math) do - thingy("math func "..tostring(index).."/"..tostring(#math)..": \n- "..tostring(k).."\n "..tostring(v)) - index = index + 1 -end +thingy("A test of your reflexes!") +thingy("math.sin(3): "..tostring(math.sin(3))) +thingy("3 ^ 2: "..tostring(3 ^ 2)) + +-- this is about it... can't even do io yet From cecb45532c13f5405a54aec23fc4b2ef9c41eec0 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sun, 23 Jul 2023 05:26:35 -0700 Subject: [PATCH 012/124] enable more lua libs, edit init.lua with string examples --- arm9/source/utils/scripting.c | 8 ++++---- init.lua | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index f3991dff0..d62b9680e 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -1560,13 +1560,13 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { luaL_Reg loadedlibs[] = { {LUA_GNAME, luaopen_base}, //{LUA_LOADLIBNAME, luaopen_package}, - //{LUA_COLIBNAME, luaopen_coroutine}, - //{LUA_TABLIBNAME, luaopen_table}, + {LUA_COLIBNAME, luaopen_coroutine}, + {LUA_TABLIBNAME, luaopen_table}, //{LUA_IOLIBNAME, luaopen_io}, //{LUA_OSLIBNAME, luaopen_os}, - //{LUA_STRLIBNAME, luaopen_string}, + {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, - //{LUA_UTF8LIBNAME, luaopen_utf8}, + {LUA_UTF8LIBNAME, luaopen_utf8}, //{LUA_DBLIBNAME, luaopen_debug}, {NULL, NULL} }; diff --git a/init.lua b/init.lua index f3f6462a6..fc3b2f1d6 100755 --- a/init.lua +++ b/init.lua @@ -2,4 +2,11 @@ thingy("A test of your reflexes!") thingy("math.sin(3): "..tostring(math.sin(3))) thingy("3 ^ 2: "..tostring(3 ^ 2)) +thingy(string.sub("freeman", 5)) + +words = 'black,mesa' +for word in string.gmatch(words, '([^,]+)') do + thingy("gmatch test: "..word) +end + -- this is about it... can't even do io yet From 406885e4f2d561d6626dadb0541203721acd4b95 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Mon, 24 Jul 2023 06:06:26 -0700 Subject: [PATCH 013/124] make lua a proper file type, add test UI library with two functions, remove luacmd command --- arm9/source/filesys/filetype.c | 4 + arm9/source/filesys/filetype.h | 2 + arm9/source/filesys/vff.h | 1 + arm9/source/godmode.c | 15 +- arm9/source/language.inl | 1 + arm9/source/lua/gm9lua.c | 127 +++++ arm9/source/lua/gm9lua.h | 14 + arm9/source/lua/gm9ui.c | 40 ++ arm9/source/lua/gm9ui.h | 7 + arm9/source/lua/lauxlib.h.old | 305 ++++++++++++ arm9/source/lua/liolib.c.old | 810 ++++++++++++++++++++++++++++++++ arm9/source/utils/scripting.c | 72 +-- init.lua | 14 +- resources/languages/source.json | 1 + 14 files changed, 1336 insertions(+), 77 deletions(-) create mode 100644 arm9/source/lua/gm9lua.c create mode 100644 arm9/source/lua/gm9lua.h create mode 100644 arm9/source/lua/gm9ui.c create mode 100644 arm9/source/lua/gm9ui.h create mode 100644 arm9/source/lua/lauxlib.h.old create mode 100644 arm9/source/lua/liolib.c.old diff --git a/arm9/source/filesys/filetype.c b/arm9/source/filesys/filetype.c index cc3caa610..625bede6e 100644 --- a/arm9/source/filesys/filetype.c +++ b/arm9/source/filesys/filetype.c @@ -8,6 +8,7 @@ #include "keydb.h" #include "ctrtransfer.h" #include "scripting.h" +#include "gm9lua.h" #include "png.h" #include "ui.h" // only for font file detection @@ -165,6 +166,9 @@ u64 IdentifyFileType(const char* path) { u64 type = 0; if ((fsize < SCRIPT_MAX_SIZE) && (strcasecmp(ext, SCRIPT_EXT) == 0)) type |= TXT_SCRIPT; // should be a script (which is also generic text) + // this should check if it's compiled lua bytecode (done with luac), which is NOT text + else if ((fsize < LUASCRIPT_MAX_SIZE) && (strcasecmp(ext, LUASCRIPT_EXT) == 0)) + type |= TXT_LUA; if (fsize < STD_BUFFER_SIZE) type |= TXT_GENERIC; return type; } else if ((strncmp(path + 2, "/Nintendo DSiWare/", 18) == 0) && diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index b750e1e59..bef05b5db 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -40,6 +40,7 @@ #define NOIMG_NAND (1ULL<<35) #define HDR_NAND (1ULL<<36) #define TRANSLATION (1ULL<<37) +#define TXT_LUA (1ULL<<38) #define TYPE_BASE 0xFFFFFFFFFFULL // 40 bit reserved for base types // #define FLAG_FIRM (1ULL<<58) // <--- for CXIs containing FIRMs @@ -77,6 +78,7 @@ #define FTYPE_KEYINIT(tp) (tp&(BIN_KEYDB)) #define FTYPE_KEYINSTALL(tp) (tp&(BIN_KEYDB)) #define FTYPE_SCRIPT(tp) (tp&(TXT_SCRIPT)) +#define FTYPE_LUA(tp) (tp&(TXT_LUA)) #define FTYPE_FONT(tp) (tp&(FONT_PBM|FONT_RIFF)) #define FTYPE_TRANSLATION(tp) (tp&(TRANSLATION)) #define FTYPE_GFX(tp) (tp&(GFX_PNG)) diff --git a/arm9/source/filesys/vff.h b/arm9/source/filesys/vff.h index 23a6d0bab..864ba0681 100644 --- a/arm9/source/filesys/vff.h +++ b/arm9/source/filesys/vff.h @@ -8,6 +8,7 @@ #define fvx_tell(fp) ((fp)->fptr) #define fvx_size(fp) ((fp)->obj.objsize) +#define fvx_eof(fp) (fvx_tell(fp) == fvx_size(fp)) #define FN_ANY 0x00 #define FN_HIGHEST 0x01 diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index c398ddb24..bfdece941 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -26,6 +26,7 @@ #include "i2c.h" #include "pxi.h" #include "language.h" +#include "gm9lua.h" #ifndef N_PANES #define N_PANES 3 @@ -1192,6 +1193,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool installable = (FTYPE_INSTALLABLE(filetype)); bool agbexportable = (FTYPE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); bool agbimportable = (FTYPE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); + bool luascriptable = (FTYPE_LUA(filetype)); char cxi_path[256] = { 0 }; // special options for TMD if ((filetype & GAME_TMD) && @@ -1207,7 +1209,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || trimable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || keyinstallable || bootable || scriptable || fontable || translationable || viewable || installable || - agbexportable || agbimportable || cia_installable || tik_installable || tik_dumpable || cif_installable; + agbexportable || agbimportable || cia_installable || tik_installable || tik_dumpable || cif_installable || + luascriptable; char pathstr[UTF_BUFFER_BYTESIZE(32)]; TruncateString(pathstr, file_path, 32, 8); @@ -1290,6 +1293,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan (filetype & BIN_LEGKEY) ? buildkeydb_str : (filetype & BIN_NCCHNFO)? STR_NCCHINFO_OPTIONS : (filetype & TXT_SCRIPT) ? STR_EXECUTE_GM9_SCRIPT : + (filetype & TXT_LUA) ? STR_EXECUTE_LUA_SCRIPT : (FTYPE_FONT(filetype)) ? STR_FONT_OPTIONS : (filetype & TRANSLATION)? STR_LANGUAGE_OPTIONS : (filetype & GFX_PNG) ? STR_VIEW_PNG_FILE : @@ -1453,6 +1457,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan int install = (installable) ? ++n_opt : -1; int boot = (bootable) ? ++n_opt : -1; int script = (scriptable) ? ++n_opt : -1; + int luascript = (luascriptable) ? ++n_opt : -1; int font = (fontable) ? ++n_opt : -1; int translation = (translationable) ? ++n_opt : -1; int view = (viewable) ? ++n_opt : -1; @@ -1490,6 +1495,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (install > 0) optionstr[install-1] = STR_INSTALL_FIRM; if (boot > 0) optionstr[boot-1] = STR_BOOT_FIRM; if (script > 0) optionstr[script-1] = STR_EXECUTE_GM9_SCRIPT; + if (luascript > 0) optionstr[luascript-1] = STR_EXECUTE_LUA_SCRIPT; if (view > 0) optionstr[view-1] = STR_VIEW_PNG_FILE; if (font > 0) optionstr[font-1] = STR_SET_AS_ACTIVE_FONT; if (translation > 0) optionstr[translation-1] = STR_SET_AS_ACTIVE_LANGUAGE; @@ -2137,6 +2143,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan ClearScreenF(true, true, COLOR_STD_BG); return 0; } + else if (user_select == luascript) { // execute lua script + if (ShowPrompt(true, "%s\n%s", pathstr, STR_WARNING_DO_NOT_RUN_UNTRUSTED_SCRIPTS)) + ShowPrompt(false, "%s\n%s", pathstr, ExecuteLuaScript(file_path) ? STR_SCRIPT_EXECUTE_SUCCESS : STR_SCRIPT_EXECUTE_FAILURE); + GetDirContents(current_dir, current_path); + ClearScreenF(true, true, COLOR_STD_BG); + return 0; + } else if (user_select == font) { // set font u8* font = (u8*) malloc(0x20000); // arbitrary, should be enough by far if (!font) return 1; diff --git a/arm9/source/language.inl b/arm9/source/language.inl index 547f10b2c..38e684fea 100644 --- a/arm9/source/language.inl +++ b/arm9/source/language.inl @@ -183,6 +183,7 @@ STRING(AESKEYDB_OPTIONS, "AESkeydb options...") STRING(BUILD_X, "Build %s") STRING(NCCHINFO_OPTIONS, "NCCHinfo options...") STRING(EXECUTE_GM9_SCRIPT, "Execute GM9 script") +STRING(EXECUTE_LUA_SCRIPT, "Execute Lua script") STRING(FONT_OPTIONS, "Font options...") STRING(LANGUAGE_OPTIONS, "Language options...") STRING(VIEW_PNG_FILE, "View PNG file") diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c new file mode 100644 index 000000000..298dc3807 --- /dev/null +++ b/arm9/source/lua/gm9lua.c @@ -0,0 +1,127 @@ +#include "gm9lua.h" +#include "ui.h" +#include "ff.h" +#include "vff.h" +#include "fsutil.h" +#include "gm9ui.h" + +#define DEBUGSP ShowPrompt + +typedef struct GM9LuaLoadF { + int n; // pre-read characters + FIL f; + FRESULT res; + char buff[BUFSIZ]; +} GM9LuaLoadF; + +// similar to "getF" in lauxlib.c +static const char* GetF(lua_State* L, void* ud, size_t *size) { + GM9LuaLoadF* lf = (GM9LuaLoadF*)ud; + UINT br = 0; + (void)L; // unused + if (lf->n > 0) { // check for pre-read characters + *size = lf->n; // return those + lf->n = 0; + } else { + if (fvx_eof(&lf->f)) return NULL; + lf->res = fvx_read(&lf->f, lf->buff, BUFSIZ, &br); + *size = (size_t)br; + if (lf->res != FR_OK) return NULL; + } + return lf->buff; +} + +// similar to "errfile" in lauxlib.c +static int ErrFile(lua_State* L, const char* what, int fnameindex, FRESULT res) { + const char* filename = lua_tostring(L, fnameindex) + 1; + lua_pushfstring(L, "cannot %s %s:\nfatfs error %d", what, filename, res); + lua_remove(L, fnameindex); + return LUA_ERRFILE; +} + +void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) { + if (lua_gettop(L) != argcount) { + luaL_error(L, "bad number of arguments passed to '%s' (expected %d, got %d)", cmd, argcount); + } +} + +int LoadLuaFile(lua_State* L, const char *filename) { + GM9LuaLoadF lf; + lf.n = 0; + int status; + int fnameindex = lua_gettop(L) + 1; // index of filename on the stack + lua_pushfstring(L, "@%s", filename); + lf.res = fvx_open(&lf.f, filename, FA_READ | FA_OPEN_EXISTING); + if (lf.res != FR_OK) return ErrFile(L, "open", fnameindex, lf.res); + + status = lua_load(L, GetF, &lf, lua_tostring(L, -1), NULL); + fvx_close(&lf.f); + if (lf.res != FR_OK) { + lua_settop(L, fnameindex); + return ErrFile(L, "read", fnameindex, lf.res); + } + lua_remove(L, fnameindex); + return status; +} + +static const luaL_Reg gm9lualibs[] = { + // built-ins + {LUA_GNAME, luaopen_base}, + {LUA_LOADLIBNAME, luaopen_package}, + {LUA_COLIBNAME, luaopen_coroutine}, + {LUA_TABLIBNAME, luaopen_table}, + //{LUA_IOLIBNAME, luaopen_io}, + //{LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_UTF8LIBNAME, luaopen_utf8}, + {LUA_DBLIBNAME, luaopen_debug}, + + // gm9 custom + {GM9LUA_UILIBNAME, gm9lua_open_UI}, + + {NULL, NULL} +}; + +static int LuaShowPrompt(lua_State* L) { + bool ask = lua_toboolean(L, 1); + const char* text = lua_tostring(L, 2); + + bool ret = ShowPrompt(ask, "%s", text); + lua_pushboolean(L, ret); + return 1; +} + +static void loadlibs(lua_State* L) { + const luaL_Reg* lib; + for (lib = gm9lualibs; lib->func; lib++) { + luaL_requiref(L, lib->name, lib->func, 1); + lua_pop(L, 1); // remove lib from stack + } +} + +bool ExecuteLuaScript(const char* path_script) { + lua_State* L = luaL_newstate(); + loadlibs(L); + + lua_pushcfunction(L, LuaShowPrompt); + lua_setglobal(L, "ShowPrompt"); + + //int result = luaL_loadbuffer(L, script_buffer, script_size, path_script); + int result = LoadLuaFile(L, path_script); + //free(script_buffer); + if (result != LUA_OK) { + ShowPrompt(false, "Error during loading:\n%s", lua_tostring(L, -1)); + lua_close(L); + return false; + } + + if (lua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) { + ShowPrompt(false, "Error during execution:\n%s", lua_tostring(L, -1)); + lua_close(L); + return false; + } + + lua_close(L); + return true; +} diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h new file mode 100644 index 000000000..0b6f604e7 --- /dev/null +++ b/arm9/source/lua/gm9lua.h @@ -0,0 +1,14 @@ +#pragma once +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + +#include "common.h" +#include "scripting.h" + +#define LUASCRIPT_EXT "lua" +#define LUASCRIPT_MAX_SIZE STD_BUFFER_SIZE + +void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd); +int LoadLuaFile(lua_State* L, const char *filename); +bool ExecuteLuaScript(const char* path_script); diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c new file mode 100644 index 000000000..8463c9597 --- /dev/null +++ b/arm9/source/lua/gm9ui.c @@ -0,0 +1,40 @@ +#include "gm9ui.h" + +static int UI_ShowPrompt(lua_State* L) { + CheckLuaArgCount(L, 2, "ShowPrompt"); + bool ask = lua_toboolean(L, 1); + const char* text = lua_tostring(L, 2); + + bool ret = ShowPrompt(ask, "%s", text); + lua_pushboolean(L, ret); + return 1; +} + +static int UI_ShowSelectPrompt(lua_State* L) { + CheckLuaArgCount(L, 2, "ShowSelectPrompt"); + const char* text = lua_tostring(L, 2); + const char* options[16]; + + luaL_argcheck(L, lua_istable(L, 1), 1, "table expected"); + + lua_Integer opttablesize = luaL_len(L, 1); + luaL_argcheck(L, opttablesize <= 16, 1, "more than 16 options given"); + for (int i = 0; i < opttablesize; i++) { + lua_geti(L, 1, i + 1); + options[i] = lua_tostring(L, -1); + } + int result = ShowSelectPrompt(opttablesize, options, "%s", text); + lua_pushinteger(L, result); + return 1; +} + +static const luaL_Reg UIlib[] = { + {"ShowPrompt", UI_ShowPrompt}, + {"ShowSelectPrompt", UI_ShowSelectPrompt}, + {NULL, NULL} +}; + +int gm9lua_open_UI(lua_State* L) { + luaL_newlib(L, UIlib); + return 1; +} diff --git a/arm9/source/lua/gm9ui.h b/arm9/source/lua/gm9ui.h new file mode 100644 index 000000000..bf51f9411 --- /dev/null +++ b/arm9/source/lua/gm9ui.h @@ -0,0 +1,7 @@ +#pragma once +#include "gm9lua.h" +#include "ui.h" + +#define GM9LUA_UILIBNAME "UI" + +int gm9lua_open_UI(lua_State* L); diff --git a/arm9/source/lua/lauxlib.h.old b/arm9/source/lua/lauxlib.h.old new file mode 100644 index 000000000..5ae859bb5 --- /dev/null +++ b/arm9/source/lua/lauxlib.h.old @@ -0,0 +1,305 @@ +/* +** $Id: lauxlib.h $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include +#include + +// GM9Lua: include ff.h from fatfs for FIL * definition +#include "ff.h" + +#include "luaconf.h" +#include "lua.h" + + +/* global table */ +#define LUA_GNAME "_G" + + +typedef struct luaL_Buffer luaL_Buffer; + + +/* extra error code for 'luaL_loadfilex' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +/* key, in the registry, for table of loaded modules */ +#define LUA_LOADED_TABLE "_LOADED" + + +/* key, in the registry, for table of preloaded loaders */ +#define LUA_PRELOAD_TABLE "_PRELOAD" + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) + +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); +LUALIB_API int (luaL_execresult) (lua_State *L, int stat); + + +/* predefined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, + const char *mode); + +#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) + +LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); + +LUALIB_API void (luaL_addgsub) (luaL_Buffer *b, const char *s, + const char *p, const char *r); +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, + const char *p, const char *r); + +LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); + +LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); + +LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, + const char *msg, int level); + +LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, + lua_CFunction openf, int glb); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + + +#define luaL_newlibtable(L,l) \ + lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) + +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) + +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)(luai_likely(cond) || luaL_argerror(L, (arg), (extramsg)))) + +#define luaL_argexpected(L,cond,arg,tname) \ + ((void)(luai_likely(cond) || luaL_typeerror(L, (arg), (tname)))) + +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) + + +/* +** Perform arithmetic operations on lua_Integer values with wrap-around +** semantics, as the Lua core does. +*/ +#define luaL_intop(op,v1,v2) \ + ((lua_Integer)((lua_Unsigned)(v1) op (lua_Unsigned)(v2))) + + +/* push the value used to represent failure/error */ +#define luaL_pushfail(L) lua_pushnil(L) + + +/* +** Internal assertions for in-house debugging +*/ +#if !defined(lua_assert) + +#if defined LUAI_ASSERT + #include + #define lua_assert(c) assert(c) +#else + #define lua_assert(c) ((void)0) +#endif + +#endif + + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +struct luaL_Buffer { + char *b; /* buffer address */ + size_t size; /* buffer size */ + size_t n; /* number of characters in buffer */ + lua_State *L; + union { + LUAI_MAXALIGN; /* ensure maximum alignment for buffer */ + char b[LUAL_BUFFERSIZE]; /* initial buffer */ + } init; +}; + + +#define luaL_bufflen(bf) ((bf)->n) +#define luaL_buffaddr(bf) ((bf)->b) + + +#define luaL_addchar(B,c) \ + ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ + ((B)->b[(B)->n++] = (c))) + +#define luaL_addsize(B,s) ((B)->n += (s)) + +#define luaL_buffsub(B,s) ((B)->n -= (s)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); +LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); + +#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) + +/* }====================================================== */ + + + +/* +** {====================================================== +** File handles for IO library +** ======================================================= +*/ + +/* +** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and +** initial structure 'luaL_Stream' (it may contain other fields +** after that initial structure). +*/ + +#define LUA_FILEHANDLE "FILE*" + + +// GM9Lua: accept FIL * (fatfs) instead of FILE * (stdio) +typedef struct luaL_Stream { + FIL *f; /* stream (NULL for incompletely created streams) */ + lua_CFunction closef; /* to close stream (NULL for closed streams) */ +} luaL_Stream; + +/* }====================================================== */ + +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + +#endif + + diff --git a/arm9/source/lua/liolib.c.old b/arm9/source/lua/liolib.c.old new file mode 100644 index 000000000..9b124ad77 --- /dev/null +++ b/arm9/source/lua/liolib.c.old @@ -0,0 +1,810 @@ +/* +** $Id: liolib.c $ +** Standard I/O (and system) library +** See Copyright Notice in lua.h +*/ + +#define liolib_c +#define LUA_LIB + +#include "lprefix.h" + + +#include +#include +#include +#include +#include +#include + +#include "vff.h" + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + + + +/* +** Change this macro to accept other modes for 'fopen' besides +** the standard ones. +*/ +#if !defined(l_checkmode) + +/* accepted extensions to 'mode' in 'fopen' */ +#if !defined(L_MODEEXT) +#define L_MODEEXT "b" +#endif + +/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ +static int l_checkmode (const char *mode) { + return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && + (*mode != '+' || ((void)(++mode), 1)) && /* skip if char is '+' */ + (strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */ +} + +#endif + +/* +** {====================================================== +** l_popen spawns a new process connected to the current +** one through the file streams. +** ======================================================= +*/ + +// removed because this does not make sense for GM9 + +#if !defined(l_checkmodep) +/* By default, Lua accepts only "r" or "w" as valid modes */ +#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && m[1] == '\0') +#endif + +/* }====================================================== */ + +// getc for fvx_* +static inline char l_getc(FIL *f) { + char buf = EOF; + fvx_read(f, &buf, 1, NULL); + return buf; +} + +//#if !defined(l_getc) /* { */ +// +//#if defined(LUA_USE_POSIX) +//#define l_getc(f) getc_unlocked(f) +//#define l_lockfile(f) flockfile(f) +//#define l_unlockfile(f) funlockfile(f) +//#else +//#define l_getc(f) getc(f) +//#define l_lockfile(f) ((void)0) +//#define l_unlockfile(f) ((void)0) +//#endif +// +//#endif /* } */ + + +/* +** {====================================================== +** l_fseek: configuration for longer offsets +** ======================================================= +*/ + +static inline FSIZE_t l_fseek(FIL *f, FSIZE_t offset, int whence) { + FRESULT res; + switch(whence) { + case 0: + res = fvx_lseek(f, offset); + break; + case 1: + res = fvx_lseek(f, offset + fvx_tell(f)); + break; + case 2: + res = fvx_lseek(f, fvx_size(f) + offset); + break; + default: + break; + } + + return fvx_tell(f); +} +#define l_ftell(f) fvx_tell(f) +#define l_seeknum FSIZE_t + +//#if !defined(l_fseek) /* { */ +// +//#if defined(LUA_USE_POSIX) /* { */ +// +//#include +// +//#define l_fseek(f,o,w) fseeko(f,o,w) +//#define l_ftell(f) ftello(f) +//#define l_seeknum off_t +// +//#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \ +// && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ +// +///* Windows (but not DDK) and Visual C++ 2005 or higher */ +//#define l_fseek(f,o,w) _fseeki64(f,o,w) +//#define l_ftell(f) _ftelli64(f) +//#define l_seeknum __int64 +// +//#else /* }{ */ +// +///* ISO C definitions */ +//#define l_fseek(f,o,w) fseek(f,o,w) +//#define l_ftell(f) ftell(f) +//#define l_seeknum long +// +//#endif /* } */ +// +//#endif /* } */ + +/* }====================================================== */ + + + +#define IO_PREFIX "_IO_" +#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1) +#define IO_INPUT (IO_PREFIX "input") +#define IO_OUTPUT (IO_PREFIX "output") + + +typedef luaL_Stream LStream; + + +#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE)) + +#define isclosed(p) ((p)->closef == NULL) + + +static int io_type (lua_State *L) { + LStream *p; + luaL_checkany(L, 1); + p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE); + if (p == NULL) + luaL_pushfail(L); /* not a file */ + else if (isclosed(p)) + lua_pushliteral(L, "closed file"); + else + lua_pushliteral(L, "file"); + return 1; +} + + +static int fil_tostring (lua_State *L) { + LStream *p = tolstream(L); + if (isclosed(p)) + lua_pushliteral(L, "file (closed)"); + else + lua_pushfstring(L, "file (%p)", p->f); + return 1; +} + + +static FIL *tofile (lua_State *L) { + LStream *p = tolstream(L); + if (l_unlikely(isclosed(p))) + luaL_error(L, "attempt to use a closed file"); + lua_assert(p->f); + return p->f; +} + + +/* +** When creating file handles, always creates a 'closed' file handle +** before opening the actual file; so, if there is a memory error, the +** handle is in a consistent state. +*/ +static LStream *newprefile (lua_State *L) { + LStream *p = (LStream *)lua_newuserdatauv(L, sizeof(LStream), 0); + p->closef = NULL; /* mark file handle as 'closed' */ + luaL_setmetatable(L, LUA_FILEHANDLE); + return p; +} + + +/* +** Calls the 'close' function from a file handle. The 'volatile' avoids +** a bug in some versions of the Clang compiler (e.g., clang 3.0 for +** 32 bits). +*/ +static int aux_close (lua_State *L) { + LStream *p = tolstream(L); + volatile lua_CFunction cf = p->closef; + p->closef = NULL; /* mark stream as closed */ + return (*cf)(L); /* close it */ +} + + +static int fil_close (lua_State *L) { + tofile(L); /* make sure argument is an open stream */ + return aux_close(L); +} + + +static int io_close (lua_State *L) { + if (lua_isnone(L, 1)) /* no argument? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use default output */ + return fil_close(L); +} + + +static int fil_gc (lua_State *L) { + LStream *p = tolstream(L); + if (!isclosed(p) && p->f != NULL) + aux_close(L); /* ignore closed and incompletely open files */ + return 0; +} + + +/* +** function to close regular files +*/ +static int io_fclose (lua_State *L) { + LStream *p = tolstream(L); + FRESULT res = fvx_close(p->f); + return luaL_fileresult(L, (res == FR_OK), NULL); +} + + +static LStream *newfile (lua_State *L) { + LStream *p = newprefile(L); + p->f = NULL; + p->closef = &io_fclose; + return p; +} + + +static void opencheck (lua_State *L, const char *fname, const char *mode) { + LStream *p = newfile(L); + FRESULT res = fvx_open(p->f, fname, FA_READ | FA_WRITE); + if (res != FR_OK) + // note: %d is required here, not %i (lua_pushfstring does not take the same types as printf) + luaL_error(L, "cannot open file '%s' (%d)", fname, res); +} + + +static int io_open (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + LStream *p = newfile(L); + const char *md = mode; /* to traverse/check mode */ + luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); + FRESULT res = fvx_open(p->f, filename, FA_READ | FA_WRITE); + return (res != FR_OK) ? luaL_fileresult(L, 0, filename) : 1; +} + + +static int io_tmpfile (lua_State *L) { + luaL_error(L, "io.tmpfile is not yet implemented"); + //LStream *p = newfile(L); + //p->f = tmpfile(); + //return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1; +} + + +static FIL *getiofile (lua_State *L, const char *findex) { + LStream *p; + lua_getfield(L, LUA_REGISTRYINDEX, findex); + p = (LStream *)lua_touserdata(L, -1); + if (l_unlikely(isclosed(p))) + luaL_error(L, "default %s file is closed", findex + IOPREF_LEN); + return p->f; +} + + +static int g_iofile (lua_State *L, const char *f, const char *mode) { + if (!lua_isnoneornil(L, 1)) { + const char *filename = lua_tostring(L, 1); + if (filename) + opencheck(L, filename, mode); + else { + tofile(L); /* check that it's a valid file handle */ + lua_pushvalue(L, 1); + } + lua_setfield(L, LUA_REGISTRYINDEX, f); + } + /* return current value */ + lua_getfield(L, LUA_REGISTRYINDEX, f); + return 1; +} + + +static int io_input (lua_State *L) { + return g_iofile(L, IO_INPUT, "r"); +} + + +static int io_output (lua_State *L) { + return g_iofile(L, IO_OUTPUT, "w"); +} + + +static int io_readline (lua_State *L); + + +/* +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit +** in the limit for upvalues of a closure) +*/ +#define MAXARGLINE 250 + +/* +** Auxiliary function to create the iteration function for 'lines'. +** The iteration function is a closure over 'io_readline', with +** the following upvalues: +** 1) The file being read (first value in the stack) +** 2) the number of arguments to read +** 3) a boolean, true iff file has to be closed when finished ('toclose') +** *) a variable number of format arguments (rest of the stack) +*/ +static void aux_lines (lua_State *L, int toclose) { + int n = lua_gettop(L) - 1; /* number of arguments to read */ + luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); + lua_pushvalue(L, 1); /* file */ + lua_pushinteger(L, n); /* number of arguments to read */ + lua_pushboolean(L, toclose); /* close/not close file when finished */ + lua_rotate(L, 2, 3); /* move the three values to their positions */ + lua_pushcclosure(L, io_readline, 3 + n); +} + + +static int fil_lines (lua_State *L) { + tofile(L); /* check that it's a valid file handle */ + aux_lines(L, 0); + return 1; +} + + +/* +** Return an iteration function for 'io.lines'. If file has to be +** closed, also returns the file itself as a second result (to be +** closed as the state at the exit of a generic for). +*/ +static int io_lines (lua_State *L) { + int toclose; + if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ + if (lua_isnil(L, 1)) { /* no file name? */ + lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT); /* get default input */ + lua_replace(L, 1); /* put it at index 1 */ + tofile(L); /* check that it's a valid file handle */ + toclose = 0; /* do not close it after iteration */ + } + else { /* open a new file */ + const char *filename = luaL_checkstring(L, 1); + opencheck(L, filename, "r"); + lua_replace(L, 1); /* put file at index 1 */ + toclose = 1; /* close it after iteration */ + } + aux_lines(L, toclose); /* push iteration function */ + if (toclose) { + lua_pushnil(L); /* state */ + lua_pushnil(L); /* control */ + lua_pushvalue(L, 1); /* file is the to-be-closed variable (4th result) */ + return 4; + } + else + return 1; +} + + +/* +** {====================================================== +** READ +** ======================================================= +*/ + + +/* maximum length of a numeral */ +#if !defined (L_MAXLENNUM) +#define L_MAXLENNUM 200 +#endif + + +/* auxiliary structure used by 'read_number' */ +typedef struct { + FIL *f; /* file being read */ + int c; /* current character (look ahead) */ + int n; /* number of elements in buffer 'buff' */ + char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ +} RN; + + +/* +** Add current char to buffer (if not out of space) and read next one +*/ +static int nextc (RN *rn) { + if (l_unlikely(rn->n >= L_MAXLENNUM)) { /* buffer overflow? */ + rn->buff[0] = '\0'; /* invalidate result */ + return 0; /* fail */ + } + else { + rn->buff[rn->n++] = rn->c; /* save current char */ + rn->c = l_getc(rn->f); /* read next one */ + return 1; + } +} + + +/* +** Accept current char if it is in 'set' (of size 2) +*/ +static int test2 (RN *rn, const char *set) { + if (rn->c == set[0] || rn->c == set[1]) + return nextc(rn); + else return 0; +} + + +/* +** Read a sequence of (hex)digits +*/ +static int readdigits (RN *rn, int hex) { + int count = 0; + while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn)) + count++; + return count; +} + + +/* +** Read a number: first reads a valid prefix of a numeral into a buffer. +** Then it calls 'lua_stringtonumber' to check whether the format is +** correct and to convert it to a Lua number. +*/ +static int read_number (lua_State *L, FIL *f) { + RN rn; + int count = 0; + int hex = 0; + char decp[2]; + rn.f = f; rn.n = 0; + decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ + decp[1] = '.'; /* always accept a dot */ + l_lockfile(rn.f); + do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ + test2(&rn, "-+"); /* optional sign */ + if (test2(&rn, "00")) { + if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ + else count = 1; /* count initial '0' as a valid digit */ + } + count += readdigits(&rn, hex); /* integral part */ + if (test2(&rn, decp)) /* decimal point? */ + count += readdigits(&rn, hex); /* fractional part */ + if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */ + test2(&rn, "-+"); /* exponent sign */ + readdigits(&rn, 0); /* exponent digits */ + } + ungetc(rn.c, rn.f); /* unread look-ahead char */ + l_unlockfile(rn.f); + rn.buff[rn.n] = '\0'; /* finish string */ + if (l_likely(lua_stringtonumber(L, rn.buff))) + return 1; /* ok, it is a valid number */ + else { /* invalid format */ + lua_pushnil(L); /* "result" to be removed */ + return 0; /* read fails */ + } +} + + +static int test_eof (lua_State *L, FIL *f) { + // we aren't dealing with streams here, i think it's ok to just check size... + //int c = getc(f); + //ungetc(c, f); /* no-op when c == EOF */ + lua_pushliteral(L, ""); + //return (c != EOF); + // returns true if not eof... + return (fvx_tell(f) != fvx_size(f)); +} + + +static int read_line (lua_State *L, FIL *f, int chop) { + luaL_Buffer b; + int c; + luaL_buffinit(L, &b); + do { /* may need to read several chunks to get whole line */ + char *buff = luaL_prepbuffer(&b); /* preallocate buffer space */ + int i = 0; + l_lockfile(f); /* no memory errors can happen inside the lock */ + while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') + buff[i++] = c; /* read up to end of line or buffer limit */ + l_unlockfile(f); + luaL_addsize(&b, i); + } while (c != EOF && c != '\n'); /* repeat until end of line */ + if (!chop && c == '\n') /* want a newline and have one? */ + luaL_addchar(&b, c); /* add ending newline to result */ + luaL_pushresult(&b); /* close buffer */ + /* return ok if read something (either a newline or something else) */ + return (c == '\n' || lua_rawlen(L, -1) > 0); +} + + +static void read_all (lua_State *L, FIL *f) { + size_t nr; + UINT nr; + luaL_Buffer b; + luaL_buffinit(L, &b); + do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ + char *p = luaL_prepbuffer(&b); + FRESULT res = fvx_read(f, p, LUAL_BUFFERSIZE, &nr); + luaL_addsize(&b, nr); + } while (nr == LUAL_BUFFERSIZE); + luaL_pushresult(&b); /* close buffer */ +} + + +static int read_chars (lua_State *L, FIL *f, size_t n) { + size_t nr; /* number of chars actually read */ + char *p; + luaL_Buffer b; + luaL_buffinit(L, &b); + p = luaL_prepbuffsize(&b, n); /* prepare buffer to read whole block */ + FRESULT res = fvx_read(f, p, n, &nr); /* try to read 'n' chars */ + luaL_addsize(&b, nr); + luaL_pushresult(&b); /* close buffer */ + return (nr > 0); /* true iff read something */ +} + + +static int g_read (lua_State *L, FIL *f, int first) { + int nargs = lua_gettop(L) - 1; + int n, success; + //clearerr(f); + if (nargs == 0) { /* no arguments? */ + success = read_line(L, f, 1); + n = first + 1; /* to return 1 result */ + } + else { + /* ensure stack space for all results and for auxlib's buffer */ + luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); + success = 1; + for (n = first; nargs-- && success; n++) { + if (lua_type(L, n) == LUA_TNUMBER) { + size_t l = (size_t)luaL_checkinteger(L, n); + success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); + } + else { + const char *p = luaL_checkstring(L, n); + if (*p == '*') p++; /* skip optional '*' (for compatibility) */ + switch (*p) { + case 'n': /* number */ + success = read_number(L, f); + break; + case 'l': /* line */ + success = read_line(L, f, 1); + break; + case 'L': /* line with end-of-line */ + success = read_line(L, f, 0); + break; + case 'a': /* file */ + read_all(L, f); /* read entire file */ + success = 1; /* always success */ + break; + default: + return luaL_argerror(L, n, "invalid format"); + } + } + } + } + if (ferror(f)) + return luaL_fileresult(L, 0, NULL); + if (!success) { + lua_pop(L, 1); /* remove last result */ + luaL_pushfail(L); /* push nil instead */ + } + return n - first; +} + + +static int io_read (lua_State *L) { + return g_read(L, getiofile(L, IO_INPUT), 1); +} + + +static int fil_read (lua_State *L) { + return g_read(L, tofile(L), 2); +} + + +/* +** Iteration function for 'lines'. +*/ +static int io_readline (lua_State *L) { + LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); + int i; + int n = (int)lua_tointeger(L, lua_upvalueindex(2)); + if (isclosed(p)) /* file is already closed? */ + return luaL_error(L, "file is already closed"); + lua_settop(L , 1); + luaL_checkstack(L, n, "too many arguments"); + for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ + lua_pushvalue(L, lua_upvalueindex(3 + i)); + n = g_read(L, p->f, 2); /* 'n' is number of results */ + lua_assert(n > 0); /* should return at least a nil */ + if (lua_toboolean(L, -n)) /* read at least one value? */ + return n; /* return them */ + else { /* first result is false: EOF or error */ + if (n > 1) { /* is there error information? */ + /* 2nd result is error message */ + return luaL_error(L, "%s", lua_tostring(L, -n + 1)); + } + if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ + lua_settop(L, 0); /* clear stack */ + lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */ + aux_close(L); /* close it */ + } + return 0; + } +} + +/* }====================================================== */ + +static int g_write (lua_State *L, FIL *f, int arg) { + int nargs = lua_gettop(L) - arg; + int status = 1; + for (; nargs--; arg++) { + if (lua_type(L, arg) == LUA_TNUMBER) { + /* optimization: could be done exactly as for strings */ + int len = lua_isinteger(L, arg) + ? fprintf(f, LUA_INTEGER_FMT, + (LUAI_UACINT)lua_tointeger(L, arg)) + : fprintf(f, LUA_NUMBER_FMT, + (LUAI_UACNUMBER)lua_tonumber(L, arg)); + status = status && (len > 0); + } + else { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + status = status && (fwrite(s, sizeof(char), l, f) == l); + } + } + if (l_likely(status)) + return 1; /* file handle already on stack top */ + else return luaL_fileresult(L, status, NULL); +} + + +static int io_write (lua_State *L) { + return g_write(L, getiofile(L, IO_OUTPUT), 1); +} + + +static int fil_write (lua_State *L) { + FIL *f = tofile(L); + lua_pushvalue(L, 1); /* push file at the stack top (to be returned) */ + return g_write(L, f, 2); +} + + +static int fil_seek (lua_State *L) { + static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; + static const char *const modenames[] = {"set", "cur", "end", NULL}; + FIL *f = tofile(L); + int op = luaL_checkoption(L, 2, "cur", modenames); + lua_Integer p3 = luaL_optinteger(L, 3, 0); + l_seeknum offset = (l_seeknum)p3; + luaL_argcheck(L, (lua_Integer)offset == p3, 3, + "not an integer in proper range"); + op = l_fseek(f, offset, mode[op]); + if (l_unlikely(op)) + return luaL_fileresult(L, 0, NULL); /* error */ + else { + lua_pushinteger(L, (lua_Integer)l_ftell(f)); + return 1; + } +} + + +static int fil_setvbuf (lua_State *L) { + static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; + static const char *const modenames[] = {"no", "full", "line", NULL}; + FIL *f = tofile(L); + int op = luaL_checkoption(L, 2, NULL, modenames); + lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); + int res = setvbuf(f, NULL, mode[op], (size_t)sz); + return luaL_fileresult(L, res == 0, NULL); +} + + + +static int io_flush (lua_State *L) { + return luaL_fileresult(L, fvx_sync(getiofile(L, IO_OUTPUT)) == FR_OK, NULL); +} + + +static int fil_flush (lua_State *L) { + return luaL_fileresult(L, fvx_sync(tofile(L)) == 0, NULL); +} + + +/* +** functions for 'io' library +*/ +static const luaL_Reg iolib[] = { + {"close", io_close}, + {"flush", io_flush}, + {"input", io_input}, + {"lines", io_lines}, + {"open", io_open}, + {"output", io_output}, + {"read", io_read}, + {"tmpfile", io_tmpfile}, + {"type", io_type}, + {"write", io_write}, + {NULL, NULL} +}; + + +/* +** methods for file handles +*/ +static const luaL_Reg meth[] = { + {"read", fil_read}, + {"write", fil_write}, + {"lines", fil_lines}, + {"flush", fil_flush}, + {"seek", fil_seek}, + {"close", fil_close}, + {"setvbuf", fil_setvbuf}, + {NULL, NULL} +}; + + +/* +** metamethods for file handles +*/ +static const luaL_Reg metameth[] = { + {"__index", NULL}, /* place holder */ + {"__gc", fil_gc}, + {"__close", fil_gc}, + {"__tostring", fil_tostring}, + {NULL, NULL} +}; + + +static void createmeta (lua_State *L) { + luaL_newmetatable(L, LUA_FILEHANDLE); /* metatable for file handles */ + luaL_setfuncs(L, metameth, 0); /* add metamethods to new metatable */ + luaL_newlibtable(L, meth); /* create method table */ + luaL_setfuncs(L, meth, 0); /* add file methods to method table */ + lua_setfield(L, -2, "__index"); /* metatable.__index = method table */ + lua_pop(L, 1); /* pop metatable */ +} + + +/* +** function to (not) close the standard files stdin, stdout, and stderr +*/ +static int io_noclose (lua_State *L) { + LStream *p = tolstream(L); + p->closef = &io_noclose; /* keep file opened */ + luaL_pushfail(L); + lua_pushliteral(L, "cannot close standard file"); + return 2; +} + + +//static void createstdfile (lua_State *L, FIL *f, const char *k, +// const char *fname) { +// LStream *p = newprefile(L); +// p->f = f; +// p->closef = &io_noclose; +// if (k != NULL) { +// lua_pushvalue(L, -1); +// lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */ +// } +// lua_setfield(L, -2, fname); /* add file to module */ +//} + + +LUAMOD_API int luaopen_io (lua_State *L) { + luaL_newlib(L, iolib); /* new module */ + createmeta(L); + /* create (and set) default files */ + //createstdfile(L, stdin, IO_INPUT, "stdin"); + //createstdfile(L, stdout, IO_OUTPUT, "stdout"); + //createstdfile(L, stderr, NULL, "stderr"); + return 1; +} + diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index d62b9680e..f7c175859 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -18,9 +18,6 @@ #include "ips.h" #include "bps.h" #include "pxi.h" -#include "lua.h" -#include "lauxlib.h" -#include "lualib.h" #define _MAX_ARGS 4 @@ -131,7 +128,6 @@ typedef enum { CMD_ID_REBOOT, CMD_ID_POWEROFF, CMD_ID_BKPT, - CMD_ID_LUARUN } cmd_id; typedef struct { @@ -206,8 +202,7 @@ static const Gm9ScriptCmd cmd_list[] = { { CMD_ID_NEXTEMU , "nextemu" , 0, 0 }, { CMD_ID_REBOOT , "reboot" , 0, 0 }, { CMD_ID_POWEROFF, "poweroff", 0, 0 }, - { CMD_ID_BKPT , "bkpt" , 0, 0 }, - { CMD_ID_LUARUN , "luarun" , 1, 0 } + { CMD_ID_BKPT , "bkpt" , 0, 0 } }; // global vars for preview @@ -864,16 +859,6 @@ bool parse_line(const char* line_start, const char* line_end, cmd_id* cmdid, u32 return false; } -int Thingy(lua_State *L) { - const char* mystr = luaL_checkstring(L, 1); - // so this would normally be done when a loop in ExecuteGM9Script continues, - // but because of how i made this luarun command, i need to manually update the top screen - ClearScreen(TOP_SCREEN, COLOR_STD_BG); - DrawStringCenter(TOP_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", mystr); - ShowPrompt(false, "thingy called: %s\n", mystr); - return 0; -} - bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { bool ret = true; // true unless some cmd messes up @@ -1556,61 +1541,6 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { bkpt; while(1); } - else if (id == CMD_ID_LUARUN) { - luaL_Reg loadedlibs[] = { - {LUA_GNAME, luaopen_base}, - //{LUA_LOADLIBNAME, luaopen_package}, - {LUA_COLIBNAME, luaopen_coroutine}, - {LUA_TABLIBNAME, luaopen_table}, - //{LUA_IOLIBNAME, luaopen_io}, - //{LUA_OSLIBNAME, luaopen_os}, - {LUA_STRLIBNAME, luaopen_string}, - {LUA_MATHLIBNAME, luaopen_math}, - {LUA_UTF8LIBNAME, luaopen_utf8}, - //{LUA_DBLIBNAME, luaopen_debug}, - {NULL, NULL} - }; - // this code is awful - //ShowPrompt(false, "Make lua state"); - lua_State *L = luaL_newstate(); - // NOTE most libs will most likely not work right away because GM9 is a very weird environment - //luaL_openlibs(L); - - // this will require a bunch of other funcs defined - const luaL_Reg *lib; - /* "require" functions from 'loadedlibs' and set results to global table */ - for (lib = loadedlibs; lib->func; lib++) { - //ShowPrompt(false, "Loading %s", lib->name); - luaL_requiref(L, lib->name, lib->func, 1); - lua_pop(L, 1); /* remove lib */ - } - - lua_pushcfunction(L, Thingy); - lua_setglobal(L, "thingy"); - - char* text = calloc(1, STD_BUFFER_SIZE); - if (!text) return false; - - //ShowPrompt(false, "Reading data..."); - - size_t flen = FileGetData(argv[0], text, STD_BUFFER_SIZE - 1, 0); - - //ShowPrompt(false, "Read %s size %zu", argv[0], flen); - - if (luaL_dostring(L, text) == LUA_OK) { - lua_pop(L, lua_gettop(L)); - //ShowPrompt(false, "Success"); - } else { - ClearScreen(TOP_SCREEN, COLOR_STD_BG); - DrawStringCenter(TOP_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", lua_tostring(L, -1)); - ShowPrompt(false, "%s", lua_tostring(L, -1)); - } - - //ShowPrompt(false, "Closing lua"); - - lua_close(L); - free(text); - } else { // command not recognized / bad number of arguments ret = false; if (err_str) snprintf(err_str, _ERR_STR_LEN, "%s", STR_SCRIPTERR_UNKNOWN_ERROR); diff --git a/init.lua b/init.lua index fc3b2f1d6..7cdfc4d38 100755 --- a/init.lua +++ b/init.lua @@ -1,12 +1,16 @@ -thingy("A test of your reflexes!") -thingy("math.sin(3): "..tostring(math.sin(3))) -thingy("3 ^ 2: "..tostring(3 ^ 2)) +UI.ShowPrompt(false, "math.sin(3): "..tostring(math.sin(3))) +UI.ShowPrompt(false, "3 ^ 2: "..tostring(3 ^ 2)) -thingy(string.sub("freeman", 5)) +options = {'Dustbowl', 'Granary', 'Gravel Pit', 'Well', '2Fort', 'Hydro'} +result = UI.ShowSelectPrompt(options, "Choose one...") +UI.ShowPrompt(false, "You chose: "..tostring(result)..", or "..options[result]) + +res = UI.ShowPrompt(true, "I am asking you...") +UI.ShowPrompt(false, "I got: "..tostring(res)) words = 'black,mesa' for word in string.gmatch(words, '([^,]+)') do - thingy("gmatch test: "..word) + UI.ShowPrompt(false, "gmatch test: "..word) end -- this is about it... can't even do io yet diff --git a/resources/languages/source.json b/resources/languages/source.json index 79be425ff..90445fdef 100644 --- a/resources/languages/source.json +++ b/resources/languages/source.json @@ -181,6 +181,7 @@ "BUILD_X": "Build %s", "NCCHINFO_OPTIONS": "NCCHinfo options...", "EXECUTE_GM9_SCRIPT": "Execute GM9 script", + "EXECUTE_GM9_SCRIPT": "Execute Lua script", "FONT_OPTIONS": "Font options...", "LANGUAGE_OPTIONS": "Language options...", "VIEW_PNG_FILE": "View PNG file", From a3b7c92ddf05826c0ec8e92fe977e8ef172605e5 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Mon, 24 Jul 2023 06:06:59 -0700 Subject: [PATCH 014/124] remove old attempts at editing lauxlib and liolib --- arm9/source/lua/lauxlib.h.old | 305 ------------- arm9/source/lua/liolib.c.old | 810 ---------------------------------- 2 files changed, 1115 deletions(-) delete mode 100644 arm9/source/lua/lauxlib.h.old delete mode 100644 arm9/source/lua/liolib.c.old diff --git a/arm9/source/lua/lauxlib.h.old b/arm9/source/lua/lauxlib.h.old deleted file mode 100644 index 5ae859bb5..000000000 --- a/arm9/source/lua/lauxlib.h.old +++ /dev/null @@ -1,305 +0,0 @@ -/* -** $Id: lauxlib.h $ -** Auxiliary functions for building Lua libraries -** See Copyright Notice in lua.h -*/ - - -#ifndef lauxlib_h -#define lauxlib_h - - -#include -#include - -// GM9Lua: include ff.h from fatfs for FIL * definition -#include "ff.h" - -#include "luaconf.h" -#include "lua.h" - - -/* global table */ -#define LUA_GNAME "_G" - - -typedef struct luaL_Buffer luaL_Buffer; - - -/* extra error code for 'luaL_loadfilex' */ -#define LUA_ERRFILE (LUA_ERRERR+1) - - -/* key, in the registry, for table of loaded modules */ -#define LUA_LOADED_TABLE "_LOADED" - - -/* key, in the registry, for table of preloaded loaders */ -#define LUA_PRELOAD_TABLE "_PRELOAD" - - -typedef struct luaL_Reg { - const char *name; - lua_CFunction func; -} luaL_Reg; - - -#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) - -LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); -#define luaL_checkversion(L) \ - luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) - -LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); -LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); -LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); -LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); -LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname); -LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, - size_t *l); -LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, - const char *def, size_t *l); -LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); -LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); - -LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); -LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, - lua_Integer def); - -LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); -LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); -LUALIB_API void (luaL_checkany) (lua_State *L, int arg); - -LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); -LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); -LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); -LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); - -LUALIB_API void (luaL_where) (lua_State *L, int lvl); -LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); - -LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, - const char *const lst[]); - -LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); -LUALIB_API int (luaL_execresult) (lua_State *L, int stat); - - -/* predefined references */ -#define LUA_NOREF (-2) -#define LUA_REFNIL (-1) - -LUALIB_API int (luaL_ref) (lua_State *L, int t); -LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); - -LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, - const char *mode); - -#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) - -LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, - const char *name, const char *mode); -LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); - -LUALIB_API lua_State *(luaL_newstate) (void); - -LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); - -LUALIB_API void (luaL_addgsub) (luaL_Buffer *b, const char *s, - const char *p, const char *r); -LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, - const char *p, const char *r); - -LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); - -LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); - -LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, - const char *msg, int level); - -LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, - lua_CFunction openf, int glb); - -/* -** =============================================================== -** some useful macros -** =============================================================== -*/ - - -#define luaL_newlibtable(L,l) \ - lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) - -#define luaL_newlib(L,l) \ - (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) - -#define luaL_argcheck(L, cond,arg,extramsg) \ - ((void)(luai_likely(cond) || luaL_argerror(L, (arg), (extramsg)))) - -#define luaL_argexpected(L,cond,arg,tname) \ - ((void)(luai_likely(cond) || luaL_typeerror(L, (arg), (tname)))) - -#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) -#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) - -#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) - -#define luaL_dofile(L, fn) \ - (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) - -#define luaL_dostring(L, s) \ - (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) - -#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) - -#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) - -#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) - - -/* -** Perform arithmetic operations on lua_Integer values with wrap-around -** semantics, as the Lua core does. -*/ -#define luaL_intop(op,v1,v2) \ - ((lua_Integer)((lua_Unsigned)(v1) op (lua_Unsigned)(v2))) - - -/* push the value used to represent failure/error */ -#define luaL_pushfail(L) lua_pushnil(L) - - -/* -** Internal assertions for in-house debugging -*/ -#if !defined(lua_assert) - -#if defined LUAI_ASSERT - #include - #define lua_assert(c) assert(c) -#else - #define lua_assert(c) ((void)0) -#endif - -#endif - - - -/* -** {====================================================== -** Generic Buffer manipulation -** ======================================================= -*/ - -struct luaL_Buffer { - char *b; /* buffer address */ - size_t size; /* buffer size */ - size_t n; /* number of characters in buffer */ - lua_State *L; - union { - LUAI_MAXALIGN; /* ensure maximum alignment for buffer */ - char b[LUAL_BUFFERSIZE]; /* initial buffer */ - } init; -}; - - -#define luaL_bufflen(bf) ((bf)->n) -#define luaL_buffaddr(bf) ((bf)->b) - - -#define luaL_addchar(B,c) \ - ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ - ((B)->b[(B)->n++] = (c))) - -#define luaL_addsize(B,s) ((B)->n += (s)) - -#define luaL_buffsub(B,s) ((B)->n -= (s)) - -LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); -LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); -LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); -LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); -LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); -LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); -LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); -LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); - -#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) - -/* }====================================================== */ - - - -/* -** {====================================================== -** File handles for IO library -** ======================================================= -*/ - -/* -** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and -** initial structure 'luaL_Stream' (it may contain other fields -** after that initial structure). -*/ - -#define LUA_FILEHANDLE "FILE*" - - -// GM9Lua: accept FIL * (fatfs) instead of FILE * (stdio) -typedef struct luaL_Stream { - FIL *f; /* stream (NULL for incompletely created streams) */ - lua_CFunction closef; /* to close stream (NULL for closed streams) */ -} luaL_Stream; - -/* }====================================================== */ - -/* -** {================================================================== -** "Abstraction Layer" for basic report of messages and errors -** =================================================================== -*/ - -/* print a string */ -#if !defined(lua_writestring) -#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) -#endif - -/* print a newline and flush the output */ -#if !defined(lua_writeline) -#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) -#endif - -/* print an error message */ -#if !defined(lua_writestringerror) -#define lua_writestringerror(s,p) \ - (fprintf(stderr, (s), (p)), fflush(stderr)) -#endif - -/* }================================================================== */ - - -/* -** {============================================================ -** Compatibility with deprecated conversions -** ============================================================= -*/ -#if defined(LUA_COMPAT_APIINTCASTS) - -#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) -#define luaL_optunsigned(L,a,d) \ - ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) - -#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) -#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) - -#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) -#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) - -#endif -/* }============================================================ */ - - - -#endif - - diff --git a/arm9/source/lua/liolib.c.old b/arm9/source/lua/liolib.c.old deleted file mode 100644 index 9b124ad77..000000000 --- a/arm9/source/lua/liolib.c.old +++ /dev/null @@ -1,810 +0,0 @@ -/* -** $Id: liolib.c $ -** Standard I/O (and system) library -** See Copyright Notice in lua.h -*/ - -#define liolib_c -#define LUA_LIB - -#include "lprefix.h" - - -#include -#include -#include -#include -#include -#include - -#include "vff.h" - -#include "lua.h" - -#include "lauxlib.h" -#include "lualib.h" - - - - -/* -** Change this macro to accept other modes for 'fopen' besides -** the standard ones. -*/ -#if !defined(l_checkmode) - -/* accepted extensions to 'mode' in 'fopen' */ -#if !defined(L_MODEEXT) -#define L_MODEEXT "b" -#endif - -/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ -static int l_checkmode (const char *mode) { - return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && - (*mode != '+' || ((void)(++mode), 1)) && /* skip if char is '+' */ - (strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */ -} - -#endif - -/* -** {====================================================== -** l_popen spawns a new process connected to the current -** one through the file streams. -** ======================================================= -*/ - -// removed because this does not make sense for GM9 - -#if !defined(l_checkmodep) -/* By default, Lua accepts only "r" or "w" as valid modes */ -#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && m[1] == '\0') -#endif - -/* }====================================================== */ - -// getc for fvx_* -static inline char l_getc(FIL *f) { - char buf = EOF; - fvx_read(f, &buf, 1, NULL); - return buf; -} - -//#if !defined(l_getc) /* { */ -// -//#if defined(LUA_USE_POSIX) -//#define l_getc(f) getc_unlocked(f) -//#define l_lockfile(f) flockfile(f) -//#define l_unlockfile(f) funlockfile(f) -//#else -//#define l_getc(f) getc(f) -//#define l_lockfile(f) ((void)0) -//#define l_unlockfile(f) ((void)0) -//#endif -// -//#endif /* } */ - - -/* -** {====================================================== -** l_fseek: configuration for longer offsets -** ======================================================= -*/ - -static inline FSIZE_t l_fseek(FIL *f, FSIZE_t offset, int whence) { - FRESULT res; - switch(whence) { - case 0: - res = fvx_lseek(f, offset); - break; - case 1: - res = fvx_lseek(f, offset + fvx_tell(f)); - break; - case 2: - res = fvx_lseek(f, fvx_size(f) + offset); - break; - default: - break; - } - - return fvx_tell(f); -} -#define l_ftell(f) fvx_tell(f) -#define l_seeknum FSIZE_t - -//#if !defined(l_fseek) /* { */ -// -//#if defined(LUA_USE_POSIX) /* { */ -// -//#include -// -//#define l_fseek(f,o,w) fseeko(f,o,w) -//#define l_ftell(f) ftello(f) -//#define l_seeknum off_t -// -//#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \ -// && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ -// -///* Windows (but not DDK) and Visual C++ 2005 or higher */ -//#define l_fseek(f,o,w) _fseeki64(f,o,w) -//#define l_ftell(f) _ftelli64(f) -//#define l_seeknum __int64 -// -//#else /* }{ */ -// -///* ISO C definitions */ -//#define l_fseek(f,o,w) fseek(f,o,w) -//#define l_ftell(f) ftell(f) -//#define l_seeknum long -// -//#endif /* } */ -// -//#endif /* } */ - -/* }====================================================== */ - - - -#define IO_PREFIX "_IO_" -#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1) -#define IO_INPUT (IO_PREFIX "input") -#define IO_OUTPUT (IO_PREFIX "output") - - -typedef luaL_Stream LStream; - - -#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE)) - -#define isclosed(p) ((p)->closef == NULL) - - -static int io_type (lua_State *L) { - LStream *p; - luaL_checkany(L, 1); - p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE); - if (p == NULL) - luaL_pushfail(L); /* not a file */ - else if (isclosed(p)) - lua_pushliteral(L, "closed file"); - else - lua_pushliteral(L, "file"); - return 1; -} - - -static int fil_tostring (lua_State *L) { - LStream *p = tolstream(L); - if (isclosed(p)) - lua_pushliteral(L, "file (closed)"); - else - lua_pushfstring(L, "file (%p)", p->f); - return 1; -} - - -static FIL *tofile (lua_State *L) { - LStream *p = tolstream(L); - if (l_unlikely(isclosed(p))) - luaL_error(L, "attempt to use a closed file"); - lua_assert(p->f); - return p->f; -} - - -/* -** When creating file handles, always creates a 'closed' file handle -** before opening the actual file; so, if there is a memory error, the -** handle is in a consistent state. -*/ -static LStream *newprefile (lua_State *L) { - LStream *p = (LStream *)lua_newuserdatauv(L, sizeof(LStream), 0); - p->closef = NULL; /* mark file handle as 'closed' */ - luaL_setmetatable(L, LUA_FILEHANDLE); - return p; -} - - -/* -** Calls the 'close' function from a file handle. The 'volatile' avoids -** a bug in some versions of the Clang compiler (e.g., clang 3.0 for -** 32 bits). -*/ -static int aux_close (lua_State *L) { - LStream *p = tolstream(L); - volatile lua_CFunction cf = p->closef; - p->closef = NULL; /* mark stream as closed */ - return (*cf)(L); /* close it */ -} - - -static int fil_close (lua_State *L) { - tofile(L); /* make sure argument is an open stream */ - return aux_close(L); -} - - -static int io_close (lua_State *L) { - if (lua_isnone(L, 1)) /* no argument? */ - lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use default output */ - return fil_close(L); -} - - -static int fil_gc (lua_State *L) { - LStream *p = tolstream(L); - if (!isclosed(p) && p->f != NULL) - aux_close(L); /* ignore closed and incompletely open files */ - return 0; -} - - -/* -** function to close regular files -*/ -static int io_fclose (lua_State *L) { - LStream *p = tolstream(L); - FRESULT res = fvx_close(p->f); - return luaL_fileresult(L, (res == FR_OK), NULL); -} - - -static LStream *newfile (lua_State *L) { - LStream *p = newprefile(L); - p->f = NULL; - p->closef = &io_fclose; - return p; -} - - -static void opencheck (lua_State *L, const char *fname, const char *mode) { - LStream *p = newfile(L); - FRESULT res = fvx_open(p->f, fname, FA_READ | FA_WRITE); - if (res != FR_OK) - // note: %d is required here, not %i (lua_pushfstring does not take the same types as printf) - luaL_error(L, "cannot open file '%s' (%d)", fname, res); -} - - -static int io_open (lua_State *L) { - const char *filename = luaL_checkstring(L, 1); - const char *mode = luaL_optstring(L, 2, "r"); - LStream *p = newfile(L); - const char *md = mode; /* to traverse/check mode */ - luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); - FRESULT res = fvx_open(p->f, filename, FA_READ | FA_WRITE); - return (res != FR_OK) ? luaL_fileresult(L, 0, filename) : 1; -} - - -static int io_tmpfile (lua_State *L) { - luaL_error(L, "io.tmpfile is not yet implemented"); - //LStream *p = newfile(L); - //p->f = tmpfile(); - //return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1; -} - - -static FIL *getiofile (lua_State *L, const char *findex) { - LStream *p; - lua_getfield(L, LUA_REGISTRYINDEX, findex); - p = (LStream *)lua_touserdata(L, -1); - if (l_unlikely(isclosed(p))) - luaL_error(L, "default %s file is closed", findex + IOPREF_LEN); - return p->f; -} - - -static int g_iofile (lua_State *L, const char *f, const char *mode) { - if (!lua_isnoneornil(L, 1)) { - const char *filename = lua_tostring(L, 1); - if (filename) - opencheck(L, filename, mode); - else { - tofile(L); /* check that it's a valid file handle */ - lua_pushvalue(L, 1); - } - lua_setfield(L, LUA_REGISTRYINDEX, f); - } - /* return current value */ - lua_getfield(L, LUA_REGISTRYINDEX, f); - return 1; -} - - -static int io_input (lua_State *L) { - return g_iofile(L, IO_INPUT, "r"); -} - - -static int io_output (lua_State *L) { - return g_iofile(L, IO_OUTPUT, "w"); -} - - -static int io_readline (lua_State *L); - - -/* -** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit -** in the limit for upvalues of a closure) -*/ -#define MAXARGLINE 250 - -/* -** Auxiliary function to create the iteration function for 'lines'. -** The iteration function is a closure over 'io_readline', with -** the following upvalues: -** 1) The file being read (first value in the stack) -** 2) the number of arguments to read -** 3) a boolean, true iff file has to be closed when finished ('toclose') -** *) a variable number of format arguments (rest of the stack) -*/ -static void aux_lines (lua_State *L, int toclose) { - int n = lua_gettop(L) - 1; /* number of arguments to read */ - luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); - lua_pushvalue(L, 1); /* file */ - lua_pushinteger(L, n); /* number of arguments to read */ - lua_pushboolean(L, toclose); /* close/not close file when finished */ - lua_rotate(L, 2, 3); /* move the three values to their positions */ - lua_pushcclosure(L, io_readline, 3 + n); -} - - -static int fil_lines (lua_State *L) { - tofile(L); /* check that it's a valid file handle */ - aux_lines(L, 0); - return 1; -} - - -/* -** Return an iteration function for 'io.lines'. If file has to be -** closed, also returns the file itself as a second result (to be -** closed as the state at the exit of a generic for). -*/ -static int io_lines (lua_State *L) { - int toclose; - if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ - if (lua_isnil(L, 1)) { /* no file name? */ - lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT); /* get default input */ - lua_replace(L, 1); /* put it at index 1 */ - tofile(L); /* check that it's a valid file handle */ - toclose = 0; /* do not close it after iteration */ - } - else { /* open a new file */ - const char *filename = luaL_checkstring(L, 1); - opencheck(L, filename, "r"); - lua_replace(L, 1); /* put file at index 1 */ - toclose = 1; /* close it after iteration */ - } - aux_lines(L, toclose); /* push iteration function */ - if (toclose) { - lua_pushnil(L); /* state */ - lua_pushnil(L); /* control */ - lua_pushvalue(L, 1); /* file is the to-be-closed variable (4th result) */ - return 4; - } - else - return 1; -} - - -/* -** {====================================================== -** READ -** ======================================================= -*/ - - -/* maximum length of a numeral */ -#if !defined (L_MAXLENNUM) -#define L_MAXLENNUM 200 -#endif - - -/* auxiliary structure used by 'read_number' */ -typedef struct { - FIL *f; /* file being read */ - int c; /* current character (look ahead) */ - int n; /* number of elements in buffer 'buff' */ - char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ -} RN; - - -/* -** Add current char to buffer (if not out of space) and read next one -*/ -static int nextc (RN *rn) { - if (l_unlikely(rn->n >= L_MAXLENNUM)) { /* buffer overflow? */ - rn->buff[0] = '\0'; /* invalidate result */ - return 0; /* fail */ - } - else { - rn->buff[rn->n++] = rn->c; /* save current char */ - rn->c = l_getc(rn->f); /* read next one */ - return 1; - } -} - - -/* -** Accept current char if it is in 'set' (of size 2) -*/ -static int test2 (RN *rn, const char *set) { - if (rn->c == set[0] || rn->c == set[1]) - return nextc(rn); - else return 0; -} - - -/* -** Read a sequence of (hex)digits -*/ -static int readdigits (RN *rn, int hex) { - int count = 0; - while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn)) - count++; - return count; -} - - -/* -** Read a number: first reads a valid prefix of a numeral into a buffer. -** Then it calls 'lua_stringtonumber' to check whether the format is -** correct and to convert it to a Lua number. -*/ -static int read_number (lua_State *L, FIL *f) { - RN rn; - int count = 0; - int hex = 0; - char decp[2]; - rn.f = f; rn.n = 0; - decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ - decp[1] = '.'; /* always accept a dot */ - l_lockfile(rn.f); - do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ - test2(&rn, "-+"); /* optional sign */ - if (test2(&rn, "00")) { - if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ - else count = 1; /* count initial '0' as a valid digit */ - } - count += readdigits(&rn, hex); /* integral part */ - if (test2(&rn, decp)) /* decimal point? */ - count += readdigits(&rn, hex); /* fractional part */ - if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */ - test2(&rn, "-+"); /* exponent sign */ - readdigits(&rn, 0); /* exponent digits */ - } - ungetc(rn.c, rn.f); /* unread look-ahead char */ - l_unlockfile(rn.f); - rn.buff[rn.n] = '\0'; /* finish string */ - if (l_likely(lua_stringtonumber(L, rn.buff))) - return 1; /* ok, it is a valid number */ - else { /* invalid format */ - lua_pushnil(L); /* "result" to be removed */ - return 0; /* read fails */ - } -} - - -static int test_eof (lua_State *L, FIL *f) { - // we aren't dealing with streams here, i think it's ok to just check size... - //int c = getc(f); - //ungetc(c, f); /* no-op when c == EOF */ - lua_pushliteral(L, ""); - //return (c != EOF); - // returns true if not eof... - return (fvx_tell(f) != fvx_size(f)); -} - - -static int read_line (lua_State *L, FIL *f, int chop) { - luaL_Buffer b; - int c; - luaL_buffinit(L, &b); - do { /* may need to read several chunks to get whole line */ - char *buff = luaL_prepbuffer(&b); /* preallocate buffer space */ - int i = 0; - l_lockfile(f); /* no memory errors can happen inside the lock */ - while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') - buff[i++] = c; /* read up to end of line or buffer limit */ - l_unlockfile(f); - luaL_addsize(&b, i); - } while (c != EOF && c != '\n'); /* repeat until end of line */ - if (!chop && c == '\n') /* want a newline and have one? */ - luaL_addchar(&b, c); /* add ending newline to result */ - luaL_pushresult(&b); /* close buffer */ - /* return ok if read something (either a newline or something else) */ - return (c == '\n' || lua_rawlen(L, -1) > 0); -} - - -static void read_all (lua_State *L, FIL *f) { - size_t nr; - UINT nr; - luaL_Buffer b; - luaL_buffinit(L, &b); - do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ - char *p = luaL_prepbuffer(&b); - FRESULT res = fvx_read(f, p, LUAL_BUFFERSIZE, &nr); - luaL_addsize(&b, nr); - } while (nr == LUAL_BUFFERSIZE); - luaL_pushresult(&b); /* close buffer */ -} - - -static int read_chars (lua_State *L, FIL *f, size_t n) { - size_t nr; /* number of chars actually read */ - char *p; - luaL_Buffer b; - luaL_buffinit(L, &b); - p = luaL_prepbuffsize(&b, n); /* prepare buffer to read whole block */ - FRESULT res = fvx_read(f, p, n, &nr); /* try to read 'n' chars */ - luaL_addsize(&b, nr); - luaL_pushresult(&b); /* close buffer */ - return (nr > 0); /* true iff read something */ -} - - -static int g_read (lua_State *L, FIL *f, int first) { - int nargs = lua_gettop(L) - 1; - int n, success; - //clearerr(f); - if (nargs == 0) { /* no arguments? */ - success = read_line(L, f, 1); - n = first + 1; /* to return 1 result */ - } - else { - /* ensure stack space for all results and for auxlib's buffer */ - luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); - success = 1; - for (n = first; nargs-- && success; n++) { - if (lua_type(L, n) == LUA_TNUMBER) { - size_t l = (size_t)luaL_checkinteger(L, n); - success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); - } - else { - const char *p = luaL_checkstring(L, n); - if (*p == '*') p++; /* skip optional '*' (for compatibility) */ - switch (*p) { - case 'n': /* number */ - success = read_number(L, f); - break; - case 'l': /* line */ - success = read_line(L, f, 1); - break; - case 'L': /* line with end-of-line */ - success = read_line(L, f, 0); - break; - case 'a': /* file */ - read_all(L, f); /* read entire file */ - success = 1; /* always success */ - break; - default: - return luaL_argerror(L, n, "invalid format"); - } - } - } - } - if (ferror(f)) - return luaL_fileresult(L, 0, NULL); - if (!success) { - lua_pop(L, 1); /* remove last result */ - luaL_pushfail(L); /* push nil instead */ - } - return n - first; -} - - -static int io_read (lua_State *L) { - return g_read(L, getiofile(L, IO_INPUT), 1); -} - - -static int fil_read (lua_State *L) { - return g_read(L, tofile(L), 2); -} - - -/* -** Iteration function for 'lines'. -*/ -static int io_readline (lua_State *L) { - LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); - int i; - int n = (int)lua_tointeger(L, lua_upvalueindex(2)); - if (isclosed(p)) /* file is already closed? */ - return luaL_error(L, "file is already closed"); - lua_settop(L , 1); - luaL_checkstack(L, n, "too many arguments"); - for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ - lua_pushvalue(L, lua_upvalueindex(3 + i)); - n = g_read(L, p->f, 2); /* 'n' is number of results */ - lua_assert(n > 0); /* should return at least a nil */ - if (lua_toboolean(L, -n)) /* read at least one value? */ - return n; /* return them */ - else { /* first result is false: EOF or error */ - if (n > 1) { /* is there error information? */ - /* 2nd result is error message */ - return luaL_error(L, "%s", lua_tostring(L, -n + 1)); - } - if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ - lua_settop(L, 0); /* clear stack */ - lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */ - aux_close(L); /* close it */ - } - return 0; - } -} - -/* }====================================================== */ - -static int g_write (lua_State *L, FIL *f, int arg) { - int nargs = lua_gettop(L) - arg; - int status = 1; - for (; nargs--; arg++) { - if (lua_type(L, arg) == LUA_TNUMBER) { - /* optimization: could be done exactly as for strings */ - int len = lua_isinteger(L, arg) - ? fprintf(f, LUA_INTEGER_FMT, - (LUAI_UACINT)lua_tointeger(L, arg)) - : fprintf(f, LUA_NUMBER_FMT, - (LUAI_UACNUMBER)lua_tonumber(L, arg)); - status = status && (len > 0); - } - else { - size_t l; - const char *s = luaL_checklstring(L, arg, &l); - status = status && (fwrite(s, sizeof(char), l, f) == l); - } - } - if (l_likely(status)) - return 1; /* file handle already on stack top */ - else return luaL_fileresult(L, status, NULL); -} - - -static int io_write (lua_State *L) { - return g_write(L, getiofile(L, IO_OUTPUT), 1); -} - - -static int fil_write (lua_State *L) { - FIL *f = tofile(L); - lua_pushvalue(L, 1); /* push file at the stack top (to be returned) */ - return g_write(L, f, 2); -} - - -static int fil_seek (lua_State *L) { - static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; - static const char *const modenames[] = {"set", "cur", "end", NULL}; - FIL *f = tofile(L); - int op = luaL_checkoption(L, 2, "cur", modenames); - lua_Integer p3 = luaL_optinteger(L, 3, 0); - l_seeknum offset = (l_seeknum)p3; - luaL_argcheck(L, (lua_Integer)offset == p3, 3, - "not an integer in proper range"); - op = l_fseek(f, offset, mode[op]); - if (l_unlikely(op)) - return luaL_fileresult(L, 0, NULL); /* error */ - else { - lua_pushinteger(L, (lua_Integer)l_ftell(f)); - return 1; - } -} - - -static int fil_setvbuf (lua_State *L) { - static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; - static const char *const modenames[] = {"no", "full", "line", NULL}; - FIL *f = tofile(L); - int op = luaL_checkoption(L, 2, NULL, modenames); - lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); - int res = setvbuf(f, NULL, mode[op], (size_t)sz); - return luaL_fileresult(L, res == 0, NULL); -} - - - -static int io_flush (lua_State *L) { - return luaL_fileresult(L, fvx_sync(getiofile(L, IO_OUTPUT)) == FR_OK, NULL); -} - - -static int fil_flush (lua_State *L) { - return luaL_fileresult(L, fvx_sync(tofile(L)) == 0, NULL); -} - - -/* -** functions for 'io' library -*/ -static const luaL_Reg iolib[] = { - {"close", io_close}, - {"flush", io_flush}, - {"input", io_input}, - {"lines", io_lines}, - {"open", io_open}, - {"output", io_output}, - {"read", io_read}, - {"tmpfile", io_tmpfile}, - {"type", io_type}, - {"write", io_write}, - {NULL, NULL} -}; - - -/* -** methods for file handles -*/ -static const luaL_Reg meth[] = { - {"read", fil_read}, - {"write", fil_write}, - {"lines", fil_lines}, - {"flush", fil_flush}, - {"seek", fil_seek}, - {"close", fil_close}, - {"setvbuf", fil_setvbuf}, - {NULL, NULL} -}; - - -/* -** metamethods for file handles -*/ -static const luaL_Reg metameth[] = { - {"__index", NULL}, /* place holder */ - {"__gc", fil_gc}, - {"__close", fil_gc}, - {"__tostring", fil_tostring}, - {NULL, NULL} -}; - - -static void createmeta (lua_State *L) { - luaL_newmetatable(L, LUA_FILEHANDLE); /* metatable for file handles */ - luaL_setfuncs(L, metameth, 0); /* add metamethods to new metatable */ - luaL_newlibtable(L, meth); /* create method table */ - luaL_setfuncs(L, meth, 0); /* add file methods to method table */ - lua_setfield(L, -2, "__index"); /* metatable.__index = method table */ - lua_pop(L, 1); /* pop metatable */ -} - - -/* -** function to (not) close the standard files stdin, stdout, and stderr -*/ -static int io_noclose (lua_State *L) { - LStream *p = tolstream(L); - p->closef = &io_noclose; /* keep file opened */ - luaL_pushfail(L); - lua_pushliteral(L, "cannot close standard file"); - return 2; -} - - -//static void createstdfile (lua_State *L, FIL *f, const char *k, -// const char *fname) { -// LStream *p = newprefile(L); -// p->f = f; -// p->closef = &io_noclose; -// if (k != NULL) { -// lua_pushvalue(L, -1); -// lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */ -// } -// lua_setfield(L, -2, fname); /* add file to module */ -//} - - -LUAMOD_API int luaopen_io (lua_State *L) { - luaL_newlib(L, iolib); /* new module */ - createmeta(L); - /* create (and set) default files */ - //createstdfile(L, stdin, IO_INPUT, "stdin"); - //createstdfile(L, stdout, IO_OUTPUT, "stdout"); - //createstdfile(L, stderr, NULL, "stderr"); - return 1; -} - From 7fc6c95aa52207d6190a3da71096e462038ab8bd Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Mon, 24 Jul 2023 06:08:32 -0700 Subject: [PATCH 015/124] README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ff8d8edd..e72a015eb 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,10 @@ A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings * link.ld on arm9 edited to include .ARM.exidx into .rodata, caused by lua's code. Limit of AHBWRAM max size was increased until address 0x80C0000, 744KiB since 0x8006000 -copy init.lua to the root and then run "run.gm9" to use it +Copy init.lua to the SD card and select it. There is now an "Execute Lua script" option. -This exposes one function "thingy" which will display text on both screens, similar to "echo" and "set PREVIEW\_MODE". +This has one custom library so far, "UI", that exposes two functions: +* UI.ShowPrompt(ask, text) +* UI.ShowSelectPrompt(optionstable, text) Before you think about using this: it doesn't even have an API or a proper way to run the code. This is currenetly ONLY a test to see if Lua can be used at all. From 50aba3a9ff8d13cb2f539dff8551caca37930032 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Mon, 24 Jul 2023 06:12:45 -0700 Subject: [PATCH 016/124] README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e72a015eb..25dac172c 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings Copy init.lua to the SD card and select it. There is now an "Execute Lua script" option. +The main lua stuff is at `arm9/source/lua`. Custom stuff is `gm9lua` and `gm9ui`. + This has one custom library so far, "UI", that exposes two functions: * UI.ShowPrompt(ask, text) * UI.ShowSelectPrompt(optionstable, text) - -Before you think about using this: it doesn't even have an API or a proper way to run the code. This is currenetly ONLY a test to see if Lua can be used at all. From 16aec40122ced8148ff9ffcd6fca3c96d52a7eea Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Tue, 25 Jul 2023 03:12:26 -0700 Subject: [PATCH 017/124] FS lib, new UI stuff --- README.md | 6 +- arm9/source/lua/gm9fs.c | 27 +++ arm9/source/lua/gm9fs.h | 8 + arm9/source/lua/gm9lua.c | 18 +- arm9/source/lua/gm9lua.h | 8 +- arm9/source/lua/gm9ui.c | 64 ++++++- bigoptlist.lua | 304 ++++++++++++++++++++++++++++++++ init.lua | 31 +++- lua.gm9 | 1 - mount.lua | 3 + resources/languages/source.json | 2 +- 11 files changed, 447 insertions(+), 25 deletions(-) create mode 100644 arm9/source/lua/gm9fs.c create mode 100644 arm9/source/lua/gm9fs.h create mode 100644 bigoptlist.lua mode change 100755 => 100644 init.lua delete mode 100755 lua.gm9 create mode 100644 mount.lua diff --git a/README.md b/README.md index 25dac172c..93326d6e5 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ Copy init.lua to the SD card and select it. There is now an "Execute Lua script" The main lua stuff is at `arm9/source/lua`. Custom stuff is `gm9lua` and `gm9ui`. -This has one custom library so far, "UI", that exposes two functions: +The API here is not at all stable. But there are currently two libraries to play with. This is not set in stone! * UI.ShowPrompt(ask, text) +* UI.ShowString(text) +* UI.WordWrapString(text[, llen]) * UI.ShowSelectPrompt(optionstable, text) +* UI.ShowProgress(current, total, text) +* FS.InitImgFS(path) diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c new file mode 100644 index 000000000..b80968828 --- /dev/null +++ b/arm9/source/lua/gm9fs.c @@ -0,0 +1,27 @@ +#include "gm9fs.h" + +static int FS_InitImgFS(lua_State* L) { + CheckLuaArgCount(L, 1, "InitImgFS"); + + const char* path; + if (lua_isnil(L, 1)) { + path = NULL; + } else { + path = lua_tostring(L, 1); + luaL_argcheck(L, path, 1, "string or nil expected"); + } + + bool ret = InitImgFS(path); + lua_pushboolean(L, ret); + return 1; +} + +static const luaL_Reg FSlib[] = { + {"InitImgFS", FS_InitImgFS}, + {NULL, NULL} +}; + +int gm9lua_open_FS(lua_State* L) { + luaL_newlib(L, FSlib); + return 1; +} diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h new file mode 100644 index 000000000..1e093ebca --- /dev/null +++ b/arm9/source/lua/gm9fs.h @@ -0,0 +1,8 @@ +#pragma once +#include "gm9lua.h" +#include "vff.h" +#include "fsinit.h" + +#define GM9LUA_FSLIBNAME "FS" + +int gm9lua_open_FS(lua_State* L); diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 298dc3807..12c106a6d 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -4,6 +4,7 @@ #include "vff.h" #include "fsutil.h" #include "gm9ui.h" +#include "gm9fs.h" #define DEBUGSP ShowPrompt @@ -39,12 +40,6 @@ static int ErrFile(lua_State* L, const char* what, int fnameindex, FRESULT res) return LUA_ERRFILE; } -void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) { - if (lua_gettop(L) != argcount) { - luaL_error(L, "bad number of arguments passed to '%s' (expected %d, got %d)", cmd, argcount); - } -} - int LoadLuaFile(lua_State* L, const char *filename) { GM9LuaLoadF lf; lf.n = 0; @@ -79,6 +74,7 @@ static const luaL_Reg gm9lualibs[] = { // gm9 custom {GM9LUA_UILIBNAME, gm9lua_open_UI}, + {GM9LUA_FSLIBNAME, gm9lua_open_FS}, {NULL, NULL} }; @@ -111,13 +107,19 @@ bool ExecuteLuaScript(const char* path_script) { int result = LoadLuaFile(L, path_script); //free(script_buffer); if (result != LUA_OK) { - ShowPrompt(false, "Error during loading:\n%s", lua_tostring(L, -1)); + char errstr[BUFSIZ] = {0}; + strlcpy(errstr, lua_tostring(L, -1), BUFSIZ); + WordWrapString(errstr, 0); + ShowPrompt(false, "Error during loading:\n%s", errstr); lua_close(L); return false; } if (lua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) { - ShowPrompt(false, "Error during execution:\n%s", lua_tostring(L, -1)); + char errstr[BUFSIZ] = {0}; + strlcpy(errstr, lua_tostring(L, -1), BUFSIZ); + WordWrapString(errstr, 0); + ShowPrompt(false, "Error during execution:\n%s", errstr); lua_close(L); return false; } diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index 0b6f604e7..d4db6a199 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -9,6 +9,12 @@ #define LUASCRIPT_EXT "lua" #define LUASCRIPT_MAX_SIZE STD_BUFFER_SIZE -void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd); +static inline void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) { + int args = lua_gettop(L); + if (args != argcount) { + luaL_error(L, "bad number of arguments passed to '%s' (expected %d, got %d)", cmd, argcount, args); + } +} + int LoadLuaFile(lua_State* L, const char *filename); bool ExecuteLuaScript(const char* path_script); diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 8463c9597..e571af894 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -1,5 +1,8 @@ #include "gm9ui.h" +#define MAXOPTIONS 256 +#define MAXOPTIONS_STR "256" + static int UI_ShowPrompt(lua_State* L) { CheckLuaArgCount(L, 2, "ShowPrompt"); bool ask = lua_toboolean(L, 1); @@ -10,27 +13,76 @@ static int UI_ShowPrompt(lua_State* L) { return 1; } +static int UI_ShowString(lua_State* L) { + CheckLuaArgCount(L, 1, "ShowString"); + const char *text = lua_tostring(L, 1); + + ShowString("%s", text); + return 0; +} + +static int UI_WordWrapString(lua_State* L) { + size_t len; + int isnum; + const char *text = lua_tolstring(L, 1, &len); + int llen = lua_tointegerx(L, 2, &isnum); + // i should check arg 2 if it's a number (but only if it was provided at all) + char* buf = malloc(len + 1); + strlcpy(buf, text, len + 1); + WordWrapString(buf, llen); + lua_pushlstring(L, buf, len); + free(buf); + return 1; +} + static int UI_ShowSelectPrompt(lua_State* L) { CheckLuaArgCount(L, 2, "ShowSelectPrompt"); const char* text = lua_tostring(L, 2); - const char* options[16]; + char* options[MAXOPTIONS]; + const char* tmpstr; + size_t len; + int i; luaL_argcheck(L, lua_istable(L, 1), 1, "table expected"); lua_Integer opttablesize = luaL_len(L, 1); - luaL_argcheck(L, opttablesize <= 16, 1, "more than 16 options given"); - for (int i = 0; i < opttablesize; i++) { + luaL_argcheck(L, opttablesize <= MAXOPTIONS, 1, "more than " MAXOPTIONS_STR " options given"); + for (i = 0; i < opttablesize; i++) { lua_geti(L, 1, i + 1); - options[i] = lua_tostring(L, -1); + tmpstr = lua_tolstring(L, -1, &len); + options[i] = malloc(len + 1); + strlcpy(options[i], tmpstr, len + 1); + lua_pop(L, 1); } - int result = ShowSelectPrompt(opttablesize, options, "%s", text); - lua_pushinteger(L, result); + int result = ShowSelectPrompt(opttablesize, (const char**)options, "%s", text); + for (i = 0; i < opttablesize; i++) free(options[i]); + // lua only treats "false" and "nil" as false values + // so to make this easier, return nil and not 0 if no choice was made + // https://www.lua.org/manual/5.4/manual.html#3.3.4 + if (result) + lua_pushinteger(L, result); + else + lua_pushnil(L); + return 1; +} + +static int UI_ShowProgress(lua_State* L) { + CheckLuaArgCount(L, 3, "ShowProgress"); + u64 current = lua_tointeger(L, 1); + u64 total = lua_tointeger(L, 2); + const char* optstr = lua_tostring(L, 3); + + bool result = ShowProgress(current, total, optstr); + lua_pushboolean(L, result); return 1; } static const luaL_Reg UIlib[] = { {"ShowPrompt", UI_ShowPrompt}, + {"ShowString", UI_ShowString}, + {"WordWrapString", UI_WordWrapString}, {"ShowSelectPrompt", UI_ShowSelectPrompt}, + {"ShowProgress", UI_ShowProgress}, {NULL, NULL} }; diff --git a/bigoptlist.lua b/bigoptlist.lua new file mode 100644 index 000000000..042747dba --- /dev/null +++ b/bigoptlist.lua @@ -0,0 +1,304 @@ +games = {"Half-Life 2", "Half-Life 2: Episode One", "Half-Life 2: Episode Two", "Team Fortress 2"} +maps = { + { + "background01", + "background02", + "background03", + "background04", + "background05", + "background06", + "background07", + "credits", + "d1_canals_01a", + "d1_canals_01", + "d1_canals_02", + "d1_canals_03", + "d1_canals_05", + "d1_canals_06", + "d1_canals_07", + "d1_canals_08", + "d1_canals_09", + "d1_canals_10", + "d1_canals_11", + "d1_canals_12", + "d1_canals_13", + "d1_eli_01", + "d1_eli_02", + "d1_town_01a", + "d1_town_01", + "d1_town_02a", + "d1_town_02", + "d1_town_03", + "d1_town_04", + "d1_town_05", + "d1_trainstation_01", + "d1_trainstation_02", + "d1_trainstation_03", + "d1_trainstation_04", + "d1_trainstation_05", + "d1_trainstation_06", + "d2_coast_01", + "d2_coast_02", + "d2_coast_03", + "d2_coast_04", + "d2_coast_05", + "d2_coast_07", + "d2_coast_08", + "d2_coast_09", + "d2_coast_10", + "d2_coast_11", + "d2_coast_12", + "d2_prison_01", + "d2_prison_02", + "d2_prison_03", + "d2_prison_04", + "d2_prison_05", + "d2_prison_06", + "d2_prison_07", + "d2_prison_08", + "d3_breen_01", + "d3_c17_01", + "d3_c17_02", + "d3_c17_02_camera", + "d3_c17_03", + "d3_c17_04", + "d3_c17_05", + "d3_c17_06a", + "d3_c17_06b", + "d3_c17_07", + "d3_c17_08", + "d3_c17_09", + "d3_c17_10a", + "d3_c17_10b", + "d3_c17_11", + "d3_c17_12b", + "d3_c17_12", + "d3_c17_13", + "d3_citadel_01", + "d3_citadel_02", + "d3_citadel_03", + "d3_citadel_04", + "d3_citadel_05", + "intro" + }, + { + "credits", + "ep1_background01", + "ep1_background01a", + "ep1_background02", + "ep1_c17_00", + "ep1_c17_00a", + "ep1_c17_01", + "ep1_c17_01a", + "ep1_c17_02", + "ep1_c17_02a", + "ep1_c17_02b", + "ep1_c17_05", + "ep1_c17_06", + "ep1_citadel_00", + "ep1_citadel_00_demo", + "ep1_citadel_01", + "ep1_citadel_02", + "ep1_citadel_02b", + "ep1_citadel_03", + "ep1_citadel_04" + }, + { + "ep2_background01", + "ep2_background02a", + "ep2_background02", + "ep2_background03", + "ep2_outland_01a", + "ep2_outland_01", + "ep2_outland_02", + "ep2_outland_03", + "ep2_outland_04", + "ep2_outland_05", + "ep2_outland_06a", + "ep2_outland_06", + "ep2_outland_07", + "ep2_outland_08", + "ep2_outland_09", + "ep2_outland_10a", + "ep2_outland_10", + "ep2_outland_11a", + "ep2_outland_11b", + "ep2_outland_11", + "ep2_outland_12a", + "ep2_outland_12" + }, + { + "arena_badlands", + "arena_byre", + "arena_granary", + "arena_lumberyard", + "arena_lumberyard_event", + "arena_nucleus", + "arena_offblast_final", + "arena_ravine", + "arena_sawmill", + "arena_watchtower", + "arena_well", + "background01", + "cp_5gorge", + "cp_altitude", + "cp_ambush_event", + "cp_badlands", + "cp_cloak", + "cp_coldfront", + "cp_degrootkeep", + "cp_dustbowl", + "cp_egypt_final", + "cp_fastlane", + "cp_foundry", + "cp_freight_final1", + "cp_frostwatch", + "cp_gorge", + "cp_gorge_event", + "cp_granary", + "cp_gravelpit", + "cp_gravelpit_snowy", + "cp_gullywash_final1", + "cp_hardwood_final", + "cp_junction_final", + "cp_manor_event", + "cp_mercenarypark", + "cp_metalworks", + "cp_mossrock", + "cp_mountainlab", + "cp_powerhouse", + "cp_process_final", + "cp_reckoner", + "cp_snakewater_final1", + "cp_snowplow", + "cp_spookeyridge", + "cp_standin_final", + "cp_steel", + "cp_sulfur", + "cp_sunshine", + "cp_sunshine_event", + "cp_vanguard", + "cp_well", + "cp_yukon_final", + "ctf_2fort", + "ctf_2fort_invasion", + "ctf_crasher", + "ctf_doublecross", + "ctf_doublecross_snowy", + "ctf_foundry", + "ctf_frosty", + "ctf_gorge", + "ctf_hellfire", + "ctf_helltrain_event", + "ctf_landfall", + "ctf_pelican_peak", + "ctf_sawmill", + "ctf_snowfall_final", + "ctf_thundermountain", + "ctf_turbine", + "ctf_well", + "itemtest", + "koth_badlands", + "koth_bagel_event", + "koth_brazil", + "koth_cascade", + "koth_harvest_event", + "koth_harvest_final", + "koth_highpass", + "koth_king", + "koth_lakeside_event", + "koth_lakeside_final", + "koth_lazarus", + "koth_los_muertos", + "koth_maple_ridge_event", + "koth_megalo", + "koth_moonshine_event", + "koth_nucleus", + "koth_probed", + "koth_rotunda", + "koth_sawmill", + "koth_sawmill_event", + "koth_sharkbay", + "koth_slasher", + "koth_slaughter_event", + "koth_suijin", + "koth_synthetic_event", + "koth_undergrove_event", + "koth_viaduct", + "koth_viaduct_event", + "mvm_bigrock", + "mvm_coaltown", + "mvm_decoy", + "mvm_ghost_town", + "mvm_mannhattan", + "mvm_mannworks", + "mvm_rottenburg", + "pass_brickyard", + "pass_district", + "pass_timbertown", + "pd_cursed_cove_event", + "pd_farmageddon", + "pd_monster_bash", + "pd_pit_of_death_event", + "pd_selbyen", + "pd_snowville_event", + "pd_watergate", + "pl_badwater", + "pl_barnblitz", + "pl_bloodwater", + "pl_borneo", + "pl_breadspace", + "pl_cactuscanyon", + "pl_cashworks", + "pl_chilly", + "pl_coal_event", + "pl_enclosure_final", + "pl_fifthcurve_event", + "pl_frontier_final", + "pl_frostcliff", + "pl_goldrush", + "pl_hasslecastle", + "pl_hoodoo_final", + "pl_millstone_event", + "pl_phoenix", + "pl_pier", + "pl_precipice_event_final", + "pl_rumble_event", + "pl_rumford_event", + "pl_sludgepit_event", + "pl_snowycoast", + "pl_swiftwater_final1", + "pl_terror_event", + "pl_thundermountain", + "pl_upward", + "pl_venice", + "pl_wutville_event", + "plr_bananabay", + "plr_hacksaw_event", + "plr_hightower", + "plr_hightower_event", + "plr_nightfall_final", + "plr_pipeline", + "rd_asteroid", + "sd_doomsday", + "sd_doomsday_event", + "tc_hydro", + "tr_dustbowl", + "tr_target", + "vsh_distillery", + "vsh_nucleus", + "vsh_skirmish", + "vsh_tinyrock" + } +} + +while true do + gameresult = UI.ShowSelectPrompt(games, "Choose a game!") + if not gameresult then return end + + curmaps = maps[gameresult] + mapresult = UI.ShowSelectPrompt(curmaps, "Choose a map!") + if mapresult then + UI.ShowPrompt(false, UI.WordWrapString("You chose the game "..games[gameresult].." and the map "..curmaps[mapresult])) + end +end diff --git a/init.lua b/init.lua old mode 100755 new mode 100644 index 7cdfc4d38..e136332e3 --- a/init.lua +++ b/init.lua @@ -1,14 +1,31 @@ -UI.ShowPrompt(false, "math.sin(3): "..tostring(math.sin(3))) -UI.ShowPrompt(false, "3 ^ 2: "..tostring(3 ^ 2)) +UI.ShowPrompt(false, "math.sin(3): "..math.sin(3)) +UI.ShowPrompt(false, "3 ^ 2: "..(3 ^ 2)) -options = {'Dustbowl', 'Granary', 'Gravel Pit', 'Well', '2Fort', 'Hydro'} -result = UI.ShowSelectPrompt(options, "Choose one...") -UI.ShowPrompt(false, "You chose: "..tostring(result)..", or "..options[result]) +local options = {'Dustbowl', 'Granary', 'Gravel Pit', 'Well', '2Fort', 'Hydro'} +local result = UI.ShowSelectPrompt(options, "Choose one...") +if result then + UI.ShowPrompt(false, "You chose: "..result..", or "..options[result]) +else + UI.ShowPrompt(false, "Oh you don't like any of them?") +end -res = UI.ShowPrompt(true, "I am asking you...") +local res = UI.ShowPrompt(true, "I am asking you...") UI.ShowPrompt(false, "I got: "..tostring(res)) -words = 'black,mesa' +local max = 1000000 +for i = 0, max do + if not UI.ShowProgress(i, max, "Pushing the Payload...") then break end +end +local max = 500000 +for i = max, 0, -1 do + if not UI.ShowProgress(i, max, "Un-copying your file...") then break end +end +local max = 1000 +for i = 0, max do + UI.ShowString("Pushing the Payload..."..i) +end + +local words = 'black,mesa' for word in string.gmatch(words, '([^,]+)') do UI.ShowPrompt(false, "gmatch test: "..word) end diff --git a/lua.gm9 b/lua.gm9 deleted file mode 100755 index a667473c2..000000000 --- a/lua.gm9 +++ /dev/null @@ -1 +0,0 @@ -luarun 0:/init.lua diff --git a/mount.lua b/mount.lua new file mode 100644 index 000000000..678ee8ba8 --- /dev/null +++ b/mount.lua @@ -0,0 +1,3 @@ +UI.ShowPrompt(false, "I am about to mount...") +local res = FS.InitImgFS("0:/finalize.romfs") +UI.ShowPrompt(false, "Did it work? \n"..tostring(res)) diff --git a/resources/languages/source.json b/resources/languages/source.json index 90445fdef..602f1052d 100644 --- a/resources/languages/source.json +++ b/resources/languages/source.json @@ -181,7 +181,7 @@ "BUILD_X": "Build %s", "NCCHINFO_OPTIONS": "NCCHinfo options...", "EXECUTE_GM9_SCRIPT": "Execute GM9 script", - "EXECUTE_GM9_SCRIPT": "Execute Lua script", + "EXECUTE_LUA_SCRIPT": "Execute Lua script", "FONT_OPTIONS": "Font options...", "LANGUAGE_OPTIONS": "Language options...", "VIEW_PNG_FILE": "View PNG file", From 1efa7b9b46b06d6ddd7d3659e0041184b384dbfb Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Tue, 25 Jul 2023 03:32:06 -0700 Subject: [PATCH 018/124] consistency with "type* ptr" maybe --- arm9/source/lua/gm9lua.c | 4 ++-- arm9/source/lua/gm9lua.h | 2 +- arm9/source/lua/gm9ui.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 12c106a6d..7a5fe72a0 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -16,7 +16,7 @@ typedef struct GM9LuaLoadF { } GM9LuaLoadF; // similar to "getF" in lauxlib.c -static const char* GetF(lua_State* L, void* ud, size_t *size) { +static const char* GetF(lua_State* L, void* ud, size_t* size) { GM9LuaLoadF* lf = (GM9LuaLoadF*)ud; UINT br = 0; (void)L; // unused @@ -40,7 +40,7 @@ static int ErrFile(lua_State* L, const char* what, int fnameindex, FRESULT res) return LUA_ERRFILE; } -int LoadLuaFile(lua_State* L, const char *filename) { +int LoadLuaFile(lua_State* L, const char* filename) { GM9LuaLoadF lf; lf.n = 0; int status; diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index d4db6a199..a8eabd6dc 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -16,5 +16,5 @@ static inline void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) } } -int LoadLuaFile(lua_State* L, const char *filename); +int LoadLuaFile(lua_State* L, const char* filename); bool ExecuteLuaScript(const char* path_script); diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index e571af894..280f558c6 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -15,7 +15,7 @@ static int UI_ShowPrompt(lua_State* L) { static int UI_ShowString(lua_State* L) { CheckLuaArgCount(L, 1, "ShowString"); - const char *text = lua_tostring(L, 1); + const char* text = lua_tostring(L, 1); ShowString("%s", text); return 0; @@ -24,7 +24,7 @@ static int UI_ShowString(lua_State* L) { static int UI_WordWrapString(lua_State* L) { size_t len; int isnum; - const char *text = lua_tolstring(L, 1, &len); + const char* text = lua_tolstring(L, 1, &len); int llen = lua_tointegerx(L, 2, &isnum); // i should check arg 2 if it's a number (but only if it was provided at all) char* buf = malloc(len + 1); From 6d00c3e06f02bdd454777ade179cd21cc618a107 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Thu, 27 Jul 2023 03:26:32 -0700 Subject: [PATCH 019/124] add custom package searcher, reset package.path --- arm9/source/lua/gm9loader.c | 138 ++++++++++++++++++++++++++++++++++++ arm9/source/lua/gm9loader.h | 3 + arm9/source/lua/gm9lua.c | 4 +- testrequire.lua | 3 + 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 arm9/source/lua/gm9loader.c create mode 100644 arm9/source/lua/gm9loader.h create mode 100644 testrequire.lua diff --git a/arm9/source/lua/gm9loader.c b/arm9/source/lua/gm9loader.c new file mode 100644 index 000000000..7ec123dce --- /dev/null +++ b/arm9/source/lua/gm9loader.c @@ -0,0 +1,138 @@ +#include "gm9lua.h" +#include "vff.h" +#include "ui.h" + +// a lot of this code is based on stuff in loadlib.c but adapted for GM9 + +// similar to readable +static int Readable(const char* filename) { + FIL f; + FRESULT res = fvx_open(&f, filename, FA_READ | FA_OPEN_EXISTING); + if (res != FR_OK) return 0; + fvx_close(&f); + return 1; +} + +// similar to getnextfilename +static const char* GetNextFileName(char** path, char* end) { + char *sep; + char *name = *path; + if (name == end) + return NULL; /* no more names */ + else if (*name == '\0') { /* from previous iteration? */ + *name = *LUA_PATH_SEP; /* restore separator */ + name++; /* skip it */ + } + sep = strchr(name, *LUA_PATH_SEP); /* find next separator */ + if (sep == NULL) /* separator not found? */ + sep = end; /* name goes until the end */ + *sep = '\0'; /* finish file name */ + *path = sep; /* will start next search from here */ + return name; +} + +// similar to pusherrornotfound +static void PushErrorNotFound(lua_State* L, const char* path) { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addstring(&b, "no file '"); + luaL_addgsub(&b, path, LUA_PATH_SEP, "'\n\tno file '"); + luaL_addstring(&b, "'"); + luaL_pushresult(&b); +} + +// similar to searchpath +static const char* SearchPath(lua_State* L, const char* name, const char* path, const char* sep) { + luaL_Buffer buff; + char* pathname; + char* endpathname; + const char* filename; + if (*sep != '\0' && strchr(name, *sep) != NULL) + name = luaL_gsub(L, name, sep, "/"); + + luaL_buffinit(L, &buff); + // add path to the buffer, replacing marks ('?') with the file name + luaL_addgsub(&buff, path, LUA_PATH_MARK, name); + luaL_addchar(&buff, '\0'); + pathname = luaL_buffaddr(&buff); + endpathname = pathname + luaL_bufflen(&buff) + 1; + while ((filename = GetNextFileName(&pathname, endpathname)) != NULL) { + if (Readable(filename)) + return lua_pushstring(L, filename); + } + luaL_pushresult(&buff); + PushErrorNotFound(L, lua_tostring(L, -1)); + return NULL; +} + +// similar to findfile +static const char* FindLuaFile(lua_State* L, const char* name, const char* pname) { + const char* path; + lua_getfield(L, lua_upvalueindex(1), pname); // gets 'package' table + path = lua_tostring(L, -1); + if (path == NULL) luaL_error(L, "'package.%s' must be a string", pname); + return SearchPath(L, name, path, "."); +} + +// similar to checkload +static int CheckLoad(lua_State* L, int stat, const char* filename) { + if (stat) { + lua_pushstring(L, filename); + return 2; // return open function and filename + } else { + return luaL_error(L, "error loading module '%s' from file '%s':\n\t%s", + lua_tostring(L, 1), filename, lua_tostring(L, -1)); + } +} + +// similar to searcher_Lua +static int PackageSearcher(lua_State* L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + + filename = FindLuaFile(L, name, "path"); + + if (filename == NULL) return 1; // module not found in this path + return CheckLoad(L, (LoadLuaFile(L, filename) == LUA_OK), filename); +} + +void ResetPackageSearchersAndPath(lua_State* L) { + // get package module + lua_getglobal(L, "package"); + + // the default package.path only makes sense on a full OS + // maybe this should include the lua script's current directory somehow... + lua_pushliteral(L, + "0:/gm9/luapackages/"LUA_PATH_MARK".lua" LUA_PATH_SEP + "0:/gm9/luapackages/"LUA_PATH_MARK"/init.lua" LUA_PATH_SEP + "V:/luapackages/"LUA_PATH_MARK".lua" LUA_PATH_SEP + "V:/luapackages/"LUA_PATH_MARK"/init.lua" + ); + lua_setfield(L, -2, "path"); + + // package.cpath is for loading binary modules, useless on GM9 + lua_pushliteral(L, ""); + lua_setfield(L, -2, "cpath"); + + // the default package searchers only make sense on a full OS + /*lua_createtable(L, 1, 0); + lua_pushvalue(L, -2); // set 'package' as upvalue for all searchers + lua_pushcclosure(L, PackageSearcher, 1); + lua_rawseti(L, -2, 1); + lua_setfield(L, -2, "searchers"); // replace the default 'package.searchers' with our own + */ + + // the default package searchers only make sense on a full OS + // so here we replace the lua loader with a custom one, and remove the C/Croot loaders + // leaving the initial one (preload) + lua_getfield(L, -1, "searchers"); + lua_pushvalue(L, -2); // copy 'package' to the top of the stack, to set 'package' as upvalue for all searchers + lua_pushcclosure(L, PackageSearcher, 1); // push PackageSearcher with one upvalue being the "packages" module/table + lua_rawseti(L, -2, 2); // replace default lua loader + lua_pushnil(L); + lua_rawseti(L, -2, 3); // remove C loader + lua_pushnil(L); + lua_rawseti(L, -2, 4); // remove C root loader + lua_pop(L, 1); // remove "searchers" + lua_pop(L, 1); // remove "packages" +} diff --git a/arm9/source/lua/gm9loader.h b/arm9/source/lua/gm9loader.h new file mode 100644 index 000000000..93452cf5a --- /dev/null +++ b/arm9/source/lua/gm9loader.h @@ -0,0 +1,3 @@ +#include "gm9lua.h" + +void ResetPackageSearchersAndPath(lua_State* L); diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 7a5fe72a0..aff1adebe 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -5,6 +5,7 @@ #include "fsutil.h" #include "gm9ui.h" #include "gm9fs.h" +#include "gm9loader.h" #define DEBUGSP ShowPrompt @@ -100,8 +101,7 @@ bool ExecuteLuaScript(const char* path_script) { lua_State* L = luaL_newstate(); loadlibs(L); - lua_pushcfunction(L, LuaShowPrompt); - lua_setglobal(L, "ShowPrompt"); + ResetPackageSearchersAndPath(L); //int result = luaL_loadbuffer(L, script_buffer, script_size, path_script); int result = LoadLuaFile(L, path_script); diff --git a/testrequire.lua b/testrequire.lua new file mode 100644 index 000000000..6cced193f --- /dev/null +++ b/testrequire.lua @@ -0,0 +1,3 @@ +j = require('json') +UI.ShowPrompt(false, j.encode({a=1, b=2})) +UI.ShowPrompt(false, j.encode({"one", 2, 3.1})) From b3d331f361a73d7d50223ce493b7c5ad50bb1400 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Fri, 28 Jul 2023 03:50:58 -0700 Subject: [PATCH 020/124] new functions for UI including basic print output buffer, add "Lua scripts..." option to home/power menu --- README.md | 24 ++++++++--- arm9/.DS_Store | Bin 0 -> 6148 bytes arm9/source/filesys/support.h | 1 + arm9/source/godmode.c | 13 +++++- arm9/source/language.inl | 4 ++ arm9/source/lua/gm9loader.c | 8 ---- arm9/source/lua/gm9lua.c | 1 + arm9/source/lua/gm9ui.c | 79 ++++++++++++++++++++++++++++++++++ arm9/source/lua/gm9ui.h | 4 ++ init.lua | 5 +++ testprint.lua | 66 ++++++++++++++++++++++++++++ 11 files changed, 190 insertions(+), 15 deletions(-) create mode 100644 arm9/.DS_Store create mode 100644 testprint.lua diff --git a/README.md b/README.md index 93326d6e5..cb88b9064 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,24 @@ A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings Copy init.lua to the SD card and select it. There is now an "Execute Lua script" option. +Or put your script at `0:/gm9/luascripts/*.lua`. Then press HOME or POWER and choose "Lua scripts...". + The main lua stuff is at `arm9/source/lua`. Custom stuff is `gm9lua` and `gm9ui`. The API here is not at all stable. But there are currently two libraries to play with. This is not set in stone! -* UI.ShowPrompt(ask, text) -* UI.ShowString(text) -* UI.WordWrapString(text[, llen]) -* UI.ShowSelectPrompt(optionstable, text) -* UI.ShowProgress(current, total, text) -* FS.InitImgFS(path) +* print(...) + * Calling this will replace the alt screen with an output buffer. It doesn't support newlines or word wrapping properly yet +* bool UI.ShowPrompt(bool ask, string text) +* void UI.ShowString(string text) +* string UI.WordWrapString(string text[, int llen]) +* void UI.ClearScreenF(bool clear\_main, bool clear\_alt, u32 color) +* number UI.ShowSelectPrompt(table optionstable, string text) +* bool UI.ShowProgress(u32 current, u32 total, text text) + * returns true if B is pressed +* void UI.DrawString(int which\_screen, string text, int x, int y, int color, int bgcolor) + * which\_screen: + * 0 = main screen + * 1 = alt screen + * 2 = top screen + * 3 = bottom screen +* bool FS.InitImgFS(path) diff --git a/arm9/.DS_Store b/arm9/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0404c701b0327122bd4009fec01367c472d3ae39 GIT binary patch literal 6148 zcmeHKJ5Iwu5SuY}I#g7V<0K&2augJ5cbtL)a0xmju7bD(Z+1o@ z8xzqX1T)g?ThC{A{A_u=MC7tnIV2hoQ37S`9b))G*w5OLo?6)H!Wv~YAC4wXz39Z6 z;de5?&u*UzTG1_46hFWDLl%4MCeMqiDPT>e@Apq{r!QyQ5#=`#i<^x%WI0AU9nc*$ zs9;GGnnrKXoeK^u($V<2W=Pc8@C|Ic@y|KlXPG6sx+KgEFS=c7Es zEopD<+#L5>4;@2UIIb03r(hCVF=DwDpFo4a?s)=C6 0) optionstr[poweroff - 1] = STR_POWEROFF_SYSTEM; @@ -2940,6 +2941,7 @@ u32 GodMode(int entrypoint) { if (language > 0) optionstr[language - 1] = STR_LANGUAGE; if (brick > 0) optionstr[brick - 1] = STR_BRICK_MY_3DS; if (scripts > 0) optionstr[scripts - 1] = STR_SCRIPTS; + if (luascripts > 0) optionstr[luascripts - 1] = STR_LUA_SCRIPTS; if (payloads > 0) optionstr[payloads - 1] = STR_PAYLOADS; if (more > 0) optionstr[more - 1] = STR_MORE; @@ -3017,6 +3019,15 @@ u32 GodMode(int entrypoint) { ClearScreenF(true, true, COLOR_STD_BG); break; } + } else if (user_select == luascripts) { + if (!CheckSupportDir(LUASCRIPTS_DIR)) { + ShowPrompt(false, STR_LUA_SCRIPTS_DIRECTORY_NOT_FOUND, LUASCRIPTS_DIR); + } else if (FileSelectorSupport(loadpath, STR_HOME_LUA_SCRIPTS_MENU_SELECT_SCRIPT, LUASCRIPTS_DIR, "*.lua")) { + ExecuteLuaScript(loadpath); + GetDirContents(current_dir, current_path); + ClearScreenF(true, true, COLOR_STD_BG); + break; + } } else if (user_select == payloads) { if (!CheckSupportDir(PAYLOADS_DIR)) ShowPrompt(false, STR_PAYLOADS_DIRECTORY_NOT_FOUND, PAYLOADS_DIR); else if (FileSelectorSupport(loadpath, STR_HOME_PAYLOADS_MENU_SELECT_PAYLOAD, PAYLOADS_DIR, "*.firm")) diff --git a/arm9/source/language.inl b/arm9/source/language.inl index 38e684fea..1eeba96d7 100644 --- a/arm9/source/language.inl +++ b/arm9/source/language.inl @@ -407,6 +407,7 @@ STRING(RESUME_GODMODE9, "Resume GodMode9") STRING(RESUME_BOOTLOADER, "Resume bootloader") STRING(SELECT_PAYLOAD, "Select payload...") STRING(SELECT_SCRIPT, "Select script...") +STRING(SELECT_LUA_SCRIPT, "Select Lua script...") STRING(POWEROFF_SYSTEM, "Poweroff system") STRING(REBOOT_SYSTEM, "Reboot system") STRING(FLAVOR_BOOTLOADER_SELECT_OPTION, "%s bootloader menu.\nSelect action:") @@ -461,6 +462,7 @@ STRING(TITLE_MANAGER, "Title manager") STRING(BRICK_MY_3DS, "Brick my 3DS") STRING(LANGUAGE, "Language...") STRING(SCRIPTS, "Scripts...") +STRING(LUA_SCRIPTS, "Lua scripts...") STRING(PAYLOADS, "Payloads...") STRING(MORE, "More...") STRING(BRACKET_MORE, "[more...]") @@ -473,8 +475,10 @@ STRING(B_DRIVE_SD_CARD, "[B:] SD CARD") STRING(TITLE_MANAGER_MENU_SELECT_TITLES_SOURCE, "Title manager menu.\nSelect titles source:") STRING(LANGUAGES_DIRECTORY_NOT_FOUND, "Languages directory not found.\n(default path: 0:/gm9/%s)") STRING(SCRIPTS_DIRECTORY_NOT_FOUND, "Scripts directory not found.\n(default path: 0:/gm9/%s)") +STRING(LUA_SCRIPTS_DIRECTORY_NOT_FOUND, "Lua scripts directory not found.\n(default path: 0:/gm9/%s)") STRING(HOME_LANGUAGE_MENU_SELECT_LANGUAGE, "HOME language... menu.\nSelect language:") STRING(HOME_SCRIPTS_MENU_SELECT_SCRIPT, "HOME scripts... menu.\nSelect script:") +STRING(HOME_LUA_SCRIPTS_MENU_SELECT_SCRIPT, "HOME Lua scripts... menu.\nSelect script:") STRING(PAYLOADS_DIRECTORY_NOT_FOUND, "Payloads directory not found.\n(default path: 0:/gm9/%s)") STRING(HOME_PAYLOADS_MENU_SELECT_PAYLOAD, "HOME payloads... menu.\nSelect payload:") STRING(UNEXPECTED_SD_CARD_REMOVAL_TO_PREVENT_DATA_LOSS_UNMOUNT_BEFORE_EJECT, "!Unexpected SD card removal!\n \nTo prevent data loss, unmount\nbefore ejecting the SD card.") diff --git a/arm9/source/lua/gm9loader.c b/arm9/source/lua/gm9loader.c index 7ec123dce..022a64cd2 100644 --- a/arm9/source/lua/gm9loader.c +++ b/arm9/source/lua/gm9loader.c @@ -114,14 +114,6 @@ void ResetPackageSearchersAndPath(lua_State* L) { lua_pushliteral(L, ""); lua_setfield(L, -2, "cpath"); - // the default package searchers only make sense on a full OS - /*lua_createtable(L, 1, 0); - lua_pushvalue(L, -2); // set 'package' as upvalue for all searchers - lua_pushcclosure(L, PackageSearcher, 1); - lua_rawseti(L, -2, 1); - lua_setfield(L, -2, "searchers"); // replace the default 'package.searchers' with our own - */ - // the default package searchers only make sense on a full OS // so here we replace the lua loader with a custom one, and remove the C/Croot loaders // leaving the initial one (preload) diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index aff1adebe..8ed2b1700 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -102,6 +102,7 @@ bool ExecuteLuaScript(const char* path_script) { loadlibs(L); ResetPackageSearchersAndPath(L); + ClearOutputBuffer(); //int result = luaL_loadbuffer(L, script_buffer, script_size, path_script); int result = LoadLuaFile(L, path_script); diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 280f558c6..985ef29d4 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -3,6 +3,34 @@ #define MAXOPTIONS 256 #define MAXOPTIONS_STR "256" +#define OUTPUTMAXLINES 24 +#define OUTPUTMAXCHARSPERLINE 51 // make sure this includes space for '\0' + +// this output buffer stuff is especially a test, it needs to take into account newlines and fonts that are not 8x10 + +char output_buffer[OUTPUTMAXLINES][OUTPUTMAXCHARSPERLINE]; // hold 24 lines + +void ShiftOutputBufferUp(void) { + for (int i = 0; i < OUTPUTMAXLINES - 1; i++) { + memcpy(output_buffer[i], output_buffer[i + 1], OUTPUTMAXCHARSPERLINE); + } +} + +void ClearOutputBuffer(void) { + memset(output_buffer, 0, sizeof(output_buffer)); +} + +void WriteToOutputBuffer(char* text) { + strlcpy(output_buffer[OUTPUTMAXLINES - 1], text, OUTPUTMAXCHARSPERLINE); +} + +void RenderOutputBuffer(void) { + ClearScreenF(false, true, COLOR_STD_BG); + for (int i = 0; i < OUTPUTMAXLINES; i++) { + DrawString(ALT_SCREEN, output_buffer[i], 0, i * 10, COLOR_STD_FONT, COLOR_TRANSPARENT); + } +} + static int UI_ShowPrompt(lua_State* L) { CheckLuaArgCount(L, 2, "ShowPrompt"); bool ask = lua_toboolean(L, 1); @@ -35,6 +63,14 @@ static int UI_WordWrapString(lua_State* L) { return 1; } +static int UI_ClearScreenF(lua_State* L) { + bool clear_main = lua_toboolean(L, 1); + bool clear_alt = lua_toboolean(L, 2); + u32 color = lua_tointeger(L, 3); + ClearScreenF(clear_main, clear_alt, color); + return 0; +} + static int UI_ShowSelectPrompt(lua_State* L) { CheckLuaArgCount(L, 2, "ShowSelectPrompt"); const char* text = lua_tostring(L, 2); @@ -77,16 +113,59 @@ static int UI_ShowProgress(lua_State* L) { return 1; } +static int UI_DrawString(lua_State* L) { + int which_screen = lua_tointeger(L, 1); + const char* text = lua_tostring(L, 2); + int x = lua_tointeger(L, 3); + int y = lua_tointeger(L, 4); + u32 color = lua_tointeger(L, 5); + u32 bgcolor = lua_tointeger(L, 6); + u16* screen; + switch (which_screen) { + case 0: screen = MAIN_SCREEN; break; + case 1: screen = ALT_SCREEN; break; + case 2: screen = TOP_SCREEN; break; + case 3: screen = BOT_SCREEN; break; + default: screen = MAIN_SCREEN; + } + DrawString(screen, text, x, y, color, bgcolor); + return 0; +} + +static int UIGlobal_Print(lua_State* L) { + //const char* text = lua_tostring(L, 1); + char buf[OUTPUTMAXCHARSPERLINE] = {0}; + int argcount = lua_gettop(L); + for (int i = 0; i < lua_gettop(L); i++) { + strlcat(buf, lua_tostring(L, i+1), OUTPUTMAXCHARSPERLINE); + if (i < argcount) strlcat(buf, " ", OUTPUTMAXCHARSPERLINE); + } + ShiftOutputBufferUp(); + WriteToOutputBuffer((char*)buf); + RenderOutputBuffer(); + return 0; +} + static const luaL_Reg UIlib[] = { {"ShowPrompt", UI_ShowPrompt}, {"ShowString", UI_ShowString}, {"WordWrapString", UI_WordWrapString}, + {"ClearScreenF", UI_ClearScreenF}, {"ShowSelectPrompt", UI_ShowSelectPrompt}, {"ShowProgress", UI_ShowProgress}, + {"DrawString", UI_DrawString}, + {NULL, NULL} +}; + +static const luaL_Reg UIGlobalLib[] = { + {"print", UIGlobal_Print}, {NULL, NULL} }; int gm9lua_open_UI(lua_State* L) { luaL_newlib(L, UIlib); + lua_pushglobaltable(L); // push global table to stack + luaL_setfuncs(L, UIGlobalLib, 0); // set global funcs + lua_pop(L, 1); // pop global table from stack return 1; } diff --git a/arm9/source/lua/gm9ui.h b/arm9/source/lua/gm9ui.h index bf51f9411..f3c73adad 100644 --- a/arm9/source/lua/gm9ui.h +++ b/arm9/source/lua/gm9ui.h @@ -4,4 +4,8 @@ #define GM9LUA_UILIBNAME "UI" +void ShiftOutputBufferUp(void); +void ClearOutputBuffer(void); +void RenderOutputBuffer(void); +void WriteToOutputBuffer(char* text); int gm9lua_open_UI(lua_State* L); diff --git a/init.lua b/init.lua index e136332e3..309ef231e 100644 --- a/init.lua +++ b/init.lua @@ -4,8 +4,10 @@ UI.ShowPrompt(false, "3 ^ 2: "..(3 ^ 2)) local options = {'Dustbowl', 'Granary', 'Gravel Pit', 'Well', '2Fort', 'Hydro'} local result = UI.ShowSelectPrompt(options, "Choose one...") if result then + print("Chosen: "..options[result]) UI.ShowPrompt(false, "You chose: "..result..", or "..options[result]) else + print("Chosen: none") UI.ShowPrompt(false, "Oh you don't like any of them?") end @@ -14,6 +16,9 @@ UI.ShowPrompt(false, "I got: "..tostring(res)) local max = 1000000 for i = 0, max do + if i % 5000 == 0 then + print("i = "..i.."/max") + end if not UI.ShowProgress(i, max, "Pushing the Payload...") then break end end local max = 500000 diff --git a/testprint.lua b/testprint.lua new file mode 100644 index 000000000..7ea7612c2 --- /dev/null +++ b/testprint.lua @@ -0,0 +1,66 @@ +function RGB(r, g, b) + return ((r >> 3) << 11 | (g >> 2) << 5 | (b >> 3)) +end + +white = RGB(0xFF, 0xFF, 0xFF) +black = RGB(0x00, 0x00, 0x00) +grey = RGB(0x80, 0x80, 0x80) +red = RGB(0xFF, 0x00, 0x00) +superfuchsia = RGB(0xFF, 0x00, 0xEF) + +list = { + "Half-Life", + "Half-Life: Opposing Force", + "Half-Life: Blue Shift", + "Half-Life: Source", + "Half-Life 2", + "Half-Life 2: Episode One", + "Half-Life 2: Episode Two", + "Counter-Strike", + "Counter-Strike: Condition Zero", + "Counter-Strike: Condition Zero Deleted Scenes", + "Counter-Strike: Source", + "Counter-Strike: Global Offensive", + "Left 4 Dead", + "Left 4 Dead 2", + "Portal", + "Portal 2", + "Team Fortress 2", + "Dota 2", + "Half-Life: Alyx", + "Black Mesa", + "Garry's Mod", + "Portal Stories: Mel", + "The Stanley Parable" +} + +function drawshadow(which, text, x, y) + UI.DrawString(which, text, x+1, y+1, grey, black) + UI.DrawString(which, text, x, y, white, superfuchsia) +end +function drawshadow2(which, text, x, y) + UI.DrawString(which, text, x+1, y+1, grey, black) + UI.DrawString(which, text, x, y+1, grey, superfuchsia) + UI.DrawString(which, text, x+1, y, grey, superfuchsia) + UI.DrawString(which, text, x, y, white, superfuchsia) +end + +for i = 8, 12 do + UI.ClearScreenF(false, true, red) + UI.DrawString(1, "Current height: "..i, 0, 0, white, black) + for k, v in ipairs(list) do + UI.DrawString(1, k..": "..v, 0, (k) * i, white, black) + end + UI.ShowPrompt(false, "Press A") +end +for k, v in ipairs(list) do + print(k, v) + UI.ShowPrompt(false, "Press A") +end + +print('test1') +UI.ShowPrompt(false, "Press A") +print('test2') +UI.ShowPrompt(false, "Press A") +print('does it work') +UI.ShowPrompt(false, "Press A") From 8281e0cc45f75bddf5cfada9242df0661521876c Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Wed, 2 Aug 2023 17:04:06 -0700 Subject: [PATCH 021/124] build vram0.tar including subdirs of data --- Makefile | 6 +- arm9/.DS_Store | Bin 6148 -> 0 bytes data/luapackages/json.lua | 388 ++++++++++++++++++++++++++++++++++++++ testjson.lua | 10 + 4 files changed, 401 insertions(+), 3 deletions(-) delete mode 100644 arm9/.DS_Store create mode 100644 data/luapackages/json.lua create mode 100644 testjson.lua diff --git a/Makefile b/Makefile index a58ed9307..6156c68a0 100644 --- a/Makefile +++ b/Makefile @@ -17,10 +17,10 @@ export COMMON_DIR := ../common # Definitions for initial RAM disk VRAM_TAR := $(OUTDIR)/vram0.tar -VRAM_DATA := data +VRAM_DATA := data/* VRAM_FLAGS := --make-new --path-limit 99 --size-limit 262144 ifeq ($(NTRBOOT),1) - VRAM_SCRIPTS := resources/gm9/scripts + VRAM_SCRIPTS := resources/gm9/scripts/* endif ifeq ($(OS),Windows_NT) @@ -83,7 +83,7 @@ release: clean unmarked_readme $(VRAM_TAR): $(SPLASH) $(OVERRIDE_FONT) $(VRAM_DATA) $(VRAM_SCRIPTS) @mkdir -p "$(@D)" @echo "Creating $@" - @$(PY3) utils/add2tar.py $(VRAM_FLAGS) $(VRAM_TAR) $(shell find $^ -type f) + $(PY3) utils/add2tar.py $(VRAM_FLAGS) $(VRAM_TAR) $(shell ls -d -1 $^) %.elf: .FORCE @echo "Building $@" diff --git a/arm9/.DS_Store b/arm9/.DS_Store deleted file mode 100644 index 0404c701b0327122bd4009fec01367c472d3ae39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ5Iwu5SuY}I#g7V<0K&2augJ5cbtL)a0xmju7bD(Z+1o@ z8xzqX1T)g?ThC{A{A_u=MC7tnIV2hoQ37S`9b))G*w5OLo?6)H!Wv~YAC4wXz39Z6 z;de5?&u*UzTG1_46hFWDLl%4MCeMqiDPT>e@Apq{r!QyQ5#=`#i<^x%WI0AU9nc*$ zs9;GGnnrKXoeK^u($V<2W=Pc8@C|Ic@y|KlXPG6sx+KgEFS=c7Es zEopD<+#L5>4;@2UIIb03r(hCVF=DwDpFo4a?s)=C6= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/testjson.lua b/testjson.lua new file mode 100644 index 000000000..b49baa42c --- /dev/null +++ b/testjson.lua @@ -0,0 +1,10 @@ +local json = require('json') + +local mytable = {a=1, b='two'} +UI.ShowPrompt(false, json.encode(mytable)) + +local myjson = '{"c": 3, "d": "four"}' +UI.ShowPrompt(false, "Decoding: \n"..myjson) +for k, v in pairs(json.decode(myjson)) do + UI.ShowPrompt(false, "Key: "..k.."\nValue: "..v) +end From 7106f4401129b6a7cdf99ec9c70d9a1a74edab5f Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Wed, 2 Aug 2023 17:04:30 -0700 Subject: [PATCH 022/124] move default path to GM9LUA_DEFAULT_PATH --- arm9/source/lua/gm9loader.c | 8 ++------ arm9/source/lua/gm9loader.h | 12 ++++++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/arm9/source/lua/gm9loader.c b/arm9/source/lua/gm9loader.c index 022a64cd2..c325a8b61 100644 --- a/arm9/source/lua/gm9loader.c +++ b/arm9/source/lua/gm9loader.c @@ -1,3 +1,4 @@ +#include "gm9loader.h" #include "gm9lua.h" #include "vff.h" #include "ui.h" @@ -102,12 +103,7 @@ void ResetPackageSearchersAndPath(lua_State* L) { // the default package.path only makes sense on a full OS // maybe this should include the lua script's current directory somehow... - lua_pushliteral(L, - "0:/gm9/luapackages/"LUA_PATH_MARK".lua" LUA_PATH_SEP - "0:/gm9/luapackages/"LUA_PATH_MARK"/init.lua" LUA_PATH_SEP - "V:/luapackages/"LUA_PATH_MARK".lua" LUA_PATH_SEP - "V:/luapackages/"LUA_PATH_MARK"/init.lua" - ); + lua_pushliteral(L, GM9LUA_DEFAULT_PATH); lua_setfield(L, -2, "path"); // package.cpath is for loading binary modules, useless on GM9 diff --git a/arm9/source/lua/gm9loader.h b/arm9/source/lua/gm9loader.h index 93452cf5a..b1ebcd500 100644 --- a/arm9/source/lua/gm9loader.h +++ b/arm9/source/lua/gm9loader.h @@ -1,3 +1,15 @@ #include "gm9lua.h" +/* + * 0:/gm9/luapackages/?.lua; + * 0:/gm9/luapackages/?/init.lua; + * V:/luapackages/?.lua; + * V:/luapackages/?/init.lua + */ +#define GM9LUA_DEFAULT_PATH \ + "0:/gm9/luapackages/"LUA_PATH_MARK".lua" LUA_PATH_SEP \ + "0:/gm9/luapackages/"LUA_PATH_MARK"/init.lua" LUA_PATH_SEP \ + "V:/luapackages/"LUA_PATH_MARK".lua" LUA_PATH_SEP \ + "V:/luapackages/"LUA_PATH_MARK"/init.lua" + void ResetPackageSearchersAndPath(lua_State* L); From 2b2419339ebcae86bbdd167c18ea73a166326899 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Wed, 2 Aug 2023 19:02:49 -0700 Subject: [PATCH 023/124] FS_FileGetData --- README.md | 5 +++-- arm9/source/lua/gm9fs.c | 21 +++++++++++++++++++++ arm9/source/lua/gm9fs.h | 2 -- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cb88b9064..b7656bd93 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Copy init.lua to the SD card and select it. There is now an "Execute Lua script" Or put your script at `0:/gm9/luascripts/*.lua`. Then press HOME or POWER and choose "Lua scripts...". -The main lua stuff is at `arm9/source/lua`. Custom stuff is `gm9lua` and `gm9ui`. +The main lua stuff is at `arm9/source/lua`. Custom files are prefixed with `gm9`. The API here is not at all stable. But there are currently two libraries to play with. This is not set in stone! * print(...) @@ -30,4 +30,5 @@ The API here is not at all stable. But there are currently two libraries to play * 1 = alt screen * 2 = top screen * 3 = bottom screen -* bool FS.InitImgFS(path) +* bool FS.InitImgFS(string path) +* string FS.FileGetData(string path, int size, int offset) diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index b80968828..1d5676767 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -1,4 +1,7 @@ #include "gm9fs.h" +#include "fsinit.h" +#include "fsutil.h" +#include "vff.h" static int FS_InitImgFS(lua_State* L) { CheckLuaArgCount(L, 1, "InitImgFS"); @@ -16,8 +19,26 @@ static int FS_InitImgFS(lua_State* L) { return 1; } +static int FS_FileGetData(lua_State* L) { + CheckLuaArgCount(L, 3, "FileGetData"); + const char* path = luaL_checkstring(L, 1); + lua_Integer size = lua_tointeger(L, 2); + lua_Integer offset = lua_tointeger(L, 3); + if (size == -1) size = STD_BUFFER_SIZE; + else if (size == 0) return luaL_error(L, "size cannot be 0"); + else if (size > STD_BUFFER_SIZE) return luaL_error(L, "size cannot be above %I (STD_BUFFER_SIZE)", STD_BUFFER_SIZE); + + void* buf = malloc(size); + if (!buf) return luaL_error(L, "could not allocate buffer"); + size_t read = FileGetData(path, buf, size, offset); + lua_pushlstring(L, buf, read); + free(buf); + return 1; +} + static const luaL_Reg FSlib[] = { {"InitImgFS", FS_InitImgFS}, + {"FileGetData", FS_FileGetData}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h index 1e093ebca..ab6dd25d9 100644 --- a/arm9/source/lua/gm9fs.h +++ b/arm9/source/lua/gm9fs.h @@ -1,7 +1,5 @@ #pragma once #include "gm9lua.h" -#include "vff.h" -#include "fsinit.h" #define GM9LUA_FSLIBNAME "FS" From 0f4a4c45b937a2631c6e590bd1b9ad9bf4e8c2ee Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Wed, 2 Aug 2023 23:12:35 -0700 Subject: [PATCH 024/124] testfgd, add GM9VERSION global, update README, fix indentation --- README.md | 2 ++ arm9/source/filesys/filetype.c | 4 ++-- arm9/source/lua/gm9lua.c | 14 +++----------- testfgd.lua | 13 +++++++++++++ 4 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 testfgd.lua diff --git a/README.md b/README.md index b7656bd93..76954ce85 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # GodMode9 but with lua +Fork of [d0k3/GodMode9](https://github.com/d0k3/GodMode9) with super experimental Lua support. + A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings * Makefile.build edited to support a LIBS variable on build diff --git a/arm9/source/filesys/filetype.c b/arm9/source/filesys/filetype.c index 625bede6e..ea8efe809 100644 --- a/arm9/source/filesys/filetype.c +++ b/arm9/source/filesys/filetype.c @@ -166,8 +166,8 @@ u64 IdentifyFileType(const char* path) { u64 type = 0; if ((fsize < SCRIPT_MAX_SIZE) && (strcasecmp(ext, SCRIPT_EXT) == 0)) type |= TXT_SCRIPT; // should be a script (which is also generic text) - // this should check if it's compiled lua bytecode (done with luac), which is NOT text - else if ((fsize < LUASCRIPT_MAX_SIZE) && (strcasecmp(ext, LUASCRIPT_EXT) == 0)) + // this should check if it's compiled lua bytecode (done with luac), which is NOT text + else if ((fsize < LUASCRIPT_MAX_SIZE) && (strcasecmp(ext, LUASCRIPT_EXT) == 0)) type |= TXT_LUA; if (fsize < STD_BUFFER_SIZE) type |= TXT_GENERIC; return type; diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 8ed2b1700..1bdc1feb7 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -80,15 +80,6 @@ static const luaL_Reg gm9lualibs[] = { {NULL, NULL} }; -static int LuaShowPrompt(lua_State* L) { - bool ask = lua_toboolean(L, 1); - const char* text = lua_tostring(L, 2); - - bool ret = ShowPrompt(ask, "%s", text); - lua_pushboolean(L, ret); - return 1; -} - static void loadlibs(lua_State* L) { const luaL_Reg* lib; for (lib = gm9lualibs; lib->func; lib++) { @@ -104,9 +95,10 @@ bool ExecuteLuaScript(const char* path_script) { ResetPackageSearchersAndPath(L); ClearOutputBuffer(); - //int result = luaL_loadbuffer(L, script_buffer, script_size, path_script); + lua_pushliteral(L, VERSION); + lua_setglobal(L, "GM9VERSION"); + int result = LoadLuaFile(L, path_script); - //free(script_buffer); if (result != LUA_OK) { char errstr[BUFSIZ] = {0}; strlcpy(errstr, lua_tostring(L, -1), BUFSIZ); diff --git a/testfgd.lua b/testfgd.lua new file mode 100644 index 000000000..93a9ccb04 --- /dev/null +++ b/testfgd.lua @@ -0,0 +1,13 @@ +json = require('json') +fpath = "0:/test.json" + +print("Reading: "..fpath) +data = FS.FileGetData("0:/test.json", -1, 0) +print("Got data of "..string.len(data).." bytes") +UI.ShowPrompt(false, "got data:\n"..data) + +print("Parsing...") +parsed = json.decode(data) +for k, v in pairs(parsed) do + UI.ShowPrompt(false, "Key: "..k.."\nValue: "..v) +end From 7d1223d5c95e62cc86c02ccfec03c23e0b4bdfe8 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Wed, 2 Aug 2023 23:49:55 -0700 Subject: [PATCH 025/124] FS_FileGetData will return a nil instead if it fails --- arm9/source/lua/gm9fs.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 1d5676767..47c5b2a52 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -30,8 +30,15 @@ static int FS_FileGetData(lua_State* L) { void* buf = malloc(size); if (!buf) return luaL_error(L, "could not allocate buffer"); - size_t read = FileGetData(path, buf, size, offset); - lua_pushlstring(L, buf, read); + // instead of using FileGetData directly we can use fvx_qread and handle the result + // and return a nil if it works (and an empty string if it really is empty) + UINT br; + FRESULT res = fvx_qread(path, buf, size, offset, &br); + if (res != FR_OK) { + lua_pushfail(L); + return 1; + } + lua_pushlstring(L, buf, br); free(buf); return 1; } From adf43bc4b31bf9dde0741861fcb01ddf194e328f Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Thu, 3 Aug 2023 00:51:33 -0700 Subject: [PATCH 026/124] it's actually luaL_pushfail --- arm9/source/lua/gm9fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 47c5b2a52..1b0441adf 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -35,7 +35,7 @@ static int FS_FileGetData(lua_State* L) { UINT br; FRESULT res = fvx_qread(path, buf, size, offset, &br); if (res != FR_OK) { - lua_pushfail(L); + luaL_pushfail(L); return 1; } lua_pushlstring(L, buf, br); From 3572658f896bcecd13cbbf6554663c52696799e3 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sat, 12 Aug 2023 10:21:00 +0200 Subject: [PATCH 027/124] os --- .vscode/settings.json | 5 + arm9/source/lua/gm9lua.c | 2 + arm9/source/lua/gm9os.c | 546 +++++++++++++++++++++++++++++++++++++++ arm9/source/lua/gm9os.h | 6 + testos.lua | 15 ++ testprint2.lua | 2 + 6 files changed, 576 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 arm9/source/lua/gm9os.c create mode 100644 arm9/source/lua/gm9os.h create mode 100644 testos.lua create mode 100644 testprint2.lua diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..de8441973 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "gm9fs.h": "c" + } +} \ No newline at end of file diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 1bdc1feb7..34a3be9ce 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -6,6 +6,7 @@ #include "gm9ui.h" #include "gm9fs.h" #include "gm9loader.h" +#include "gm9os.h" #define DEBUGSP ShowPrompt @@ -76,6 +77,7 @@ static const luaL_Reg gm9lualibs[] = { // gm9 custom {GM9LUA_UILIBNAME, gm9lua_open_UI}, {GM9LUA_FSLIBNAME, gm9lua_open_FS}, + {GM9LUA_OSLIBNAME, gm9lua_open_os}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c new file mode 100644 index 000000000..0e5826ff7 --- /dev/null +++ b/arm9/source/lua/gm9os.c @@ -0,0 +1,546 @@ +#include "gm9os.h" +#include "timer.h" +#include "rtc.h" +#include "ui.h" + +u64 osclock; + +static inline bool isLeapYear(u32 year) { + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); +} + +size_t getWeekday(bool abbreviated, char* out, u8 weekday) { + if (abbreviated) { + switch(weekday) { + case 1: + strcat(out, "Mon"); + break; + case 2: + strcat(out, "Tue"); + break; + case 3: + strcat(out, "Wed"); + break; + case 4: + strcat(out, "Thu"); + break; + case 5: + strcpy(out, "Fri"); + break; + case 6: + strcat(out, "Sat"); + break; + case 7: + strcat(out, "Sun"); + break; + default: + strcat(out, ""); + return 0; + } + return 3; + } + switch(weekday) { + case 1: + strcat(out, "Monday"); + return 6; + case 2: + strcat(out, "Tuesday"); + return 7; + case 3: + strcat(out, "Wednesday"); + return 9; + case 4: + strcat(out, "Thursday"); + return 8; + case 5: + strcpy(out, "Friday"); + return 6; + case 6: + strcat(out, "Saturday"); + return 8; + case 7: + strcat(out, "Sunday"); + return 6; + default: + strcat(out, ""); + return 0; + } + return 0; + +} + +size_t getMonthName(bool abbreviated, char* out, u8 month) { + if (abbreviated) { + switch(month) { + case 1: + strcat(out, "Jan"); + break; + case 2: + strcat(out, "Feb"); + break; + case 3: + strcat(out, "Mar"); + break; + case 4: + strcat(out, "Apr"); + break; + case 5: + strcat(out, "May"); + break; + case 6: + strcat(out, "Jun"); + break; + case 7: + strcat(out, "Jul"); + break; + case 8: + strcat(out, "Aug"); + break; + case 9: + strcat(out, "Sep"); + break; + case 10: + strcat(out, "Oct"); + break; + case 11: + strcat(out, "Nov"); + break; + case 12: + strcat(out, "Dec"); + break; + default: + strcat(out, ""); + return 0; + } + return 3; + } + switch(month) { + case 1: + strcat(out, "January"); + return 7; + case 2: + strcat(out, "February"); + return 8; + case 3: + strcat(out, "March"); + return 5; + case 4: + strcat(out, "April"); + return 5; + case 5: + strcat(out, "May"); + return 3; + case 6: + strcat(out, "Juny"); + return 4; + case 7: + strcat(out, "July"); + return 4; + case 8: + strcat(out, "August"); + return 6; + case 9: + strcat(out, "September"); + return 9; + case 10: + strcat(out, "October"); + return 7; + case 11: + strcat(out, "November"); + return 8; + case 12: + strcat(out, "December"); + return 8; + default: + strcat(out, ""); + return 0; + } + return 0; +} + +u16 getDaysMonths(u32 months, u8 years) { + u8 daysInMonth[12] = {31, isLeapYear(2000 + years) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //is this ?: bad practice? + u16 ret; + for (u32 month = 0; month < months - 1; month++) { + ret += daysInMonth[month]; + } + return ret; +} + +bool my_strftime(char* _out, size_t _maxsize, char* str, DsTime *dstime) { //my refers to github.com/Gruetzig + size_t strl = strlen(str); + size_t outpos = 0; + char out[_maxsize+10]; + memset(out, 0, _maxsize+10); + u8 minute, hour, day, month, year, weekday, second, weeknumber; + u16 currentday, nextsunday, fyear; + char numbuf[3], numbuf2[5], numbuf3[9], numnum1[3], numnum2[3], numnum3[3]; + if (!is_valid_dstime(dstime)) { + return false; + } + for (size_t i = 0;i 12) { + hour = hour - 12; + } + if (!hour) { + hour = 12; + } + if (hour < 10) { + sprintf(numbuf, "0%d", hour); + } else { + sprintf(numbuf, "%d", hour); + } + strcat(out, numbuf); + outpos += 2; + break; + case 'j': + currentday = getDaysMonths(DSTIMEGET(dstime, bcd_M), DSTIMEGET(dstime, bcd_Y))+DSTIMEGET(dstime, bcd_D); + if (currentday < 10) { + sprintf(numbuf, "00%d", currentday); + } else if (currentday < 100) { + sprintf(numbuf, "0%d", currentday); + } else { + sprintf(numbuf, "%d", currentday); + } + strcat(out, numbuf); + outpos += 3; + break; + case 'm': + month = DSTIMEGET(dstime, bcd_M); + if (month < 10) { + sprintf(numbuf, "0%d", month); + } else { + sprintf(numbuf, "%d", month); + } + strcat(out, numbuf); + outpos += 2; + break; + case 'M': + minute = DSTIMEGET(dstime, bcd_m); + if (minute < 10) { + sprintf(numbuf, "0%d", minute); + } else { + sprintf(numbuf, "%d", minute); + } + strcat(out, numbuf); + outpos += 2; + break; + case 'p': + if (hour >= 12) { + strcat(out, "PM"); + } else { + strcat(out, "AM"); + } + outpos += 2; + break; + case 'S': + second = DSTIMEGET(dstime, bcd_m); + if (second < 10) { + sprintf(numbuf, "0%d", second); + } else { + sprintf(numbuf, "%d", second); + } + strcat(out, numbuf); + outpos += 2; + break; + case 'U': + currentday = getDaysMonths(DSTIMEGET(dstime, bcd_M), DSTIMEGET(dstime, bcd_Y))+DSTIMEGET(dstime, bcd_D); + weekday = DSTIMEGET(dstime, weekday); + nextsunday = ((7-weekday)+currentday); + weeknumber = (nextsunday/7)+1; + if (weeknumber < 10) { + sprintf(numbuf, "0%d", weeknumber); + } else { + sprintf(numbuf, "%d", weeknumber); + } + strcat(out, numbuf); + outpos += 2; + break; + case 'w': + weekday = DSTIMEGET(dstime, weekday); + if (weekday == 7) { + weekday = 0; + } + sprintf(out, "%d", weekday); + strcat(out, numbuf); + outpos++; + break; + case 'W': + currentday = getDaysMonths(DSTIMEGET(dstime, bcd_M), DSTIMEGET(dstime, bcd_Y))+DSTIMEGET(dstime, bcd_D); + weekday = DSTIMEGET(dstime, weekday); + nextsunday = ((8-weekday)+currentday); + weeknumber = (nextsunday/7); + if (weeknumber < 10) { + sprintf(numbuf, "0%d", weeknumber); + } else { + sprintf(numbuf, "%d", weeknumber); + } + strcat(out, numbuf); + outpos += 2; + break; + case 'x': + month = DSTIMEGET(dstime, bcd_M); + day = DSTIMEGET(dstime, bcd_D); + year = DSTIMEGET(dstime, bcd_Y); + + if (month < 10) { + sprintf(numnum1, "0%d", month); + } else { + sprintf(numnum1, "%d", month); + } + + if (day < 10) { + sprintf(numnum2, "0%d", day); + } else { + sprintf(numnum2, "%d", day); + } + + if (year < 10) { + sprintf(numnum3, "0%d", year); + } else { + sprintf(numnum3, "%d", year); + } + sprintf(numbuf3, "%s/%s/%s", numnum1, numnum2, numnum3); + strcat(out, numbuf3); + outpos += 8; + break; + case 'X': + hour = DSTIMEGET(dstime, bcd_h); + minute = DSTIMEGET(dstime, bcd_m); + second = DSTIMEGET(dstime, bcd_s); + + if (hour < 10) { + sprintf(numnum1, "0%d", hour); + } else { + sprintf(numnum1, "%d", hour); + } + + if (minute < 10) { + sprintf(numnum2, "0%d", minute); + } else { + sprintf(numnum2, "%d", minute); + } + + if (second < 10) { + sprintf(numnum3, "0%d", second); + } else { + sprintf(numnum3, "%d", second); + } + sprintf(numbuf3, "%s:%s:%s", numnum1, numnum2, numnum3); + strcat(out, numbuf3); + outpos += 8; + break; + case 'y': + year = DSTIMEGET(dstime, bcd_Y); + if (year < 10) { + sprintf(numbuf, "0%d", year); + } else { + sprintf(numbuf, "%d", year); + } + strcat(out, numbuf); + outpos += 2; + break; + case 'Y': + fyear = DSTIMEGET(dstime, bcd_Y); + fyear += 2000; + sprintf(numbuf2, "%d", fyear); + strcat(out, numbuf2); + outpos += 4; + break; + case '%': + strcat(out, "%"); + outpos++; + break; + default: + break; //not implemented + + + } + + } else { + out[outpos] = str[i]; + outpos++; + } + if (outpos > _maxsize) { + break; + } + + } + strncpy(_out, out, _maxsize); + return true; +} + +u64 calcUnixTime(u8 years, u8 months, u8 days, u8 hours, u8 minutes, u8 seconds) { + + u8 daysInMonth[12] = {31, isLeapYear(2000 + years) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //is this ?: bad practice? + u32 curdays; + u64 ret = 0; + + ret += seconds; + ret += minutes * 60; + ret += hours * 60 * 60; + ret += (days - 1) * 24 * 60 * 60; + + for (u16 year = 0; year < years + 30; year++) { //+30 because unix time starting in 1970 but rtc starts in 2000 + if (isLeapYear(2000 + year)) { + curdays = 366; + } else { + curdays = 365; + } + ret += curdays * 24 * 60 * 60; + } + + for (u16 month = 0; month < months - 1; month++) { + ret += daysInMonth[month] * 24 * 60 * 60; + } + + return ret; +} + +u64 getUnixTimeFromRtc(DsTime *dstime) { + + u8 + seconds = DSTIMEGET(dstime, bcd_s), + minutes = DSTIMEGET(dstime, bcd_m), + hours = DSTIMEGET(dstime, bcd_h), + days = DSTIMEGET(dstime, bcd_D), + months = DSTIMEGET(dstime, bcd_M), + years = DSTIMEGET(dstime, bcd_Y); + return calcUnixTime(years, months, days, hours, minutes, seconds); + +} + +static int os_time(lua_State *L) { + int args = lua_gettop(L); + u64 unixtime; + switch(args) { + case 0: + DsTime dstime; + get_dstime(&dstime); + unixtime = getUnixTimeFromRtc(&dstime); + ShowPrompt(false, "%lld", unixtime); + lua_pushinteger(L, unixtime); + return 1; + case 6: + lua_pushinteger(L, 1); + lua_gettable(L, 1); + int year = lua_tointeger(L, -1); + if (year >= 2000) { + year -= 2000; + } + lua_pop(L, 1); + + lua_pushinteger(L, 2); + lua_gettable(L, 1); + int month = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_pushinteger(L, 3); + lua_gettable(L, 1); + int day = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_pushinteger(L, 4); + lua_gettable(L, 1); + int hour = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_pushinteger(L, 5); + lua_gettable(L, 1); + int minute = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_pushinteger(L, 6); + lua_gettable(L, 1); + int second = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_pushinteger(L, calcUnixTime(year, month, day, hour, minute, second)); + + return 1; + default: + return luaL_error(L, "not a valid amount of arguments"); + + } +} + +static int os_date(lua_State *L) { + int args = lua_gettop(L); + switch(args) { + case 0: + DsTime dstime; + get_dstime(&dstime); + char retbuf[100]; + memset(retbuf, 0, 100); + my_strftime(retbuf, 100, "%c", &dstime); + lua_pushstring(L, retbuf); + return 1; + + + default: + return luaL_error(L, "not a valid amount of arguments"); + } +} + +static int os_clock(lua_State *L) { + ShowPrompt(false, "%lld", osclock); + lua_pushinteger(L, timer_ticks(osclock)); + return 1; +} + +static const luaL_Reg os[] = { + {"clock", os_clock}, + {"time", os_time}, + {"date", os_date}, + {NULL, NULL} +}; + +int gm9lua_open_os(lua_State* L) { + luaL_newlib(L, os); + osclock = timer_start(); + ShowPrompt(false, "HUMMMMMM %lld", osclock); + return 1; +} \ No newline at end of file diff --git a/arm9/source/lua/gm9os.h b/arm9/source/lua/gm9os.h new file mode 100644 index 000000000..b737953b5 --- /dev/null +++ b/arm9/source/lua/gm9os.h @@ -0,0 +1,6 @@ +#pragma once +#include "gm9lua.h" + +#define GM9LUA_OSLIBNAME "os" + +int gm9lua_open_os(lua_State* L); \ No newline at end of file diff --git a/testos.lua b/testos.lua new file mode 100644 index 000000000..2a8ac20c3 --- /dev/null +++ b/testos.lua @@ -0,0 +1,15 @@ +function PrintTable(tbl) + for k, v in pairs(tbl) do + print(k, v) + end +end +-- time = os.date() +-- print(time) +-- UI.ShowPrompt(false, "press A") +-- unix = os.time() +-- print(unix) +UI.ShowPrompt(false, "press A") +print(os.clock) +UI.ShowPrompt(false, "press A") +print(os.clock) +UI.ShowPrompt(false, "press A") \ No newline at end of file diff --git a/testprint2.lua b/testprint2.lua new file mode 100644 index 000000000..9e4f54279 --- /dev/null +++ b/testprint2.lua @@ -0,0 +1,2 @@ +print({1,2,3}) +UI.ShowPrompt(false, "press A") \ No newline at end of file From c4a79545c796a33a03973fa9e4bb1eb5ad48c09a Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sat, 12 Aug 2023 01:38:25 -0700 Subject: [PATCH 028/124] use luaL_tolstring instead of lua_tostring --- arm9/source/lua/gm9ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 985ef29d4..8971a2b41 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -137,7 +137,7 @@ static int UIGlobal_Print(lua_State* L) { char buf[OUTPUTMAXCHARSPERLINE] = {0}; int argcount = lua_gettop(L); for (int i = 0; i < lua_gettop(L); i++) { - strlcat(buf, lua_tostring(L, i+1), OUTPUTMAXCHARSPERLINE); + strlcat(buf, luaL_tolstring(L, i+1, NULL), OUTPUTMAXCHARSPERLINE); if (i < argcount) strlcat(buf, " ", OUTPUTMAXCHARSPERLINE); } ShiftOutputBufferUp(); From 28e31793c75112d51434f3e99c5e068793088f8a Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sat, 12 Aug 2023 10:41:25 +0200 Subject: [PATCH 029/124] fix test/remove debugging showprompt --- arm9/source/lua/gm9os.c | 2 -- testos.lua | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c index 0e5826ff7..0a51b644d 100644 --- a/arm9/source/lua/gm9os.c +++ b/arm9/source/lua/gm9os.c @@ -526,7 +526,6 @@ static int os_date(lua_State *L) { } static int os_clock(lua_State *L) { - ShowPrompt(false, "%lld", osclock); lua_pushinteger(L, timer_ticks(osclock)); return 1; } @@ -541,6 +540,5 @@ static const luaL_Reg os[] = { int gm9lua_open_os(lua_State* L) { luaL_newlib(L, os); osclock = timer_start(); - ShowPrompt(false, "HUMMMMMM %lld", osclock); return 1; } \ No newline at end of file diff --git a/testos.lua b/testos.lua index 2a8ac20c3..8757cc880 100644 --- a/testos.lua +++ b/testos.lua @@ -9,7 +9,7 @@ end -- unix = os.time() -- print(unix) UI.ShowPrompt(false, "press A") -print(os.clock) +print(os.clock()) UI.ShowPrompt(false, "press A") -print(os.clock) +print(os.clock()) UI.ShowPrompt(false, "press A") \ No newline at end of file From ce71316e04271cade98756024867874fe5b93192 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sat, 12 Aug 2023 10:58:54 +0200 Subject: [PATCH 030/124] os.clock float attempt --- arm9/source/lua/gm9os.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c index 0a51b644d..9a1a106a5 100644 --- a/arm9/source/lua/gm9os.c +++ b/arm9/source/lua/gm9os.c @@ -526,7 +526,14 @@ static int os_date(lua_State *L) { } static int os_clock(lua_State *L) { - lua_pushinteger(L, timer_ticks(osclock)); + lua_pushnumber(L, (double)timer_msec(osclock)/1000); + return 1; +} + +static int os_difftime(lua_State *L) { + u64 t2 = lua_tointeger(L, 1); + u64 t1 = lua_tointeger(L, 2); + lua_pushinteger(L, t2-t1); return 1; } @@ -534,6 +541,7 @@ static const luaL_Reg os[] = { {"clock", os_clock}, {"time", os_time}, {"date", os_date}, + {"difftime", os_difftime}, {NULL, NULL} }; From 01100b2b94ace062836eff67b5cced9195d937ab Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sat, 12 Aug 2023 02:32:48 -0700 Subject: [PATCH 031/124] fix print for real --- arm9/source/lua/gm9ui.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 8971a2b41..faa15cee5 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -137,7 +137,14 @@ static int UIGlobal_Print(lua_State* L) { char buf[OUTPUTMAXCHARSPERLINE] = {0}; int argcount = lua_gettop(L); for (int i = 0; i < lua_gettop(L); i++) { - strlcat(buf, luaL_tolstring(L, i+1, NULL), OUTPUTMAXCHARSPERLINE); + const char* str = luaL_tolstring(L, i+1, NULL); + if (str) { + strlcat(buf, str, OUTPUTMAXCHARSPERLINE); + lua_pop(L, 1); + } else { + // idk + strlcat(buf, "(unknown)", OUTPUTMAXCHARSPERLINE); + } if (i < argcount) strlcat(buf, " ", OUTPUTMAXCHARSPERLINE); } ShiftOutputBufferUp(); From ba36494aced80b4cdfd01ac79d9b3d47cbf6f410 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sat, 12 Aug 2023 03:27:44 -0700 Subject: [PATCH 032/124] fix swapped offset and size for FileGetData --- arm9/source/lua/gm9fs.c | 4 ++-- testfgd.lua | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 1b0441adf..04618cfc1 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -32,8 +32,8 @@ static int FS_FileGetData(lua_State* L) { if (!buf) return luaL_error(L, "could not allocate buffer"); // instead of using FileGetData directly we can use fvx_qread and handle the result // and return a nil if it works (and an empty string if it really is empty) - UINT br; - FRESULT res = fvx_qread(path, buf, size, offset, &br); + UINT br = 0; + FRESULT res = fvx_qread(path, buf, offset, size, &br); if (res != FR_OK) { luaL_pushfail(L); return 1; diff --git a/testfgd.lua b/testfgd.lua index 93a9ccb04..55f1b4690 100644 --- a/testfgd.lua +++ b/testfgd.lua @@ -3,6 +3,7 @@ fpath = "0:/test.json" print("Reading: "..fpath) data = FS.FileGetData("0:/test.json", -1, 0) +print("Got type "..type(data)) print("Got data of "..string.len(data).." bytes") UI.ShowPrompt(false, "got data:\n"..data) From 4ef4529f65408e58d410ec1c9836bbd6ee7a595f Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sat, 12 Aug 2023 21:27:50 +0200 Subject: [PATCH 033/124] finish OS stuff --- .vscode/settings.json | 2 +- arm9/source/lua/gm9os.c | 251 ++++++++++++++++++++++++++++++++-------- testos.lua | 29 ++--- 3 files changed, 219 insertions(+), 63 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index de8441973..e19c2c3f3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { "files.associations": { - "gm9fs.h": "c" + "timer.h": "c" } } \ No newline at end of file diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c index 9a1a106a5..de1f34ecb 100644 --- a/arm9/source/lua/gm9os.c +++ b/arm9/source/lua/gm9os.c @@ -30,6 +30,7 @@ size_t getWeekday(bool abbreviated, char* out, u8 weekday) { case 6: strcat(out, "Sat"); break; + case 0: case 7: strcat(out, "Sun"); break; @@ -59,6 +60,7 @@ size_t getWeekday(bool abbreviated, char* out, u8 weekday) { strcat(out, "Saturday"); return 8; case 7: + case 0: strcat(out, "Sunday"); return 6; default: @@ -167,7 +169,107 @@ u16 getDaysMonths(u32 months, u8 years) { return ret; } -bool my_strftime(char* _out, size_t _maxsize, char* str, DsTime *dstime) { //my refers to github.com/Gruetzig +u64 calcUnixTime(u8 years, u8 months, u8 days, u8 hours, u8 minutes, u8 seconds) { + u8 daysInMonth[12] = {31, isLeapYear(2000 + years) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //is this ?: bad practice? + u32 curdays; + u64 ret = 0; + + ret += seconds; + ret += minutes * 60; + ret += hours * 60 * 60; + ret += (days - 1) * 24 * 60 * 60; + + for (u16 year = 0; year < years + 30; year++) { //+30 because unix time starting in 1970 but rtc starts in 2000 + if (isLeapYear(2000 + year)) { + curdays = 366; + } else { + curdays = 365; + } + ret += curdays * 24 * 60 * 60; + } + + for (u16 month = 0; month < months - 1; month++) { + ret += daysInMonth[month] * 24 * 60 * 60; + } + + return ret; +} + +u64 getUnixTimeFromRtc(DsTime *dstime) { + + u8 + seconds = DSTIMEGET(dstime, bcd_s), + minutes = DSTIMEGET(dstime, bcd_m), + hours = DSTIMEGET(dstime, bcd_h), + days = DSTIMEGET(dstime, bcd_D), + months = DSTIMEGET(dstime, bcd_M), + years = DSTIMEGET(dstime, bcd_Y); + return calcUnixTime(years, months, days, hours, minutes, seconds); + +} + +u64 timer_usec( u64 start_time ) { + return timer_ticks( start_time ) / (TICKS_PER_SEC/1000*1000); +} + +void weekdayfix(DsTime *dstime) { + int days = getUnixTimeFromRtc(dstime) / 86400; //days since thursday 1 1 1970 + u8 weekday = (days + 5) % 7; + dstime->weekday = NUM2BCD(weekday); +} + +void unixtodstime(u64 unixtime, DsTime *dstime) { + u32 seconds, minutes, hours, days, year, month; + seconds = unixtime; + minutes = seconds / 60; + seconds %= 60; + hours = minutes / 60; + minutes %= 60; + days = hours / 24; + hours %= 24; + year = 1970; + + while(true) + { + bool leapYear = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + u16 daysInYear = leapYear ? 366 : 365; + if(days >= daysInYear) + { + days -= daysInYear; + ++year; + } + else + { + static const u8 daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + for(month = 0; month < 12; ++month) + { + u8 dim = daysInMonth[month]; + + if (month == 1 && leapYear) + ++dim; + + if (days >= dim) + days -= dim; + else + break; + } + break; + } + } + days++; + month++; + u8 bcd_year = year-2000; + dstime->bcd_Y = NUM2BCD(bcd_year); + dstime->bcd_M = NUM2BCD(month); + dstime->bcd_D = NUM2BCD(days); + dstime->bcd_h = NUM2BCD(hours); + dstime->bcd_m = NUM2BCD(minutes); + dstime->bcd_s = NUM2BCD(seconds); + dstime->weekday = 0; +} + +bool my_strftime(char* _out, size_t _maxsize, const char* str, DsTime *dstime) { //my refers to github.com/Gruetzig + weekdayfix(dstime); size_t strl = strlen(str); size_t outpos = 0; char out[_maxsize+10]; @@ -269,6 +371,7 @@ bool my_strftime(char* _out, size_t _maxsize, char* str, DsTime *dstime) { //my outpos += 2; break; case 'p': + hour = DSTIMEGET(dstime, bcd_h); if (hour >= 12) { strcat(out, "PM"); } else { @@ -413,46 +516,6 @@ bool my_strftime(char* _out, size_t _maxsize, char* str, DsTime *dstime) { //my return true; } -u64 calcUnixTime(u8 years, u8 months, u8 days, u8 hours, u8 minutes, u8 seconds) { - - u8 daysInMonth[12] = {31, isLeapYear(2000 + years) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //is this ?: bad practice? - u32 curdays; - u64 ret = 0; - - ret += seconds; - ret += minutes * 60; - ret += hours * 60 * 60; - ret += (days - 1) * 24 * 60 * 60; - - for (u16 year = 0; year < years + 30; year++) { //+30 because unix time starting in 1970 but rtc starts in 2000 - if (isLeapYear(2000 + year)) { - curdays = 366; - } else { - curdays = 365; - } - ret += curdays * 24 * 60 * 60; - } - - for (u16 month = 0; month < months - 1; month++) { - ret += daysInMonth[month] * 24 * 60 * 60; - } - - return ret; -} - -u64 getUnixTimeFromRtc(DsTime *dstime) { - - u8 - seconds = DSTIMEGET(dstime, bcd_s), - minutes = DSTIMEGET(dstime, bcd_m), - hours = DSTIMEGET(dstime, bcd_h), - days = DSTIMEGET(dstime, bcd_D), - months = DSTIMEGET(dstime, bcd_M), - years = DSTIMEGET(dstime, bcd_Y); - return calcUnixTime(years, months, days, hours, minutes, seconds); - -} - static int os_time(lua_State *L) { int args = lua_gettop(L); u64 unixtime; @@ -461,10 +524,9 @@ static int os_time(lua_State *L) { DsTime dstime; get_dstime(&dstime); unixtime = getUnixTimeFromRtc(&dstime); - ShowPrompt(false, "%lld", unixtime); lua_pushinteger(L, unixtime); return 1; - case 6: + case 1: lua_pushinteger(L, 1); lua_gettable(L, 1); int year = lua_tointeger(L, -1); @@ -508,17 +570,108 @@ static int os_time(lua_State *L) { } static int os_date(lua_State *L) { + DsTime dstime; + + get_dstime(&dstime); + char retbuf[100]; + memset(retbuf, 0, 100); int args = lua_gettop(L); switch(args) { case 0: - DsTime dstime; - get_dstime(&dstime); - char retbuf[100]; - memset(retbuf, 0, 100); my_strftime(retbuf, 100, "%c", &dstime); lua_pushstring(L, retbuf); return 1; - + case 1: + const char* str = lua_tostring(L, 1); + if ((strcmp(str, "*t") == 0 || strcmp(str, "!*t") == 0)) { + weekdayfix(&dstime); + //return table with date values + lua_newtable(L); + //year + lua_pushstring(L, "year"); + lua_pushinteger(L, 2000+DSTIMEGET(&dstime, bcd_Y)); + lua_settable(L, -3); + //month + lua_pushstring(L, "month"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_M)); + lua_settable(L, -3); + //wday (weekday) + lua_pushstring(L, "wday"); + lua_pushinteger(L, DSTIMEGET(&dstime, weekday)); + lua_settable(L, -3); + //yday (yearday) + lua_pushstring(L, "yday"); + lua_pushinteger(L, getDaysMonths(DSTIMEGET(&dstime, bcd_M), DSTIMEGET(&dstime, bcd_Y))+DSTIMEGET(&dstime, bcd_D)); + lua_settable(L, -3); + //day + lua_pushstring(L, "day"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_D)); + lua_settable(L, -3); + //hour + lua_pushstring(L, "hour"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_h)); + lua_settable(L, -3); + //minute + lua_pushstring(L, "min"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_m)); + lua_settable(L, -3); + //second + lua_pushstring(L, "sec"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_s)); + lua_settable(L, -3); + return 1; + } else { + my_strftime(retbuf, 100, lua_tostring(L, 1), &dstime); + lua_pushstring(L, retbuf); + return 1; + } + case 2: + const char* str2 = lua_tostring(L, 1); + if ((strcmp(str2, "*t") == 0 || strcmp(str2, "!*t") == 0)) { + unixtodstime( lua_tointeger(L, 2) , &dstime); + weekdayfix(&dstime); + //return table with date values + lua_newtable(L); + + //year + lua_pushstring(L, "year"); + lua_pushinteger(L, 2000+DSTIMEGET(&dstime, bcd_Y)); + lua_settable(L, -3); + //month + lua_pushstring(L, "month"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_M)); + lua_settable(L, -3); + //wday (weekday) + lua_pushstring(L, "wday"); + lua_pushinteger(L, DSTIMEGET(&dstime, weekday)); + lua_settable(L, -3); + //yday (yearday) + lua_pushstring(L, "yday"); + lua_pushinteger(L, getDaysMonths(DSTIMEGET(&dstime, bcd_M), DSTIMEGET(&dstime, bcd_Y))+DSTIMEGET(&dstime, bcd_D)); + lua_settable(L, -3); + //day + lua_pushstring(L, "day"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_D)); + lua_settable(L, -3); + //hour + lua_pushstring(L, "hour"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_h)); + lua_settable(L, -3); + //minute + lua_pushstring(L, "min"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_m)); + lua_settable(L, -3); + //second + lua_pushstring(L, "sec"); + lua_pushinteger(L, DSTIMEGET(&dstime, bcd_s)); + lua_settable(L, -3); + return 1; + } else { + unixtodstime( lua_tointeger(L, 2) , &dstime); + my_strftime(retbuf, 100, str2, &dstime); + lua_pushstring(L, retbuf); + return 1; + } default: return luaL_error(L, "not a valid amount of arguments"); @@ -526,7 +679,7 @@ static int os_date(lua_State *L) { } static int os_clock(lua_State *L) { - lua_pushnumber(L, (double)timer_msec(osclock)/1000); + lua_pushnumber(L, timer_usec(osclock)/10000000.0); return 1; } diff --git a/testos.lua b/testos.lua index 8757cc880..435afa9eb 100644 --- a/testos.lua +++ b/testos.lua @@ -1,15 +1,18 @@ function PrintTable(tbl) - for k, v in pairs(tbl) do - print(k, v) - end + for k, v in pairs(tbl) do + print(k, v) + end end --- time = os.date() --- print(time) --- UI.ShowPrompt(false, "press A") --- unix = os.time() --- print(unix) -UI.ShowPrompt(false, "press A") -print(os.clock()) -UI.ShowPrompt(false, "press A") -print(os.clock()) -UI.ShowPrompt(false, "press A") \ No newline at end of file +print("First clock" .. os.clock()) +print(os.time()) +print(os.time({10, 10, 10, 10, 10, 10})) +print(os.date()) +print(os.date("%x - %I:%M%p")) + +print("Second clock" .. os.clock()) + + +PrintTable(os.date("*t")) +PrintTable(os.date("*t", 1286705410)) +print(os.date("%c hehe", 1286705410)) +UI.ShowPrompt(false, "Press A") From 04fc3351599dfc78a188dc148c09444fac42afd0 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sat, 12 Aug 2023 21:39:10 +0200 Subject: [PATCH 034/124] fix os.clock --- arm9/source/lua/gm9os.c | 5 ++++- testos.lua | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c index de1f34ecb..5622912b9 100644 --- a/arm9/source/lua/gm9os.c +++ b/arm9/source/lua/gm9os.c @@ -209,7 +209,7 @@ u64 getUnixTimeFromRtc(DsTime *dstime) { } u64 timer_usec( u64 start_time ) { - return timer_ticks( start_time ) / (TICKS_PER_SEC/1000*1000); + return timer_ticks( start_time ) / (TICKS_PER_SEC/1000000); } void weekdayfix(DsTime *dstime) { @@ -626,6 +626,9 @@ static int os_date(lua_State *L) { return 1; } case 2: + if (lua_tointeger(L, 2) < 946684800) { //unix timestamp is 01.01.2000 00:00:00, so everything before is previous century and not supported + return luaL_error(L, "unix timestamp from before 2000 is not supported"); + } const char* str2 = lua_tostring(L, 1); if ((strcmp(str2, "*t") == 0 || strcmp(str2, "!*t") == 0)) { unixtodstime( lua_tointeger(L, 2) , &dstime); diff --git a/testos.lua b/testos.lua index 435afa9eb..c064a49f3 100644 --- a/testos.lua +++ b/testos.lua @@ -3,13 +3,13 @@ function PrintTable(tbl) print(k, v) end end -print("First clock" .. os.clock()) +print("First clock " .. string.format("%.8f", os.clock())) print(os.time()) print(os.time({10, 10, 10, 10, 10, 10})) print(os.date()) print(os.date("%x - %I:%M%p")) -print("Second clock" .. os.clock()) +print("Second clock " .. string.format("%.8f", os.clock())) PrintTable(os.date("*t")) From 7c9275e75a104d623b218d3206bd55cc60de3a09 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sun, 13 Aug 2023 12:54:03 +0200 Subject: [PATCH 035/124] shorten table in/out --- .vscode/settings.json | 3 +- arm9/source/lua/gm9os.c | 71 +++++++++++++---------------------------- testos.lua | 2 ++ 3 files changed, 27 insertions(+), 49 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e19c2c3f3..e7707c655 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "files.associations": { - "timer.h": "c" + "timer.h": "c", + "chrono": "c" } } \ No newline at end of file diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c index 5622912b9..a5f25c332 100644 --- a/arm9/source/lua/gm9os.c +++ b/arm9/source/lua/gm9os.c @@ -527,36 +527,28 @@ static int os_time(lua_State *L) { lua_pushinteger(L, unixtime); return 1; case 1: - lua_pushinteger(L, 1); - lua_gettable(L, 1); + lua_geti(L, 1, 1); int year = lua_tointeger(L, -1); - if (year >= 2000) { - year -= 2000; - } lua_pop(L, 1); + if (year >= 2000) year -= 2000; - lua_pushinteger(L, 2); - lua_gettable(L, 1); + lua_geti(L, 1, 2); int month = lua_tointeger(L, -1); lua_pop(L, 1); - lua_pushinteger(L, 3); - lua_gettable(L, 1); + lua_geti(L, 1, 3); int day = lua_tointeger(L, -1); lua_pop(L, 1); - lua_pushinteger(L, 4); - lua_gettable(L, 1); + lua_geti(L, 1, 4); int hour = lua_tointeger(L, -1); lua_pop(L, 1); - lua_pushinteger(L, 5); - lua_gettable(L, 1); + lua_geti(L, 1, 5); int minute = lua_tointeger(L, -1); lua_pop(L, 1); - lua_pushinteger(L, 6); - lua_gettable(L, 1); + lua_geti(L, 1, 6); int second = lua_tointeger(L, -1); lua_pop(L, 1); @@ -588,37 +580,29 @@ static int os_date(lua_State *L) { //return table with date values lua_newtable(L); //year - lua_pushstring(L, "year"); lua_pushinteger(L, 2000+DSTIMEGET(&dstime, bcd_Y)); - lua_settable(L, -3); + lua_setfield(L, -2, "year"); //month - lua_pushstring(L, "month"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_M)); - lua_settable(L, -3); + lua_setfield(L, -2, "month"); //wday (weekday) - lua_pushstring(L, "wday"); lua_pushinteger(L, DSTIMEGET(&dstime, weekday)); - lua_settable(L, -3); + lua_setfield(L, -2, "wday"); //yday (yearday) - lua_pushstring(L, "yday"); lua_pushinteger(L, getDaysMonths(DSTIMEGET(&dstime, bcd_M), DSTIMEGET(&dstime, bcd_Y))+DSTIMEGET(&dstime, bcd_D)); - lua_settable(L, -3); + lua_setfield(L, -2, "yday"); //day - lua_pushstring(L, "day"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_D)); - lua_settable(L, -3); + lua_setfield(L, -2, "day"); //hour - lua_pushstring(L, "hour"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_h)); - lua_settable(L, -3); + lua_setfield(L, -2, "hour"); //minute - lua_pushstring(L, "min"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_m)); - lua_settable(L, -3); + lua_setfield(L, -2, "min"); //second - lua_pushstring(L, "sec"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_s)); - lua_settable(L, -3); + lua_setfield(L, -2, "sec"); return 1; } else { my_strftime(retbuf, 100, lua_tostring(L, 1), &dstime); @@ -635,39 +619,30 @@ static int os_date(lua_State *L) { weekdayfix(&dstime); //return table with date values lua_newtable(L); - //year - lua_pushstring(L, "year"); lua_pushinteger(L, 2000+DSTIMEGET(&dstime, bcd_Y)); - lua_settable(L, -3); + lua_setfield(L, -2, "year"); //month - lua_pushstring(L, "month"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_M)); - lua_settable(L, -3); + lua_setfield(L, -2, "month"); //wday (weekday) - lua_pushstring(L, "wday"); lua_pushinteger(L, DSTIMEGET(&dstime, weekday)); - lua_settable(L, -3); + lua_setfield(L, -2, "wday"); //yday (yearday) - lua_pushstring(L, "yday"); lua_pushinteger(L, getDaysMonths(DSTIMEGET(&dstime, bcd_M), DSTIMEGET(&dstime, bcd_Y))+DSTIMEGET(&dstime, bcd_D)); - lua_settable(L, -3); + lua_setfield(L, -2, "yday"); //day - lua_pushstring(L, "day"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_D)); - lua_settable(L, -3); + lua_setfield(L, -2, "day"); //hour - lua_pushstring(L, "hour"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_h)); - lua_settable(L, -3); + lua_setfield(L, -2, "hour"); //minute - lua_pushstring(L, "min"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_m)); - lua_settable(L, -3); + lua_setfield(L, -2, "min"); //second - lua_pushstring(L, "sec"); lua_pushinteger(L, DSTIMEGET(&dstime, bcd_s)); - lua_settable(L, -3); + lua_setfield(L, -2, "sec"); return 1; } else { unixtodstime( lua_tointeger(L, 2) , &dstime); diff --git a/testos.lua b/testos.lua index c064a49f3..a46f63477 100644 --- a/testos.lua +++ b/testos.lua @@ -13,6 +13,8 @@ print("Second clock " .. string.format("%.8f", os.clock())) PrintTable(os.date("*t")) +print("") PrintTable(os.date("*t", 1286705410)) +print("") print(os.date("%c hehe", 1286705410)) UI.ShowPrompt(false, "Press A") From c266bdf9ef448f3f2a0f9d4823eb804016b40cf5 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sun, 13 Aug 2023 12:54:55 +0200 Subject: [PATCH 036/124] remove .vscode dir --- .vscode/settings.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e7707c655..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "files.associations": { - "timer.h": "c", - "chrono": "c" - } -} \ No newline at end of file From a875d0113fc891f810f40e31aafe1488bca8cf5d Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Tue, 15 Aug 2023 03:12:53 -0700 Subject: [PATCH 037/124] enum test --- .gitignore | 1 + arm9/source/lua/gm9enum.c | 19 +++++++++++++++++++ arm9/source/lua/gm9enum.h | 13 +++++++++++++ arm9/source/lua/gm9fs.c | 29 +++++++++++++++++++++++++++-- arm9/source/lua/gm9lua.c | 4 ++++ testenum.lua | 11 +++++++++++ 6 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 arm9/source/lua/gm9enum.c create mode 100644 arm9/source/lua/gm9enum.h create mode 100644 testenum.lua diff --git a/.gitignore b/.gitignore index 2f203cf95..ba42f7591 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ # OS leftovers desktop.ini +.DS_Store # Sublime files *.sublime-* diff --git a/arm9/source/lua/gm9enum.c b/arm9/source/lua/gm9enum.c new file mode 100644 index 000000000..539367c94 --- /dev/null +++ b/arm9/source/lua/gm9enum.c @@ -0,0 +1,19 @@ +#include "gm9enum.h" + +void AddLuaEnumItems(lua_State* L, const char* name, const EnumItem* items) { + lua_getglobal(L, GM9LUA_ENUMLIBNAME); // stack: 1 + // this should probably use mua_createtable to pre-allocate the size + lua_newtable(L); // stack: 2 + const EnumItem* e; + for (e = items; e->name; e++) { + lua_pushinteger(L, e->value); // stack: 3 + lua_setfield(L, -2, e->name); // stack: 2 + } + lua_setfield(L, -2, name); // stack: 1 + lua_pop(L, 1); // stack: 0 +} + +int gm9lua_open_Enum(lua_State* L) { + lua_newtable(L); + return 1; +} diff --git a/arm9/source/lua/gm9enum.h b/arm9/source/lua/gm9enum.h new file mode 100644 index 000000000..1b2ea9897 --- /dev/null +++ b/arm9/source/lua/gm9enum.h @@ -0,0 +1,13 @@ +#include "gm9lua.h" + +#define GM9LUA_ENUMLIBNAME "Enum" +#define GLENUMITEM(what) {#what, what} + +typedef struct EnumItem { + const char *name; + lua_Unsigned value; +} EnumItem; + +void AddLuaEnumItems(lua_State* L, const char* name, const EnumItem* items); + +int gm9lua_open_Enum(lua_State* L); diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 04618cfc1..0c0fcdfaf 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -1,7 +1,7 @@ #include "gm9fs.h" -#include "fsinit.h" -#include "fsutil.h" +#include "fs.h" #include "vff.h" +#include "gm9enum.h" static int FS_InitImgFS(lua_State* L) { CheckLuaArgCount(L, 1, "InitImgFS"); @@ -49,7 +49,32 @@ static const luaL_Reg FSlib[] = { {NULL, NULL} }; +static const EnumItem Enum_FS[] = { + GLENUMITEM(T_ROOT), + GLENUMITEM(T_DIR), + GLENUMITEM(T_FILE), + GLENUMITEM(T_DOTDOT), + GLENUMITEM(PERM_SDCARD), + GLENUMITEM(PERM_IMAGE), + GLENUMITEM(PERM_RAMDRIVE), + GLENUMITEM(PERM_EMU_LVL0), + GLENUMITEM(PERM_EMU_LVL1), + GLENUMITEM(PERM_SYS_LVL0), + GLENUMITEM(PERM_SYS_LVL1), + GLENUMITEM(PERM_SYS_LVL2), + GLENUMITEM(PERM_SYS_LVL3), + GLENUMITEM(PERM_SDDATA), + GLENUMITEM(PERM_MEMORY), + GLENUMITEM(PERM_GAME), + GLENUMITEM(PERM_XORPAD), + GLENUMITEM(PERM_CART), + GLENUMITEM(PERM_VRAM), + GLENUMITEM(PERM_BASE), + {NULL, 0} +}; + int gm9lua_open_FS(lua_State* L) { luaL_newlib(L, FSlib); + AddLuaEnumItems(L, "FS", Enum_FS); return 1; } diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 34a3be9ce..1726109d9 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -4,6 +4,7 @@ #include "vff.h" #include "fsutil.h" #include "gm9ui.h" +#include "gm9enum.h" #include "gm9fs.h" #include "gm9loader.h" #include "gm9os.h" @@ -62,6 +63,9 @@ int LoadLuaFile(lua_State* L, const char* filename) { } static const luaL_Reg gm9lualibs[] = { + // enum is special so we load it first + {GM9LUA_ENUMLIBNAME, gm9lua_open_Enum}, + // built-ins {LUA_GNAME, luaopen_base}, {LUA_LOADLIBNAME, luaopen_package}, diff --git a/testenum.lua b/testenum.lua new file mode 100644 index 000000000..445db28be --- /dev/null +++ b/testenum.lua @@ -0,0 +1,11 @@ +function PrintTable(tbl) + for k, v in pairs(tbl) do + print(k, v) + end +end + +print('-- Enum --') +PrintTable(Enum) +print('-- Enum.FS --') +PrintTable(Enum.FS) +UI.ShowPrompt(false, "done") From b4ce10b8835b2153bbb038bfc6614d8f15337f72 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Wed, 16 Aug 2023 00:46:29 -0700 Subject: [PATCH 038/124] support building without lua --- Makefile.common | 4 ++++ arm9/source/lua/gm9enum.c | 2 ++ arm9/source/lua/gm9fs.c | 2 ++ arm9/source/lua/gm9loader.c | 2 ++ arm9/source/lua/gm9lua.c | 10 ++++++++++ arm9/source/lua/gm9lua.h | 5 +++-- arm9/source/lua/gm9os.c | 4 +++- arm9/source/lua/gm9ui.c | 2 ++ 8 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Makefile.common b/Makefile.common index d8c82582a..4ad6341da 100644 --- a/Makefile.common +++ b/Makefile.common @@ -67,6 +67,10 @@ ifdef SD_TIMEOUT CFLAGS += -DSD_TIMEOUT=$(SD_TIMEOUT) endif +ifeq ($(NO_LUA),1) + CFLAGS += -DNO_LUA +endif + ifdef N_PANES CFLAGS += -DN_PANES=$(N_PANES) endif diff --git a/arm9/source/lua/gm9enum.c b/arm9/source/lua/gm9enum.c index 539367c94..29c86656c 100644 --- a/arm9/source/lua/gm9enum.c +++ b/arm9/source/lua/gm9enum.c @@ -1,3 +1,4 @@ +#ifndef NO_LUA #include "gm9enum.h" void AddLuaEnumItems(lua_State* L, const char* name, const EnumItem* items) { @@ -17,3 +18,4 @@ int gm9lua_open_Enum(lua_State* L) { lua_newtable(L); return 1; } +#endif diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 0c0fcdfaf..648999546 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -1,3 +1,4 @@ +#ifndef NO_LUA #include "gm9fs.h" #include "fs.h" #include "vff.h" @@ -78,3 +79,4 @@ int gm9lua_open_FS(lua_State* L) { AddLuaEnumItems(L, "FS", Enum_FS); return 1; } +#endif diff --git a/arm9/source/lua/gm9loader.c b/arm9/source/lua/gm9loader.c index c325a8b61..abfead45c 100644 --- a/arm9/source/lua/gm9loader.c +++ b/arm9/source/lua/gm9loader.c @@ -1,3 +1,4 @@ +#ifndef NO_LUA #include "gm9loader.h" #include "gm9lua.h" #include "vff.h" @@ -124,3 +125,4 @@ void ResetPackageSearchersAndPath(lua_State* L) { lua_pop(L, 1); // remove "searchers" lua_pop(L, 1); // remove "packages" } +#endif diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 1726109d9..4b25dec3f 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -1,5 +1,6 @@ #include "gm9lua.h" #include "ui.h" +#ifndef NO_LUA #include "ff.h" #include "vff.h" #include "fsutil.h" @@ -126,3 +127,12 @@ bool ExecuteLuaScript(const char* path_script) { lua_close(L); return true; } +#else +// No-Lua version +bool ExecuteLuaScript(const char* path_script) { + (void)path_script; // unused + ShowPrompt(false, "This build of GodMode9 was\n" + "compiled without Lua support."); + return false; +} +#endif diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index a8eabd6dc..e7cea910b 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -1,14 +1,14 @@ #pragma once +#include "common.h" #include "lua.h" #include "lauxlib.h" #include "lualib.h" - -#include "common.h" #include "scripting.h" #define LUASCRIPT_EXT "lua" #define LUASCRIPT_MAX_SIZE STD_BUFFER_SIZE +#ifndef NO_LUA static inline void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) { int args = lua_gettop(L); if (args != argcount) { @@ -17,4 +17,5 @@ static inline void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) } int LoadLuaFile(lua_State* L, const char* filename); +#endif bool ExecuteLuaScript(const char* path_script); diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c index a5f25c332..a98b42e20 100644 --- a/arm9/source/lua/gm9os.c +++ b/arm9/source/lua/gm9os.c @@ -1,3 +1,4 @@ +#ifndef NO_LUA #include "gm9os.h" #include "timer.h" #include "rtc.h" @@ -680,4 +681,5 @@ int gm9lua_open_os(lua_State* L) { luaL_newlib(L, os); osclock = timer_start(); return 1; -} \ No newline at end of file +} +#endif diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index faa15cee5..e5826cb38 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -1,3 +1,4 @@ +#ifndef NO_LUA #include "gm9ui.h" #define MAXOPTIONS 256 @@ -176,3 +177,4 @@ int gm9lua_open_UI(lua_State* L) { lua_pop(L, 1); // pop global table from stack return 1; } +#endif From 0971a6902ccbb9e3fde24b83629c1798e214ffd4 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sat, 19 Aug 2023 01:44:57 -0700 Subject: [PATCH 039/124] NO_LUA hides menu options (except when you directly select a lua file) --- arm9/source/godmode.c | 13 +++++++++++++ arm9/source/language.inl | 1 + arm9/source/lua/gm9lua.c | 4 ++-- resources/languages/source.json | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 6c0c64597..790d1eb21 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -2144,8 +2144,15 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan return 0; } else if (user_select == luascript) { // execute lua script +#ifndef NO_LUA if (ShowPrompt(true, "%s\n%s", pathstr, STR_WARNING_DO_NOT_RUN_UNTRUSTED_SCRIPTS)) ShowPrompt(false, "%s\n%s", pathstr, ExecuteLuaScript(file_path) ? STR_SCRIPT_EXECUTE_SUCCESS : STR_SCRIPT_EXECUTE_FAILURE); +#else + // instead of outright removing the option, i think + // having it display an error would be useful + // so the user knows their issue exactly + ShowPrompt(false, "%s", STR_LUA_NOT_INCLUDED); +#endif GetDirContents(current_dir, current_path); ClearScreenF(true, true, COLOR_STD_BG); return 0; @@ -2932,7 +2939,9 @@ u32 GodMode(int entrypoint) { int brick = (HID_ReadState() & BUTTON_R1) ? ++n_opt : 0; int titleman = ++n_opt; int scripts = ++n_opt; +#ifndef NO_LUA int luascripts = ++n_opt; +#endif int payloads = ++n_opt; int more = ++n_opt; if (poweroff > 0) optionstr[poweroff - 1] = STR_POWEROFF_SYSTEM; @@ -2941,7 +2950,9 @@ u32 GodMode(int entrypoint) { if (language > 0) optionstr[language - 1] = STR_LANGUAGE; if (brick > 0) optionstr[brick - 1] = STR_BRICK_MY_3DS; if (scripts > 0) optionstr[scripts - 1] = STR_SCRIPTS; +#ifndef NO_LUA if (luascripts > 0) optionstr[luascripts - 1] = STR_LUA_SCRIPTS; +#endif if (payloads > 0) optionstr[payloads - 1] = STR_PAYLOADS; if (more > 0) optionstr[more - 1] = STR_MORE; @@ -3019,6 +3030,7 @@ u32 GodMode(int entrypoint) { ClearScreenF(true, true, COLOR_STD_BG); break; } +#ifndef NO_LUA } else if (user_select == luascripts) { if (!CheckSupportDir(LUASCRIPTS_DIR)) { ShowPrompt(false, STR_LUA_SCRIPTS_DIRECTORY_NOT_FOUND, LUASCRIPTS_DIR); @@ -3028,6 +3040,7 @@ u32 GodMode(int entrypoint) { ClearScreenF(true, true, COLOR_STD_BG); break; } +#endif } else if (user_select == payloads) { if (!CheckSupportDir(PAYLOADS_DIR)) ShowPrompt(false, STR_PAYLOADS_DIRECTORY_NOT_FOUND, PAYLOADS_DIR); else if (FileSelectorSupport(loadpath, STR_HOME_PAYLOADS_MENU_SELECT_PAYLOAD, PAYLOADS_DIR, "*.firm")) diff --git a/arm9/source/language.inl b/arm9/source/language.inl index 1eeba96d7..ecfc6b162 100644 --- a/arm9/source/language.inl +++ b/arm9/source/language.inl @@ -812,3 +812,4 @@ STRING(SYSINFO_SD_CID, "SD CID: %s\r\n") STRING(SYSINFO_SYSTEM_ID0, "System ID0: %s\r\n") STRING(SYSINFO_SYSTEM_ID1, "System ID1: %s\r\n") STRING(SORTING_TICKETS_PLEASE_WAIT, "Sorting tickets, please wait ...") +STRING(LUA_NOT_INCLUDED, "This build of GodMode9 was\ncompiled without Lua support.") diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 4b25dec3f..1b7437cac 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -1,5 +1,6 @@ #include "gm9lua.h" #include "ui.h" +#include "language.h" #ifndef NO_LUA #include "ff.h" #include "vff.h" @@ -131,8 +132,7 @@ bool ExecuteLuaScript(const char* path_script) { // No-Lua version bool ExecuteLuaScript(const char* path_script) { (void)path_script; // unused - ShowPrompt(false, "This build of GodMode9 was\n" - "compiled without Lua support."); + ShowPrompt(false, "%s", STR_LUA_NOT_INCLUDED); return false; } #endif diff --git a/resources/languages/source.json b/resources/languages/source.json index 602f1052d..c400cbc3e 100644 --- a/resources/languages/source.json +++ b/resources/languages/source.json @@ -805,5 +805,6 @@ "SYSINFO_SD_CID": "SD CID: %s\r\n", "SYSINFO_SYSTEM_ID0": "System ID0: %s\r\n", "SYSINFO_SYSTEM_ID1": "System ID1: %s\r\n", - "SORTING_TICKETS_PLEASE_WAIT": "Sorting tickets, please wait ..." + "SORTING_TICKETS_PLEASE_WAIT": "Sorting tickets, please wait ...", + "LUA_NOT_INCLUDED": "Sorting tickets, please wait ..." } From e1cd5eb320569785b81cdde3115fec9cb3e23f07 Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sat, 19 Aug 2023 04:27:55 -0700 Subject: [PATCH 040/124] update UI lib to better match the ideas on #1 --- README.md | 15 +++++- arm9/source/lua/gm9ui.c | 76 +++++++++++++++++++-------- init.lua | 38 -------------- mount.lua | 3 -- bigoptlist.lua => test-bigoptlist.lua | 3 +- testos.lua => test-os.lua | 2 +- test-printenum.lua | 11 ++++ testenum.lua | 11 ---- testfgd.lua | 14 ----- testjson.lua | 10 ---- testprint.lua | 66 ----------------------- testprint2.lua | 2 - testrequire.lua | 3 -- 13 files changed, 81 insertions(+), 173 deletions(-) delete mode 100644 init.lua delete mode 100644 mount.lua rename bigoptlist.lua => test-bigoptlist.lua (97%) rename testos.lua => test-os.lua (93%) create mode 100644 test-printenum.lua delete mode 100644 testenum.lua delete mode 100644 testfgd.lua delete mode 100644 testjson.lua delete mode 100644 testprint.lua delete mode 100644 testprint2.lua delete mode 100644 testrequire.lua diff --git a/README.md b/README.md index 76954ce85..44dce4ebe 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,20 @@ Or put your script at `0:/gm9/luascripts/*.lua`. Then press HOME or POWER and ch The main lua stuff is at `arm9/source/lua`. Custom files are prefixed with `gm9`. The API here is not at all stable. But there are currently two libraries to play with. This is not set in stone! + +## Global + * print(...) * Calling this will replace the alt screen with an output buffer. It doesn't support newlines or word wrapping properly yet -* bool UI.ShowPrompt(bool ask, string text) + +## UI + +* void UI.ShowPrompt(string text) +* bool UI.AskPrompt(string text) * void UI.ShowString(string text) * string UI.WordWrapString(string text[, int llen]) -* void UI.ClearScreenF(bool clear\_main, bool clear\_alt, u32 color) + * llen == -1 means alt screen +* void UI.ClearScreen(number screen, u32 color) * number UI.ShowSelectPrompt(table optionstable, string text) * bool UI.ShowProgress(u32 current, u32 total, text text) * returns true if B is pressed @@ -32,5 +40,8 @@ The API here is not at all stable. But there are currently two libraries to play * 1 = alt screen * 2 = top screen * 3 = bottom screen + +## FS + * bool FS.InitImgFS(string path) * string FS.FileGetData(string path, int size, int offset) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index e5826cb38..0029fcfee 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -1,5 +1,6 @@ #ifndef NO_LUA #include "gm9ui.h" +#include "gm9enum.h" #define MAXOPTIONS 256 #define MAXOPTIONS_STR "256" @@ -32,30 +33,59 @@ void RenderOutputBuffer(void) { } } +static u16* GetScreenFromIndex(int index) { + switch (index) { + case 0: return MAIN_SCREEN; + case 1: return ALT_SCREEN; + case 2: return TOP_SCREEN; + case 3: return BOT_SCREEN; + default: return MAIN_SCREEN; + } +} + static int UI_ShowPrompt(lua_State* L) { - CheckLuaArgCount(L, 2, "ShowPrompt"); - bool ask = lua_toboolean(L, 1); + CheckLuaArgCount(L, 1, "ShowPrompt"); + const char* text = lua_tostring(L, 1); + + ShowPrompt(false, "%s", text); + return 0; +} + +static int UI_AskPrompt(lua_State* L) { + CheckLuaArgCount(L, 2, "AskPrompt"); const char* text = lua_tostring(L, 2); - bool ret = ShowPrompt(ask, "%s", text); + bool ret = ShowPrompt(true, "%s", text); lua_pushboolean(L, ret); return 1; } static int UI_ShowString(lua_State* L) { - CheckLuaArgCount(L, 1, "ShowString"); - const char* text = lua_tostring(L, 1); + CheckLuaArgCount(L, 2, "ShowString"); + lua_Integer screen = luaL_checknumber(L, 1); + const char* text = lua_tostring(L, 2); - ShowString("%s", text); + ShowStringF(GetScreenFromIndex(screen), "%s", text); return 0; } static int UI_WordWrapString(lua_State* L) { size_t len; int isnum; + int llen; + int top = lua_gettop(L); + if (top == 1) { + llen = 0; // WordWrapString will automatically wrap it for the main screen + } else if (top == 2) { + llen = lua_tointegerx(L, 2, &isnum); + if (llen == -1) { + // special case for "word wrap for alt screen" + llen = (SCREEN_WIDTH_ALT / GetFontWidth()); + } + } else { + return luaL_error(L, "bad number of arguments passed to WordWrapString (expected 1 or 2, got %d", top); + } const char* text = lua_tolstring(L, 1, &len); - int llen = lua_tointegerx(L, 2, &isnum); - // i should check arg 2 if it's a number (but only if it was provided at all) char* buf = malloc(len + 1); strlcpy(buf, text, len + 1); WordWrapString(buf, llen); @@ -64,11 +94,10 @@ static int UI_WordWrapString(lua_State* L) { return 1; } -static int UI_ClearScreenF(lua_State* L) { - bool clear_main = lua_toboolean(L, 1); - bool clear_alt = lua_toboolean(L, 2); - u32 color = lua_tointeger(L, 3); - ClearScreenF(clear_main, clear_alt, color); +static int UI_ClearScreen(lua_State* L) { + bool which_screen = luaL_checknumber(L, 1); + u32 color = lua_tointeger(L, 2); + ClearScreen(GetScreenFromIndex(which_screen), color); return 0; } @@ -121,14 +150,7 @@ static int UI_DrawString(lua_State* L) { int y = lua_tointeger(L, 4); u32 color = lua_tointeger(L, 5); u32 bgcolor = lua_tointeger(L, 6); - u16* screen; - switch (which_screen) { - case 0: screen = MAIN_SCREEN; break; - case 1: screen = ALT_SCREEN; break; - case 2: screen = TOP_SCREEN; break; - case 3: screen = BOT_SCREEN; break; - default: screen = MAIN_SCREEN; - } + u16* screen = GetScreenFromIndex(which_screen); DrawString(screen, text, x, y, color, bgcolor); return 0; } @@ -156,9 +178,10 @@ static int UIGlobal_Print(lua_State* L) { static const luaL_Reg UIlib[] = { {"ShowPrompt", UI_ShowPrompt}, + {"AskPrompt", UI_AskPrompt}, {"ShowString", UI_ShowString}, {"WordWrapString", UI_WordWrapString}, - {"ClearScreenF", UI_ClearScreenF}, + {"ClearScreen", UI_ClearScreen}, {"ShowSelectPrompt", UI_ShowSelectPrompt}, {"ShowProgress", UI_ShowProgress}, {"DrawString", UI_DrawString}, @@ -170,8 +193,17 @@ static const luaL_Reg UIGlobalLib[] = { {NULL, NULL} }; +static const EnumItem Enum_UI[] = { + {"MainScreen", 0}, + {"AltScreen", 1}, + {"TopScreen", 2}, + {"BottomScreen", 3}, + {NULL, 0} +}; + int gm9lua_open_UI(lua_State* L) { luaL_newlib(L, UIlib); + AddLuaEnumItems(L, "UI", Enum_UI); lua_pushglobaltable(L); // push global table to stack luaL_setfuncs(L, UIGlobalLib, 0); // set global funcs lua_pop(L, 1); // pop global table from stack diff --git a/init.lua b/init.lua deleted file mode 100644 index 309ef231e..000000000 --- a/init.lua +++ /dev/null @@ -1,38 +0,0 @@ -UI.ShowPrompt(false, "math.sin(3): "..math.sin(3)) -UI.ShowPrompt(false, "3 ^ 2: "..(3 ^ 2)) - -local options = {'Dustbowl', 'Granary', 'Gravel Pit', 'Well', '2Fort', 'Hydro'} -local result = UI.ShowSelectPrompt(options, "Choose one...") -if result then - print("Chosen: "..options[result]) - UI.ShowPrompt(false, "You chose: "..result..", or "..options[result]) -else - print("Chosen: none") - UI.ShowPrompt(false, "Oh you don't like any of them?") -end - -local res = UI.ShowPrompt(true, "I am asking you...") -UI.ShowPrompt(false, "I got: "..tostring(res)) - -local max = 1000000 -for i = 0, max do - if i % 5000 == 0 then - print("i = "..i.."/max") - end - if not UI.ShowProgress(i, max, "Pushing the Payload...") then break end -end -local max = 500000 -for i = max, 0, -1 do - if not UI.ShowProgress(i, max, "Un-copying your file...") then break end -end -local max = 1000 -for i = 0, max do - UI.ShowString("Pushing the Payload..."..i) -end - -local words = 'black,mesa' -for word in string.gmatch(words, '([^,]+)') do - UI.ShowPrompt(false, "gmatch test: "..word) -end - --- this is about it... can't even do io yet diff --git a/mount.lua b/mount.lua deleted file mode 100644 index 678ee8ba8..000000000 --- a/mount.lua +++ /dev/null @@ -1,3 +0,0 @@ -UI.ShowPrompt(false, "I am about to mount...") -local res = FS.InitImgFS("0:/finalize.romfs") -UI.ShowPrompt(false, "Did it work? \n"..tostring(res)) diff --git a/bigoptlist.lua b/test-bigoptlist.lua similarity index 97% rename from bigoptlist.lua rename to test-bigoptlist.lua index 042747dba..bba47ef1e 100644 --- a/bigoptlist.lua +++ b/test-bigoptlist.lua @@ -299,6 +299,7 @@ while true do curmaps = maps[gameresult] mapresult = UI.ShowSelectPrompt(curmaps, "Choose a map!") if mapresult then - UI.ShowPrompt(false, UI.WordWrapString("You chose the game "..games[gameresult].." and the map "..curmaps[mapresult])) + UI.ShowPrompt(UI.WordWrapString("You chose the game "..games[gameresult].." and the map "..curmaps[mapresult])) + print(games[gameresult]..' - '..curmaps[mapresult]) end end diff --git a/testos.lua b/test-os.lua similarity index 93% rename from testos.lua rename to test-os.lua index a46f63477..0efe5e4e4 100644 --- a/testos.lua +++ b/test-os.lua @@ -17,4 +17,4 @@ print("") PrintTable(os.date("*t", 1286705410)) print("") print(os.date("%c hehe", 1286705410)) -UI.ShowPrompt(false, "Press A") +UI.ShowPrompt("Press A") diff --git a/test-printenum.lua b/test-printenum.lua new file mode 100644 index 000000000..812e308f8 --- /dev/null +++ b/test-printenum.lua @@ -0,0 +1,11 @@ +function PrintTable(tbl) + for k, v in pairs(tbl) do + print(k, v) + end +end + +print('-- Enum --') +PrintTable(Enum) +print('-- Enum.UI --') +PrintTable(Enum.UI) +UI.ShowPrompt("Done") diff --git a/testenum.lua b/testenum.lua deleted file mode 100644 index 445db28be..000000000 --- a/testenum.lua +++ /dev/null @@ -1,11 +0,0 @@ -function PrintTable(tbl) - for k, v in pairs(tbl) do - print(k, v) - end -end - -print('-- Enum --') -PrintTable(Enum) -print('-- Enum.FS --') -PrintTable(Enum.FS) -UI.ShowPrompt(false, "done") diff --git a/testfgd.lua b/testfgd.lua deleted file mode 100644 index 55f1b4690..000000000 --- a/testfgd.lua +++ /dev/null @@ -1,14 +0,0 @@ -json = require('json') -fpath = "0:/test.json" - -print("Reading: "..fpath) -data = FS.FileGetData("0:/test.json", -1, 0) -print("Got type "..type(data)) -print("Got data of "..string.len(data).." bytes") -UI.ShowPrompt(false, "got data:\n"..data) - -print("Parsing...") -parsed = json.decode(data) -for k, v in pairs(parsed) do - UI.ShowPrompt(false, "Key: "..k.."\nValue: "..v) -end diff --git a/testjson.lua b/testjson.lua deleted file mode 100644 index b49baa42c..000000000 --- a/testjson.lua +++ /dev/null @@ -1,10 +0,0 @@ -local json = require('json') - -local mytable = {a=1, b='two'} -UI.ShowPrompt(false, json.encode(mytable)) - -local myjson = '{"c": 3, "d": "four"}' -UI.ShowPrompt(false, "Decoding: \n"..myjson) -for k, v in pairs(json.decode(myjson)) do - UI.ShowPrompt(false, "Key: "..k.."\nValue: "..v) -end diff --git a/testprint.lua b/testprint.lua deleted file mode 100644 index 7ea7612c2..000000000 --- a/testprint.lua +++ /dev/null @@ -1,66 +0,0 @@ -function RGB(r, g, b) - return ((r >> 3) << 11 | (g >> 2) << 5 | (b >> 3)) -end - -white = RGB(0xFF, 0xFF, 0xFF) -black = RGB(0x00, 0x00, 0x00) -grey = RGB(0x80, 0x80, 0x80) -red = RGB(0xFF, 0x00, 0x00) -superfuchsia = RGB(0xFF, 0x00, 0xEF) - -list = { - "Half-Life", - "Half-Life: Opposing Force", - "Half-Life: Blue Shift", - "Half-Life: Source", - "Half-Life 2", - "Half-Life 2: Episode One", - "Half-Life 2: Episode Two", - "Counter-Strike", - "Counter-Strike: Condition Zero", - "Counter-Strike: Condition Zero Deleted Scenes", - "Counter-Strike: Source", - "Counter-Strike: Global Offensive", - "Left 4 Dead", - "Left 4 Dead 2", - "Portal", - "Portal 2", - "Team Fortress 2", - "Dota 2", - "Half-Life: Alyx", - "Black Mesa", - "Garry's Mod", - "Portal Stories: Mel", - "The Stanley Parable" -} - -function drawshadow(which, text, x, y) - UI.DrawString(which, text, x+1, y+1, grey, black) - UI.DrawString(which, text, x, y, white, superfuchsia) -end -function drawshadow2(which, text, x, y) - UI.DrawString(which, text, x+1, y+1, grey, black) - UI.DrawString(which, text, x, y+1, grey, superfuchsia) - UI.DrawString(which, text, x+1, y, grey, superfuchsia) - UI.DrawString(which, text, x, y, white, superfuchsia) -end - -for i = 8, 12 do - UI.ClearScreenF(false, true, red) - UI.DrawString(1, "Current height: "..i, 0, 0, white, black) - for k, v in ipairs(list) do - UI.DrawString(1, k..": "..v, 0, (k) * i, white, black) - end - UI.ShowPrompt(false, "Press A") -end -for k, v in ipairs(list) do - print(k, v) - UI.ShowPrompt(false, "Press A") -end - -print('test1') -UI.ShowPrompt(false, "Press A") -print('test2') -UI.ShowPrompt(false, "Press A") -print('does it work') -UI.ShowPrompt(false, "Press A") diff --git a/testprint2.lua b/testprint2.lua deleted file mode 100644 index 9e4f54279..000000000 --- a/testprint2.lua +++ /dev/null @@ -1,2 +0,0 @@ -print({1,2,3}) -UI.ShowPrompt(false, "press A") \ No newline at end of file diff --git a/testrequire.lua b/testrequire.lua deleted file mode 100644 index 6cced193f..000000000 --- a/testrequire.lua +++ /dev/null @@ -1,3 +0,0 @@ -j = require('json') -UI.ShowPrompt(false, j.encode({a=1, b=2})) -UI.ShowPrompt(false, j.encode({"one", 2, 3.1})) From 4c4ae789ae61f9abd580a2675fb8eef0028bc7ad Mon Sep 17 00:00:00 2001 From: Ian Burgwin Date: Sat, 26 Aug 2023 01:20:32 -0700 Subject: [PATCH 041/124] dockermake --- dockermake.sh | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 dockermake.sh diff --git a/dockermake.sh b/dockermake.sh new file mode 100755 index 000000000..e1899b4c7 --- /dev/null +++ b/dockermake.sh @@ -0,0 +1,5 @@ +#!/bin/sh +IMAGE=ianburgwin/firmbuilder + +set -eux +docker run -it --rm -v $PWD:/host $IMAGE make $@ From 4c0c30ea146ff39f2fb7d156f0936e4e262c218c Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Mon, 20 Nov 2023 23:51:45 +0100 Subject: [PATCH 042/124] add DrawPNG function --- arm9/source/lua/gm9ui.c | 40 ++++++++++++++++++++++++++++++++++++++++ arm9/source/lua/gm9ui.h | 2 ++ 2 files changed, 42 insertions(+) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 0029fcfee..e4a11b261 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -43,6 +43,45 @@ static u16* GetScreenFromIndex(int index) { } } +static int UI_ShowPNG(lua_State* L) { + CheckLuaArgCount(L, 2, "ShowPNG"); + lua_Integer which_screen = luaL_checknumber(L, 1); + const char* path = lua_tostring(L, 2); + u16* screen = GetScreenFromIndex(which_screen); + u16 *bitmap = NULL; + u8* png = (u8*) malloc(SCREEN_SIZE(screen)); + u32 bitmap_width, bitmap_height; + if (png) { + u32 png_size = FileGetData(path, png, SCREEN_SIZE(screen), 0); + if (!png_size) { + free(png); + return luaL_error(L, "Could not read %s", path); + } + if (png_size && png_size < SCREEN_SIZE(screen)) { + bitmap = PNG_Decompress(png, png_size, &bitmap_width, &bitmap_height); + if (!bitmap) { + free(png); + return luaL_error(L, "Invalid PNG file"); + } + } + free(png); + if (bitmap) { + DrawBitmap( + screen, // screen + (SCREEN_WIDTH(screen)-bitmap_width)/2, // x coordinate calculated to be centered + (SCREEN_HEIGHT-bitmap_height)/2, // y coordinate calculated to be centered + bitmap_width, // width + bitmap_height, // height + bitmap // bitmap + ); + free(bitmap); + } else { + return luaL_error(L, "PNG too large for console screen"); + } + } + return 0; +} + static int UI_ShowPrompt(lua_State* L) { CheckLuaArgCount(L, 1, "ShowPrompt"); const char* text = lua_tostring(L, 1); @@ -185,6 +224,7 @@ static const luaL_Reg UIlib[] = { {"ShowSelectPrompt", UI_ShowSelectPrompt}, {"ShowProgress", UI_ShowProgress}, {"DrawString", UI_DrawString}, + {"DrawPNG", UI_ShowPNG}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9ui.h b/arm9/source/lua/gm9ui.h index f3c73adad..807f6b0bd 100644 --- a/arm9/source/lua/gm9ui.h +++ b/arm9/source/lua/gm9ui.h @@ -1,6 +1,8 @@ #pragma once #include "gm9lua.h" #include "ui.h" +#include "fs.h" +#include "png.h" #define GM9LUA_UILIBNAME "UI" From 144434e1d56e6801bd44ba4ca1e37607b164ea53 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Mon, 20 Nov 2023 23:52:52 +0100 Subject: [PATCH 043/124] whoops its ShowPNG --- arm9/source/lua/gm9ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index e4a11b261..e64d860bb 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -224,7 +224,7 @@ static const luaL_Reg UIlib[] = { {"ShowSelectPrompt", UI_ShowSelectPrompt}, {"ShowProgress", UI_ShowProgress}, {"DrawString", UI_DrawString}, - {"DrawPNG", UI_ShowPNG}, + {"ShowPNG", UI_ShowPNG}, {NULL, NULL} }; From 9e1cb51669459ecfc27e15347946282adce7a247 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Tue, 21 Nov 2023 00:30:11 +0100 Subject: [PATCH 044/124] minor fixes, add DrawPNG --- arm9/source/lua/gm9ui.c | 65 ++++++++++++++++++++++++++++++++++++----- test-pngdraw.lua | 3 ++ 2 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 test-pngdraw.lua diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index e64d860bb..6f71ffd58 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -65,19 +65,67 @@ static int UI_ShowPNG(lua_State* L) { } } free(png); - if (bitmap) { + if (!bitmap) { + return luaL_error(L, "PNG too large"); + } else if ((SCREEN_WIDTH(screen) < bitmap_width) || (SCREEN_HEIGHT < bitmap_height)) { + free(bitmap); + return luaL_error(L, "PNG too large"); + } else { DrawBitmap( - screen, // screen - (SCREEN_WIDTH(screen)-bitmap_width)/2, // x coordinate calculated to be centered - (SCREEN_HEIGHT-bitmap_height)/2, // y coordinate calculated to be centered - bitmap_width, // width - bitmap_height, // height - bitmap // bitmap + screen, // screen + -1, // x coordinate from argument + -1, // y coordinate from argument + bitmap_width, // width + bitmap_height, // height + bitmap // bitmap ); free(bitmap); + } + } + return 0; +} + +static int UI_DrawPNG(lua_State* L) { + CheckLuaArgCount(L, 4, "DrawPNG"); + lua_Integer which_screen = luaL_checknumber(L, 1); + int bitmapx = lua_tointeger(L, 2); + int bitmapy = lua_tointeger(L, 3); + const char* path = lua_tostring(L, 4); + u16* screen = GetScreenFromIndex(which_screen); + u16 *bitmap = NULL; + u8* png = (u8*) malloc(SCREEN_SIZE(screen)); + u32 bitmap_width, bitmap_height; + if (png) { + u32 png_size = FileGetData(path, png, SCREEN_SIZE(screen), 0); + if (!png_size) { + free(png); + return luaL_error(L, "Could not read %s", path); + } + if (png_size && png_size < SCREEN_SIZE(screen)) { + bitmap = PNG_Decompress(png, png_size, &bitmap_width, &bitmap_height); + if (!bitmap) { + free(png); + return luaL_error(L, "Invalid PNG file"); + } + } + free(png); + if (!bitmap) { + return luaL_error(L, "PNG too large"); + } else if ((SCREEN_WIDTH(screen) < bitmapx + bitmap_width) || (SCREEN_HEIGHT < bitmapy + bitmap_height)) { + free(bitmap); + return luaL_error(L, "PNG too large"); } else { - return luaL_error(L, "PNG too large for console screen"); + DrawBitmap( + screen, // screen + bitmapx, // x coordinate from argument + bitmapy, // y coordinate from argument + bitmap_width, // width + bitmap_height, // height + bitmap // bitmap + ); + free(bitmap); } + } return 0; } @@ -225,6 +273,7 @@ static const luaL_Reg UIlib[] = { {"ShowProgress", UI_ShowProgress}, {"DrawString", UI_DrawString}, {"ShowPNG", UI_ShowPNG}, + {"DrawPNG", UI_DrawPNG}, {NULL, NULL} }; diff --git a/test-pngdraw.lua b/test-pngdraw.lua new file mode 100644 index 000000000..8330d6c5a --- /dev/null +++ b/test-pngdraw.lua @@ -0,0 +1,3 @@ +UI.ShowPNG(1, "0:/art.png") +UI.DrawPNG(1, 2, 2, "0:/art.png") +UI.ShowPrompt("Press A") \ No newline at end of file From 1692a8ad24ad6b57e2ad6c3eff3669ad738f1556 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sat, 16 Mar 2024 12:17:32 +0100 Subject: [PATCH 045/124] fix AskPrompt, add all showprompts mentioned in #1 --- arm9/source/lua/gm9ui.c | 45 +++++++++++++++++++++++++++++++++++++++-- arm9/source/lua/gm9ui.h | 1 + test-showprompts.lua | 4 ++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 test-showprompts.lua diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 6f71ffd58..113f86992 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -138,9 +138,47 @@ static int UI_ShowPrompt(lua_State* L) { return 0; } -static int UI_AskPrompt(lua_State* L) { - CheckLuaArgCount(L, 2, "AskPrompt"); +static int UI_ShowHexPrompt(lua_State* L) { + CheckLuaArgCount(L, 3, "ShowHexPrompt"); + const char* text = lua_tostring(L, 3); + u64 start_val = lua_tonumber(L, 1); + u32 n_digits = lua_tonumber(L, 2); + + u64 ret = ShowHexPrompt(start_val, n_digits, "%s", text); + lua_pushnumber(L, ret); + return 1; +} + +static int UI_ShowNumberPrompt(lua_State* L) { + CheckLuaArgCount(L, 2, "ShowNumberPrompt"); const char* text = lua_tostring(L, 2); + u64 start_val = lua_tonumber(L, 1); + + u64 ret = ShowNumberPrompt(start_val, "%s", text); + lua_pushnumber(L, ret); + return 1; + +} + +static int UI_ShowKeyboardOrPrompt(lua_State* L) { + CheckLuaArgCount(L, 3, "ShowKeyboardOrPrompt"); + const char* text = lua_tostring(L, 3); + const char* _start_val = lua_tostring(L, 1); + u32 start_val_size = strlen(_start_val)+1; + char start_val[start_val_size]; + snprintf(start_val, start_val_size, "%s", _start_val); + u32 max_size = lua_tonumber(L, 2); + bool result = ShowKeyboardOrPrompt(start_val, max_size, "%s", text); + if (result) + lua_pushstring(L, start_val); + else + lua_pushnil(L); + return 1; +} + +static int UI_AskPrompt(lua_State* L) { + CheckLuaArgCount(L, 1, "AskPrompt"); + const char* text = lua_tostring(L, 1); bool ret = ShowPrompt(true, "%s", text); lua_pushboolean(L, ret); @@ -270,6 +308,9 @@ static const luaL_Reg UIlib[] = { {"WordWrapString", UI_WordWrapString}, {"ClearScreen", UI_ClearScreen}, {"ShowSelectPrompt", UI_ShowSelectPrompt}, + {"ShowKeyboardOrPrompt", UI_ShowKeyboardOrPrompt}, + {"ShowNumberPrompt", UI_ShowNumberPrompt}, + {"ShowHexPrompt", UI_ShowHexPrompt}, {"ShowProgress", UI_ShowProgress}, {"DrawString", UI_DrawString}, {"ShowPNG", UI_ShowPNG}, diff --git a/arm9/source/lua/gm9ui.h b/arm9/source/lua/gm9ui.h index 807f6b0bd..d1876ab26 100644 --- a/arm9/source/lua/gm9ui.h +++ b/arm9/source/lua/gm9ui.h @@ -3,6 +3,7 @@ #include "ui.h" #include "fs.h" #include "png.h" +#include "swkbd.h" #define GM9LUA_UILIBNAME "UI" diff --git a/test-showprompts.lua b/test-showprompts.lua new file mode 100644 index 000000000..e24401ef5 --- /dev/null +++ b/test-showprompts.lua @@ -0,0 +1,4 @@ +UI.AskPrompt(UI.ShowKeyboardOrPrompt("Hi", 16, "Enter text:")) +UI.AskPrompt(UI.ShowHexPrompt(0xF, 16, "Enter hex:")) +UI.AskPrompt(UI.ShowNumberPrompt(53, "Enter number:")) +UI.ShowPrompt("Press A") From c6426ce9d6ab3c17f167da8e02d165d994803ff1 Mon Sep 17 00:00:00 2001 From: Gruetzig Date: Sat, 16 Mar 2024 12:23:10 +0100 Subject: [PATCH 046/124] add newly added functions to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 44dce4ebe..3abb9a471 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ The API here is not at all stable. But there are currently two libraries to play * void UI.ShowPrompt(string text) * bool UI.AskPrompt(string text) +* bool UI.ShowKeyboardOrPrompt(string start\_val, number max\_size, string text) +* number UI.ShowNumberPrompt(number start\_val, string text) +* number UI.ShowHexPrompt(number start\_val, number max_size, string text) * void UI.ShowString(string text) * string UI.WordWrapString(string text[, int llen]) * llen == -1 means alt screen From cecb117405cb3d939f676a1180e484baf36a4210 Mon Sep 17 00:00:00 2001 From: Wolfvak Date: Sun, 22 Sep 2024 23:29:13 -0300 Subject: [PATCH 047/124] try to keep separate code and data --- .gitignore | 1 + Makefile | 14 +++++++++----- Makefile.build | 3 ++- Makefile.common | 1 + arm9/Makefile | 6 ++++++ arm9/link.ld | 16 +++++++++------- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index ba42f7591..17d0e33e8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.obj *.elf *.map +*.dis # Precompiled Headers *.gch diff --git a/Makefile b/Makefile index 6917ad204..c75b0cfea 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ export CFLAGS := -DDBUILTS="\"$(DBUILTS)\"" -DDBUILTL="\"$(DBUILTL)\"" -DVERSIO -fomit-frame-pointer -ffast-math -std=gnu11 -MMD -MP \ -Wno-unused-function -Wno-format-truncation -Wno-format-nonliteral $(INCLUDE) -ffunction-sections -fdata-sections export LDFLAGS := -Tlink.ld -nostartfiles -Wl,--gc-sections,-z,max-page-size=4096 -ELF := arm9/arm9.elf arm11/arm11.elf +ELF := arm9/arm9_code.elf arm9/arm9_data.elf arm11/arm11.elf .PHONY: all firm $(VRAM_TAR) elf release clean all: firm @@ -87,9 +87,13 @@ $(VRAM_TAR): $(SPLASH) $(OVERRIDE_FONT) $(VRAM_DATA) $(VRAM_SCRIPTS) %.elf: .FORCE @echo "Building $@" - @$(MAKE) --no-print-directory -C $(@D) + @$(MAKE) --no-print-directory -C $(@D) $(@F) -arm9/arm9.elf: $(VRAM_TAR) +# Indicate a few explicit dependencies: +# The ARM9 data section depends on the VRAM drive +arm9/arm9_data.elf: $(VRAM_TAR) +# And the code section depends on the data section being built already +arm9/arm9_code.elf: arm9/arm9_data.elf firm: $(ELF) @mkdir -p $(call dirname,"$(FIRM)") $(call dirname,"$(FIRMD)") @@ -97,9 +101,9 @@ firm: $(ELF) @echo "[VERSION] $(VERSION)" @echo "[BUILD] $(DBUILTL)" @echo "[FIRM] $(FIRM)" - @$(PY3) -m firmtool build $(FIRM) $(FTFLAGS) -g -D $(ELF) -C NDMA XDMA + @$(PY3) -m firmtool build $(FIRM) $(FTFLAGS) -g -D $(ELF) -C NDMA NDMA XDMA @echo "[FIRM] $(FIRMD)" - @$(PY3) -m firmtool build $(FIRMD) $(FTDFLAGS) -g -D $(ELF) -C NDMA XDMA + @$(PY3) -m firmtool build $(FIRMD) $(FTDFLAGS) -g -D $(ELF) -C NDMA NDMA XDMA vram0: $(VRAM_TAR) .FORCE # legacy target name diff --git a/Makefile.build b/Makefile.build index a8f80e625..67f53e185 100755 --- a/Makefile.build +++ b/Makefile.build @@ -13,11 +13,12 @@ all: $(TARGET).elf .PHONY: clean clean: - @rm -rf $(BUILD) $(TARGET).elf $(TARGET).map + @rm -rf $(BUILD) $(TARGET).elf $(TARGET).dis $(TARGET).map $(TARGET).elf: $(OBJECTS) $(OBJECTS_COMMON) @mkdir -p "$(@D)" @$(CC) $(LDFLAGS) $^ -o $@ $(LIBS) + @$(OBJDUMP) -S $@ > $@.dis $(BUILD)/%.cmn.o: $(COMMON_DIR)/%.c @mkdir -p "$(@D)" diff --git a/Makefile.common b/Makefile.common index 4ad6341da..71e40f9b2 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1,3 +1,4 @@ +export OBJDUMP := arm-none-eabi-objdump dirname = $(shell dirname $(1)) diff --git a/arm9/Makefile b/arm9/Makefile index 4b177ce78..ee137826f 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -16,3 +16,9 @@ LIBS += -lm include ../Makefile.common include ../Makefile.build + +arm9_data.elf: arm9.elf + arm-none-eabi-objcopy -O elf32-littlearm -j .rodata* -j .data* -j .bss* $< $@ + +arm9_code.elf: arm9.elf + arm-none-eabi-objcopy -O elf32-littlearm -j .text* -j .vectors* $< $@ diff --git a/arm9/link.ld b/arm9/link.ld index d677a5228..0e5cf11fa 100644 --- a/arm9/link.ld +++ b/arm9/link.ld @@ -5,7 +5,9 @@ ENTRY(_start) MEMORY { VECTORS (RX) : ORIGIN = 0x08000000, LENGTH = 64 - AHBWRAM (RWX) : ORIGIN = 0x08000040, LENGTH = 744K - 64 + CODEMEM (RX) : ORIGIN = 0x08000040, LENGTH = 512K - 64 + BOOTMIR (R) : ORIGIN = 0x08080000, LENGTH = 128K /* BootROM mirrors, don't touch! */ + DATAMEM (RW) : ORIGIN = 0x080A0000, LENGTH = 384K } SECTIONS @@ -16,7 +18,7 @@ SECTIONS KEEP(*(.vectors)); . = ALIGN(4); __vectors_len = ABSOLUTE(.) - __vectors_vma; - } >VECTORS AT>AHBWRAM + } >VECTORS AT>CODEMEM .text : ALIGN(4) { __text_s = ABSOLUTE(.); @@ -24,7 +26,7 @@ SECTIONS *(.text*); . = ALIGN(4); __text_e = ABSOLUTE(.); - } >AHBWRAM + } >CODEMEM .rodata : ALIGN(4) { *(.rodata*); @@ -33,19 +35,19 @@ SECTIONS *(.ARM.exidx* .gnu.linkonce.armexidx.*) __exidx_end = .; . = ALIGN(4); - } >AHBWRAM + } >DATAMEM .data : ALIGN(4) { *(.data*); . = ALIGN(4); - } >AHBWRAM + } >DATAMEM - .bss : ALIGN(4) { + .bss (NOLOAD) : ALIGN(4) { __bss_start = .; *(.bss*); . = ALIGN(4); __bss_end = .; - } >AHBWRAM + } >DATAMEM __end__ = ABSOLUTE(.); } From dab5c9a3a1a03349f4c0df9ab8fc51338b375347 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 30 Sep 2024 17:43:23 -0500 Subject: [PATCH 048/124] update lua to 5.4.7 --- arm9/source/lua/lapi.c | 4 +- arm9/source/lua/lauxlib.c | 28 +++-- arm9/source/lua/lcode.c | 35 +++--- arm9/source/lua/lcode.h | 3 - arm9/source/lua/ldebug.c | 218 ++++++++++++++++++++++--------------- arm9/source/lua/ldebug.h | 1 + arm9/source/lua/ldo.c | 10 +- arm9/source/lua/ldo.h | 1 - arm9/source/lua/lgc.c | 20 ++-- arm9/source/lua/liolib.c | 27 +++-- arm9/source/lua/llimits.h | 2 +- arm9/source/lua/lmathlib.c | 31 ++++-- arm9/source/lua/loadlib.c | 9 -- arm9/source/lua/lobject.c | 2 +- arm9/source/lua/lobject.h | 18 ++- arm9/source/lua/lopcodes.h | 8 +- arm9/source/lua/loslib.c | 2 + arm9/source/lua/lparser.c | 12 +- arm9/source/lua/lstate.c | 6 +- arm9/source/lua/lstate.h | 3 +- arm9/source/lua/lstring.c | 13 ++- arm9/source/lua/ltable.c | 39 +++++-- arm9/source/lua/ltable.h | 2 - arm9/source/lua/ltm.h | 5 +- arm9/source/lua/lua.h | 8 +- arm9/source/lua/lua.hpp | 9 -- arm9/source/lua/luaconf.h | 9 ++ arm9/source/lua/lundump.c | 4 +- arm9/source/lua/lundump.h | 3 +- arm9/source/lua/lvm.c | 78 +++++++------ 30 files changed, 350 insertions(+), 260 deletions(-) delete mode 100644 arm9/source/lua/lua.hpp diff --git a/arm9/source/lua/lapi.c b/arm9/source/lua/lapi.c index 34e64af14..332e97d16 100644 --- a/arm9/source/lua/lapi.c +++ b/arm9/source/lua/lapi.c @@ -417,9 +417,9 @@ LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { o = index2value(L, idx); /* previous call may reallocate the stack */ } if (len != NULL) - *len = vslen(o); + *len = tsslen(tsvalue(o)); lua_unlock(L); - return svalue(o); + return getstr(tsvalue(o)); } diff --git a/arm9/source/lua/lauxlib.c b/arm9/source/lua/lauxlib.c index 4ca6c6548..923105ed3 100644 --- a/arm9/source/lua/lauxlib.c +++ b/arm9/source/lua/lauxlib.c @@ -80,6 +80,7 @@ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { int top = lua_gettop(L); lua_getinfo(L, "f", ar); /* push function */ lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); + luaL_checkstack(L, 6, "not enough stack"); /* slots for 'findfield' */ if (findfield(L, top + 1, 2)) { const char *name = lua_tostring(L, -1); if (strncmp(name, LUA_GNAME ".", 3) == 0) { /* name start with '_G.'? */ @@ -249,11 +250,13 @@ LUALIB_API int luaL_fileresult (lua_State *L, int stat, const char *fname) { return 1; } else { + const char *msg; luaL_pushfail(L); + msg = (en != 0) ? strerror(en) : "(no extra info)"; if (fname) - lua_pushfstring(L, "%s: %s", fname, strerror(en)); + lua_pushfstring(L, "%s: %s", fname, msg); else - lua_pushstring(L, strerror(en)); + lua_pushstring(L, msg); lua_pushinteger(L, en); return 3; } @@ -732,9 +735,12 @@ static const char *getF (lua_State *L, void *ud, size_t *size) { static int errfile (lua_State *L, const char *what, int fnameindex) { - const char *serr = strerror(errno); + int err = errno; const char *filename = lua_tostring(L, fnameindex) + 1; - lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); + if (err != 0) + lua_pushfstring(L, "cannot %s %s: %s", what, filename, strerror(err)); + else + lua_pushfstring(L, "cannot %s %s", what, filename); lua_remove(L, fnameindex); return LUA_ERRFILE; } @@ -787,6 +793,7 @@ LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, } else { lua_pushfstring(L, "@%s", filename); + errno = 0; lf.f = fopen(filename, "r"); if (lf.f == NULL) return errfile(L, "open", fnameindex); } @@ -796,6 +803,7 @@ LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, if (c == LUA_SIGNATURE[0]) { /* binary file? */ lf.n = 0; /* remove possible newline */ if (filename) { /* "real" file? */ + errno = 0; lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ if (lf.f == NULL) return errfile(L, "reopen", fnameindex); skipcomment(lf.f, &c); /* re-read initial portion */ @@ -803,6 +811,7 @@ LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename, } if (c != EOF) lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */ + errno = 0; status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode); readstatus = ferror(lf.f); if (filename) fclose(lf.f); /* close file (even in case of errors) */ @@ -933,7 +942,7 @@ LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ - if (l->func == NULL) /* place holder? */ + if (l->func == NULL) /* placeholder? */ lua_pushboolean(L, 0); else { int i; @@ -1025,9 +1034,14 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { } +/* +** Standard panic funcion just prints an error message. The test +** with 'lua_type' avoids possible memory errors in 'lua_tostring'. +*/ static int panic (lua_State *L) { - const char *msg = lua_tostring(L, -1); - if (msg == NULL) msg = "error object is not a string"; + const char *msg = (lua_type(L, -1) == LUA_TSTRING) + ? lua_tostring(L, -1) + : "error object is not a string"; lua_writestringerror("PANIC: unprotected error in call to Lua API (%s)\n", msg); return 0; /* return to Lua to abort */ diff --git a/arm9/source/lua/lcode.c b/arm9/source/lua/lcode.c index 1a371ca94..87616140e 100644 --- a/arm9/source/lua/lcode.c +++ b/arm9/source/lua/lcode.c @@ -415,7 +415,7 @@ int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { /* ** Format and emit an 'iAsBx' instruction. */ -int luaK_codeAsBx (FuncState *fs, OpCode o, int a, int bc) { +static int codeAsBx (FuncState *fs, OpCode o, int a, int bc) { unsigned int b = bc + OFFSET_sBx; lua_assert(getOpMode(o) == iAsBx); lua_assert(a <= MAXARG_A && b <= MAXARG_Bx); @@ -671,7 +671,7 @@ static int fitsBx (lua_Integer i) { void luaK_int (FuncState *fs, int reg, lua_Integer i) { if (fitsBx(i)) - luaK_codeAsBx(fs, OP_LOADI, reg, cast_int(i)); + codeAsBx(fs, OP_LOADI, reg, cast_int(i)); else luaK_codek(fs, reg, luaK_intK(fs, i)); } @@ -680,7 +680,7 @@ void luaK_int (FuncState *fs, int reg, lua_Integer i) { static void luaK_float (FuncState *fs, int reg, lua_Number f) { lua_Integer fi; if (luaV_flttointeger(f, &fi, F2Ieq) && fitsBx(fi)) - luaK_codeAsBx(fs, OP_LOADF, reg, cast_int(fi)); + codeAsBx(fs, OP_LOADF, reg, cast_int(fi)); else luaK_codek(fs, reg, luaK_numberK(fs, f)); } @@ -776,7 +776,8 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { break; } case VLOCAL: { /* already in a register */ - e->u.info = e->u.var.ridx; + int temp = e->u.var.ridx; + e->u.info = temp; /* (can't do a direct assignment; values overlap) */ e->k = VNONRELOC; /* becomes a non-relocatable value */ break; } @@ -1025,7 +1026,7 @@ static int luaK_exp2K (FuncState *fs, expdesc *e) { ** in the range of R/K indices). ** Returns 1 iff expression is K. */ -int luaK_exp2RK (FuncState *fs, expdesc *e) { +static int exp2RK (FuncState *fs, expdesc *e) { if (luaK_exp2K(fs, e)) return 1; else { /* not a constant in the right range: put it in a register */ @@ -1037,7 +1038,7 @@ int luaK_exp2RK (FuncState *fs, expdesc *e) { static void codeABRK (FuncState *fs, OpCode o, int a, int b, expdesc *ec) { - int k = luaK_exp2RK(fs, ec); + int k = exp2RK(fs, ec); luaK_codeABCk(fs, o, a, b, ec->u.info, k); } @@ -1215,7 +1216,7 @@ static void codenot (FuncState *fs, expdesc *e) { /* -** Check whether expression 'e' is a small literal string +** Check whether expression 'e' is a short literal string */ static int isKstr (FuncState *fs, expdesc *e) { return (e->k == VK && !hasjumps(e) && e->u.info <= MAXARG_B && @@ -1225,7 +1226,7 @@ static int isKstr (FuncState *fs, expdesc *e) { /* ** Check whether expression 'e' is a literal integer. */ -int luaK_isKint (expdesc *e) { +static int isKint (expdesc *e) { return (e->k == VKINT && !hasjumps(e)); } @@ -1235,7 +1236,7 @@ int luaK_isKint (expdesc *e) { ** proper range to fit in register C */ static int isCint (expdesc *e) { - return luaK_isKint(e) && (l_castS2U(e->u.ival) <= l_castS2U(MAXARG_C)); + return isKint(e) && (l_castS2U(e->u.ival) <= l_castS2U(MAXARG_C)); } @@ -1244,7 +1245,7 @@ static int isCint (expdesc *e) { ** proper range to fit in register sC */ static int isSCint (expdesc *e) { - return luaK_isKint(e) && fitsC(e->u.ival); + return isKint(e) && fitsC(e->u.ival); } @@ -1283,15 +1284,17 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */ luaK_exp2anyreg(fs, t); /* put it in a register */ if (t->k == VUPVAL) { - t->u.ind.t = t->u.info; /* upvalue index */ - t->u.ind.idx = k->u.info; /* literal string */ + int temp = t->u.info; /* upvalue index */ + lua_assert(isKstr(fs, k)); + t->u.ind.t = temp; /* (can't do a direct assignment; values overlap) */ + t->u.ind.idx = k->u.info; /* literal short string */ t->k = VINDEXUP; } else { /* register index of the table */ t->u.ind.t = (t->k == VLOCAL) ? t->u.var.ridx: t->u.info; if (isKstr(fs, k)) { - t->u.ind.idx = k->u.info; /* literal string */ + t->u.ind.idx = k->u.info; /* literal short string */ t->k = VINDEXSTR; } else if (isCint(k)) { @@ -1459,7 +1462,7 @@ static void codebinK (FuncState *fs, BinOpr opr, */ static int finishbinexpneg (FuncState *fs, expdesc *e1, expdesc *e2, OpCode op, int line, TMS event) { - if (!luaK_isKint(e2)) + if (!isKint(e2)) return 0; /* not an integer constant */ else { lua_Integer i2 = e2->u.ival; @@ -1592,7 +1595,7 @@ static void codeeq (FuncState *fs, BinOpr opr, expdesc *e1, expdesc *e2) { op = OP_EQI; r2 = im; /* immediate operand */ } - else if (luaK_exp2RK(fs, e2)) { /* 2nd expression is constant? */ + else if (exp2RK(fs, e2)) { /* 2nd expression is constant? */ op = OP_EQK; r2 = e2->u.info; /* constant index */ } @@ -1658,7 +1661,7 @@ void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { } case OPR_EQ: case OPR_NE: { if (!tonumeral(v, NULL)) - luaK_exp2RK(fs, v); + exp2RK(fs, v); /* else keep numeral, which may be an immediate operand */ break; } diff --git a/arm9/source/lua/lcode.h b/arm9/source/lua/lcode.h index 326582445..0b971fc43 100644 --- a/arm9/source/lua/lcode.h +++ b/arm9/source/lua/lcode.h @@ -61,10 +61,8 @@ typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; LUAI_FUNC int luaK_code (FuncState *fs, Instruction i); LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); -LUAI_FUNC int luaK_codeAsBx (FuncState *fs, OpCode o, int A, int Bx); LUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A, int B, int C, int k); -LUAI_FUNC int luaK_isKint (expdesc *e); LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v); LUAI_FUNC void luaK_fixline (FuncState *fs, int line); LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); @@ -76,7 +74,6 @@ LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); -LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); diff --git a/arm9/source/lua/ldebug.c b/arm9/source/lua/ldebug.c index 28b1caabf..591b3528a 100644 --- a/arm9/source/lua/ldebug.c +++ b/arm9/source/lua/ldebug.c @@ -31,7 +31,7 @@ -#define noLuaClosure(f) ((f) == NULL || (f)->c.tt == LUA_VCCL) +#define LuaClosure(f) ((f) != NULL && (f)->c.tt == LUA_VLCL) static const char *funcnamefromcall (lua_State *L, CallInfo *ci, @@ -254,7 +254,7 @@ LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { static void funcinfo (lua_Debug *ar, Closure *cl) { - if (noLuaClosure(cl)) { + if (!LuaClosure(cl)) { ar->source = "=[C]"; ar->srclen = LL("=[C]"); ar->linedefined = -1; @@ -288,29 +288,31 @@ static int nextline (const Proto *p, int currentline, int pc) { static void collectvalidlines (lua_State *L, Closure *f) { - if (noLuaClosure(f)) { + if (!LuaClosure(f)) { setnilvalue(s2v(L->top.p)); api_incr_top(L); } else { - int i; - TValue v; const Proto *p = f->l.p; int currentline = p->linedefined; Table *t = luaH_new(L); /* new table to store active lines */ sethvalue2s(L, L->top.p, t); /* push it on stack */ api_incr_top(L); - setbtvalue(&v); /* boolean 'true' to be the value of all indices */ - if (!p->is_vararg) /* regular function? */ - i = 0; /* consider all instructions */ - else { /* vararg function */ - lua_assert(GET_OPCODE(p->code[0]) == OP_VARARGPREP); - currentline = nextline(p, currentline, 0); - i = 1; /* skip first instruction (OP_VARARGPREP) */ - } - for (; i < p->sizelineinfo; i++) { /* for each instruction */ - currentline = nextline(p, currentline, i); /* get its line */ - luaH_setint(L, t, currentline, &v); /* table[line] = true */ + if (p->lineinfo != NULL) { /* proto with debug information? */ + int i; + TValue v; + setbtvalue(&v); /* boolean 'true' to be the value of all indices */ + if (!p->is_vararg) /* regular function? */ + i = 0; /* consider all instructions */ + else { /* vararg function */ + lua_assert(GET_OPCODE(p->code[0]) == OP_VARARGPREP); + currentline = nextline(p, currentline, 0); + i = 1; /* skip first instruction (OP_VARARGPREP) */ + } + for (; i < p->sizelineinfo; i++) { /* for each instruction */ + currentline = nextline(p, currentline, i); /* get its line */ + luaH_setint(L, t, currentline, &v); /* table[line] = true */ + } } } } @@ -339,7 +341,7 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, } case 'u': { ar->nups = (f == NULL) ? 0 : f->c.nupvalues; - if (noLuaClosure(f)) { + if (!LuaClosure(f)) { ar->isvararg = 1; ar->nparams = 0; } @@ -417,40 +419,6 @@ LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { ** ======================================================= */ -static const char *getobjname (const Proto *p, int lastpc, int reg, - const char **name); - - -/* -** Find a "name" for the constant 'c'. -*/ -static void kname (const Proto *p, int c, const char **name) { - TValue *kvalue = &p->k[c]; - *name = (ttisstring(kvalue)) ? svalue(kvalue) : "?"; -} - - -/* -** Find a "name" for the register 'c'. -*/ -static void rname (const Proto *p, int pc, int c, const char **name) { - const char *what = getobjname(p, pc, c, name); /* search for 'c' */ - if (!(what && *what == 'c')) /* did not find a constant name? */ - *name = "?"; -} - - -/* -** Find a "name" for a 'C' value in an RK instruction. -*/ -static void rkname (const Proto *p, int pc, Instruction i, const char **name) { - int c = GETARG_C(i); /* key index */ - if (GETARG_k(i)) /* is 'c' a constant? */ - kname(p, c, name); - else /* 'c' is a register */ - rname(p, pc, c, name); -} - static int filterpc (int pc, int jmptarget) { if (pc < jmptarget) /* is code conditional (inside a jump)? */ @@ -509,28 +477,29 @@ static int findsetreg (const Proto *p, int lastpc, int reg) { /* -** Check whether table being indexed by instruction 'i' is the -** environment '_ENV' +** Find a "name" for the constant 'c'. */ -static const char *gxf (const Proto *p, int pc, Instruction i, int isup) { - int t = GETARG_B(i); /* table index */ - const char *name; /* name of indexed variable */ - if (isup) /* is an upvalue? */ - name = upvalname(p, t); - else - getobjname(p, pc, t, &name); - return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field"; +static const char *kname (const Proto *p, int index, const char **name) { + TValue *kvalue = &p->k[index]; + if (ttisstring(kvalue)) { + *name = getstr(tsvalue(kvalue)); + return "constant"; + } + else { + *name = "?"; + return NULL; + } } -static const char *getobjname (const Proto *p, int lastpc, int reg, - const char **name) { - int pc; - *name = luaF_getlocalname(p, reg + 1, lastpc); +static const char *basicgetobjname (const Proto *p, int *ppc, int reg, + const char **name) { + int pc = *ppc; + *name = luaF_getlocalname(p, reg + 1, pc); if (*name) /* is a local? */ return "local"; /* else try symbolic execution */ - pc = findsetreg(p, lastpc, reg); + *ppc = pc = findsetreg(p, pc, reg); if (pc != -1) { /* could find instruction? */ Instruction i = p->code[pc]; OpCode op = GET_OPCODE(i); @@ -538,18 +507,80 @@ static const char *getobjname (const Proto *p, int lastpc, int reg, case OP_MOVE: { int b = GETARG_B(i); /* move from 'b' to 'a' */ if (b < GETARG_A(i)) - return getobjname(p, pc, b, name); /* get name for 'b' */ + return basicgetobjname(p, ppc, b, name); /* get name for 'b' */ break; } + case OP_GETUPVAL: { + *name = upvalname(p, GETARG_B(i)); + return "upvalue"; + } + case OP_LOADK: return kname(p, GETARG_Bx(i), name); + case OP_LOADKX: return kname(p, GETARG_Ax(p->code[pc + 1]), name); + default: break; + } + } + return NULL; /* could not find reasonable name */ +} + + +/* +** Find a "name" for the register 'c'. +*/ +static void rname (const Proto *p, int pc, int c, const char **name) { + const char *what = basicgetobjname(p, &pc, c, name); /* search for 'c' */ + if (!(what && *what == 'c')) /* did not find a constant name? */ + *name = "?"; +} + + +/* +** Find a "name" for a 'C' value in an RK instruction. +*/ +static void rkname (const Proto *p, int pc, Instruction i, const char **name) { + int c = GETARG_C(i); /* key index */ + if (GETARG_k(i)) /* is 'c' a constant? */ + kname(p, c, name); + else /* 'c' is a register */ + rname(p, pc, c, name); +} + + +/* +** Check whether table being indexed by instruction 'i' is the +** environment '_ENV' +*/ +static const char *isEnv (const Proto *p, int pc, Instruction i, int isup) { + int t = GETARG_B(i); /* table index */ + const char *name; /* name of indexed variable */ + if (isup) /* is 't' an upvalue? */ + name = upvalname(p, t); + else /* 't' is a register */ + basicgetobjname(p, &pc, t, &name); + return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field"; +} + + +/* +** Extend 'basicgetobjname' to handle table accesses +*/ +static const char *getobjname (const Proto *p, int lastpc, int reg, + const char **name) { + const char *kind = basicgetobjname(p, &lastpc, reg, name); + if (kind != NULL) + return kind; + else if (lastpc != -1) { /* could find instruction? */ + Instruction i = p->code[lastpc]; + OpCode op = GET_OPCODE(i); + switch (op) { case OP_GETTABUP: { int k = GETARG_C(i); /* key index */ kname(p, k, name); - return gxf(p, pc, i, 1); + return isEnv(p, lastpc, i, 1); } case OP_GETTABLE: { int k = GETARG_C(i); /* key index */ - rname(p, pc, k, name); - return gxf(p, pc, i, 0); + rname(p, lastpc, k, name); + return isEnv(p, lastpc, i, 0); } case OP_GETI: { *name = "integer index"; @@ -558,24 +589,10 @@ static const char *getobjname (const Proto *p, int lastpc, int reg, case OP_GETFIELD: { int k = GETARG_C(i); /* key index */ kname(p, k, name); - return gxf(p, pc, i, 0); - } - case OP_GETUPVAL: { - *name = upvalname(p, GETARG_B(i)); - return "upvalue"; - } - case OP_LOADK: - case OP_LOADKX: { - int b = (op == OP_LOADK) ? GETARG_Bx(i) - : GETARG_Ax(p->code[pc + 1]); - if (ttisstring(&p->k[b])) { - *name = svalue(&p->k[b]); - return "constant"; - } - break; + return isEnv(p, lastpc, i, 0); } case OP_SELF: { - rkname(p, pc, i, name); + rkname(p, lastpc, i, name); return "method"; } default: break; /* go through to return NULL */ @@ -627,7 +644,7 @@ static const char *funcnamefromcode (lua_State *L, const Proto *p, default: return NULL; /* cannot find a reasonable name */ } - *name = getstr(G(L)->tmname[tm]) + 2; + *name = getshrstr(G(L)->tmname[tm]) + 2; return "metamethod"; } @@ -865,6 +882,28 @@ static int changedline (const Proto *p, int oldpc, int newpc) { } +/* +** Traces Lua calls. If code is running the first instruction of a function, +** and function is not vararg, and it is not coming from an yield, +** calls 'luaD_hookcall'. (Vararg functions will call 'luaD_hookcall' +** after adjusting its variable arguments; otherwise, they could call +** a line/count hook before the call hook. Functions coming from +** an yield already called 'luaD_hookcall' before yielding.) +*/ +int luaG_tracecall (lua_State *L) { + CallInfo *ci = L->ci; + Proto *p = ci_func(ci)->p; + ci->u.l.trap = 1; /* ensure hooks will be checked */ + if (ci->u.l.savedpc == p->code) { /* first instruction (not resuming)? */ + if (p->is_vararg) + return 0; /* hooks will start at VARARGPREP instruction */ + else if (!(ci->callstatus & CIST_HOOKYIELD)) /* not yieded? */ + luaD_hookcall(L, ci); /* check 'call' hook */ + } + return 1; /* keep 'trap' on */ +} + + /* ** Traces the execution of a Lua function. Called before the execution ** of each opcode, when debug is on. 'L->oldpc' stores the last @@ -888,12 +927,12 @@ int luaG_traceexec (lua_State *L, const Instruction *pc) { } pc++; /* reference is always next instruction */ ci->u.l.savedpc = pc; /* save 'pc' */ - counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT)); + counthook = (mask & LUA_MASKCOUNT) && (--L->hookcount == 0); if (counthook) resethookcount(L); /* reset count */ else if (!(mask & LUA_MASKLINE)) return 1; /* no line hook and count != 0; nothing to be done now */ - if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ + if (ci->callstatus & CIST_HOOKYIELD) { /* hook yielded last time? */ ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ return 1; /* do not call hook again (VM yielded, so it did not move) */ } @@ -915,7 +954,6 @@ int luaG_traceexec (lua_State *L, const Instruction *pc) { if (L->status == LUA_YIELD) { /* did hook yield? */ if (counthook) L->hookcount = 1; /* undo decrement to zero */ - ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ luaD_throw(L, LUA_YIELD); } diff --git a/arm9/source/lua/ldebug.h b/arm9/source/lua/ldebug.h index 2c3074c61..2bfce3cb5 100644 --- a/arm9/source/lua/ldebug.h +++ b/arm9/source/lua/ldebug.h @@ -58,6 +58,7 @@ LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, int line); LUAI_FUNC l_noret luaG_errormsg (lua_State *L); LUAI_FUNC int luaG_traceexec (lua_State *L, const Instruction *pc); +LUAI_FUNC int luaG_tracecall (lua_State *L); #endif diff --git a/arm9/source/lua/ldo.c b/arm9/source/lua/ldo.c index 2a0017ca6..ea0529507 100644 --- a/arm9/source/lua/ldo.c +++ b/arm9/source/lua/ldo.c @@ -409,7 +409,7 @@ static void rethook (lua_State *L, CallInfo *ci, int nres) { ** stack, below original 'func', so that 'luaD_precall' can call it. Raise ** an error if there is no '__call' metafield. */ -StkId luaD_tryfuncTM (lua_State *L, StkId func) { +static StkId tryfuncTM (lua_State *L, StkId func) { const TValue *tm; StkId p; checkstackGCp(L, 1, func); /* space for metamethod */ @@ -568,7 +568,7 @@ int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, return -1; } default: { /* not a function */ - func = luaD_tryfuncTM(L, func); /* try to get '__call' metamethod */ + func = tryfuncTM(L, func); /* try to get '__call' metamethod */ /* return luaD_pretailcall(L, ci, func, narg1 + 1, delta); */ narg1++; goto retry; /* try again */ @@ -609,7 +609,7 @@ CallInfo *luaD_precall (lua_State *L, StkId func, int nresults) { return ci; } default: { /* not a function */ - func = luaD_tryfuncTM(L, func); /* try to get '__call' metamethod */ + func = tryfuncTM(L, func); /* try to get '__call' metamethod */ /* return luaD_precall(L, func, nresults); */ goto retry; /* try again with metamethod */ } @@ -792,6 +792,10 @@ static void resume (lua_State *L, void *ud) { lua_assert(L->status == LUA_YIELD); L->status = LUA_OK; /* mark that it is running (again) */ if (isLua(ci)) { /* yielded inside a hook? */ + /* undo increment made by 'luaG_traceexec': instruction was not + executed yet */ + lua_assert(ci->callstatus & CIST_HOOKYIELD); + ci->u.l.savedpc--; L->top.p = firstArg; /* discard arguments */ luaV_execute(L, ci); /* just continue running Lua code */ } diff --git a/arm9/source/lua/ldo.h b/arm9/source/lua/ldo.h index b61cd1494..f30022494 100644 --- a/arm9/source/lua/ldo.h +++ b/arm9/source/lua/ldo.h @@ -71,7 +71,6 @@ LUAI_FUNC int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, LUAI_FUNC CallInfo *luaD_precall (lua_State *L, StkId func, int nResults); LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); -LUAI_FUNC StkId luaD_tryfuncTM (lua_State *L, StkId func); LUAI_FUNC int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status); LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); diff --git a/arm9/source/lua/lgc.c b/arm9/source/lua/lgc.c index a1d09d2d4..f1652714a 100644 --- a/arm9/source/lua/lgc.c +++ b/arm9/source/lua/lgc.c @@ -542,10 +542,12 @@ static void traversestrongtable (global_State *g, Table *h) { static lu_mem traversetable (global_State *g, Table *h) { const char *weakkey, *weakvalue; const TValue *mode = gfasttm(g, h->metatable, TM_MODE); + TString *smode; markobjectN(g, h->metatable); - if (mode && ttisstring(mode) && /* is there a weak mode? */ - (cast_void(weakkey = strchr(svalue(mode), 'k')), - cast_void(weakvalue = strchr(svalue(mode), 'v')), + if (mode && ttisshrstring(mode) && /* is there a weak mode? */ + (cast_void(smode = tsvalue(mode)), + cast_void(weakkey = strchr(getshrstr(smode), 'k')), + cast_void(weakvalue = strchr(getshrstr(smode), 'v')), (weakkey || weakvalue))) { /* is really weak? */ if (!weakkey) /* strong keys? */ traverseweakvalue(g, h); @@ -638,7 +640,9 @@ static int traversethread (global_State *g, lua_State *th) { for (uv = th->openupval; uv != NULL; uv = uv->u.open.next) markobject(g, uv); /* open upvalues cannot be collected */ if (g->gcstate == GCSatomic) { /* final traversal? */ - for (; o < th->stack_last.p + EXTRA_STACK; o++) + if (!g->gcemergency) + luaD_shrinkstack(th); /* do not change stack in emergency cycle */ + for (o = th->top.p; o < th->stack_last.p + EXTRA_STACK; o++) setnilvalue(s2v(o)); /* clear dead stack slice */ /* 'remarkupvals' may have removed thread from 'twups' list */ if (!isintwups(th) && th->openupval != NULL) { @@ -646,8 +650,6 @@ static int traversethread (global_State *g, lua_State *th) { g->twups = th; } } - else if (!g->gcemergency) - luaD_shrinkstack(th); /* do not change stack in emergency cycle */ return 1 + stacksize(th); } @@ -1409,7 +1411,7 @@ static void stepgenfull (lua_State *L, global_State *g) { setminordebt(g); } else { /* another bad collection; stay in incremental mode */ - g->GCestimate = gettotalbytes(g); /* first estimate */; + g->GCestimate = gettotalbytes(g); /* first estimate */ entersweep(L); luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ setpause(g); @@ -1604,7 +1606,7 @@ static lu_mem singlestep (lua_State *L) { case GCSenteratomic: { work = atomic(L); /* work is what was traversed by 'atomic' */ entersweep(L); - g->GCestimate = gettotalbytes(g); /* first estimate */; + g->GCestimate = gettotalbytes(g); /* first estimate */ break; } case GCSswpallgc: { /* sweep "regular" objects */ @@ -1710,6 +1712,8 @@ static void fullinc (lua_State *L, global_State *g) { entersweep(L); /* sweep everything to turn them back to white */ /* finish any pending sweep phase to start a new cycle */ luaC_runtilstate(L, bitmask(GCSpause)); + luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + g->gcstate = GCSenteratomic; /* go straight to atomic phase */ luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ /* estimate must be correct after a full GC cycle */ lua_assert(g->GCestimate == gettotalbytes(g)); diff --git a/arm9/source/lua/liolib.c b/arm9/source/lua/liolib.c index b08397da4..c5075f3e7 100644 --- a/arm9/source/lua/liolib.c +++ b/arm9/source/lua/liolib.c @@ -245,8 +245,8 @@ static int f_gc (lua_State *L) { */ static int io_fclose (lua_State *L) { LStream *p = tolstream(L); - int res = fclose(p->f); - return luaL_fileresult(L, (res == 0), NULL); + errno = 0; + return luaL_fileresult(L, (fclose(p->f) == 0), NULL); } @@ -272,6 +272,7 @@ static int io_open (lua_State *L) { LStream *p = newfile(L); const char *md = mode; /* to traverse/check mode */ luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); + errno = 0; p->f = fopen(filename, mode); return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; } @@ -292,6 +293,7 @@ static int io_popen (lua_State *L) { const char *mode = luaL_optstring(L, 2, "r"); LStream *p = newprefile(L); luaL_argcheck(L, l_checkmodep(mode), 2, "invalid mode"); + errno = 0; p->f = l_popen(L, filename, mode); p->closef = &io_pclose; return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; @@ -300,6 +302,7 @@ static int io_popen (lua_State *L) { static int io_tmpfile (lua_State *L) { LStream *p = newfile(L); + errno = 0; p->f = tmpfile(); return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1; } @@ -567,6 +570,7 @@ static int g_read (lua_State *L, FILE *f, int first) { int nargs = lua_gettop(L) - 1; int n, success; clearerr(f); + errno = 0; if (nargs == 0) { /* no arguments? */ success = read_line(L, f, 1); n = first + 1; /* to return 1 result */ @@ -660,6 +664,7 @@ static int io_readline (lua_State *L) { static int g_write (lua_State *L, FILE *f, int arg) { int nargs = lua_gettop(L) - arg; int status = 1; + errno = 0; for (; nargs--; arg++) { if (lua_type(L, arg) == LUA_TNUMBER) { /* optimization: could be done exactly as for strings */ @@ -678,7 +683,8 @@ static int g_write (lua_State *L, FILE *f, int arg) { } if (l_likely(status)) return 1; /* file handle already on stack top */ - else return luaL_fileresult(L, status, NULL); + else + return luaL_fileresult(L, status, NULL); } @@ -703,6 +709,7 @@ static int f_seek (lua_State *L) { l_seeknum offset = (l_seeknum)p3; luaL_argcheck(L, (lua_Integer)offset == p3, 3, "not an integer in proper range"); + errno = 0; op = l_fseek(f, offset, mode[op]); if (l_unlikely(op)) return luaL_fileresult(L, 0, NULL); /* error */ @@ -719,19 +726,25 @@ static int f_setvbuf (lua_State *L) { FILE *f = tofile(L); int op = luaL_checkoption(L, 2, NULL, modenames); lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); - int res = setvbuf(f, NULL, mode[op], (size_t)sz); + int res; + errno = 0; + res = setvbuf(f, NULL, mode[op], (size_t)sz); return luaL_fileresult(L, res == 0, NULL); } static int io_flush (lua_State *L) { - return luaL_fileresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL); + FILE *f = getiofile(L, IO_OUTPUT); + errno = 0; + return luaL_fileresult(L, fflush(f) == 0, NULL); } static int f_flush (lua_State *L) { - return luaL_fileresult(L, fflush(tofile(L)) == 0, NULL); + FILE *f = tofile(L); + errno = 0; + return luaL_fileresult(L, fflush(f) == 0, NULL); } @@ -773,7 +786,7 @@ static const luaL_Reg meth[] = { ** metamethods for file handles */ static const luaL_Reg metameth[] = { - {"__index", NULL}, /* place holder */ + {"__index", NULL}, /* placeholder */ {"__gc", f_gc}, {"__close", f_gc}, {"__tostring", f_tostring}, diff --git a/arm9/source/lua/llimits.h b/arm9/source/lua/llimits.h index d0575ea26..cd44ddf2c 100644 --- a/arm9/source/lua/llimits.h +++ b/arm9/source/lua/llimits.h @@ -133,7 +133,7 @@ typedef LUAI_UACINT l_uacInt; /* type casts (a macro highlights casts in the code) */ -#define cast(t, exp) ((t)(exp)) +#define cast(t, exp) ((t)(exp)) #define castp(t, exp) ((t)(void *)(exp)) #define cast_void(i) cast(void, (i)) diff --git a/arm9/source/lua/lmathlib.c b/arm9/source/lua/lmathlib.c index d0b1e1e5d..438106348 100644 --- a/arm9/source/lua/lmathlib.c +++ b/arm9/source/lua/lmathlib.c @@ -249,6 +249,15 @@ static int math_type (lua_State *L) { ** =================================================================== */ +/* +** This code uses lots of shifts. ANSI C does not allow shifts greater +** than or equal to the width of the type being shifted, so some shifts +** are written in convoluted ways to match that restriction. For +** preprocessor tests, it assumes a width of 32 bits, so the maximum +** shift there is 31 bits. +*/ + + /* number of binary digits in the mantissa of a float */ #define FIGS l_floatatt(MANT_DIG) @@ -271,16 +280,19 @@ static int math_type (lua_State *L) { /* 'long' has at least 64 bits */ #define Rand64 unsigned long +#define SRand64 long #elif !defined(LUA_USE_C89) && defined(LLONG_MAX) /* there is a 'long long' type (which must have at least 64 bits) */ #define Rand64 unsigned long long +#define SRand64 long long #elif ((LUA_MAXUNSIGNED >> 31) >> 31) >= 3 /* 'lua_Unsigned' has at least 64 bits */ #define Rand64 lua_Unsigned +#define SRand64 lua_Integer #endif @@ -319,23 +331,30 @@ static Rand64 nextrand (Rand64 *state) { } -/* must take care to not shift stuff by more than 63 slots */ - - /* ** Convert bits from a random integer into a float in the ** interval [0,1), getting the higher FIG bits from the ** random unsigned integer and converting that to a float. +** Some old Microsoft compilers cannot cast an unsigned long +** to a floating-point number, so we use a signed long as an +** intermediary. When lua_Number is float or double, the shift ensures +** that 'sx' is non negative; in that case, a good compiler will remove +** the correction. */ /* must throw out the extra (64 - FIGS) bits */ #define shift64_FIG (64 - FIGS) -/* to scale to [0, 1), multiply by scaleFIG = 2^(-FIGS) */ +/* 2^(-FIGS) == 2^-1 / 2^(FIGS-1) */ #define scaleFIG (l_mathop(0.5) / ((Rand64)1 << (FIGS - 1))) static lua_Number I2d (Rand64 x) { - return (lua_Number)(trim64(x) >> shift64_FIG) * scaleFIG; + SRand64 sx = (SRand64)(trim64(x) >> shift64_FIG); + lua_Number res = (lua_Number)(sx) * scaleFIG; + if (sx < 0) + res += l_mathop(1.0); /* correct the two's complement if negative */ + lua_assert(0 <= res && res < 1); + return res; } /* convert a 'Rand64' to a 'lua_Unsigned' */ @@ -471,8 +490,6 @@ static lua_Number I2d (Rand64 x) { #else /* 32 < FIGS <= 64 */ -/* must take care to not shift stuff by more than 31 slots */ - /* 2^(-FIGS) = 1.0 / 2^30 / 2^3 / 2^(FIGS-33) */ #define scaleFIG \ (l_mathop(1.0) / (UONE << 30) / l_mathop(8.0) / (UONE << (FIGS - 33))) diff --git a/arm9/source/lua/loadlib.c b/arm9/source/lua/loadlib.c index d792dffaa..6d289fceb 100644 --- a/arm9/source/lua/loadlib.c +++ b/arm9/source/lua/loadlib.c @@ -24,15 +24,6 @@ #include "lualib.h" -/* -** LUA_IGMARK is a mark to ignore all before it when building the -** luaopen_ function name. -*/ -#if !defined (LUA_IGMARK) -#define LUA_IGMARK "-" -#endif - - /* ** LUA_CSUBSEP is the character that replaces dots in submodule names ** when searching for a C loader. diff --git a/arm9/source/lua/lobject.c b/arm9/source/lua/lobject.c index f73ffc6d9..9cfa5227e 100644 --- a/arm9/source/lua/lobject.c +++ b/arm9/source/lua/lobject.c @@ -542,7 +542,7 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { addstr2buff(&buff, fmt, strlen(fmt)); /* rest of 'fmt' */ clearbuff(&buff); /* empty buffer into the stack */ lua_assert(buff.pushed == 1); - return svalue(s2v(L->top.p - 1)); + return getstr(tsvalue(s2v(L->top.p - 1))); } diff --git a/arm9/source/lua/lobject.h b/arm9/source/lua/lobject.h index 556608e4a..980e42f8c 100644 --- a/arm9/source/lua/lobject.h +++ b/arm9/source/lua/lobject.h @@ -386,7 +386,7 @@ typedef struct GCObject { typedef struct TString { CommonHeader; lu_byte extra; /* reserved words for short strings; "has hash" for longs */ - lu_byte shrlen; /* length for short strings */ + lu_byte shrlen; /* length for short strings, 0xFF for long strings */ unsigned int hash; union { size_t lnglen; /* length for long strings */ @@ -398,19 +398,17 @@ typedef struct TString { /* -** Get the actual string (array of bytes) from a 'TString'. +** Get the actual string (array of bytes) from a 'TString'. (Generic +** version and specialized versions for long and short strings.) */ -#define getstr(ts) ((ts)->contents) +#define getstr(ts) ((ts)->contents) +#define getlngstr(ts) check_exp((ts)->shrlen == 0xFF, (ts)->contents) +#define getshrstr(ts) check_exp((ts)->shrlen != 0xFF, (ts)->contents) -/* get the actual string (array of bytes) from a Lua value */ -#define svalue(o) getstr(tsvalue(o)) - /* get string length from 'TString *s' */ -#define tsslen(s) ((s)->tt == LUA_VSHRSTR ? (s)->shrlen : (s)->u.lnglen) - -/* get string length from 'TValue *o' */ -#define vslen(o) tsslen(tsvalue(o)) +#define tsslen(s) \ + ((s)->shrlen != 0xFF ? (s)->shrlen : (s)->u.lnglen) /* }================================================================== */ diff --git a/arm9/source/lua/lopcodes.h b/arm9/source/lua/lopcodes.h index 4c5514539..46911cac1 100644 --- a/arm9/source/lua/lopcodes.h +++ b/arm9/source/lua/lopcodes.h @@ -210,15 +210,15 @@ OP_LOADNIL,/* A B R[A], R[A+1], ..., R[A+B] := nil */ OP_GETUPVAL,/* A B R[A] := UpValue[B] */ OP_SETUPVAL,/* A B UpValue[B] := R[A] */ -OP_GETTABUP,/* A B C R[A] := UpValue[B][K[C]:string] */ +OP_GETTABUP,/* A B C R[A] := UpValue[B][K[C]:shortstring] */ OP_GETTABLE,/* A B C R[A] := R[B][R[C]] */ OP_GETI,/* A B C R[A] := R[B][C] */ -OP_GETFIELD,/* A B C R[A] := R[B][K[C]:string] */ +OP_GETFIELD,/* A B C R[A] := R[B][K[C]:shortstring] */ -OP_SETTABUP,/* A B C UpValue[A][K[B]:string] := RK(C) */ +OP_SETTABUP,/* A B C UpValue[A][K[B]:shortstring] := RK(C) */ OP_SETTABLE,/* A B C R[A][R[B]] := RK(C) */ OP_SETI,/* A B C R[A][B] := RK(C) */ -OP_SETFIELD,/* A B C R[A][K[B]:string] := RK(C) */ +OP_SETFIELD,/* A B C R[A][K[B]:shortstring] := RK(C) */ OP_NEWTABLE,/* A B C k R[A] := {} */ diff --git a/arm9/source/lua/loslib.c b/arm9/source/lua/loslib.c index ad5a92768..ba80d72c4 100644 --- a/arm9/source/lua/loslib.c +++ b/arm9/source/lua/loslib.c @@ -155,6 +155,7 @@ static int os_execute (lua_State *L) { static int os_remove (lua_State *L) { const char *filename = luaL_checkstring(L, 1); + errno = 0; return luaL_fileresult(L, remove(filename) == 0, filename); } @@ -162,6 +163,7 @@ static int os_remove (lua_State *L) { static int os_rename (lua_State *L) { const char *fromname = luaL_checkstring(L, 1); const char *toname = luaL_checkstring(L, 2); + errno = 0; return luaL_fileresult(L, rename(fromname, toname) == 0, NULL); } diff --git a/arm9/source/lua/lparser.c b/arm9/source/lua/lparser.c index b745f236f..2b888c7cf 100644 --- a/arm9/source/lua/lparser.c +++ b/arm9/source/lua/lparser.c @@ -1022,10 +1022,11 @@ static int explist (LexState *ls, expdesc *v) { } -static void funcargs (LexState *ls, expdesc *f, int line) { +static void funcargs (LexState *ls, expdesc *f) { FuncState *fs = ls->fs; expdesc args; int base, nparams; + int line = ls->linenumber; switch (ls->t.token) { case '(': { /* funcargs -> '(' [ explist ] ')' */ luaX_next(ls); @@ -1063,8 +1064,8 @@ static void funcargs (LexState *ls, expdesc *f, int line) { } init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); luaK_fixline(fs, line); - fs->freereg = base+1; /* call remove function and arguments and leaves - (unless changed) one result */ + fs->freereg = base+1; /* call removes function and arguments and leaves + one result (unless changed later) */ } @@ -1103,7 +1104,6 @@ static void suffixedexp (LexState *ls, expdesc *v) { /* suffixedexp -> primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ FuncState *fs = ls->fs; - int line = ls->linenumber; primaryexp(ls, v); for (;;) { switch (ls->t.token) { @@ -1123,12 +1123,12 @@ static void suffixedexp (LexState *ls, expdesc *v) { luaX_next(ls); codename(ls, &key); luaK_self(fs, v, &key); - funcargs(ls, v, line); + funcargs(ls, v); break; } case '(': case TK_STRING: case '{': { /* funcargs */ luaK_exp2nextreg(fs, v); - funcargs(ls, v, line); + funcargs(ls, v); break; } default: return; diff --git a/arm9/source/lua/lstate.c b/arm9/source/lua/lstate.c index cf8d1bc0f..703eabb57 100644 --- a/arm9/source/lua/lstate.c +++ b/arm9/source/lua/lstate.c @@ -119,7 +119,7 @@ CallInfo *luaE_extendCI (lua_State *L) { /* ** free all CallInfo structures not in use by a thread */ -void luaE_freeCI (lua_State *L) { +static void freeCI (lua_State *L) { CallInfo *ci = L->ci; CallInfo *next = ci->next; ci->next = NULL; @@ -204,7 +204,7 @@ static void freestack (lua_State *L) { if (L->stack.p == NULL) return; /* stack not completely built yet */ L->ci = &L->base_ci; /* free the entire 'ci' list */ - luaE_freeCI(L); + freeCI(L); lua_assert(L->nci == 0); luaM_freearray(L, L->stack.p, stacksize(L) + EXTRA_STACK); /* free stack */ } @@ -433,7 +433,7 @@ void luaE_warning (lua_State *L, const char *msg, int tocont) { void luaE_warnerror (lua_State *L, const char *where) { TValue *errobj = s2v(L->top.p - 1); /* error object */ const char *msg = (ttisstring(errobj)) - ? svalue(errobj) + ? getstr(tsvalue(errobj)) : "error object is not a string"; /* produce warning "error in %s (%s)" (where, msg) */ luaE_warning(L, "error in ", 1); diff --git a/arm9/source/lua/lstate.h b/arm9/source/lua/lstate.h index 6a8a20233..620f24893 100644 --- a/arm9/source/lua/lstate.h +++ b/arm9/source/lua/lstate.h @@ -181,7 +181,7 @@ struct CallInfo { union { struct { /* only for Lua functions */ const Instruction *savedpc; - volatile l_signalT trap; + volatile l_signalT trap; /* function is tracing lines/counts */ int nextraargs; /* # of extra arguments in vararg functions */ } l; struct { /* only for C functions */ @@ -396,7 +396,6 @@ union GCUnion { LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); -LUAI_FUNC void luaE_freeCI (lua_State *L); LUAI_FUNC void luaE_shrinkCI (lua_State *L); LUAI_FUNC void luaE_checkcstack (lua_State *L); LUAI_FUNC void luaE_incCstack (lua_State *L); diff --git a/arm9/source/lua/lstring.c b/arm9/source/lua/lstring.c index 13dcaf425..97757355c 100644 --- a/arm9/source/lua/lstring.c +++ b/arm9/source/lua/lstring.c @@ -36,7 +36,7 @@ int luaS_eqlngstr (TString *a, TString *b) { lua_assert(a->tt == LUA_VLNGSTR && b->tt == LUA_VLNGSTR); return (a == b) || /* same instance or... */ ((len == b->u.lnglen) && /* equal length and ... */ - (memcmp(getstr(a), getstr(b), len) == 0)); /* equal contents */ + (memcmp(getlngstr(a), getlngstr(b), len) == 0)); /* equal contents */ } @@ -52,7 +52,7 @@ unsigned int luaS_hashlongstr (TString *ts) { lua_assert(ts->tt == LUA_VLNGSTR); if (ts->extra == 0) { /* no hash? */ size_t len = ts->u.lnglen; - ts->hash = luaS_hash(getstr(ts), len, ts->hash); + ts->hash = luaS_hash(getlngstr(ts), len, ts->hash); ts->extra = 1; /* now it has its hash */ } return ts->hash; @@ -157,6 +157,7 @@ static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { TString *luaS_createlngstrobj (lua_State *L, size_t l) { TString *ts = createstrobj(L, l, LUA_VLNGSTR, G(L)->seed); ts->u.lnglen = l; + ts->shrlen = 0xFF; /* signals that it is a long string */ return ts; } @@ -193,7 +194,7 @@ static TString *internshrstr (lua_State *L, const char *str, size_t l) { TString **list = &tb->hash[lmod(h, tb->size)]; lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ for (ts = *list; ts != NULL; ts = ts->u.hnext) { - if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { + if (l == ts->shrlen && (memcmp(str, getshrstr(ts), l * sizeof(char)) == 0)) { /* found! */ if (isdead(g, ts)) /* dead (but not collected yet)? */ changewhite(ts); /* resurrect it */ @@ -206,8 +207,8 @@ static TString *internshrstr (lua_State *L, const char *str, size_t l) { list = &tb->hash[lmod(h, tb->size)]; /* rehash with new size */ } ts = createstrobj(L, l, LUA_VSHRSTR, h); - memcpy(getstr(ts), str, l * sizeof(char)); ts->shrlen = cast_byte(l); + memcpy(getshrstr(ts), str, l * sizeof(char)); ts->u.hnext = *list; *list = ts; tb->nuse++; @@ -223,10 +224,10 @@ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { return internshrstr(L, str, l); else { TString *ts; - if (l_unlikely(l >= (MAX_SIZE - sizeof(TString))/sizeof(char))) + if (l_unlikely(l * sizeof(char) >= (MAX_SIZE - sizeof(TString)))) luaM_toobig(L); ts = luaS_createlngstrobj(L, l); - memcpy(getstr(ts), str, l * sizeof(char)); + memcpy(getlngstr(ts), str, l * sizeof(char)); return ts; } } diff --git a/arm9/source/lua/ltable.c b/arm9/source/lua/ltable.c index 3c690c5f1..3353c0479 100644 --- a/arm9/source/lua/ltable.c +++ b/arm9/source/lua/ltable.c @@ -252,7 +252,7 @@ LUAI_FUNC unsigned int luaH_realasize (const Table *t) { return t->alimit; /* this is the size */ else { unsigned int size = t->alimit; - /* compute the smallest power of 2 not smaller than 'n' */ + /* compute the smallest power of 2 not smaller than 'size' */ size |= (size >> 1); size |= (size >> 2); size |= (size >> 4); @@ -662,7 +662,8 @@ static Node *getfreepos (Table *t) { ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ -void luaH_newkey (lua_State *L, Table *t, const TValue *key, TValue *value) { +static void luaH_newkey (lua_State *L, Table *t, const TValue *key, + TValue *value) { Node *mp; TValue aux; if (l_unlikely(ttisnil(key))) @@ -721,22 +722,36 @@ void luaH_newkey (lua_State *L, Table *t, const TValue *key, TValue *value) { /* ** Search function for integers. If integer is inside 'alimit', get it -** directly from the array part. Otherwise, if 'alimit' is not equal to -** the real size of the array, key still can be in the array part. In -** this case, try to avoid a call to 'luaH_realasize' when key is just -** one more than the limit (so that it can be incremented without -** changing the real size of the array). +** directly from the array part. Otherwise, if 'alimit' is not +** the real size of the array, the key still can be in the array part. +** In this case, do the "Xmilia trick" to check whether 'key-1' is +** smaller than the real size. +** The trick works as follow: let 'p' be an integer such that +** '2^(p+1) >= alimit > 2^p', or '2^(p+1) > alimit-1 >= 2^p'. +** That is, 2^(p+1) is the real size of the array, and 'p' is the highest +** bit on in 'alimit-1'. What we have to check becomes 'key-1 < 2^(p+1)'. +** We compute '(key-1) & ~(alimit-1)', which we call 'res'; it will +** have the 'p' bit cleared. If the key is outside the array, that is, +** 'key-1 >= 2^(p+1)', then 'res' will have some bit on higher than 'p', +** therefore it will be larger or equal to 'alimit', and the check +** will fail. If 'key-1 < 2^(p+1)', then 'res' has no bit on higher than +** 'p', and as the bit 'p' itself was cleared, 'res' will be smaller +** than 2^p, therefore smaller than 'alimit', and the check succeeds. +** As special cases, when 'alimit' is 0 the condition is trivially false, +** and when 'alimit' is 1 the condition simplifies to 'key-1 < alimit'. +** If key is 0 or negative, 'res' will have its higher bit on, so that +** if cannot be smaller than alimit. */ const TValue *luaH_getint (Table *t, lua_Integer key) { - if (l_castS2U(key) - 1u < t->alimit) /* 'key' in [1, t->alimit]? */ + lua_Unsigned alimit = t->alimit; + if (l_castS2U(key) - 1u < alimit) /* 'key' in [1, t->alimit]? */ return &t->array[key - 1]; - else if (!limitequalsasize(t) && /* key still may be in the array part? */ - (l_castS2U(key) == t->alimit + 1 || - l_castS2U(key) - 1u < luaH_realasize(t))) { + else if (!isrealasize(t) && /* key still may be in the array part? */ + (((l_castS2U(key) - 1u) & ~(alimit - 1u)) < alimit)) { t->alimit = cast_uint(key); /* probably '#t' is here now */ return &t->array[key - 1]; } - else { + else { /* key is not in the array part; check the hash */ Node *n = hashint(t, key); for (;;) { /* check whether 'key' is somewhere in the chain */ if (keyisinteger(n) && keyival(n) == key) diff --git a/arm9/source/lua/ltable.h b/arm9/source/lua/ltable.h index 75dd9e26e..8e6890342 100644 --- a/arm9/source/lua/ltable.h +++ b/arm9/source/lua/ltable.h @@ -41,8 +41,6 @@ LUAI_FUNC void luaH_setint (lua_State *L, Table *t, lua_Integer key, LUAI_FUNC const TValue *luaH_getshortstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); -LUAI_FUNC void luaH_newkey (lua_State *L, Table *t, const TValue *key, - TValue *value); LUAI_FUNC void luaH_set (lua_State *L, Table *t, const TValue *key, TValue *value); LUAI_FUNC void luaH_finishset (lua_State *L, Table *t, const TValue *key, diff --git a/arm9/source/lua/ltm.h b/arm9/source/lua/ltm.h index c309e2ae1..73b833c60 100644 --- a/arm9/source/lua/ltm.h +++ b/arm9/source/lua/ltm.h @@ -9,7 +9,6 @@ #include "lobject.h" -#include "lstate.h" /* @@ -96,8 +95,8 @@ LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, int inv, int isfloat, TMS event); LUAI_FUNC void luaT_adjustvarargs (lua_State *L, int nfixparams, - CallInfo *ci, const Proto *p); -LUAI_FUNC void luaT_getvarargs (lua_State *L, CallInfo *ci, + struct CallInfo *ci, const Proto *p); +LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, StkId where, int wanted); diff --git a/arm9/source/lua/lua.h b/arm9/source/lua/lua.h index fd16cf805..f050dac09 100644 --- a/arm9/source/lua/lua.h +++ b/arm9/source/lua/lua.h @@ -18,14 +18,14 @@ #define LUA_VERSION_MAJOR "5" #define LUA_VERSION_MINOR "4" -#define LUA_VERSION_RELEASE "6" +#define LUA_VERSION_RELEASE "7" #define LUA_VERSION_NUM 504 -#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 6) +#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 7) #define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE -#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2023 Lua.org, PUC-Rio" +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2024 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" @@ -497,7 +497,7 @@ struct lua_Debug { /****************************************************************************** -* Copyright (C) 1994-2023 Lua.org, PUC-Rio. +* Copyright (C) 1994-2024 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/arm9/source/lua/lua.hpp b/arm9/source/lua/lua.hpp deleted file mode 100644 index ec417f594..000000000 --- a/arm9/source/lua/lua.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// lua.hpp -// Lua header files for C++ -// <> not supplied automatically because Lua also compiles as C++ - -extern "C" { -#include "lua.h" -#include "lualib.h" -#include "lauxlib.h" -} diff --git a/arm9/source/lua/luaconf.h b/arm9/source/lua/luaconf.h index 137103ede..33bb580d1 100644 --- a/arm9/source/lua/luaconf.h +++ b/arm9/source/lua/luaconf.h @@ -257,6 +257,15 @@ #endif + +/* +** LUA_IGMARK is a mark to ignore all after it when building the +** module name (e.g., used to build the luaopen_ function name). +** Typically, the suffix after the mark is the module version, +** as in "mod-v1.2.so". +*/ +#define LUA_IGMARK "-" + /* }================================================================== */ diff --git a/arm9/source/lua/lundump.c b/arm9/source/lua/lundump.c index 02aed64fb..e8d92a853 100644 --- a/arm9/source/lua/lundump.c +++ b/arm9/source/lua/lundump.c @@ -81,7 +81,7 @@ static size_t loadUnsigned (LoadState *S, size_t limit) { static size_t loadSize (LoadState *S) { - return loadUnsigned(S, ~(size_t)0); + return loadUnsigned(S, MAX_SIZET); } @@ -122,7 +122,7 @@ static TString *loadStringN (LoadState *S, Proto *p) { ts = luaS_createlngstrobj(L, size); /* create string */ setsvalue2s(L, L->top.p, ts); /* anchor it ('loadVector' can GC) */ luaD_inctop(L); - loadVector(S, getstr(ts), size); /* load directly in final place */ + loadVector(S, getlngstr(ts), size); /* load directly in final place */ L->top.p--; /* pop string */ } luaC_objbarrier(L, p, ts); diff --git a/arm9/source/lua/lundump.h b/arm9/source/lua/lundump.h index f3748a998..a97676ca1 100644 --- a/arm9/source/lua/lundump.h +++ b/arm9/source/lua/lundump.h @@ -21,8 +21,7 @@ /* ** Encode major-minor version in one byte, one nibble for each */ -#define MYINT(s) (s[0]-'0') /* assume one-digit numerals */ -#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR)) +#define LUAC_VERSION (((LUA_VERSION_NUM / 100) * 16) + LUA_VERSION_NUM % 100) #define LUAC_FORMAT 0 /* this is the official format */ diff --git a/arm9/source/lua/lvm.c b/arm9/source/lua/lvm.c index 8493a770c..fcd24e11d 100644 --- a/arm9/source/lua/lvm.c +++ b/arm9/source/lua/lvm.c @@ -91,8 +91,10 @@ static int l_strton (const TValue *obj, TValue *result) { lua_assert(obj != result); if (!cvt2num(obj)) /* is object not a string? */ return 0; - else - return (luaO_str2num(svalue(obj), result) == vslen(obj) + 1); + else { + TString *st = tsvalue(obj); + return (luaO_str2num(getstr(st), result) == tsslen(st) + 1); + } } @@ -366,30 +368,32 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key, /* -** Compare two strings 'ls' x 'rs', returning an integer less-equal- -** -greater than zero if 'ls' is less-equal-greater than 'rs'. +** Compare two strings 'ts1' x 'ts2', returning an integer less-equal- +** -greater than zero if 'ts1' is less-equal-greater than 'ts2'. ** The code is a little tricky because it allows '\0' in the strings -** and it uses 'strcoll' (to respect locales) for each segments -** of the strings. +** and it uses 'strcoll' (to respect locales) for each segment +** of the strings. Note that segments can compare equal but still +** have different lengths. */ -static int l_strcmp (const TString *ls, const TString *rs) { - const char *l = getstr(ls); - size_t ll = tsslen(ls); - const char *r = getstr(rs); - size_t lr = tsslen(rs); +static int l_strcmp (const TString *ts1, const TString *ts2) { + const char *s1 = getstr(ts1); + size_t rl1 = tsslen(ts1); /* real length */ + const char *s2 = getstr(ts2); + size_t rl2 = tsslen(ts2); for (;;) { /* for each segment */ - int temp = strcoll(l, r); + int temp = strcoll(s1, s2); if (temp != 0) /* not equal? */ return temp; /* done */ else { /* strings are equal up to a '\0' */ - size_t len = strlen(l); /* index of first '\0' in both strings */ - if (len == lr) /* 'rs' is finished? */ - return (len == ll) ? 0 : 1; /* check 'ls' */ - else if (len == ll) /* 'ls' is finished? */ - return -1; /* 'ls' is less than 'rs' ('rs' is not finished) */ - /* both strings longer than 'len'; go on comparing after the '\0' */ - len++; - l += len; ll -= len; r += len; lr -= len; + size_t zl1 = strlen(s1); /* index of first '\0' in 's1' */ + size_t zl2 = strlen(s2); /* index of first '\0' in 's2' */ + if (zl2 == rl2) /* 's2' is finished? */ + return (zl1 == rl1) ? 0 : 1; /* check 's1' */ + else if (zl1 == rl1) /* 's1' is finished? */ + return -1; /* 's1' is less than 's2' ('s2' is not finished) */ + /* both strings longer than 'zl'; go on comparing after the '\0' */ + zl1++; zl2++; + s1 += zl1; rl1 -= zl1; s2 += zl2; rl2 -= zl2; } } } @@ -624,8 +628,9 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { static void copy2buff (StkId top, int n, char *buff) { size_t tl = 0; /* size already copied */ do { - size_t l = vslen(s2v(top - n)); /* length of string being copied */ - memcpy(buff + tl, svalue(s2v(top - n)), l * sizeof(char)); + TString *st = tsvalue(s2v(top - n)); + size_t l = tsslen(st); /* length of string being copied */ + memcpy(buff + tl, getstr(st), l * sizeof(char)); tl += l; } while (--n > 0); } @@ -651,12 +656,12 @@ void luaV_concat (lua_State *L, int total) { } else { /* at least two non-empty string values; get as many as possible */ - size_t tl = vslen(s2v(top - 1)); + size_t tl = tsslen(tsvalue(s2v(top - 1))); TString *ts; /* collect total length and number of strings */ for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++) { - size_t l = vslen(s2v(top - n - 1)); - if (l_unlikely(l >= (MAX_SIZE/sizeof(char)) - tl)) { + size_t l = tsslen(tsvalue(s2v(top - n - 1))); + if (l_unlikely(l >= MAX_SIZE - sizeof(TString) - tl)) { L->top.p = top - total; /* pop strings to avoid wasting stack */ luaG_runerror(L, "string length overflow"); } @@ -669,7 +674,7 @@ void luaV_concat (lua_State *L, int total) { } else { /* long string; copy strings directly to final result */ ts = luaS_createlngstrobj(L, tl); - copy2buff(top, n, getstr(ts)); + copy2buff(top, n, getlngstr(ts)); } setsvalue2s(L, top - n, ts); /* create result */ } @@ -1155,18 +1160,11 @@ void luaV_execute (lua_State *L, CallInfo *ci) { startfunc: trap = L->hookmask; returning: /* trap already set */ - cl = clLvalue(s2v(ci->func.p)); + cl = ci_func(ci); k = cl->p->k; pc = ci->u.l.savedpc; - if (l_unlikely(trap)) { - if (pc == cl->p->code) { /* first instruction (not resuming)? */ - if (cl->p->is_vararg) - trap = 0; /* hooks will start after VARARGPREP instruction */ - else /* check 'call' hook */ - luaD_hookcall(L, ci); - } - ci->u.l.trap = 1; /* assume trap is on, for now */ - } + if (l_unlikely(trap)) + trap = luaG_tracecall(L); base = ci->func.p + 1; /* main loop of interpreter */ for (;;) { @@ -1253,7 +1251,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { const TValue *slot; TValue *upval = cl->upvals[GETARG_B(i)]->v.p; TValue *rc = KC(i); - TString *key = tsvalue(rc); /* key must be a string */ + TString *key = tsvalue(rc); /* key must be a short string */ if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { setobj2s(L, ra, slot); } @@ -1296,7 +1294,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { const TValue *slot; TValue *rb = vRB(i); TValue *rc = KC(i); - TString *key = tsvalue(rc); /* key must be a string */ + TString *key = tsvalue(rc); /* key must be a short string */ if (luaV_fastget(L, rb, key, slot, luaH_getshortstr)) { setobj2s(L, ra, slot); } @@ -1309,7 +1307,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { TValue *upval = cl->upvals[GETARG_A(i)]->v.p; TValue *rb = KB(i); TValue *rc = RKC(i); - TString *key = tsvalue(rb); /* key must be a string */ + TString *key = tsvalue(rb); /* key must be a short string */ if (luaV_fastget(L, upval, key, slot, luaH_getshortstr)) { luaV_finishfastset(L, upval, slot, rc); } @@ -1352,7 +1350,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { const TValue *slot; TValue *rb = KB(i); TValue *rc = RKC(i); - TString *key = tsvalue(rb); /* key must be a string */ + TString *key = tsvalue(rb); /* key must be a short string */ if (luaV_fastget(L, s2v(ra), key, slot, luaH_getshortstr)) { luaV_finishfastset(L, s2v(ra), slot, rc); } From 137f223002a51d0737d29d0f7f79116a139f3a98 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 18 Nov 2024 23:47:38 -0600 Subject: [PATCH 049/124] remove test libraries now that i want to attempt to implement in a real api --- README.md | 34 +--- arm9/source/lua/gm9fs.c | 82 ---------- arm9/source/lua/gm9fs.h | 6 - arm9/source/lua/gm9lua.c | 4 - arm9/source/lua/gm9ui.c | 342 --------------------------------------- arm9/source/lua/gm9ui.h | 14 -- 6 files changed, 5 insertions(+), 477 deletions(-) delete mode 100644 arm9/source/lua/gm9fs.c delete mode 100644 arm9/source/lua/gm9fs.h delete mode 100644 arm9/source/lua/gm9ui.c delete mode 100644 arm9/source/lua/gm9ui.h diff --git a/README.md b/README.md index 3abb9a471..5e692285b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Fork of [d0k3/GodMode9](https://github.com/d0k3/GodMode9) with super experimental Lua support. -A simple test... Includes Lua 5.4.6 with a few changes for compile-time warnings - * Makefile.build edited to support a LIBS variable on build * Math lib (-lm) now needed for lua @@ -18,33 +16,11 @@ The main lua stuff is at `arm9/source/lua`. Custom files are prefixed with `gm9` The API here is not at all stable. But there are currently two libraries to play with. This is not set in stone! +Make sure to read this for the API proposal: https://github.com/ihaveamac/GM9-lua-attempt/issues/3 + +Look at the other branches for older development tests. lua-attempt is where most of the stuff happened. + ## Global * print(...) - * Calling this will replace the alt screen with an output buffer. It doesn't support newlines or word wrapping properly yet - -## UI - -* void UI.ShowPrompt(string text) -* bool UI.AskPrompt(string text) -* bool UI.ShowKeyboardOrPrompt(string start\_val, number max\_size, string text) -* number UI.ShowNumberPrompt(number start\_val, string text) -* number UI.ShowHexPrompt(number start\_val, number max_size, string text) -* void UI.ShowString(string text) -* string UI.WordWrapString(string text[, int llen]) - * llen == -1 means alt screen -* void UI.ClearScreen(number screen, u32 color) -* number UI.ShowSelectPrompt(table optionstable, string text) -* bool UI.ShowProgress(u32 current, u32 total, text text) - * returns true if B is pressed -* void UI.DrawString(int which\_screen, string text, int x, int y, int color, int bgcolor) - * which\_screen: - * 0 = main screen - * 1 = alt screen - * 2 = top screen - * 3 = bottom screen - -## FS - -* bool FS.InitImgFS(string path) -* string FS.FileGetData(string path, int size, int offset) + * Calling this will replace the alt screen with an output buffer. It doesn't support newlines or word wrapping properly yet. diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c deleted file mode 100644 index 648999546..000000000 --- a/arm9/source/lua/gm9fs.c +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef NO_LUA -#include "gm9fs.h" -#include "fs.h" -#include "vff.h" -#include "gm9enum.h" - -static int FS_InitImgFS(lua_State* L) { - CheckLuaArgCount(L, 1, "InitImgFS"); - - const char* path; - if (lua_isnil(L, 1)) { - path = NULL; - } else { - path = lua_tostring(L, 1); - luaL_argcheck(L, path, 1, "string or nil expected"); - } - - bool ret = InitImgFS(path); - lua_pushboolean(L, ret); - return 1; -} - -static int FS_FileGetData(lua_State* L) { - CheckLuaArgCount(L, 3, "FileGetData"); - const char* path = luaL_checkstring(L, 1); - lua_Integer size = lua_tointeger(L, 2); - lua_Integer offset = lua_tointeger(L, 3); - if (size == -1) size = STD_BUFFER_SIZE; - else if (size == 0) return luaL_error(L, "size cannot be 0"); - else if (size > STD_BUFFER_SIZE) return luaL_error(L, "size cannot be above %I (STD_BUFFER_SIZE)", STD_BUFFER_SIZE); - - void* buf = malloc(size); - if (!buf) return luaL_error(L, "could not allocate buffer"); - // instead of using FileGetData directly we can use fvx_qread and handle the result - // and return a nil if it works (and an empty string if it really is empty) - UINT br = 0; - FRESULT res = fvx_qread(path, buf, offset, size, &br); - if (res != FR_OK) { - luaL_pushfail(L); - return 1; - } - lua_pushlstring(L, buf, br); - free(buf); - return 1; -} - -static const luaL_Reg FSlib[] = { - {"InitImgFS", FS_InitImgFS}, - {"FileGetData", FS_FileGetData}, - {NULL, NULL} -}; - -static const EnumItem Enum_FS[] = { - GLENUMITEM(T_ROOT), - GLENUMITEM(T_DIR), - GLENUMITEM(T_FILE), - GLENUMITEM(T_DOTDOT), - GLENUMITEM(PERM_SDCARD), - GLENUMITEM(PERM_IMAGE), - GLENUMITEM(PERM_RAMDRIVE), - GLENUMITEM(PERM_EMU_LVL0), - GLENUMITEM(PERM_EMU_LVL1), - GLENUMITEM(PERM_SYS_LVL0), - GLENUMITEM(PERM_SYS_LVL1), - GLENUMITEM(PERM_SYS_LVL2), - GLENUMITEM(PERM_SYS_LVL3), - GLENUMITEM(PERM_SDDATA), - GLENUMITEM(PERM_MEMORY), - GLENUMITEM(PERM_GAME), - GLENUMITEM(PERM_XORPAD), - GLENUMITEM(PERM_CART), - GLENUMITEM(PERM_VRAM), - GLENUMITEM(PERM_BASE), - {NULL, 0} -}; - -int gm9lua_open_FS(lua_State* L) { - luaL_newlib(L, FSlib); - AddLuaEnumItems(L, "FS", Enum_FS); - return 1; -} -#endif diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h deleted file mode 100644 index ab6dd25d9..000000000 --- a/arm9/source/lua/gm9fs.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "gm9lua.h" - -#define GM9LUA_FSLIBNAME "FS" - -int gm9lua_open_FS(lua_State* L); diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 1b7437cac..fab88c64e 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -5,9 +5,7 @@ #include "ff.h" #include "vff.h" #include "fsutil.h" -#include "gm9ui.h" #include "gm9enum.h" -#include "gm9fs.h" #include "gm9loader.h" #include "gm9os.h" @@ -81,8 +79,6 @@ static const luaL_Reg gm9lualibs[] = { {LUA_DBLIBNAME, luaopen_debug}, // gm9 custom - {GM9LUA_UILIBNAME, gm9lua_open_UI}, - {GM9LUA_FSLIBNAME, gm9lua_open_FS}, {GM9LUA_OSLIBNAME, gm9lua_open_os}, {NULL, NULL} diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c deleted file mode 100644 index 113f86992..000000000 --- a/arm9/source/lua/gm9ui.c +++ /dev/null @@ -1,342 +0,0 @@ -#ifndef NO_LUA -#include "gm9ui.h" -#include "gm9enum.h" - -#define MAXOPTIONS 256 -#define MAXOPTIONS_STR "256" - -#define OUTPUTMAXLINES 24 -#define OUTPUTMAXCHARSPERLINE 51 // make sure this includes space for '\0' - -// this output buffer stuff is especially a test, it needs to take into account newlines and fonts that are not 8x10 - -char output_buffer[OUTPUTMAXLINES][OUTPUTMAXCHARSPERLINE]; // hold 24 lines - -void ShiftOutputBufferUp(void) { - for (int i = 0; i < OUTPUTMAXLINES - 1; i++) { - memcpy(output_buffer[i], output_buffer[i + 1], OUTPUTMAXCHARSPERLINE); - } -} - -void ClearOutputBuffer(void) { - memset(output_buffer, 0, sizeof(output_buffer)); -} - -void WriteToOutputBuffer(char* text) { - strlcpy(output_buffer[OUTPUTMAXLINES - 1], text, OUTPUTMAXCHARSPERLINE); -} - -void RenderOutputBuffer(void) { - ClearScreenF(false, true, COLOR_STD_BG); - for (int i = 0; i < OUTPUTMAXLINES; i++) { - DrawString(ALT_SCREEN, output_buffer[i], 0, i * 10, COLOR_STD_FONT, COLOR_TRANSPARENT); - } -} - -static u16* GetScreenFromIndex(int index) { - switch (index) { - case 0: return MAIN_SCREEN; - case 1: return ALT_SCREEN; - case 2: return TOP_SCREEN; - case 3: return BOT_SCREEN; - default: return MAIN_SCREEN; - } -} - -static int UI_ShowPNG(lua_State* L) { - CheckLuaArgCount(L, 2, "ShowPNG"); - lua_Integer which_screen = luaL_checknumber(L, 1); - const char* path = lua_tostring(L, 2); - u16* screen = GetScreenFromIndex(which_screen); - u16 *bitmap = NULL; - u8* png = (u8*) malloc(SCREEN_SIZE(screen)); - u32 bitmap_width, bitmap_height; - if (png) { - u32 png_size = FileGetData(path, png, SCREEN_SIZE(screen), 0); - if (!png_size) { - free(png); - return luaL_error(L, "Could not read %s", path); - } - if (png_size && png_size < SCREEN_SIZE(screen)) { - bitmap = PNG_Decompress(png, png_size, &bitmap_width, &bitmap_height); - if (!bitmap) { - free(png); - return luaL_error(L, "Invalid PNG file"); - } - } - free(png); - if (!bitmap) { - return luaL_error(L, "PNG too large"); - } else if ((SCREEN_WIDTH(screen) < bitmap_width) || (SCREEN_HEIGHT < bitmap_height)) { - free(bitmap); - return luaL_error(L, "PNG too large"); - } else { - DrawBitmap( - screen, // screen - -1, // x coordinate from argument - -1, // y coordinate from argument - bitmap_width, // width - bitmap_height, // height - bitmap // bitmap - ); - free(bitmap); - } - } - return 0; -} - -static int UI_DrawPNG(lua_State* L) { - CheckLuaArgCount(L, 4, "DrawPNG"); - lua_Integer which_screen = luaL_checknumber(L, 1); - int bitmapx = lua_tointeger(L, 2); - int bitmapy = lua_tointeger(L, 3); - const char* path = lua_tostring(L, 4); - u16* screen = GetScreenFromIndex(which_screen); - u16 *bitmap = NULL; - u8* png = (u8*) malloc(SCREEN_SIZE(screen)); - u32 bitmap_width, bitmap_height; - if (png) { - u32 png_size = FileGetData(path, png, SCREEN_SIZE(screen), 0); - if (!png_size) { - free(png); - return luaL_error(L, "Could not read %s", path); - } - if (png_size && png_size < SCREEN_SIZE(screen)) { - bitmap = PNG_Decompress(png, png_size, &bitmap_width, &bitmap_height); - if (!bitmap) { - free(png); - return luaL_error(L, "Invalid PNG file"); - } - } - free(png); - if (!bitmap) { - return luaL_error(L, "PNG too large"); - } else if ((SCREEN_WIDTH(screen) < bitmapx + bitmap_width) || (SCREEN_HEIGHT < bitmapy + bitmap_height)) { - free(bitmap); - return luaL_error(L, "PNG too large"); - } else { - DrawBitmap( - screen, // screen - bitmapx, // x coordinate from argument - bitmapy, // y coordinate from argument - bitmap_width, // width - bitmap_height, // height - bitmap // bitmap - ); - free(bitmap); - } - - } - return 0; -} - -static int UI_ShowPrompt(lua_State* L) { - CheckLuaArgCount(L, 1, "ShowPrompt"); - const char* text = lua_tostring(L, 1); - - ShowPrompt(false, "%s", text); - return 0; -} - -static int UI_ShowHexPrompt(lua_State* L) { - CheckLuaArgCount(L, 3, "ShowHexPrompt"); - const char* text = lua_tostring(L, 3); - u64 start_val = lua_tonumber(L, 1); - u32 n_digits = lua_tonumber(L, 2); - - u64 ret = ShowHexPrompt(start_val, n_digits, "%s", text); - lua_pushnumber(L, ret); - return 1; -} - -static int UI_ShowNumberPrompt(lua_State* L) { - CheckLuaArgCount(L, 2, "ShowNumberPrompt"); - const char* text = lua_tostring(L, 2); - u64 start_val = lua_tonumber(L, 1); - - u64 ret = ShowNumberPrompt(start_val, "%s", text); - lua_pushnumber(L, ret); - return 1; - -} - -static int UI_ShowKeyboardOrPrompt(lua_State* L) { - CheckLuaArgCount(L, 3, "ShowKeyboardOrPrompt"); - const char* text = lua_tostring(L, 3); - const char* _start_val = lua_tostring(L, 1); - u32 start_val_size = strlen(_start_val)+1; - char start_val[start_val_size]; - snprintf(start_val, start_val_size, "%s", _start_val); - u32 max_size = lua_tonumber(L, 2); - bool result = ShowKeyboardOrPrompt(start_val, max_size, "%s", text); - if (result) - lua_pushstring(L, start_val); - else - lua_pushnil(L); - return 1; -} - -static int UI_AskPrompt(lua_State* L) { - CheckLuaArgCount(L, 1, "AskPrompt"); - const char* text = lua_tostring(L, 1); - - bool ret = ShowPrompt(true, "%s", text); - lua_pushboolean(L, ret); - return 1; -} - -static int UI_ShowString(lua_State* L) { - CheckLuaArgCount(L, 2, "ShowString"); - lua_Integer screen = luaL_checknumber(L, 1); - const char* text = lua_tostring(L, 2); - - ShowStringF(GetScreenFromIndex(screen), "%s", text); - return 0; -} - -static int UI_WordWrapString(lua_State* L) { - size_t len; - int isnum; - int llen; - int top = lua_gettop(L); - if (top == 1) { - llen = 0; // WordWrapString will automatically wrap it for the main screen - } else if (top == 2) { - llen = lua_tointegerx(L, 2, &isnum); - if (llen == -1) { - // special case for "word wrap for alt screen" - llen = (SCREEN_WIDTH_ALT / GetFontWidth()); - } - } else { - return luaL_error(L, "bad number of arguments passed to WordWrapString (expected 1 or 2, got %d", top); - } - const char* text = lua_tolstring(L, 1, &len); - char* buf = malloc(len + 1); - strlcpy(buf, text, len + 1); - WordWrapString(buf, llen); - lua_pushlstring(L, buf, len); - free(buf); - return 1; -} - -static int UI_ClearScreen(lua_State* L) { - bool which_screen = luaL_checknumber(L, 1); - u32 color = lua_tointeger(L, 2); - ClearScreen(GetScreenFromIndex(which_screen), color); - return 0; -} - -static int UI_ShowSelectPrompt(lua_State* L) { - CheckLuaArgCount(L, 2, "ShowSelectPrompt"); - const char* text = lua_tostring(L, 2); - char* options[MAXOPTIONS]; - const char* tmpstr; - size_t len; - int i; - - luaL_argcheck(L, lua_istable(L, 1), 1, "table expected"); - - lua_Integer opttablesize = luaL_len(L, 1); - luaL_argcheck(L, opttablesize <= MAXOPTIONS, 1, "more than " MAXOPTIONS_STR " options given"); - for (i = 0; i < opttablesize; i++) { - lua_geti(L, 1, i + 1); - tmpstr = lua_tolstring(L, -1, &len); - options[i] = malloc(len + 1); - strlcpy(options[i], tmpstr, len + 1); - lua_pop(L, 1); - } - int result = ShowSelectPrompt(opttablesize, (const char**)options, "%s", text); - for (i = 0; i < opttablesize; i++) free(options[i]); - // lua only treats "false" and "nil" as false values - // so to make this easier, return nil and not 0 if no choice was made - // https://www.lua.org/manual/5.4/manual.html#3.3.4 - if (result) - lua_pushinteger(L, result); - else - lua_pushnil(L); - return 1; -} - -static int UI_ShowProgress(lua_State* L) { - CheckLuaArgCount(L, 3, "ShowProgress"); - u64 current = lua_tointeger(L, 1); - u64 total = lua_tointeger(L, 2); - const char* optstr = lua_tostring(L, 3); - - bool result = ShowProgress(current, total, optstr); - lua_pushboolean(L, result); - return 1; -} - -static int UI_DrawString(lua_State* L) { - int which_screen = lua_tointeger(L, 1); - const char* text = lua_tostring(L, 2); - int x = lua_tointeger(L, 3); - int y = lua_tointeger(L, 4); - u32 color = lua_tointeger(L, 5); - u32 bgcolor = lua_tointeger(L, 6); - u16* screen = GetScreenFromIndex(which_screen); - DrawString(screen, text, x, y, color, bgcolor); - return 0; -} - -static int UIGlobal_Print(lua_State* L) { - //const char* text = lua_tostring(L, 1); - char buf[OUTPUTMAXCHARSPERLINE] = {0}; - int argcount = lua_gettop(L); - for (int i = 0; i < lua_gettop(L); i++) { - const char* str = luaL_tolstring(L, i+1, NULL); - if (str) { - strlcat(buf, str, OUTPUTMAXCHARSPERLINE); - lua_pop(L, 1); - } else { - // idk - strlcat(buf, "(unknown)", OUTPUTMAXCHARSPERLINE); - } - if (i < argcount) strlcat(buf, " ", OUTPUTMAXCHARSPERLINE); - } - ShiftOutputBufferUp(); - WriteToOutputBuffer((char*)buf); - RenderOutputBuffer(); - return 0; -} - -static const luaL_Reg UIlib[] = { - {"ShowPrompt", UI_ShowPrompt}, - {"AskPrompt", UI_AskPrompt}, - {"ShowString", UI_ShowString}, - {"WordWrapString", UI_WordWrapString}, - {"ClearScreen", UI_ClearScreen}, - {"ShowSelectPrompt", UI_ShowSelectPrompt}, - {"ShowKeyboardOrPrompt", UI_ShowKeyboardOrPrompt}, - {"ShowNumberPrompt", UI_ShowNumberPrompt}, - {"ShowHexPrompt", UI_ShowHexPrompt}, - {"ShowProgress", UI_ShowProgress}, - {"DrawString", UI_DrawString}, - {"ShowPNG", UI_ShowPNG}, - {"DrawPNG", UI_DrawPNG}, - {NULL, NULL} -}; - -static const luaL_Reg UIGlobalLib[] = { - {"print", UIGlobal_Print}, - {NULL, NULL} -}; - -static const EnumItem Enum_UI[] = { - {"MainScreen", 0}, - {"AltScreen", 1}, - {"TopScreen", 2}, - {"BottomScreen", 3}, - {NULL, 0} -}; - -int gm9lua_open_UI(lua_State* L) { - luaL_newlib(L, UIlib); - AddLuaEnumItems(L, "UI", Enum_UI); - lua_pushglobaltable(L); // push global table to stack - luaL_setfuncs(L, UIGlobalLib, 0); // set global funcs - lua_pop(L, 1); // pop global table from stack - return 1; -} -#endif diff --git a/arm9/source/lua/gm9ui.h b/arm9/source/lua/gm9ui.h deleted file mode 100644 index d1876ab26..000000000 --- a/arm9/source/lua/gm9ui.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include "gm9lua.h" -#include "ui.h" -#include "fs.h" -#include "png.h" -#include "swkbd.h" - -#define GM9LUA_UILIBNAME "UI" - -void ShiftOutputBufferUp(void); -void ClearOutputBuffer(void); -void RenderOutputBuffer(void); -void WriteToOutputBuffer(char* text); -int gm9lua_open_UI(lua_State* L); From e3677685fd0fa1a6902be70338c9211e97f94af5 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 14:38:38 -0600 Subject: [PATCH 050/124] add nix flake for building --- firmtool.nix | 20 ++++++++++++++ flake.lock | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 31 +++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 firmtool.nix create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/firmtool.nix b/firmtool.nix new file mode 100644 index 000000000..9d519baae --- /dev/null +++ b/firmtool.nix @@ -0,0 +1,20 @@ +{ lib, buildPythonPackage, fetchFromGitHub, pythonOlder, setuptools, pip, pycryptodome }: + +buildPythonPackage rec { + pname = "firmtool"; + version = "1.4"; + format = "setuptools"; + + disabled = pythonOlder "3.2"; + + src = fetchFromGitHub { + owner = "TuxSH"; + repo = "firmtool"; + rev = "v${version}"; + hash = "sha256-7fvMeHbbkOEIutLiZt+zU8ZNBgrX6WRq66NIOyDgRV0="; + }; + + propagatedBuildInputs = [ setuptools pip pycryptodome ]; + + pythonImportsCheck = [ "firmtool" ]; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..c97277f3a --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "devkitNix": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1731803694, + "narHash": "sha256-WfYXkVaJzqnuP3S7AF3O+Ee6Mrwf3bvrMAW8sZ3YTPQ=", + "owner": "ihaveamac", + "repo": "devkitNix", + "rev": "2be1d71b669d36dd94c8ade93704069819f393a8", + "type": "github" + }, + "original": { + "owner": "ihaveamac", + "repo": "devkitNix", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1671877799, + "narHash": "sha256-jjC0NtPOT4huSwyichdrKHVCjuGr1al7Wu6PcHo5XZs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8351f271f85dae1ee28269028acde661e60394dd", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1731890469, + "narHash": "sha256-D1FNZ70NmQEwNxpSSdTXCSklBH1z2isPR84J6DQrJGs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5083ec887760adfe12af64830a66807423a859a7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devkitNix": "devkitNix", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..d5e59a429 --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + devkitNix.url = "github:ihaveamac/devkitNix"; + }; + + outputs = { self, nixpkgs, devkitNix }: { + packages.x86_64-linux = let + pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ devkitNix.overlays.default ]; }; + in rec { + godmode9 = pkgs.stdenvNoCC.mkDerivation rec { + pname = "GodMode9"; + version = "unstable"; + src = builtins.path { path = ./.; name = "GodMode9"; }; + + nativeBuildInputs = with pkgs.python3Packages; [ + python + ( callPackage ./firmtool.nix { } ) + ]; + + preBuild = pkgs.devkitNix.devkitARM.shellHook; + + installPhase = '' + mkdir $out + cp output/* $out + ''; + }; + default = godmode9; + }; + }; +} From 884730f42d8b683cbf14417d2d3f8c3f656ab01c Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 14:44:15 -0600 Subject: [PATCH 051/124] add various lua functions, some of them taken from the old attempt but with new names --- arm9/source/lua/gm9lua.c | 4 + arm9/source/lua/gm9sys.c | 44 ++++++++ arm9/source/lua/gm9sys.h | 6 ++ arm9/source/lua/gm9ui.c | 215 +++++++++++++++++++++++++++++++++++++++ arm9/source/lua/gm9ui.h | 14 +++ 5 files changed, 283 insertions(+) create mode 100644 arm9/source/lua/gm9sys.c create mode 100644 arm9/source/lua/gm9sys.h create mode 100644 arm9/source/lua/gm9ui.c create mode 100644 arm9/source/lua/gm9ui.h diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index fab88c64e..520dc51b9 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -8,6 +8,8 @@ #include "gm9enum.h" #include "gm9loader.h" #include "gm9os.h" +#include "gm9sys.h" +#include "gm9ui.h" #define DEBUGSP ShowPrompt @@ -80,6 +82,8 @@ static const luaL_Reg gm9lualibs[] = { // gm9 custom {GM9LUA_OSLIBNAME, gm9lua_open_os}, + {GM9LUA_SYSLIBNAME, gm9lua_open_sys}, + {GM9LUA_UILIBNAME, gm9lua_open_ui}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9sys.c b/arm9/source/lua/gm9sys.c new file mode 100644 index 000000000..b516bad64 --- /dev/null +++ b/arm9/source/lua/gm9sys.c @@ -0,0 +1,44 @@ +#ifndef NO_LUA +#include "gm9sys.h" +#include "bootfirm.h" +#include "fs.h" +#include "pxi.h" +#include "game.h" + +static int sys_boot(lua_State* L) { + CheckLuaArgCount(L, 1, "sys.boot"); + const char* path = lua_tostring(L, 1); + + u8* firm = (u8*) malloc(FIRM_MAX_SIZE); + if (!firm) { + return luaL_error(L, STR_SCRIPTERR_OUT_OF_MEMORY); + } + + size_t firm_size = FileGetData(path, firm, FIRM_MAX_SIZE, 0); + if (!(firm_size && IsBootableFirm(firm, firm_size))) { + return luaL_error(L, STR_SCRIPTERR_NOT_A_BOOTABLE_FIRM); + } + + char fixpath[256] = { 0 }; + if ((*path == '0') || (*path == '1')) + snprintf(fixpath, sizeof(fixpath), "%s%s", (*path == '0') ? "sdmc" : "nand", path + 1); + else strncpy(fixpath, path, 256); + fixpath[255] = '\0'; + DeinitExtFS(); + DeinitSDCardFS(); + PXI_DoCMD(PXICMD_LEGACY_BOOT, NULL, 0); + PXI_Barrier(PXI_FIRMLAUNCH_BARRIER); + BootFirm((FirmHeader*)(void*)firm, fixpath); + while(1); +} + +static const luaL_Reg sys_lib[] = { + {"boot", sys_boot}, + {NULL, NULL} +}; + +int gm9lua_open_sys(lua_State* L) { + luaL_newlib(L, sys_lib); + return 1; +} +#endif diff --git a/arm9/source/lua/gm9sys.h b/arm9/source/lua/gm9sys.h new file mode 100644 index 000000000..5a82fda00 --- /dev/null +++ b/arm9/source/lua/gm9sys.h @@ -0,0 +1,6 @@ +#pragma once +#include "gm9lua.h" + +#define GM9LUA_SYSLIBNAME "sys" + +int gm9lua_open_sys(lua_State* L); diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c new file mode 100644 index 000000000..de94a9f6a --- /dev/null +++ b/arm9/source/lua/gm9ui.c @@ -0,0 +1,215 @@ +#ifndef NO_LUA +#include "gm9ui.h" + +#define MAXOPTIONS 256 +#define MAXOPTIONS_STR "256" + +#define OUTPUTMAXLINES 24 +#define OUTPUTMAXCHARSPERLINE 51 // make sure this includes space for '\0' + +// this output buffer stuff is especially a test, it needs to take into account newlines and fonts that are not 8x10 + +char output_buffer[OUTPUTMAXLINES][OUTPUTMAXCHARSPERLINE]; // hold 24 lines + +void ShiftOutputBufferUp(void) { + for (int i = 0; i < OUTPUTMAXLINES - 1; i++) { + memcpy(output_buffer[i], output_buffer[i + 1], OUTPUTMAXCHARSPERLINE); + } +} + +void ClearOutputBuffer(void) { + memset(output_buffer, 0, sizeof(output_buffer)); +} + +void WriteToOutputBuffer(char* text) { + strlcpy(output_buffer[OUTPUTMAXLINES - 1], text, OUTPUTMAXCHARSPERLINE); +} + +void RenderOutputBuffer(void) { + ClearScreenF(false, true, COLOR_STD_BG); + for (int i = 0; i < OUTPUTMAXLINES; i++) { + DrawString(ALT_SCREEN, output_buffer[i], 0, i * 10, COLOR_STD_FONT, COLOR_TRANSPARENT); + } +} + +static int ui_echo(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.echo"); + const char* text = lua_tostring(L, 1); + + ShowPrompt(false, "%s", text); + return 0; +} + +static int ui_ask(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.ask"); + const char* text = lua_tostring(L, 1); + + bool ret = ShowPrompt(true, "%s", text); + lua_pushboolean(L, ret); + return 1; +} + +static int ui_ask_hex(lua_State* L) { + CheckLuaArgCount(L, 3, "ui.ask_hex"); + const char* text = lua_tostring(L, 3); + u64 initial_hex = lua_tonumber(L, 1); + u32 n_digits = lua_tonumber(L, 2); + + u64 ret = ShowHexPrompt(initial_hex, n_digits, "%s", text); + lua_pushnumber(L, ret); + return 1; +} + +static int ui_ask_number(lua_State* L) { + CheckLuaArgCount(L, 2, "ui.ask_number"); + const char* text = lua_tostring(L, 2); + u64 initial_num = lua_tonumber(L, 1); + + u64 ret = ShowNumberPrompt(initial_num, "%s", text); + lua_pushnumber(L, ret); + return 1; + +} + +static int ui_ask_text(lua_State* L) { + CheckLuaArgCount(L, 3, "ui.ask_text"); + const char* prompt = lua_tostring(L, 1); + const char* _initial_text = lua_tostring(L, 2); + u32 initial_text_size = strlen(_initial_text)+1; + char initial_text[initial_text_size]; + snprintf(initial_text, initial_text_size, "%s", _initial_text); + u32 max_size = lua_tonumber(L, 3); + bool result = ShowKeyboardOrPrompt(initial_text, max_size, "%s", prompt); + if (result) + lua_pushstring(L, initial_text); + else + lua_pushnil(L); + return 1; +} + +static int ui_show_png(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.show_png"); + const char* path = lua_tostring(L, 1); + u16 *screen = MAIN_SCREEN; + u16 *bitmap = NULL; + u8* png = (u8*) malloc(SCREEN_SIZE(screen)); + u32 bitmap_width, bitmap_height; + if (png) { + u32 png_size = FileGetData(path, png, SCREEN_SIZE(screen), 0); + if (!png_size) { + free(png); + return luaL_error(L, "Could not read %s", path); + } + if (png_size && png_size < SCREEN_SIZE(screen)) { + bitmap = PNG_Decompress(png, png_size, &bitmap_width, &bitmap_height); + if (!bitmap) { + free(png); + return luaL_error(L, "Invalid PNG file"); + } + } + free(png); + if (!bitmap) { + return luaL_error(L, "PNG too large"); + } else if ((SCREEN_WIDTH(screen) < bitmap_width) || (SCREEN_HEIGHT < bitmap_height)) { + free(bitmap); + return luaL_error(L, "PNG too large"); + } else { + DrawBitmap( + screen, // screen + -1, // x coordinate from argument + -1, // y coordinate from argument + bitmap_width, // width + bitmap_height, // height + bitmap // bitmap + ); + free(bitmap); + } + } + return 0; +} + +static int ui_show_text(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.show_text"); + const char* text = lua_tostring(L, 1); + + ShowStringF(MAIN_SCREEN, "%s", text); + return 0; +} + +static int ui_ask_selection(lua_State* L) { + CheckLuaArgCount(L, 2, "ui.ask_selection"); + const char* text = lua_tostring(L, 2); + char* options[MAXOPTIONS]; + const char* tmpstr; + size_t len; + int i; + + luaL_argcheck(L, lua_istable(L, 1), 1, "table expected"); + + lua_Integer opttablesize = luaL_len(L, 1); + luaL_argcheck(L, opttablesize <= MAXOPTIONS, 1, "more than " MAXOPTIONS_STR " options given"); + for (i = 0; i < opttablesize; i++) { + lua_geti(L, 1, i + 1); + tmpstr = lua_tolstring(L, -1, &len); + options[i] = malloc(len + 1); + strlcpy(options[i], tmpstr, len + 1); + lua_pop(L, 1); + } + int result = ShowSelectPrompt(opttablesize, (const char**)options, "%s", text); + for (i = 0; i < opttablesize; i++) free(options[i]); + // lua only treats "false" and "nil" as false values + // so to make this easier, return nil and not 0 if no choice was made + // https://www.lua.org/manual/5.4/manual.html#3.3.4 + if (result) + lua_pushinteger(L, result); + else + lua_pushnil(L); + return 1; +} + +static int ui_global_print(lua_State* L) { + //const char* text = lua_tostring(L, 1); + char buf[OUTPUTMAXCHARSPERLINE] = {0}; + int argcount = lua_gettop(L); + for (int i = 0; i < lua_gettop(L); i++) { + const char* str = luaL_tolstring(L, i+1, NULL); + if (str) { + strlcat(buf, str, OUTPUTMAXCHARSPERLINE); + lua_pop(L, 1); + } else { + // idk + strlcat(buf, "(unknown)", OUTPUTMAXCHARSPERLINE); + } + if (i < argcount) strlcat(buf, " ", OUTPUTMAXCHARSPERLINE); + } + ShiftOutputBufferUp(); + WriteToOutputBuffer((char*)buf); + RenderOutputBuffer(); + return 0; +} + +static const luaL_Reg ui_lib[] = { + {"echo", ui_echo}, + {"ask_hex", ui_ask_hex}, + {"ask_number", ui_ask_number}, + {"ask_text", ui_ask_text}, + {"ask", ui_ask}, + {"show_png", ui_show_png}, + {"show_text", ui_show_text}, + {"ask_selection", ui_ask_selection}, + {NULL, NULL} +}; + +static const luaL_Reg ui_global_lib[] = { + {"print", ui_global_print}, + {NULL, NULL} +}; + +int gm9lua_open_ui(lua_State* L) { + luaL_newlib(L, ui_lib); + lua_pushglobaltable(L); // push global table to stack + luaL_setfuncs(L, ui_global_lib, 0); // set global funcs + lua_pop(L, 1); // pop global table from stack + return 1; +} +#endif diff --git a/arm9/source/lua/gm9ui.h b/arm9/source/lua/gm9ui.h new file mode 100644 index 000000000..2a50547b1 --- /dev/null +++ b/arm9/source/lua/gm9ui.h @@ -0,0 +1,14 @@ +#pragma once +#include "gm9lua.h" +#include "ui.h" +#include "fs.h" +#include "png.h" +#include "swkbd.h" + +#define GM9LUA_UILIBNAME "ui" + +void ShiftOutputBufferUp(void); +void ClearOutputBuffer(void); +void RenderOutputBuffer(void); +void WriteToOutputBuffer(char* text); +int gm9lua_open_ui(lua_State* L); From 3c3d807209f08560204ea5feabe73a05c705bea2 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 14:56:47 -0600 Subject: [PATCH 052/124] add dev shell to flake.nix --- flake.nix | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index d5e59a429..5636b9193 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,18 @@ }; outputs = { self, nixpkgs, devkitNix }: { + devShells.x86_64-linux.default = let + pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ devkitNix.overlays.default ]; }; + in pkgs.mkShell { + packages = with pkgs; [ + pkgs.devkitNix.devkitARM + python3Packages.python + ( python3Packages.callPackage ./firmtool.nix { } ) + ]; + + inherit (pkgs.devkitNix.devkitARM) shellHook; + }; + packages.x86_64-linux = let pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ devkitNix.overlays.default ]; }; in rec { @@ -13,9 +25,9 @@ version = "unstable"; src = builtins.path { path = ./.; name = "GodMode9"; }; - nativeBuildInputs = with pkgs.python3Packages; [ - python - ( callPackage ./firmtool.nix { } ) + nativeBuildInputs = with pkgs; [ + python3Packages.python + ( python3Packages.callPackage ./firmtool.nix { } ) ]; preBuild = pkgs.devkitNix.devkitARM.shellHook; From bc3a21ba9dec8c33a76389737883dcb7f65c972a Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 15:10:00 -0600 Subject: [PATCH 053/124] remove test lua scripts in root --- test-bigoptlist.lua | 305 ------------------------------------------- test-os.lua | 20 --- test-pngdraw.lua | 3 - test-printenum.lua | 11 -- test-showprompts.lua | 4 - 5 files changed, 343 deletions(-) delete mode 100644 test-bigoptlist.lua delete mode 100644 test-os.lua delete mode 100644 test-pngdraw.lua delete mode 100644 test-printenum.lua delete mode 100644 test-showprompts.lua diff --git a/test-bigoptlist.lua b/test-bigoptlist.lua deleted file mode 100644 index bba47ef1e..000000000 --- a/test-bigoptlist.lua +++ /dev/null @@ -1,305 +0,0 @@ -games = {"Half-Life 2", "Half-Life 2: Episode One", "Half-Life 2: Episode Two", "Team Fortress 2"} -maps = { - { - "background01", - "background02", - "background03", - "background04", - "background05", - "background06", - "background07", - "credits", - "d1_canals_01a", - "d1_canals_01", - "d1_canals_02", - "d1_canals_03", - "d1_canals_05", - "d1_canals_06", - "d1_canals_07", - "d1_canals_08", - "d1_canals_09", - "d1_canals_10", - "d1_canals_11", - "d1_canals_12", - "d1_canals_13", - "d1_eli_01", - "d1_eli_02", - "d1_town_01a", - "d1_town_01", - "d1_town_02a", - "d1_town_02", - "d1_town_03", - "d1_town_04", - "d1_town_05", - "d1_trainstation_01", - "d1_trainstation_02", - "d1_trainstation_03", - "d1_trainstation_04", - "d1_trainstation_05", - "d1_trainstation_06", - "d2_coast_01", - "d2_coast_02", - "d2_coast_03", - "d2_coast_04", - "d2_coast_05", - "d2_coast_07", - "d2_coast_08", - "d2_coast_09", - "d2_coast_10", - "d2_coast_11", - "d2_coast_12", - "d2_prison_01", - "d2_prison_02", - "d2_prison_03", - "d2_prison_04", - "d2_prison_05", - "d2_prison_06", - "d2_prison_07", - "d2_prison_08", - "d3_breen_01", - "d3_c17_01", - "d3_c17_02", - "d3_c17_02_camera", - "d3_c17_03", - "d3_c17_04", - "d3_c17_05", - "d3_c17_06a", - "d3_c17_06b", - "d3_c17_07", - "d3_c17_08", - "d3_c17_09", - "d3_c17_10a", - "d3_c17_10b", - "d3_c17_11", - "d3_c17_12b", - "d3_c17_12", - "d3_c17_13", - "d3_citadel_01", - "d3_citadel_02", - "d3_citadel_03", - "d3_citadel_04", - "d3_citadel_05", - "intro" - }, - { - "credits", - "ep1_background01", - "ep1_background01a", - "ep1_background02", - "ep1_c17_00", - "ep1_c17_00a", - "ep1_c17_01", - "ep1_c17_01a", - "ep1_c17_02", - "ep1_c17_02a", - "ep1_c17_02b", - "ep1_c17_05", - "ep1_c17_06", - "ep1_citadel_00", - "ep1_citadel_00_demo", - "ep1_citadel_01", - "ep1_citadel_02", - "ep1_citadel_02b", - "ep1_citadel_03", - "ep1_citadel_04" - }, - { - "ep2_background01", - "ep2_background02a", - "ep2_background02", - "ep2_background03", - "ep2_outland_01a", - "ep2_outland_01", - "ep2_outland_02", - "ep2_outland_03", - "ep2_outland_04", - "ep2_outland_05", - "ep2_outland_06a", - "ep2_outland_06", - "ep2_outland_07", - "ep2_outland_08", - "ep2_outland_09", - "ep2_outland_10a", - "ep2_outland_10", - "ep2_outland_11a", - "ep2_outland_11b", - "ep2_outland_11", - "ep2_outland_12a", - "ep2_outland_12" - }, - { - "arena_badlands", - "arena_byre", - "arena_granary", - "arena_lumberyard", - "arena_lumberyard_event", - "arena_nucleus", - "arena_offblast_final", - "arena_ravine", - "arena_sawmill", - "arena_watchtower", - "arena_well", - "background01", - "cp_5gorge", - "cp_altitude", - "cp_ambush_event", - "cp_badlands", - "cp_cloak", - "cp_coldfront", - "cp_degrootkeep", - "cp_dustbowl", - "cp_egypt_final", - "cp_fastlane", - "cp_foundry", - "cp_freight_final1", - "cp_frostwatch", - "cp_gorge", - "cp_gorge_event", - "cp_granary", - "cp_gravelpit", - "cp_gravelpit_snowy", - "cp_gullywash_final1", - "cp_hardwood_final", - "cp_junction_final", - "cp_manor_event", - "cp_mercenarypark", - "cp_metalworks", - "cp_mossrock", - "cp_mountainlab", - "cp_powerhouse", - "cp_process_final", - "cp_reckoner", - "cp_snakewater_final1", - "cp_snowplow", - "cp_spookeyridge", - "cp_standin_final", - "cp_steel", - "cp_sulfur", - "cp_sunshine", - "cp_sunshine_event", - "cp_vanguard", - "cp_well", - "cp_yukon_final", - "ctf_2fort", - "ctf_2fort_invasion", - "ctf_crasher", - "ctf_doublecross", - "ctf_doublecross_snowy", - "ctf_foundry", - "ctf_frosty", - "ctf_gorge", - "ctf_hellfire", - "ctf_helltrain_event", - "ctf_landfall", - "ctf_pelican_peak", - "ctf_sawmill", - "ctf_snowfall_final", - "ctf_thundermountain", - "ctf_turbine", - "ctf_well", - "itemtest", - "koth_badlands", - "koth_bagel_event", - "koth_brazil", - "koth_cascade", - "koth_harvest_event", - "koth_harvest_final", - "koth_highpass", - "koth_king", - "koth_lakeside_event", - "koth_lakeside_final", - "koth_lazarus", - "koth_los_muertos", - "koth_maple_ridge_event", - "koth_megalo", - "koth_moonshine_event", - "koth_nucleus", - "koth_probed", - "koth_rotunda", - "koth_sawmill", - "koth_sawmill_event", - "koth_sharkbay", - "koth_slasher", - "koth_slaughter_event", - "koth_suijin", - "koth_synthetic_event", - "koth_undergrove_event", - "koth_viaduct", - "koth_viaduct_event", - "mvm_bigrock", - "mvm_coaltown", - "mvm_decoy", - "mvm_ghost_town", - "mvm_mannhattan", - "mvm_mannworks", - "mvm_rottenburg", - "pass_brickyard", - "pass_district", - "pass_timbertown", - "pd_cursed_cove_event", - "pd_farmageddon", - "pd_monster_bash", - "pd_pit_of_death_event", - "pd_selbyen", - "pd_snowville_event", - "pd_watergate", - "pl_badwater", - "pl_barnblitz", - "pl_bloodwater", - "pl_borneo", - "pl_breadspace", - "pl_cactuscanyon", - "pl_cashworks", - "pl_chilly", - "pl_coal_event", - "pl_enclosure_final", - "pl_fifthcurve_event", - "pl_frontier_final", - "pl_frostcliff", - "pl_goldrush", - "pl_hasslecastle", - "pl_hoodoo_final", - "pl_millstone_event", - "pl_phoenix", - "pl_pier", - "pl_precipice_event_final", - "pl_rumble_event", - "pl_rumford_event", - "pl_sludgepit_event", - "pl_snowycoast", - "pl_swiftwater_final1", - "pl_terror_event", - "pl_thundermountain", - "pl_upward", - "pl_venice", - "pl_wutville_event", - "plr_bananabay", - "plr_hacksaw_event", - "plr_hightower", - "plr_hightower_event", - "plr_nightfall_final", - "plr_pipeline", - "rd_asteroid", - "sd_doomsday", - "sd_doomsday_event", - "tc_hydro", - "tr_dustbowl", - "tr_target", - "vsh_distillery", - "vsh_nucleus", - "vsh_skirmish", - "vsh_tinyrock" - } -} - -while true do - gameresult = UI.ShowSelectPrompt(games, "Choose a game!") - if not gameresult then return end - - curmaps = maps[gameresult] - mapresult = UI.ShowSelectPrompt(curmaps, "Choose a map!") - if mapresult then - UI.ShowPrompt(UI.WordWrapString("You chose the game "..games[gameresult].." and the map "..curmaps[mapresult])) - print(games[gameresult]..' - '..curmaps[mapresult]) - end -end diff --git a/test-os.lua b/test-os.lua deleted file mode 100644 index 0efe5e4e4..000000000 --- a/test-os.lua +++ /dev/null @@ -1,20 +0,0 @@ -function PrintTable(tbl) - for k, v in pairs(tbl) do - print(k, v) - end -end -print("First clock " .. string.format("%.8f", os.clock())) -print(os.time()) -print(os.time({10, 10, 10, 10, 10, 10})) -print(os.date()) -print(os.date("%x - %I:%M%p")) - -print("Second clock " .. string.format("%.8f", os.clock())) - - -PrintTable(os.date("*t")) -print("") -PrintTable(os.date("*t", 1286705410)) -print("") -print(os.date("%c hehe", 1286705410)) -UI.ShowPrompt("Press A") diff --git a/test-pngdraw.lua b/test-pngdraw.lua deleted file mode 100644 index 8330d6c5a..000000000 --- a/test-pngdraw.lua +++ /dev/null @@ -1,3 +0,0 @@ -UI.ShowPNG(1, "0:/art.png") -UI.DrawPNG(1, 2, 2, "0:/art.png") -UI.ShowPrompt("Press A") \ No newline at end of file diff --git a/test-printenum.lua b/test-printenum.lua deleted file mode 100644 index 812e308f8..000000000 --- a/test-printenum.lua +++ /dev/null @@ -1,11 +0,0 @@ -function PrintTable(tbl) - for k, v in pairs(tbl) do - print(k, v) - end -end - -print('-- Enum --') -PrintTable(Enum) -print('-- Enum.UI --') -PrintTable(Enum.UI) -UI.ShowPrompt("Done") diff --git a/test-showprompts.lua b/test-showprompts.lua deleted file mode 100644 index e24401ef5..000000000 --- a/test-showprompts.lua +++ /dev/null @@ -1,4 +0,0 @@ -UI.AskPrompt(UI.ShowKeyboardOrPrompt("Hi", 16, "Enter text:")) -UI.AskPrompt(UI.ShowHexPrompt(0xF, 16, "Enter hex:")) -UI.AskPrompt(UI.ShowNumberPrompt(53, "Enter number:")) -UI.ShowPrompt("Press A") From 7ab66286403acb15b2a0da26c7bd9b0bb0a924be Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 15:15:25 -0600 Subject: [PATCH 054/124] add test lua scripts in data/luascripts --- data/luascripts/boot.lua | 4 ++++ data/luascripts/testing.lua | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 data/luascripts/boot.lua create mode 100644 data/luascripts/testing.lua diff --git a/data/luascripts/boot.lua b/data/luascripts/boot.lua new file mode 100644 index 000000000..67efb2335 --- /dev/null +++ b/data/luascripts/boot.lua @@ -0,0 +1,4 @@ +local lumafirm = "0:/luma.firm" + +ui.echo("I'm gonna boot "..lumafirm.."!!!") +sys.boot(lumafirm) diff --git a/data/luascripts/testing.lua b/data/luascripts/testing.lua new file mode 100644 index 000000000..2bf70e462 --- /dev/null +++ b/data/luascripts/testing.lua @@ -0,0 +1,3 @@ +ui.echo("a") +print("a") +ui.echo("b") From 0787d03921103c143b48a4f6f4d5f0e2c3389748 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 19:47:15 -0600 Subject: [PATCH 055/124] add a whole bunch more lua functions and stuff --- arm9/source/lua/gm9fs.c | 151 +++++++++++++++++++++++++++++++++++++++ arm9/source/lua/gm9fs.h | 7 ++ arm9/source/lua/gm9lua.c | 5 ++ arm9/source/lua/gm9ui.c | 34 +++++---- 4 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 arm9/source/lua/gm9fs.c create mode 100644 arm9/source/lua/gm9fs.h diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c new file mode 100644 index 000000000..b9d564978 --- /dev/null +++ b/arm9/source/lua/gm9fs.c @@ -0,0 +1,151 @@ +#ifndef NO_LUA +#include "gm9fs.h" + +static void CreateStatTable(lua_State* L, FILINFO* fno) { + lua_createtable(L, 0, 4); // create nested table + lua_pushstring(L, fno->fname); + lua_setfield(L, -2, "name"); + lua_pushstring(L, (fno->fattrib & AM_DIR) ? "dir" : "file"); + lua_setfield(L, -2, "type"); + lua_pushinteger(L, fno->fsize); + lua_setfield(L, -2, "size"); + lua_pushboolean(L, fno->fattrib & AM_RDO); + lua_setfield(L, -2, "read_only"); + // ... and leave this table on the stack for the caller to deal with +} + +static int fs_list_dir(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.list_dir"); + const char* path = luaL_checkstring(L, 1); + lua_newtable(L); + + DIR dir; + FILINFO fno; + + FRESULT res = fvx_opendir(&dir, path); + if (res != FR_OK) { + lua_pop(L, 1); // remove final table from stack + return luaL_error(L, "could not opendir %s (%u)", path, res); + } + + for (int i = 1; true; i++) { + res = fvx_readdir(&dir, &fno); + if (res != FR_OK) { + lua_pop(L, 1); // remove final table from stack + return luaL_error(L, "could not readdir %s (%u)", path, res); + } + if (fno.fname[0] == 0) break; + CreateStatTable(L, &fno); + lua_seti(L, -2, i); // add nested table to final table + } + + return 1; +} + +static int fs_stat(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.stat"); + const char* path = luaL_checkstring(L, 1); + FILINFO fno; + + FRESULT res = fvx_stat(path, &fno); + if (res != FR_OK) { + return luaL_error(L, "could not stat %s (%u)", path, res); + } + CreateStatTable(L, &fno); + return 1; +} + +static int fs_read_file(lua_State* L) { + CheckLuaArgCount(L, 3, "fs.read_file"); + const char* path = luaL_checkstring(L, 1); + lua_Integer offset = luaL_checkinteger(L, 2); + lua_Integer size = luaL_checkinteger(L, 3); + + char *buf = malloc(size); + if (!buf) { + return luaL_error(L, "could not allocate memory to read file"); + } + UINT bytes_read = 0; + FRESULT res = fvx_qread(path, buf, offset, size, &bytes_read); + if (res != FR_OK) { + free(buf); + return luaL_error(L, "could not read %s (%u)", path, res); + } + lua_pushlstring(L, buf, bytes_read); + free(buf); + return 1; +} + +static int fs_write_file(lua_State* L) { + CheckLuaArgCount(L, 3, "fs.write_file"); + const char* path = luaL_checkstring(L, 1); + lua_Integer offset = luaL_checkinteger(L, 2); + size_t data_length = 0; + const char* data = luaL_checklstring(L, 3, &data_length); + + bool allowed = CheckWritePermissions(path); + if (!allowed) { + return luaL_error(L, "writing not allowed: %s", path); + } + + UINT bytes_written = 0; + FRESULT res = fvx_qwrite(path, data, offset, data_length, &bytes_written); + if (res != FR_OK) { + return luaL_error(L, "error writing %s (%u)", path, res); + } + + lua_pushinteger(L, bytes_written); + return 1; +} + +static int fs_img_mount(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.img_mount"); + const char* path = luaL_checkstring(L, 1); + + bool res = InitImgFS(path); + if (!res) { + return luaL_error(L, "failed to mount %s", path); + } + + return 0; +} + +static int fs_img_umount(lua_State* L) { + CheckLuaArgCount(L, 0, "fs.img_umount"); + + InitImgFS(NULL); + + return 0; +} + +static int fs_get_img_mount(lua_State* L) { + CheckLuaArgCount(L, 0, "fs.img_umount"); + + char path[256] = { 0 }; + strncpy(path, GetMountPath(), 256); + if (path[0] == 0) { + // since lua treats "" as true, return a nil to make if/else easier + lua_pushnil(L); + } else { + lua_pushstring(L, path); + } + + return 1; +} + +static const luaL_Reg fs_lib[] = { + {"list_dir", fs_list_dir}, + {"stat", fs_stat}, + {"read_file", fs_read_file}, + {"write_file", fs_write_file}, + {"img_mount", fs_img_mount}, + {"img_umount", fs_img_umount}, + {"get_img_mount", fs_get_img_mount}, + {NULL, NULL} +}; + +int gm9lua_open_fs(lua_State* L) { + luaL_newlib(L, fs_lib); + return 1; +} +#endif diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h new file mode 100644 index 000000000..de4d86fdf --- /dev/null +++ b/arm9/source/lua/gm9fs.h @@ -0,0 +1,7 @@ +#pragma once +#include "gm9lua.h" +#include "fs.h" + +#define GM9LUA_FSLIBNAME "fs" + +int gm9lua_open_fs(lua_State* L); diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 520dc51b9..91fd7c5f0 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -7,6 +7,7 @@ #include "fsutil.h" #include "gm9enum.h" #include "gm9loader.h" +#include "gm9fs.h" #include "gm9os.h" #include "gm9sys.h" #include "gm9ui.h" @@ -81,6 +82,7 @@ static const luaL_Reg gm9lualibs[] = { {LUA_DBLIBNAME, luaopen_debug}, // gm9 custom + {GM9LUA_FSLIBNAME, gm9lua_open_fs}, {GM9LUA_OSLIBNAME, gm9lua_open_os}, {GM9LUA_SYSLIBNAME, gm9lua_open_sys}, {GM9LUA_UILIBNAME, gm9lua_open_ui}, @@ -106,6 +108,9 @@ bool ExecuteLuaScript(const char* path_script) { lua_pushliteral(L, VERSION); lua_setglobal(L, "GM9VERSION"); + lua_pushstring(L, path_script); + lua_setglobal(L, "SCRIPT"); + int result = LoadLuaFile(L, path_script); if (result != LUA_OK) { char errstr[BUFSIZ] = {0}; diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index de94a9f6a..a9748e848 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -51,22 +51,30 @@ static int ui_ask(lua_State* L) { static int ui_ask_hex(lua_State* L) { CheckLuaArgCount(L, 3, "ui.ask_hex"); - const char* text = lua_tostring(L, 3); - u64 initial_hex = lua_tonumber(L, 1); - u32 n_digits = lua_tonumber(L, 2); + const char* text = lua_tostring(L, 1); + u64 initial_hex = lua_tonumber(L, 2); + u32 n_digits = lua_tonumber(L, 3); u64 ret = ShowHexPrompt(initial_hex, n_digits, "%s", text); - lua_pushnumber(L, ret); + if (ret == (u64) -1) { + lua_pushnil(L); + } else { + lua_pushnumber(L, ret); + } return 1; } static int ui_ask_number(lua_State* L) { CheckLuaArgCount(L, 2, "ui.ask_number"); - const char* text = lua_tostring(L, 2); - u64 initial_num = lua_tonumber(L, 1); + const char* text = lua_tostring(L, 1); + u64 initial_num = lua_tonumber(L, 2); u64 ret = ShowNumberPrompt(initial_num, "%s", text); - lua_pushnumber(L, ret); + if (ret == (u64) -1) { + lua_pushnil(L); + } else { + lua_pushnumber(L, ret); + } return 1; } @@ -90,7 +98,7 @@ static int ui_ask_text(lua_State* L) { static int ui_show_png(lua_State* L) { CheckLuaArgCount(L, 1, "ui.show_png"); const char* path = lua_tostring(L, 1); - u16 *screen = MAIN_SCREEN; + u16 *screen = ALT_SCREEN; u16 *bitmap = NULL; u8* png = (u8*) malloc(SCREEN_SIZE(screen)); u32 bitmap_width, bitmap_height; @@ -138,18 +146,18 @@ static int ui_show_text(lua_State* L) { static int ui_ask_selection(lua_State* L) { CheckLuaArgCount(L, 2, "ui.ask_selection"); - const char* text = lua_tostring(L, 2); + const char* text = lua_tostring(L, 1); char* options[MAXOPTIONS]; const char* tmpstr; size_t len; int i; - luaL_argcheck(L, lua_istable(L, 1), 1, "table expected"); + luaL_argcheck(L, lua_istable(L, 2), 2, "table expected"); - lua_Integer opttablesize = luaL_len(L, 1); - luaL_argcheck(L, opttablesize <= MAXOPTIONS, 1, "more than " MAXOPTIONS_STR " options given"); + lua_Integer opttablesize = luaL_len(L, 2); + luaL_argcheck(L, opttablesize <= MAXOPTIONS, 2, "more than " MAXOPTIONS_STR " options given"); for (i = 0; i < opttablesize; i++) { - lua_geti(L, 1, i + 1); + lua_geti(L, 2, i + 1); tmpstr = lua_tolstring(L, -1, &len); options[i] = malloc(len + 1); strlcpy(options[i], tmpstr, len + 1); From fd1cf5037c7f99bfa235b89231bfdfe6cd64cd8d Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 19:47:28 -0600 Subject: [PATCH 056/124] add more test lua scripts --- data/luascripts/asking-test.lua | 13 +++++++++++++ data/luascripts/fstest.lua | 18 ++++++++++++++++++ data/luascripts/mount-test.lua | 9 +++++++++ data/luascripts/read-write-test.lua | 7 +++++++ data/luascripts/selection.lua | 17 +++++++++++++++++ data/luascripts/testing.lua | 8 ++++++++ 6 files changed, 72 insertions(+) create mode 100644 data/luascripts/asking-test.lua create mode 100644 data/luascripts/fstest.lua create mode 100644 data/luascripts/mount-test.lua create mode 100644 data/luascripts/read-write-test.lua create mode 100644 data/luascripts/selection.lua diff --git a/data/luascripts/asking-test.lua b/data/luascripts/asking-test.lua new file mode 100644 index 000000000..f0d1a7172 --- /dev/null +++ b/data/luascripts/asking-test.lua @@ -0,0 +1,13 @@ +local num1 = ui.ask_hex("Gimmie a hex number.", 621, 8) +local num2 = ui.ask_number("Gimmie a normal number.", 123) +local text = ui.ask_text("Enter something!", "initial prompt", 20) + +print("First number", num1) +print("Second number", num2) +print("And the text:", text) + +ui.echo("Cool!!!!") +ui.show_png("0:/ihaveahax.png") +ui.echo("PNG time") +--ui.show_text("testing") +--ui.echo("Text time") diff --git a/data/luascripts/fstest.lua b/data/luascripts/fstest.lua new file mode 100644 index 000000000..2e748cbb5 --- /dev/null +++ b/data/luascripts/fstest.lua @@ -0,0 +1,18 @@ +function printtable(t) + for ik, iv in pairs(t) do + print("", ik, ":", iv) + end +end + +print("Listing V:/") +local vfiles = fs.list_dir("V:/") +print("I have gotten the V:/:", vfiles) +for k, v in pairs(vfiles) do + print("File", k) + printtable(v) +end +ui.echo("Look up there!") + +local bootstat = fs.stat("0:/boot.firm") +print("For that boot firm:") +printtable(bootstat) diff --git a/data/luascripts/mount-test.lua b/data/luascripts/mount-test.lua new file mode 100644 index 000000000..0a138a462 --- /dev/null +++ b/data/luascripts/mount-test.lua @@ -0,0 +1,9 @@ +local mf = "0:/luma.firm" + +print("Mounted:", fs.get_img_mount()) + +print("Mounting:", mf) +fs.img_mount(mf) +print("Mounted:", fs.get_img_mount()) + +ui.echo("There") diff --git a/data/luascripts/read-write-test.lua b/data/luascripts/read-write-test.lua new file mode 100644 index 000000000..62efb1d6c --- /dev/null +++ b/data/luascripts/read-write-test.lua @@ -0,0 +1,7 @@ +local stuff = fs.read_file("0:/boot.firm", 0, 4) +print("I got:", stuff) + +local written = fs.write_file("9:/test.txt", 0, "Yeah!!!") +print("I wrote:", written) + +ui.echo("Waiting...") diff --git a/data/luascripts/selection.lua b/data/luascripts/selection.lua new file mode 100644 index 000000000..04bd21aee --- /dev/null +++ b/data/luascripts/selection.lua @@ -0,0 +1,17 @@ +local options = { + "0:/boot.firm", + "0:/luma.firm", + "1:/boot.firm", + "1:/luma.firm" +} + +local sel = ui.ask_selection("Choose one to boot.", options) +if sel then + local path = options[sel] + local doboot = ui.ask("Boot "..path.."?") + if doboot then + sys.boot(path) + else + ui.echo("I won't boot it then!") + end +end diff --git a/data/luascripts/testing.lua b/data/luascripts/testing.lua index 2bf70e462..f80fdb395 100644 --- a/data/luascripts/testing.lua +++ b/data/luascripts/testing.lua @@ -1,3 +1,11 @@ ui.echo("a") print("a") +print("GM9VERSION: "..GM9VERSION) +print("SCRIPT: "..SCRIPT) ui.echo("b") + +local thingy = sys.testtable() +ui.echo(tostring(thingy)) +for k, v in pairs(thingy) do + ui.echo(tostring(k)..": "..tostring(v)) +end From da0a1bcb14665e1612ffebc781ccbcf2d2c50463 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 19 Nov 2024 22:55:45 -0600 Subject: [PATCH 057/124] add more functions, add preload script, add test io compatibility module --- arm9/source/lua/gm9fs.c | 20 +++- arm9/source/lua/gm9lua.c | 39 +++++--- data/luapackages/io.lua | 159 ++++++++++++++++++++++++++++++++ data/luascripts/datatest.lua | 1 + data/luascripts/global-test.lua | 2 + data/luascripts/io-test.lua | 13 +++ data/preload.lua | 5 + 7 files changed, 220 insertions(+), 19 deletions(-) create mode 100644 data/luapackages/io.lua create mode 100644 data/luascripts/datatest.lua create mode 100644 data/luascripts/global-test.lua create mode 100644 data/luascripts/io-test.lua create mode 100644 data/preload.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index b9d564978..c5e654051 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -25,14 +25,14 @@ static int fs_list_dir(lua_State* L) { FRESULT res = fvx_opendir(&dir, path); if (res != FR_OK) { lua_pop(L, 1); // remove final table from stack - return luaL_error(L, "could not opendir %s (%u)", path, res); + return luaL_error(L, "could not opendir %s (%d)", path, res); } for (int i = 1; true; i++) { res = fvx_readdir(&dir, &fno); if (res != FR_OK) { lua_pop(L, 1); // remove final table from stack - return luaL_error(L, "could not readdir %s (%u)", path, res); + return luaL_error(L, "could not readdir %s (%d)", path, res); } if (fno.fname[0] == 0) break; CreateStatTable(L, &fno); @@ -49,7 +49,7 @@ static int fs_stat(lua_State* L) { FRESULT res = fvx_stat(path, &fno); if (res != FR_OK) { - return luaL_error(L, "could not stat %s (%u)", path, res); + return luaL_error(L, "could not stat %s (%d)", path, res); } CreateStatTable(L, &fno); return 1; @@ -69,7 +69,7 @@ static int fs_read_file(lua_State* L) { FRESULT res = fvx_qread(path, buf, offset, size, &bytes_read); if (res != FR_OK) { free(buf); - return luaL_error(L, "could not read %s (%u)", path, res); + return luaL_error(L, "could not read %s (%d)", path, res); } lua_pushlstring(L, buf, bytes_read); free(buf); @@ -91,7 +91,7 @@ static int fs_write_file(lua_State* L) { UINT bytes_written = 0; FRESULT res = fvx_qwrite(path, data, offset, data_length, &bytes_written); if (res != FR_OK) { - return luaL_error(L, "error writing %s (%u)", path, res); + return luaL_error(L, "error writing %s (%d)", path, res); } lua_pushinteger(L, bytes_written); @@ -133,6 +133,15 @@ static int fs_get_img_mount(lua_State* L) { return 1; } +static int fs_allow(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.img_mount"); + const char* path = luaL_checkstring(L, 1); + + bool allowed = CheckWritePermissions(path); + lua_pushboolean(L, allowed); + return 1; +}; + static const luaL_Reg fs_lib[] = { {"list_dir", fs_list_dir}, {"stat", fs_stat}, @@ -141,6 +150,7 @@ static const luaL_Reg fs_lib[] = { {"img_mount", fs_img_mount}, {"img_umount", fs_img_umount}, {"get_img_mount", fs_get_img_mount}, + {"allow", fs_allow}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 91fd7c5f0..d63b8d6bf 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -98,6 +98,27 @@ static void loadlibs(lua_State* L) { } } +static bool RunFile(lua_State* L, const char* file) { + int result = LoadLuaFile(L, file); + if (result != LUA_OK) { + char errstr[BUFSIZ] = {0}; + strlcpy(errstr, lua_tostring(L, -1), BUFSIZ); + WordWrapString(errstr, 0); + ShowPrompt(false, "Error during loading:\n%s", errstr); + return false; + } + + if (lua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) { + char errstr[BUFSIZ] = {0}; + strlcpy(errstr, lua_tostring(L, -1), BUFSIZ); + WordWrapString(errstr, 0); + ShowPrompt(false, "Error during execution:\n%s", errstr); + return false; + } + + return true; +} + bool ExecuteLuaScript(const char* path_script) { lua_State* L = luaL_newstate(); loadlibs(L); @@ -111,24 +132,14 @@ bool ExecuteLuaScript(const char* path_script) { lua_pushstring(L, path_script); lua_setglobal(L, "SCRIPT"); - int result = LoadLuaFile(L, path_script); - if (result != LUA_OK) { - char errstr[BUFSIZ] = {0}; - strlcpy(errstr, lua_tostring(L, -1), BUFSIZ); - WordWrapString(errstr, 0); - ShowPrompt(false, "Error during loading:\n%s", errstr); + bool result = RunFile(L, "V:/preload.lua"); + if (!result) { + ShowPrompt(false, "A fatal error happened in GodMode9's preload script.\n\nThis is not an error with your code, but with\nGodMode9. Please report it on GitHub."); lua_close(L); return false; } - if (lua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) { - char errstr[BUFSIZ] = {0}; - strlcpy(errstr, lua_tostring(L, -1), BUFSIZ); - WordWrapString(errstr, 0); - ShowPrompt(false, "Error during execution:\n%s", errstr); - lua_close(L); - return false; - } + RunFile(L, path_script); lua_close(L); return true; diff --git a/data/luapackages/io.lua b/data/luapackages/io.lua new file mode 100644 index 000000000..eb019d4d7 --- /dev/null +++ b/data/luapackages/io.lua @@ -0,0 +1,159 @@ +-- This module has not been fully tested against Lua's built-in module. +-- Functionality might be incorrect. + +local io = {} + +local file = {} +file.__index = file + +local function debugf(...) + print("DEBUG:", table.unpack({...})) +end + +local function not_impl(fnname) + return function (...) + error(fnname.." is not implemented") + end +end + +function file.new(filename, mode) + -- TODO: make this more accurate (disallow opening dirs as files) + local success, stat, of, allowed + if mode == nil then + mode = "r" + end + debugf("opening", filename, mode) + of = setmetatable({_filename=filename, _mode=mode, _seek=0, _open=true, _readonly=true}, file) + if string.find(mode, "w") then + debugf("opening", filename, "for writing") + -- preemptively allow writing instead of having that prompt at file:write + allowed = fs.allow(filename) + debugf("allowed:", allowed) + if not allowed then return nil end + of._stat = {} + of._size = 0 + of._readonly = false + elseif string.find(mode, "r+") then + debugf("opening", filename, "for updating") + allowed = fs.allow(filename) + debugf("allowed:", allowed) + if not allowed then return nil end + success, stat = fs.stat(filename) + debugf("stat success:", success) + if success then + debugf("type:", stat.type) + if stat.type == "dir" then return nil end + of._stat = stat + of._size = stat.size + else + of._stat = {} + of._size = 0 + end + else + debugf("opening", filename, "for reading") + -- check if file exists first + success, stat = fs.stat(filename) + debugf("stat success:", success) + -- lua returns nil if it fails to open for some reason + if not success then return nil end + debugf("type:", stat.type) + if stat.type == "dir" then return nil end + of._stat = stat + -- this is so i can adjust the size when data is written + of._size = stat.size + end + debugf("returning of") + return of +end + +function file:_closed_check() + if not self._open then error("attempt to use a closed file") end +end + +function file:close() + self._open = false + return true +end + +function file:flush() + self:_closed_check() + -- nothing happens here +end + +function file:read(...) + self:_closed_check() + local to_return = {} + for i, v in ipairs({...}) do + if v == "n" or v == "l" or v == "L" then + error('mode "'..v..'" is not implemented') + elseif v == "a" then + local btr = self._size - self._seek + local data = fs.read_file(self._filename, self._seek, btr) + self._seek = self._seek + string.len(data) + table.insert(to_return, data) + else + -- assuming this is a number... + local data = fs.read_file(self._filename, self._seek, v) + self._seek = self._seek + string.len(data) + table.insert(to_return, data) + end + end + return table.unpack(to_return) +end + +function file:seek(whence, offset) + if whence == nil then + whence = "cur" + end + if offset == nil then + offset = 0 + end + if type(offset) ~= "number" then + error("bad argument #2 to 'seek' (number expected, got '..type(offset)..')") + end + + if whence == "set" then + self._seek = offset + elseif whence == "cur" then + self._seek = self._seek + offset + elseif whence == "end" then + self._seek = self._size + offset + else + error("bad argument #1 to 'seek' (invalid option '"..tostring(whence).."')") + end + + return self._seek +end + +function file:write(...) + local to_write = '' + for i, v in pairs({...}) do + to_write = to_write..tostring(v) + end + local len = string.len(to_write) + local br = fs.write_file(self._filename, self._seek, to_write) + self._seek = self._seek + br + if self._seek > self._size then + self._size = self._seek + end + return self +end + +file.lines = not_impl("file:lines") +file.setvbuf = not_impl("file:setvbuf") + +function io.open(filename, mode) + return file.new(filename, mode) +end + +io.close = not_impl("io.close") +io.flush = not_impl("io.flush") +io.lines = not_impl("io.lines") +io.output = not_impl("io.output") +io.popen = not_impl("io.popen") +io.read = not_impl("io.read") +io.tmpfile = not_impl("io.tmpfile") +io.type = not_impl("io.type") +io.write = not_impl("io.write") + +return io diff --git a/data/luascripts/datatest.lua b/data/luascripts/datatest.lua new file mode 100644 index 000000000..8b74c16b7 --- /dev/null +++ b/data/luascripts/datatest.lua @@ -0,0 +1 @@ +ui.echo("SUCCESS") diff --git a/data/luascripts/global-test.lua b/data/luascripts/global-test.lua new file mode 100644 index 000000000..3b763830a --- /dev/null +++ b/data/luascripts/global-test.lua @@ -0,0 +1,2 @@ +print("Does it exist?") +ui.echo("Cool") diff --git a/data/luascripts/io-test.lua b/data/luascripts/io-test.lua new file mode 100644 index 000000000..2cb08e4fb --- /dev/null +++ b/data/luascripts/io-test.lua @@ -0,0 +1,13 @@ +print("opening") +local f = io.open("9:/test.txt", "w") +print("i got:", f) +print("writing") +f:write("testing") +print("seeking") +f:seek("set", 0) +print("reading") +local data = f:read(4) +print("the data is:", data) +data = f:read(1) +print("the data is:", data) +ui.echo("Done?") diff --git a/data/preload.lua b/data/preload.lua new file mode 100644 index 000000000..c65ab9d9e --- /dev/null +++ b/data/preload.lua @@ -0,0 +1,5 @@ +-- This file is executed when any Lua script is executed in GodMode9. +-- The purpose of this one is to initialize some variables and modules. +-- If you're looking for an auto-running script, you want "autorun.lua"! + +io = require('io') From a8762ca9f0a1da5458c24a9a2b4bdbda3654d4cc Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 20 Nov 2024 01:20:37 -0600 Subject: [PATCH 058/124] add more functions and test scripts --- arm9/source/lua/gm9fs.c | 82 ++++++++++++++++++++++++++++++++- arm9/source/lua/gm9fs.h | 2 + arm9/source/lua/gm9lua.h | 8 ++++ arm9/source/lua/gm9sys.c | 17 +++++++ arm9/source/lua/gm9ui.c | 2 +- data/luascripts/allow-test.lua | 8 ++++ data/luascripts/exists-test.lua | 5 ++ 7 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 data/luascripts/allow-test.lua create mode 100644 data/luascripts/exists-test.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index c5e654051..b62127557 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -14,6 +14,21 @@ static void CreateStatTable(lua_State* L, FILINFO* fno) { // ... and leave this table on the stack for the caller to deal with } +static u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 allowed_flags) { + char types[8][14] = {"no_cancel", "silent", "hash", "sha1", "skip", "overwrite", "append_all", "all"}; + int types_int[8] = {NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL}; + u32 flags_ext = flags_ext_starter; + + for (int i = 0; i < 8; i++) { + if (!(allowed_flags & types_int[i])) continue; + lua_getfield(L, pos, types[i]); + if (lua_toboolean(L, -1)) flags_ext |= types_int[i]; + lua_pop(L, 1); + } + + return flags_ext; +} + static int fs_list_dir(lua_State* L) { CheckLuaArgCount(L, 1, "fs.list_dir"); const char* path = luaL_checkstring(L, 1); @@ -55,6 +70,44 @@ static int fs_stat(lua_State* L) { return 1; } +static int fs_exists(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.exists"); + const char* path = luaL_checkstring(L, 1); + FILINFO fno; + + FRESULT res = fvx_stat(path, &fno); + lua_pushboolean(L, res == FR_OK); + return 1; +} + +static int fs_is_dir(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.is_dir"); + const char* path = luaL_checkstring(L, 1); + FILINFO fno; + + FRESULT res = fvx_stat(path, &fno); + if (res != FR_OK) { + lua_pushboolean(L, false); + } else { + lua_pushboolean(L, fno.fattrib & AM_DIR); + } + return 1; +} + +static int fs_is_file(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.is_file"); + const char* path = luaL_checkstring(L, 1); + FILINFO fno; + + FRESULT res = fvx_stat(path, &fno); + if (res != FR_OK) { + lua_pushboolean(L, false); + } else { + lua_pushboolean(L, !(fno.fattrib & AM_DIR)); + } + return 1; +} + static int fs_read_file(lua_State* L) { CheckLuaArgCount(L, 3, "fs.read_file"); const char* path = luaL_checkstring(L, 1); @@ -134,17 +187,42 @@ static int fs_get_img_mount(lua_State* L) { } static int fs_allow(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.img_mount"); + int extra = CheckLuaArgCountPlusExtra(L, 1, "fs.img_mount"); const char* path = luaL_checkstring(L, 1); + u32 flags = 0; + bool allowed; + if (extra) { + flags = GetFlagsFromTable(L, 2, 0, ASK_ALL); + } - bool allowed = CheckWritePermissions(path); + if (flags & ASK_ALL) { + allowed = CheckDirWritePermissions(path); + } else { + allowed = CheckWritePermissions(path); + } lua_pushboolean(L, allowed); return 1; }; +static int fs_verify(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.verify"); + const char* path = luaL_checkstring(L, 1); + bool res = true; + + u64 filetype = IdentifyFileType(path); + if (filetype & IMG_NAND) res = (ValidateNandDump(path) == 0); + else res = (VerifyGameFile(path) == 0); + + lua_pushboolean(L, res); + return 1; +} + static const luaL_Reg fs_lib[] = { {"list_dir", fs_list_dir}, {"stat", fs_stat}, + {"exists", fs_exists}, + {"is_dir", fs_is_dir}, + {"is_file", fs_is_file}, {"read_file", fs_read_file}, {"write_file", fs_write_file}, {"img_mount", fs_img_mount}, diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h index de4d86fdf..9c6731626 100644 --- a/arm9/source/lua/gm9fs.h +++ b/arm9/source/lua/gm9fs.h @@ -1,6 +1,8 @@ #pragma once #include "gm9lua.h" #include "fs.h" +#include "ui.h" +#include "utils.h" #define GM9LUA_FSLIBNAME "fs" diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index e7cea910b..97a0a0fe8 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -15,6 +15,14 @@ static inline void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) luaL_error(L, "bad number of arguments passed to '%s' (expected %d, got %d)", cmd, argcount, args); } } +// this is used in cases where a function accepts a flags table or something else +static inline bool CheckLuaArgCountPlusExtra(lua_State* L, int argcount, const char* cmd) { + int args = lua_gettop(L); + if (args != argcount && args != argcount + 1) { + luaL_error(L, "bad number of arguments passed to '%s' (expected %d, got %d or %d)", cmd, argcount, args); + } + return args == argcount + 1; +} int LoadLuaFile(lua_State* L, const char* filename); #endif diff --git a/arm9/source/lua/gm9sys.c b/arm9/source/lua/gm9sys.c index b516bad64..feb4ee8b2 100644 --- a/arm9/source/lua/gm9sys.c +++ b/arm9/source/lua/gm9sys.c @@ -4,6 +4,7 @@ #include "fs.h" #include "pxi.h" #include "game.h" +#include "power.h" static int sys_boot(lua_State* L) { CheckLuaArgCount(L, 1, "sys.boot"); @@ -32,8 +33,24 @@ static int sys_boot(lua_State* L) { while(1); } +static int sys_reboot(lua_State* L) { + CheckLuaArgCount(L, 0, "sys.reboot"); + DeinitExtFS(); + DeinitSDCardFS(); + Reboot(); +} + +static int sys_power_off(lua_State* L) { + CheckLuaArgCount(L, 0, "sys.power_off"); + DeinitExtFS(); + DeinitSDCardFS(); + PowerOff(); +} + static const luaL_Reg sys_lib[] = { {"boot", sys_boot}, + {"reboot", sys_reboot}, + {"power_off", sys_power_off}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index a9748e848..1e74b29dc 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -140,7 +140,7 @@ static int ui_show_text(lua_State* L) { CheckLuaArgCount(L, 1, "ui.show_text"); const char* text = lua_tostring(L, 1); - ShowStringF(MAIN_SCREEN, "%s", text); + ShowStringF(ALT_SCREEN, "%s", text); return 0; } diff --git a/data/luascripts/allow-test.lua b/data/luascripts/allow-test.lua new file mode 100644 index 000000000..7da3e80b5 --- /dev/null +++ b/data/luascripts/allow-test.lua @@ -0,0 +1,8 @@ +print("allow (no table):") +print(fs.allow("1:/")) +print("allow (ask_all=false):") +print(fs.allow("1:/", {ask_all=false})) +print("allow (ask_all=true):") +print(fs.allow("1:/", {ask_all=true})) + +ui.echo("Done") diff --git a/data/luascripts/exists-test.lua b/data/luascripts/exists-test.lua new file mode 100644 index 000000000..16b5df5fd --- /dev/null +++ b/data/luascripts/exists-test.lua @@ -0,0 +1,5 @@ +for i, v in pairs({"0:/boot.firm", "S:/nope.bin"}) do + print(v, "exists", fs.exists(v)) + print(v, "is_dir", fs.is_dir(v)) + print(v, "is_file", fs.is_file(v)) +end From fb61e7d99e9c39540db8e23a768ea500384ae08e Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 20 Nov 2024 14:49:55 -0600 Subject: [PATCH 059/124] more functions and stuff --- arm9/source/lua/gm9fs.c | 46 +++++++++++++++++++++++++++ arm9/source/lua/gm9fs.h | 3 -- arm9/source/lua/gm9ui.c | 12 +++++++ data/luascripts/format-bytes-test.lua | 3 ++ data/luascripts/stat-test.lua | 17 ++++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 data/luascripts/format-bytes-test.lua create mode 100644 data/luascripts/stat-test.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index b62127557..7c502b921 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -1,5 +1,8 @@ #ifndef NO_LUA #include "gm9fs.h" +#include "fs.h" +#include "ui.h" +#include "utils.h" static void CreateStatTable(lua_State* L, FILINFO* fno) { lua_createtable(L, 0, 4); // create nested table @@ -70,6 +73,47 @@ static int fs_stat(lua_State* L) { return 1; } +static int fs_stat_fs(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.stat_fs"); + const char* path = luaL_checkstring(L, 1); + + u64 freespace = GetFreeSpace(path); + u64 totalspace = GetTotalSpace(path); + u64 usedspace = totalspace - freespace; + + lua_createtable(L, 0, 3); + lua_pushinteger(L, freespace); + lua_setfield(L, -2, "free"); + lua_pushinteger(L, totalspace); + lua_setfield(L, -2, "total"); + lua_pushinteger(L, usedspace); + lua_setfield(L, -2, "used"); + + return 1; +} + +static int fs_dir_info(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.stat_fs"); + const char* path = luaL_checkstring(L, 1); + + u64 tsize = 0; + u32 tdirs = 0; + u32 tfiles = 0; + if (!DirInfo(path, &tsize, &tdirs, &tfiles)) { + return luaL_error(L, "error when running DirInfo"); + } + + lua_createtable(L, 0, 3); + lua_pushinteger(L, tsize); + lua_setfield(L, -2, "size"); + lua_pushinteger(L, tdirs); + lua_setfield(L, -2, "dirs"); + lua_pushinteger(L, tfiles); + lua_setfield(L, -2, "files"); + + return 1; +} + static int fs_exists(lua_State* L) { CheckLuaArgCount(L, 1, "fs.exists"); const char* path = luaL_checkstring(L, 1); @@ -220,6 +264,8 @@ static int fs_verify(lua_State* L) { static const luaL_Reg fs_lib[] = { {"list_dir", fs_list_dir}, {"stat", fs_stat}, + {"stat_fs", fs_stat_fs}, + {"dir_info", fs_dir_info}, {"exists", fs_exists}, {"is_dir", fs_is_dir}, {"is_file", fs_is_file}, diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h index 9c6731626..044ce08d5 100644 --- a/arm9/source/lua/gm9fs.h +++ b/arm9/source/lua/gm9fs.h @@ -1,8 +1,5 @@ #pragma once #include "gm9lua.h" -#include "fs.h" -#include "ui.h" -#include "utils.h" #define GM9LUA_FSLIBNAME "fs" diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 1e74b29dc..595d5d19f 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -175,6 +175,17 @@ static int ui_ask_selection(lua_State* L) { return 1; } +static int ui_format_bytes(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.format_bytes"); + lua_Integer size = luaL_checkinteger(L, 1); + + char bytesstr[32] = { 0 }; + FormatBytes(bytesstr, (u64)size); + + lua_pushstring(L, bytesstr); + return 1; +} + static int ui_global_print(lua_State* L) { //const char* text = lua_tostring(L, 1); char buf[OUTPUTMAXCHARSPERLINE] = {0}; @@ -205,6 +216,7 @@ static const luaL_Reg ui_lib[] = { {"show_png", ui_show_png}, {"show_text", ui_show_text}, {"ask_selection", ui_ask_selection}, + {"format_bytes", ui_format_bytes}, {NULL, NULL} }; diff --git a/data/luascripts/format-bytes-test.lua b/data/luascripts/format-bytes-test.lua new file mode 100644 index 000000000..89cf4232b --- /dev/null +++ b/data/luascripts/format-bytes-test.lua @@ -0,0 +1,3 @@ +local sd_stat = fs.stat_fs("0:/") +print("Total size:", ui.format_bytes(sd_stat.total)) +print("Free space:", ui.format_bytes(sd_stat.free)) diff --git a/data/luascripts/stat-test.lua b/data/luascripts/stat-test.lua new file mode 100644 index 000000000..fc502556c --- /dev/null +++ b/data/luascripts/stat-test.lua @@ -0,0 +1,17 @@ +local function printtable(tbl) + for k, v in pairs(tbl) do + print("-", k, ":", v, "(", ui.format_bytes(v), ")") + end +end + +print("fs.stat_fs 0:/") +printtable(fs.stat_fs("0:/")) +print("fs.stat_fs 1:/") +printtable(fs.stat_fs("1:/")) + +print("fs.dir_info 0:/") +printtable(fs.dir_info("0:/")) +print("fs.dir_info 1:/") +printtable(fs.dir_info("1:/")) + +ui.echo("done") From 09d2d44628d3d38d77454e413ca1121819f4988b Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 20 Nov 2024 18:42:23 -0600 Subject: [PATCH 060/124] more functions and stuff --- arm9/source/lua/gm9fs.c | 84 +++++++++++++++++++++++++++++-- arm9/source/lua/gm9fs.h | 24 +++++++++ arm9/source/lua/gm9sys.c | 2 + arm9/source/lua/gm9ui.c | 56 +++++++++++++++++++-- data/luascripts/hash-test.lua | 33 ++++++++++++ data/luascripts/truncate-test.lua | 6 +++ data/luascripts/viewer-test.lua | 10 ++++ 7 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 data/luascripts/hash-test.lua create mode 100644 data/luascripts/truncate-test.lua create mode 100644 data/luascripts/viewer-test.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 7c502b921..b0aa1ff8f 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -18,11 +18,11 @@ static void CreateStatTable(lua_State* L, FILINFO* fno) { } static u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 allowed_flags) { - char types[8][14] = {"no_cancel", "silent", "hash", "sha1", "skip", "overwrite", "append_all", "all"}; - int types_int[8] = {NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL}; + char types[FLAGS_COUNT][14] = { FLAGS_STR }; + int types_int[FLAGS_COUNT] = { FLAGS_CONSTS }; u32 flags_ext = flags_ext_starter; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < FLAGS_COUNT; i++) { if (!(allowed_flags & types_int[i])) continue; lua_getfield(L, pos, types[i]); if (lua_toboolean(L, -1)) flags_ext |= types_int[i]; @@ -195,6 +195,41 @@ static int fs_write_file(lua_State* L) { return 1; } +static int fs_truncate(lua_State* L) { + CheckLuaArgCount(L, 2, "fs.write_file"); + const char* path = luaL_checkstring(L, 1); + lua_Integer size = luaL_checkinteger(L, 2); + FIL fp; + FRESULT res; + + res = f_open(&fp, path, FA_READ | FA_WRITE); + if (res != FR_OK) { + return luaL_error(L, "failed to open %s (note: this only works on FAT filesystems, not virtual)", path); + } + + // this check is *after* opening so the error happens on virtual filesystems sooner + bool allowed = CheckWritePermissions(path); + if (!allowed) { + f_close(&fp); + return luaL_error(L, "writing not allowed: %s", path); + } + + res = f_lseek(&fp, size); + if (res != FR_OK) { + f_close(&fp); + return luaL_error(L, "failed to seek on %s", path); + } + + res = f_truncate(&fp); + if (res != FR_OK) { + f_close(&fp); + return luaL_error(L, "failed to truncate %s", path); + } + + f_close(&fp); + return 0; +} + static int fs_img_mount(lua_State* L) { CheckLuaArgCount(L, 1, "fs.img_mount"); const char* path = luaL_checkstring(L, 1); @@ -230,8 +265,47 @@ static int fs_get_img_mount(lua_State* L) { return 1; } +static int fs_hash_file(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 3, "fs.hash_file"); + const char* path = luaL_checkstring(L, 1); + lua_Integer offset = luaL_checkinteger(L, 2); + lua_Integer size = luaL_checkinteger(L, 3); + FRESULT res; + FILINFO fno; + + u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; + u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; + + if (size == 0) { + res = fvx_stat(path, &fno); + if (res != FR_OK) { + return luaL_error(L, "failed to stat %s", path); + } + + size = fno.fsize; + } + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 4, flags, USE_SHA1); + } + + const u8 hashlen = (flags & USE_SHA1) ? 20 : 32; + u8 hash_fil[0x20]; + + if (size == 0) { + // shortcut by just returning the hash of empty data + memcpy(hash_fil, (flags & USE_SHA1) ? no_data_hash_1 : no_data_hash_256, hashlen); + } else if (!(FileGetSha(path, hash_fil, offset, size, (flags & USE_SHA1)))) { + return luaL_error(L, "FileGetSha failed on %s", path); + } + + lua_pushlstring(L, (char*)hash_fil, hashlen); + return 1; +} + static int fs_allow(lua_State* L) { - int extra = CheckLuaArgCountPlusExtra(L, 1, "fs.img_mount"); + bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.img_mount"); const char* path = luaL_checkstring(L, 1); u32 flags = 0; bool allowed; @@ -271,9 +345,11 @@ static const luaL_Reg fs_lib[] = { {"is_file", fs_is_file}, {"read_file", fs_read_file}, {"write_file", fs_write_file}, + {"truncate", fs_truncate}, {"img_mount", fs_img_mount}, {"img_umount", fs_img_umount}, {"get_img_mount", fs_get_img_mount}, + {"hash_file", fs_hash_file}, {"allow", fs_allow}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h index 044ce08d5..76abed512 100644 --- a/arm9/source/lua/gm9fs.h +++ b/arm9/source/lua/gm9fs.h @@ -3,4 +3,28 @@ #define GM9LUA_FSLIBNAME "fs" +// this should probably go in filesys/fsutil.h +#define RECURSIVE (1UL<<11) + +#define FLAGS_STR "no_cancel", "silent", "hash", "sha1", "skip", "overwrite", "append_all", "all", "recursive" +#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE +#define FLAGS_COUNT 9 + +#define SHA256_EMPTY_HASH \ + 0xE3, 0xB0, 0xC4, 0x42, \ + 0x98, 0xFC, 0x1C, 0x14, \ + 0x9A, 0xFB, 0xF4, 0xC8, \ + 0x99, 0x6F, 0xB9, 0x24, \ + 0x27, 0xAE, 0x41, 0xE4, \ + 0x64, 0x9B, 0x93, 0x4C, \ + 0xA4, 0x95, 0x99, 0x1B, \ + 0x78, 0x52, 0xB8, 0x55 + +#define SHA1_EMPTY_HASH \ + 0xDA, 0x39, 0xA3, 0xEE, \ + 0x5E, 0x6B, 0x4B, 0x0D, \ + 0x32, 0x55, 0xBF, 0xEF, \ + 0x95, 0x60, 0x18, 0x90, \ + 0xAF, 0xD8, 0x07, 0x09 + int gm9lua_open_fs(lua_State* L); diff --git a/arm9/source/lua/gm9sys.c b/arm9/source/lua/gm9sys.c index feb4ee8b2..73059e508 100644 --- a/arm9/source/lua/gm9sys.c +++ b/arm9/source/lua/gm9sys.c @@ -38,6 +38,7 @@ static int sys_reboot(lua_State* L) { DeinitExtFS(); DeinitSDCardFS(); Reboot(); + return 0; } static int sys_power_off(lua_State* L) { @@ -45,6 +46,7 @@ static int sys_power_off(lua_State* L) { DeinitExtFS(); DeinitSDCardFS(); PowerOff(); + return 0; } static const luaL_Reg sys_lib[] = { diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 595d5d19f..730dd843a 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -5,7 +5,7 @@ #define MAXOPTIONS_STR "256" #define OUTPUTMAXLINES 24 -#define OUTPUTMAXCHARSPERLINE 51 // make sure this includes space for '\0' +#define OUTPUTMAXCHARSPERLINE 80 // make sure this includes space for '\0' // this output buffer stuff is especially a test, it needs to take into account newlines and fonts that are not 8x10 @@ -122,6 +122,7 @@ static int ui_show_png(lua_State* L) { free(bitmap); return luaL_error(L, "PNG too large"); } else { + ClearScreen(ALT_SCREEN, COLOR_STD_BG); DrawBitmap( screen, // screen -1, // x coordinate from argument @@ -140,10 +141,57 @@ static int ui_show_text(lua_State* L) { CheckLuaArgCount(L, 1, "ui.show_text"); const char* text = lua_tostring(L, 1); + ClearScreen(ALT_SCREEN, COLOR_STD_BG); ShowStringF(ALT_SCREEN, "%s", text); return 0; } +static int ui_show_text_viewer(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.show_text_viewer"); + size_t len = 0; + const char* text = luaL_tolstring(L, 1, &len); + + // validate text ourselves so we can return a better error + // MemTextViewer calls ShowPrompt if it's bad, and i don't want that + + if (!(ValidateText(text, len))) { + return luaL_error(L, "text validation failed"); + } + + if (!(MemTextViewer(text, len, 1, false))) { + return luaL_error(L, "failed to run MemTextViewer"); + } + + return 0; +} + +static int ui_show_file_text_viewer(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.show_file_text_viewer"); + const char* path = lua_tostring(L, 1); + + // validate text ourselves so we can return a better error + // MemTextViewer calls ShowPrompt if it's bad, and i don't want that + // and FileTextViewer calls the above function + + char* text = malloc(STD_BUFFER_SIZE); + if (!text) return false; + + u32 flen = FileGetData(path, text, STD_BUFFER_SIZE - 1, 0); + + text[flen] = '\0'; + u32 len = (ptrdiff_t)memchr(text, '\0', flen + 1) - (ptrdiff_t)text; + + if (!(ValidateText(text, len))) { + return luaL_error(L, "text validation failed"); + } + + if (!(MemTextViewer(text, len, 1, false))) { + return luaL_error(L, "failed to run MemTextViewer"); + } + + return 0; +} + static int ui_ask_selection(lua_State* L) { CheckLuaArgCount(L, 2, "ui.ask_selection"); const char* text = lua_tostring(L, 1); @@ -209,13 +257,15 @@ static int ui_global_print(lua_State* L) { static const luaL_Reg ui_lib[] = { {"echo", ui_echo}, + {"ask", ui_ask}, {"ask_hex", ui_ask_hex}, {"ask_number", ui_ask_number}, {"ask_text", ui_ask_text}, - {"ask", ui_ask}, + {"ask_selection", ui_ask_selection}, {"show_png", ui_show_png}, {"show_text", ui_show_text}, - {"ask_selection", ui_ask_selection}, + {"show_text_viewer", ui_show_text_viewer}, + {"show_file_text_viewer", ui_show_file_text_viewer}, {"format_bytes", ui_format_bytes}, {NULL, NULL} }; diff --git a/data/luascripts/hash-test.lua b/data/luascripts/hash-test.lua new file mode 100644 index 000000000..edd9dcf0f --- /dev/null +++ b/data/luascripts/hash-test.lua @@ -0,0 +1,33 @@ +local hash = fs.hash_file("0:/boot.firm", 0, 0x200) +local hash1 = fs.hash_file("0:/boot.firm", 0, 0x200, {sha1=true}) + +-- https://stackoverflow.com/questions/29419345/convert-string-to-hex-in-lua +local function format_hex(str) + local data = '' + for i = 1, #str do + char = string.sub(str, i, i) + data = data..string.format("%02X", string.byte(char)).." " + end + return data +end + +print("Hash 256: "..format_hex(hash)) +print("Hash 1: "..format_hex(hash1)) + +local fullfile256 = fs.hash_file("0:/boot.firm", 0, 0) +local fullfile1 = fs.hash_file("0:/boot.firm", 0, 0, {sha1=true}) + +print("Hash 256 full file: "..format_hex(fullfile256)) +print("Hash 1 full file: "..format_hex(fullfile1)) + +if ui.ask("Create dummy?") then + fs.write_file("9:/dummy.bin", 0, "") +end + +local nodata256 = fs.hash_file("9:/dummy.bin", 0, 0) +local nodata1 = fs.hash_file("9:/dummy.bin", 0, 0, {sha1=true}) + +print("Hash 256 no data: "..format_hex(nodata256)) +print("Hash 1 no data: "..format_hex(nodata1)) + +ui.echo("Waiting") diff --git a/data/luascripts/truncate-test.lua b/data/luascripts/truncate-test.lua new file mode 100644 index 000000000..6a7eaf5e4 --- /dev/null +++ b/data/luascripts/truncate-test.lua @@ -0,0 +1,6 @@ +print("Writing trunctest") +fs.write_file("9:/trunctest.bin", 0, "testing") +print("Truncating trunctest") +fs.truncate("9:/trunctest.bin", 4) +print("Truncating vram (this should fail)") +fs.truncate("M:/vram.mem", 6291456) diff --git a/data/luascripts/viewer-test.lua b/data/luascripts/viewer-test.lua new file mode 100644 index 000000000..9ac930ae8 --- /dev/null +++ b/data/luascripts/viewer-test.lua @@ -0,0 +1,10 @@ +ui.echo("Testing...") + +ui.show_text("Text") +ui.echo("Text stuff!") + +ui.show_text_viewer("Text viewer\nis here") +ui.echo("Text viewer!") + +ui.show_file_text_viewer("2:/sys/log/inspect.log") +ui.echo("File text viewer!") From 8d9063681434bc801ee54a263599fb86dfaf9c20 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 22 Nov 2024 01:32:08 -0600 Subject: [PATCH 061/124] more functions and stuff, plus a wip ctrcheck reimpl --- arm9/source/lua/gm9internalsys.c | 83 ++++++++++++++++++++ arm9/source/lua/gm9internalsys.h | 6 ++ arm9/source/lua/gm9lua.c | 52 ++++++++++++- arm9/source/lua/gm9sys.c | 63 --------------- arm9/source/lua/gm9sys.h | 6 -- data/luapackages/io.lua | 8 +- data/luapackages/sys.lua | 55 +++++++++++++ data/luapackages/util.lua | 14 ++++ data/luascripts/ctrcheck.lua | 128 +++++++++++++++++++++++++++++++ data/preload.lua | 2 + 10 files changed, 343 insertions(+), 74 deletions(-) create mode 100644 arm9/source/lua/gm9internalsys.c create mode 100644 arm9/source/lua/gm9internalsys.h delete mode 100644 arm9/source/lua/gm9sys.c delete mode 100644 arm9/source/lua/gm9sys.h create mode 100644 data/luapackages/sys.lua create mode 100644 data/luapackages/util.lua create mode 100644 data/luascripts/ctrcheck.lua diff --git a/arm9/source/lua/gm9internalsys.c b/arm9/source/lua/gm9internalsys.c new file mode 100644 index 000000000..b6e923b40 --- /dev/null +++ b/arm9/source/lua/gm9internalsys.c @@ -0,0 +1,83 @@ +#ifndef NO_LUA +#include "gm9internalsys.h" +#include "bootfirm.h" +#include "fs.h" +#include "pxi.h" +#include "game.h" +#include "power.h" +#include "sha.h" + +static int internalsys_boot(lua_State* L) { + CheckLuaArgCount(L, 1, "_sys.boot"); + const char* path = lua_tostring(L, 1); + + u8* firm = (u8*) malloc(FIRM_MAX_SIZE); + if (!firm) { + return luaL_error(L, STR_SCRIPTERR_OUT_OF_MEMORY); + } + + size_t firm_size = FileGetData(path, firm, FIRM_MAX_SIZE, 0); + if (!(firm_size && IsBootableFirm(firm, firm_size))) { + return luaL_error(L, STR_SCRIPTERR_NOT_A_BOOTABLE_FIRM); + } + + char fixpath[256] = { 0 }; + if ((*path == '0') || (*path == '1')) + snprintf(fixpath, sizeof(fixpath), "%s%s", (*path == '0') ? "sdmc" : "nand", path + 1); + else strncpy(fixpath, path, 256); + fixpath[255] = '\0'; + DeinitExtFS(); + DeinitSDCardFS(); + PXI_DoCMD(PXICMD_LEGACY_BOOT, NULL, 0); + PXI_Barrier(PXI_FIRMLAUNCH_BARRIER); + BootFirm((FirmHeader*)(void*)firm, fixpath); + while(1); +} + +static int internalsys_reboot(lua_State* L) { + CheckLuaArgCount(L, 0, "_sys.reboot"); + DeinitExtFS(); + DeinitSDCardFS(); + Reboot(); + return 0; +} + +static int internalsys_power_off(lua_State* L) { + CheckLuaArgCount(L, 0, "_sys.power_off"); + DeinitExtFS(); + DeinitSDCardFS(); + PowerOff(); + return 0; +} + +static int internalsys_get_id0(lua_State* L) { + CheckLuaArgCount(L, 1, "_sys.get_id0"); + const char* path = lua_tostring(L, 1); + + char env_id0[32+1]; + u8 sd_keyy[0x10] __attribute__((aligned(4))); + if (FileGetData(path, sd_keyy, 0x10, 0x110) == 0x10) { + u32 sha256sum[8]; + sha_quick(sha256sum, sd_keyy, 0x10, SHA256_MODE); + snprintf(env_id0, sizeof(env_id0), "%08lx%08lx%08lx%08lx", + sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]); + lua_pushstring(L, env_id0); + } else { + lua_pushnil(L); + } + return 1; +} + +static const luaL_Reg internalsys_lib[] = { + {"boot", internalsys_boot}, + {"reboot", internalsys_reboot}, + {"power_off", internalsys_power_off}, + {"get_id0", internalsys_get_id0}, + {NULL, NULL} +}; + +int gm9lua_open_internalsys(lua_State* L) { + luaL_newlib(L, internalsys_lib); + return 1; +} +#endif diff --git a/arm9/source/lua/gm9internalsys.h b/arm9/source/lua/gm9internalsys.h new file mode 100644 index 000000000..7158ff10c --- /dev/null +++ b/arm9/source/lua/gm9internalsys.h @@ -0,0 +1,6 @@ +#pragma once +#include "gm9lua.h" + +#define GM9LUA_INTERNALSYSLIBNAME "_sys" + +int gm9lua_open_internalsys(lua_State* L); diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index d63b8d6bf..fdee2110c 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -5,14 +5,18 @@ #include "ff.h" #include "vff.h" #include "fsutil.h" +#include "unittype.h" +#include "nand.h" #include "gm9enum.h" #include "gm9loader.h" #include "gm9fs.h" #include "gm9os.h" -#include "gm9sys.h" +#include "gm9internalsys.h" #include "gm9ui.h" -#define DEBUGSP ShowPrompt +#define DEBUGSP(x) ShowPrompt(false, (x)) +// this is taken from scripting.c +#define _VAR_CNT_LEN 256 typedef struct GM9LuaLoadF { int n; // pre-read characters @@ -84,9 +88,11 @@ static const luaL_Reg gm9lualibs[] = { // gm9 custom {GM9LUA_FSLIBNAME, gm9lua_open_fs}, {GM9LUA_OSLIBNAME, gm9lua_open_os}, - {GM9LUA_SYSLIBNAME, gm9lua_open_sys}, {GM9LUA_UILIBNAME, gm9lua_open_ui}, + // gm9 custom internals (usually wrapped by a pure lua module) + {GM9LUA_INTERNALSYSLIBNAME, gm9lua_open_internalsys}, + {NULL, NULL} }; @@ -119,6 +125,17 @@ static bool RunFile(lua_State* L, const char* file) { return true; } +// this is also taken from scripting.c +static inline bool isntrboot(void) { + // taken over from Luma 3DS: + // https://github.com/AuroraWright/Luma3DS/blob/bb5518b0f68d89bcd8efaf326355a770d5e57856/source/main.c#L58-L62 + const vu8 *bootMediaStatus = (const vu8 *) 0x1FFFE00C; + const vu32 *bootPartitionsStatus = (const vu32 *) 0x1FFFE010; + + // shell closed, no error booting NTRCARD, NAND partitions not even considered + return (bootMediaStatus[3] == 2) && !bootMediaStatus[1] && !bootPartitionsStatus[0] && !bootPartitionsStatus[1]; +} + bool ExecuteLuaScript(const char* path_script) { lua_State* L = luaL_newstate(); loadlibs(L); @@ -126,12 +143,39 @@ bool ExecuteLuaScript(const char* path_script) { ResetPackageSearchersAndPath(L); ClearOutputBuffer(); + // current path + char curr_dir[_VAR_CNT_LEN]; + if (path_script) { + strncpy(curr_dir, path_script, _VAR_CNT_LEN); + curr_dir[_VAR_CNT_LEN-1] = '\0'; + char* slash = strrchr(curr_dir, '/'); + if (slash) *slash = '\0'; + } else strncpy(curr_dir, "(null)", _VAR_CNT_LEN - 1); + lua_pushliteral(L, VERSION); - lua_setglobal(L, "GM9VERSION"); + lua_setglobal(L, "GM9VER"); lua_pushstring(L, path_script); lua_setglobal(L, "SCRIPT"); + lua_pushstring(L, curr_dir); + lua_setglobal(L, "CURRDIR"); + + lua_pushliteral(L, OUTPUT_PATH); + lua_setglobal(L, "GM9OUT"); + + lua_pushstring(L, IS_UNLOCKED ? (isntrboot() ? "ntrboot" : "sighax") : ""); + lua_setglobal(L, "HAX"); + + lua_pushinteger(L, GetNandSizeSectors(NAND_SYSNAND) * 0x200); + lua_setglobal(L, "NANDSIZE"); + + lua_pushboolean(L, IS_DEVKIT); + lua_setglobal(L, "IS_DEVKIT"); + + lua_pushstring(L, IS_O3DS ? "O3DS" : "N3DS"); + lua_setglobal(L, "CONSOLE_TYPE"); + bool result = RunFile(L, "V:/preload.lua"); if (!result) { ShowPrompt(false, "A fatal error happened in GodMode9's preload script.\n\nThis is not an error with your code, but with\nGodMode9. Please report it on GitHub."); diff --git a/arm9/source/lua/gm9sys.c b/arm9/source/lua/gm9sys.c deleted file mode 100644 index 73059e508..000000000 --- a/arm9/source/lua/gm9sys.c +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef NO_LUA -#include "gm9sys.h" -#include "bootfirm.h" -#include "fs.h" -#include "pxi.h" -#include "game.h" -#include "power.h" - -static int sys_boot(lua_State* L) { - CheckLuaArgCount(L, 1, "sys.boot"); - const char* path = lua_tostring(L, 1); - - u8* firm = (u8*) malloc(FIRM_MAX_SIZE); - if (!firm) { - return luaL_error(L, STR_SCRIPTERR_OUT_OF_MEMORY); - } - - size_t firm_size = FileGetData(path, firm, FIRM_MAX_SIZE, 0); - if (!(firm_size && IsBootableFirm(firm, firm_size))) { - return luaL_error(L, STR_SCRIPTERR_NOT_A_BOOTABLE_FIRM); - } - - char fixpath[256] = { 0 }; - if ((*path == '0') || (*path == '1')) - snprintf(fixpath, sizeof(fixpath), "%s%s", (*path == '0') ? "sdmc" : "nand", path + 1); - else strncpy(fixpath, path, 256); - fixpath[255] = '\0'; - DeinitExtFS(); - DeinitSDCardFS(); - PXI_DoCMD(PXICMD_LEGACY_BOOT, NULL, 0); - PXI_Barrier(PXI_FIRMLAUNCH_BARRIER); - BootFirm((FirmHeader*)(void*)firm, fixpath); - while(1); -} - -static int sys_reboot(lua_State* L) { - CheckLuaArgCount(L, 0, "sys.reboot"); - DeinitExtFS(); - DeinitSDCardFS(); - Reboot(); - return 0; -} - -static int sys_power_off(lua_State* L) { - CheckLuaArgCount(L, 0, "sys.power_off"); - DeinitExtFS(); - DeinitSDCardFS(); - PowerOff(); - return 0; -} - -static const luaL_Reg sys_lib[] = { - {"boot", sys_boot}, - {"reboot", sys_reboot}, - {"power_off", sys_power_off}, - {NULL, NULL} -}; - -int gm9lua_open_sys(lua_State* L) { - luaL_newlib(L, sys_lib); - return 1; -} -#endif diff --git a/arm9/source/lua/gm9sys.h b/arm9/source/lua/gm9sys.h deleted file mode 100644 index 5a82fda00..000000000 --- a/arm9/source/lua/gm9sys.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#include "gm9lua.h" - -#define GM9LUA_SYSLIBNAME "sys" - -int gm9lua_open_sys(lua_State* L); diff --git a/data/luapackages/io.lua b/data/luapackages/io.lua index eb019d4d7..5463466ec 100644 --- a/data/luapackages/io.lua +++ b/data/luapackages/io.lua @@ -1,6 +1,8 @@ -- This module has not been fully tested against Lua's built-in module. -- Functionality might be incorrect. +-- TODO: set up __gc in files so they can clean up properly + local io = {} local file = {} @@ -38,7 +40,7 @@ function file.new(filename, mode) allowed = fs.allow(filename) debugf("allowed:", allowed) if not allowed then return nil end - success, stat = fs.stat(filename) + success, stat = pcall(fs.stat, filename) debugf("stat success:", success) if success then debugf("type:", stat.type) @@ -49,6 +51,8 @@ function file.new(filename, mode) of._stat = {} of._size = 0 end + elseif string.find(mode, "a") then + error("append mode is not yet functional") else debugf("opening", filename, "for reading") -- check if file exists first @@ -131,7 +135,9 @@ function file:write(...) to_write = to_write..tostring(v) end local len = string.len(to_write) + debugf("attempting to write "..tostring(len).." bytes to "..self._filename) local br = fs.write_file(self._filename, self._seek, to_write) + debugf("wrote "..tostring(br).." bytes to "..self._filename) self._seek = self._seek + br if self._seek > self._size then self._size = self._seek diff --git a/data/luapackages/sys.lua b/data/luapackages/sys.lua new file mode 100644 index 000000000..a4d80116c --- /dev/null +++ b/data/luapackages/sys.lua @@ -0,0 +1,55 @@ +local sys = {} + +sys.boot = _sys.boot +sys.reboot = _sys.reboot +sys.power_off = _sys.power_off + +sys.secureinfo_letter = nil +sys.region = nil +sys.serial = nil +sys.sys_id0 = nil +sys.emu_id0 = nil + +local regions = {"JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"} + +local function refresh_secureinfo() + local letter = nil + if fs.exists("1:/rw/sys/SecureInfo_A") then letter = "A" + elseif fs.exists("1:rw/sys/SecureInfo_B") then letter = "B" + else error("could not read SecureInfo") end + + local secinfo = fs.read_file("1:/rw/sys/SecureInfo_"..letter, 0, 0x111) + + -- remember, Lua starts indexes at 1 so these offsets appear off by one if you're used to other langs + local serial = string.sub(secinfo, 0x103, 0x111) + serial = string.gsub(serial, "\0", "") + + local region_byte = string.sub(secinfo, 0x101, 0x101) + local region_num = string.byte(region_byte) + if region_num > 6 then error("SecureInfo region byte is invalid") end + + sys.serial = serial + sys.region = regions[region_num + 1] + sys.secureinfo_letter = letter +end + +local function refresh_id0() + local sys_id0 = _sys.get_id0("1:/private/movable.sed") + + if fs.exists("4:/private/movable.sed") then + local emu_id0 = _sys.get_id0("4:/private/movable.sed") + end + + sys.sys_id0 = sys_id0 + sys.emu_id0 = emu_id0 +end + +function sys.refresh_info() + refresh_secureinfo() + refresh_id0() +end + +-- in the preload scripts, we have to avoid possibilities of unhandled exceptions +pcall(sys.refresh_info) + +return sys diff --git a/data/luapackages/util.lua b/data/luapackages/util.lua new file mode 100644 index 000000000..e22584592 --- /dev/null +++ b/data/luapackages/util.lua @@ -0,0 +1,14 @@ +local util = {} + +-- https://stackoverflow.com/a/71896879 +function util.bytes_to_hex(data) + local hex = '' + local char + for i = 1, #data do + char = string.sub(data, i, i) + hex = hex..string.format("%02x", string.byte(char)) + end + return hex +end + +return util diff --git a/data/luascripts/ctrcheck.lua b/data/luascripts/ctrcheck.lua new file mode 100644 index 000000000..b917c9b03 --- /dev/null +++ b/data/luascripts/ctrcheck.lua @@ -0,0 +1,128 @@ +local version = "5" +local store_log = false + +local NAND_HDR = "S:/nand_hdr.bin" + +local function make_selection_text() + return "Select which parts of the system to check.\n".. + "Console is a "..sys.region.." "..CONSOLE_TYPE.." "..(IS_DEVKIT and "devkit" or "retail")..".\n".. + "Current id0 is "..(sys.sys_id0 or "unknown").."\n".. + "Permanent logging: "..(store_log and "enabled" or "disabled") +end + +local options_short = {"all", "nand", "sd", "togglelog", "exit"} +local options = { + "All", + "NAND Only", + "SD Only", + "Toggle permanent log", + "Exit" +} + +local function chk_nand_header() + local log = {} + -- TODO: replace the multiple file reads with fs.hash_data once that's implemented + local is_sighax = false + local success, stat = pcall(fs.stat, NAND_HDR) + if not success then + table.insert(log, "Error: NAND header not found.") + return + end + if stat.size ~= 512 then + table.insert(log, "Error: NAND header is an invalid size (expected 512, got "..tostring(stat.size)..")") + return + end + + local hdr_hash = util.bytes_to_hex(fs.hash_file(NAND_HDR, 0, 0x100)) + + if hdr_hash == "a4ae99b93412e4643e4686987b6cfd59701d5c655ca2ff671ce680b4ddcf0948" then + table.insert(log, "Information: NAND header's signature is sighax.") + is_sighax = true + end + + local part_table_hash = util.bytes_to_hex(fs.hash_file(NAND_HDR, 0x100, 0x60)) + + if part_table_hash == "dfd434b883874d8b585a102f3cf3ae4cef06767801db515fdf694a7e7cd98bc2" then + table.insert(log, (CONSOLE_TYPE == "N3DS") and "Information: NAND header is stock. (n3DS)" or "Critical: o3DS has an n3DS nand header.") + elseif part_table_hash == "ae9b6645105f3aec22c2e3ee247715ab302874fca283343c731ca43ea1baa25d" then + table.insert(log, (CONSOLE_TYPE == "03DS") and "Information: NAND header is stock. (o3DS)" or "Critical: n3DS has an o3DS nand header.") + else + local header_magic = fs.read_file(NAND_HDR, 0x100, 4) + if header_magic == "NCSD" then + if is_sighax then + table.insert(log, "Warning: NAND partition table is modified, but there is sighax in the NAND header.") + else + table.insert(log, "Error: NAND partition table is modified, and there is no sighax in the NAND header.") + end + else + table.insert(log, "Error: NAND header data is invalid. You've met with a terrible fate, haven't you?") + end + end + + return table.concat(log, "\n") +end + +local function chk_nand_sectors() + local log = {} + + table.insert(log, "NAND sectors check not implemented.") + + return table.concat(log, "\n") +end + +local function nand_checks() + local nand_log = {} + + table.insert(nand_log, chk_nand_header()) + + return table.concat(nand_log, "\n\n") +end + +local function sd_checks() + sd_log = {} + + table.insert(sd_log, "SD checks not implemented.") + + return table.concat(sd_log, "\n\n") +end + +local function main() + local final_log = "" + while true do + ui.show_text("ctrcheck v"..version) + + local selection = ui.ask_selection(make_selection_text(), options) + if not selection then return end + + local seltext = options_short[selection] + if seltext == "exit" then + return + elseif seltext == "togglelog" then + store_log = not store_log + elseif seltext == "all" then + final_log = final_log..nand_checks().."\n\n" + final_log = final_log..sd_checks() + elseif seltext == "nand" then + final_log = nand_checks() + elseif seltext == "sd" then + final_log = sd_checks() + end + if final_log ~= "" then + final_log = "Date and Time: "..os.date().."\n---\n"..final_log + ui.show_text_viewer(final_log) + + if store_log then + -- append mode is not implemented in io yet + local f = io.open(GM9OUT.."/ctrcheck_log.txt", "r+") + f:seek("end") + f:write(final_log, "\n") + f:close() + ui.echo("I think I wrote to "..GM9OUT.."/ctrcheck_log.txt") + end + + final_log = "" + end + end +end + +main() diff --git a/data/preload.lua b/data/preload.lua index c65ab9d9e..75f417a9b 100644 --- a/data/preload.lua +++ b/data/preload.lua @@ -2,4 +2,6 @@ -- The purpose of this one is to initialize some variables and modules. -- If you're looking for an auto-running script, you want "autorun.lua"! +util = require('util') io = require('io') +sys = require('sys') From 1c9499ab923011f732afeae77f9367ddb7ce5b85 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sat, 23 Nov 2024 00:04:05 -0600 Subject: [PATCH 062/124] yet more functions and stuff --- arm9/source/lua/gm9fs.c | 155 +++++++++++++++++++++++++++--- arm9/source/lua/gm9fs.h | 2 +- data/luapackages/util.lua | 14 ++- data/luascripts/fs-ops-test.lua | 15 +++ data/luascripts/hashdata-test.lua | 10 ++ 5 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 data/luascripts/fs-ops-test.lua create mode 100644 data/luascripts/hashdata-test.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index b0aa1ff8f..b63b80c09 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -3,6 +3,20 @@ #include "fs.h" #include "ui.h" #include "utils.h" +#include "sha.h" + +static u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; +static u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; + +static bool PathIsDirectory(const char* path) { + DIR fdir; + if (fvx_opendir(&fdir, path) == FR_OK) { + fvx_closedir(&fdir); + return true; + } else { + return false; + } +} static void CreateStatTable(lua_State* L, FILINFO* fno) { lua_createtable(L, 0, 4); // create nested table @@ -32,6 +46,104 @@ static u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 a return flags_ext; } +static int fs_move(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "fs.rename"); + const char* path_src = luaL_checkstring(L, 1); + const char* path_dst = luaL_checkstring(L, 2); + FILINFO fno; + + u32 flags = BUILD_PATH; + if (extra) { + flags = GetFlagsFromTable(L, 3, flags, NO_CANCEL | SILENT | OVERWRITE_ALL | SKIP_ALL); + } + + if (!(flags & OVERWRITE_ALL) && (fvx_stat(path_dst, &fno) == FR_OK)) { + return luaL_error(L, "destination already exists on %s -> %s and {overwrite_all=true} was not used", path_src, path_dst); + } + + if (!(PathMoveCopy(path_dst, path_src, &flags, true))) { + return luaL_error(L, "PathMoveCopy failed on %s -> %s", path_src, path_dst); + } + return 0; +} + +static int fs_remove(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.remove"); + const char* path = luaL_checkstring(L, 1); + + bool allowed = CheckWritePermissions(path); + if (!allowed) { + return luaL_error(L, "writing not allowed: %s", path); + } + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 2, flags, RECURSIVE); + } + + if (!(flags & RECURSIVE)) { + if (PathIsDirectory(path)) { + return luaL_error(L, "requested directory remove without {recursive=true} on %s", path); + } + } + + if (!(PathDelete(path))) { + return luaL_error(L, "PathDelete failed on %s", path); + } + + return 0; +} + +static int fs_copy(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "fs.copy"); + const char* path_src = luaL_checkstring(L, 1); + const char* path_dst = luaL_checkstring(L, 2); + FILINFO fno; + + bool allowed = CheckWritePermissions(path_dst); + if (!allowed) { + return luaL_error(L, "writing not allowed: %s", path_dst); + } + + u32 flags = BUILD_PATH; + if (extra) { + flags = GetFlagsFromTable(L, 3, flags, CALC_SHA | USE_SHA1 | NO_CANCEL | SILENT | OVERWRITE_ALL | SKIP_ALL | APPEND_ALL | RECURSIVE); + } + + if (!(flags & RECURSIVE)) { + if (PathIsDirectory(path_src)) { + return luaL_error(L, "requested directory copy without {recursive=true} on %s -> %s", path_src, path_dst); + } + } + + if (!(flags & OVERWRITE_ALL) && (fvx_stat(path_dst, &fno) == FR_OK)) { + return luaL_error(L, "destination already exists on %s -> %s and {overwrite_all=true} was not used", path_src, path_dst); + } + + if (!(PathMoveCopy(path_dst, path_src, &flags, false))) { + return luaL_error(L, "PathMoveCopy failed on %s -> %s", path_src, path_dst); + } + + return 0; +} + +static int fs_mkdir(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.mkdir"); + const char* path = luaL_checkstring(L, 1); + + bool allowed = CheckWritePermissions(path); + if (!allowed) { + return luaL_error(L, "writing not allowed: %s", path); + } + + FRESULT res = fvx_rmkdir(path); + if (res != FR_OK) { + return luaL_error(L, "could not mkdir (%d)", path, res); + } + + return 0; +} + static int fs_list_dir(lua_State* L) { CheckLuaArgCount(L, 1, "fs.list_dir"); const char* path = luaL_checkstring(L, 1); @@ -119,22 +231,15 @@ static int fs_exists(lua_State* L) { const char* path = luaL_checkstring(L, 1); FILINFO fno; - FRESULT res = fvx_stat(path, &fno); - lua_pushboolean(L, res == FR_OK); + lua_pushboolean(L, (fvx_stat(path, &fno) == FR_OK)); return 1; } static int fs_is_dir(lua_State* L) { CheckLuaArgCount(L, 1, "fs.is_dir"); const char* path = luaL_checkstring(L, 1); - FILINFO fno; - FRESULT res = fvx_stat(path, &fno); - if (res != FR_OK) { - lua_pushboolean(L, false); - } else { - lua_pushboolean(L, fno.fattrib & AM_DIR); - } + lua_pushboolean(L, PathIsDirectory(path)); return 1; } @@ -273,9 +378,6 @@ static int fs_hash_file(lua_State* L) { FRESULT res; FILINFO fno; - u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; - u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; - if (size == 0) { res = fvx_stat(path, &fno); if (res != FR_OK) { @@ -304,6 +406,30 @@ static int fs_hash_file(lua_State* L) { return 1; } +static int fs_hash_data(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.hash_data"); + size_t data_length = 0; + const char* data = luaL_checklstring(L, 1, &data_length); + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 2, flags, USE_SHA1); + } + + const u8 hashlen = (flags & USE_SHA1) ? 20 : 32; + u8 hash_fil[0x20]; + + if (data_length == 0) { + // shortcut by just returning the hash of empty data + memcpy(hash_fil, (flags & USE_SHA1) ? no_data_hash_1 : no_data_hash_256, hashlen); + } else { + sha_quick(hash_fil, data, data_length, (flags & USE_SHA1) ? SHA1_MODE : SHA256_MODE); + } + + lua_pushlstring(L, (char*)hash_fil, hashlen); + return 1; +} + static int fs_allow(lua_State* L) { bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.img_mount"); const char* path = luaL_checkstring(L, 1); @@ -336,6 +462,10 @@ static int fs_verify(lua_State* L) { } static const luaL_Reg fs_lib[] = { + {"move", fs_move}, + {"remove", fs_remove}, + {"copy", fs_copy}, + {"mkdir", fs_mkdir}, {"list_dir", fs_list_dir}, {"stat", fs_stat}, {"stat_fs", fs_stat_fs}, @@ -350,6 +480,7 @@ static const luaL_Reg fs_lib[] = { {"img_umount", fs_img_umount}, {"get_img_mount", fs_get_img_mount}, {"hash_file", fs_hash_file}, + {"hash_data", fs_hash_data}, {"allow", fs_allow}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h index 76abed512..8ae5d299a 100644 --- a/arm9/source/lua/gm9fs.h +++ b/arm9/source/lua/gm9fs.h @@ -6,7 +6,7 @@ // this should probably go in filesys/fsutil.h #define RECURSIVE (1UL<<11) -#define FLAGS_STR "no_cancel", "silent", "hash", "sha1", "skip", "overwrite", "append_all", "all", "recursive" +#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive" #define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE #define FLAGS_COUNT 9 diff --git a/data/luapackages/util.lua b/data/luapackages/util.lua index e22584592..a0b26b134 100644 --- a/data/luapackages/util.lua +++ b/data/luapackages/util.lua @@ -2,13 +2,21 @@ local util = {} -- https://stackoverflow.com/a/71896879 function util.bytes_to_hex(data) - local hex = '' + local hex = {} local char for i = 1, #data do char = string.sub(data, i, i) - hex = hex..string.format("%02x", string.byte(char)) + --hex = hex..string.format("%02x", string.byte(char)) + table.insert(hex, string.format("%02x", string.byte(char))) end - return hex + return table.concat(hex) +end + +-- https://stackoverflow.com/a/9140231 +function util.hex_to_bytes(hexstring) + return (string.gsub(hexstring, '..', function (cc) + return string.char(tonumber(cc, 16)) + end)) end return util diff --git a/data/luascripts/fs-ops-test.lua b/data/luascripts/fs-ops-test.lua new file mode 100644 index 000000000..e9030149d --- /dev/null +++ b/data/luascripts/fs-ops-test.lua @@ -0,0 +1,15 @@ +print("fs.mkdir") +fs.mkdir("9:/test") +print("fs.copy") +fs.copy("1:/private", "9:/test", {recursive=true, overwrite_all=true}) +print("fs.move") +fs.move("9:/test", "9:/test2") +print("fs.remove") +fs.remove("9:/test2", {recursive=true}) + +print("this next one should fail") +fs.mkdir("9:/test1") +fs.mkdir("9:/test2") +fs.move("9:/test2", "9:/test1") + +ui.echo("Done?") diff --git a/data/luascripts/hashdata-test.lua b/data/luascripts/hashdata-test.lua new file mode 100644 index 000000000..e04a9ac3d --- /dev/null +++ b/data/luascripts/hashdata-test.lua @@ -0,0 +1,10 @@ +local hash256 = fs.hash_data("a") +local hash1 = fs.hash_data("a", {sha1=true}) + +print("I hashed the letter a") +print("Hash 256: ", util.bytes_to_hex(hash256)) +print("Hash 1: ", util.bytes_to_hex(hash1)) +print() +print("Hex to bytes:", util.hex_to_bytes("676179")) + +ui.echo("Cool") From 1f58eba0a9f908bce6b0b55abaa522f8701b2576 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sat, 23 Nov 2024 01:09:22 -0600 Subject: [PATCH 063/124] even more functions and stuff --- arm9/source/lua/gm9fs.c | 64 ++++++++++++++++++++++++++++++++- data/luascripts/sd-tests.lua | 5 +++ data/luascripts/verify-test.lua | 7 ++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 data/luascripts/sd-tests.lua create mode 100644 data/luascripts/verify-test.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index b63b80c09..9494561b1 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -4,6 +4,9 @@ #include "ui.h" #include "utils.h" #include "sha.h" +#include "nand.h" +#include "language.h" +#include "hid.h" static u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; static u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; @@ -185,6 +188,18 @@ static int fs_stat(lua_State* L) { return 1; } +static int fs_fix_cmacs(lua_State* L) { + CheckLuaArgCount(L, 1, "fs.fix_cmacs"); + const char* path = luaL_checkstring(L, 1); + + ShowString("%s", STR_FIXING_CMACS_PLEASE_WAIT); + if (RecursiveFixFileCmac(path) != 0) { + return luaL_error(L, "%s", STR_SCRIPTERR_FIXCMAC_FAILED); + } + + return 0; +} + static int fs_stat_fs(lua_State* L) { CheckLuaArgCount(L, 1, "fs.stat_fs"); const char* path = luaL_checkstring(L, 1); @@ -451,7 +466,7 @@ static int fs_allow(lua_State* L) { static int fs_verify(lua_State* L) { CheckLuaArgCount(L, 1, "fs.verify"); const char* path = luaL_checkstring(L, 1); - bool res = true; + bool res; u64 filetype = IdentifyFileType(path); if (filetype & IMG_NAND) res = (ValidateNandDump(path) == 0); @@ -461,6 +476,49 @@ static int fs_verify(lua_State* L) { return 1; } +static int fs_sd_is_mounted(lua_State* L) { + CheckLuaArgCount(L, 0, "fs.sd_is_mounted"); + + lua_pushboolean(L, CheckSDMountState()); + return 1; +} + +static int fs_sd_switch(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 0, "fs.sd_switch"); + const char* message; + + if (extra) { + message = luaL_checkstring(L, 1); + } else { + message = "Please switch the SD card now."; + } + + bool ret; + + DeinitExtFS(); + if (!(ret = CheckSDMountState())) { + return luaL_error(L, "%s", STR_SCRIPTERR_SD_NOT_MOUNTED); + } + + u32 pad_state; + DeinitSDCardFS(); + ShowString("%s\n \n%s", message, STR_EJECT_SD_CARD); + while (!((pad_state = InputWait(0)) & (BUTTON_B|SD_EJECT))); + if (pad_state & SD_EJECT) { + ShowString("%s\n \n%s", message, STR_INSERT_SD_CARD); + while (!((pad_state = InputWait(0)) & (BUTTON_B|SD_INSERT))); + } + if (pad_state & BUTTON_B) { + return luaL_error(L, "%s", STR_SCRIPTERR_USER_ABORT); + } + + InitSDCardFS(); + AutoEmuNandBase(true); + InitExtFS(); + + return 0; +} + static const luaL_Reg fs_lib[] = { {"move", fs_move}, {"remove", fs_remove}, @@ -481,7 +539,11 @@ static const luaL_Reg fs_lib[] = { {"get_img_mount", fs_get_img_mount}, {"hash_file", fs_hash_file}, {"hash_data", fs_hash_data}, + {"verify", fs_verify}, {"allow", fs_allow}, + {"sd_is_mounted", fs_sd_is_mounted}, + {"sd_switch", fs_sd_switch}, + {"fix_cmacs", fs_fix_cmacs}, {NULL, NULL} }; diff --git a/data/luascripts/sd-tests.lua b/data/luascripts/sd-tests.lua new file mode 100644 index 000000000..414cc3572 --- /dev/null +++ b/data/luascripts/sd-tests.lua @@ -0,0 +1,5 @@ +print("sd mounted:", fs.sd_is_mounted()) + +fs.sd_switch() + +ui.echo("Done!") diff --git a/data/luascripts/verify-test.lua b/data/luascripts/verify-test.lua new file mode 100644 index 000000000..c50fe58dc --- /dev/null +++ b/data/luascripts/verify-test.lua @@ -0,0 +1,7 @@ +print("fs.verify nand:", fs.verify("S:/nand.bin")) +print("fs.verify title:", fs.verify("1:/title/00040010/00021000/content/00000051.app")) +print("fixing cmacs") +fs.fix_cmacs("1:/") +print("done") + +ui.echo("Done") From 75b106a5e0e336b4aebbc952d5284cd3aff565dc Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sat, 23 Nov 2024 01:37:04 -0600 Subject: [PATCH 064/124] command comparison table.ods --- command comparison table.ods | Bin 0 -> 21163 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 command comparison table.ods diff --git a/command comparison table.ods b/command comparison table.ods new file mode 100644 index 0000000000000000000000000000000000000000..c69ecc85da082ddf200d5ec9d920161c5ac1dc40 GIT binary patch literal 21163 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 Date: Sat, 23 Nov 2024 01:47:52 -0600 Subject: [PATCH 065/124] update command comparison table.ods --- command comparison table.ods | Bin 21163 -> 21834 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/command comparison table.ods b/command comparison table.ods index c69ecc85da082ddf200d5ec9d920161c5ac1dc40..793fec66200d7212e957021160333ff0365a936c 100644 GIT binary patch delta 6382 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 From f121fa757f9252c77fa15c8ce77b9e5a1a9d12c2 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 26 Nov 2024 17:02:07 -0600 Subject: [PATCH 066/124] update command comparison table.ods --- command comparison table.ods | Bin 21834 -> 21865 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/command comparison table.ods b/command comparison table.ods index 793fec66200d7212e957021160333ff0365a936c..942c3bfda5ec74b00e2e9d76032613bdb25b2a18 100644 GIT binary patch delta 7331 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 HKiK~RL29T( delta 7222 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 From badbe94b2074b304947a88770c0bb5fbdfbccdb0 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 27 Nov 2024 18:44:55 -0600 Subject: [PATCH 067/124] Add files via upload --- command comparison table.ods | Bin 21865 -> 5086 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/command comparison table.ods b/command comparison table.ods index 942c3bfda5ec74b00e2e9d76032613bdb25b2a18..198e4a34058a925c6ffb972753f6441dc34e5e19 100644 GIT binary patch literal 5086 zcmZ`-2RK|!+g`yEf>l-v(IQ2Q79ni3=)Kpqgw?t{5|0|o}R8wwl)YS5BGb%?sh^RHr`I&LLQ!QcRLSTA2+x=Lde_G3vOfQ?Ffe> zbT2s>0KmT(xBzlo9nolhR2EosFmqk2#5juU`D?= z*7P+Sc4p|OE{PHneS-=6cxGNVS6%<<7;JILpxQPNlYhO58q}G=C3U{uzBG8!1yw>> z43gUTFy6|Oyylr%6FQC-I6uG}AsB%Qxs7s(6Oxp>Rpj^V7ekQ4{qd`kf~E_`^+|DW zju_B@q%tMG(TpqJ16zq)@{i5)C^|i}s)1AP+OxLWgh~4k0uV$aIR61`Sy~EFx4g4k z5i5eIw*US~NYb1tv)qqp?e}3kRc6p_N5kqn4o0Y(M3rX)^LqM=c1!yGx7cG%$|fA| zvxu}Z-^Ons%1d&L4C4Y@3)c+2!6(cH<%xXVeRo)-7aA+pQ)n=ia&(XnD)XU6R5cXV zpP$MYQ$tZ0zBR6{Ak$1j&l#eo->R{`aZ0?VUnaFW{Jc_ z^TjWtxQE=eA1Md(LIqwje3=T^y6;<4C|$AO>xRlcU=xs&YJ2$X)!+u!Rz<3CTrV+e z$UM&LhroDzsu??S+;cBT=xP|bA~9Swo`;o7oe>nE%Wp>7yRL31%^p-ETV)5_1m8mQ z`WFQbTbEy>LNU7+(DEw z1YQwJK)O`F2V+L94^fOFi{GYNxojY38LQ3)D4tm+@+_oet#6n@>*& zM{Okj)l7d`iyNP;-q1MKwY?PrwOHrRFA+>klJ{oNn1j;+4KNqORwd^#b#ifmPBUYW zbqKTNqW5sUST~O_j+%)+%rLF~DOid;mdDddDj-KBsQdt}`)eJ1zrVQ|zxCM1Io)+h5fL;^(jD4*TMNq;*+-xKE@jm<_DLOm%ady-jtnaa zJ%sVGyWI(}A2$Lbo@gK4wdb!gh`z;PDsr?ZYzRi%QXB3rN^cyplX(aTib>#%7%7xr zwHZs8-edzwaxxgm$lx2hD^`1DYhYuH$U+S2ed)&GQxl99KeDAf8{^Z+^a~$FJ68o5 z8qbxtG*Z2>(r_zvoCa(lFjGt~#dE;+ef^9t8@WN2G29g13QO>VVW-s(HMSZ&=WTSt zw>%PGclfVIqb}e%mhlfq-Z5p=li=l=n;NmuoZEb8j+98PLx=j?7caUJvNb=6Ej%kM z(b{es?r`g~=K;(@fjz3(bTs4)4+gE<^=iVH%Dgqtnqb#;UuR-(CZ1wH#2$BSZ=E2lnyHDkY^eHQg)PX_^~g?( za7i`kIia)!-VRwu7>`XkrU zkBW0E8=aKC)>6zI@^*|2PP(_ueMs-RI{|!xZ6+5n0ab8b)9P!W{{4I zNiQjWDGO%+8QacwJ+JY+0(^^B?T^)jc_h_83$%6bMK339dw_PpU0FF~@S@jLBT#Sr z{o_ph1tkD*aX}3D*Mon{i2EY&DgY1z2LLW#0N8rCBmVp;E>He#=#bY+J#A|d-|q7< zt{Fo?UX9--uDiUL7T0yrgc=HNd0JXkPryjE`vI#3rg>gQ?GY$HqPn6=Zieli8;2d5 zWOV5L_0O+?Bdw=vLg3Y;yF+ka2s2GAFbtj(?N?WS_Qjl5l^-ez$Qpx!R2~B|WysYcX;cOaA2QI6i}w zx-$Lq{M9VM_hhd`P7vbe6~>(t`?{rFjzRwZQhdAAeDX?5g;1+IdUa79wDg1OMzP(U z`^E0*;EjxuXHwC{IW&8tINh>e*ehDd=B((W9mVeFa8XW7l=xwgXnl#1WW+j9gvtz5 zrTZy7g@Z?2)m-CA!Zvb^*% zy{>`6vovR)Oh)BGP7siHhDv%3Cupo7QD=+)!Dyh;#O5pC8&(%tl{Mh>LGrke7Nfb?7rV0l{8!hLy=oD9iSbcRN#WRb>o= z^9HPkpOgSv!0x|pR?01#9@|! zdg^cb$$^^19|k$h3uj9p2rG<9`ha80)B1;zdeMlEl!gYcd_}$vg~Z_dBZH<LHe)iDe0V{KQdo^M_7U_Q8C-M&kSK_9#kiDR&M z4hC0B@0GY&M=OhfS)X{@wYAs}*4zOhhCdck*?lv|WKYl!A8?y%(5mw|7f<7co8n_J z-zF2tgUb{A^PLM)Wb(IlDl|lhq{2#xENU&9 zmC5Y^Zy5F6Q(q#hqVb~QEi~?wlL{aF@n?KqElMkzGvDo{mA*689i}nW zwy81))<9=fR1!T0@7kej+MNh9?fWQtQYvd~qEL|of9q-_%jZVZ6C^_DnsTl zS`{VFNzSEqN{syM=UL&gOCVccvlzIoyx|h}u4Yd2_{FGI2PubsONNu?J4I>KVcu4E zXWfg97oa3p_FDrIy4&N`uODo&n+j+?tWg;;4)KB*jeXjL$|EDCsBQz#)+5JrCdXsV zbtV!V{B6$PNHQFmQKmtZWYuwy1wnL+Ebk5sGmU`E4d*zD*5D`=8_odoF=QnTUSYwfe zl0rnH*i_-rRmRB(*L>meu4by<7RnhSvTDO%T`Wfz_!x~mga)7}xJ;lgIJRsk10b)X zI))K9DkDWa#YMPaVJuEu4uZjD=_%;weVqw;Gr9%xZ@SZC@4LLDf-O2GCjYcC zB_D7R)2emhF`2$xtZJZha29jX0+w4W=mP%cBZyrgvw1(=H1R%Zc#PMJglHMiT-s|Qs4`$NobbxeO2z& z^>-TQbEh0SIS(X=Le!a;d`HLFXID%MEa+X!?~(9u4ZVb1H=~v-x3woZn|^9Iw&1W@ zWE5%vq)ZLzY7s~F9GD*B9H>ZOQ_rJMpebV`yHt>HylS5HNpYYCZqX z2hvv)4ILKj0{Muuat=Ew?^QrR^8C3FFhHW}4#Ucx;j#$=Jx?`Q0PKYc^>Y z0Fh~@P{{LBp~zFayt;(PZ70w1y$@1Og>>W(=Dqp6pKrXS*hsKZ^Ig}%6-DGdw*8F? z`=W(gRPdXD(rMkEWxIMXpWv<&ZhfRV@8x@JHE*gK#d=*!F&uvpo89JNhwKh^&zosk zln=>|LvWjjopp$vb!%c8Aa-LHHps6{weVJ<54Sc}Su!ET}kCI>rEG=z#tV${V z3qcS*)<_fc$c11&1Z^~letYhhnUuRs0dA?=Y+%o{%+QwbSxiH3Njuj-_G5e>BJ_=g z5L5#?7Aa4g#;1)5WsTn~%_Nu_OrMC$SeDjOo(L1_Ykf!21K$9b3v28B|hwq zv!K5gj>LbmKo~aeubu5zCsInt^c-xiy=M#T0g`wH%g#pjXWzP&(JJz z$H?$*nD`e~X$GJ=D*R5zlK=4rO~JkdWD4U>Jgn6}61&bpF~-JgaULsxqhWI~mB;lI z3xP5~F%Kn!cl{*GlY~p3cV&@Y&9;EI(Lc&s$xfB@8j{=z@9b%0_-P!sb(+hMf`*h` zP*L==Bwt9ctzTS5VE-PYMfTI|Pha*hTmb;Um!+1Hp}e4)w(>nU8+Rvrxc6VtTKOA! zXJV0SN=M-f?S8VpHT<`-wgWXWu-b(vYV+@?L~~USXFmh*pH9o7A$*V^?Lw-{eghFZPhyhRRP)9>}{$k-iCz zf#`|Dhz61Gmn3)|KjMz6i@k0(8SNUd2^$Y}sG9mJE2qf2q4Ha-_U#9hYT(&?1@pcC zcPdQA_H81F0DyzbsrXC#fFo@F#H!Ky?d~g_pca!&S$%@kaTZ1A14|u5%IUNBsVio;%4S_9;)%f#N9>N-zF*Af(wtOOisf4-#)WUQPm~j@K#J_i0+L1y zBwkP2!W{{1n=b-O!Oij^&5L@`$QGm2DhmNgAgQ8yq?)pDn%9lQ@sQZVU%}ZsOnP#q zr5&_Ned1sHqA13P@igDv*u`AsQSgQ7pGsaX88gRI_1v1@t7W7ZIcV4zf~}*DqE0Fa zu$3PM`bkPwD`c?&w_H z-HT-k%chKnR*9b)a%o;EzWeD0HN>5qg|CxO`87`ZC7;|6sXtbk^Z38DBDx~@mwt5F z0Qh&M>QC$6>3?-AU5!7e|Exv*jWE2d{;f>?6Zg-n%-=XI;J;jK{t5i&{`woZPxBvk m+dnD(>6d>~oP#bEy8rP~T@BL9kpKXsmk;Nq7Z=n0vHlC5oJI!# literal 21865 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�E+xH%D zqNBS&{RTLC)|JBP{$&zS>dm0iyL(?)_sau`-YmMTuWP#Q%?$Eci2m z&xM!!9qpozJ5V>J$P4JFQ(Oj-rOr4ZH+vHz?j#2eBvNE% z3x8_w7SniAHPO&J4#s2_VKQ;w6h&K`BsEKT1hMtDQ5}KRT zC*oks4+pjQTZd0FzV_}uIR3qLW`Q!WW)@*o7b3p|Iwiu?Kq`}Igy3v9OiNx$*$|Ko z2rmNzlUW5hb0$SlX^e2)a?3Xj2h@`bLKDXvi<&LkbTKw+%~EyuCP`$BKkTeyQMVR+ z3XSj=3prUJ$+yP7Gl3E!QlaBXOlj{)Pyu5hM^RyF+KjzRtmh=b$!e8WI!034wre;_ zi|hJ^w}kTs&q|G=P_)NbJ-W7pkJ<{Y|Oc74Nm{*3T*%t+XIqYPjPc z^mjwXZ_t(G)ZtlIO+9E=Y@;e+_s7>Q36SctbP*`mJ5NzJaXfuKaaLGwTYw<7vA zk0*+uuTAj(&fHFnrZr5`MT0`g`c`7BZ1xs{Dtc}pUs zy-GN5xj^AgS>e#>Jn|>jx8v3VZ-pm}F~IK36h+dhj|%my?POheIhx_C&&tk&d1+qi z_vT#Fh(nY~>#HrZ4e~K3{dCi8(KzV!u%h}_cL;L|r&?*kHQF0gS9JvZiH2UikzqqO z;#~D=Z;FttE(Lj>y?$EU)0LPpOIzUFAOrhl(r`~(e%=P5DpCo~Xtl8$9__up5ICS6cOs^;YGw-%)zgNs0IK$W%C*KX@eYk%7hBVD3wk z7*3gn2y#eXbUjy7`P}m37aG$2qw-8wysVIU0->86OYWVyVpL1I1*RZ}q-S==b{0O~ z8yd_$9^UP8H(=nq;tE2`@Zr&Gs%Kx+aa!CRzISF@8P2)$1HTN&-;p14stD8;1~`<0 zNB6iZM~LZ&4%a)}dqDg4Oa1Uqul*y4i1ri>_e~Gj{J;8!2SeC*CL-F?^4qs~VDnF~ z-}Md;$Jry|>>C=`{2R_sy}rXy4o0*47nI-i5D&*W7>zjaDE^FQUmx*sls&z|eRKK? z%0a!v!;ubN*VDfs{jQ&QIL^U{CjW+WKu_^-q=R>ek@$b_(7wLnPn2KsJHHF_>~X91 zO%p)b*IWGc6aMA*yCTgVeR$u5f!+Tm9YDnAPtV_Z=6i}Y`*!E#0sWdk9e?LC@6pir zZ5YUSfX?VYr1U?%erKK?477;hAnW;;=V8pW{qqT*J)EcZ3+301`aOH>CFXte(L5wg WYpIh0R5^eW2Ylc_(pk~k+xrja1ZZ6V From c42bcea60622a32bed9ec8ee948a394596cf831f Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 27 Nov 2024 23:06:26 -0600 Subject: [PATCH 068/124] update command comparison table.ods --- command comparison table.ods | Bin 5086 -> 23603 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/command comparison table.ods b/command comparison table.ods index 198e4a34058a925c6ffb972753f6441dc34e5e19..900cc0a2414e91cbd622c02be1b1de507a0eb46d 100644 GIT binary patch literal 23603 zcmb5V1yChR(>BP!;4rwm4K9PbyE}usyE_c-u7k_q?(XjH?(PRWaA4=&f4_fsBi?;C zwj-*#&gp(Cv$Lzap3JOPl!5q)4h9AT2Bw4PqZwku70Cbw2KG<+`xlt4rLC!pr-P}X zgM*Ewv7w8ly&a>Qoe6`zp|hnkgS~^Por%4%tF5V>3xl(Rlc}MJvxTXti{k&I&%fRL z?+yQNOT^yJ%+lP|>EGU*nHdd@jZJM#|NgXhV*Gzc68#6HrLCd4sWYRnrHiehgY$m@ z3>u8KS^_TF?4ZtHZuHgxc?;WA1(ho>OYy( z!O7m-$<+D(L*9SV)4|@s)#2~`bp8Knm46`rm!2>%F#isxzvlbj8{FTPvx}#VsWXFz zt<9B=ZQMpH%Ez|8A*aP0_>y$}(~w)kw`;GoX2C>EscMi%9>uyDX#}`bjr+p|k55vW z#zaSVC0Z65`496@mMn8K&g53l*K>Xu3HNkI;fD=J50q#L&Nxfq`^T%7w78nCvzMiP zB*rrid3V_39B0?3u5TR+?xTuCfHc-n~7ZpI|MUyk>upMbc{8wnA#CCPJlU^fcH-j27yO z%RVu~&*NI`>3YPSOWA|E3geWu%9Fq|2E0z@S9YFh@ygF~O12}teoVo^M4M`9v|lW> z8;csKgJ&ncJ9Eq?YJpG9R=Z0X?#d93B(|<3Vi!4bz2-En)~o)YsEbpf!X**V*C0Za4 z{|OTYd?G0UkT2{+882!d<6Mh*j_AJ&ZjO-}7>@lE&NCP;jo-5q$nkZvoC(Jj`<_x& z@uLMdm+-R1R)SL?-*PXZnIIjMmL(}X@yB~yh2X}0C>UpDDy4~~kG4@xZo<@@oxKbI zFv==6p3ox>T}HsbtNBGWB#V5yzzW-Hh^gfPy9a($4*|naE!q7XQMoI}Woj;=Q(5}m z`5toAP!WbLwZNxK%o@AD4L6)*3^K{?ovB*kLTJCBb8Em!PiCdTxvG;GL%B4AkzpXy0jesJB?USggT`)T zq6%5uMr^(@>(+`(`@06*9l_7bIbkBWL#zt3Ak_8;;$<$(?XE^9gp&Nm+K`Xk`6gPE zTxh)3w7IXCdSHZaVAoN8I2#tURE+TmzKI6qno3$W@!4Ig$91da$j^V4cC|=R=#9a2 zcIk1%XLSNKBCL886tOhyCc&H7aDz9S_6SQ`<>+RF5$Y7ZUAvDX0Go_|d*$kbJ98 z*;iu-AE%OGU(8{;S5Z6ov-x*3`z5Wdnsd=p^)4E8C@Fk)3zSSJ;`fV~+BRh}T$yN0 z$#!sx`?WY|NJ$**DF!LMe7Z8^`!Pw{A49ZrLPqP+d{bpl$o|1-7=F6mqEV`*aZCV- z7UsQp(o+*%?SqB6l}49J5XOar#?}&M6M$2saYcGk5VS;SnY;hJ>2Ylmf37oC6mgiX zIN>!lL5HAWnK&(;4F%tY3XnWqsJB|~~boJX7 zMplt^RQ9X<{RmYEVNm54+TWfy9_-iORGS{oqQ5Pc1YF(Jy=9KrX-I<5&biSW21u3~ zhIqM8<#CjkkkfTCzRSj3O~%&!v7C>{!gLJR8c*CXm8o%;N<)zT`1x}()cRef&LXu zVQc7o@0XvDLy7gGIbeBBT?~VgO`t-o1;Gz)Id7D*83*&OV%5W$<64!K%-zu^@{%Gx~DP|yeKX&=^_Gr z@r92s%MPD@$dWd%YuxUuq#r_-PVz(4v96BXasVCufmqH?#RGc$nt5lZu&SjJhY=gM zEFM2ma}1n$loBT~{0cqGTH24W#ev<1smCP77xQ5%9!kIzxcq)}3J~Em5{b22;ljp0 z-1vlKx&=*n=DI!yt<}t<8p6Z-P@SmOS0G`-IfI>vcfqioAXJ6WY8o-L6f5*J%RqAR z#FB}(aAwDt|J3qR6Vq0#DwnlqI^U`njp``!df+xcOi{YiPo1C8@mce zeoHzhp^fRgeaNTeQN@rFQ^waqs@a3Fs~1}s<1(!AzA5q-Qu;af?}Je0tDCvAXrJ2p zuGLHyJ@;@sc=aZ&!Es_b174Xy_I5M(q*gNbzK)Owfy0*( z82j;rraNroJgda{71Gr>cq)pVs;f?VYn$w~hLp9DsMZ7*O`=VJ)QKmQbc=Abm1u6b z)ShA6puB&RwWd#2lR6K8OYh@a9AAsM&ThAt#&WxJnyEP3_TMy*r$6AQu6t9tvQ&J3 zMWll=;C!8Ca?!^P#?p9mhnn7?bjFd8g5=0gvEo{QzSlS-34>s^iNU*@|9RDdqG*2J zfF|nhLe+l3a28%oMqsPTOx4_C{MbMV+{fpPk2}3bi-i#A~Tjy-JoO&aj8%wTS<^{q-(i}T%`@kJQf;+yk^=nEpJ9?)aZ2qE`rSd zl;$fwG`VO$QrX_nFArFXVG9hFZ~ept61CYph*c}LKt#=E97nTNHmRuH;uuuzUA8RS z=Q+dFJ|9C*d);QR-p=n}>rGhS%s&~EEJA$j&j9rU!xv4Lc0T8Cb;XQLB`HLw;5gF` zrpdTjy(yC1!{)0xSI}h{x|jgpIS#~-U(Vf;v1!Q<_G`$jnJP(Pj?djKU9=t4uin0s zE(acTfS+eP8L>M<0#@53Jk*1`_6R_FvEAQ)`n@&pw|wS)iTZ?4l!1o%)P1MkhyBa@ z1i`@m7q$OS%n;!(Gqg3dvotexc42TbF`G)7vI}BD4!+?HF`Zr&WQ2rtq8e|r%I#$f z{RzoAGTv`>}Qn3`f-(xT~%mb-(Yt!RUL zeYKgED1+e6DyS%zrrq6+hD$#+gq{PzW~QNl$|K`}SD}-%Z;8`U5@EP876z6uM%eQ4 z&-sDRhtmNs@~bvqoP_(*TJtM*zaXvZhT!t2`D%XMMsZig({AdC50;k<*# z^Yi3`VdfKOzoyYm)JAZ+&>R?l(s)m#CQ&HJ`D)oy$K? z*k5LSq&wwwz>eB|qmtLCroC3K*hu#&skPB&Ly`6y-|f4aw~CN~${^fVY_R0YOnox7 zv58lyW+7$PB1u9ebt)D9XcN>e5L>t1d*t9I=oTBE8QQir@i|-Xxj;ma8N-3A>*oFb zQNn)qb#Kq!od$s`NGb0pZcrnk%KScukgcV71XPg5qM5idy+kCgop|D~fp}_+D!5Y- z53pgHYGCSy#TfY1ji#9y?c-nf(*+AuJ+Jb4;qRkytm)DCA?;NjBxsY=-PwBjrXrA+ z>gD_`s;7pPkPOd$=@c{p9ocp@fkxFx^WBSB4@8t7>;ctH`0$#l~$^ zALw}}&8c`NKS8zEhYw3%gt)+4_ZCW)Rt?oJbW?FxD(nfmQv?Q&hMyluxxqJqFy>Y`WQ~wAy%enuw%r56HNOo~H&n}kYa`pE zx)GtVI7*{pvEw#db_ngMoH80t9oHX4PMMxL|JN&VuiEBImtBpGo;*^fwoqD5DuH0B zGR~nb#|aW4+UzEVQbJkGGM%a?iyQS*tm}83rDnztAC)yb;tGdPbdjzJ)Fh)*{h7M_ zTv#=-R>st%q3h;zi@dkKk8@C7c?H^AWbR6LsHmm3?NJ=0Ds?A)v2+`QI3$UiNBR=Y z+8kiYr`_G=WPv~~N=)^J;7HbkUQCDlX&R+HfFP*ojfQ8fyjl^uF*WcZGNfGU_b}S5 zHzh=@yT27P2{g(jmhsV$8B4D`4~K?@dNDj`dcevqK8|>p(LR_{;#PRP7ycLQgF_mR z`u6wNriX*>heD_qVTxG2bPU?x=}|{oN=v_J0mZ~~zL#^8^FPB^L{aT+uuz-Pap|c+R3LUTnT^p^y6H?vUEi-N~IP9{w8g=+Bl` z81lHB56nEK^(ML2+sv-Iff#mXa@gLe3B&ab^Of5GR9W-c)F6_4TshX+-yB~q+h_BF z^fc)Pv3MOGu<&8SAQ)IaX|FqvH{?{hA3Ivo2K4w&sxzFf3%s43opop0HjcCK5UOj= zxi~BsDzSq6q-xZD>Ap6soEl&INvIBU_f^A7N+wcL}svw-u*R7PpvJUyY#>AQ2W z?UoJRr^D5h+quHM@z|ZYjc9NfA0|cr$`qY7-IFmKqepdk zIvJu7q!spdQzm55JLc)ad%eQsH(oH5-)ub@byqDWe1I42b#|g(HP+oBNoaax!fuIvk znjmhmg^)KV&%D8=iFxK?VAL-qg7mb@arlNAVTUE@gY@y<%EgK$x3=bD&Bl590!A;aA)!2JVmTCn$+#4&M5%ij*!NLY;8jXhrZvaCvKQ4v^3dvg##4vtT=e^#D(! zn!InBWM9L?SBjXWbS;8aM+QdX@x@Op(`z1!@cP&HK$sVygb*$vK<*LmUyx+6G z{wK-##_tXn!~?mufYi%?Axw`;!NIx(A-FL@XU%S&pkADK)YgDIlWE4L{*V#m_V)AW z-oH14U^ag3@^@Wbrd}>j&(9IF=PiEB4k6qTHt%jS06b>^q4_9Lpa}toTy1FF?Bg(t zjt56h$D@$;Xr;92fiZ4e$pOT#jS)L9W-lZmJE9)DBLC}5$Ld&R0)gb4s*HtD4g!JY z%bGL#Eu^99hz#FWsIi#Rh;O@3F}?a^c9MC$hnX96;Ou0{Ohs(zqGL81ol!2xC+p9_ z+NdSNi4hx*se(o{Dl~(490uDP*Y2VUv)KgcXjj(ut$DJS#@5`8930%lo1ppR9_%5= z78%qEVvJwF#8wTD`X@7(wBYa86vP_e4m-qx#n=nj{qGs~kzpT}!SKVh`F>SU zerEn^?`D+6k|h*pZQK%faQP=9)Sq>X`rpF`C1#vS++rL7v&n0YzAsO6llzCNtR0$7 z-h~$;w>F#oXPX?Tj$g=AdCRy!e%8!NG4LODp?uYd+ugF`G~8SiV@-Y9GENOW)b{Z@ zXq~>~9gOaTtC>>YLJFE2)B`cLuSFTOfwR_LD7K_c*c}sf72KybM3&&>Y{;!88M<7K zN92n%-y3~iUN@hkv6r665E3`Db_3w-hKY6~;5-hC@2bua0GH5%5#?_@FWQuoG7D8m z>-!Vl3 z9?Wm>Pe(h8qAOn%?WTZ3OS{1Oo|tgH_jYdP=P(iVn@Su#o*&e6yO^}b&l69X3UclI z_hq6Slw3!r_g%S8015%yiaElHCn<@mi;|pNzfZO!o8xU2x0|+o*L1k&b*DmE|7SLE z#9%y9jQsw<)Wv(QR)Vi9s<0H;2#L86GNmM&|2!Mv@J(>#ye!e z_})UA->foLoenNH$K(!^SwH@4MD89kauV%j-|>iT1UIyY`4U7Jgz+CU`ZwbUc)lq)*So<8gth(~9o5vG6&Y_U*}-bZy+XHjXXH*Mz^oWM6f zhViO1_-vbuV_OQWQ6HJ6u_xz*f-Ec@A}96O%Sm4J*UN}L^o#|a39}7X4mw6s-=LBW zsdUx*KCZ(&T>FH-zOD76snTx{G^mFtZ2p4M<}t3|X=}M%LSvF6!_}1loXekIH+r|O zU9wx=M%VFZJ>aztP{-8pyC_G*W4;s|91=IV)T@)U?9M6N_m0Wwr?E0N(iHZ1>pUy|%2#f|Ke~0$_yuT{wUyoxM9uvOpFJ%t-uax;8kHgv2#l_Oj z{C`EGwQN-mI1xVldIHDXPw_@%pxag2C;I5=vWp$u-;Rdm7MFwmCMm)i-!2s7wV>Kz zlN-&HdLeRJL`hoZa%pThsfiquWzswNaF=StR|3 zzfnf&isrgu8)$mWi19U7*N(Wpc}){R+pvVNY}0Qyo?mN(!ouSV3xqS%)`DX8NS)q`pML`bQY!H61t;+dVtOio*F>Gw;OPKvaSFvBoF;<9!45Di&LWHKnCW_b!H^Hkn#SK<#0=vxY`fFjjkc$xCPJ^q!yNk~jXAVz$fzJ35bG5$*{#sdMm z8Svt&ggELGF^2ZkSyhQNz1!@(TXit>*fA-S-UX$H1gAA(ifM59OPsto6)8q((g_6?RapDiPM&ETq7_SA`gP)U>Mu}P()cj?3 zhsHHrv+=um4%Ty3s!A2_iH`JuO5qew%|LH;KEz6sP>0$K=d(Joq z(_eiotIX1CY#cQCMc>@$lK<=ECcGDlchJjJZ_Vh+PTx-sW@lY%=V@qR0SO~pb7fzG~M%=s2ePPvDQMPds?dBA+7=m0aEqvm!h*B zi+eX}JDiODWwC8NF(Ug>)(A4po)x65^$vn_p~;;17CDY=rS-#kbgc2#%#Au#UKKggv4-BCA(^o63 z?gwlAY_4t<7f;vY_jXqX#LQPsGlJgMbgFL^h9~dF4>!oA#5>A2%1>21HObyGUlBtY~md+rNCq&~@2Y-m9=;qoX4c!WqYlVkUjYwwd+|C8U~OHfcyR8&+_ zQc`ASW?^ArMMXtJLqkVLN5JrN(C|y>=yT}kOVs#F($q`Z>}%%SOX1RM*$S|7?X_VO z*t!ku+64{_3`|T+EG#T+Y-|i1yp0{dO`X0iT)eGczi;2Z9~>N9TwEO7zn?#X9v&V5 zub_{QkH1&^`T1GC6z&KHMhqq;Dx~VMex(cVz93n@{03^}VG&F>8I!`U-sZmS?+*zg z3}CAIqw@KAvss>cx!PwKaX^mjie{`y-*Tu>bbbwNFGr6*+KVJ5i9eme0~x+FM>@jJ z_~zxFhuWF68g3YsSUZ*?sxUU&bGTA83xxXRVfyA$|8Ni~yK}BMXDWq7x9V{1sH3g} zkpS?D@~g_h?AE?GgxjFOW#{`6yJHtzL~KaWQP zc6ads3ol1J(9+ifaG&bN$v1ZjZg~FKW)wqKx5&d4ZCTCILb_x)^Gpqp48M&ue)D?I zQ$xMBpF^S#9$Rn;30TI!Y_+U$993O#&EBvg4B#L4Gg^-&o!j~4mmj`oR`Zz0u~{0E zF9I;M=tHtA^n4M1Qd6gSF|aHO_8v$dxfO+usITEb#^z_`Jch+7kG?8mBQzBd`Wv5s z@GbvyX)oHd;3`n7ihpa(7W+%uLh*^7Y#DU55reVfSXr#`zN+%34ja^4Z_?6X**r8s zH7Ny3Mrh$e;GEjm^oSb6bkWQ(lY*8@uXXyxEz2cC0nDUFtmh>Mx*N~$;c=KI)HBYd zoyx$F15rWQsqRG8RouQ1x5ofbSa#0MRNB$;&WOX)3Z$*C52C{z5dV|s_2p%el*kS;nu#%?~SPc!C6wJ@Y%OQ*!xy8 zgY@(yuWoW+)Ao9{*0=Z2HM1|xp{lQ~9@j$vgx5<*upExF^zhKuy1Vt?L!dZ+*oN%n zLf7!_F6Dj!%Y8cgC3D#gYFelsYN;)Yn!v;yNgB<8ZySQrm`F1}F{aZC z#l`Ah-T1b9d>r0_8YmeBdOW;q-F?0gJ-$;A>z&b`rxtt zc>mh>A<^p5=6}0Sn?=~|>Gb6JY4GXs{%HTnyZXs=%_rdD_i}v;{^14MOQ&JjJ-nUZ z&*J9`LwdXYkleiJS^;NS2CUrEbR_?*{;-{y?)mKjh|Wv}MuX2o_-2Qf!>^C)JN`OZ{2^$Yeh-vc zSJ}Crcb<-FBOcx#>p?1&c-pUU{-CFGv@vGgGwaS9##qruedvR-&pX`h&lzjp`~CMZ z2~KYyN)BOGU+##<`>VXk&I6cN5iQ$0_PPNDSsliD%RDbtWNuEsgb%+yVIS7}@7w#3 zQqR{&Vv?x`g?20eSgxO6T)&s2 z=L=x}?@ARSz37hJtp{^PdBz+*|GWL&-J2S~RS(j0P7g}EU+(s|ZDL}F4*<(!WQew#nQnC@mtLeIIi$q zZpnYPa`qbwd{Pi|3h<+GpB!j-MA_zi_|fEqgN$1R`zWG&u5bJU3I#r6kZ}DD_V?r5 zZ;yv?V%v5X1Zo*a@ZK@?c5g4X&<5{aY>rxLazBNbdOU?Syo54*?e$fO_RAFXs`BDM z2>j+X^1Ev#N)NnSh``O|?4LEnb3O|~zcqvs%Cw z1(#S4pHE=ho8=nR>Fxlq`h;hL@xa=>`t-q>69`lA&+6gvVa#cX^X4osFq~NXh$`e0 zwf|g$=G-h%-`#2eVZCu;y#P%7O?*7*kc$z5PlU)9+eIcw|{`Z*neOmS2G$c;;wimC^4uxCzg>C0^#uN|>a`I+fy#otO5Sd>Ka45#T)aYSVSELKmVS>Otf3FpJKpkD^LMvp6Zi&!r7 zF9q)H?C0$AcQn(t7cTGLX6+X=6-7TXgL_MqGD9rq=HKs(Q|L)(RQmR?w{#$5On!wUz) z6GQo{aJnM}cAfKSE^Hns60{|HorA9OSkk$%@@}MWzb(18?B;rY(mF&Mn~(%W5zv0> zS6&BK@KoFA+3vj`J%BVW^uwa8*H=4<*omNb@By|R^TX7(S@n_t3Sj*vHpt6z%A_v? zUdGk6JLQPOwi|)t5~1e+v$m}I;q^c)7l9Y>z&Y*sHi7$Xti38A$opW(b_q)4>=~2A zUt2?URw51?lr?DyQN?nh26f=Y(%14Qm>-Y9-D9JsJUau$-V3q<-sffTFq}h)gJAMZ zYxC?wS60ID>D0vEL2ltZZ^3xNfs1K`wJRXCm%Z4ueZVN}`Jn!W%%TbM_s=}|CQF=` zEyBz?9>Le^XfH<-tDoZFfwNM(f#)_8x>$?$%Y?0;#kN{PyoDyYH{(S#q&fI1H`yxP{rRWT(iq_(F+B=%3B)0Rl8(vEu z>t&Qxp@=KMvcQF8Ibw-A)mHo48EAU}0JClx){}Bshzk36F`v?hi0%V4ld9=Zt&(QD zreNi2v#od@+rgNQk!NZ2DcOFYBQa9+NC^KpKmWF98G!XT!j>J;H+c76OD5EE1ktqT zr$tWcY_sL=tFQOC}4{%>SVjl!6NHztr?|k=jwOvC2uEgTp!e=3DODNg7 z6!E87Kwn|~1A5KgM}XtFUnRGEen{7GM{}&?S!_@bT0T7MjJPS^RcpYdM&tpofafL| z(`EoxxoDhqWln5nywQf+p?5C%Y(J>jGj*)=+frT-#~1v8K85bY#&fW`YaEZ{kJDKq z#qJH%u$049O^rWjq2;*4k=;6LeRfRC*D_YI#VCBxdP|&X17o6^F+9I?zu{#jjN!l% zl;bIcVgQCKQs3q3+I3YMMuI;E(5Hu)ty?0YMS6x35D}dtx!#(1vtp7MKmJ@!kpAVP z6HYy+D=Re^_QmCr!^>?0ZP%_KBYygaIkk^E49Hzh0c;y2LtSNR*IRj8>Kr6o=;624x@ z&$iGMhUrzc^RE!QPzkOIvhT%W9nKV}g?H2T_22>E#T*U$@^0l1ieRs>`{if#!n@08 zss_*P0NwMrV@Fn~v`7bc3rxcymonz-;&5U~gh z-sT4&(I9a&1UM_-{cNkLA6bq}+yw$U3?lKSVW-Z@TwAr|Y|)V)MB$g%Uu*g#lnawA zw!Q!_9<+})P*cK${cCM-I)5Hq?(si){0T#f3MOyKp%>w>ZHN`dF+=)@j^R;wO z1bR~6=*q0U%#WmVc%KJfs$j7yMtib?L%+ZUPJYd>(0WV-Rh4gzR!JD3m$IbOxAu}S zBZ?x-K>N39)R+sJsw;Zyi*2-&<8`QQ_!XB$IE0y|yBMD08Zl>y+-R<#Wu~SO^^^SZ zJjD{?9~;JGos^iT&RXxdt?}_AzI7N@th_d@nGN^Af1H6cV6Z`SZKbK3E93aV?!9Ts?veNNsxED&|njwv(cDcZUGm zR(LA|5PPAZ&ENulaMDahh*e_N>&yPQ|LaEGha8_3n<(^SRA5Hk8hZnVnC5>M>eFH@ zic9zfxAj8=eDp4%w--eXOhwrX*~3l^fI}6JwBZ@~&;M$1=`K)pd+khem`kVRphN9j zXN^fT)1KWkC}F?a5H-7y9hkx&)jg%&!mLs^JPfp6m7Q!>&gEY-qZoOE|GaVK>MSxI z@G@SIrtfACJ#ywSli`UI)h}R-`oC45j)e+-??!!E6k96S1ip5^?edmnpIO^}-fquP zrWPu$3q?@iKgV^~zR^q(6Oa>v`%)4^r@CpmF=B@fqxpf9o}JBlhW=&t6cj1Sd6%`!-Uirnqnfz{=xAV}aK9eX4bqXyhFLA6=O9IwpZ zfk6-)hwR#!52>XjLueb&Z7y;_V{65t{JDCe&ST)A;j0lUZ|PC~vOAd7ncy&plkRjk z9`0w4>xQS*Wv*q$V5WU{)c~w~s}64ez34TUk!t#A)NERU`exZFkf7BeXHmbr+FO$& zJl@^-@bCQme^w_!evV;#3Jr2BVXkgNmF6-09n;&Rc0}ZrJ51Ky^=_rz3}00+roQ;{ zgW-v;n#yZ3gqbpo>DagApxK&f{1i**g8|97iDlK$Qt$KPm77JvZy_P`ew4NvBRK8c z>97UMKM<&x8zZAYBMhyrV?XJ-6$J`w^P|6v!RTn05&!U!aLzAQ{50Dx%Md^U1pUCr zCVR%9MLr!_PJc7d2Gp2pFpm>eEvI>v>IK71B!{Nfx8U-BH){{a1H8;Viz*Ust2C#q z>I5^kV*st|kxY}%;oTqsF0NAyMa@*Ay@YTZp|$Sk!QC8a01&LNR(~!oPXb8B&bqH! z6mJcK| z3O{|!o4y$_PNXPPXdi;*u}bP6007S|gz-d=R5cws0!OnkonpCiaON(eE^H+3V|-K6 zbO9`zeq>)e&DG5m1?9#2Qrb#xZuF&?$taExbWKQb66!=pTTzY*Z>GLHD}mu_PieS2>qvYIf=z(M^XG;l~3AVzRNr?~tTC=Mmdcldkxq6S+h6)$740VxFL2 zX1EF9Rj0`)1Gqu;C9F`=cmxM=M_NR;|-9Rr73RBF}LPb*|hnk?xRcrd?tG{t@$IS z$BN%Rs=T2$c8bR3H0E?tALpO!7g4x);I{*DZ*vemn^w_vgXvq9*CSYC%>qVdK||K3 zpca&?Q|5V%3L?QKwj#En!uXw?w||EprXzE!GD$K)A?xavQz4;2rUyTS%mdo?Bvi&P2rp7`#{SBzY!a zj6dCB1AaTwRl-a574N3)K2O7E9&+~`NIl)*0vc~)B!hl{L@~~xOth2^O=;(PtO35z zJbL+_Fra`?koa6Y#8|JRjFC&p>58y{rbDo5Ebc#~ z7IJeO#=UnO>&=t9Ihqr3snHREurHc9EbBuH;0m$wYCK(0Qtdlz?bjhY;YkEZzjTAi z2wQa{6xxxzp@Si0z=hJuDvG7}`j|>jS-kGYBY7wp(wh*)LFqZ{dZvGig8>Og8PkWV zMv*n)BJi!D0P9lYZ{QcYe#Z1IBnsM={NhW1CHmdr$V{=TX`Ww zs2s)~S5&}(3fdW|IfV<#pZ8S7;!n3;Yx(6ViNur$*f3N`Z~#qk0TxM;_!=c#i5kwx zh^(jt+z6v9J;vOfBJvNL!oe_TJVAg+S~q(#de68#ArdggY6vv1W-4*I;aCzsCQmUc z#@Ro6w+IPrOp;&ps;*GYL?|ozCTkrq5?jKPRzG9Bui?hl>9C&) zuDk^9JHMB)15mFOBqCyF3Dc6=P^ZN@n`w;$-{mS=U0%mWYPxJk`l}c~jS$B)CvEFM zs7GXOGRDwJ4c7fHTjrWeE@SAgXtIR;szsvMnl_rIIoR1|=>ASl%t-UqC<-MF>;F z>%+f*gO=t?q1?dQcSKXCk$riaoiK)7d!`nFcX=~pBj2E%)bd*`CtclsoXy zTqVLIC~;0!T~I?QT(}rrA3Zj0h+VCp`n3xzHnGQMI}22x!X=)vp#*KWXcz+}{|>JX zA^#I#vi_S=J+#IpuK)$H4}bmtq5%^#zxZDpH}xLxr;--(k1k>!tPcY0q7{by4W>69 z^ua-f2Tb~my;LnHsl%)Fv*!b$gpDgP+m-6HG*|7_?tJ*27O5h(?)L`>s(D?ZQ!!QImS*(z5%S5oghGF*>Fn^Z#qJD zY_ay{oCXKZ;AoXtRcJa`uEG7$hAM2YzJTiSjvAXQrPy)+JC{2L(w3=UCXtwcSgo-8 zdOk7A?4TB7gw>XhK;&IU;e^V&ddXAqbMO>PE2~thFym<6UF@LiT=WZ0FWl_7c#~aw z`ZC9Ype@?a1=yVZ|)NdRF ztxXAKt`Pb5k)4=ZY5<|d$~=Ef$FZ{L(}xMY76evpof3>F`SyJ2!k=9tia?J^;Rj?} zrH{eWh4_wHtkbw|S}sa;b_dbX+CcOHOb8#2!%|OWr||{F8>GPaCB?#!@_3L4+RRBkq3cS zE}(h$BGbr)Wa%Fv2o2eI#b7vq+A;us6N5ywlzv=2kw_^zL+(Wj(lgTra5(DPdx#rFr$#u`Y2`wYTVcK6!PL~$cc$v+OdegMZpeu< z(WScy7>mYMLm2X=FxW2DDVV=YsqsgOV9dm`l~2%(CSI;u14&%0@l&3*YdMP>vuXM& zYJA^Hrpg_%i}E#YEY3`nB7&_t7%UkvVff&9kanctQey~Q%RC4X?O!l=K-=%aLz;uP zoU=q&S6g460IH^Ix`)eU3;Qc%Mt?#)%}!;+HO4=pXUzq=X}YS8!}v=e2QQ=zDd7st z4q(Q$^kw>M7Q}rL(A@0mi8hCVXFKn_Ru(gUUyd{r?2~Vg{#4b{RbJ;}&AB3RP{R1R zr(Srv-Qrk{N8_e#8WHBa#c$;>F3n<1YcotuKV_F0?#$~yImFwFNKyyCQTCUK8nGf8 zReJl@aGx1uIPO?WVVyx>dnsLLi~VW=RnMMFOL;{?q+vDZqJbw=IVvPYYE3@wJH4h3^C$B)%nm_rR@4(`}-$ ztLLijKefBq$;xcarU z;<9n6KNCe{t4oBolWwA6kgyWBiGIyckfsU?aLFt7d25ldB8~BITut`rt*{n~%C%Uo z)F#DuqGa2dXc+WWn@I2Sgq}j(JF?6s{fryqVjWZ{J1>caeVF4_L*KWE6Ki9#Ni_+; zXWmPyJTa}^+DtMxjNY?hWb@7W8k~h8;#We(6PV1 z{ZY=`$lmuIk{??(;ROpR0Llp{0FNnhLlr?lP3vXmoU9SUDB9SSo|ry?TWc^43rDn| ziuFXRO`KQt1#-%K9LnxnubZ@x>z_t>R~cswV+%rBn-L5i+DdRO$(+Blei#B zbCXZ8z8kSiN_ncb-}*~>bxnw}imJYLFjEV(T+lo}^YkO=cnlUXgMa5vaF3#wYa(Z(Dcn7Hqt-jttov!u;{?@m~WqM0DnDN&Uwp7 z8nCk4>gfI87j|A0Aq?008!1_)_C#|d&Bz}siIEin2Ty^_T)UnZ8~8q+w zN8z!isfK7{T~-9LQZ6gVowkpz@D`3Ibru(%?S1M7Lk0YyqG;~%nf3c6*> zL7a>J&I!WxhOpPZAfQm=R9eZ zD}L5WN05NKiUhos_OrtV3w?z#RL&n3?_0TvYqOZ{M3Fxx$T*}lO3g_Ik98!nZVTr= zTgO%V2w&N{gn7)O`HFM5I$5uO*5Gx29;$q}; z5&*7N$7Z~KG6ISE8nEw^r3z=9~}7m*(@ zfEX0t5s1DQ%+wGb-851vDS+5FoI!JmPVNGI>*kV?*@PSErex4|Egvvk8rtHQsi%Og zda}1rE~+ zl2h|Q=hoU`(#0Bvzw!L=yk!!8znj@9$HdyVsOo}TTkF7A!VXkd(w!YIL#xqyr?zEt zvy78Oo!`xanQgY;Vk<7k$9bba(Dpc9Xz~|gX>B2aHt9&f5C|po46iR;V|ncKaP!+d z-@73r>$c!pykd0km=h@Q=kwf+sv0(A=Zs5bH(Grz>4q!jeF%$&RQ3F&|jym2cn?>&0ri3qU(DY zfKV^7qupB-or}QKSB9d(Fu%M_6m9Dq8MmUjd_Wf*K+mL9A~*&?IlF1{5mO)e_uqVF z2DRKu3GlC2#M^gB;BTXiW}GHtn^7U_W(ohkYDLUU{>h|2Y+r}*O?Mo_I|8 zWt7ALY4rk3<7PxDf6}>rD76Uw6_5;p9tsg;t8Qd601HCzz_8(|tQq%%#ElKBcZoe^ zkz`w8_QG%E49EVd)yxKL;;*BnXM!YR$V9c5tG`i6W}j-1byK_1P_&FKTyhr5eGWYP zKl-@NuqL*xjr1l+6@p4rKtc^5MFAl+=>ig3q=nvF5F~WziXcTnDWNGPKtNC;AXNmx zAiYR0L82gpB2sR+_d6Fo_xP>+naMNj-7|a7vuEvhul2r_P4SCMpUY%);6I#B^YZFazCY}7t8LS{$F&kVgiOW=HS3JK>dn5wPq(5^AABv>f-h3N`)Pp{6s?e% zuyy??I+||NRr;eE*jmhQy7v_eJ!q~1qF@jW0+pqtcFDNt9+6EOU6ZW>%DsGuPnZc9 za*Pa(MT?K6bF1Z+U%je%q&b=%nhtew>PD64e+-NzK5t(DURwj7(AbS~-h@HEX>ZRG z=_28=bc}lLXgZt#O~H3J7BqLFc2l9R9Z?f>t@is9w|Lg*y|N+ zs{YQq;Q&+(uq>tJv3(X?9HvYNuhgV0J1wIcGbfjv?R!^#3b!* zB(nXYK)XVL6*aM;^$Yb0I(7O&GF|DUq#-}p{k5aH`Grh;b{B46H;B$)BJ7?W-Or>0 z36#4h-2}|PW!{T)i&Nt41nj$fUUcmFF4obP?3UA;(qiL0{x-V(@@X}1MbKldYjg7a zdkqioS+TtEQFwFR&N291FA}G^GA|q2!DG+Ar<#$)9R>r_~4G;kjze4D|QqEQSz0oj$S(4^!_V>Vbwz}ZeU;OAu$&WYWTpS%Kb6ew6~ zX||T^8f{0x66O`I(8sJq?*(L0C4cb@KcvN=;}T-C1WlkNhAaRd4N3`nQIHu@0rhnb z*5ZShw;XdS2-8GoWolb@g&xsSau&QsG<26^I1mBPdqTqtWS-6D@&Me=8VY1mQD>x@JZk8TeWMTQ1tRH z6YczAl(<=8N^bAypqS(~DQ8+;I#&Z$mOYQ@2SYaPH8U7kWe2oQj%+)*L}flPtH7x=`M|A|sWO1w$aG^p^*Y$uI}0o0FO~PnRB; z8Wh*%{dmh}T@EW=$zG%8disPGY&g@Wzx^^rgip$^85nTiOt!Sy5Y~>8gXb(*2v-%0 z%xS1-=m_14d@~eddSgHY>lE8`$_?Lv!p06ubqT+H_ksHX6Qv54x>C*7LyRlE++A22 z;N$pm&K}X~6c;L3Ua!mXC@`jpV~<)P1{)|i@e@R+#k;H9eFtV@THQL-M~6VbR1^a0 zvjlygTQ7p&M%P_V2M9-1IG&OuAqm-JWH%3nm6STXtK(kycMR zkU-Vh!yZ%0SrfdO0fx}OP;U}&$JNmppUqM%M+0>>;?_6=-H6Aon9_p;~egKo`>H7l%DI|+%hSr+!^xi z#ARxeJu!MzN#lB9no=Kb+g2RV*-{tt4v0@+wA$*3^x5#!$}{4qO}K9D=Ts5Y)hWN2 z(I>jmxZ-?Y!ZQzlcS1D^TTguLBX9D`a(`0flTJ;cCL{(GggqI<7)oa+6YNBWNADhH z^lTAB=2x{V*_?N1eHs@k)f>frJWXRpqzvU~X-u%_8!dFGqu0E|f6il_UPU&|GrFo= zR4_avd^O8_f1nrVRmTJ}gjjD&vIgsV#kbB^*V*(SX?D|Lwl_tA1Y z9{R$NMSA#^%X+6o<7fd5q}F5dsn-}At>z4?T^(ESLubbYhnW)oO+F_}pfTTQFZI5~ zvQLx0Rb~d|ZzINh@U>e3V>isa;?d7t*F|34hZ?L0ObY(s*9f}@eX_3;ZLP%bslL!4 zvXzagCGLb#GcSH}C<2Zxs?i&m4)ItyJul`o(8GLh?Rw%^)jInf+1!c>ciIi+F_n&D ziz03n10sqh<>rRjrrEj!y;snw&r!=FdLtDVL_Ry(^~!pLa^_OGS!uB;BHrLCtAI~B zBQDESTq@y_S86&dJUHGv(b$SIQLCaa6|^!<^VNNImEAYnC3=G1>oR2m)4+Xh{wNu< zi4>1-Og+Ta$Xu%Ybi&xhpE+8)_i>vZ*k%Hhcg#9VKG8EFqE;Ji;4XE~!sDAd;dq2k#y3c`i9jYWilcsI%s&sFB|N=wv)<7iCf=2DFf4?ba?W>Gz0vaS*=jaf^u*T@QgNhqmLy$Jnyj38RiT zP}dyK6`n!T!v&Uydk6B$Yk;OT{^mY(&Z1H@pCw!AVm&)Nib10MMScL!rR%(QHCN6WH|qKKjSdHiWVtkZ-i-ab(1N7FI&p20Tk=j1R(Z?a zuYC`}TU$-6L5Tf_T9|&KN{nwF^c#3d%uip!fYOD7lW>eZaXszcgM11Q z>(tr=TxX_8EX%%J+-eihU6IEOQe_+DN1hJuPm68BU%ntMqADydPOmV|HG06W7AEcZ zGRcfV+{d3vZR3{C?vsb{6%wkDdR_>D`^qb;?@(gAZ)Ds}CPgvwT~yJ}<7Gr&m10Hs zC?ylRa~*2wVxdlVfp@RRpF7P*j`!|egZS9qhxC2XN*(Zg=x?dr zi=?ivg~6Ha>O!fOiaL&Eb@BDg-F2=05`XTVXcmOZ_t$pDtg6V*2RD)phc-Z_{$@2#nE6p-K4u{exZUWM0@2=%Y$5)4MbP9pN7LWn>t5%=$aD zMSOLn8mL2PV?s<*y<~5YfOZ{59~Pt|>axBS2)ZdthD3+HKu+btlp8tss=84kUw^V- z=E_|zAXJq(9JrI)3aAm<(uLyv%gKxfv|%=dwPo^3gRBaoQP`OeH&ulASg!;3zvqS# zsm_LlfJ!t_5l;3x724fI6(K*9s8MmvBm>Pon#_b)_Xqlox4AUje3%aqDa?ueW?3T2B8y%OTwGp@LP-qg)E1^rK z`rPUp%I4Y1BL)om%Y*t^LLzJOYDt*C$y@MfP#pB80K6y-->rBy=S~C+a)B0Met`qQ z0LsRs9ps)mfa6B?l*BeQCb{=eMiz^urq0qU-67VvFs?wWXva+AM&fccAmA1w1(1|B z-Iyv!)Zu6f%g9=p?~R9Z0BKmY&sjF89W_ZaicSyf(9(h3mM=&6Ahmk^!f=5wL}3^7 z#ieqL(dMM!-T~=BJM)rN{GlSUa;q>X$;xc;J~_`L`<$O(>y;bR)MK)|rmLUQ&|zLU z#uk#B;4nibjO~wriw;MJBB#;&YB-&i%;QA%|4?XsSYo+08S+RJgAA3Lu=C~dm&JM6 zK$>W1+C<_HpXD{%K)OTkL1*9CxM&>$1L7XpToZ6$E+?j#8t!)r4Ue+j-^t>-r+qp; zquupX4s&EEK(M3(}s71U+k=% zBk~^RNVf&5#TkB%ag%|+Y}h2VWCjUlcfD+Up?2HtLjI>nAUh--azJQcURFE zjrWU>m-VqkLUW7AghWC)g$~^;NDSO#tp>qmDK?+)7pbtvH>rhzQ=-ma{`qe*DVgD5 zn}OZn^vH7Iw=02{MAta)%|GGX6Ehzi4wb=deZ>XLD-;OHe$iw2^x)I(_Ya^4M8@;& z>#afUXqM(C@@eu(T&JO(ADFoCVc)3wvL4H5{@6!oFb`9N!vtUP_j#PCS=v4<{8T$| z$Pms1jMWHqn^AvkrwQzzK%%xja(gu|v{pR6X^^4NzK-aIt zL;?;@_cIBF68Sz)8IrOtU-E85MpG8`-D3;zX_gk)2pP$%YaLYNCY%kzEA|E=WKL}j z2P?~jb85&xweghE(MKk zp3gIg%)M(oMOwB|#wX&D;v88^N6eTTc-_!*8`9M@UaM`opf1Hf#?8uVG=X%sVZG+} zs&Pq$;6qSy{5E&yC&Q|hGSdxHfnM;72d1R!x;QtP65df=kb%K%zX#@>4N=)Qvz-;4 zZ-(6^ur#`=k_8t;Y51(4G7QeQ77{94gio%ITUDT3b7-#>pzb@15gD~s`+8k>T7DeX zTTsd?dnxu#)*yVJ;qeIBj*n7iKD!-n2R}X!&1cgDjyEeNu@yNN*V~d4`R>89w^rD* z;I!>M6Cw1Z%w9FK+&H*AbIymumS$7FfYBo>`TS?aWWt>cqZ_5>1>!wmrK zaTeRm1z)ttpZb7Whuio$Kz0jHwI8*8UDK%K!UHN)@!eX}<)(gi5aj!&kIPYqx8V1{ z{y*RM1&*;Xw8+-T+dg_Mi23y0AErP)+YjjTkTgRB=LXo|MGY`W2IH6lhWTRx)WuzJ z%bzkQWe-qxtL&9S4ROJ4_OQ7PCDK({<)tAk{b=F(QkH<`KWT{m#YHA5cQ8#8_DG#cN}1<`)2nrtP}y z4QnobIK@%#Q)tk0KH}d?aZ1e*F{#9y#Xwvp<|XjTN_X^&(FNR8`g91t6S!qLR;lR6 zhrkykfDVUDfVy~PU2mp~7Nps>p6`cPZZ4kd>Un~L8}`d0Wl^pLS5tqVFo|qF_XNDX zrOU3TWQM-({FKEm0K93&yVO8>a1BlBUhD5Z{wOhFu+$yXupjef=cWPLK!@IGUNMD3 z&z`~tpk6@rur$DJDp+rJW>qi$R)y^N%ITWtFR5KGty$5PR`VGn1&qX>K?ttcFSpg@ z*D<;tPLZJ!Vq7Ekv!K?|uz;YSyuJ3^ls2wH_#>n!HDUJ7}!!_W1~OF=*zlgQSlb;`LH&Gj}Jke@4* z;ovf*i!D)q>MGIcAl2gg!#TKFW{(8nnUv*}k~sxy?W9LIS|GEbNaZQ0>`o5PNdbxZIX$&^0dBMpz&_l^w>BmszYqqc_YMoY*|mI$hIc^-^(YFBS!xZR&pa6XGV!-b0jy^ z_0>?SHj#f;c48x5rKfJ9B6>q#L;SxIPsAGgibpD*Z9HexcjWJN)PV}W7OVQ8pAWJW zQsp^%KU@#DiA$Y0Yw0srn!u_vCi!9gFju^K>{&46Jg>lNwQXfcukhPpw?t@L(~k8P z)r1Em`*yy}LoNN-(l>4XqFfpL+Lft#%(obC7y%I^gH64PJnx$~eN7rnrO4Lsk?;%p zEV6K4QqJ6)K;eP2ymAzAaOcZ{jgoZ~^888K6So2^Tg<2eIFpyNPd$287KH@2wL`7z z&s?{>H6VYz&4jwIu7KA^t@j=MZb(3mDtXbssqn{Uc8dcA)zAn0ijDD9qJ>FsF21c< z{Q9&?_gep$3gCJm>_s6kW}6T1|E}N)Bi^|9HoIF|g$woJ%2dSQKt4@ex7Xd!Pd~xm z!VJwb$@MfZRAKn-?hZw8?5Pv^%7fjnL^)O}YLD9Rn;>Fwy9lue)KKRXDJ#jJm3xl| z=*0fg@%_8k$rU6d$3n6vW{{iJ!rQbi}{C+n+|A2F%{QGB=--oQ^ zA5eZ*0{%17@6R>+Pe{Kg1OFN4_vhjy{oh_bQ40PK%3q3bziP!D7bu^Y0TJayIry(v z_%FX-MbeIS9Zt-Jc>3Q-ho~#}ch6s?L5@Y#PVD*V-z3%k?)a<7#IbV7iQ(CaP5z&b z$ltwwE#LnAMkk29_NyGlU!H#|?>=d-{`{Ydz5hb_tD}CMJ&q^l69b$6VVXA7p`bjz QjEVSjAWk~3AjfC_0>OGzx&QzG literal 5086 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 From 0572137d202c90552610e9234f45efca305d5e5e Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Thu, 28 Nov 2024 00:43:04 -0600 Subject: [PATCH 069/124] update ui.show_text to use DrawStringCenter, update ctrcheck rewrite --- arm9/source/lua/gm9ui.c | 2 +- data/luascripts/ctrcheck.lua | 175 +++++++++++++++++++++++++++++++++-- 2 files changed, 167 insertions(+), 10 deletions(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 730dd843a..f03b6bba0 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -142,7 +142,7 @@ static int ui_show_text(lua_State* L) { const char* text = lua_tostring(L, 1); ClearScreen(ALT_SCREEN, COLOR_STD_BG); - ShowStringF(ALT_SCREEN, "%s", text); + DrawStringCenter(ALT_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", text); return 0; } diff --git a/data/luascripts/ctrcheck.lua b/data/luascripts/ctrcheck.lua index b917c9b03..374c5a831 100644 --- a/data/luascripts/ctrcheck.lua +++ b/data/luascripts/ctrcheck.lua @@ -1,7 +1,40 @@ local version = "5" local store_log = false -local NAND_HDR = "S:/nand_hdr.bin" +local NAND = "S:/nand.bin" + +local status = { + ["NAND Header"] = false, + ["NAND Sectors"] = false, + ["CTRNAND"] = false, + ["TWLN"] = false, + ["TWLP"] = false, + ["FIRM Partitions"] = false +} + +local function show_status(current_process) + local function get_text(requested_process) + if status[requested_process] then + return "\n"..requested_process..": DONE" + elseif current_process == requested_process then + return "\n"..requested_process..": ~~~" + else + return "\n"..requested_process..": ---" + end + end + + ui.show_text( + "ctrcheck v"..version.. + "\nCurrently processing: "..current_process..". Progress:\n ".. + get_text("NAND Header").. + get_text("NAND Sectors").. + get_text("CTRNAND").. + get_text("TWLN").. + get_text("TWLP").. + get_text("FIRM Partitions").. + "\n\ntest" + ) +end local function make_selection_text() return "Select which parts of the system to check.\n".. @@ -20,34 +53,47 @@ local options = { } local function chk_nand_header() + show_status("NAND Header") + local hdr_hash, part_table_hash, header_magic, success, hdr, stat local log = {} - -- TODO: replace the multiple file reads with fs.hash_data once that's implemented local is_sighax = false - local success, stat = pcall(fs.stat, NAND_HDR) + + -- check whether NAND header signature is sighax, based on hashes + -- sighax will always be read as valid by boot9 even without cfw, so it's just used to check whether custom partitions should be possible + success, stat = pcall(fs.stat, "S:/nand_hdr.bin") if not success then - table.insert(log, "Error: NAND header not found.") - return + table.insert(log, "Critical: Could not stat S:/nand_hdr.bin!") + goto fail end + if stat.size ~= 512 then table.insert(log, "Error: NAND header is an invalid size (expected 512, got "..tostring(stat.size)..")") return end - local hdr_hash = util.bytes_to_hex(fs.hash_file(NAND_HDR, 0, 0x100)) + success, hdr = pcall(fs.read_file, "S:/nand_hdr.bin", 0, 0x200) + if not success then + table.insert(log, "Critical: Could not read S:/nand_hdr.bin!") + goto fail + end + + hdr_hash = util.bytes_to_hex(fs.hash_data(string.sub(hdr, 1, 0x100))) if hdr_hash == "a4ae99b93412e4643e4686987b6cfd59701d5c655ca2ff671ce680b4ddcf0948" then table.insert(log, "Information: NAND header's signature is sighax.") is_sighax = true end - local part_table_hash = util.bytes_to_hex(fs.hash_file(NAND_HDR, 0x100, 0x60)) + -- hash-based check of NAND header partition table against retail partition tables + part_table_hash = util.bytes_to_hex(fs.hash_data(string.sub(hdr, 0x101, 0x160))) if part_table_hash == "dfd434b883874d8b585a102f3cf3ae4cef06767801db515fdf694a7e7cd98bc2" then table.insert(log, (CONSOLE_TYPE == "N3DS") and "Information: NAND header is stock. (n3DS)" or "Critical: o3DS has an n3DS nand header.") elseif part_table_hash == "ae9b6645105f3aec22c2e3ee247715ab302874fca283343c731ca43ea1baa25d" then table.insert(log, (CONSOLE_TYPE == "03DS") and "Information: NAND header is stock. (o3DS)" or "Critical: n3DS has an o3DS nand header.") else - local header_magic = fs.read_file(NAND_HDR, 0x100, 4) + -- check for the NCSD magic header, if it's not present then there's definitely something wrong + header_magic = string.sub(hdr, 0x101, 0x104) if header_magic == "NCSD" then if is_sighax then table.insert(log, "Warning: NAND partition table is modified, but there is sighax in the NAND header.") @@ -55,17 +101,124 @@ local function chk_nand_header() table.insert(log, "Error: NAND partition table is modified, and there is no sighax in the NAND header.") end else + -- your NAND has a minor case of serious brain damage table.insert(log, "Error: NAND header data is invalid. You've met with a terrible fate, haven't you?") end end + ::fail:: + + status["NAND Header"] = true + return table.concat(log, "\n") end local function chk_nand_sectors() + show_status("NAND Sectors") + local secret_sector, success, secret_sector_hash, sbyte, twlmbr, twlmbr_hash, stage2_byte + local log = {} + + if CONSOLE_TYPE == "N3DS" then + success, stat = pcall(fs.stat, "S:/sector0x96.bin") + if not success then + table.insert(log, "Critical: Could not stat S:/sector0x96.bin!") + goto checktwl + end + + success, secret_sector = pcall(fs.read_file, "S:/sector0x96.bin", 0, 0x200) + if not success then + table.insert(log, "Critical: Could not read S:/sector0x96.bin!") + goto checktwl + end + + secret_sector_hash = util.bytes_to_hex(fs.hash_data(secret_sector)) + if secret_sector_hash ~= "82f2730d2c2da3f30165f987fdccac5cbab24b4e5f65c981cd7be6f438e6d9d3" then + table.insert(log, "Warning: Secret Sector data is invalid. a9lh might be installed.") + end + else + success, sbyte = pcall(fs_read_file, NAND, 0x12C00, 2) + if not success then + table.insert(log, "Critical: Could not read S:/nand.bin at 0x12C00!") + goto checktwl + end + if string.len(sbyte) ~= 4 then + table.insert(log, "Critical: NAND is unreadable at offset 0x12C00...?") + elseif sbyte ~= "0000" and sbyte ~= "ffff" then + table.insert(log, "Warning: There may be a9lh leftovers in the secret sector.") + end + end + + ::checktwl:: + + -- verify the TWL MBR exists and is retail (there is no good reason this should ever be modified) + success, stat = pcall(fs.stat, "S:/twlmbr.bin") + if not success then + table.insert(log, "Critical: Could not stat S:/twlmbr.bin!") + goto checkstage2 + end + + success, twlmbr = pcall(fs.read_file, "S:/twlmbr.bin", 0, 66) + if not success then + table.insert(log, "Critical: Could not read S:/twlmbr.bin!") + goto checkstage2 + end + + twlmbr_hash = util.bytes_to_hex(fs.hash_data(twlmbr)) + if twlmbr_hash ~= "77a98e31f1ff7ec4ef2bfacca5a114a49a70dcf8f1dcd23e7a486973cfd06617" then + table.insert(log, "Critical: TWL MBR data is invalid.") + end + + ::checkstage2:: + + -- instead of checking the full sector against multiple stage2s, this just checks if the sector is "clean" + -- (if first byte is not "clean" assume stage2 is there, can be done in a better way) + -- if stage2 was replaced with trash it would trigger this warning tho + success, stage2_byte = pcall(fs.read_file, "S:/nand.bin", 0xB800000, 1) + if not success then + table.insert(log, "Critical: Could not read S:/nand.bin at 0xB800000!") + goto checkbonus + end + + stage2_byte = util.bytes_to_hex(stage2_byte) + + if stage2_byte ~= "00" and stage2_byte ~= "FF" then + table.insert(log, "Warning: There are likely leftovers from a9lh's stage2 payload.") + end + + ::checkbonus:: + + -- check for presence of bonus drive, just because it might come in handy to know sometimes + if fs.is_dir("8:/") then + table.insert(log, "Information: Bonus drive is enabled.") + end + + ::fail:: + + status["NAND Sectors"] = true + + return table.concat(log, "\n") +end + +local function chk_ctrnand() + local log = {} + + table.insert(log, "CTRNAND checks not yet implemented.") + + return table.concat(log, "\n") +end + +local function chk_twln() + local log = {} + + table.insert(log, "TWLN checks not yet implemented.") + + return table.concat(log, "\n") +end + +local function chk_twlp() local log = {} - table.insert(log, "NAND sectors check not implemented.") + table.insert(log, "TWLP checks not yet implemented.") return table.concat(log, "\n") end @@ -74,6 +227,10 @@ local function nand_checks() local nand_log = {} table.insert(nand_log, chk_nand_header()) + table.insert(nand_log, chk_nand_sectors()) + table.insert(nand_log, chk_ctrnand()) + table.insert(nand_log, chk_twln()) + table.insert(nand_log, chk_twlp()) return table.concat(nand_log, "\n\n") end From 8dca84b067d7c84d605ad4aaf298c9f01f61456a Mon Sep 17 00:00:00 2001 From: Wolfvak Date: Sun, 22 Sep 2024 23:29:13 -0300 Subject: [PATCH 070/124] Split up the ARM9 code (.text, .vectors) and data (.rodata, .data, .bss) sections into their own ELFs. This allows us to use more ARM9 WRAM while leaving the 128k BootROM mirror intact. --- .gitignore | 1 + Makefile | 14 +++++++++----- Makefile.build | 3 ++- Makefile.common | 1 + arm9/Makefile | 6 ++++++ arm9/link.ld | 20 +++++++++++++------- 6 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index ae5aeca70..f9ed58ea1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.obj *.elf *.map +*.dis # Precompiled Headers *.gch diff --git a/Makefile b/Makefile index da57d2e1e..5cda78c86 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ export CFLAGS := -DDBUILTS="\"$(DBUILTS)\"" -DDBUILTL="\"$(DBUILTL)\"" -DVERSIO -fomit-frame-pointer -ffast-math -std=gnu11 -MMD -MP \ -Wno-unused-function -Wno-format-truncation -Wno-format-nonliteral $(INCLUDE) -ffunction-sections -fdata-sections export LDFLAGS := -Tlink.ld -nostartfiles -Wl,--gc-sections,-z,max-page-size=4096 -ELF := arm9/arm9.elf arm11/arm11.elf +ELF := arm9/arm9_code.elf arm9/arm9_data.elf arm11/arm11.elf .PHONY: all firm $(VRAM_TAR) elf release clean all: firm @@ -104,9 +104,13 @@ $(TRF_FOLDER)/%.trf: $(JSON_FOLDER)/%.json %.elf: .FORCE @echo "Building $@" - @$(MAKE) --no-print-directory -C $(@D) + @$(MAKE) --no-print-directory -C $(@D) $(@F) -arm9/arm9.elf: $(VRAM_TAR) $(LANGUAGE_INL) +# Indicate a few explicit dependencies: +# The ARM9 data section depends on the VRAM drive +arm9/arm9_data.elf: $(VRAM_TAR) $(LANGUAGE_INL) +# And the code section depends on the data section being built already +arm9/arm9_code.elf: arm9/arm9_data.elf firm: $(ELF) $(TRF_FILES) @mkdir -p $(call dirname,"$(FIRM)") $(call dirname,"$(FIRMD)") @@ -114,9 +118,9 @@ firm: $(ELF) $(TRF_FILES) @echo "[VERSION] $(VERSION)" @echo "[BUILD] $(DBUILTL)" @echo "[FIRM] $(FIRM)" - @$(PY3) -m firmtool build $(FIRM) $(FTFLAGS) -g -D $(ELF) -C NDMA XDMA + @$(PY3) -m firmtool build $(FIRM) $(FTFLAGS) -g -D $(ELF) -C NDMA NDMA XDMA @echo "[FIRM] $(FIRMD)" - @$(PY3) -m firmtool build $(FIRMD) $(FTDFLAGS) -g -D $(ELF) -C NDMA XDMA + @$(PY3) -m firmtool build $(FIRMD) $(FTDFLAGS) -g -D $(ELF) -C NDMA NDMA XDMA vram0: $(VRAM_TAR) .FORCE # legacy target name diff --git a/Makefile.build b/Makefile.build index 0a36f6fed..49b2a0314 100755 --- a/Makefile.build +++ b/Makefile.build @@ -11,11 +11,12 @@ all: $(TARGET).elf .PHONY: clean clean: - @rm -rf $(BUILD) $(TARGET).elf $(TARGET).map + @rm -rf $(BUILD) $(TARGET).elf $(TARGET).dis $(TARGET).map $(TARGET).elf: $(OBJECTS) $(OBJECTS_COMMON) @mkdir -p "$(@D)" @$(CC) $(LDFLAGS) $^ -o $@ + @$(OBJDUMP) -S $@ > $@.dis $(BUILD)/%.cmn.o: $(COMMON_DIR)/%.c @mkdir -p "$(@D)" diff --git a/Makefile.common b/Makefile.common index d8c82582a..24036db81 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1,3 +1,4 @@ +export OBJDUMP := arm-none-eabi-objdump dirname = $(shell dirname $(1)) diff --git a/arm9/Makefile b/arm9/Makefile index 0c5b8520f..15d305cb8 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -15,3 +15,9 @@ LDFLAGS += $(SUBARCH) -Wl,--use-blx,-Map,$(TARGET).map -flto include ../Makefile.common include ../Makefile.build + +arm9_data.elf: arm9.elf + arm-none-eabi-objcopy -O elf32-littlearm -j .rodata* -j .data* -j .bss* $< $@ + +arm9_code.elf: arm9.elf + arm-none-eabi-objcopy -O elf32-littlearm -j .text* -j .vectors* $< $@ diff --git a/arm9/link.ld b/arm9/link.ld index 5ff9a6359..b8beedf0c 100644 --- a/arm9/link.ld +++ b/arm9/link.ld @@ -5,7 +5,9 @@ ENTRY(_start) MEMORY { VECTORS (RX) : ORIGIN = 0x08000000, LENGTH = 64 - AHBWRAM (RWX) : ORIGIN = 0x08000040, LENGTH = 512K - 64 + CODEMEM (RX) : ORIGIN = 0x08000040, LENGTH = 512K - 64 + BOOTROM (R) : ORIGIN = 0x08080000, LENGTH = 128K /* BootROM mirrors, don't touch! */ + DATAMEM (RW) : ORIGIN = 0x080A0000, LENGTH = 384K } SECTIONS @@ -16,7 +18,7 @@ SECTIONS KEEP(*(.vectors)); . = ALIGN(4); __vectors_len = ABSOLUTE(.) - __vectors_vma; - } >VECTORS AT>AHBWRAM + } >VECTORS AT>CODEMEM .text : ALIGN(4) { __text_s = ABSOLUTE(.); @@ -24,24 +26,28 @@ SECTIONS *(.text*); . = ALIGN(4); __text_e = ABSOLUTE(.); - } >AHBWRAM + } >CODEMEM .rodata : ALIGN(4) { *(.rodata*); . = ALIGN(4); - } >AHBWRAM + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); + } >DATAMEM .data : ALIGN(4) { *(.data*); . = ALIGN(4); - } >AHBWRAM + } >DATAMEM - .bss : ALIGN(4) { + .bss (NOLOAD) : ALIGN(4) { __bss_start = .; *(.bss*); . = ALIGN(4); __bss_end = .; - } >AHBWRAM + } >DATAMEM __end__ = ABSOLUTE(.); } From 76c322a9ac6478de908d20a06d0dbcba726558e9 Mon Sep 17 00:00:00 2001 From: Wolfvak Date: Sun, 1 Dec 2024 18:29:17 -0300 Subject: [PATCH 071/124] use the makefile definition --- arm9/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arm9/Makefile b/arm9/Makefile index 15d305cb8..b31133bcf 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -17,7 +17,7 @@ include ../Makefile.common include ../Makefile.build arm9_data.elf: arm9.elf - arm-none-eabi-objcopy -O elf32-littlearm -j .rodata* -j .data* -j .bss* $< $@ + $(OBJCOPY) -O elf32-littlearm -j .rodata* -j .data* -j .bss* $< $@ arm9_code.elf: arm9.elf - arm-none-eabi-objcopy -O elf32-littlearm -j .text* -j .vectors* $< $@ + $(OBJCOPY) -O elf32-littlearm -j .text* -j .vectors* $< $@ From 0273326dbb69085bc7b1488a0d17c6e22ee39d04 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 1 Dec 2024 23:41:14 -0600 Subject: [PATCH 072/124] add title module, move around some functions, update command comparison table --- arm9/source/lua/gm9fs.c | 15 - arm9/source/lua/gm9fs.h | 7 - arm9/source/lua/gm9lua.c | 25 +- arm9/source/lua/gm9lua.h | 11 + arm9/source/lua/gm9title.c | 183 ++++++ arm9/source/lua/gm9title.h | 6 + command comparison table.ods | Bin 23603 -> 23971 bytes data/luascripts/build-cia-test.lua | 3 + data/luascripts/dec-enc-test.lua | 9 + data/luascripts/extract-code-test.lua | 5 + data/luascripts/install-test.lua | 3 + data/scripts/bkpt-test.gm9 | 3 + data/scripts/ctrcheck.gm9 | 791 ++++++++++++++++++++++++++ 13 files changed, 1037 insertions(+), 24 deletions(-) create mode 100644 arm9/source/lua/gm9title.c create mode 100644 arm9/source/lua/gm9title.h create mode 100644 data/luascripts/build-cia-test.lua create mode 100644 data/luascripts/dec-enc-test.lua create mode 100644 data/luascripts/extract-code-test.lua create mode 100644 data/luascripts/install-test.lua create mode 100644 data/scripts/bkpt-test.gm9 create mode 100644 data/scripts/ctrcheck.gm9 diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 9494561b1..887730794 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -34,21 +34,6 @@ static void CreateStatTable(lua_State* L, FILINFO* fno) { // ... and leave this table on the stack for the caller to deal with } -static u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 allowed_flags) { - char types[FLAGS_COUNT][14] = { FLAGS_STR }; - int types_int[FLAGS_COUNT] = { FLAGS_CONSTS }; - u32 flags_ext = flags_ext_starter; - - for (int i = 0; i < FLAGS_COUNT; i++) { - if (!(allowed_flags & types_int[i])) continue; - lua_getfield(L, pos, types[i]); - if (lua_toboolean(L, -1)) flags_ext |= types_int[i]; - lua_pop(L, 1); - } - - return flags_ext; -} - static int fs_move(lua_State* L) { bool extra = CheckLuaArgCountPlusExtra(L, 2, "fs.rename"); const char* path_src = luaL_checkstring(L, 1); diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9fs.h index 8ae5d299a..dda9c0b52 100644 --- a/arm9/source/lua/gm9fs.h +++ b/arm9/source/lua/gm9fs.h @@ -3,13 +3,6 @@ #define GM9LUA_FSLIBNAME "fs" -// this should probably go in filesys/fsutil.h -#define RECURSIVE (1UL<<11) - -#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive" -#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE -#define FLAGS_COUNT 9 - #define SHA256_EMPTY_HASH \ 0xE3, 0xB0, 0xC4, 0x42, \ 0x98, 0xFC, 0x1C, 0x14, \ diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index fdee2110c..ae22df548 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -13,6 +13,7 @@ #include "gm9os.h" #include "gm9internalsys.h" #include "gm9ui.h" +#include "gm9title.h" #define DEBUGSP(x) ShowPrompt(false, (x)) // this is taken from scripting.c @@ -69,6 +70,27 @@ int LoadLuaFile(lua_State* L, const char* filename) { return status; } +u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 allowed_flags) { + char types[FLAGS_COUNT][14] = { FLAGS_STR }; + int types_int[FLAGS_COUNT] = { FLAGS_CONSTS }; + u32 flags_ext = flags_ext_starter; + + for (int i = 0; i < FLAGS_COUNT; i++) { + if (!(allowed_flags & types_int[i])) continue; + lua_getfield(L, pos, types[i]); + if (lua_toboolean(L, -1)) flags_ext |= types_int[i]; + lua_pop(L, 1); + } + + return flags_ext; +} + +void CheckWritePermissionsLuaError(lua_State* L, const char* path) { + if (!CheckWritePermissions(path)) { + luaL_error(L, "writing not allowed: %s", path); + } +} + static const luaL_Reg gm9lualibs[] = { // enum is special so we load it first {GM9LUA_ENUMLIBNAME, gm9lua_open_Enum}, @@ -78,8 +100,6 @@ static const luaL_Reg gm9lualibs[] = { {LUA_LOADLIBNAME, luaopen_package}, {LUA_COLIBNAME, luaopen_coroutine}, {LUA_TABLIBNAME, luaopen_table}, - //{LUA_IOLIBNAME, luaopen_io}, - //{LUA_OSLIBNAME, luaopen_os}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, {LUA_UTF8LIBNAME, luaopen_utf8}, @@ -89,6 +109,7 @@ static const luaL_Reg gm9lualibs[] = { {GM9LUA_FSLIBNAME, gm9lua_open_fs}, {GM9LUA_OSLIBNAME, gm9lua_open_os}, {GM9LUA_UILIBNAME, gm9lua_open_ui}, + {GM9LUA_TITLELIBNAME, gm9lua_open_title}, // gm9 custom internals (usually wrapped by a pure lua module) {GM9LUA_INTERNALSYSLIBNAME, gm9lua_open_internalsys}, diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index 97a0a0fe8..981f51024 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -5,6 +5,15 @@ #include "lualib.h" #include "scripting.h" +// this should probably go in filesys/fsutil.h +#define RECURSIVE (1UL<<11) +#define TO_EMUNAND (1UL<<12) +#define LEGIT (1UL<<13) + +#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive", "to_emunand", "legit" +#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT +#define FLAGS_COUNT 11 + #define LUASCRIPT_EXT "lua" #define LUASCRIPT_MAX_SIZE STD_BUFFER_SIZE @@ -25,5 +34,7 @@ static inline bool CheckLuaArgCountPlusExtra(lua_State* L, int argcount, const c } int LoadLuaFile(lua_State* L, const char* filename); +u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 allowed_flags); +void CheckWritePermissionsLuaError(lua_State* L, const char* path); #endif bool ExecuteLuaScript(const char* path_script); diff --git a/arm9/source/lua/gm9title.c b/arm9/source/lua/gm9title.c new file mode 100644 index 000000000..66ac07431 --- /dev/null +++ b/arm9/source/lua/gm9title.c @@ -0,0 +1,183 @@ +#ifndef NO_LUA +#include "gm9lua.h" +#include "utils.h" +#include "fs.h" +#include "language.h" +#include "ui.h" +#include "game.h" +#include "ips.h" +#include "bps.h" + +static int title_decrypt(lua_State* L) { + CheckLuaArgCount(L, 1, "title.decrypt"); + const char* path = luaL_checkstring(L, 1); + const char* whichfailed = ""; + + u64 filetype = IdentifyFileType(path); + bool ret; + if (filetype & BIN_KEYDB) { + ret = (CryptAesKeyDb(path, true, false) == 0); + whichfailed = "CryptAesKeyDb"; + } else { + ret = (CryptGameFile(path, true, false) == 0); + whichfailed = "CryptGameFile"; + } + + if (!ret) { + return luaL_error(L, "%s failed on %s", whichfailed, path); + } + + return 0; +} + +static int title_encrypt(lua_State* L) { + CheckLuaArgCount(L, 1, "title.encrypt"); + const char* path = luaL_checkstring(L, 1); + const char* whichfailed = ""; + + u64 filetype = IdentifyFileType(path); + bool ret; + if (filetype & BIN_KEYDB) { + ret = (CryptAesKeyDb(path, true, true) == 0); + whichfailed = "CryptAesKeyDb"; + } else { + ret = (CryptGameFile(path, true, true) == 0); + whichfailed = "CryptGameFile"; + } + + if (!ret) { + return luaL_error(L, "%s failed on %s", whichfailed, path); + } + + return 0; +} + +static int title_install(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "title.install"); + const char* path = luaL_checkstring(L, 1); + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 2, flags, TO_EMUNAND); + }; + + bool ret = (InstallGameFile(path, (flags & TO_EMUNAND)) == 0); + if (!ret) { + return luaL_error(L, "InstallGameFile failed on %s", path); + } + + return 0; +} + +static int title_build_cia(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "title.build_cia"); + const char* path = luaL_checkstring(L, 1); + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 2, flags, LEGIT); + }; + + bool ret = (BuildCiaFromGameFile(path, (flags & LEGIT)) == 0); + if (!ret) { + return luaL_error(L, "BuildCiaFromGameFile failed on %s", path); + } + + return 0; +} + +static int title_extract_code(lua_State* L) { + CheckLuaArgCount(L, 2, "title.extract_code"); + const char* path_src = luaL_checkstring(L, 1); + const char* path_dst = luaL_checkstring(L, 2); + + u64 filetype = IdentifyFileType(path_src); + if (!FTYPE_HASCODE(filetype)) { + return luaL_error(L, "%s does not have code", path_src); + } else { + CheckWritePermissionsLuaError(L, path_dst); + ShowString("%s", STR_EXTRACTING_DOT_CODE); + bool ret = (ExtractCodeFromCxiFile(path_src, path_dst, NULL) == 0); + if (!ret) { + return luaL_error(L, "failed to extract code from %s", path_src); + } + } + + return 0; +} + +static int title_compress_code(lua_State* L) { + CheckLuaArgCount(L, 2, "title.compress_code"); + const char* path_src = luaL_checkstring(L, 1); + const char* path_dst = luaL_checkstring(L, 2); + + CheckWritePermissionsLuaError(L, path_dst); + ShowString("%s", STR_COMPRESSING_DOT_CODE); + bool ret = (CompressCode(path_src, path_dst) == 0); + if (!ret) { + return luaL_error(L, "failed to compress code from %s", path_src); + } + + return 0; +} + +static int title_apply_ips(lua_State* L) { + CheckLuaArgCount(L, 3, "title.apply_ips"); + const char* path_patch = luaL_checkstring(L, 1); + const char* path_src = luaL_checkstring(L, 2); + const char* path_target = luaL_checkstring(L, 3); + + bool ret = (ApplyIPSPatch(path_patch, path_src, path_target) == 0); + if (!ret) { + return luaL_error(L, "ApplyIPSPatch failed"); + } + + return 0; +} + +static int title_apply_bps(lua_State* L) { + CheckLuaArgCount(L, 3, "title.apply_bps"); + const char* path_patch = luaL_checkstring(L, 1); + const char* path_src = luaL_checkstring(L, 2); + const char* path_target = luaL_checkstring(L, 3); + + bool ret = (ApplyBPSPatch(path_patch, path_src, path_target) == 0); + if (!ret) { + return luaL_error(L, "ApplyBPSPatch failed"); + } + + return 0; +} + +static int title_apply_bpm(lua_State* L) { + CheckLuaArgCount(L, 3, "title.apply_bpm"); + const char* path_patch = luaL_checkstring(L, 1); + const char* path_src = luaL_checkstring(L, 2); + const char* path_target = luaL_checkstring(L, 3); + + bool ret = (ApplyBPMPatch(path_patch, path_src, path_target) == 0); + if (!ret) { + return luaL_error(L, "ApplyBPMPatch failed"); + } + + return 0; +} + +static const luaL_Reg title_lib[] = { + {"decrypt", title_decrypt}, + {"encrypt", title_encrypt}, + {"install", title_install}, + {"build_cia", title_build_cia}, + {"extract_code", title_extract_code}, + {"compress_code", title_compress_code}, + {"apply_ips", title_apply_ips}, + {"apply_bps", title_apply_bps}, + {"apply_bpm", title_apply_bpm}, + {NULL, NULL} +}; + +int gm9lua_open_title(lua_State* L) { + luaL_newlib(L, title_lib); + return 1; +} +#endif diff --git a/arm9/source/lua/gm9title.h b/arm9/source/lua/gm9title.h new file mode 100644 index 000000000..530c7c83c --- /dev/null +++ b/arm9/source/lua/gm9title.h @@ -0,0 +1,6 @@ +#pragma once +#include "gm9lua.h" + +#define GM9LUA_TITLELIBNAME "title" + +int gm9lua_open_title(lua_State* L); diff --git a/command comparison table.ods b/command comparison table.ods index 900cc0a2414e91cbd622c02be1b1de507a0eb46d..24621525dcb4e87a2ede6a9e72ec115ffb3e70ad 100644 GIT binary patch delta 7651 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 Xz8)F4Mhj}FNd%?P$3|lK>u&xBgVrqu delta 7310 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 diff --git a/data/luascripts/build-cia-test.lua b/data/luascripts/build-cia-test.lua new file mode 100644 index 000000000..62d9187f3 --- /dev/null +++ b/data/luascripts/build-cia-test.lua @@ -0,0 +1,3 @@ +ui.echo("Testing building cia") +title.build_cia("A:/title/00040000/0f800100/content/00000000.tmd") +ui.echo("Done") diff --git a/data/luascripts/dec-enc-test.lua b/data/luascripts/dec-enc-test.lua new file mode 100644 index 000000000..6da201b38 --- /dev/null +++ b/data/luascripts/dec-enc-test.lua @@ -0,0 +1,9 @@ +print("Copying") +fs.copy("0:/FBI.cia", "9:/FBI-enc.cia") +print("Encrypting") +title.encrypt("9:/FBI-enc.cia") +print("Copying") +fs.copy("9:/FBI-enc.cia", "9:/FBI-dec.cia") +print("Decrypting") +title.decrypt("9:/FBI-dec.cia") +ui.echo("Done") diff --git a/data/luascripts/extract-code-test.lua b/data/luascripts/extract-code-test.lua new file mode 100644 index 000000000..df3da5472 --- /dev/null +++ b/data/luascripts/extract-code-test.lua @@ -0,0 +1,5 @@ +ui.echo("Testing extract code") +title.extract_code("A:/title/00040000/0f800100/content/7926fc67.app", "9:/code.bin") +ui.echo("Testing compress code") +title.compress_code("9:/code.bin", "9:/code-blz.bin") +ui.echo("Done") diff --git a/data/luascripts/install-test.lua b/data/luascripts/install-test.lua new file mode 100644 index 000000000..13ff32413 --- /dev/null +++ b/data/luascripts/install-test.lua @@ -0,0 +1,3 @@ +ui.echo("Testing install!") +title.install("0:/FBI.cia") +ui.echo("Did it work?") diff --git a/data/scripts/bkpt-test.gm9 b/data/scripts/bkpt-test.gm9 new file mode 100644 index 000000000..c2d58c395 --- /dev/null +++ b/data/scripts/bkpt-test.gm9 @@ -0,0 +1,3 @@ +echo "Testing..." +bkpt +echo "Post-bkpt" diff --git a/data/scripts/ctrcheck.gm9 b/data/scripts/ctrcheck.gm9 new file mode 100644 index 000000000..1c63122cc --- /dev/null +++ b/data/scripts/ctrcheck.gm9 @@ -0,0 +1,791 @@ +# A script for checking all of a 3DS console's important files and their integrity. +# original script by FrozenFire +# overhaul, fixes, and current maintenance by StarlitSkies +# last modified: 2024-09-30 +# if you didn't get this file from https://github.com/nh-server/scripts, go there and make sure this copy wasn't tampered with + +@cleanup +set VERSION "4" +set FULL "0" +set PREVIEW_MODE "ctrcheck v$[VERSION]" + +set NANDSECTORS_LOG "" +set ESSENTIALS_LOG "" +set CTRNAND_LOG "" +set TWLNAND_LOG "" +set FIRM_LOG "" +set SD_LOG "" +set MISC_LOG "" +set LOG "disabled" + +@menu +labelsel -o -s "Select which parts of the system to check.\nConsole is a $[REGION] $[RDTYPE] $[ONTYPE] using $[HAX].\nCurrent id0 is $[SYSID0].\n\nPermanent logging: $[LOG]" check_* +goto menu + +@check_All +set FULL "1" +goto NAND_Header + +@check_NAND_Only +goto NAND_Header + +@check_SD_Only +goto SD_Files + +@NAND_Header +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: ~~~\nNAND Sectors: ---\nCTRNAND: ---\nTWLNAND: ---\nTWLP: ---\nFIRM Partitions: ---" + +# check whether NAND header signature is sighax, based on hashes +# sighax will always be read as valid by boot9 even without cfw, so it's just used to check whether custom partitions should be possible +set SIGHAX "0" +if find S:/nand_hdr.bin NULL + if not shaget S:/nand_hdr.bin@0:200 NULL + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND header is an invalid size.\n" + goto NAND_Sectors + end +else + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND header not found.\n" + goto NAND_Sectors +end + +if sha S:/nand_hdr.bin@0:100 A4AE99B93412E4643E4686987B6CFD59701D5C655CA2FF671CE680B4DDCF0948 + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: NAND header's signature is sighax.\n" + set SIGHAX "1" +end + +# hash-based check of NAND header partition table against retail partition tables +if sha S:/nand_hdr.bin@100:60 dfd434b883874d8b585a102f3cf3ae4cef06767801db515fdf694a7e7cd98bc2 + if chk $[ONTYPE] "N3DS" + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: NAND header is stock. (n3DS)\n" + else + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: o3DS has an n3DS NAND header.\n" + end +elif sha S:/nand_hdr.bin@100:60 ae9b6645105f3aec22c2e3ee247715ab302874fca283343c731ca43ea1baa25d + if chk $[ONTYPE] "O3DS" + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: NAND header is stock. (o3DS)\n" + else + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: n3DS has an o3DS NAND header.\n" + end +else + fget S:/nand_hdr.bin@100:4 NCSD + #check for the NCSD magic header, if it's not present then there's definitely something wrong + if chk $[NCSD] "4E435344" + if chk $[SIGHAX] "1" + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: NAND partition table is modified, but there is sighax in the NAND header.\n" + else + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND partition table is modified, and there is no sighax in the NAND header.\n" + end + else + # your NAND has a minor case of serious brain damage + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND header data is invalid. You've met with a terrible fate, haven't you?\n" + end +end + +@NAND_Sectors +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: ~~~\nCTRNAND: ---\nTWLNAND: ---\nTWLP: ---\nTWLP: ---\nFIRM Partitions: ---" + +# verify Secret Sector, which is doubly important for N3DSes +if chk $[ONTYPE] "N3DS" + if not sha S:/sector0x96.bin 82F2730D2C2DA3F30165F987FDCCAC5CBAB24B4E5F65C981CD7BE6F438E6D9D3 + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: Secret Sector data is invalid. a9lh might be installed.\n" + end +else + if fget S:/nand.bin@12C00:2 SBYTE + if chk -u $[SBYTE] "0000" + if chk -u $[SBYTE] "FFFF" + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: There may be a9lh leftovers in the secret sector.\n" + end + end + else + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: NAND is unreadable at offset 0x12C00...?\n" + end +end + +# verify the TWL MBR exists and is retail (there is no good reason this should ever be modified) +if find S:/twlmbr.bin NULL + if not sha S:/twlmbr.bin 77a98e31f1ff7ec4ef2bfacca5a114a49a70dcf8f1dcd23e7a486973cfd06617 + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: TWL MBR data is invalid.\n" + end +else + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: TWL MBR not found.\n" +end + +# get first byte in stage2 location +if not fget S:/nand.bin@B800000:1 ABYTE + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: NAND is unreadable at offset 0xB800000...?\n" +end +# instead of checking the full sector against multiple stage2s, this just checks if the sector is "clean" +# (if first byte is not "clean" assume stage2 is there, can be done in a better way) +# if stage2 was replaced with trash it would trigger this warning tho +if chk -u $[ABYTE] "00" + if chk -u $[ABYTE] "FF" + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: There are likely leftovers from a9lh's stage2 payload.\n" + end +end + +# check for presence of bonus drive, just because it might come in handy to know sometimes +if isdir 8: + set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: Bonus drive is enabled.\n" +end + + +@CTRNAND +# check if CTRNAND can be accessed, skip all CTRNAND checks if it isn't +if not isdir 1: + set CTRNAND_LOG "$[CTRNAND_LOG]Error: CTRNAND not found.\n" + goto TWLNAND +end + +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: ~~~\nTWLNAND: ---\nTWLP: ---\nFIRM Partitions: ---" + +# find movable.sed, check if it's valid, and check whether CMAC is set correctly based on size (288 bytes vs 320 bytes) +set MOVABLEVALID "0" +set MLFCSHASH "" +if not find 1:/private/movable.sed movable + goto LFCS +end +if shaget $[movable]@0:100 NULL + fget $[movable]@8:10 ISVALID + if chk $[ISVALID] "00000000000000000000000000000000" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Critical: movable.sed's copy of LFCS is blank.\n" + end +else + goto LFCS +end +fget $[movable]@0:4 HEADER +if chk -u $[HEADER] "53454544" + goto LFCS +end +set MOVABLEVALID "1" +fget $[movable]@5:1 CMAC +shaget $[movable]@8:110 MLFCSHASH +#check if movable.sed at least has the SEED magic header, then check whether CMAC is set correctly based on size (320 vs 288 bytes) +if chk $[CMAC] "01" + if not fget $[movable]@120:1 NULL + if ask "movable.sed is misconfigured.\nPress (A) to reconfigure it to normal values." + fset $[movable]@5 00 + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: movable.sed has been fixed by removing the CMAC flag.\n" + else + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Critical: movable.sed is 288 bytes but has the CMAC flag.\n" + end + end +end + +@LFCS +# find LFCS, check if it's valid, check if it's the same as movable.sed's copy of LFCS, and check against OTP seed that's used to make part of LFCS +# account for both potential filenames of LocalFriendCodeSeed +set LFCSVALID "0" +if find 1:/rw/sys/LocalFriendCodeSeed_B LFCS +elif find 1:/rw/sys/LocalFriendCodeSeed_A LFCS +else + goto SecureInfo +end +if shaget $[LFCS]@0:100 NULL + fget $[LFCS]@0:10 ISVALID + if chk $[ISVALID] "00000000000000000000000000000000" + goto SecureInfo + end +else + goto SecureInfo +end +set LFCSVALID "1" +shaget $[LFCS]@0:110 LFCSHASH +# compare the LFCS in movable.sed to the raw LFCS file, if movable.sed exists +if chk -u $[MLFCSHASH] "" + if chk -u $[MLFCSHASH] $[LFCSHASH] + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: LFCS doesn't match movable.sed.\n" + end +end +if find M:/otp_dec.mem OTP + fget $[LFCS]@108:8 LFCSEED + fget $[OTP]@8:8 OTPSEED + if chk -u $[LFCSEED] $[OTPSEED] + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Warning: Console is using a donor LFCS.\n" + end +end + +@SecureInfo +# find secureinfo_(a/b) and check if it's valid +set SECVALID "0" +if find 1:/rw/sys/SecureInfo_A SEC +elif find 1:/rw/sys/SecureInfo_B SEC +else + goto HWCAL0 +end +if not find -s 1:/rw/sys/SecureInfo_C ALTSEC + set ALTSEC "" +end +if not shaget $[SEC]@0:110 NULL + goto HWCAL0 +end +# the checks here are done against the end of the file since that part contains the serial; the RSA signature isn't important for troubleshooting purposes +fget $[SEC]@100:10 ISVALID +if chk $[ISVALID] "00000000000000000000000000000000" + goto HWCAL0 +else + set SECVALID "1" + fget $[SEC]@10C:1 SECSERIALCHECK + if chk $[SECSERIALCHECK] "00" + fget $[SEC]@102:0A SECSERIAL + else + fget $[SEC]@102:0B SECSERIAL + end +end + +# then check if it matches the console region, check for region change (presence of secureinfo_c), and use that for both data comparison and to see what region was changed to +if chk -u $[ALTSEC] "" + set REGCHG "1" + fget $[ALTSEC]@100:1 ALTREGSEC + if chk $[ALTREGSEC] "00" + set ALTREG "JPN" + elif chk $[ALTREGSEC] "01" + set ALTREG "USA" + elif chk $[ALTREGSEC] "02" + set ALTREG "EUR" + elif chk $[ALTREGSEC] "04" + set ALTREG "CHN" + elif chk $[ALTREGSEC] "05" + set ALTREG "KOR" + elif chk $[ALTREGSEC] "06" + set ALTREG "TWN" + end +else + set REGCHG "0" +end +fget $[SEC]@100:1 REGSEC +if chk $[REGSEC] "00" + set REG "JPN" +elif chk $[REGSEC] "01" + set REG "USA" +elif chk $[REGSEC] "02" + set REG "EUR" +elif chk $[REGSEC] "04" + set REG "CHN" +elif chk $[REGSEC] "05" + set REG "KOR" +elif chk $[REGSEC] "06" + set REG "TWN" +end +if chk -u $[REG] $[REGION] + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Warning: SecureInfo doesn't match the console's region.\n" +end +if chk $[REGCHG] "1" + if chk -u $[ALTREG] $[REGION] + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: Console's region is changed from $[REG] to $[ALTREG].\n" + else + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: SecureInfo_C exists, but console's region is unchanged.\n" + end +end + +@HWCAL0 +# check whether first HWCAL exists +set HWCAL0VALID "0" +if find 1:/ro/sys/HWCAL0.dat HWCAL0 + if not shaget $[HWCAL0]@0:900 NULL + goto HWCAL1 + end +else + goto HWCAL1 +end +# if it's valid, check if it has the CCAL magic header +fget $[HWCAL0]@0:4 HEADER +if chk $[HEADER] "4343414C" + set HWCAL0VALID "1" +end + +@HWCAL1 +# check whether second HWCAL exists +set HWCAL1VALID "0" +if find 1:/ro/sys/HWCAL1.dat HWCAL1 + if not shaget $[HWCAL1]@0:900 NULL + goto Misc_CTRNAND + end +else + goto Misc_CTRNAND +end +# if it's valid, check if it has the CCAL magic header +fget $[HWCAL1]@0:4 HEADER +if chk $[HEADER] "4343414C" + set HWCAL1VALID "1" +end + +@Misc_CTRNAND +# check whether system can boot without SD card +if find 1:/boot.firm NULL + if find 1:/rw/luma/payloads/GodMode9.firm NULL + set CTRNAND_LOG "$[CTRNAND_LOG]Information: GodMode9 and Luma3DS are in the NAND.\n" + else + set CTRNAND_LOG "$[CTRNAND_LOG]Information: Luma3DS is in the NAND, but GodMode9 isn't.\n" + end +else + set CTRNAND_LOG "$[CTRNAND_LOG]Warning: Luma3DS is not in the NAND. (This console cannot boot without an SD card.)\n" +end + +# check whether nand title database exists +if find 1:/dbs/title.db NANDTITLEDB + if shaget $[NANDTITLEDB]@0:400 NULL + fget $[NANDTITLEDB]@100:4 HEADER + # check whether nand title.db has the DIFF magic header + if chk -u $[HEADER] "44494646" + set CTRNAND_LOG "$[CTRNAND_LOG]Critical: CTRNAND title.db data is invalid.\n" + end + else + set CTRNAND_LOG "$[CTRNAND_LOG]Critical: CTRNAND title.db is an invalid size.\n" + end +else + set CTRNAND_LOG "$[CTRNAND_LOG]Critical: CTRNAND title.db not found.\n" +end + +set RECOVERYMODE "0" +if chk $[MOVABLEVALID] "0" + set RECOVERYMODE "1" +end +if chk $[LFCSVALID] "0" + set RECOVERYMODE "1" +end +if chk $[SECVALID] "0" + set RECOVERYMODE "1" +end +if chk $[HWCAL0VALID] "0" + set RECOVERYMODE "1" +end +if chk $[HWCAL1VALID] "0" + set RECOVERYMODE "1" +end + +if chk $[RECOVERYMODE] "1" + goto CTRNAND_Recovery +else + goto TWLNAND +end + +@CTRNAND_Recovery +# if the recovery flag exists, then they've already tried this. if any part of it failed before, it'll fail again now - no point in redoing it +if find 9:/RECOVERYFLAG NULL + if chk $[MOVABLEVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: movable.sed is still invalid after a recovery attempt." + end + if chk $[LFCSVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: LFCS is still invalid after a recovery attempt." + end + if chk $[SECVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: SecureInfo is still invalid after a recovery attempt." + end + if chk $[HWCAL0VALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: HWCAL0 is still invalid after a recovery attempt." + end + if chk $[HWCAL1VALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: HWCAL1 is still invalid after a recovery attempt." + end + goto TWLNAND +end +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: !!!\nTWLNAND: ---\nTWLP: ---\nFIRM Partitions: ---" +set TRYRECOVERY "0" +if find S:/essential.exefs NULL + if ask "Critical files in the CTRNAND are invalid,\nbut a backup was found.\nPress (A) to enter data recovery mode.\n \n(If you have already tried this recently,\nyou may safely skip this part.)" + allow -a 1: + set TRYRECOVERY "1" + else + if chk $[MOVABLEVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: movable.sed is invalid, and data recovery was denied." + end + if chk $[LFCSVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: LFCS is invalid, and data recovery was denied." + end + if chk $[SECVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: SecuureInfo is invalid, and data recovery was denied." + end + if chk $[HWCAL0VALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL0 is invalid, and data recovery was denied." + end + if chk $[HWCAL1VALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL1 is invalid, and data recovery was denied." + end + end +else + if chk $[MOVABLEVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: movable.sed is invalid, and essential.exefs does not exist." + end + if chk $[LFCSVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: LFCS is invalid, and essential.exefs does not exist." + end + if chk $[SECVALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: SecuureInfo is invalid, and essential.exefs does not exist." + end + if chk $[HWCAL0VALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL0 is invalid, and essential.exefs does not exist." + end + if chk $[HWCAL1VALID] "0" + set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL1 is invalid, and essential.exefs does not exist." + end +end + +if chk $[TRYRECOVERY] "0" + goto TWLNAND +end + +imgmount S:/essential.exefs + +# check backup movable.sed integrity, fix CMAC if needed, then copy it into NAND if it's valid +set VALIDBACKUP "1" +if shaget G:/movable@0:100 NULL + fget G:/movable@0:4 HEADER + fget G:/movable@5:1 CMAC + if chk -u $[HEADER] "53454544" + set VALIDBACKUP "0" + end + if chk $[CMAC] "01" + if not fget G:/movable@120:1 NULL + set CMACFIX "1" + else + set CMACFIX "0" + end + end +else + set VALIDBACKUP "0" +end +if chk $[VALIDBACKUP] "1" + if chk $[CMACFIX] "0" + cp -n -w G:/movable 1:/private/movable.sed + else + cp -n -w G:/movable 9:/movable + fset 9:/movable@5 00 + cp -n -w 9:/movable 1:/private/movable.sed + rm -o -s 9:/movable + end +end + +# check backup LFCS integrity, then copy it into NAND if it's valid +set VALIDBACKUP "1" +if shaget G:/frndseed@0:100 NULL + fget G:/frndseed@0:10 ISVALID + if chk $[ISVALID] "00000000000000000000000000000000" + set VALIDBACKUP "0" + end +else + set VALIDBACKUP "0" +end +if chk $[VALIDBACKUP] "1" + cp -n -w G:/frndseed 1:/rw/sys/LocalFriendCodeSeed_B +end + +# check backup SecureInfo integrity, then copy it into NAND if it's valid +set VALIDBACKUP "1" +if shaget G:/secinfo@0:100 NULL + fget G:/secinfo@0:10 ISVALID + if chk $[ISVALID] "00000000000000000000000000000000" + set VALIDBACKUP "0" + end +else + set VALIDBACKUP "0" +end +if chk $[VALIDBACKUP] "1" + cp -n -w G:/secinfo 1:/rw/sys/SecureInfo_A +end + +# check backup HWCAL0 integrity, then copy it into NAND if it's valid +if shaget G:/hwcal0@0:900 NULL + fget G:/hwcal0@0:4 HEADER + if chk -u $[HEADER] "4343414C" + set VALIDBACKUP "0" + end +else + set VALIDBACKUP "0" +end +if chk $[VALIDBACKUP] "1" + cp -n -w G:/hwcal0 1:/ro/sys/HWCAL0.dat +end + +# check backup HWCAL1 integrity, then copy it into NAND if it's valid +if shaget G:/hwcal1@0:900 NULL + fget G:/hwcal1@0:4 HEADER + if chk -u $[HEADER] "4343414C" + set VALIDBACKUP "0" + end +else + set VALIDBACKUP "0" +end +if chk $[VALIDBACKUP] "1" + cp -n -w G:/hwcal1 1:/ro/sys/HWCAL1.dat +end + +# the flag is made in ramdrive since people should do the requested rerun immediately, and i'm shoving it down their throat to do that as hard as i can. if they ignore it and reboot anyway, first off what the hell +fdummy 9:/RECOVERYFLAG 400 +echo "Recovery attempt finished.\nctrcheck will now restart automatically.\n \Run another check to get the true results.\n" +imgumount +goto cleanup + +@TWLNAND +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: DONE\nTWLNAND: ~~~\nTWLP: ---\nFIRM Partitions: ---" +#check if TWLNAND can be accessed +if not isdir 2: + set TWLNAND_LOG "$[TWLNAND_LOG]Error: TWLNAND not found.\n" + goto TWLP +end +# the console won't boot if shared2 doesn't exist, but remaking it is easy and harmless +if not isdir 2:/shared2 + if ask "A folder required to boot is missing. Press (A) to fix this issue." + mkdir 2:/shared2 + set TWLNAND_LOG "$[TWLNAND_LOG]Information: shared2 has been recreated.\n" + else + set TWLNAND_LOG "$[TWLNAND_LOG]Critical: shared2 not found, and was not recreated when asked.\n" + end +end +# check for inspect.log, and if it exists, compare its copy of the serial number against SecureInfo +if isdir 2:/sys/log + if find 2:/sys/log/inspect.log INSPECTLOG + if chk $[SECVALID] "1" + fget $[INSPECTLOG]@77:1 TWLNSERIALCHECK + if chk $[TWLNSERIALCHECK] "0a" + fget $[INSPECTLOG]@6D:0A TWLNSERIAL + else + fget $[INSPECTLOG]@6D:0B TWLNSERIAL + end + if chk -u $[SECSERIAL] $[TWLNSERIAL] + set TWLNAND_LOG "$[TWLNAND_LOG]Warning: inspect.log serial does not match SecureInfo.\n" + end + end + else + set TWLNAND_LOG "$[TWLNAND_LOG]Warning: inspect.log not found.\n" + end +else + set TWLNAND_LOG "$[TWLNAND_LOG]Warning: 2:/sys/log folder not found, meaning inspect.log also not found.\n" +end + +@TWLP +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: DONE\nTWLNAND: DONE\nTWLP: ~~~\nFIRM Partitions: ---" +# you don't need TWLP to boot (or for a lot of things, really), but it's still good to check everything's in place +if not isdir 3: + set TWLNAND_LOG "$[TWLNAND_LOG]Warning: TWLP not found.\n" +end + +@FIRM_Data +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: DONE\nTWLNAND: DONE\nTWLP: DONE\nFIRM Partitions: ~~~" +# basic checks for how many FIRM partitions exist and which ones they are +set FIRMEXIST "0" +set ALTFIRMEXIST "0" +set BONUSFIRMS "0" +set TOOMANYFIRMS "0" +if find S:/firm0.bin NULL + set FIRMEXIST "1" +end +if find S:/firm1.bin NULL + set ALTFIRMEXIST "1" +end +if find S:/firm2.bin NULL + set BONUSFIRMS "1" +end +if find S:/firm5.bin NULL + set TOOMANYFIRMS "1" +end +if chk $[FIRMEXIST] "0" + if chk $[ALTFIRMEXIST] "0" + set FIRM_LOG "$[FIRM_LOG]Error: FIRM0 and FIRM1 both not found. Unless you have access to ntrboot, do NOT power off the console until you've fixed this.\n" + goto NAND_End + else + set FIRM_LOG "$[FIRM_LOG]Bruh Moment: FIRM0 not found, but FIRM1 exists. This shouldn't be possible, the console's probably having a stroke.\n" + end +else + if chk $[ALTFIRMEXIST] "0" + set FIRM_LOG "$[FIRM_LOG]Critical: FIRM1 not found, but FIRM0 exists. The console isn't dead yet, but fix this ASAP.\n" + end +end +if chk $[BONUSFIRMS] "1" + if chk $[TOOMANYFIRMS] "1" + set FIRM_LOG "$[FIRM_LOG]Bruh Moment: ...Why are there more than 5 FIRM partitions? I hope you only sacrificed the AGBSAVE.\n" + else + set FIRM_LOG "$[FIRM_LOG]Information: Extra FIRM partitions detected. But hey, rules were meant to be broken, right?\n" + end +end + +# compare firm slots against the hashes of all payloads we could reasonably expect +# todo: add hashes for new b9s/fb3ds versions as they're released +for S: firm*.bin + strsplit FIRM $[FORPATH] "/" + strsplit -b FIRM $[FIRM] "." + # immediately start with b9s checks since they're the most common, it speeds up the script if they get hit first + if sha $[FORPATH]@0:3E00 72002296D1B8B1D4E462EA340124D836BA7F8D6BF16617F369ED90983C42BB98 + set FIRM_LOG "$[FIRM_LOG]Information: b9s v1.4 installed to $[FIRM].\n" + elif sha $[FORPATH]@0:7800 79C68585B4BA1D7C4A91B858861553E768C6576B92E303810B33219736B3598B + set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.3 installed to $[FIRM].\n" + elif sha $[FORPATH]@0:10C00 A765E44844BD5667CC1D7A9A89AD45EC674F8392367F4418CCB08152581D7B3A + set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.2 installed to $[FIRM].\n" + elif sha $[FORPATH]@0:10C00 D77BEE742E7E7D528BAA20E6ADA7AC822598DDCACDFC81B1F13E32C94F4EBC50 + set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.1 installed to $[FIRM].\n" + elif sha $[FORPATH]@0:20800 43978C226D3164047051B1B534D6589608F1FA04E0B1766E1FDBEB3BC41707B6 + set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.0 installed to $[FIRM].\n" + elif verify $[FORPATH] + # check for the sighax signatures that fastboot3DS uses + # sciresm is used when fb3DS is installed by outside sources, derrek is used if fb3DS updated itself + # and that means you'll basically never see derrek sig in practice. be wary if you do see it + if sha $[FORPATH]@100:100 078CC0CFD850A27093DDA2630C3603CA0C96969BD1F26DA48AC7B1BAE5DD5219 + set FIRMSIG "SciresM" + elif sha $[FORPATH]@100:100 ADB73ABC35708EF1DFE9EF9CA5FAC8BFC2DF916BB2E38101858482409F0D450A + set FIRMSIG "derrek" + else + set FIRMSIG "none" + end + # minfirm is a special case, since it's only used by b9stool and shouldn't stick around for long after cfw setup. best to get it out of the way before fb3DS + if sha $[FORPATH]@0:100 93EE0A3799072EFB368DAD3174D8DE2EC9735BC13AC78C087DA80 + set FIRM_LOG "$[FIRM_LOG]Critical: minfirm installed to $[FIRM].\n" + elif chk -u $[FIRMSIG] "none" + if sha $[FORPATH]@0:100 D36E802EEA55B92110438D0A3B09DFCEEEC71AEB7BF05073A2E0E857827F3903 + set FIRM_LOG "$[FIRM_LOG]Information: fb3DS v1.2 ($[FIRMSIG] sig) installed to $[FIRM].\n" + elif sha $[FORPATH]@0:100 9C8D28272421C78AC796EB9023A6D1373F31176CB693CE1B04B1B78112E25226 + set FIRM_LOG "$[FIRM_LOG]Warning: fb3DS v1.1 ($[FIRMSIG] sig) installed to $[FIRM].\n" + elif sha $[FORPATH]@0:100 5E58A159C057D0762E6BFC53FE5A5CDAECA338544B252B85524DFBBB1D546DCB + set FIRM_LOG "$[FIRM_LOG]Warning: fb3DS v1.1-beta ($[FIRMSIG] sig) installed to $[FIRM].\n" + elif sha $[FORPATH]@0:100 12EBA2DDB6B5203E66CBE82A963B56AECF540814F15F8539D0CE65DAE818BBB7 + set FIRM_LOG "$[FIRM_LOG]Warning: fb3DS v1.0 ($[FIRMSIG] sig) installed to $[FIRM].\n" + else + set FIRM_LOG "$[FIRM_LOG]Warning: Valid unknown firm with $[FIRMSIG] sig installed to $[FIRM].\n" + end + else + set FIRM_LOG "$[FIRM_LOG]Information: Valid stock/unknown firm installed to $[FIRM].\n" + end + else + set FIRM_LOG "$[FIRM_LOG]Error: Invalid firm installed to $[FIRM].\n" + end +next + +@NAND_End +if chk $[FULL] "1" + goto SD_Files +else + goto Misc_Files +end + + +@SD_Files +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: SD. Progress:\n \nSD Structure: ~~~" +# this script doesn't NEED an SD to run, necessarily, but you'd have to copy it to your NAND on purpose +if not isdir 0: + set SD_LOG "$[SD_LOG]Bruh Moment: No SD card is inserted. You went out of your way to find this, didn't you?\n" + goto SD_End +end +if isdir "0:/Nintendo 3DS" + if isdir A: + if find A:/dbs/title.db TITLEDB + if shaget $[TITLEDB]@0:400 NULL + fget $[TITLEDB]@100:4 HEADER + #check whether SD title.db has the DIFF magic header + if chk -u $[HEADER] "44494646" + if ask "The SD title.db is invalid.\nPress (A) to reset it.\n \n(This will delete all of your games and apps,\nbut they were likely also corrupted\nby whatever caused this.)" + rm $[TITLEDB] + fdummy $[TITLEDB] 400 + set SD_LOG "$[SD_LOG]Warning: The SD title.db needs to be reset. Reboot and go into System Settings -> Data Management -> Nintendo 3DS -> Software to do this.\n" + else + set SD_LOG "$[SD_LOG]Critical: SD title.db data is invalid.\n" + end + end + else + if ask "The SD title.db is invalid.\nPress (A) to reset it.\n \n(This will delete all of your games and apps,\nbut they were likely also corrupted\nby whatever caused this.)" + rm $[TITLEDB] + fdummy $[TITLEDB] 400 + set SD_LOG "$[SD_LOG]Warning: The SD title.db needs to be reset. Reboot and go into System Settings -> Data Management -> Nintendo 3DS -> Software to do this.\n" + else + set SD_LOG "$[SD_LOG]Critical: SD title.db is an invalid size.\n" + end + end + else + set TITLEDB A:/dbs/title.db + if ask "The SD title.db does not exist.\nPress (A) to create a blank one.\n \n(A title database is necessary\nto install games and apps.)" + if not isdir A:/dbs + mkdir A:/dbs + end + fdummy $[TITLEDB] 400 + set SD_LOG "$[SD_LOG]Warning: The SD title.db needs to be reset. Reboot and go into System Settings -> Data Management -> Nintendo 3DS -> Software to do this.\n" + else + set SD_LOG "$[SD_LOG]Critical: SD title.db not found.\n" + end + end + else + if isdir "0:/Nintendo 3DS/$[SYSID0]" + set SD_LOG "$[SD_LOG]Warning: Nintendo 3DS folder has valid data, but the data is inaccessible.\n" + else + set SD_LOG "$[SD_LOG]Information: Nintendo 3DS folder exists, but has no data.\n" + end + end + if not find 0:/boot.3dsx NULL + set SD_LOG "$[SD_LOG]Warning: There is no boot.3dsx in the SD card root.\n" + end + if not find 0:/boot.firm NULL + set SD_LOG "$[SD_LOG]Warning: There is no boot.firm in the SD card root.\n" + end +else + set SD_LOG "$[SD_LOG]Warning: Nintendo 3DS folder not found.\n" +end + +@Misc_Files +set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: Other. Progress:\nNVRAM: ~~~\OTP: ---\n" +# check whether the NVRAM SPI flash works, because it's bad news if it doesn't +if not shaget M:/nvram.mem@0:400 NULL + set MISC_LOG "$[MISC_LOG]Critical: NVRAM is inaccessible.\n" +end +# boot9strap decrypts the OTP and makes it available to GodMode9, most other bootloaders don't +if not find M:/otp_dec.mem NULL + set MISC_LOG "$[MISC_LOG]Warning: Decrypted OTP not found. (If you aren't using b9s, you can ignore this.)\n" +end + +@Results +dumptxt 0:/gm9/ctrcheck_latest.txt "Date and Time: $[DATESTAMP] $[TIMESTAMP]\n---" +if chk -u $[NANDSECTORS_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_latest.txt $[NANDSECTORS_LOG] +end +if chk -u $[ESSENTIALS_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_latest.txt $[ESSENTIALS_LOG] +end +if chk -u $[CTRNAND_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_latest.txt $[CTRNAND_LOG] +end +if chk -u $[TWLNAND_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_latest.txt $[TWLNAND_LOG] +end +if chk -u $[FIRM_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_latest.txt $[FIRM_LOG] +end +if chk -u $[SD_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_latest.txt $[SD_LOG] +end +if chk -u $[MISC_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_latest.txt $[MISC_LOG] +end +textview 0:/gm9/ctrcheck_latest.txt +if chk $[LOG] "activated" + dumptxt -p 0:/gm9/ctrcheck_log.txt "Date and Time: $[DATESTAMP] $[TIMESTAMP]\n---" + if chk -u $[NANDSECTORS_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_log.txt $[NANDSECTORS_LOG] + end + if chk -u $[ESSENTIALS_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_log.txt $[ESSENTIALS_LOG] + end + if chk -u $[CTRNAND_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_log.txt $[CTRNAND_LOG] + end + if chk -u $[TWLNAND_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_log.txt $[TWLNAND_LOG] + end + if chk -u $[FIRM_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_log.txt $[FIRM_LOG] + end + if chk -u $[SD_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_log.txt $[SD_LOG] + end + if chk -u $[MISC_LOG] "" + dumptxt -p 0:/gm9/ctrcheck_log.txt $[MISC_LOG] + end + echo "Check complete.\n \nThese results are also in 0:/gm9/ctrcheck_latest.txt,\nbut the file will be overwritten if you rerun this.\n \nHowever, they have also been appended to\nthe permanent log in 0:/gm9/ctrcheck_log.txt." +else + echo "Check complete.\n \nThese results are also in 0:/gm9/ctrcheck_latest.txt,\nbut the file will be overwritten if you rerun this." +end +goto cleanup + +@check_Toggle_permanent_log +if chk $[LOG] "disabled" + set LOG "activated" +else + set LOG "disabled" +end +goto menu + +@check_Exit From 6316ba164061e880b8623b95bddae9ae6ef4484c Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 1 Dec 2024 23:51:04 -0600 Subject: [PATCH 073/124] add readme for lua --- arm9/source/lua/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 arm9/source/lua/README.md diff --git a/arm9/source/lua/README.md b/arm9/source/lua/README.md new file mode 100644 index 000000000..6be5fba0f --- /dev/null +++ b/arm9/source/lua/README.md @@ -0,0 +1,13 @@ +This is Lua 5.4.7 with a few modifications: +* Patches made to silence warnings: https://github.com/ihaveamac/GodMode9/commit/9905b939b26aae3422c906c7858d8852764fa279 +* lua.c, luac.c, lua.hpp removed (not useful in GodMode9) + +## License of Lua 5.4.7 + +Copyright © 1994–2024 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 6ad9a439d3d5459f3c04b1be3890b7349907e4bc Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 1 Dec 2024 23:54:06 -0600 Subject: [PATCH 074/124] remove liolib.c and loslib.c --- arm9/source/lua/README.md | 1 + arm9/source/lua/liolib.c | 841 -------------------------------------- arm9/source/lua/loslib.c | 430 ------------------- 3 files changed, 1 insertion(+), 1271 deletions(-) delete mode 100644 arm9/source/lua/liolib.c delete mode 100644 arm9/source/lua/loslib.c diff --git a/arm9/source/lua/README.md b/arm9/source/lua/README.md index 6be5fba0f..91d99eece 100644 --- a/arm9/source/lua/README.md +++ b/arm9/source/lua/README.md @@ -1,6 +1,7 @@ This is Lua 5.4.7 with a few modifications: * Patches made to silence warnings: https://github.com/ihaveamac/GodMode9/commit/9905b939b26aae3422c906c7858d8852764fa279 * lua.c, luac.c, lua.hpp removed (not useful in GodMode9) +* liolib.c, loslib.c removed (replaced with custom implementations) ## License of Lua 5.4.7 diff --git a/arm9/source/lua/liolib.c b/arm9/source/lua/liolib.c deleted file mode 100644 index c5075f3e7..000000000 --- a/arm9/source/lua/liolib.c +++ /dev/null @@ -1,841 +0,0 @@ -/* -** $Id: liolib.c $ -** Standard I/O (and system) library -** See Copyright Notice in lua.h -*/ - -#define liolib_c -#define LUA_LIB - -#include "lprefix.h" - - -#include -#include -#include -#include -#include -#include - -#include "lua.h" - -#include "lauxlib.h" -#include "lualib.h" - - - - -/* -** Change this macro to accept other modes for 'fopen' besides -** the standard ones. -*/ -#if !defined(l_checkmode) - -/* accepted extensions to 'mode' in 'fopen' */ -#if !defined(L_MODEEXT) -#define L_MODEEXT "b" -#endif - -/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */ -static int l_checkmode (const char *mode) { - return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL && - (*mode != '+' || ((void)(++mode), 1)) && /* skip if char is '+' */ - (strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */ -} - -#endif - -/* -** {====================================================== -** l_popen spawns a new process connected to the current -** one through the file streams. -** ======================================================= -*/ - -#if !defined(l_popen) /* { */ - -#if defined(LUA_USE_POSIX) /* { */ - -#define l_popen(L,c,m) (fflush(NULL), popen(c,m)) -#define l_pclose(L,file) (pclose(file)) - -#elif defined(LUA_USE_WINDOWS) /* }{ */ - -#define l_popen(L,c,m) (_popen(c,m)) -#define l_pclose(L,file) (_pclose(file)) - -#if !defined(l_checkmodep) -/* Windows accepts "[rw][bt]?" as valid modes */ -#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && \ - (m[1] == '\0' || ((m[1] == 'b' || m[1] == 't') && m[2] == '\0'))) -#endif - -#else /* }{ */ - -/* ISO C definitions */ -#define l_popen(L,c,m) \ - ((void)c, (void)m, \ - luaL_error(L, "'popen' not supported"), \ - (FILE*)0) -#define l_pclose(L,file) ((void)L, (void)file, -1) - -#endif /* } */ - -#endif /* } */ - - -#if !defined(l_checkmodep) -/* By default, Lua accepts only "r" or "w" as valid modes */ -#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && m[1] == '\0') -#endif - -/* }====================================================== */ - - -#if !defined(l_getc) /* { */ - -#if defined(LUA_USE_POSIX) -#define l_getc(f) getc_unlocked(f) -#define l_lockfile(f) flockfile(f) -#define l_unlockfile(f) funlockfile(f) -#else -#define l_getc(f) getc(f) -#define l_lockfile(f) ((void)0) -#define l_unlockfile(f) ((void)0) -#endif - -#endif /* } */ - - -/* -** {====================================================== -** l_fseek: configuration for longer offsets -** ======================================================= -*/ - -#if !defined(l_fseek) /* { */ - -#if defined(LUA_USE_POSIX) /* { */ - -#include - -#define l_fseek(f,o,w) fseeko(f,o,w) -#define l_ftell(f) ftello(f) -#define l_seeknum off_t - -#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \ - && defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */ - -/* Windows (but not DDK) and Visual C++ 2005 or higher */ -#define l_fseek(f,o,w) _fseeki64(f,o,w) -#define l_ftell(f) _ftelli64(f) -#define l_seeknum __int64 - -#else /* }{ */ - -/* ISO C definitions */ -#define l_fseek(f,o,w) fseek(f,o,w) -#define l_ftell(f) ftell(f) -#define l_seeknum long - -#endif /* } */ - -#endif /* } */ - -/* }====================================================== */ - - - -#define IO_PREFIX "_IO_" -#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1) -#define IO_INPUT (IO_PREFIX "input") -#define IO_OUTPUT (IO_PREFIX "output") - - -typedef luaL_Stream LStream; - - -#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE)) - -#define isclosed(p) ((p)->closef == NULL) - - -static int io_type (lua_State *L) { - LStream *p; - luaL_checkany(L, 1); - p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE); - if (p == NULL) - luaL_pushfail(L); /* not a file */ - else if (isclosed(p)) - lua_pushliteral(L, "closed file"); - else - lua_pushliteral(L, "file"); - return 1; -} - - -static int f_tostring (lua_State *L) { - LStream *p = tolstream(L); - if (isclosed(p)) - lua_pushliteral(L, "file (closed)"); - else - lua_pushfstring(L, "file (%p)", p->f); - return 1; -} - - -static FILE *tofile (lua_State *L) { - LStream *p = tolstream(L); - if (l_unlikely(isclosed(p))) - luaL_error(L, "attempt to use a closed file"); - lua_assert(p->f); - return p->f; -} - - -/* -** When creating file handles, always creates a 'closed' file handle -** before opening the actual file; so, if there is a memory error, the -** handle is in a consistent state. -*/ -static LStream *newprefile (lua_State *L) { - LStream *p = (LStream *)lua_newuserdatauv(L, sizeof(LStream), 0); - p->closef = NULL; /* mark file handle as 'closed' */ - luaL_setmetatable(L, LUA_FILEHANDLE); - return p; -} - - -/* -** Calls the 'close' function from a file handle. The 'volatile' avoids -** a bug in some versions of the Clang compiler (e.g., clang 3.0 for -** 32 bits). -*/ -static int aux_close (lua_State *L) { - LStream *p = tolstream(L); - volatile lua_CFunction cf = p->closef; - p->closef = NULL; /* mark stream as closed */ - return (*cf)(L); /* close it */ -} - - -static int f_close (lua_State *L) { - tofile(L); /* make sure argument is an open stream */ - return aux_close(L); -} - - -static int io_close (lua_State *L) { - if (lua_isnone(L, 1)) /* no argument? */ - lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use default output */ - return f_close(L); -} - - -static int f_gc (lua_State *L) { - LStream *p = tolstream(L); - if (!isclosed(p) && p->f != NULL) - aux_close(L); /* ignore closed and incompletely open files */ - return 0; -} - - -/* -** function to close regular files -*/ -static int io_fclose (lua_State *L) { - LStream *p = tolstream(L); - errno = 0; - return luaL_fileresult(L, (fclose(p->f) == 0), NULL); -} - - -static LStream *newfile (lua_State *L) { - LStream *p = newprefile(L); - p->f = NULL; - p->closef = &io_fclose; - return p; -} - - -static void opencheck (lua_State *L, const char *fname, const char *mode) { - LStream *p = newfile(L); - p->f = fopen(fname, mode); - if (l_unlikely(p->f == NULL)) - luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno)); -} - - -static int io_open (lua_State *L) { - const char *filename = luaL_checkstring(L, 1); - const char *mode = luaL_optstring(L, 2, "r"); - LStream *p = newfile(L); - const char *md = mode; /* to traverse/check mode */ - luaL_argcheck(L, l_checkmode(md), 2, "invalid mode"); - errno = 0; - p->f = fopen(filename, mode); - return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; -} - - -/* -** function to close 'popen' files -*/ -static int io_pclose (lua_State *L) { - LStream *p = tolstream(L); - errno = 0; - return luaL_execresult(L, l_pclose(L, p->f)); -} - - -static int io_popen (lua_State *L) { - const char *filename = luaL_checkstring(L, 1); - const char *mode = luaL_optstring(L, 2, "r"); - LStream *p = newprefile(L); - luaL_argcheck(L, l_checkmodep(mode), 2, "invalid mode"); - errno = 0; - p->f = l_popen(L, filename, mode); - p->closef = &io_pclose; - return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1; -} - - -static int io_tmpfile (lua_State *L) { - LStream *p = newfile(L); - errno = 0; - p->f = tmpfile(); - return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1; -} - - -static FILE *getiofile (lua_State *L, const char *findex) { - LStream *p; - lua_getfield(L, LUA_REGISTRYINDEX, findex); - p = (LStream *)lua_touserdata(L, -1); - if (l_unlikely(isclosed(p))) - luaL_error(L, "default %s file is closed", findex + IOPREF_LEN); - return p->f; -} - - -static int g_iofile (lua_State *L, const char *f, const char *mode) { - if (!lua_isnoneornil(L, 1)) { - const char *filename = lua_tostring(L, 1); - if (filename) - opencheck(L, filename, mode); - else { - tofile(L); /* check that it's a valid file handle */ - lua_pushvalue(L, 1); - } - lua_setfield(L, LUA_REGISTRYINDEX, f); - } - /* return current value */ - lua_getfield(L, LUA_REGISTRYINDEX, f); - return 1; -} - - -static int io_input (lua_State *L) { - return g_iofile(L, IO_INPUT, "r"); -} - - -static int io_output (lua_State *L) { - return g_iofile(L, IO_OUTPUT, "w"); -} - - -static int io_readline (lua_State *L); - - -/* -** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit -** in the limit for upvalues of a closure) -*/ -#define MAXARGLINE 250 - -/* -** Auxiliary function to create the iteration function for 'lines'. -** The iteration function is a closure over 'io_readline', with -** the following upvalues: -** 1) The file being read (first value in the stack) -** 2) the number of arguments to read -** 3) a boolean, true iff file has to be closed when finished ('toclose') -** *) a variable number of format arguments (rest of the stack) -*/ -static void aux_lines (lua_State *L, int toclose) { - int n = lua_gettop(L) - 1; /* number of arguments to read */ - luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); - lua_pushvalue(L, 1); /* file */ - lua_pushinteger(L, n); /* number of arguments to read */ - lua_pushboolean(L, toclose); /* close/not close file when finished */ - lua_rotate(L, 2, 3); /* move the three values to their positions */ - lua_pushcclosure(L, io_readline, 3 + n); -} - - -static int f_lines (lua_State *L) { - tofile(L); /* check that it's a valid file handle */ - aux_lines(L, 0); - return 1; -} - - -/* -** Return an iteration function for 'io.lines'. If file has to be -** closed, also returns the file itself as a second result (to be -** closed as the state at the exit of a generic for). -*/ -static int io_lines (lua_State *L) { - int toclose; - if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */ - if (lua_isnil(L, 1)) { /* no file name? */ - lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT); /* get default input */ - lua_replace(L, 1); /* put it at index 1 */ - tofile(L); /* check that it's a valid file handle */ - toclose = 0; /* do not close it after iteration */ - } - else { /* open a new file */ - const char *filename = luaL_checkstring(L, 1); - opencheck(L, filename, "r"); - lua_replace(L, 1); /* put file at index 1 */ - toclose = 1; /* close it after iteration */ - } - aux_lines(L, toclose); /* push iteration function */ - if (toclose) { - lua_pushnil(L); /* state */ - lua_pushnil(L); /* control */ - lua_pushvalue(L, 1); /* file is the to-be-closed variable (4th result) */ - return 4; - } - else - return 1; -} - - -/* -** {====================================================== -** READ -** ======================================================= -*/ - - -/* maximum length of a numeral */ -#if !defined (L_MAXLENNUM) -#define L_MAXLENNUM 200 -#endif - - -/* auxiliary structure used by 'read_number' */ -typedef struct { - FILE *f; /* file being read */ - int c; /* current character (look ahead) */ - int n; /* number of elements in buffer 'buff' */ - char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */ -} RN; - - -/* -** Add current char to buffer (if not out of space) and read next one -*/ -static int nextc (RN *rn) { - if (l_unlikely(rn->n >= L_MAXLENNUM)) { /* buffer overflow? */ - rn->buff[0] = '\0'; /* invalidate result */ - return 0; /* fail */ - } - else { - rn->buff[rn->n++] = rn->c; /* save current char */ - rn->c = l_getc(rn->f); /* read next one */ - return 1; - } -} - - -/* -** Accept current char if it is in 'set' (of size 2) -*/ -static int test2 (RN *rn, const char *set) { - if (rn->c == set[0] || rn->c == set[1]) - return nextc(rn); - else return 0; -} - - -/* -** Read a sequence of (hex)digits -*/ -static int readdigits (RN *rn, int hex) { - int count = 0; - while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn)) - count++; - return count; -} - - -/* -** Read a number: first reads a valid prefix of a numeral into a buffer. -** Then it calls 'lua_stringtonumber' to check whether the format is -** correct and to convert it to a Lua number. -*/ -static int read_number (lua_State *L, FILE *f) { - RN rn; - int count = 0; - int hex = 0; - char decp[2]; - rn.f = f; rn.n = 0; - decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */ - decp[1] = '.'; /* always accept a dot */ - l_lockfile(rn.f); - do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */ - test2(&rn, "-+"); /* optional sign */ - if (test2(&rn, "00")) { - if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */ - else count = 1; /* count initial '0' as a valid digit */ - } - count += readdigits(&rn, hex); /* integral part */ - if (test2(&rn, decp)) /* decimal point? */ - count += readdigits(&rn, hex); /* fractional part */ - if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */ - test2(&rn, "-+"); /* exponent sign */ - readdigits(&rn, 0); /* exponent digits */ - } - ungetc(rn.c, rn.f); /* unread look-ahead char */ - l_unlockfile(rn.f); - rn.buff[rn.n] = '\0'; /* finish string */ - if (l_likely(lua_stringtonumber(L, rn.buff))) - return 1; /* ok, it is a valid number */ - else { /* invalid format */ - lua_pushnil(L); /* "result" to be removed */ - return 0; /* read fails */ - } -} - - -static int test_eof (lua_State *L, FILE *f) { - int c = getc(f); - ungetc(c, f); /* no-op when c == EOF */ - lua_pushliteral(L, ""); - return (c != EOF); -} - - -static int read_line (lua_State *L, FILE *f, int chop) { - luaL_Buffer b; - int c; - luaL_buffinit(L, &b); - do { /* may need to read several chunks to get whole line */ - char *buff = luaL_prepbuffer(&b); /* preallocate buffer space */ - int i = 0; - l_lockfile(f); /* no memory errors can happen inside the lock */ - while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n') - buff[i++] = c; /* read up to end of line or buffer limit */ - l_unlockfile(f); - luaL_addsize(&b, i); - } while (c != EOF && c != '\n'); /* repeat until end of line */ - if (!chop && c == '\n') /* want a newline and have one? */ - luaL_addchar(&b, c); /* add ending newline to result */ - luaL_pushresult(&b); /* close buffer */ - /* return ok if read something (either a newline or something else) */ - return (c == '\n' || lua_rawlen(L, -1) > 0); -} - - -static void read_all (lua_State *L, FILE *f) { - size_t nr; - luaL_Buffer b; - luaL_buffinit(L, &b); - do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ - char *p = luaL_prepbuffer(&b); - nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f); - luaL_addsize(&b, nr); - } while (nr == LUAL_BUFFERSIZE); - luaL_pushresult(&b); /* close buffer */ -} - - -static int read_chars (lua_State *L, FILE *f, size_t n) { - size_t nr; /* number of chars actually read */ - char *p; - luaL_Buffer b; - luaL_buffinit(L, &b); - p = luaL_prepbuffsize(&b, n); /* prepare buffer to read whole block */ - nr = fread(p, sizeof(char), n, f); /* try to read 'n' chars */ - luaL_addsize(&b, nr); - luaL_pushresult(&b); /* close buffer */ - return (nr > 0); /* true iff read something */ -} - - -static int g_read (lua_State *L, FILE *f, int first) { - int nargs = lua_gettop(L) - 1; - int n, success; - clearerr(f); - errno = 0; - if (nargs == 0) { /* no arguments? */ - success = read_line(L, f, 1); - n = first + 1; /* to return 1 result */ - } - else { - /* ensure stack space for all results and for auxlib's buffer */ - luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); - success = 1; - for (n = first; nargs-- && success; n++) { - if (lua_type(L, n) == LUA_TNUMBER) { - size_t l = (size_t)luaL_checkinteger(L, n); - success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); - } - else { - const char *p = luaL_checkstring(L, n); - if (*p == '*') p++; /* skip optional '*' (for compatibility) */ - switch (*p) { - case 'n': /* number */ - success = read_number(L, f); - break; - case 'l': /* line */ - success = read_line(L, f, 1); - break; - case 'L': /* line with end-of-line */ - success = read_line(L, f, 0); - break; - case 'a': /* file */ - read_all(L, f); /* read entire file */ - success = 1; /* always success */ - break; - default: - return luaL_argerror(L, n, "invalid format"); - } - } - } - } - if (ferror(f)) - return luaL_fileresult(L, 0, NULL); - if (!success) { - lua_pop(L, 1); /* remove last result */ - luaL_pushfail(L); /* push nil instead */ - } - return n - first; -} - - -static int io_read (lua_State *L) { - return g_read(L, getiofile(L, IO_INPUT), 1); -} - - -static int f_read (lua_State *L) { - return g_read(L, tofile(L), 2); -} - - -/* -** Iteration function for 'lines'. -*/ -static int io_readline (lua_State *L) { - LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1)); - int i; - int n = (int)lua_tointeger(L, lua_upvalueindex(2)); - if (isclosed(p)) /* file is already closed? */ - return luaL_error(L, "file is already closed"); - lua_settop(L , 1); - luaL_checkstack(L, n, "too many arguments"); - for (i = 1; i <= n; i++) /* push arguments to 'g_read' */ - lua_pushvalue(L, lua_upvalueindex(3 + i)); - n = g_read(L, p->f, 2); /* 'n' is number of results */ - lua_assert(n > 0); /* should return at least a nil */ - if (lua_toboolean(L, -n)) /* read at least one value? */ - return n; /* return them */ - else { /* first result is false: EOF or error */ - if (n > 1) { /* is there error information? */ - /* 2nd result is error message */ - return luaL_error(L, "%s", lua_tostring(L, -n + 1)); - } - if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */ - lua_settop(L, 0); /* clear stack */ - lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */ - aux_close(L); /* close it */ - } - return 0; - } -} - -/* }====================================================== */ - - -static int g_write (lua_State *L, FILE *f, int arg) { - int nargs = lua_gettop(L) - arg; - int status = 1; - errno = 0; - for (; nargs--; arg++) { - if (lua_type(L, arg) == LUA_TNUMBER) { - /* optimization: could be done exactly as for strings */ - int len = lua_isinteger(L, arg) - ? fprintf(f, LUA_INTEGER_FMT, - (LUAI_UACINT)lua_tointeger(L, arg)) - : fprintf(f, LUA_NUMBER_FMT, - (LUAI_UACNUMBER)lua_tonumber(L, arg)); - status = status && (len > 0); - } - else { - size_t l; - const char *s = luaL_checklstring(L, arg, &l); - status = status && (fwrite(s, sizeof(char), l, f) == l); - } - } - if (l_likely(status)) - return 1; /* file handle already on stack top */ - else - return luaL_fileresult(L, status, NULL); -} - - -static int io_write (lua_State *L) { - return g_write(L, getiofile(L, IO_OUTPUT), 1); -} - - -static int f_write (lua_State *L) { - FILE *f = tofile(L); - lua_pushvalue(L, 1); /* push file at the stack top (to be returned) */ - return g_write(L, f, 2); -} - - -static int f_seek (lua_State *L) { - static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; - static const char *const modenames[] = {"set", "cur", "end", NULL}; - FILE *f = tofile(L); - int op = luaL_checkoption(L, 2, "cur", modenames); - lua_Integer p3 = luaL_optinteger(L, 3, 0); - l_seeknum offset = (l_seeknum)p3; - luaL_argcheck(L, (lua_Integer)offset == p3, 3, - "not an integer in proper range"); - errno = 0; - op = l_fseek(f, offset, mode[op]); - if (l_unlikely(op)) - return luaL_fileresult(L, 0, NULL); /* error */ - else { - lua_pushinteger(L, (lua_Integer)l_ftell(f)); - return 1; - } -} - - -static int f_setvbuf (lua_State *L) { - static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; - static const char *const modenames[] = {"no", "full", "line", NULL}; - FILE *f = tofile(L); - int op = luaL_checkoption(L, 2, NULL, modenames); - lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); - int res; - errno = 0; - res = setvbuf(f, NULL, mode[op], (size_t)sz); - return luaL_fileresult(L, res == 0, NULL); -} - - - -static int io_flush (lua_State *L) { - FILE *f = getiofile(L, IO_OUTPUT); - errno = 0; - return luaL_fileresult(L, fflush(f) == 0, NULL); -} - - -static int f_flush (lua_State *L) { - FILE *f = tofile(L); - errno = 0; - return luaL_fileresult(L, fflush(f) == 0, NULL); -} - - -/* -** functions for 'io' library -*/ -static const luaL_Reg iolib[] = { - {"close", io_close}, - {"flush", io_flush}, - {"input", io_input}, - {"lines", io_lines}, - {"open", io_open}, - {"output", io_output}, - {"popen", io_popen}, - {"read", io_read}, - {"tmpfile", io_tmpfile}, - {"type", io_type}, - {"write", io_write}, - {NULL, NULL} -}; - - -/* -** methods for file handles -*/ -static const luaL_Reg meth[] = { - {"read", f_read}, - {"write", f_write}, - {"lines", f_lines}, - {"flush", f_flush}, - {"seek", f_seek}, - {"close", f_close}, - {"setvbuf", f_setvbuf}, - {NULL, NULL} -}; - - -/* -** metamethods for file handles -*/ -static const luaL_Reg metameth[] = { - {"__index", NULL}, /* placeholder */ - {"__gc", f_gc}, - {"__close", f_gc}, - {"__tostring", f_tostring}, - {NULL, NULL} -}; - - -static void createmeta (lua_State *L) { - luaL_newmetatable(L, LUA_FILEHANDLE); /* metatable for file handles */ - luaL_setfuncs(L, metameth, 0); /* add metamethods to new metatable */ - luaL_newlibtable(L, meth); /* create method table */ - luaL_setfuncs(L, meth, 0); /* add file methods to method table */ - lua_setfield(L, -2, "__index"); /* metatable.__index = method table */ - lua_pop(L, 1); /* pop metatable */ -} - - -/* -** function to (not) close the standard files stdin, stdout, and stderr -*/ -static int io_noclose (lua_State *L) { - LStream *p = tolstream(L); - p->closef = &io_noclose; /* keep file opened */ - luaL_pushfail(L); - lua_pushliteral(L, "cannot close standard file"); - return 2; -} - - -static void createstdfile (lua_State *L, FILE *f, const char *k, - const char *fname) { - LStream *p = newprefile(L); - p->f = f; - p->closef = &io_noclose; - if (k != NULL) { - lua_pushvalue(L, -1); - lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */ - } - lua_setfield(L, -2, fname); /* add file to module */ -} - - -LUAMOD_API int luaopen_io (lua_State *L) { - luaL_newlib(L, iolib); /* new module */ - createmeta(L); - /* create (and set) default files */ - createstdfile(L, stdin, IO_INPUT, "stdin"); - createstdfile(L, stdout, IO_OUTPUT, "stdout"); - createstdfile(L, stderr, NULL, "stderr"); - return 1; -} - diff --git a/arm9/source/lua/loslib.c b/arm9/source/lua/loslib.c deleted file mode 100644 index ba80d72c4..000000000 --- a/arm9/source/lua/loslib.c +++ /dev/null @@ -1,430 +0,0 @@ -/* -** $Id: loslib.c $ -** Standard Operating System library -** See Copyright Notice in lua.h -*/ - -#define loslib_c -#define LUA_LIB - -#include "lprefix.h" - - -#include -#include -#include -#include -#include - -#include "lua.h" - -#include "lauxlib.h" -#include "lualib.h" - - -/* -** {================================================================== -** List of valid conversion specifiers for the 'strftime' function; -** options are grouped by length; group of length 2 start with '||'. -** =================================================================== -*/ -#if !defined(LUA_STRFTIMEOPTIONS) /* { */ - -#if defined(LUA_USE_WINDOWS) -#define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYzZ%" \ - "||" "#c#x#d#H#I#j#m#M#S#U#w#W#y#Y" /* two-char options */ -#elif defined(LUA_USE_C89) /* ANSI C 89 (only 1-char options) */ -#define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYZ%" -#else /* C99 specification */ -#define LUA_STRFTIMEOPTIONS "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%" \ - "||" "EcECExEXEyEY" "OdOeOHOIOmOMOSOuOUOVOwOWOy" /* two-char options */ -#endif - -#endif /* } */ -/* }================================================================== */ - - -/* -** {================================================================== -** Configuration for time-related stuff -** =================================================================== -*/ - -/* -** type to represent time_t in Lua -*/ -#if !defined(LUA_NUMTIME) /* { */ - -#define l_timet lua_Integer -#define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) -#define l_gettime(L,arg) luaL_checkinteger(L, arg) - -#else /* }{ */ - -#define l_timet lua_Number -#define l_pushtime(L,t) lua_pushnumber(L,(lua_Number)(t)) -#define l_gettime(L,arg) luaL_checknumber(L, arg) - -#endif /* } */ - - -#if !defined(l_gmtime) /* { */ -/* -** By default, Lua uses gmtime/localtime, except when POSIX is available, -** where it uses gmtime_r/localtime_r -*/ - -#if defined(LUA_USE_POSIX) /* { */ - -#define l_gmtime(t,r) gmtime_r(t,r) -#define l_localtime(t,r) localtime_r(t,r) - -#else /* }{ */ - -/* ISO C definitions */ -#define l_gmtime(t,r) ((void)(r)->tm_sec, gmtime(t)) -#define l_localtime(t,r) ((void)(r)->tm_sec, localtime(t)) - -#endif /* } */ - -#endif /* } */ - -/* }================================================================== */ - - -/* -** {================================================================== -** Configuration for 'tmpnam': -** By default, Lua uses tmpnam except when POSIX is available, where -** it uses mkstemp. -** =================================================================== -*/ -#if !defined(lua_tmpnam) /* { */ - -#if defined(LUA_USE_POSIX) /* { */ - -#include - -#define LUA_TMPNAMBUFSIZE 32 - -#if !defined(LUA_TMPNAMTEMPLATE) -#define LUA_TMPNAMTEMPLATE "/tmp/lua_XXXXXX" -#endif - -#define lua_tmpnam(b,e) { \ - strcpy(b, LUA_TMPNAMTEMPLATE); \ - e = mkstemp(b); \ - if (e != -1) close(e); \ - e = (e == -1); } - -#else /* }{ */ - -/* ISO C definitions */ -#define LUA_TMPNAMBUFSIZE L_tmpnam -#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } - -#endif /* } */ - -#endif /* } */ -/* }================================================================== */ - - -#if !defined(l_system) -#if defined(LUA_USE_IOS) -/* Despite claiming to be ISO C, iOS does not implement 'system'. */ -#define l_system(cmd) ((cmd) == NULL ? 0 : -1) -#else -#define l_system(cmd) system(cmd) /* default definition */ -#endif -#endif - - -static int os_execute (lua_State *L) { - const char *cmd = luaL_optstring(L, 1, NULL); - int stat; - errno = 0; - stat = l_system(cmd); - if (cmd != NULL) - return luaL_execresult(L, stat); - else { - lua_pushboolean(L, stat); /* true if there is a shell */ - return 1; - } -} - - -static int os_remove (lua_State *L) { - const char *filename = luaL_checkstring(L, 1); - errno = 0; - return luaL_fileresult(L, remove(filename) == 0, filename); -} - - -static int os_rename (lua_State *L) { - const char *fromname = luaL_checkstring(L, 1); - const char *toname = luaL_checkstring(L, 2); - errno = 0; - return luaL_fileresult(L, rename(fromname, toname) == 0, NULL); -} - - -static int os_tmpname (lua_State *L) { - char buff[LUA_TMPNAMBUFSIZE]; - int err; - lua_tmpnam(buff, err); - if (l_unlikely(err)) - return luaL_error(L, "unable to generate a unique filename"); - lua_pushstring(L, buff); - return 1; -} - - -static int os_getenv (lua_State *L) { - lua_pushstring(L, getenv(luaL_checkstring(L, 1))); /* if NULL push nil */ - return 1; -} - - -static int os_clock (lua_State *L) { - lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC); - return 1; -} - - -/* -** {====================================================== -** Time/Date operations -** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S, -** wday=%w+1, yday=%j, isdst=? } -** ======================================================= -*/ - -/* -** About the overflow check: an overflow cannot occur when time -** is represented by a lua_Integer, because either lua_Integer is -** large enough to represent all int fields or it is not large enough -** to represent a time that cause a field to overflow. However, if -** times are represented as doubles and lua_Integer is int, then the -** time 0x1.e1853b0d184f6p+55 would cause an overflow when adding 1900 -** to compute the year. -*/ -static void setfield (lua_State *L, const char *key, int value, int delta) { - #if (defined(LUA_NUMTIME) && LUA_MAXINTEGER <= INT_MAX) - if (l_unlikely(value > LUA_MAXINTEGER - delta)) - luaL_error(L, "field '%s' is out-of-bound", key); - #endif - lua_pushinteger(L, (lua_Integer)value + delta); - lua_setfield(L, -2, key); -} - - -static void setboolfield (lua_State *L, const char *key, int value) { - if (value < 0) /* undefined? */ - return; /* does not set field */ - lua_pushboolean(L, value); - lua_setfield(L, -2, key); -} - - -/* -** Set all fields from structure 'tm' in the table on top of the stack -*/ -static void setallfields (lua_State *L, struct tm *stm) { - setfield(L, "year", stm->tm_year, 1900); - setfield(L, "month", stm->tm_mon, 1); - setfield(L, "day", stm->tm_mday, 0); - setfield(L, "hour", stm->tm_hour, 0); - setfield(L, "min", stm->tm_min, 0); - setfield(L, "sec", stm->tm_sec, 0); - setfield(L, "yday", stm->tm_yday, 1); - setfield(L, "wday", stm->tm_wday, 1); - setboolfield(L, "isdst", stm->tm_isdst); -} - - -static int getboolfield (lua_State *L, const char *key) { - int res; - res = (lua_getfield(L, -1, key) == LUA_TNIL) ? -1 : lua_toboolean(L, -1); - lua_pop(L, 1); - return res; -} - - -static int getfield (lua_State *L, const char *key, int d, int delta) { - int isnum; - int t = lua_getfield(L, -1, key); /* get field and its type */ - lua_Integer res = lua_tointegerx(L, -1, &isnum); - if (!isnum) { /* field is not an integer? */ - if (l_unlikely(t != LUA_TNIL)) /* some other value? */ - return luaL_error(L, "field '%s' is not an integer", key); - else if (l_unlikely(d < 0)) /* absent field; no default? */ - return luaL_error(L, "field '%s' missing in date table", key); - res = d; - } - else { - if (!(res >= 0 ? res - delta <= INT_MAX : INT_MIN + delta <= res)) - return luaL_error(L, "field '%s' is out-of-bound", key); - res -= delta; - } - lua_pop(L, 1); - return (int)res; -} - - -static const char *checkoption (lua_State *L, const char *conv, - ptrdiff_t convlen, char *buff) { - const char *option = LUA_STRFTIMEOPTIONS; - int oplen = 1; /* length of options being checked */ - for (; *option != '\0' && oplen <= convlen; option += oplen) { - if (*option == '|') /* next block? */ - oplen++; /* will check options with next length (+1) */ - else if (memcmp(conv, option, oplen) == 0) { /* match? */ - memcpy(buff, conv, oplen); /* copy valid option to buffer */ - buff[oplen] = '\0'; - return conv + oplen; /* return next item */ - } - } - luaL_argerror(L, 1, - lua_pushfstring(L, "invalid conversion specifier '%%%s'", conv)); - return conv; /* to avoid warnings */ -} - - -static time_t l_checktime (lua_State *L, int arg) { - l_timet t = l_gettime(L, arg); - luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); - return (time_t)t; -} - - -/* maximum size for an individual 'strftime' item */ -#define SIZETIMEFMT 250 - - -static int os_date (lua_State *L) { - size_t slen; - const char *s = luaL_optlstring(L, 1, "%c", &slen); - time_t t = luaL_opt(L, l_checktime, 2, time(NULL)); - const char *se = s + slen; /* 's' end */ - struct tm tmr, *stm; - if (*s == '!') { /* UTC? */ - stm = l_gmtime(&t, &tmr); - s++; /* skip '!' */ - } - else - stm = l_localtime(&t, &tmr); - if (stm == NULL) /* invalid date? */ - return luaL_error(L, - "date result cannot be represented in this installation"); - if (strcmp(s, "*t") == 0) { - lua_createtable(L, 0, 9); /* 9 = number of fields */ - setallfields(L, stm); - } - else { - char cc[4]; /* buffer for individual conversion specifiers */ - luaL_Buffer b; - cc[0] = '%'; - luaL_buffinit(L, &b); - while (s < se) { - if (*s != '%') /* not a conversion specifier? */ - luaL_addchar(&b, *s++); - else { - size_t reslen; - char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT); - s++; /* skip '%' */ - s = checkoption(L, s, se - s, cc + 1); /* copy specifier to 'cc' */ - reslen = strftime(buff, SIZETIMEFMT, cc, stm); - luaL_addsize(&b, reslen); - } - } - luaL_pushresult(&b); - } - return 1; -} - - -static int os_time (lua_State *L) { - time_t t; - if (lua_isnoneornil(L, 1)) /* called without args? */ - t = time(NULL); /* get current time */ - else { - struct tm ts; - luaL_checktype(L, 1, LUA_TTABLE); - lua_settop(L, 1); /* make sure table is at the top */ - ts.tm_year = getfield(L, "year", -1, 1900); - ts.tm_mon = getfield(L, "month", -1, 1); - ts.tm_mday = getfield(L, "day", -1, 0); - ts.tm_hour = getfield(L, "hour", 12, 0); - ts.tm_min = getfield(L, "min", 0, 0); - ts.tm_sec = getfield(L, "sec", 0, 0); - ts.tm_isdst = getboolfield(L, "isdst"); - t = mktime(&ts); - setallfields(L, &ts); /* update fields with normalized values */ - } - if (t != (time_t)(l_timet)t || t == (time_t)(-1)) - return luaL_error(L, - "time result cannot be represented in this installation"); - l_pushtime(L, t); - return 1; -} - - -static int os_difftime (lua_State *L) { - time_t t1 = l_checktime(L, 1); - time_t t2 = l_checktime(L, 2); - lua_pushnumber(L, (lua_Number)difftime(t1, t2)); - return 1; -} - -/* }====================================================== */ - - -static int os_setlocale (lua_State *L) { - static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, - LC_NUMERIC, LC_TIME}; - static const char *const catnames[] = {"all", "collate", "ctype", "monetary", - "numeric", "time", NULL}; - const char *l = luaL_optstring(L, 1, NULL); - int op = luaL_checkoption(L, 2, "all", catnames); - lua_pushstring(L, setlocale(cat[op], l)); - return 1; -} - - -static int os_exit (lua_State *L) { - int status; - if (lua_isboolean(L, 1)) - status = (lua_toboolean(L, 1) ? EXIT_SUCCESS : EXIT_FAILURE); - else - status = (int)luaL_optinteger(L, 1, EXIT_SUCCESS); - if (lua_toboolean(L, 2)) - lua_close(L); - if (L) exit(status); /* 'if' to avoid warnings for unreachable 'return' */ - return 0; -} - - -static const luaL_Reg syslib[] = { - {"clock", os_clock}, - {"date", os_date}, - {"difftime", os_difftime}, - {"execute", os_execute}, - {"exit", os_exit}, - {"getenv", os_getenv}, - {"remove", os_remove}, - {"rename", os_rename}, - {"setlocale", os_setlocale}, - {"time", os_time}, - {"tmpname", os_tmpname}, - {NULL, NULL} -}; - -/* }====================================================== */ - - - -LUAMOD_API int luaopen_os (lua_State *L) { - luaL_newlib(L, syslib); - return 1; -} - From afc9d461670a459cddfc42c6d99a0ad15ecfbeff Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 2 Dec 2024 01:22:34 -0600 Subject: [PATCH 075/124] more functions and things, use CheckWritePermissionsLuaError in place of more manual checks, update command comparison table --- arm9/source/lua/gm9fs.c | 72 +++++++++++++++++++++---------- arm9/source/lua/gm9internalsys.c | 28 ++++++++++++ arm9/source/lua/gm9lua.c | 1 + arm9/source/lua/gm9lua.h | 6 +-- arm9/source/lua/gm9ui.c | 59 ++++++++++++++++++++++++- arm9/source/lua/gm9ui.h | 4 -- command comparison table.ods | Bin 23971 -> 24973 bytes data/luapackages/sys.lua | 5 +++ data/luapackages/util.lua | 10 +++++ data/luascripts/bkpt-test.lua | 2 + data/luascripts/fill-test.lua | 5 +++ data/luascripts/preview-test.lua | 5 +++ 12 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 data/luascripts/bkpt-test.lua create mode 100644 data/luascripts/fill-test.lua create mode 100644 data/luascripts/preview-test.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9fs.c index 887730794..78955d002 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9fs.c @@ -59,10 +59,7 @@ static int fs_remove(lua_State* L) { bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.remove"); const char* path = luaL_checkstring(L, 1); - bool allowed = CheckWritePermissions(path); - if (!allowed) { - return luaL_error(L, "writing not allowed: %s", path); - } + CheckWritePermissionsLuaError(L, path); u32 flags = 0; if (extra) { @@ -88,10 +85,7 @@ static int fs_copy(lua_State* L) { const char* path_dst = luaL_checkstring(L, 2); FILINFO fno; - bool allowed = CheckWritePermissions(path_dst); - if (!allowed) { - return luaL_error(L, "writing not allowed: %s", path_dst); - } + CheckWritePermissionsLuaError(L, path_dst); u32 flags = BUILD_PATH; if (extra) { @@ -119,10 +113,7 @@ static int fs_mkdir(lua_State* L) { CheckLuaArgCount(L, 1, "fs.mkdir"); const char* path = luaL_checkstring(L, 1); - bool allowed = CheckWritePermissions(path); - if (!allowed) { - return luaL_error(L, "writing not allowed: %s", path); - } + CheckWritePermissionsLuaError(L, path); FRESULT res = fvx_rmkdir(path); if (res != FR_OK) { @@ -285,10 +276,7 @@ static int fs_write_file(lua_State* L) { size_t data_length = 0; const char* data = luaL_checklstring(L, 3, &data_length); - bool allowed = CheckWritePermissions(path); - if (!allowed) { - return luaL_error(L, "writing not allowed: %s", path); - } + CheckWritePermissionsLuaError(L, path); UINT bytes_written = 0; FRESULT res = fvx_qwrite(path, data, offset, data_length, &bytes_written); @@ -300,6 +288,47 @@ static int fs_write_file(lua_State* L) { return 1; } +static int fs_fill_file(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 4, "fs.fill_file"); + const char* path = luaL_checkstring(L, 1); + lua_Integer offset = luaL_checkinteger(L, 2); + lua_Integer size = luaL_checkinteger(L, 3); + lua_Integer byte = luaL_checkinteger(L, 4); + + u8 real_byte = byte & 0xFF; + + u32 flags = ALLOW_EXPAND; + if (extra) { + flags = GetFlagsFromTable(L, 4, flags, NO_CANCEL); + }; + + if ((byte < 0) || (byte > 0xFF)) { + return luaL_error(L, "byte is not between 0x00 and 0xFF (got: %I)", byte); + } + + CheckWritePermissionsLuaError(L, path); + + if (!(FileSetByte(path, offset, size, real_byte, &flags))) { + return luaL_error(L, "FileSetByte failed on %s", path); + } + + return 0; +} + +static int fs_make_dummy_file(lua_State* L) { + CheckLuaArgCount(L, 2, "fs.make_dummy_file"); + const char* path = luaL_checkstring(L, 1); + lua_Integer size = luaL_checkinteger(L, 2); + + CheckWritePermissionsLuaError(L, path); + + if (!(FileCreateDummy(path, NULL, size))) { + return luaL_error(L, "FileCreateDummy failed on %s"); + } + + return 0; +} + static int fs_truncate(lua_State* L) { CheckLuaArgCount(L, 2, "fs.write_file"); const char* path = luaL_checkstring(L, 1); @@ -307,18 +336,13 @@ static int fs_truncate(lua_State* L) { FIL fp; FRESULT res; + CheckWritePermissionsLuaError(L, path); + res = f_open(&fp, path, FA_READ | FA_WRITE); if (res != FR_OK) { return luaL_error(L, "failed to open %s (note: this only works on FAT filesystems, not virtual)", path); } - // this check is *after* opening so the error happens on virtual filesystems sooner - bool allowed = CheckWritePermissions(path); - if (!allowed) { - f_close(&fp); - return luaL_error(L, "writing not allowed: %s", path); - } - res = f_lseek(&fp, size); if (res != FR_OK) { f_close(&fp); @@ -518,6 +542,8 @@ static const luaL_Reg fs_lib[] = { {"is_file", fs_is_file}, {"read_file", fs_read_file}, {"write_file", fs_write_file}, + {"fill_file", fs_fill_file}, + {"make_dummy_file", fs_make_dummy_file}, {"truncate", fs_truncate}, {"img_mount", fs_img_mount}, {"img_umount", fs_img_umount}, diff --git a/arm9/source/lua/gm9internalsys.c b/arm9/source/lua/gm9internalsys.c index b6e923b40..88338ae7a 100644 --- a/arm9/source/lua/gm9internalsys.c +++ b/arm9/source/lua/gm9internalsys.c @@ -6,6 +6,9 @@ #include "game.h" #include "power.h" #include "sha.h" +#include "nand.h" + +#define UNUSED(x) ((void)(x)) static int internalsys_boot(lua_State* L) { CheckLuaArgCount(L, 1, "_sys.boot"); @@ -68,16 +71,41 @@ static int internalsys_get_id0(lua_State* L) { return 1; } +static int internalsys_next_emu(lua_State* L) { + CheckLuaArgCount(L, 0, "_sys.next_emu"); + + DismountDriveType(DRV_EMUNAND); + AutoEmuNandBase(false); + InitExtFS(); + + return 0; +} + +static int internalsys_global_bkpt(lua_State* L) { + UNUSED(L); + bkpt; + while(1); +} + static const luaL_Reg internalsys_lib[] = { {"boot", internalsys_boot}, {"reboot", internalsys_reboot}, {"power_off", internalsys_power_off}, {"get_id0", internalsys_get_id0}, + {"next_emu", internalsys_next_emu}, + {NULL, NULL} +}; + +static const luaL_Reg internalsys_global_lib[] = { + {"bkpt", internalsys_global_bkpt}, {NULL, NULL} }; int gm9lua_open_internalsys(lua_State* L) { luaL_newlib(L, internalsys_lib); + lua_pushglobaltable(L); // push global table to stack + luaL_setfuncs(L, internalsys_global_lib, 0); // set global funcs + lua_pop(L, 1); // pop global table from stack return 1; } #endif diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index ae22df548..7a6beedda 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -2,6 +2,7 @@ #include "ui.h" #include "language.h" #ifndef NO_LUA +#include "fs.h" #include "ff.h" #include "vff.h" #include "fsutil.h" diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index 981f51024..f373b4802 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -10,9 +10,9 @@ #define TO_EMUNAND (1UL<<12) #define LEGIT (1UL<<13) -#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive", "to_emunand", "legit" -#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT -#define FLAGS_COUNT 11 +#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive", "to_emunand", "legit", "first" +#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT, FIND_FIRST +#define FLAGS_COUNT 12 #define LUASCRIPT_EXT "lua" #define LUASCRIPT_MAX_SIZE STD_BUFFER_SIZE diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index f03b6bba0..f0fc7db36 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -1,5 +1,11 @@ #ifndef NO_LUA #include "gm9ui.h" +#include "ui.h" +#include "fs.h" +#include "png.h" +#include "swkbd.h" +#include "qrcodegen.h" +#include "utils.h" #define MAXOPTIONS 256 #define MAXOPTIONS_STR "256" @@ -95,9 +101,17 @@ static int ui_ask_text(lua_State* L) { return 1; } +static int ui_clear(lua_State* L) { + CheckLuaArgCount(L, 0, "ui.clear"); + + ClearScreen(ALT_SCREEN, COLOR_STD_BG); + + return 0; +} + static int ui_show_png(lua_State* L) { CheckLuaArgCount(L, 1, "ui.show_png"); - const char* path = lua_tostring(L, 1); + const char* path = luaL_checkstring(L, 1); u16 *screen = ALT_SCREEN; u16 *bitmap = NULL; u8* png = (u8*) malloc(SCREEN_SIZE(screen)); @@ -139,6 +153,7 @@ static int ui_show_png(lua_State* L) { static int ui_show_text(lua_State* L) { CheckLuaArgCount(L, 1, "ui.show_text"); + const char* text = lua_tostring(L, 1); ClearScreen(ALT_SCREEN, COLOR_STD_BG); @@ -146,6 +161,43 @@ static int ui_show_text(lua_State* L) { return 0; } +static int ui_show_game_info(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.show_game_info"); + const char* path = luaL_checkstring(L, 1); + + bool ret = (ShowGameFileIcon(path, ALT_SCREEN) == 0); + if (!ret) { + return luaL_error(L, "ShowGameFileIcon failed on %s", path); + } + + return 0; +} + +static int ui_show_qr(lua_State* L) { + CheckLuaArgCount(L, 2, "ui.show_qr"); + size_t data_len; + const char* text = luaL_checkstring(L, 1); + const char* data = luaL_checklstring(L, 2, &data_len); + + const u32 screen_size = SCREEN_SIZE(ALT_SCREEN); + u8* screen_copy = (u8*) malloc(screen_size); + u8 qrcode[qrcodegen_BUFFER_LEN_MAX]; + u8 temp[qrcodegen_BUFFER_LEN_MAX]; + bool ret = screen_copy && qrcodegen_encodeText(data, temp, qrcode, qrcodegen_Ecc_LOW, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); + if (ret) { + memcpy(screen_copy, ALT_SCREEN, screen_size); + DrawQrCode(ALT_SCREEN, qrcode); + ShowPrompt(false, "%s", text); + memcpy(ALT_SCREEN, screen_copy, screen_size); + } else { + return luaL_error(L, "could not allocate memory"); + } + free(screen_copy); + + return 0; +} + static int ui_show_text_viewer(lua_State* L) { CheckLuaArgCount(L, 1, "ui.show_text_viewer"); size_t len = 0; @@ -167,7 +219,7 @@ static int ui_show_text_viewer(lua_State* L) { static int ui_show_file_text_viewer(lua_State* L) { CheckLuaArgCount(L, 1, "ui.show_file_text_viewer"); - const char* path = lua_tostring(L, 1); + const char* path = luaL_checkstring(L, 1); // validate text ourselves so we can return a better error // MemTextViewer calls ShowPrompt if it's bad, and i don't want that @@ -262,8 +314,11 @@ static const luaL_Reg ui_lib[] = { {"ask_number", ui_ask_number}, {"ask_text", ui_ask_text}, {"ask_selection", ui_ask_selection}, + {"clear", ui_clear}, {"show_png", ui_show_png}, {"show_text", ui_show_text}, + {"show_game_info", ui_show_game_info}, + {"show_qr", ui_show_qr}, {"show_text_viewer", ui_show_text_viewer}, {"show_file_text_viewer", ui_show_file_text_viewer}, {"format_bytes", ui_format_bytes}, diff --git a/arm9/source/lua/gm9ui.h b/arm9/source/lua/gm9ui.h index 2a50547b1..00f3ffa8f 100644 --- a/arm9/source/lua/gm9ui.h +++ b/arm9/source/lua/gm9ui.h @@ -1,9 +1,5 @@ #pragma once #include "gm9lua.h" -#include "ui.h" -#include "fs.h" -#include "png.h" -#include "swkbd.h" #define GM9LUA_UILIBNAME "ui" diff --git a/command comparison table.ods b/command comparison table.ods index 24621525dcb4e87a2ede6a9e72ec115ffb3e70ad..863364ed72cf9750f149ad7c1343723808adda83 100644 GIT binary patch delta 19908 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 i4A-0jYhvBZ0VcKaIuHoz^3{bZ9uLm75x{ Date: Mon, 2 Dec 2024 01:24:40 -0600 Subject: [PATCH 076/124] add missing constant --- arm9/source/lua/gm9lua.h | 1 + 1 file changed, 1 insertion(+) diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index f373b4802..d1c570762 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -9,6 +9,7 @@ #define RECURSIVE (1UL<<11) #define TO_EMUNAND (1UL<<12) #define LEGIT (1UL<<13) +#define FIND_FIRST (1UL<<14) #define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive", "to_emunand", "legit", "first" #define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT, FIND_FIRST From 8af7a1dfc808202ee30fe292e140463b70f837f7 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 2 Dec 2024 01:37:23 -0600 Subject: [PATCH 077/124] set CURRDIR to nil instead of "(null)" if not found --- arm9/source/lua/gm9lua.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 7a6beedda..fcc3e9fa5 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -172,7 +172,12 @@ bool ExecuteLuaScript(const char* path_script) { curr_dir[_VAR_CNT_LEN-1] = '\0'; char* slash = strrchr(curr_dir, '/'); if (slash) *slash = '\0'; - } else strncpy(curr_dir, "(null)", _VAR_CNT_LEN - 1); + + lua_pushstring(L, curr_dir); + } else { + lua_pushnil(L); + } + lua_setglobal(L, "CURRDIR"); lua_pushliteral(L, VERSION); lua_setglobal(L, "GM9VER"); @@ -180,9 +185,6 @@ bool ExecuteLuaScript(const char* path_script) { lua_pushstring(L, path_script); lua_setglobal(L, "SCRIPT"); - lua_pushstring(L, curr_dir); - lua_setglobal(L, "CURRDIR"); - lua_pushliteral(L, OUTPUT_PATH); lua_setglobal(L, "GM9OUT"); From 6d5b6e25aaf810ae78aec129b0ebe1a31af4d7a5 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 2 Dec 2024 01:39:57 -0600 Subject: [PATCH 078/124] remove gm9enum (unused since the restart) --- arm9/source/lua/gm9enum.c | 21 --------------------- arm9/source/lua/gm9enum.h | 13 ------------- arm9/source/lua/gm9lua.c | 4 ---- 3 files changed, 38 deletions(-) delete mode 100644 arm9/source/lua/gm9enum.c delete mode 100644 arm9/source/lua/gm9enum.h diff --git a/arm9/source/lua/gm9enum.c b/arm9/source/lua/gm9enum.c deleted file mode 100644 index 29c86656c..000000000 --- a/arm9/source/lua/gm9enum.c +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef NO_LUA -#include "gm9enum.h" - -void AddLuaEnumItems(lua_State* L, const char* name, const EnumItem* items) { - lua_getglobal(L, GM9LUA_ENUMLIBNAME); // stack: 1 - // this should probably use mua_createtable to pre-allocate the size - lua_newtable(L); // stack: 2 - const EnumItem* e; - for (e = items; e->name; e++) { - lua_pushinteger(L, e->value); // stack: 3 - lua_setfield(L, -2, e->name); // stack: 2 - } - lua_setfield(L, -2, name); // stack: 1 - lua_pop(L, 1); // stack: 0 -} - -int gm9lua_open_Enum(lua_State* L) { - lua_newtable(L); - return 1; -} -#endif diff --git a/arm9/source/lua/gm9enum.h b/arm9/source/lua/gm9enum.h deleted file mode 100644 index 1b2ea9897..000000000 --- a/arm9/source/lua/gm9enum.h +++ /dev/null @@ -1,13 +0,0 @@ -#include "gm9lua.h" - -#define GM9LUA_ENUMLIBNAME "Enum" -#define GLENUMITEM(what) {#what, what} - -typedef struct EnumItem { - const char *name; - lua_Unsigned value; -} EnumItem; - -void AddLuaEnumItems(lua_State* L, const char* name, const EnumItem* items); - -int gm9lua_open_Enum(lua_State* L); diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index fcc3e9fa5..1ad07dd96 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -8,7 +8,6 @@ #include "fsutil.h" #include "unittype.h" #include "nand.h" -#include "gm9enum.h" #include "gm9loader.h" #include "gm9fs.h" #include "gm9os.h" @@ -93,9 +92,6 @@ void CheckWritePermissionsLuaError(lua_State* L, const char* path) { } static const luaL_Reg gm9lualibs[] = { - // enum is special so we load it first - {GM9LUA_ENUMLIBNAME, gm9lua_open_Enum}, - // built-ins {LUA_GNAME, luaopen_base}, {LUA_LOADLIBNAME, luaopen_package}, From 723ff050c337a2b2c37030e27733e4f61f570cc6 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 2 Dec 2024 21:18:45 -0600 Subject: [PATCH 079/124] add ui.check_key --- arm9/source/lua/gm9ui.c | 11 +++++++++++ command comparison table.ods | Bin 24973 -> 25387 bytes data/luascripts/check-key-test.lua | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 data/luascripts/check-key-test.lua diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index f0fc7db36..cc1ca67ca 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -6,6 +6,7 @@ #include "swkbd.h" #include "qrcodegen.h" #include "utils.h" +#include "hid.h" #define MAXOPTIONS 256 #define MAXOPTIONS_STR "256" @@ -307,6 +308,15 @@ static int ui_global_print(lua_State* L) { return 0; } +static int ui_check_key(lua_State* L) { + CheckLuaArgCount(L, 1, "ui.check_key"); + const char* key = luaL_checkstring(L, 1); + + lua_pushboolean(L, CheckButton(StringToButton((char*)key))); + + return 1; +} + static const luaL_Reg ui_lib[] = { {"echo", ui_echo}, {"ask", ui_ask}, @@ -322,6 +332,7 @@ static const luaL_Reg ui_lib[] = { {"show_text_viewer", ui_show_text_viewer}, {"show_file_text_viewer", ui_show_file_text_viewer}, {"format_bytes", ui_format_bytes}, + {"check_key", ui_check_key}, {NULL, NULL} }; diff --git a/command comparison table.ods b/command comparison table.ods index 863364ed72cf9750f149ad7c1343723808adda83..ec7ecc5dd4d617027484fae03ae1427255630bc0 100644 GIT binary patch delta 23184 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 delta 22755 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

_MSiHT}m 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 diff --git a/data/luascripts/check-key-test.lua b/data/luascripts/check-key-test.lua new file mode 100644 index 000000000..db21f3b53 --- /dev/null +++ b/data/luascripts/check-key-test.lua @@ -0,0 +1,3 @@ +ui.echo("Checking for X in a second...") +print("Is Key X pressed:", ui.check_key("X")) +ui.echo("Done") From 9d9b327e93a3765317fd2b04be138acd55e99bc2 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 2 Dec 2024 21:43:58 -0600 Subject: [PATCH 080/124] split fs module to lua overlay and _fs internal module, and add a check for fs.write_file in lua --- arm9/source/lua/{gm9fs.c => gm9internalfs.c} | 164 +++++++++---------- arm9/source/lua/{gm9fs.h => gm9internalfs.h} | 4 +- arm9/source/lua/gm9lua.c | 8 +- data/luapackages/fs.lua | 50 ++++++ data/luascripts/fs-write-end-test.lua | 8 + data/preload.lua | 1 + 6 files changed, 147 insertions(+), 88 deletions(-) rename arm9/source/lua/{gm9fs.c => gm9internalfs.c} (76%) rename arm9/source/lua/{gm9fs.h => gm9internalfs.h} (85%) create mode 100644 data/luapackages/fs.lua create mode 100644 data/luascripts/fs-write-end-test.lua diff --git a/arm9/source/lua/gm9fs.c b/arm9/source/lua/gm9internalfs.c similarity index 76% rename from arm9/source/lua/gm9fs.c rename to arm9/source/lua/gm9internalfs.c index 78955d002..4474d4cd4 100644 --- a/arm9/source/lua/gm9fs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -1,5 +1,5 @@ #ifndef NO_LUA -#include "gm9fs.h" +#include "gm9internalfs.h" #include "fs.h" #include "ui.h" #include "utils.h" @@ -34,8 +34,8 @@ static void CreateStatTable(lua_State* L, FILINFO* fno) { // ... and leave this table on the stack for the caller to deal with } -static int fs_move(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 2, "fs.rename"); +static int internalfs_move(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.rename"); const char* path_src = luaL_checkstring(L, 1); const char* path_dst = luaL_checkstring(L, 2); FILINFO fno; @@ -55,8 +55,8 @@ static int fs_move(lua_State* L) { return 0; } -static int fs_remove(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.remove"); +static int internalfs_remove(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.remove"); const char* path = luaL_checkstring(L, 1); CheckWritePermissionsLuaError(L, path); @@ -79,8 +79,8 @@ static int fs_remove(lua_State* L) { return 0; } -static int fs_copy(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 2, "fs.copy"); +static int internalfs_copy(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.copy"); const char* path_src = luaL_checkstring(L, 1); const char* path_dst = luaL_checkstring(L, 2); FILINFO fno; @@ -109,8 +109,8 @@ static int fs_copy(lua_State* L) { return 0; } -static int fs_mkdir(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.mkdir"); +static int internalfs_mkdir(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.mkdir"); const char* path = luaL_checkstring(L, 1); CheckWritePermissionsLuaError(L, path); @@ -123,8 +123,8 @@ static int fs_mkdir(lua_State* L) { return 0; } -static int fs_list_dir(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.list_dir"); +static int internalfs_list_dir(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.list_dir"); const char* path = luaL_checkstring(L, 1); lua_newtable(L); @@ -151,8 +151,8 @@ static int fs_list_dir(lua_State* L) { return 1; } -static int fs_stat(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.stat"); +static int internalfs_stat(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.stat"); const char* path = luaL_checkstring(L, 1); FILINFO fno; @@ -164,8 +164,8 @@ static int fs_stat(lua_State* L) { return 1; } -static int fs_fix_cmacs(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.fix_cmacs"); +static int internalfs_fix_cmacs(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.fix_cmacs"); const char* path = luaL_checkstring(L, 1); ShowString("%s", STR_FIXING_CMACS_PLEASE_WAIT); @@ -176,8 +176,8 @@ static int fs_fix_cmacs(lua_State* L) { return 0; } -static int fs_stat_fs(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.stat_fs"); +static int internalfs_stat_fs(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.stat_fs"); const char* path = luaL_checkstring(L, 1); u64 freespace = GetFreeSpace(path); @@ -195,8 +195,8 @@ static int fs_stat_fs(lua_State* L) { return 1; } -static int fs_dir_info(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.stat_fs"); +static int internalfs_dir_info(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.stat_fs"); const char* path = luaL_checkstring(L, 1); u64 tsize = 0; @@ -217,8 +217,8 @@ static int fs_dir_info(lua_State* L) { return 1; } -static int fs_exists(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.exists"); +static int internalfs_exists(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.exists"); const char* path = luaL_checkstring(L, 1); FILINFO fno; @@ -226,16 +226,16 @@ static int fs_exists(lua_State* L) { return 1; } -static int fs_is_dir(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.is_dir"); +static int internalfs_is_dir(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.is_dir"); const char* path = luaL_checkstring(L, 1); lua_pushboolean(L, PathIsDirectory(path)); return 1; } -static int fs_is_file(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.is_file"); +static int internalfs_is_file(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.is_file"); const char* path = luaL_checkstring(L, 1); FILINFO fno; @@ -248,8 +248,8 @@ static int fs_is_file(lua_State* L) { return 1; } -static int fs_read_file(lua_State* L) { - CheckLuaArgCount(L, 3, "fs.read_file"); +static int internalfs_read_file(lua_State* L) { + CheckLuaArgCount(L, 3, "_fs.read_file"); const char* path = luaL_checkstring(L, 1); lua_Integer offset = luaL_checkinteger(L, 2); lua_Integer size = luaL_checkinteger(L, 3); @@ -269,8 +269,8 @@ static int fs_read_file(lua_State* L) { return 1; } -static int fs_write_file(lua_State* L) { - CheckLuaArgCount(L, 3, "fs.write_file"); +static int internalfs_write_file(lua_State* L) { + CheckLuaArgCount(L, 3, "_fs.write_file"); const char* path = luaL_checkstring(L, 1); lua_Integer offset = luaL_checkinteger(L, 2); size_t data_length = 0; @@ -288,8 +288,8 @@ static int fs_write_file(lua_State* L) { return 1; } -static int fs_fill_file(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 4, "fs.fill_file"); +static int internalfs_fill_file(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 4, "_fs.fill_file"); const char* path = luaL_checkstring(L, 1); lua_Integer offset = luaL_checkinteger(L, 2); lua_Integer size = luaL_checkinteger(L, 3); @@ -315,8 +315,8 @@ static int fs_fill_file(lua_State* L) { return 0; } -static int fs_make_dummy_file(lua_State* L) { - CheckLuaArgCount(L, 2, "fs.make_dummy_file"); +static int internalfs_make_dummy_file(lua_State* L) { + CheckLuaArgCount(L, 2, "_fs.make_dummy_file"); const char* path = luaL_checkstring(L, 1); lua_Integer size = luaL_checkinteger(L, 2); @@ -329,8 +329,8 @@ static int fs_make_dummy_file(lua_State* L) { return 0; } -static int fs_truncate(lua_State* L) { - CheckLuaArgCount(L, 2, "fs.write_file"); +static int internalfs_truncate(lua_State* L) { + CheckLuaArgCount(L, 2, "_fs.write_file"); const char* path = luaL_checkstring(L, 1); lua_Integer size = luaL_checkinteger(L, 2); FIL fp; @@ -359,8 +359,8 @@ static int fs_truncate(lua_State* L) { return 0; } -static int fs_img_mount(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.img_mount"); +static int internalfs_img_mount(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.img_mount"); const char* path = luaL_checkstring(L, 1); bool res = InitImgFS(path); @@ -371,16 +371,16 @@ static int fs_img_mount(lua_State* L) { return 0; } -static int fs_img_umount(lua_State* L) { - CheckLuaArgCount(L, 0, "fs.img_umount"); +static int internalfs_img_umount(lua_State* L) { + CheckLuaArgCount(L, 0, "_fs.img_umount"); InitImgFS(NULL); return 0; } -static int fs_get_img_mount(lua_State* L) { - CheckLuaArgCount(L, 0, "fs.img_umount"); +static int internalfs_get_img_mount(lua_State* L) { + CheckLuaArgCount(L, 0, "_fs.img_umount"); char path[256] = { 0 }; strncpy(path, GetMountPath(), 256); @@ -394,8 +394,8 @@ static int fs_get_img_mount(lua_State* L) { return 1; } -static int fs_hash_file(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 3, "fs.hash_file"); +static int internalfs_hash_file(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 3, "_fs.hash_file"); const char* path = luaL_checkstring(L, 1); lua_Integer offset = luaL_checkinteger(L, 2); lua_Integer size = luaL_checkinteger(L, 3); @@ -430,8 +430,8 @@ static int fs_hash_file(lua_State* L) { return 1; } -static int fs_hash_data(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.hash_data"); +static int internalfs_hash_data(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.hash_data"); size_t data_length = 0; const char* data = luaL_checklstring(L, 1, &data_length); @@ -454,8 +454,8 @@ static int fs_hash_data(lua_State* L) { return 1; } -static int fs_allow(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 1, "fs.img_mount"); +static int internalfs_allow(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.img_mount"); const char* path = luaL_checkstring(L, 1); u32 flags = 0; bool allowed; @@ -472,8 +472,8 @@ static int fs_allow(lua_State* L) { return 1; }; -static int fs_verify(lua_State* L) { - CheckLuaArgCount(L, 1, "fs.verify"); +static int internalfs_verify(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.verify"); const char* path = luaL_checkstring(L, 1); bool res; @@ -485,15 +485,15 @@ static int fs_verify(lua_State* L) { return 1; } -static int fs_sd_is_mounted(lua_State* L) { - CheckLuaArgCount(L, 0, "fs.sd_is_mounted"); +static int internalfs_sd_is_mounted(lua_State* L) { + CheckLuaArgCount(L, 0, "_fs.sd_is_mounted"); lua_pushboolean(L, CheckSDMountState()); return 1; } -static int fs_sd_switch(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 0, "fs.sd_switch"); +static int internalfs_sd_switch(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 0, "_fs.sd_switch"); const char* message; if (extra) { @@ -528,38 +528,38 @@ static int fs_sd_switch(lua_State* L) { return 0; } -static const luaL_Reg fs_lib[] = { - {"move", fs_move}, - {"remove", fs_remove}, - {"copy", fs_copy}, - {"mkdir", fs_mkdir}, - {"list_dir", fs_list_dir}, - {"stat", fs_stat}, - {"stat_fs", fs_stat_fs}, - {"dir_info", fs_dir_info}, - {"exists", fs_exists}, - {"is_dir", fs_is_dir}, - {"is_file", fs_is_file}, - {"read_file", fs_read_file}, - {"write_file", fs_write_file}, - {"fill_file", fs_fill_file}, - {"make_dummy_file", fs_make_dummy_file}, - {"truncate", fs_truncate}, - {"img_mount", fs_img_mount}, - {"img_umount", fs_img_umount}, - {"get_img_mount", fs_get_img_mount}, - {"hash_file", fs_hash_file}, - {"hash_data", fs_hash_data}, - {"verify", fs_verify}, - {"allow", fs_allow}, - {"sd_is_mounted", fs_sd_is_mounted}, - {"sd_switch", fs_sd_switch}, - {"fix_cmacs", fs_fix_cmacs}, +static const luaL_Reg internalfs_lib[] = { + {"move", internalfs_move}, + {"remove", internalfs_remove}, + {"copy", internalfs_copy}, + {"mkdir", internalfs_mkdir}, + {"list_dir", internalfs_list_dir}, + {"stat", internalfs_stat}, + {"stat_fs", internalfs_stat_fs}, + {"dir_info", internalfs_dir_info}, + {"exists", internalfs_exists}, + {"is_dir", internalfs_is_dir}, + {"is_file", internalfs_is_file}, + {"read_file", internalfs_read_file}, + {"write_file", internalfs_write_file}, + {"fill_file", internalfs_fill_file}, + {"make_dummy_file", internalfs_make_dummy_file}, + {"truncate", internalfs_truncate}, + {"img_mount", internalfs_img_mount}, + {"img_umount", internalfs_img_umount}, + {"get_img_mount", internalfs_get_img_mount}, + {"hash_file", internalfs_hash_file}, + {"hash_data", internalfs_hash_data}, + {"verify", internalfs_verify}, + {"allow", internalfs_allow}, + {"sd_is_mounted", internalfs_sd_is_mounted}, + {"sd_switch", internalfs_sd_switch}, + {"fix_cmacs", internalfs_fix_cmacs}, {NULL, NULL} }; -int gm9lua_open_fs(lua_State* L) { - luaL_newlib(L, fs_lib); +int gm9lua_open_internalfs(lua_State* L) { + luaL_newlib(L, internalfs_lib); return 1; } #endif diff --git a/arm9/source/lua/gm9fs.h b/arm9/source/lua/gm9internalfs.h similarity index 85% rename from arm9/source/lua/gm9fs.h rename to arm9/source/lua/gm9internalfs.h index dda9c0b52..99f1c18a2 100644 --- a/arm9/source/lua/gm9fs.h +++ b/arm9/source/lua/gm9internalfs.h @@ -1,7 +1,7 @@ #pragma once #include "gm9lua.h" -#define GM9LUA_FSLIBNAME "fs" +#define GM9LUA_INTERNALFSLIBNAME "_fs" #define SHA256_EMPTY_HASH \ 0xE3, 0xB0, 0xC4, 0x42, \ @@ -20,4 +20,4 @@ 0x95, 0x60, 0x18, 0x90, \ 0xAF, 0xD8, 0x07, 0x09 -int gm9lua_open_fs(lua_State* L); +int gm9lua_open_internalfs(lua_State* L); diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 1ad07dd96..0e8173299 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -9,11 +9,11 @@ #include "unittype.h" #include "nand.h" #include "gm9loader.h" -#include "gm9fs.h" #include "gm9os.h" -#include "gm9internalsys.h" #include "gm9ui.h" #include "gm9title.h" +#include "gm9internalfs.h" +#include "gm9internalsys.h" #define DEBUGSP(x) ShowPrompt(false, (x)) // this is taken from scripting.c @@ -103,12 +103,12 @@ static const luaL_Reg gm9lualibs[] = { {LUA_DBLIBNAME, luaopen_debug}, // gm9 custom - {GM9LUA_FSLIBNAME, gm9lua_open_fs}, {GM9LUA_OSLIBNAME, gm9lua_open_os}, {GM9LUA_UILIBNAME, gm9lua_open_ui}, {GM9LUA_TITLELIBNAME, gm9lua_open_title}, // gm9 custom internals (usually wrapped by a pure lua module) + {GM9LUA_INTERNALFSLIBNAME, gm9lua_open_internalfs}, {GM9LUA_INTERNALSYSLIBNAME, gm9lua_open_internalsys}, {NULL, NULL} @@ -198,7 +198,7 @@ bool ExecuteLuaScript(const char* path_script) { bool result = RunFile(L, "V:/preload.lua"); if (!result) { - ShowPrompt(false, "A fatal error happened in GodMode9's preload script.\n\nThis is not an error with your code, but with\nGodMode9. Please report it on GitHub."); + ShowPrompt(false, "A fatal error happened in GodMode9's preload script.\n \nThis is not an error with your code, but with\nGodMode9. Please report it on GitHub."); lua_close(L); return false; } diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua new file mode 100644 index 000000000..71e78f0da --- /dev/null +++ b/data/luapackages/fs.lua @@ -0,0 +1,50 @@ +local fs = {} + +fs.move = _fs.move +fs.remove = _fs.remove +fs.copy = _fs.copy +fs.mkdir = _fs.mkdir +fs.list_dir = _fs.list_dir +fs.stat = _fs.stat +fs.stat_fs = _fs.stat_fs +fs.dir_info = _fs.dir_info +fs.exists = _fs.exists +fs.is_dir = _fs.is_dir +fs.is_file = _fs.is_file +fs.read_file = _fs.read_file +fs.fill_file = _fs.fill_file +fs.make_dummy_file = _fs.make_dummy_file +fs.truncate = _fs.truncate +fs.img_mount = _fs.img_mount +fs.img_umount = _fs.img_umount +fs.get_img_mount = _fs.get_img_mount +fs.hash_file = _fs.hash_file +fs.hash_data = _fs.hash_data +fs.verify = _fs.verify +fs.allow = _fs.allow +fs.sd_is_mounted = _fs.sd_is_mounted +fs.sd_switch = _fs.sd_switch +fs.fix_cmacs = _fs.fix_cmacs + +function fs.write_file(path, offset, data) + local success, filestat + if type(offset) == "string" then + if offset == "end" then + success, filestat = pcall(fs.stat, path) + if success then + offset = filestat.size + else + -- allows for this to work on files that don't yet exist + offset = 0 + end + else + error("offset should be an integer or \"end\"") + end + elseif type(offset) ~= "number" then + error("offset should be an integer or \"end\"") + end + + return _fs.write_file(path, offset, data) +end + +return fs diff --git a/data/luascripts/fs-write-end-test.lua b/data/luascripts/fs-write-end-test.lua new file mode 100644 index 000000000..1574cf2cb --- /dev/null +++ b/data/luascripts/fs-write-end-test.lua @@ -0,0 +1,8 @@ +print("Testing") +-- create file normally +fs.write_file("9:/test1.txt", 0, "test") +-- append to end of it +fs.write_file("9:/test1.txt", "end", "ing") +-- try to append, but should create if it doesn't exist +fs.write_file("9:/test2.txt", "end", "stuff") +ui.echo("Done") diff --git a/data/preload.lua b/data/preload.lua index 75f417a9b..7c78615a8 100644 --- a/data/preload.lua +++ b/data/preload.lua @@ -2,6 +2,7 @@ -- The purpose of this one is to initialize some variables and modules. -- If you're looking for an auto-running script, you want "autorun.lua"! +fs = require('fs') util = require('util') io = require('io') sys = require('sys') From f8738ef5f78f184ba6170886256fe7f65965c775 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Mon, 2 Dec 2024 23:31:13 -0600 Subject: [PATCH 081/124] add fs.ask_select_file and fs.ask_select_dir --- arm9/source/lua/gm9internalfs.c | 55 ++++++++++++++++++++++++++++++ arm9/source/lua/gm9lua.c | 2 -- arm9/source/lua/gm9lua.h | 11 ++++-- command comparison table.ods | Bin 25387 -> 26093 bytes data/luapackages/fs.lua | 2 ++ data/luascripts/selector-test.lua | 21 ++++++++++++ flake.nix | 1 + 7 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 data/luascripts/selector-test.lua diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 4474d4cd4..756360d8f 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -217,6 +217,59 @@ static int internalfs_dir_info(lua_State* L) { return 1; } +static int FileDirSelector(lua_State* L, const char* path_orig, const char* prompt, bool is_dir, bool include_dirs, bool explorer) { + bool ret; + char path[_VAR_CNT_LEN] = { 0 }; + char choice[_VAR_CNT_LEN] = { 0 }; + strncpy(path, path_orig, _VAR_CNT_LEN); + if (strncmp(path, "Z:", 2) == 0) { + return luaL_error(L, "forbidden drive"); + } else if (!is_dir) { + u32 flags_ext = include_dirs ? 0 : NO_DIRS; + char *npattern = strrchr(path, '/'); + if (!npattern) { + return luaL_error(L, "invalid path"); + } + *(npattern++) = '\0'; + ret = FileSelector(choice, prompt, path, npattern, flags_ext, explorer); + } else { + ret = FileSelector(choice, prompt, path, NULL, NO_FILES | SELECT_DIRS, explorer); + } + + if (ret) { + lua_pushstring(L, choice); + } else { + lua_pushnil(L); + } + return 1; +} + +static int internalfs_ask_select_file(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.ask_select_file"); + const char* prompt = luaL_checkstring(L, 1); + const char* path = luaL_checkstring(L, 2); + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 3, flags, INCLUDE_DIRS | EXPLORER); + }; + + return FileDirSelector(L, path, prompt, false, (flags & INCLUDE_DIRS), (flags & EXPLORER)); +} + +static int internalfs_ask_select_dir(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.ask_select_dir"); + const char* prompt = luaL_checkstring(L, 1); + const char* path = luaL_checkstring(L, 2); + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 3, flags, EXPLORER); + }; + + return FileDirSelector(L, path, prompt, true, true, (flags & EXPLORER)); +} + static int internalfs_exists(lua_State* L) { CheckLuaArgCount(L, 1, "_fs.exists"); const char* path = luaL_checkstring(L, 1); @@ -537,6 +590,8 @@ static const luaL_Reg internalfs_lib[] = { {"stat", internalfs_stat}, {"stat_fs", internalfs_stat_fs}, {"dir_info", internalfs_dir_info}, + {"ask_select_file", internalfs_ask_select_file}, + {"ask_select_dir", internalfs_ask_select_dir}, {"exists", internalfs_exists}, {"is_dir", internalfs_is_dir}, {"is_file", internalfs_is_file}, diff --git a/arm9/source/lua/gm9lua.c b/arm9/source/lua/gm9lua.c index 0e8173299..c90b0ffc3 100644 --- a/arm9/source/lua/gm9lua.c +++ b/arm9/source/lua/gm9lua.c @@ -16,8 +16,6 @@ #include "gm9internalsys.h" #define DEBUGSP(x) ShowPrompt(false, (x)) -// this is taken from scripting.c -#define _VAR_CNT_LEN 256 typedef struct GM9LuaLoadF { int n; // pre-read characters diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index d1c570762..0942666fa 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -10,14 +10,19 @@ #define TO_EMUNAND (1UL<<12) #define LEGIT (1UL<<13) #define FIND_FIRST (1UL<<14) +#define INCLUDE_DIRS (1UL<<15) +#define EXPLORER (1UL<<16) -#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive", "to_emunand", "legit", "first" -#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT, FIND_FIRST -#define FLAGS_COUNT 12 +#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive", "to_emunand", "legit", "first", "include_dirs", "explorer" +#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT, FIND_FIRST, INCLUDE_DIRS, EXPLORER +#define FLAGS_COUNT 14 #define LUASCRIPT_EXT "lua" #define LUASCRIPT_MAX_SIZE STD_BUFFER_SIZE +// taken from arm9/source/utils/scripting.c +#define _VAR_CNT_LEN 256 + #ifndef NO_LUA static inline void CheckLuaArgCount(lua_State* L, int argcount, const char* cmd) { int args = lua_gettop(L); diff --git a/command comparison table.ods b/command comparison table.ods index ec7ecc5dd4d617027484fae03ae1427255630bc0..3d7fe8120222daafe06d3670379c4b5f95863ded 100644 GIT binary patch delta 23769 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 delta 23138 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 I?0jndKM~G@Gynhq diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua index 71e78f0da..b9341b3f2 100644 --- a/data/luapackages/fs.lua +++ b/data/luapackages/fs.lua @@ -8,6 +8,8 @@ fs.list_dir = _fs.list_dir fs.stat = _fs.stat fs.stat_fs = _fs.stat_fs fs.dir_info = _fs.dir_info +fs.ask_select_file = _fs.ask_select_file +fs.ask_select_dir = _fs.ask_select_dir fs.exists = _fs.exists fs.is_dir = _fs.is_dir fs.is_file = _fs.is_file diff --git a/data/luascripts/selector-test.lua b/data/luascripts/selector-test.lua new file mode 100644 index 000000000..5c62c042d --- /dev/null +++ b/data/luascripts/selector-test.lua @@ -0,0 +1,21 @@ +for i, v in pairs({"9:/test_1", "9:/test_2", "9:/extradir_3"}) do + print("Making dir", v) + fs.mkdir(v) +end + +for i, v in pairs({"9:/test_1.bin", "9:/test_2.bin", "9:/extratest_3.bin", "9:/test_2/thingy.txt"}) do + print("Touching file", v) + fs.write_file(v, 0, "a") +end + +print() +print("Selected:", fs.ask_select_file("Select a file!", "9:/test_*")) +print("Selected:", fs.ask_select_file("Select a file! (explorer)", "9:/test_*", {explorer=true})) + +print("Selected:", fs.ask_select_dir("Select a directory!", "9:/")) +print("Selected:", fs.ask_select_dir("Select a directory! (explorer)", "9:/", {explorer=true})) + +print("Selected:", fs.ask_select_file("Select a file OR directory!", "9:/test_*", {include_dirs=true})) +print("Selected:", fs.ask_select_file("Select a file OR directory! (explorer)", "9:/test_*", {include_dirs=true, explorer=true})) +print("Done") +ui.echo("Done") diff --git a/flake.nix b/flake.nix index 5636b9193..29a878e27 100644 --- a/flake.nix +++ b/flake.nix @@ -12,6 +12,7 @@ pkgs.devkitNix.devkitARM python3Packages.python ( python3Packages.callPackage ./firmtool.nix { } ) + lua54Packages.lua ]; inherit (pkgs.devkitNix.devkitARM) shellHook; From 37904010411afe3787dd0e47b807b0d3451c705d Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 3 Dec 2024 00:17:24 -0600 Subject: [PATCH 082/124] add fs.key_dump, replace overwrite_all and append_all with overwrite and append --- arm9/source/lua/gm9internalfs.c | 77 ++++++++++++++++++++++++++++++ arm9/source/lua/gm9lua.h | 2 +- command comparison table.ods | Bin 26093 -> 26378 bytes data/luapackages/fs.lua | 3 ++ data/luascripts/find-test.lua | 5 ++ data/luascripts/fs-ops-test.lua | 2 +- data/luascripts/key-dump-test.lua | 5 ++ data/scripts/sdump-test.gm9 | 4 ++ 8 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 data/luascripts/find-test.lua create mode 100644 data/luascripts/key-dump-test.lua create mode 100644 data/scripts/sdump-test.gm9 diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 756360d8f..6c13a8cce 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -7,6 +7,7 @@ #include "nand.h" #include "language.h" #include "hid.h" +#include "game.h" static u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; static u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; @@ -270,6 +271,40 @@ static int internalfs_ask_select_dir(lua_State* L) { return FileDirSelector(L, path, prompt, true, true, (flags & EXPLORER)); } +static int internalfs_find(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.find"); + const char* pattern = luaL_checkstring(L, 1); + char path[_VAR_CNT_LEN] = { 0 }; + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 2, flags, FIND_FIRST); + } + + u8 mode = (flags & FIND_FIRST) ? FN_LOWEST : FN_HIGHEST; + FRESULT res = fvx_findpath(path, pattern, mode); + if (res != FR_OK) { + return luaL_error(L, "failed to find %s (%d)", path, res); + } + + lua_pushstring(L, path); + return 1; +} + +static int internalfs_find_not(lua_State* L) { + CheckLuaArgCount(L, 1, "_fs.find_not"); + const char* pattern = luaL_checkstring(L, 1); + char path[_VAR_CNT_LEN] = { 0 }; + + FRESULT res = fvx_findnopath(path, pattern); + if (res != FR_OK) { + return luaL_error(L, "failed to find %s (%d)", path, res); + } + + lua_pushstring(L, path); + return 1; +} + static int internalfs_exists(lua_State* L) { CheckLuaArgCount(L, 1, "_fs.exists"); const char* path = luaL_checkstring(L, 1); @@ -581,6 +616,45 @@ static int internalfs_sd_switch(lua_State* L) { return 0; } +static int internalfs_key_dump(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.key_dump"); + const char* opts[] = {SEEDINFO_NAME, TIKDB_NAME_ENC, TIKDB_NAME_DEC, NULL}; + int opt = luaL_checkoption(L, 1, NULL, opts); + bool ret = false; + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 2, 0, OVERWRITE_ALL); + } + + if (opt == 1 || opt == 2) { + bool tik_dec = opt == 2; + if (flags & OVERWRITE_ALL) fvx_unlink(tik_dec ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC); + if (BuildTitleKeyInfo(NULL, tik_dec, false) == 0) { + ShowString(STR_BUILDING_TO_OUT_ARG, OUTPUT_PATH, opts[opt]); + if (((BuildTitleKeyInfo("1:/dbs/ticket.db", tik_dec, false) == 0) || + (BuildTitleKeyInfo("4:/dbs/ticket.db", tik_dec, false) == 0)) && + (BuildTitleKeyInfo(NULL, tik_dec, true) == 0)) + ret = true; + } + } else if (opt == 0) { + if (flags & OVERWRITE_ALL) fvx_unlink(OUTPUT_PATH "/" SEEDINFO_NAME); + if (BuildSeedInfo(NULL, false) == 0) { + ShowString(STR_BUILDING_TO_OUT_ARG, OUTPUT_PATH, opts[opt]); + if (((BuildSeedInfo("1:", false) == 0) || + (BuildSeedInfo("4:", false) == 0)) && + (BuildSeedInfo(NULL, true) == 0)) + ret = true; + } + } + + if (!ret) { + return luaL_error(L, "building %s failed", opts[opt]); + } + + return 0; +}; + static const luaL_Reg internalfs_lib[] = { {"move", internalfs_move}, {"remove", internalfs_remove}, @@ -592,6 +666,8 @@ static const luaL_Reg internalfs_lib[] = { {"dir_info", internalfs_dir_info}, {"ask_select_file", internalfs_ask_select_file}, {"ask_select_dir", internalfs_ask_select_dir}, + {"find", internalfs_find}, + {"find_not", internalfs_find_not}, {"exists", internalfs_exists}, {"is_dir", internalfs_is_dir}, {"is_file", internalfs_is_file}, @@ -610,6 +686,7 @@ static const luaL_Reg internalfs_lib[] = { {"sd_is_mounted", internalfs_sd_is_mounted}, {"sd_switch", internalfs_sd_switch}, {"fix_cmacs", internalfs_fix_cmacs}, + {"key_dump", internalfs_key_dump}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index 0942666fa..618840156 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -13,7 +13,7 @@ #define INCLUDE_DIRS (1UL<<15) #define EXPLORER (1UL<<16) -#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite_all", "append_all", "all", "recursive", "to_emunand", "legit", "first", "include_dirs", "explorer" +#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite", "append", "all", "recursive", "to_emunand", "legit", "first", "include_dirs", "explorer" #define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT, FIND_FIRST, INCLUDE_DIRS, EXPLORER #define FLAGS_COUNT 14 diff --git a/command comparison table.ods b/command comparison table.ods index 3d7fe8120222daafe06d3670379c4b5f95863ded..1a52edbc6527e69803bc5bbeb8d7406bc49396e8 100644 GIT binary patch delta 21124 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 hV*o(t|CJfU=6L Date: Tue, 3 Dec 2024 01:13:10 -0600 Subject: [PATCH 083/124] add fs.cart_dump and sys.emu_base --- arm9/source/lua/gm9internalfs.c | 48 +++++++++++++++++++++++++++++- arm9/source/lua/gm9internalsys.c | 8 +++++ arm9/source/lua/gm9lua.h | 7 +++-- command comparison table.ods | Bin 26378 -> 26372 bytes data/luapackages/fs.lua | 1 + data/luapackages/sys.lua | 2 ++ data/luascripts/cartdump-test.lua | 5 ++++ data/scripts/cartdump-test.gm9 | 3 ++ 8 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 data/luascripts/cartdump-test.lua create mode 100644 data/scripts/cartdump-test.gm9 diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 6c13a8cce..565da9daa 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -8,6 +8,7 @@ #include "language.h" #include "hid.h" #include "game.h" +#include "gamecart.h" static u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; static u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; @@ -653,7 +654,51 @@ static int internalfs_key_dump(lua_State* L) { } return 0; -}; +} + +static int internalfs_cart_dump(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.cart_dump"); + const char* path = luaL_checkstring(L, 1); + u64 fsize = (u64)luaL_checkinteger(L, 2); + bool ret = false; + const char* errstr = ""; + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 3, flags, ENCRYPTED); + } + + CartData* cdata = (CartData*) malloc(sizeof(CartData)); + u8* buf = (u8*) malloc(STD_BUFFER_SIZE); + ret = false; + if (!cdata || !buf) { + errstr = "out of memory"; + } else if (InitCartRead(cdata) != 0){ + errstr = "cart init fail"; + } else { + SetSecureAreaEncryption(flags & ENCRYPTED); + fvx_unlink(path); + ret = true; + errstr = "cart dump failed or canceled"; + for (u64 p = 0; p < fsize; p += STD_BUFFER_SIZE) { + u64 len = min((fsize - p), STD_BUFFER_SIZE); + ShowProgress(p, fsize, path); + if (!ShowProgress(p, fsize, path) || + (ReadCartBytes(buf, p, len, cdata, false) != 0) || + (fvx_qwrite(path, buf, p, len, NULL) != FR_OK)) { + ret = false; + break; + } + } + } + free(buf); + free(cdata); + + if (!ret) { + return luaL_error(L, "%s", errstr); + } + return 0; +} static const luaL_Reg internalfs_lib[] = { {"move", internalfs_move}, @@ -687,6 +732,7 @@ static const luaL_Reg internalfs_lib[] = { {"sd_switch", internalfs_sd_switch}, {"fix_cmacs", internalfs_fix_cmacs}, {"key_dump", internalfs_key_dump}, + {"cart_dump", internalfs_cart_dump}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9internalsys.c b/arm9/source/lua/gm9internalsys.c index 88338ae7a..4d74a7220 100644 --- a/arm9/source/lua/gm9internalsys.c +++ b/arm9/source/lua/gm9internalsys.c @@ -81,6 +81,13 @@ static int internalsys_next_emu(lua_State* L) { return 0; } +static int internalsys_get_emu_base(lua_State* L) { + CheckLuaArgCount(L, 0, "_sys.get_emu_base"); + + lua_pushinteger(L, GetEmuNandBase()); + return 1; +} + static int internalsys_global_bkpt(lua_State* L) { UNUSED(L); bkpt; @@ -93,6 +100,7 @@ static const luaL_Reg internalsys_lib[] = { {"power_off", internalsys_power_off}, {"get_id0", internalsys_get_id0}, {"next_emu", internalsys_next_emu}, + {"get_emu_base", internalsys_get_emu_base}, {NULL, NULL} }; diff --git a/arm9/source/lua/gm9lua.h b/arm9/source/lua/gm9lua.h index 618840156..61a2282e6 100644 --- a/arm9/source/lua/gm9lua.h +++ b/arm9/source/lua/gm9lua.h @@ -12,10 +12,11 @@ #define FIND_FIRST (1UL<<14) #define INCLUDE_DIRS (1UL<<15) #define EXPLORER (1UL<<16) +#define ENCRYPTED (1UL<<17) -#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite", "append", "all", "recursive", "to_emunand", "legit", "first", "include_dirs", "explorer" -#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT, FIND_FIRST, INCLUDE_DIRS, EXPLORER -#define FLAGS_COUNT 14 +#define FLAGS_STR "no_cancel", "silent", "calc_sha", "sha1", "skip", "overwrite", "append", "all", "recursive", "to_emunand", "legit", "first", "include_dirs", "explorer", "encrypted" +#define FLAGS_CONSTS NO_CANCEL, SILENT, CALC_SHA, USE_SHA1, SKIP_ALL, OVERWRITE_ALL, APPEND_ALL, ASK_ALL, RECURSIVE, TO_EMUNAND, LEGIT, FIND_FIRST, INCLUDE_DIRS, EXPLORER, ENCRYPTED +#define FLAGS_COUNT 15 #define LUASCRIPT_EXT "lua" #define LUASCRIPT_MAX_SIZE STD_BUFFER_SIZE diff --git a/command comparison table.ods b/command comparison table.ods index 1a52edbc6527e69803bc5bbeb8d7406bc49396e8..317affd7209f518cdb0f969d47546b3c64f29cfd 100644 GIT binary patch delta 7998 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�M+`Qmr= zR9)P+HH1y}*#ZvyfDW$J?%*_eOvxAH6Oip;xOq~j!4&x;g6D5x_zGtHBKG}ZV}N6T zekOZ_gxq^2`c|dwmpd4_1LFfT~w`6joxpA4od-L0n($V03#+Bsv-z4xMX+l1`h zZ&0f~&3|SHXN>4VWKObSzHzw4TF+~JRI65!pr z9encV$t$~mzCKtjEg^ZZANX*u^Xrv0jYRHCh>um1gW#OYjD7C5p>r!NcjLyJHd#NB z9iwukNBc}}JT4p$i4v*8R`BSF1oQQN{ZK+4gS?ke{`?Y-SBB}R(7@oJcJ_hli&L0OZ@lPP~AUM`$ke%g&AhZ=K(RtYxgV}t6rb| z6DQ0p^QbkygQVotq@APQnjo&>a#^!YijqI)opRYx@2t8yBgvkkHBEmS=5Isi5=m}s z2Ig8OTJS4yfV6lv6~^E9O$-GfC97X>zw6=QXV{M9VY>>n1P$ZUey^z^95jKOF}ue8)4Ywx>7c$r3#u$8gNg?rWYsntZiQ%4v#fkZ{5pj z**Ygg{5V3Qs1>V_<<=9sp=r?Fu?P+DKIVISsofjCoF<@y2fyK^2!4AP&cCCIxEM@6 zcowWhq8KXgfk;rQ+J#jOU~WhlVkw6_##OXvtTj|gcW()Oi5lD~TE$MCnp1UYd z8L80^(8?A_rAflNj;yDk!^z^M4Qk+9~$nO;V~K3gM}0d_xkS zGqGVvCB6^kDu1mWiX`cEla%*r1LD@UVC?RfO6;*oge&j3c53P1`p2>|o1W1x0U0l7Zr?(~xB7i}RK0C=?gb z;e_Xe1Z6-nYzW@cQ zC}1x>>YqF0O@`Jdmtvra-_7v>i@{OtIQwrd;(j3oiezIPf~9LqSsj9;p5(6_;^I>pt`5Hru%39d`2>`anX! zxCC7wpR0kqp@HMmCk)T8Q7{)B361(ak_kzrL8<}BWu;capJ7rnpwF>42>--t*k zvHMF#5z)2+egk?%Zyy6rjtRA2I z2v`C(7e=z-)z4ATKl{J~EE{%uYJn>!5>t3NU@F`bd7()1KMe z6_A0I2BQV3jUMJt2-;ISLeSANEO2%~!5)nJ^25G4DV6Mk(vIs$0gOEFDdWMPA??+* zxu}4KMbV?|Uey4wGqE*@*g-%3p|17cUX})W#^qmV#frB0(DY|2C(`dUZVh+e^gUvj+PKKWYsvm)+!iU+*g0W?yQ$kYOenxf#)^fNbR;pE|5QF=!y z2p3VKWw`=^<^nj&+p>;@c2>i|T29$6X0!gyI7PXJq`Hc)MWm-}uEMg}M5o?&j0gj* zc4d-`{d1+mn0>g-%?RY1#6;9|S1X`t7rO^1ri}&y)RpZ*ZSH1e6Jnh;?ABJaAqyT3 z^@|R6dx!bqJLejFD&v*G<|2Qrg z=2YCD_Y2B6{uNhxbw-EI#|WXy3KfmZt16Wx`0FoLC8>8#Wk>7(N_qhbH}(inP_exK zGiUzulU#d9DJ1rfqE}02;{LC)mudR%5AcjB=&#O4VJ7?6S({1!CnA9in=$CzX*YaG-9Gakj`I85n^t^@fSu=lu delta 8087 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 diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua index b654a50ca..4d3c3dd94 100644 --- a/data/luapackages/fs.lua +++ b/data/luapackages/fs.lua @@ -30,6 +30,7 @@ fs.sd_is_mounted = _fs.sd_is_mounted fs.sd_switch = _fs.sd_switch fs.fix_cmacs = _fs.fix_cmacs fs.key_dump = _fs.key_dump +fs.cart_dump = _fs.cart_dump function fs.write_file(path, offset, data) local success, filestat diff --git a/data/luapackages/sys.lua b/data/luapackages/sys.lua index b2f3f9357..4b7b857da 100644 --- a/data/luapackages/sys.lua +++ b/data/luapackages/sys.lua @@ -9,6 +9,7 @@ sys.region = nil sys.serial = nil sys.sys_id0 = nil sys.emu_id0 = nil +sys.emu_base = nil local regions = {"JPN", "USA", "EUR", "AUS", "CHN", "KOR", "TWN"} @@ -47,6 +48,7 @@ end function sys.refresh_info() refresh_secureinfo() refresh_id0() + sys.emu_base = _sys.get_emu_base() end function sys.next_emu() diff --git a/data/luascripts/cartdump-test.lua b/data/luascripts/cartdump-test.lua new file mode 100644 index 000000000..8d929bdf7 --- /dev/null +++ b/data/luascripts/cartdump-test.lua @@ -0,0 +1,5 @@ +print("Doing dec") +fs.cart_dump("9:/test-dec.bin", 0x06000000) +print("Doing enc") +fs.cart_dump("9:/test-enc.bin", 0x06000000, {encrypted=true}) +ui.echo("Done") diff --git a/data/scripts/cartdump-test.gm9 b/data/scripts/cartdump-test.gm9 new file mode 100644 index 000000000..c2dfa1d92 --- /dev/null +++ b/data/scripts/cartdump-test.gm9 @@ -0,0 +1,3 @@ +cartdump 9:/test-dec.bin 06000000 +cartdump 9:/test-enc.bin 06000000 -e +echo done From 722c54c5caaa731aa65d1d2ed7bbb76c828dcdd6 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 4 Dec 2024 23:27:01 -0600 Subject: [PATCH 084/124] add ctrtool, update flake.lock --- flake.lock | 79 +++++++++++++++++++++++++++++++++++++++++++++--------- flake.nix | 4 ++- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/flake.lock b/flake.lock index c97277f3a..ba21547ab 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1731803694, - "narHash": "sha256-WfYXkVaJzqnuP3S7AF3O+Ee6Mrwf3bvrMAW8sZ3YTPQ=", + "lastModified": 1733376466, + "narHash": "sha256-Jl8kdjck606pxtCMZ3+pc6Jj7itY60vPCdbdVt4dAzo=", "owner": "ihaveamac", "repo": "devkitNix", - "rev": "2be1d71b669d36dd94c8ade93704069819f393a8", + "rev": "883d173b94e3da8dc4cc0860cdda8c36b738817c", "type": "github" }, "original": { @@ -20,12 +20,15 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -34,13 +37,31 @@ "type": "github" } }, + "hax-nur": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1733360420, + "narHash": "sha256-MU9R5PIztTbLGGHeFQMAS5lVvkyLDsOLjhkMf2kqOfY=", + "owner": "ihaveamac", + "repo": "nur-packages", + "rev": "c570b3830f7dd4d655afb109300529c896cd8855", + "type": "github" + }, + "original": { + "owner": "ihaveamac", + "repo": "nur-packages", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1671877799, - "narHash": "sha256-jjC0NtPOT4huSwyichdrKHVCjuGr1al7Wu6PcHo5XZs=", + "lastModified": 1733229606, + "narHash": "sha256-FLYY5M0rpa5C2QAE3CKLYAM6TwbKicdRK6qNrSHlNrE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8351f271f85dae1ee28269028acde661e60394dd", + "rev": "566e53c2ad750c84f6d31f9ccb9d00f823165550", "type": "github" }, "original": { @@ -52,11 +73,27 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1731890469, - "narHash": "sha256-D1FNZ70NmQEwNxpSSdTXCSklBH1z2isPR84J6DQrJGs=", + "lastModified": 1733097829, + "narHash": "sha256-9hbb1rqGelllb4kVUCZ307G2k3/UhmA8PPGBoyuWaSw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2c15aa59df0017ca140d9ba302412298ab4bf22a", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1733229606, + "narHash": "sha256-FLYY5M0rpa5C2QAE3CKLYAM6TwbKicdRK6qNrSHlNrE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5083ec887760adfe12af64830a66807423a859a7", + "rev": "566e53c2ad750c84f6d31f9ccb9d00f823165550", "type": "github" }, "original": { @@ -69,7 +106,23 @@ "root": { "inputs": { "devkitNix": "devkitNix", - "nixpkgs": "nixpkgs_2" + "hax-nur": "hax-nur", + "nixpkgs": "nixpkgs_3" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 29a878e27..6d4eeb866 100644 --- a/flake.nix +++ b/flake.nix @@ -2,9 +2,10 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; devkitNix.url = "github:ihaveamac/devkitNix"; + hax-nur.url = "github:ihaveamac/nur-packages"; }; - outputs = { self, nixpkgs, devkitNix }: { + outputs = { self, nixpkgs, devkitNix, hax-nur }: { devShells.x86_64-linux.default = let pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ devkitNix.overlays.default ]; }; in pkgs.mkShell { @@ -13,6 +14,7 @@ python3Packages.python ( python3Packages.callPackage ./firmtool.nix { } ) lua54Packages.lua + hax-nur.packages.x86_64-linux.ctrtool ]; inherit (pkgs.devkitNix.devkitARM) shellHook; From 6e8208d5fda38cdce42897c854de00d063749d39 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sat, 7 Dec 2024 00:05:34 -0600 Subject: [PATCH 085/124] add io append mode --- data/luapackages/io.lua | 60 ++++++++++++++++++++++++++---- data/luascripts/io-append-test.lua | 14 +++++++ 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 data/luascripts/io-append-test.lua diff --git a/data/luapackages/io.lua b/data/luapackages/io.lua index 5463466ec..a6441ce2d 100644 --- a/data/luapackages/io.lua +++ b/data/luapackages/io.lua @@ -18,6 +18,15 @@ local function not_impl(fnname) end end +-- some errors should return nil, an error string, then an error number +-- example: +-- > io.open("/nix/abc.txt", "w") +-- nil /nix/abc.txt: Permission denied 13 +-- > io.open("/nix/abc.txt", "r") +-- nil /nix/abc.txt: No such file or directory 2 +-- > a = io.open("abc.txt", "w") +-- > a:read() +-- nil Bad file descriptor 9 function file.new(filename, mode) -- TODO: make this more accurate (disallow opening dirs as files) local success, stat, of, allowed @@ -25,21 +34,22 @@ function file.new(filename, mode) mode = "r" end debugf("opening", filename, mode) - of = setmetatable({_filename=filename, _mode=mode, _seek=0, _open=true, _readonly=true}, file) + of = setmetatable({_filename=filename, _mode=mode, _seek=0, _open=true, _readable=false, _writable=false, _append_only=false}, file) if string.find(mode, "w") then debugf("opening", filename, "for writing") -- preemptively allow writing instead of having that prompt at file:write allowed = fs.allow(filename) debugf("allowed:", allowed) - if not allowed then return nil end + if not allowed then return nil, filename..": Permission denied", 13 end of._stat = {} of._size = 0 - of._readonly = false + of._readable = false + of._writable = true elseif string.find(mode, "r+") then debugf("opening", filename, "for updating") allowed = fs.allow(filename) debugf("allowed:", allowed) - if not allowed then return nil end + if not allowed then return nil, filename..": Permission denied", 13 end success, stat = pcall(fs.stat, filename) debugf("stat success:", success) if success then @@ -52,14 +62,38 @@ function file.new(filename, mode) of._size = 0 end elseif string.find(mode, "a") then - error("append mode is not yet functional") + debugf("opening", filename, "for appending") + allowed = fs.allow(filename) + debugf("allowed:", allowed) + if not allowed then return nil, filename..": Permission denied", 13 end + of._append_only = true + of._writable = true + success, stat = pcall(fs.stat, filename) + debugf("stat success:", success) + if success then + debugf("type:", stat.type) + if stat.type == "dir" then return nil end + of._stat = stat + of._size = stat.size + else + of._stat = {} + of._size = 0 + end + if string.find(mode, "+") then + debugf("append update mode") + of._readable = true + else + debugf("append only mode") + of._readable = false + of._seek = of._size + end else debugf("opening", filename, "for reading") -- check if file exists first - success, stat = fs.stat(filename) + success, stat = pcall(fs.stat, filename) debugf("stat success:", success) -- lua returns nil if it fails to open for some reason - if not success then return nil end + if not success then return nil, filename..": No such file or directory", 2 end debugf("type:", stat.type) if stat.type == "dir" then return nil end of._stat = stat @@ -74,7 +108,14 @@ function file:_closed_check() if not self._open then error("attempt to use a closed file") end end +function file:_bad_desc() + debugf("returning bad desc on file", self._filename) + return nil, "Bad file descriptor", 9 +end + function file:close() + -- yes, this check and error happens even on a closed file + self:_closed_check() self._open = false return true end @@ -86,6 +127,7 @@ end function file:read(...) self:_closed_check() + if not self._readable then return file:_bad_desc() end local to_return = {} for i, v in ipairs({...}) do if v == "n" or v == "l" or v == "L" then @@ -130,12 +172,16 @@ function file:seek(whence, offset) end function file:write(...) + if not self._writable then return file:_bad_desc() end local to_write = '' for i, v in pairs({...}) do to_write = to_write..tostring(v) end local len = string.len(to_write) debugf("attempting to write "..tostring(len).." bytes to "..self._filename) + if self._append_only then + self:seek("end") + end local br = fs.write_file(self._filename, self._seek, to_write) debugf("wrote "..tostring(br).." bytes to "..self._filename) self._seek = self._seek + br diff --git a/data/luascripts/io-append-test.lua b/data/luascripts/io-append-test.lua new file mode 100644 index 000000000..d8dcdd508 --- /dev/null +++ b/data/luascripts/io-append-test.lua @@ -0,0 +1,14 @@ +local f = io.open("9:/test.txt", "w") +f:write("test") +f:close() + +local f2 = io.open("9:/test.txt", "a") +f2:write("in") +f2:close() + +local f3 = io.open("9:/test.txt", "a+") +ui.echo("Reading 3 bytes: "..f3:read(3)) +f3:write("g") +f3:close() + +ui.echo("Done, check 9:/test.txt") From d4dbd9fa1f39de209b46990eaf89c8e7a153c662 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sat, 7 Dec 2024 00:29:29 -0600 Subject: [PATCH 086/124] make sure io.open with write mode starts with an empty file, add os.remove and os.rename aliases --- arm9/source/lua/gm9os.c | 1 + data/luapackages/fs.lua | 4 ++++ data/luapackages/io.lua | 5 ++++- data/luascripts/io-append-test.lua | 4 ++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9os.c b/arm9/source/lua/gm9os.c index a98b42e20..64cb5a80b 100644 --- a/arm9/source/lua/gm9os.c +++ b/arm9/source/lua/gm9os.c @@ -669,6 +669,7 @@ static int os_difftime(lua_State *L) { return 1; } +// os.remove and os.rename are done in data/luapackages/fs.lua static const luaL_Reg os[] = { {"clock", os_clock}, {"time", os_time}, diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua index 4d3c3dd94..f86c2713a 100644 --- a/data/luapackages/fs.lua +++ b/data/luapackages/fs.lua @@ -32,6 +32,10 @@ fs.fix_cmacs = _fs.fix_cmacs fs.key_dump = _fs.key_dump fs.cart_dump = _fs.cart_dump +-- compatibility +os.remove = fs.remove +os.rename = fs.move + function fs.write_file(path, offset, data) local success, filestat if type(offset) == "string" then diff --git a/data/luapackages/io.lua b/data/luapackages/io.lua index a6441ce2d..2f8922dcc 100644 --- a/data/luapackages/io.lua +++ b/data/luapackages/io.lua @@ -41,6 +41,9 @@ function file.new(filename, mode) allowed = fs.allow(filename) debugf("allowed:", allowed) if not allowed then return nil, filename..": Permission denied", 13 end + -- write mode truncates the file to 0 normally + success = pcall(fs.truncate, filename, 0) + if not success then return nil, filename..": Cannot truncate virtual files", 2001 end of._stat = {} of._size = 0 of._readable = false @@ -155,7 +158,7 @@ function file:seek(whence, offset) offset = 0 end if type(offset) ~= "number" then - error("bad argument #2 to 'seek' (number expected, got '..type(offset)..')") + error("bad argument #2 to 'seek' (number expected, got "..type(offset)..")") end if whence == "set" then diff --git a/data/luascripts/io-append-test.lua b/data/luascripts/io-append-test.lua index d8dcdd508..c2d962545 100644 --- a/data/luascripts/io-append-test.lua +++ b/data/luascripts/io-append-test.lua @@ -11,4 +11,8 @@ ui.echo("Reading 3 bytes: "..f3:read(3)) f3:write("g") f3:close() +local f4 = io.open("9:/test2.txt", "a") +f4:write("TESTING") +f4:close() + ui.echo("Done, check 9:/test.txt") From b40af5ac4b570b8d41adcfa8513a9e338f5cd6b3 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 8 Dec 2024 22:33:10 -0600 Subject: [PATCH 087/124] properly implement os.remove compatibility --- data/luapackages/fs.lua | 44 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua index f86c2713a..d3f9604e5 100644 --- a/data/luapackages/fs.lua +++ b/data/luapackages/fs.lua @@ -33,8 +33,48 @@ fs.key_dump = _fs.key_dump fs.cart_dump = _fs.cart_dump -- compatibility -os.remove = fs.remove -os.rename = fs.move +function os.remove(path) + local success, allowed, stat, error + success, stat = pcall(fs.stat, path) + if not success then + return nil, path..": No such file or directory", 2 + end + if stat.type == "dir" then + -- os.remove can remove an empty directory, so we gotta check + success, dir_list = pcall(fs.list_dir, path) + if not success then + return nil, "Error occurred listing directory: "..dir_list, 2001 + end + if #dir_list ~= 0 then + return nil, path..": Directory not empty", 39 + end + end + allowed = fs.allow(path) + if not allowed then + return nil, path..": Operation not permitted", 1 + end + success, error = pcall(fs.remove, path, {recursive=true}) + if success then + return true + else + return nil, "Error occurred removing item: "..error, 2001 + end +end + +-- compatibility +function os.rename(src, dst) + error("os.rename is not implemented yet (try fs.move instead)") + --local success, drv_src, drv_dst, allowed + --drv_src = string.upper(string.sub(src, 1, 1)) + --drv_dst = string.upper(string.sub(dst, 1, 1)) + --if drv_src ~= drv_dst then + -- return nil, "Invalid cross-device link", 18 + --end + --allowed = fs.allow(src) and fs.allow(dst) + --if not allowed then + -- return nil, "Permission denied", 13 + --end +end function fs.write_file(path, offset, data) local success, filestat From 3b57ffd66447c202d383987a7d1c94b1be24f62e Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 8 Dec 2024 23:28:57 -0600 Subject: [PATCH 088/124] add fs.verify_with_sha_file, fix PathIsDirectory by using stat instead of opendir --- arm9/source/lua/gm9internalfs.c | 12 +++++------- data/luapackages/fs.lua | 14 ++++++++++++++ data/luascripts/fs-hash-and-remove-test.lua | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 data/luascripts/fs-hash-and-remove-test.lua diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 565da9daa..0bdecb1bf 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -14,13 +14,11 @@ static u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; static u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; static bool PathIsDirectory(const char* path) { - DIR fdir; - if (fvx_opendir(&fdir, path) == FR_OK) { - fvx_closedir(&fdir); - return true; - } else { - return false; - } + FRESULT res; + FILINFO fno; + res = fvx_stat(path, &fno); + if (res != FR_OK) return false; + return fno.fattrib & AM_DIR; } static void CreateStatTable(lua_State* L, FILINFO* fno) { diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua index d3f9604e5..9004b05a7 100644 --- a/data/luapackages/fs.lua +++ b/data/luapackages/fs.lua @@ -97,4 +97,18 @@ function fs.write_file(path, offset, data) return _fs.write_file(path, offset, data) end +function fs.verify_with_sha_file(path) + local success, file_hash, sha_data, path_sha + path_sha = path..".sha" + -- this error should be propagated if the main file cannot be read + stat = fs.stat(path) + success, sha_data = pcall(fs.read_file, path_sha, 0, 0x20) + if not success then + return nil + end + -- TODO: make hash_file accept an "end" parameter for size + file_hash = fs.hash_file(path, 0, stat.size) + return file_hash == sha_data +end + return fs diff --git a/data/luascripts/fs-hash-and-remove-test.lua b/data/luascripts/fs-hash-and-remove-test.lua new file mode 100644 index 000000000..4ffd0a861 --- /dev/null +++ b/data/luascripts/fs-hash-and-remove-test.lua @@ -0,0 +1,15 @@ +local function printtable(tbl) + for k, v in pairs(tbl) do + print(k, "=", v) + end +end +printtable(fs.stat("S:/firm0.bin")) +ui.echo("Waiting") +print("Copying") +fs.copy("S:/firm0.bin", "9:/firm0.bin", {calc_sha=true}) +print("Hashing") +print("Result:", fs.verify_with_sha_file("9:/firm0.bin")) +print("Removing") +print(os.remove("9:/firm0.bin")) +print(os.remove("9:/firm0.bin")) +ui.echo("Done") From b0d0d0b9e925863f0e20238433140ffa08b8fdae Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Tue, 10 Dec 2024 21:52:04 -0600 Subject: [PATCH 089/124] add util.running_as_module (untested) --- data/luapackages/util.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/data/luapackages/util.lua b/data/luapackages/util.lua index a2af65afd..c1f7b5748 100644 --- a/data/luapackages/util.lua +++ b/data/luapackages/util.lua @@ -29,4 +29,22 @@ function util.get_timestamp() return os.date("%H%M%S") end +-- https://stackoverflow.com/a/49376823 +function util.running_as_module() + local success, _, __, required = pcall(debug.getlocal, 4, 1) + if not success then + -- umm uhh + return false + end + -- for the file being executed directly, this seems to be a number + -- but for a file being required by another, it returns the string given to require() + -- example: if foo.lua is executed, this should return a number like 2 + -- but when foo.lua requires bar.lua, bar.lua should get "bar" as the result + -- however, this behavior seems inconsistent between lua versions + -- the Stack Overflow answer seems to suggest the call would fail on some version before 5.4 + -- honestly, I would've liked a better method, but i have yet to find some way to get the executing file + -- in the C code in LoadLuaFile, I could set the string that contains the full filepath, but... where do I set it? + return type(required) == "string" +end + return util From 3a915cdfa29d4c1934f83b8dcfaf9e924ecbb9eb Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 11 Dec 2024 11:59:49 -0600 Subject: [PATCH 090/124] move scripts over to https://github.com/ihaveamac/GM9-lua-script-experiments --- data/luascripts/allow-test.lua | 8 - data/luascripts/asking-test.lua | 13 - data/luascripts/bkpt-test.lua | 2 - data/luascripts/boot.lua | 4 - data/luascripts/build-cia-test.lua | 3 - data/luascripts/cartdump-test.lua | 5 - data/luascripts/check-key-test.lua | 3 - data/luascripts/ctrcheck.lua | 285 -------------------- data/luascripts/datatest.lua | 1 - data/luascripts/dec-enc-test.lua | 9 - data/luascripts/exists-test.lua | 5 - data/luascripts/extract-code-test.lua | 5 - data/luascripts/fill-test.lua | 5 - data/luascripts/find-test.lua | 5 - data/luascripts/format-bytes-test.lua | 3 - data/luascripts/fs-hash-and-remove-test.lua | 15 -- data/luascripts/fs-ops-test.lua | 15 -- data/luascripts/fs-write-end-test.lua | 8 - data/luascripts/fstest.lua | 18 -- data/luascripts/global-test.lua | 2 - data/luascripts/hash-test.lua | 33 --- data/luascripts/hashdata-test.lua | 10 - data/luascripts/install-test.lua | 3 - data/luascripts/io-append-test.lua | 18 -- data/luascripts/io-test.lua | 13 - data/luascripts/key-dump-test.lua | 5 - data/luascripts/mount-test.lua | 9 - data/luascripts/preview-test.lua | 5 - data/luascripts/read-write-test.lua | 7 - data/luascripts/sd-tests.lua | 5 - data/luascripts/selection.lua | 17 -- data/luascripts/selector-test.lua | 21 -- data/luascripts/stat-test.lua | 17 -- data/luascripts/testing.lua | 11 - data/luascripts/truncate-test.lua | 6 - data/luascripts/verify-test.lua | 7 - data/luascripts/viewer-test.lua | 10 - 37 files changed, 611 deletions(-) delete mode 100644 data/luascripts/allow-test.lua delete mode 100644 data/luascripts/asking-test.lua delete mode 100644 data/luascripts/bkpt-test.lua delete mode 100644 data/luascripts/boot.lua delete mode 100644 data/luascripts/build-cia-test.lua delete mode 100644 data/luascripts/cartdump-test.lua delete mode 100644 data/luascripts/check-key-test.lua delete mode 100644 data/luascripts/ctrcheck.lua delete mode 100644 data/luascripts/datatest.lua delete mode 100644 data/luascripts/dec-enc-test.lua delete mode 100644 data/luascripts/exists-test.lua delete mode 100644 data/luascripts/extract-code-test.lua delete mode 100644 data/luascripts/fill-test.lua delete mode 100644 data/luascripts/find-test.lua delete mode 100644 data/luascripts/format-bytes-test.lua delete mode 100644 data/luascripts/fs-hash-and-remove-test.lua delete mode 100644 data/luascripts/fs-ops-test.lua delete mode 100644 data/luascripts/fs-write-end-test.lua delete mode 100644 data/luascripts/fstest.lua delete mode 100644 data/luascripts/global-test.lua delete mode 100644 data/luascripts/hash-test.lua delete mode 100644 data/luascripts/hashdata-test.lua delete mode 100644 data/luascripts/install-test.lua delete mode 100644 data/luascripts/io-append-test.lua delete mode 100644 data/luascripts/io-test.lua delete mode 100644 data/luascripts/key-dump-test.lua delete mode 100644 data/luascripts/mount-test.lua delete mode 100644 data/luascripts/preview-test.lua delete mode 100644 data/luascripts/read-write-test.lua delete mode 100644 data/luascripts/sd-tests.lua delete mode 100644 data/luascripts/selection.lua delete mode 100644 data/luascripts/selector-test.lua delete mode 100644 data/luascripts/stat-test.lua delete mode 100644 data/luascripts/testing.lua delete mode 100644 data/luascripts/truncate-test.lua delete mode 100644 data/luascripts/verify-test.lua delete mode 100644 data/luascripts/viewer-test.lua diff --git a/data/luascripts/allow-test.lua b/data/luascripts/allow-test.lua deleted file mode 100644 index 7da3e80b5..000000000 --- a/data/luascripts/allow-test.lua +++ /dev/null @@ -1,8 +0,0 @@ -print("allow (no table):") -print(fs.allow("1:/")) -print("allow (ask_all=false):") -print(fs.allow("1:/", {ask_all=false})) -print("allow (ask_all=true):") -print(fs.allow("1:/", {ask_all=true})) - -ui.echo("Done") diff --git a/data/luascripts/asking-test.lua b/data/luascripts/asking-test.lua deleted file mode 100644 index f0d1a7172..000000000 --- a/data/luascripts/asking-test.lua +++ /dev/null @@ -1,13 +0,0 @@ -local num1 = ui.ask_hex("Gimmie a hex number.", 621, 8) -local num2 = ui.ask_number("Gimmie a normal number.", 123) -local text = ui.ask_text("Enter something!", "initial prompt", 20) - -print("First number", num1) -print("Second number", num2) -print("And the text:", text) - -ui.echo("Cool!!!!") -ui.show_png("0:/ihaveahax.png") -ui.echo("PNG time") ---ui.show_text("testing") ---ui.echo("Text time") diff --git a/data/luascripts/bkpt-test.lua b/data/luascripts/bkpt-test.lua deleted file mode 100644 index 26abf1ab0..000000000 --- a/data/luascripts/bkpt-test.lua +++ /dev/null @@ -1,2 +0,0 @@ -ui.echo("I am going to breakpoint!") -bkpt() diff --git a/data/luascripts/boot.lua b/data/luascripts/boot.lua deleted file mode 100644 index 67efb2335..000000000 --- a/data/luascripts/boot.lua +++ /dev/null @@ -1,4 +0,0 @@ -local lumafirm = "0:/luma.firm" - -ui.echo("I'm gonna boot "..lumafirm.."!!!") -sys.boot(lumafirm) diff --git a/data/luascripts/build-cia-test.lua b/data/luascripts/build-cia-test.lua deleted file mode 100644 index 62d9187f3..000000000 --- a/data/luascripts/build-cia-test.lua +++ /dev/null @@ -1,3 +0,0 @@ -ui.echo("Testing building cia") -title.build_cia("A:/title/00040000/0f800100/content/00000000.tmd") -ui.echo("Done") diff --git a/data/luascripts/cartdump-test.lua b/data/luascripts/cartdump-test.lua deleted file mode 100644 index 8d929bdf7..000000000 --- a/data/luascripts/cartdump-test.lua +++ /dev/null @@ -1,5 +0,0 @@ -print("Doing dec") -fs.cart_dump("9:/test-dec.bin", 0x06000000) -print("Doing enc") -fs.cart_dump("9:/test-enc.bin", 0x06000000, {encrypted=true}) -ui.echo("Done") diff --git a/data/luascripts/check-key-test.lua b/data/luascripts/check-key-test.lua deleted file mode 100644 index db21f3b53..000000000 --- a/data/luascripts/check-key-test.lua +++ /dev/null @@ -1,3 +0,0 @@ -ui.echo("Checking for X in a second...") -print("Is Key X pressed:", ui.check_key("X")) -ui.echo("Done") diff --git a/data/luascripts/ctrcheck.lua b/data/luascripts/ctrcheck.lua deleted file mode 100644 index 374c5a831..000000000 --- a/data/luascripts/ctrcheck.lua +++ /dev/null @@ -1,285 +0,0 @@ -local version = "5" -local store_log = false - -local NAND = "S:/nand.bin" - -local status = { - ["NAND Header"] = false, - ["NAND Sectors"] = false, - ["CTRNAND"] = false, - ["TWLN"] = false, - ["TWLP"] = false, - ["FIRM Partitions"] = false -} - -local function show_status(current_process) - local function get_text(requested_process) - if status[requested_process] then - return "\n"..requested_process..": DONE" - elseif current_process == requested_process then - return "\n"..requested_process..": ~~~" - else - return "\n"..requested_process..": ---" - end - end - - ui.show_text( - "ctrcheck v"..version.. - "\nCurrently processing: "..current_process..". Progress:\n ".. - get_text("NAND Header").. - get_text("NAND Sectors").. - get_text("CTRNAND").. - get_text("TWLN").. - get_text("TWLP").. - get_text("FIRM Partitions").. - "\n\ntest" - ) -end - -local function make_selection_text() - return "Select which parts of the system to check.\n".. - "Console is a "..sys.region.." "..CONSOLE_TYPE.." "..(IS_DEVKIT and "devkit" or "retail")..".\n".. - "Current id0 is "..(sys.sys_id0 or "unknown").."\n".. - "Permanent logging: "..(store_log and "enabled" or "disabled") -end - -local options_short = {"all", "nand", "sd", "togglelog", "exit"} -local options = { - "All", - "NAND Only", - "SD Only", - "Toggle permanent log", - "Exit" -} - -local function chk_nand_header() - show_status("NAND Header") - local hdr_hash, part_table_hash, header_magic, success, hdr, stat - local log = {} - local is_sighax = false - - -- check whether NAND header signature is sighax, based on hashes - -- sighax will always be read as valid by boot9 even without cfw, so it's just used to check whether custom partitions should be possible - success, stat = pcall(fs.stat, "S:/nand_hdr.bin") - if not success then - table.insert(log, "Critical: Could not stat S:/nand_hdr.bin!") - goto fail - end - - if stat.size ~= 512 then - table.insert(log, "Error: NAND header is an invalid size (expected 512, got "..tostring(stat.size)..")") - return - end - - success, hdr = pcall(fs.read_file, "S:/nand_hdr.bin", 0, 0x200) - if not success then - table.insert(log, "Critical: Could not read S:/nand_hdr.bin!") - goto fail - end - - hdr_hash = util.bytes_to_hex(fs.hash_data(string.sub(hdr, 1, 0x100))) - - if hdr_hash == "a4ae99b93412e4643e4686987b6cfd59701d5c655ca2ff671ce680b4ddcf0948" then - table.insert(log, "Information: NAND header's signature is sighax.") - is_sighax = true - end - - -- hash-based check of NAND header partition table against retail partition tables - part_table_hash = util.bytes_to_hex(fs.hash_data(string.sub(hdr, 0x101, 0x160))) - - if part_table_hash == "dfd434b883874d8b585a102f3cf3ae4cef06767801db515fdf694a7e7cd98bc2" then - table.insert(log, (CONSOLE_TYPE == "N3DS") and "Information: NAND header is stock. (n3DS)" or "Critical: o3DS has an n3DS nand header.") - elseif part_table_hash == "ae9b6645105f3aec22c2e3ee247715ab302874fca283343c731ca43ea1baa25d" then - table.insert(log, (CONSOLE_TYPE == "03DS") and "Information: NAND header is stock. (o3DS)" or "Critical: n3DS has an o3DS nand header.") - else - -- check for the NCSD magic header, if it's not present then there's definitely something wrong - header_magic = string.sub(hdr, 0x101, 0x104) - if header_magic == "NCSD" then - if is_sighax then - table.insert(log, "Warning: NAND partition table is modified, but there is sighax in the NAND header.") - else - table.insert(log, "Error: NAND partition table is modified, and there is no sighax in the NAND header.") - end - else - -- your NAND has a minor case of serious brain damage - table.insert(log, "Error: NAND header data is invalid. You've met with a terrible fate, haven't you?") - end - end - - ::fail:: - - status["NAND Header"] = true - - return table.concat(log, "\n") -end - -local function chk_nand_sectors() - show_status("NAND Sectors") - local secret_sector, success, secret_sector_hash, sbyte, twlmbr, twlmbr_hash, stage2_byte - local log = {} - - if CONSOLE_TYPE == "N3DS" then - success, stat = pcall(fs.stat, "S:/sector0x96.bin") - if not success then - table.insert(log, "Critical: Could not stat S:/sector0x96.bin!") - goto checktwl - end - - success, secret_sector = pcall(fs.read_file, "S:/sector0x96.bin", 0, 0x200) - if not success then - table.insert(log, "Critical: Could not read S:/sector0x96.bin!") - goto checktwl - end - - secret_sector_hash = util.bytes_to_hex(fs.hash_data(secret_sector)) - if secret_sector_hash ~= "82f2730d2c2da3f30165f987fdccac5cbab24b4e5f65c981cd7be6f438e6d9d3" then - table.insert(log, "Warning: Secret Sector data is invalid. a9lh might be installed.") - end - else - success, sbyte = pcall(fs_read_file, NAND, 0x12C00, 2) - if not success then - table.insert(log, "Critical: Could not read S:/nand.bin at 0x12C00!") - goto checktwl - end - if string.len(sbyte) ~= 4 then - table.insert(log, "Critical: NAND is unreadable at offset 0x12C00...?") - elseif sbyte ~= "0000" and sbyte ~= "ffff" then - table.insert(log, "Warning: There may be a9lh leftovers in the secret sector.") - end - end - - ::checktwl:: - - -- verify the TWL MBR exists and is retail (there is no good reason this should ever be modified) - success, stat = pcall(fs.stat, "S:/twlmbr.bin") - if not success then - table.insert(log, "Critical: Could not stat S:/twlmbr.bin!") - goto checkstage2 - end - - success, twlmbr = pcall(fs.read_file, "S:/twlmbr.bin", 0, 66) - if not success then - table.insert(log, "Critical: Could not read S:/twlmbr.bin!") - goto checkstage2 - end - - twlmbr_hash = util.bytes_to_hex(fs.hash_data(twlmbr)) - if twlmbr_hash ~= "77a98e31f1ff7ec4ef2bfacca5a114a49a70dcf8f1dcd23e7a486973cfd06617" then - table.insert(log, "Critical: TWL MBR data is invalid.") - end - - ::checkstage2:: - - -- instead of checking the full sector against multiple stage2s, this just checks if the sector is "clean" - -- (if first byte is not "clean" assume stage2 is there, can be done in a better way) - -- if stage2 was replaced with trash it would trigger this warning tho - success, stage2_byte = pcall(fs.read_file, "S:/nand.bin", 0xB800000, 1) - if not success then - table.insert(log, "Critical: Could not read S:/nand.bin at 0xB800000!") - goto checkbonus - end - - stage2_byte = util.bytes_to_hex(stage2_byte) - - if stage2_byte ~= "00" and stage2_byte ~= "FF" then - table.insert(log, "Warning: There are likely leftovers from a9lh's stage2 payload.") - end - - ::checkbonus:: - - -- check for presence of bonus drive, just because it might come in handy to know sometimes - if fs.is_dir("8:/") then - table.insert(log, "Information: Bonus drive is enabled.") - end - - ::fail:: - - status["NAND Sectors"] = true - - return table.concat(log, "\n") -end - -local function chk_ctrnand() - local log = {} - - table.insert(log, "CTRNAND checks not yet implemented.") - - return table.concat(log, "\n") -end - -local function chk_twln() - local log = {} - - table.insert(log, "TWLN checks not yet implemented.") - - return table.concat(log, "\n") -end - -local function chk_twlp() - local log = {} - - table.insert(log, "TWLP checks not yet implemented.") - - return table.concat(log, "\n") -end - -local function nand_checks() - local nand_log = {} - - table.insert(nand_log, chk_nand_header()) - table.insert(nand_log, chk_nand_sectors()) - table.insert(nand_log, chk_ctrnand()) - table.insert(nand_log, chk_twln()) - table.insert(nand_log, chk_twlp()) - - return table.concat(nand_log, "\n\n") -end - -local function sd_checks() - sd_log = {} - - table.insert(sd_log, "SD checks not implemented.") - - return table.concat(sd_log, "\n\n") -end - -local function main() - local final_log = "" - while true do - ui.show_text("ctrcheck v"..version) - - local selection = ui.ask_selection(make_selection_text(), options) - if not selection then return end - - local seltext = options_short[selection] - if seltext == "exit" then - return - elseif seltext == "togglelog" then - store_log = not store_log - elseif seltext == "all" then - final_log = final_log..nand_checks().."\n\n" - final_log = final_log..sd_checks() - elseif seltext == "nand" then - final_log = nand_checks() - elseif seltext == "sd" then - final_log = sd_checks() - end - if final_log ~= "" then - final_log = "Date and Time: "..os.date().."\n---\n"..final_log - ui.show_text_viewer(final_log) - - if store_log then - -- append mode is not implemented in io yet - local f = io.open(GM9OUT.."/ctrcheck_log.txt", "r+") - f:seek("end") - f:write(final_log, "\n") - f:close() - ui.echo("I think I wrote to "..GM9OUT.."/ctrcheck_log.txt") - end - - final_log = "" - end - end -end - -main() diff --git a/data/luascripts/datatest.lua b/data/luascripts/datatest.lua deleted file mode 100644 index 8b74c16b7..000000000 --- a/data/luascripts/datatest.lua +++ /dev/null @@ -1 +0,0 @@ -ui.echo("SUCCESS") diff --git a/data/luascripts/dec-enc-test.lua b/data/luascripts/dec-enc-test.lua deleted file mode 100644 index 6da201b38..000000000 --- a/data/luascripts/dec-enc-test.lua +++ /dev/null @@ -1,9 +0,0 @@ -print("Copying") -fs.copy("0:/FBI.cia", "9:/FBI-enc.cia") -print("Encrypting") -title.encrypt("9:/FBI-enc.cia") -print("Copying") -fs.copy("9:/FBI-enc.cia", "9:/FBI-dec.cia") -print("Decrypting") -title.decrypt("9:/FBI-dec.cia") -ui.echo("Done") diff --git a/data/luascripts/exists-test.lua b/data/luascripts/exists-test.lua deleted file mode 100644 index 16b5df5fd..000000000 --- a/data/luascripts/exists-test.lua +++ /dev/null @@ -1,5 +0,0 @@ -for i, v in pairs({"0:/boot.firm", "S:/nope.bin"}) do - print(v, "exists", fs.exists(v)) - print(v, "is_dir", fs.is_dir(v)) - print(v, "is_file", fs.is_file(v)) -end diff --git a/data/luascripts/extract-code-test.lua b/data/luascripts/extract-code-test.lua deleted file mode 100644 index df3da5472..000000000 --- a/data/luascripts/extract-code-test.lua +++ /dev/null @@ -1,5 +0,0 @@ -ui.echo("Testing extract code") -title.extract_code("A:/title/00040000/0f800100/content/7926fc67.app", "9:/code.bin") -ui.echo("Testing compress code") -title.compress_code("9:/code.bin", "9:/code-blz.bin") -ui.echo("Done") diff --git a/data/luascripts/fill-test.lua b/data/luascripts/fill-test.lua deleted file mode 100644 index 18deaf467..000000000 --- a/data/luascripts/fill-test.lua +++ /dev/null @@ -1,5 +0,0 @@ -print("Doing fill test...") -fs.fill_file("9:/test.txt", 0, 16, string.byte("A")) -print("Doing second fill test...") -fs.fill_file("9:/test.txt", 4, 8, string.byte("B")) -ui.echo("Done") diff --git a/data/luascripts/find-test.lua b/data/luascripts/find-test.lua deleted file mode 100644 index 0a4650763..000000000 --- a/data/luascripts/find-test.lua +++ /dev/null @@ -1,5 +0,0 @@ -print("find:", fs.find("1:/rw/sys/SecureInfo_*")) -print("find first:", fs.find("1:/rw/sys/SecureInfo_*", {first=true})) -print("find not:", fs.find_not(GM9OUT.."/"..util.get_datestamp().."_"..sys.serial.."_essential_??.exefs")) -pcall("find bad path:", pcall(fs.find, "S:/blah*")) -ui.echo("Done") diff --git a/data/luascripts/format-bytes-test.lua b/data/luascripts/format-bytes-test.lua deleted file mode 100644 index 89cf4232b..000000000 --- a/data/luascripts/format-bytes-test.lua +++ /dev/null @@ -1,3 +0,0 @@ -local sd_stat = fs.stat_fs("0:/") -print("Total size:", ui.format_bytes(sd_stat.total)) -print("Free space:", ui.format_bytes(sd_stat.free)) diff --git a/data/luascripts/fs-hash-and-remove-test.lua b/data/luascripts/fs-hash-and-remove-test.lua deleted file mode 100644 index 4ffd0a861..000000000 --- a/data/luascripts/fs-hash-and-remove-test.lua +++ /dev/null @@ -1,15 +0,0 @@ -local function printtable(tbl) - for k, v in pairs(tbl) do - print(k, "=", v) - end -end -printtable(fs.stat("S:/firm0.bin")) -ui.echo("Waiting") -print("Copying") -fs.copy("S:/firm0.bin", "9:/firm0.bin", {calc_sha=true}) -print("Hashing") -print("Result:", fs.verify_with_sha_file("9:/firm0.bin")) -print("Removing") -print(os.remove("9:/firm0.bin")) -print(os.remove("9:/firm0.bin")) -ui.echo("Done") diff --git a/data/luascripts/fs-ops-test.lua b/data/luascripts/fs-ops-test.lua deleted file mode 100644 index 848e6e528..000000000 --- a/data/luascripts/fs-ops-test.lua +++ /dev/null @@ -1,15 +0,0 @@ -print("fs.mkdir") -fs.mkdir("9:/test") -print("fs.copy") -fs.copy("1:/private", "9:/test", {recursive=true, overwrite=true}) -print("fs.move") -fs.move("9:/test", "9:/test2") -print("fs.remove") -fs.remove("9:/test2", {recursive=true}) - -print("this next one should fail") -fs.mkdir("9:/test1") -fs.mkdir("9:/test2") -fs.move("9:/test2", "9:/test1") - -ui.echo("Done?") diff --git a/data/luascripts/fs-write-end-test.lua b/data/luascripts/fs-write-end-test.lua deleted file mode 100644 index 1574cf2cb..000000000 --- a/data/luascripts/fs-write-end-test.lua +++ /dev/null @@ -1,8 +0,0 @@ -print("Testing") --- create file normally -fs.write_file("9:/test1.txt", 0, "test") --- append to end of it -fs.write_file("9:/test1.txt", "end", "ing") --- try to append, but should create if it doesn't exist -fs.write_file("9:/test2.txt", "end", "stuff") -ui.echo("Done") diff --git a/data/luascripts/fstest.lua b/data/luascripts/fstest.lua deleted file mode 100644 index 2e748cbb5..000000000 --- a/data/luascripts/fstest.lua +++ /dev/null @@ -1,18 +0,0 @@ -function printtable(t) - for ik, iv in pairs(t) do - print("", ik, ":", iv) - end -end - -print("Listing V:/") -local vfiles = fs.list_dir("V:/") -print("I have gotten the V:/:", vfiles) -for k, v in pairs(vfiles) do - print("File", k) - printtable(v) -end -ui.echo("Look up there!") - -local bootstat = fs.stat("0:/boot.firm") -print("For that boot firm:") -printtable(bootstat) diff --git a/data/luascripts/global-test.lua b/data/luascripts/global-test.lua deleted file mode 100644 index 3b763830a..000000000 --- a/data/luascripts/global-test.lua +++ /dev/null @@ -1,2 +0,0 @@ -print("Does it exist?") -ui.echo("Cool") diff --git a/data/luascripts/hash-test.lua b/data/luascripts/hash-test.lua deleted file mode 100644 index edd9dcf0f..000000000 --- a/data/luascripts/hash-test.lua +++ /dev/null @@ -1,33 +0,0 @@ -local hash = fs.hash_file("0:/boot.firm", 0, 0x200) -local hash1 = fs.hash_file("0:/boot.firm", 0, 0x200, {sha1=true}) - --- https://stackoverflow.com/questions/29419345/convert-string-to-hex-in-lua -local function format_hex(str) - local data = '' - for i = 1, #str do - char = string.sub(str, i, i) - data = data..string.format("%02X", string.byte(char)).." " - end - return data -end - -print("Hash 256: "..format_hex(hash)) -print("Hash 1: "..format_hex(hash1)) - -local fullfile256 = fs.hash_file("0:/boot.firm", 0, 0) -local fullfile1 = fs.hash_file("0:/boot.firm", 0, 0, {sha1=true}) - -print("Hash 256 full file: "..format_hex(fullfile256)) -print("Hash 1 full file: "..format_hex(fullfile1)) - -if ui.ask("Create dummy?") then - fs.write_file("9:/dummy.bin", 0, "") -end - -local nodata256 = fs.hash_file("9:/dummy.bin", 0, 0) -local nodata1 = fs.hash_file("9:/dummy.bin", 0, 0, {sha1=true}) - -print("Hash 256 no data: "..format_hex(nodata256)) -print("Hash 1 no data: "..format_hex(nodata1)) - -ui.echo("Waiting") diff --git a/data/luascripts/hashdata-test.lua b/data/luascripts/hashdata-test.lua deleted file mode 100644 index e04a9ac3d..000000000 --- a/data/luascripts/hashdata-test.lua +++ /dev/null @@ -1,10 +0,0 @@ -local hash256 = fs.hash_data("a") -local hash1 = fs.hash_data("a", {sha1=true}) - -print("I hashed the letter a") -print("Hash 256: ", util.bytes_to_hex(hash256)) -print("Hash 1: ", util.bytes_to_hex(hash1)) -print() -print("Hex to bytes:", util.hex_to_bytes("676179")) - -ui.echo("Cool") diff --git a/data/luascripts/install-test.lua b/data/luascripts/install-test.lua deleted file mode 100644 index 13ff32413..000000000 --- a/data/luascripts/install-test.lua +++ /dev/null @@ -1,3 +0,0 @@ -ui.echo("Testing install!") -title.install("0:/FBI.cia") -ui.echo("Did it work?") diff --git a/data/luascripts/io-append-test.lua b/data/luascripts/io-append-test.lua deleted file mode 100644 index c2d962545..000000000 --- a/data/luascripts/io-append-test.lua +++ /dev/null @@ -1,18 +0,0 @@ -local f = io.open("9:/test.txt", "w") -f:write("test") -f:close() - -local f2 = io.open("9:/test.txt", "a") -f2:write("in") -f2:close() - -local f3 = io.open("9:/test.txt", "a+") -ui.echo("Reading 3 bytes: "..f3:read(3)) -f3:write("g") -f3:close() - -local f4 = io.open("9:/test2.txt", "a") -f4:write("TESTING") -f4:close() - -ui.echo("Done, check 9:/test.txt") diff --git a/data/luascripts/io-test.lua b/data/luascripts/io-test.lua deleted file mode 100644 index 2cb08e4fb..000000000 --- a/data/luascripts/io-test.lua +++ /dev/null @@ -1,13 +0,0 @@ -print("opening") -local f = io.open("9:/test.txt", "w") -print("i got:", f) -print("writing") -f:write("testing") -print("seeking") -f:seek("set", 0) -print("reading") -local data = f:read(4) -print("the data is:", data) -data = f:read(1) -print("the data is:", data) -ui.echo("Done?") diff --git a/data/luascripts/key-dump-test.lua b/data/luascripts/key-dump-test.lua deleted file mode 100644 index fda989565..000000000 --- a/data/luascripts/key-dump-test.lua +++ /dev/null @@ -1,5 +0,0 @@ -print("about to test key_dump") -fs.key_dump("seeddb.bin") -fs.key_dump("encTitleKeys.bin") -fs.key_dump("decTitleKeys.bin") -ui.echo("Done") diff --git a/data/luascripts/mount-test.lua b/data/luascripts/mount-test.lua deleted file mode 100644 index 0a138a462..000000000 --- a/data/luascripts/mount-test.lua +++ /dev/null @@ -1,9 +0,0 @@ -local mf = "0:/luma.firm" - -print("Mounted:", fs.get_img_mount()) - -print("Mounting:", mf) -fs.img_mount(mf) -print("Mounted:", fs.get_img_mount()) - -ui.echo("There") diff --git a/data/luascripts/preview-test.lua b/data/luascripts/preview-test.lua deleted file mode 100644 index e02b2ba6a..000000000 --- a/data/luascripts/preview-test.lua +++ /dev/null @@ -1,5 +0,0 @@ -ui.echo("Test...") -ui.show_game_info("0:/FBI.cia") -ui.echo("Did show_game_info work?") -ui.show_qr("QR code time", "https://ihaveahax.net") -ui.echo("done") diff --git a/data/luascripts/read-write-test.lua b/data/luascripts/read-write-test.lua deleted file mode 100644 index 62efb1d6c..000000000 --- a/data/luascripts/read-write-test.lua +++ /dev/null @@ -1,7 +0,0 @@ -local stuff = fs.read_file("0:/boot.firm", 0, 4) -print("I got:", stuff) - -local written = fs.write_file("9:/test.txt", 0, "Yeah!!!") -print("I wrote:", written) - -ui.echo("Waiting...") diff --git a/data/luascripts/sd-tests.lua b/data/luascripts/sd-tests.lua deleted file mode 100644 index 414cc3572..000000000 --- a/data/luascripts/sd-tests.lua +++ /dev/null @@ -1,5 +0,0 @@ -print("sd mounted:", fs.sd_is_mounted()) - -fs.sd_switch() - -ui.echo("Done!") diff --git a/data/luascripts/selection.lua b/data/luascripts/selection.lua deleted file mode 100644 index 04bd21aee..000000000 --- a/data/luascripts/selection.lua +++ /dev/null @@ -1,17 +0,0 @@ -local options = { - "0:/boot.firm", - "0:/luma.firm", - "1:/boot.firm", - "1:/luma.firm" -} - -local sel = ui.ask_selection("Choose one to boot.", options) -if sel then - local path = options[sel] - local doboot = ui.ask("Boot "..path.."?") - if doboot then - sys.boot(path) - else - ui.echo("I won't boot it then!") - end -end diff --git a/data/luascripts/selector-test.lua b/data/luascripts/selector-test.lua deleted file mode 100644 index 5c62c042d..000000000 --- a/data/luascripts/selector-test.lua +++ /dev/null @@ -1,21 +0,0 @@ -for i, v in pairs({"9:/test_1", "9:/test_2", "9:/extradir_3"}) do - print("Making dir", v) - fs.mkdir(v) -end - -for i, v in pairs({"9:/test_1.bin", "9:/test_2.bin", "9:/extratest_3.bin", "9:/test_2/thingy.txt"}) do - print("Touching file", v) - fs.write_file(v, 0, "a") -end - -print() -print("Selected:", fs.ask_select_file("Select a file!", "9:/test_*")) -print("Selected:", fs.ask_select_file("Select a file! (explorer)", "9:/test_*", {explorer=true})) - -print("Selected:", fs.ask_select_dir("Select a directory!", "9:/")) -print("Selected:", fs.ask_select_dir("Select a directory! (explorer)", "9:/", {explorer=true})) - -print("Selected:", fs.ask_select_file("Select a file OR directory!", "9:/test_*", {include_dirs=true})) -print("Selected:", fs.ask_select_file("Select a file OR directory! (explorer)", "9:/test_*", {include_dirs=true, explorer=true})) -print("Done") -ui.echo("Done") diff --git a/data/luascripts/stat-test.lua b/data/luascripts/stat-test.lua deleted file mode 100644 index fc502556c..000000000 --- a/data/luascripts/stat-test.lua +++ /dev/null @@ -1,17 +0,0 @@ -local function printtable(tbl) - for k, v in pairs(tbl) do - print("-", k, ":", v, "(", ui.format_bytes(v), ")") - end -end - -print("fs.stat_fs 0:/") -printtable(fs.stat_fs("0:/")) -print("fs.stat_fs 1:/") -printtable(fs.stat_fs("1:/")) - -print("fs.dir_info 0:/") -printtable(fs.dir_info("0:/")) -print("fs.dir_info 1:/") -printtable(fs.dir_info("1:/")) - -ui.echo("done") diff --git a/data/luascripts/testing.lua b/data/luascripts/testing.lua deleted file mode 100644 index f80fdb395..000000000 --- a/data/luascripts/testing.lua +++ /dev/null @@ -1,11 +0,0 @@ -ui.echo("a") -print("a") -print("GM9VERSION: "..GM9VERSION) -print("SCRIPT: "..SCRIPT) -ui.echo("b") - -local thingy = sys.testtable() -ui.echo(tostring(thingy)) -for k, v in pairs(thingy) do - ui.echo(tostring(k)..": "..tostring(v)) -end diff --git a/data/luascripts/truncate-test.lua b/data/luascripts/truncate-test.lua deleted file mode 100644 index 6a7eaf5e4..000000000 --- a/data/luascripts/truncate-test.lua +++ /dev/null @@ -1,6 +0,0 @@ -print("Writing trunctest") -fs.write_file("9:/trunctest.bin", 0, "testing") -print("Truncating trunctest") -fs.truncate("9:/trunctest.bin", 4) -print("Truncating vram (this should fail)") -fs.truncate("M:/vram.mem", 6291456) diff --git a/data/luascripts/verify-test.lua b/data/luascripts/verify-test.lua deleted file mode 100644 index c50fe58dc..000000000 --- a/data/luascripts/verify-test.lua +++ /dev/null @@ -1,7 +0,0 @@ -print("fs.verify nand:", fs.verify("S:/nand.bin")) -print("fs.verify title:", fs.verify("1:/title/00040010/00021000/content/00000051.app")) -print("fixing cmacs") -fs.fix_cmacs("1:/") -print("done") - -ui.echo("Done") diff --git a/data/luascripts/viewer-test.lua b/data/luascripts/viewer-test.lua deleted file mode 100644 index 9ac930ae8..000000000 --- a/data/luascripts/viewer-test.lua +++ /dev/null @@ -1,10 +0,0 @@ -ui.echo("Testing...") - -ui.show_text("Text") -ui.echo("Text stuff!") - -ui.show_text_viewer("Text viewer\nis here") -ui.echo("Text viewer!") - -ui.show_file_text_viewer("2:/sys/log/inspect.log") -ui.echo("File text viewer!") From b36c04496fdee853b41ca6c3a1cf2a276eeb4733 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 11 Dec 2024 12:06:51 -0600 Subject: [PATCH 091/124] remove ods and dockermake.sh --- command comparison table.ods | Bin 26372 -> 0 bytes dockermake.sh | 5 ----- 2 files changed, 5 deletions(-) delete mode 100644 command comparison table.ods delete mode 100755 dockermake.sh diff --git a/command comparison table.ods b/command comparison table.ods deleted file mode 100644 index 317affd7209f518cdb0f969d47546b3c64f29cfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26372 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 diff --git a/dockermake.sh b/dockermake.sh deleted file mode 100755 index e1899b4c7..000000000 --- a/dockermake.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -IMAGE=ianburgwin/firmbuilder - -set -eux -docker run -it --rm -v $PWD:/host $IMAGE make $@ From 606ea6a425ebe47f0c43f8f337aa3307dfec9a0c Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 11 Dec 2024 12:09:12 -0600 Subject: [PATCH 092/124] remove data/scripts --- data/scripts/bkpt-test.gm9 | 3 - data/scripts/cartdump-test.gm9 | 3 - data/scripts/ctrcheck.gm9 | 791 --------------------------------- data/scripts/sdump-test.gm9 | 4 - 4 files changed, 801 deletions(-) delete mode 100644 data/scripts/bkpt-test.gm9 delete mode 100644 data/scripts/cartdump-test.gm9 delete mode 100644 data/scripts/ctrcheck.gm9 delete mode 100644 data/scripts/sdump-test.gm9 diff --git a/data/scripts/bkpt-test.gm9 b/data/scripts/bkpt-test.gm9 deleted file mode 100644 index c2d58c395..000000000 --- a/data/scripts/bkpt-test.gm9 +++ /dev/null @@ -1,3 +0,0 @@ -echo "Testing..." -bkpt -echo "Post-bkpt" diff --git a/data/scripts/cartdump-test.gm9 b/data/scripts/cartdump-test.gm9 deleted file mode 100644 index c2dfa1d92..000000000 --- a/data/scripts/cartdump-test.gm9 +++ /dev/null @@ -1,3 +0,0 @@ -cartdump 9:/test-dec.bin 06000000 -cartdump 9:/test-enc.bin 06000000 -e -echo done diff --git a/data/scripts/ctrcheck.gm9 b/data/scripts/ctrcheck.gm9 deleted file mode 100644 index 1c63122cc..000000000 --- a/data/scripts/ctrcheck.gm9 +++ /dev/null @@ -1,791 +0,0 @@ -# A script for checking all of a 3DS console's important files and their integrity. -# original script by FrozenFire -# overhaul, fixes, and current maintenance by StarlitSkies -# last modified: 2024-09-30 -# if you didn't get this file from https://github.com/nh-server/scripts, go there and make sure this copy wasn't tampered with - -@cleanup -set VERSION "4" -set FULL "0" -set PREVIEW_MODE "ctrcheck v$[VERSION]" - -set NANDSECTORS_LOG "" -set ESSENTIALS_LOG "" -set CTRNAND_LOG "" -set TWLNAND_LOG "" -set FIRM_LOG "" -set SD_LOG "" -set MISC_LOG "" -set LOG "disabled" - -@menu -labelsel -o -s "Select which parts of the system to check.\nConsole is a $[REGION] $[RDTYPE] $[ONTYPE] using $[HAX].\nCurrent id0 is $[SYSID0].\n\nPermanent logging: $[LOG]" check_* -goto menu - -@check_All -set FULL "1" -goto NAND_Header - -@check_NAND_Only -goto NAND_Header - -@check_SD_Only -goto SD_Files - -@NAND_Header -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: ~~~\nNAND Sectors: ---\nCTRNAND: ---\nTWLNAND: ---\nTWLP: ---\nFIRM Partitions: ---" - -# check whether NAND header signature is sighax, based on hashes -# sighax will always be read as valid by boot9 even without cfw, so it's just used to check whether custom partitions should be possible -set SIGHAX "0" -if find S:/nand_hdr.bin NULL - if not shaget S:/nand_hdr.bin@0:200 NULL - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND header is an invalid size.\n" - goto NAND_Sectors - end -else - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND header not found.\n" - goto NAND_Sectors -end - -if sha S:/nand_hdr.bin@0:100 A4AE99B93412E4643E4686987B6CFD59701D5C655CA2FF671CE680B4DDCF0948 - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: NAND header's signature is sighax.\n" - set SIGHAX "1" -end - -# hash-based check of NAND header partition table against retail partition tables -if sha S:/nand_hdr.bin@100:60 dfd434b883874d8b585a102f3cf3ae4cef06767801db515fdf694a7e7cd98bc2 - if chk $[ONTYPE] "N3DS" - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: NAND header is stock. (n3DS)\n" - else - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: o3DS has an n3DS NAND header.\n" - end -elif sha S:/nand_hdr.bin@100:60 ae9b6645105f3aec22c2e3ee247715ab302874fca283343c731ca43ea1baa25d - if chk $[ONTYPE] "O3DS" - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: NAND header is stock. (o3DS)\n" - else - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: n3DS has an o3DS NAND header.\n" - end -else - fget S:/nand_hdr.bin@100:4 NCSD - #check for the NCSD magic header, if it's not present then there's definitely something wrong - if chk $[NCSD] "4E435344" - if chk $[SIGHAX] "1" - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: NAND partition table is modified, but there is sighax in the NAND header.\n" - else - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND partition table is modified, and there is no sighax in the NAND header.\n" - end - else - # your NAND has a minor case of serious brain damage - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Error: NAND header data is invalid. You've met with a terrible fate, haven't you?\n" - end -end - -@NAND_Sectors -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: ~~~\nCTRNAND: ---\nTWLNAND: ---\nTWLP: ---\nTWLP: ---\nFIRM Partitions: ---" - -# verify Secret Sector, which is doubly important for N3DSes -if chk $[ONTYPE] "N3DS" - if not sha S:/sector0x96.bin 82F2730D2C2DA3F30165F987FDCCAC5CBAB24B4E5F65C981CD7BE6F438E6D9D3 - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: Secret Sector data is invalid. a9lh might be installed.\n" - end -else - if fget S:/nand.bin@12C00:2 SBYTE - if chk -u $[SBYTE] "0000" - if chk -u $[SBYTE] "FFFF" - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: There may be a9lh leftovers in the secret sector.\n" - end - end - else - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: NAND is unreadable at offset 0x12C00...?\n" - end -end - -# verify the TWL MBR exists and is retail (there is no good reason this should ever be modified) -if find S:/twlmbr.bin NULL - if not sha S:/twlmbr.bin 77a98e31f1ff7ec4ef2bfacca5a114a49a70dcf8f1dcd23e7a486973cfd06617 - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: TWL MBR data is invalid.\n" - end -else - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: TWL MBR not found.\n" -end - -# get first byte in stage2 location -if not fget S:/nand.bin@B800000:1 ABYTE - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Critical: NAND is unreadable at offset 0xB800000...?\n" -end -# instead of checking the full sector against multiple stage2s, this just checks if the sector is "clean" -# (if first byte is not "clean" assume stage2 is there, can be done in a better way) -# if stage2 was replaced with trash it would trigger this warning tho -if chk -u $[ABYTE] "00" - if chk -u $[ABYTE] "FF" - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Warning: There are likely leftovers from a9lh's stage2 payload.\n" - end -end - -# check for presence of bonus drive, just because it might come in handy to know sometimes -if isdir 8: - set NANDSECTORS_LOG "$[NANDSECTORS_LOG]Information: Bonus drive is enabled.\n" -end - - -@CTRNAND -# check if CTRNAND can be accessed, skip all CTRNAND checks if it isn't -if not isdir 1: - set CTRNAND_LOG "$[CTRNAND_LOG]Error: CTRNAND not found.\n" - goto TWLNAND -end - -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: ~~~\nTWLNAND: ---\nTWLP: ---\nFIRM Partitions: ---" - -# find movable.sed, check if it's valid, and check whether CMAC is set correctly based on size (288 bytes vs 320 bytes) -set MOVABLEVALID "0" -set MLFCSHASH "" -if not find 1:/private/movable.sed movable - goto LFCS -end -if shaget $[movable]@0:100 NULL - fget $[movable]@8:10 ISVALID - if chk $[ISVALID] "00000000000000000000000000000000" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Critical: movable.sed's copy of LFCS is blank.\n" - end -else - goto LFCS -end -fget $[movable]@0:4 HEADER -if chk -u $[HEADER] "53454544" - goto LFCS -end -set MOVABLEVALID "1" -fget $[movable]@5:1 CMAC -shaget $[movable]@8:110 MLFCSHASH -#check if movable.sed at least has the SEED magic header, then check whether CMAC is set correctly based on size (320 vs 288 bytes) -if chk $[CMAC] "01" - if not fget $[movable]@120:1 NULL - if ask "movable.sed is misconfigured.\nPress (A) to reconfigure it to normal values." - fset $[movable]@5 00 - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: movable.sed has been fixed by removing the CMAC flag.\n" - else - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Critical: movable.sed is 288 bytes but has the CMAC flag.\n" - end - end -end - -@LFCS -# find LFCS, check if it's valid, check if it's the same as movable.sed's copy of LFCS, and check against OTP seed that's used to make part of LFCS -# account for both potential filenames of LocalFriendCodeSeed -set LFCSVALID "0" -if find 1:/rw/sys/LocalFriendCodeSeed_B LFCS -elif find 1:/rw/sys/LocalFriendCodeSeed_A LFCS -else - goto SecureInfo -end -if shaget $[LFCS]@0:100 NULL - fget $[LFCS]@0:10 ISVALID - if chk $[ISVALID] "00000000000000000000000000000000" - goto SecureInfo - end -else - goto SecureInfo -end -set LFCSVALID "1" -shaget $[LFCS]@0:110 LFCSHASH -# compare the LFCS in movable.sed to the raw LFCS file, if movable.sed exists -if chk -u $[MLFCSHASH] "" - if chk -u $[MLFCSHASH] $[LFCSHASH] - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: LFCS doesn't match movable.sed.\n" - end -end -if find M:/otp_dec.mem OTP - fget $[LFCS]@108:8 LFCSEED - fget $[OTP]@8:8 OTPSEED - if chk -u $[LFCSEED] $[OTPSEED] - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Warning: Console is using a donor LFCS.\n" - end -end - -@SecureInfo -# find secureinfo_(a/b) and check if it's valid -set SECVALID "0" -if find 1:/rw/sys/SecureInfo_A SEC -elif find 1:/rw/sys/SecureInfo_B SEC -else - goto HWCAL0 -end -if not find -s 1:/rw/sys/SecureInfo_C ALTSEC - set ALTSEC "" -end -if not shaget $[SEC]@0:110 NULL - goto HWCAL0 -end -# the checks here are done against the end of the file since that part contains the serial; the RSA signature isn't important for troubleshooting purposes -fget $[SEC]@100:10 ISVALID -if chk $[ISVALID] "00000000000000000000000000000000" - goto HWCAL0 -else - set SECVALID "1" - fget $[SEC]@10C:1 SECSERIALCHECK - if chk $[SECSERIALCHECK] "00" - fget $[SEC]@102:0A SECSERIAL - else - fget $[SEC]@102:0B SECSERIAL - end -end - -# then check if it matches the console region, check for region change (presence of secureinfo_c), and use that for both data comparison and to see what region was changed to -if chk -u $[ALTSEC] "" - set REGCHG "1" - fget $[ALTSEC]@100:1 ALTREGSEC - if chk $[ALTREGSEC] "00" - set ALTREG "JPN" - elif chk $[ALTREGSEC] "01" - set ALTREG "USA" - elif chk $[ALTREGSEC] "02" - set ALTREG "EUR" - elif chk $[ALTREGSEC] "04" - set ALTREG "CHN" - elif chk $[ALTREGSEC] "05" - set ALTREG "KOR" - elif chk $[ALTREGSEC] "06" - set ALTREG "TWN" - end -else - set REGCHG "0" -end -fget $[SEC]@100:1 REGSEC -if chk $[REGSEC] "00" - set REG "JPN" -elif chk $[REGSEC] "01" - set REG "USA" -elif chk $[REGSEC] "02" - set REG "EUR" -elif chk $[REGSEC] "04" - set REG "CHN" -elif chk $[REGSEC] "05" - set REG "KOR" -elif chk $[REGSEC] "06" - set REG "TWN" -end -if chk -u $[REG] $[REGION] - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Warning: SecureInfo doesn't match the console's region.\n" -end -if chk $[REGCHG] "1" - if chk -u $[ALTREG] $[REGION] - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: Console's region is changed from $[REG] to $[ALTREG].\n" - else - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Information: SecureInfo_C exists, but console's region is unchanged.\n" - end -end - -@HWCAL0 -# check whether first HWCAL exists -set HWCAL0VALID "0" -if find 1:/ro/sys/HWCAL0.dat HWCAL0 - if not shaget $[HWCAL0]@0:900 NULL - goto HWCAL1 - end -else - goto HWCAL1 -end -# if it's valid, check if it has the CCAL magic header -fget $[HWCAL0]@0:4 HEADER -if chk $[HEADER] "4343414C" - set HWCAL0VALID "1" -end - -@HWCAL1 -# check whether second HWCAL exists -set HWCAL1VALID "0" -if find 1:/ro/sys/HWCAL1.dat HWCAL1 - if not shaget $[HWCAL1]@0:900 NULL - goto Misc_CTRNAND - end -else - goto Misc_CTRNAND -end -# if it's valid, check if it has the CCAL magic header -fget $[HWCAL1]@0:4 HEADER -if chk $[HEADER] "4343414C" - set HWCAL1VALID "1" -end - -@Misc_CTRNAND -# check whether system can boot without SD card -if find 1:/boot.firm NULL - if find 1:/rw/luma/payloads/GodMode9.firm NULL - set CTRNAND_LOG "$[CTRNAND_LOG]Information: GodMode9 and Luma3DS are in the NAND.\n" - else - set CTRNAND_LOG "$[CTRNAND_LOG]Information: Luma3DS is in the NAND, but GodMode9 isn't.\n" - end -else - set CTRNAND_LOG "$[CTRNAND_LOG]Warning: Luma3DS is not in the NAND. (This console cannot boot without an SD card.)\n" -end - -# check whether nand title database exists -if find 1:/dbs/title.db NANDTITLEDB - if shaget $[NANDTITLEDB]@0:400 NULL - fget $[NANDTITLEDB]@100:4 HEADER - # check whether nand title.db has the DIFF magic header - if chk -u $[HEADER] "44494646" - set CTRNAND_LOG "$[CTRNAND_LOG]Critical: CTRNAND title.db data is invalid.\n" - end - else - set CTRNAND_LOG "$[CTRNAND_LOG]Critical: CTRNAND title.db is an invalid size.\n" - end -else - set CTRNAND_LOG "$[CTRNAND_LOG]Critical: CTRNAND title.db not found.\n" -end - -set RECOVERYMODE "0" -if chk $[MOVABLEVALID] "0" - set RECOVERYMODE "1" -end -if chk $[LFCSVALID] "0" - set RECOVERYMODE "1" -end -if chk $[SECVALID] "0" - set RECOVERYMODE "1" -end -if chk $[HWCAL0VALID] "0" - set RECOVERYMODE "1" -end -if chk $[HWCAL1VALID] "0" - set RECOVERYMODE "1" -end - -if chk $[RECOVERYMODE] "1" - goto CTRNAND_Recovery -else - goto TWLNAND -end - -@CTRNAND_Recovery -# if the recovery flag exists, then they've already tried this. if any part of it failed before, it'll fail again now - no point in redoing it -if find 9:/RECOVERYFLAG NULL - if chk $[MOVABLEVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: movable.sed is still invalid after a recovery attempt." - end - if chk $[LFCSVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: LFCS is still invalid after a recovery attempt." - end - if chk $[SECVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: SecureInfo is still invalid after a recovery attempt." - end - if chk $[HWCAL0VALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: HWCAL0 is still invalid after a recovery attempt." - end - if chk $[HWCAL1VALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Bruh Moment: HWCAL1 is still invalid after a recovery attempt." - end - goto TWLNAND -end -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: !!!\nTWLNAND: ---\nTWLP: ---\nFIRM Partitions: ---" -set TRYRECOVERY "0" -if find S:/essential.exefs NULL - if ask "Critical files in the CTRNAND are invalid,\nbut a backup was found.\nPress (A) to enter data recovery mode.\n \n(If you have already tried this recently,\nyou may safely skip this part.)" - allow -a 1: - set TRYRECOVERY "1" - else - if chk $[MOVABLEVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: movable.sed is invalid, and data recovery was denied." - end - if chk $[LFCSVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: LFCS is invalid, and data recovery was denied." - end - if chk $[SECVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: SecuureInfo is invalid, and data recovery was denied." - end - if chk $[HWCAL0VALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL0 is invalid, and data recovery was denied." - end - if chk $[HWCAL1VALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL1 is invalid, and data recovery was denied." - end - end -else - if chk $[MOVABLEVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: movable.sed is invalid, and essential.exefs does not exist." - end - if chk $[LFCSVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: LFCS is invalid, and essential.exefs does not exist." - end - if chk $[SECVALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: SecuureInfo is invalid, and essential.exefs does not exist." - end - if chk $[HWCAL0VALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL0 is invalid, and essential.exefs does not exist." - end - if chk $[HWCAL1VALID] "0" - set ESSENTIALS_LOG "$[ESSENTIALS_LOG]Error: HWCAL1 is invalid, and essential.exefs does not exist." - end -end - -if chk $[TRYRECOVERY] "0" - goto TWLNAND -end - -imgmount S:/essential.exefs - -# check backup movable.sed integrity, fix CMAC if needed, then copy it into NAND if it's valid -set VALIDBACKUP "1" -if shaget G:/movable@0:100 NULL - fget G:/movable@0:4 HEADER - fget G:/movable@5:1 CMAC - if chk -u $[HEADER] "53454544" - set VALIDBACKUP "0" - end - if chk $[CMAC] "01" - if not fget G:/movable@120:1 NULL - set CMACFIX "1" - else - set CMACFIX "0" - end - end -else - set VALIDBACKUP "0" -end -if chk $[VALIDBACKUP] "1" - if chk $[CMACFIX] "0" - cp -n -w G:/movable 1:/private/movable.sed - else - cp -n -w G:/movable 9:/movable - fset 9:/movable@5 00 - cp -n -w 9:/movable 1:/private/movable.sed - rm -o -s 9:/movable - end -end - -# check backup LFCS integrity, then copy it into NAND if it's valid -set VALIDBACKUP "1" -if shaget G:/frndseed@0:100 NULL - fget G:/frndseed@0:10 ISVALID - if chk $[ISVALID] "00000000000000000000000000000000" - set VALIDBACKUP "0" - end -else - set VALIDBACKUP "0" -end -if chk $[VALIDBACKUP] "1" - cp -n -w G:/frndseed 1:/rw/sys/LocalFriendCodeSeed_B -end - -# check backup SecureInfo integrity, then copy it into NAND if it's valid -set VALIDBACKUP "1" -if shaget G:/secinfo@0:100 NULL - fget G:/secinfo@0:10 ISVALID - if chk $[ISVALID] "00000000000000000000000000000000" - set VALIDBACKUP "0" - end -else - set VALIDBACKUP "0" -end -if chk $[VALIDBACKUP] "1" - cp -n -w G:/secinfo 1:/rw/sys/SecureInfo_A -end - -# check backup HWCAL0 integrity, then copy it into NAND if it's valid -if shaget G:/hwcal0@0:900 NULL - fget G:/hwcal0@0:4 HEADER - if chk -u $[HEADER] "4343414C" - set VALIDBACKUP "0" - end -else - set VALIDBACKUP "0" -end -if chk $[VALIDBACKUP] "1" - cp -n -w G:/hwcal0 1:/ro/sys/HWCAL0.dat -end - -# check backup HWCAL1 integrity, then copy it into NAND if it's valid -if shaget G:/hwcal1@0:900 NULL - fget G:/hwcal1@0:4 HEADER - if chk -u $[HEADER] "4343414C" - set VALIDBACKUP "0" - end -else - set VALIDBACKUP "0" -end -if chk $[VALIDBACKUP] "1" - cp -n -w G:/hwcal1 1:/ro/sys/HWCAL1.dat -end - -# the flag is made in ramdrive since people should do the requested rerun immediately, and i'm shoving it down their throat to do that as hard as i can. if they ignore it and reboot anyway, first off what the hell -fdummy 9:/RECOVERYFLAG 400 -echo "Recovery attempt finished.\nctrcheck will now restart automatically.\n \Run another check to get the true results.\n" -imgumount -goto cleanup - -@TWLNAND -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: DONE\nTWLNAND: ~~~\nTWLP: ---\nFIRM Partitions: ---" -#check if TWLNAND can be accessed -if not isdir 2: - set TWLNAND_LOG "$[TWLNAND_LOG]Error: TWLNAND not found.\n" - goto TWLP -end -# the console won't boot if shared2 doesn't exist, but remaking it is easy and harmless -if not isdir 2:/shared2 - if ask "A folder required to boot is missing. Press (A) to fix this issue." - mkdir 2:/shared2 - set TWLNAND_LOG "$[TWLNAND_LOG]Information: shared2 has been recreated.\n" - else - set TWLNAND_LOG "$[TWLNAND_LOG]Critical: shared2 not found, and was not recreated when asked.\n" - end -end -# check for inspect.log, and if it exists, compare its copy of the serial number against SecureInfo -if isdir 2:/sys/log - if find 2:/sys/log/inspect.log INSPECTLOG - if chk $[SECVALID] "1" - fget $[INSPECTLOG]@77:1 TWLNSERIALCHECK - if chk $[TWLNSERIALCHECK] "0a" - fget $[INSPECTLOG]@6D:0A TWLNSERIAL - else - fget $[INSPECTLOG]@6D:0B TWLNSERIAL - end - if chk -u $[SECSERIAL] $[TWLNSERIAL] - set TWLNAND_LOG "$[TWLNAND_LOG]Warning: inspect.log serial does not match SecureInfo.\n" - end - end - else - set TWLNAND_LOG "$[TWLNAND_LOG]Warning: inspect.log not found.\n" - end -else - set TWLNAND_LOG "$[TWLNAND_LOG]Warning: 2:/sys/log folder not found, meaning inspect.log also not found.\n" -end - -@TWLP -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: DONE\nTWLNAND: DONE\nTWLP: ~~~\nFIRM Partitions: ---" -# you don't need TWLP to boot (or for a lot of things, really), but it's still good to check everything's in place -if not isdir 3: - set TWLNAND_LOG "$[TWLNAND_LOG]Warning: TWLP not found.\n" -end - -@FIRM_Data -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: NAND. Progress:\n \nNAND Header: DONE\nNAND Sectors: DONE\nCTRNAND: DONE\nTWLNAND: DONE\nTWLP: DONE\nFIRM Partitions: ~~~" -# basic checks for how many FIRM partitions exist and which ones they are -set FIRMEXIST "0" -set ALTFIRMEXIST "0" -set BONUSFIRMS "0" -set TOOMANYFIRMS "0" -if find S:/firm0.bin NULL - set FIRMEXIST "1" -end -if find S:/firm1.bin NULL - set ALTFIRMEXIST "1" -end -if find S:/firm2.bin NULL - set BONUSFIRMS "1" -end -if find S:/firm5.bin NULL - set TOOMANYFIRMS "1" -end -if chk $[FIRMEXIST] "0" - if chk $[ALTFIRMEXIST] "0" - set FIRM_LOG "$[FIRM_LOG]Error: FIRM0 and FIRM1 both not found. Unless you have access to ntrboot, do NOT power off the console until you've fixed this.\n" - goto NAND_End - else - set FIRM_LOG "$[FIRM_LOG]Bruh Moment: FIRM0 not found, but FIRM1 exists. This shouldn't be possible, the console's probably having a stroke.\n" - end -else - if chk $[ALTFIRMEXIST] "0" - set FIRM_LOG "$[FIRM_LOG]Critical: FIRM1 not found, but FIRM0 exists. The console isn't dead yet, but fix this ASAP.\n" - end -end -if chk $[BONUSFIRMS] "1" - if chk $[TOOMANYFIRMS] "1" - set FIRM_LOG "$[FIRM_LOG]Bruh Moment: ...Why are there more than 5 FIRM partitions? I hope you only sacrificed the AGBSAVE.\n" - else - set FIRM_LOG "$[FIRM_LOG]Information: Extra FIRM partitions detected. But hey, rules were meant to be broken, right?\n" - end -end - -# compare firm slots against the hashes of all payloads we could reasonably expect -# todo: add hashes for new b9s/fb3ds versions as they're released -for S: firm*.bin - strsplit FIRM $[FORPATH] "/" - strsplit -b FIRM $[FIRM] "." - # immediately start with b9s checks since they're the most common, it speeds up the script if they get hit first - if sha $[FORPATH]@0:3E00 72002296D1B8B1D4E462EA340124D836BA7F8D6BF16617F369ED90983C42BB98 - set FIRM_LOG "$[FIRM_LOG]Information: b9s v1.4 installed to $[FIRM].\n" - elif sha $[FORPATH]@0:7800 79C68585B4BA1D7C4A91B858861553E768C6576B92E303810B33219736B3598B - set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.3 installed to $[FIRM].\n" - elif sha $[FORPATH]@0:10C00 A765E44844BD5667CC1D7A9A89AD45EC674F8392367F4418CCB08152581D7B3A - set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.2 installed to $[FIRM].\n" - elif sha $[FORPATH]@0:10C00 D77BEE742E7E7D528BAA20E6ADA7AC822598DDCACDFC81B1F13E32C94F4EBC50 - set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.1 installed to $[FIRM].\n" - elif sha $[FORPATH]@0:20800 43978C226D3164047051B1B534D6589608F1FA04E0B1766E1FDBEB3BC41707B6 - set FIRM_LOG "$[FIRM_LOG]Warning: b9s v1.0 installed to $[FIRM].\n" - elif verify $[FORPATH] - # check for the sighax signatures that fastboot3DS uses - # sciresm is used when fb3DS is installed by outside sources, derrek is used if fb3DS updated itself - # and that means you'll basically never see derrek sig in practice. be wary if you do see it - if sha $[FORPATH]@100:100 078CC0CFD850A27093DDA2630C3603CA0C96969BD1F26DA48AC7B1BAE5DD5219 - set FIRMSIG "SciresM" - elif sha $[FORPATH]@100:100 ADB73ABC35708EF1DFE9EF9CA5FAC8BFC2DF916BB2E38101858482409F0D450A - set FIRMSIG "derrek" - else - set FIRMSIG "none" - end - # minfirm is a special case, since it's only used by b9stool and shouldn't stick around for long after cfw setup. best to get it out of the way before fb3DS - if sha $[FORPATH]@0:100 93EE0A3799072EFB368DAD3174D8DE2EC9735BC13AC78C087DA80 - set FIRM_LOG "$[FIRM_LOG]Critical: minfirm installed to $[FIRM].\n" - elif chk -u $[FIRMSIG] "none" - if sha $[FORPATH]@0:100 D36E802EEA55B92110438D0A3B09DFCEEEC71AEB7BF05073A2E0E857827F3903 - set FIRM_LOG "$[FIRM_LOG]Information: fb3DS v1.2 ($[FIRMSIG] sig) installed to $[FIRM].\n" - elif sha $[FORPATH]@0:100 9C8D28272421C78AC796EB9023A6D1373F31176CB693CE1B04B1B78112E25226 - set FIRM_LOG "$[FIRM_LOG]Warning: fb3DS v1.1 ($[FIRMSIG] sig) installed to $[FIRM].\n" - elif sha $[FORPATH]@0:100 5E58A159C057D0762E6BFC53FE5A5CDAECA338544B252B85524DFBBB1D546DCB - set FIRM_LOG "$[FIRM_LOG]Warning: fb3DS v1.1-beta ($[FIRMSIG] sig) installed to $[FIRM].\n" - elif sha $[FORPATH]@0:100 12EBA2DDB6B5203E66CBE82A963B56AECF540814F15F8539D0CE65DAE818BBB7 - set FIRM_LOG "$[FIRM_LOG]Warning: fb3DS v1.0 ($[FIRMSIG] sig) installed to $[FIRM].\n" - else - set FIRM_LOG "$[FIRM_LOG]Warning: Valid unknown firm with $[FIRMSIG] sig installed to $[FIRM].\n" - end - else - set FIRM_LOG "$[FIRM_LOG]Information: Valid stock/unknown firm installed to $[FIRM].\n" - end - else - set FIRM_LOG "$[FIRM_LOG]Error: Invalid firm installed to $[FIRM].\n" - end -next - -@NAND_End -if chk $[FULL] "1" - goto SD_Files -else - goto Misc_Files -end - - -@SD_Files -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: SD. Progress:\n \nSD Structure: ~~~" -# this script doesn't NEED an SD to run, necessarily, but you'd have to copy it to your NAND on purpose -if not isdir 0: - set SD_LOG "$[SD_LOG]Bruh Moment: No SD card is inserted. You went out of your way to find this, didn't you?\n" - goto SD_End -end -if isdir "0:/Nintendo 3DS" - if isdir A: - if find A:/dbs/title.db TITLEDB - if shaget $[TITLEDB]@0:400 NULL - fget $[TITLEDB]@100:4 HEADER - #check whether SD title.db has the DIFF magic header - if chk -u $[HEADER] "44494646" - if ask "The SD title.db is invalid.\nPress (A) to reset it.\n \n(This will delete all of your games and apps,\nbut they were likely also corrupted\nby whatever caused this.)" - rm $[TITLEDB] - fdummy $[TITLEDB] 400 - set SD_LOG "$[SD_LOG]Warning: The SD title.db needs to be reset. Reboot and go into System Settings -> Data Management -> Nintendo 3DS -> Software to do this.\n" - else - set SD_LOG "$[SD_LOG]Critical: SD title.db data is invalid.\n" - end - end - else - if ask "The SD title.db is invalid.\nPress (A) to reset it.\n \n(This will delete all of your games and apps,\nbut they were likely also corrupted\nby whatever caused this.)" - rm $[TITLEDB] - fdummy $[TITLEDB] 400 - set SD_LOG "$[SD_LOG]Warning: The SD title.db needs to be reset. Reboot and go into System Settings -> Data Management -> Nintendo 3DS -> Software to do this.\n" - else - set SD_LOG "$[SD_LOG]Critical: SD title.db is an invalid size.\n" - end - end - else - set TITLEDB A:/dbs/title.db - if ask "The SD title.db does not exist.\nPress (A) to create a blank one.\n \n(A title database is necessary\nto install games and apps.)" - if not isdir A:/dbs - mkdir A:/dbs - end - fdummy $[TITLEDB] 400 - set SD_LOG "$[SD_LOG]Warning: The SD title.db needs to be reset. Reboot and go into System Settings -> Data Management -> Nintendo 3DS -> Software to do this.\n" - else - set SD_LOG "$[SD_LOG]Critical: SD title.db not found.\n" - end - end - else - if isdir "0:/Nintendo 3DS/$[SYSID0]" - set SD_LOG "$[SD_LOG]Warning: Nintendo 3DS folder has valid data, but the data is inaccessible.\n" - else - set SD_LOG "$[SD_LOG]Information: Nintendo 3DS folder exists, but has no data.\n" - end - end - if not find 0:/boot.3dsx NULL - set SD_LOG "$[SD_LOG]Warning: There is no boot.3dsx in the SD card root.\n" - end - if not find 0:/boot.firm NULL - set SD_LOG "$[SD_LOG]Warning: There is no boot.firm in the SD card root.\n" - end -else - set SD_LOG "$[SD_LOG]Warning: Nintendo 3DS folder not found.\n" -end - -@Misc_Files -set PREVIEW_MODE "ctrcheck v$[VERSION]\nCurrently processing: Other. Progress:\nNVRAM: ~~~\OTP: ---\n" -# check whether the NVRAM SPI flash works, because it's bad news if it doesn't -if not shaget M:/nvram.mem@0:400 NULL - set MISC_LOG "$[MISC_LOG]Critical: NVRAM is inaccessible.\n" -end -# boot9strap decrypts the OTP and makes it available to GodMode9, most other bootloaders don't -if not find M:/otp_dec.mem NULL - set MISC_LOG "$[MISC_LOG]Warning: Decrypted OTP not found. (If you aren't using b9s, you can ignore this.)\n" -end - -@Results -dumptxt 0:/gm9/ctrcheck_latest.txt "Date and Time: $[DATESTAMP] $[TIMESTAMP]\n---" -if chk -u $[NANDSECTORS_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_latest.txt $[NANDSECTORS_LOG] -end -if chk -u $[ESSENTIALS_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_latest.txt $[ESSENTIALS_LOG] -end -if chk -u $[CTRNAND_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_latest.txt $[CTRNAND_LOG] -end -if chk -u $[TWLNAND_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_latest.txt $[TWLNAND_LOG] -end -if chk -u $[FIRM_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_latest.txt $[FIRM_LOG] -end -if chk -u $[SD_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_latest.txt $[SD_LOG] -end -if chk -u $[MISC_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_latest.txt $[MISC_LOG] -end -textview 0:/gm9/ctrcheck_latest.txt -if chk $[LOG] "activated" - dumptxt -p 0:/gm9/ctrcheck_log.txt "Date and Time: $[DATESTAMP] $[TIMESTAMP]\n---" - if chk -u $[NANDSECTORS_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_log.txt $[NANDSECTORS_LOG] - end - if chk -u $[ESSENTIALS_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_log.txt $[ESSENTIALS_LOG] - end - if chk -u $[CTRNAND_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_log.txt $[CTRNAND_LOG] - end - if chk -u $[TWLNAND_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_log.txt $[TWLNAND_LOG] - end - if chk -u $[FIRM_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_log.txt $[FIRM_LOG] - end - if chk -u $[SD_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_log.txt $[SD_LOG] - end - if chk -u $[MISC_LOG] "" - dumptxt -p 0:/gm9/ctrcheck_log.txt $[MISC_LOG] - end - echo "Check complete.\n \nThese results are also in 0:/gm9/ctrcheck_latest.txt,\nbut the file will be overwritten if you rerun this.\n \nHowever, they have also been appended to\nthe permanent log in 0:/gm9/ctrcheck_log.txt." -else - echo "Check complete.\n \nThese results are also in 0:/gm9/ctrcheck_latest.txt,\nbut the file will be overwritten if you rerun this." -end -goto cleanup - -@check_Toggle_permanent_log -if chk $[LOG] "disabled" - set LOG "activated" -else - set LOG "disabled" -end -goto menu - -@check_Exit diff --git a/data/scripts/sdump-test.gm9 b/data/scripts/sdump-test.gm9 deleted file mode 100644 index 7d3a2b069..000000000 --- a/data/scripts/sdump-test.gm9 +++ /dev/null @@ -1,4 +0,0 @@ -sdump encTitleKeys.bin -sdump decTitleKeys.bin -sdump seeddb.bin -echo done From b99a6bce36981b6b2041383d430eb98e3738b2e0 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 11 Dec 2024 12:46:44 -0600 Subject: [PATCH 093/124] add lua autorun (untested) --- arm9/source/godmode.c | 9 +++++++++ arm9/source/system/vram0.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index d840cd96a..5972e2720 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -3133,12 +3133,21 @@ u32 ScriptRunner(int entrypoint) { if (PathExist("V:/" VRAM0_AUTORUN_GM9)) { ClearScreenF(true, true, COLOR_STD_BG); // clear splash ExecuteGM9Script("V:/" VRAM0_AUTORUN_GM9); + if (PathExist("V:/" VRAM0_AUTORUN_LUA)) { + ClearScreenF(true, true, COLOR_STD_BG); // clear splash + ExecuteLuaScript("V:/" VRAM0_AUTORUN_LUA); } else if (PathExist("V:/" VRAM0_SCRIPTS)) { char loadpath[256]; char title[256]; snprintf(title, sizeof(title), STR_FLAVOR_SCRIPTS_MENU_SELECT_SCRIPT, FLAVOR); if (FileSelector(loadpath, title, "V:/" VRAM0_SCRIPTS, "*.gm9", HIDE_EXT, false)) ExecuteGM9Script(loadpath); + } else if (PathExist("V:/" VRAM0_LUASCRIPTS)) { + char loadpath[256]; + char title[256]; + snprintf(title, sizeof(title), STR_FLAVOR_SCRIPTS_MENU_SELECT_SCRIPT, FLAVOR); + if (FileSelector(loadpath, title, "V:/" VRAM0_LUASCRIPTS, "*.lua", HIDE_EXT, false)) + ExecuteLuaScript(loadpath); } else ShowPrompt(false, STR_COMPILED_AS_SCRIPT_AUTORUNNER_BUT_NO_SCRIPT_DERP); // deinit diff --git a/arm9/source/system/vram0.h b/arm9/source/system/vram0.h index 87b8986e2..3e4d916d1 100644 --- a/arm9/source/system/vram0.h +++ b/arm9/source/system/vram0.h @@ -11,8 +11,10 @@ // known file names inside VRAM0 TAR #define VRAM0_AUTORUN_GM9 "autorun.gm9" +#define VRAM0_AUTORUN_LUA "autorun.lua" #define VRAM0_FONT DEFAULT_FONT #define VRAM0_SCRIPTS "scripts" +#define VRAM0_LUASCRIPTS "luascripts" #define VRAM0_README_MD "README_internal.md" #define VRAM0_SPLASH_PNG FLAVOR "_splash.png" #define VRAM0_EASTER_BIN "easter.bin" From aa52cfc30d8f2544305e5d40edb7ee9ceca7a7f2 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Wed, 11 Dec 2024 12:52:20 -0600 Subject: [PATCH 094/124] fix syntax error --- arm9/source/godmode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 5972e2720..f7301b5b1 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -3133,7 +3133,7 @@ u32 ScriptRunner(int entrypoint) { if (PathExist("V:/" VRAM0_AUTORUN_GM9)) { ClearScreenF(true, true, COLOR_STD_BG); // clear splash ExecuteGM9Script("V:/" VRAM0_AUTORUN_GM9); - if (PathExist("V:/" VRAM0_AUTORUN_LUA)) { + } else if (PathExist("V:/" VRAM0_AUTORUN_LUA)) { ClearScreenF(true, true, COLOR_STD_BG); // clear splash ExecuteLuaScript("V:/" VRAM0_AUTORUN_LUA); } else if (PathExist("V:/" VRAM0_SCRIPTS)) { From fc0a438d626c470480ab5e5a23bdd240536e4d95 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Thu, 12 Dec 2024 00:57:55 -0600 Subject: [PATCH 095/124] add sys.check_embedded_backup --- arm9/source/lua/gm9internalsys.c | 36 ++++++++++++++++++++++++++++++++ data/luapackages/sys.lua | 1 + data/luascripts | 1 + 3 files changed, 38 insertions(+) create mode 120000 data/luascripts diff --git a/arm9/source/lua/gm9internalsys.c b/arm9/source/lua/gm9internalsys.c index 4d74a7220..10c97d633 100644 --- a/arm9/source/lua/gm9internalsys.c +++ b/arm9/source/lua/gm9internalsys.c @@ -7,6 +7,8 @@ #include "power.h" #include "sha.h" #include "nand.h" +#include "utils.h" +#include "ui.h" #define UNUSED(x) ((void)(x)) @@ -88,6 +90,39 @@ static int internalsys_get_emu_base(lua_State* L) { return 1; } +static int internalsys_check_embedded_backup(lua_State* L) { + CheckLuaArgCount(L, 0, "_sys.check_embedded_backup"); + + if (PathExist("S:/essential.exefs")) { + lua_pushboolean(L, true); + return 1; + } + + bool ncsd_check = CheckGenuineNandNcsd(); + if (!ncsd_check) { + lua_pushnil(L); + return 1; + } + + bool ret = false; + + if (ncsd_check && ShowPrompt(true, "%s", STR_ESSENTIAL_BACKUP_NOT_FOUND_CREATE_NOW)) { + if (EmbedEssentialBackup("S:/nand.bin") == 0) { + u32 flags = BUILD_PATH | SKIP_ALL; + PathCopy(OUTPUT_PATH, "S:/essential.exefs", &flags); + ShowPrompt(false, STR_BACKUP_EMBEDDED_WRITTEN_TO_OUT, OUTPUT_PATH); + ret = true; + } else { + ret = false; + } + } else { + ret = false; + } + + lua_pushboolean(L, ret); + return 1; +} + static int internalsys_global_bkpt(lua_State* L) { UNUSED(L); bkpt; @@ -101,6 +136,7 @@ static const luaL_Reg internalsys_lib[] = { {"get_id0", internalsys_get_id0}, {"next_emu", internalsys_next_emu}, {"get_emu_base", internalsys_get_emu_base}, + {"check_embedded_backup", internalsys_check_embedded_backup}, {NULL, NULL} }; diff --git a/data/luapackages/sys.lua b/data/luapackages/sys.lua index 4b7b857da..7d37599a7 100644 --- a/data/luapackages/sys.lua +++ b/data/luapackages/sys.lua @@ -3,6 +3,7 @@ local sys = {} sys.boot = _sys.boot sys.reboot = _sys.reboot sys.power_off = _sys.power_off +sys.check_embedded_backup = _sys.check_embedded_backup sys.secureinfo_letter = nil sys.region = nil diff --git a/data/luascripts b/data/luascripts new file mode 120000 index 000000000..571d843d5 --- /dev/null +++ b/data/luascripts @@ -0,0 +1 @@ +../../GM9-lua-script-experiments \ No newline at end of file From ff2cb9139c10eee6b52abffd47e1c523c975b9ad Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Thu, 12 Dec 2024 00:59:07 -0600 Subject: [PATCH 096/124] remove accidental symlink --- data/luascripts | 1 - 1 file changed, 1 deletion(-) delete mode 120000 data/luascripts diff --git a/data/luascripts b/data/luascripts deleted file mode 120000 index 571d843d5..000000000 --- a/data/luascripts +++ /dev/null @@ -1 +0,0 @@ -../../GM9-lua-script-experiments \ No newline at end of file From f3558446df7af0076389fb31741924c27f1a92d7 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Thu, 12 Dec 2024 17:33:37 -0600 Subject: [PATCH 097/124] add sys.check_raw_rtc --- arm9/source/godmode.c | 1 + arm9/source/lua/gm9internalsys.c | 26 ++++++++++++++++++++++++++ data/luapackages/sys.lua | 1 + 3 files changed, 28 insertions(+) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index f7301b5b1..c07a6bccb 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -2512,6 +2512,7 @@ u32 GodMode(int entrypoint) { } } + // note: this is kinda duplicated in arm9/source/lua/gm9internalsys.c as well // check internal clock if (IS_UNLOCKED) { // we could actually do this on any entrypoint DsTime dstime; diff --git a/arm9/source/lua/gm9internalsys.c b/arm9/source/lua/gm9internalsys.c index 10c97d633..b73d215e9 100644 --- a/arm9/source/lua/gm9internalsys.c +++ b/arm9/source/lua/gm9internalsys.c @@ -9,6 +9,8 @@ #include "nand.h" #include "utils.h" #include "ui.h" +#include "rtc.h" +#include "godmode.h" #define UNUSED(x) ((void)(x)) @@ -123,6 +125,29 @@ static int internalsys_check_embedded_backup(lua_State* L) { return 1; } +static int internalsys_check_raw_rtc(lua_State* L) { + CheckLuaArgCount(L, 0, "_sys.check_raw_rtc"); + + bool result = false; + + DsTime dstime; + get_dstime(&dstime); + if (DSTIMEGET(&dstime, bcd_Y) >= 18) { + result = true; + } else if (ShowPrompt(true, "%s", STR_RTC_DATE_TIME_SEEMS_TO_BE_WRONG_SET_NOW) && + ShowRtcSetterPrompt(&dstime, "%s", STR_TITLE_SET_RTC_DATE_TIME)) { + //char timestr[UTF_BUFFER_BYTESIZE(32)]; + set_dstime(&dstime); + // this is only in godmode.h + //GetTimeString(timestr, true, true); + // ShowPrompt(false, STR_NEW_RTC_DATE_TIME_IS_TIME, timestr); + result = true; + } + + lua_pushboolean(L, result); + return 1; +} + static int internalsys_global_bkpt(lua_State* L) { UNUSED(L); bkpt; @@ -137,6 +162,7 @@ static const luaL_Reg internalsys_lib[] = { {"next_emu", internalsys_next_emu}, {"get_emu_base", internalsys_get_emu_base}, {"check_embedded_backup", internalsys_check_embedded_backup}, + {"check_raw_rtc", internalsys_check_raw_rtc}, {NULL, NULL} }; diff --git a/data/luapackages/sys.lua b/data/luapackages/sys.lua index 7d37599a7..fe018eb6e 100644 --- a/data/luapackages/sys.lua +++ b/data/luapackages/sys.lua @@ -4,6 +4,7 @@ sys.boot = _sys.boot sys.reboot = _sys.reboot sys.power_off = _sys.power_off sys.check_embedded_backup = _sys.check_embedded_backup +sys.check_raw_rtc = _sys.check_raw_rtc sys.secureinfo_letter = nil sys.region = nil From 702261aff880e42e1fc5fb5505c1bacb4c50f5df Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 02:21:51 -0600 Subject: [PATCH 098/124] fix ui.show_file_text_viewer not freeing memory or reporting an error if OOM happens --- arm9/source/lua/gm9ui.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index cc1ca67ca..77c96e5b6 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -227,7 +227,9 @@ static int ui_show_file_text_viewer(lua_State* L) { // and FileTextViewer calls the above function char* text = malloc(STD_BUFFER_SIZE); - if (!text) return false; + if (!text) { + return luaL_error(L, "could not allocate memory"); + }; u32 flen = FileGetData(path, text, STD_BUFFER_SIZE - 1, 0); @@ -235,13 +237,17 @@ static int ui_show_file_text_viewer(lua_State* L) { u32 len = (ptrdiff_t)memchr(text, '\0', flen + 1) - (ptrdiff_t)text; if (!(ValidateText(text, len))) { + free(text); return luaL_error(L, "text validation failed"); } if (!(MemTextViewer(text, len, 1, false))) { + free(text); return luaL_error(L, "failed to run MemTextViewer"); } + free(text); + return 0; } From 256f5eb23117b412f00ce5dd16b81fd14109a4d3 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 02:39:53 -0600 Subject: [PATCH 099/124] add todo notes for ui --- arm9/source/lua/gm9ui.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 77c96e5b6..09df39529 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -231,7 +231,8 @@ static int ui_show_file_text_viewer(lua_State* L) { return luaL_error(L, "could not allocate memory"); }; - u32 flen = FileGetData(path, text, STD_BUFFER_SIZE - 1, 0); + // TODO: replace this with something that can detect file read errors and actual 0-length files + size_t flen = FileGetData(path, text, STD_BUFFER_SIZE - 1, 0); text[flen] = '\0'; u32 len = (ptrdiff_t)memchr(text, '\0', flen + 1) - (ptrdiff_t)text; @@ -314,6 +315,8 @@ static int ui_global_print(lua_State* L) { return 0; } +// TODO: use luaL_checkoption which will auto-raise an error +// use BUTTON_STRINGS from common/hid_map.h static int ui_check_key(lua_State* L) { CheckLuaArgCount(L, 1, "ui.check_key"); const char* key = luaL_checkstring(L, 1); From 830392cfc7c3cc88bffbc5203f1c715d00f58244 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 02:40:03 -0600 Subject: [PATCH 100/124] work-in-progress lua doc --- resources/lua-doc.md | 352 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 resources/lua-doc.md diff --git a/resources/lua-doc.md b/resources/lua-doc.md new file mode 100644 index 000000000..6ffd103c4 --- /dev/null +++ b/resources/lua-doc.md @@ -0,0 +1,352 @@ +# GodMode9 Lua documentation + +> [!IMPORTANT] +> WORK IN PROGRESS! A list of functions is available at the [Lua support pull request](https://github.com/d0k3/GodMode9/pulls). + +GodMode9 includes a Lua 5.4.7 implementation. + +## Running scripts + +There are four ways to run Lua scripts: + +* Select it in the file browser and choose "`Execute Lua script`" +* Place it in `0:/gm9/luascripts`, then open the HOME menu, choose "`Lua scripts...`", then choose the script +* Place it in `data/luascripts`, then build GodMode9 from source + * This takes precedence over `0:/gm9/luascripts` +* Place it at `data/autorun.lua`, then build GodMode9 from source with `SCRIPT_RUNNER=1` to automatically run it at launch + +## Packages + +Lua scripts can load custom modules. The default search path (defined as `package.path`) is: + +``` +0:/gm9/luapackages/?.lua;0:/gm9/luapackages/?/init.lua;V:/luapackages/?.lua;V:/luapackages/?/init.lua +``` + +For example, when a script calls `require("foobar")`, the package searcher will look for the module in this order: + +* `0:/gm9/luapackages/foobar.lua` +* `0:/gm9/luapackages/foobar/init.lua` +* `V:/luapackages/foobar.lua` +* `V:/luapackages/foobar/init.lua` + +## Comparison with GM9Script + +These tables are provided to assist with converting from GM9Script to Lua. + +### Commands + +GM9 | Lua | Notes +-- | -- | -- +goto | http://lua-users.org/wiki/GotoStatement |   +labelsel | ui.ask_selection |   +keychk | ui.check_key |   +echo | ui.echo |   +qr | ui.show_qr |   +ask | ui.ask |   +input | ui.ask_input |   +filesel | fs.ask_select_file |   +dirsel | fs.ask_select_dir |   +set | local var = value | More details on variables and scoping: https://www.lua.org/pil/4.2.html +strsplit | string.find and string.sub |   +strrep | string.gsub | https://www.lua.org/manual/5.4/manual.html#pdf-string.gsub +allow | fs.allow |   +cp | fs.copy |   +mv | fs.move |   +inject | fs.write_file |   +fill | fs.fill_file |   +fdummy | fs.make_dummy_file |   +rm | fs.remove |   +mkdir | fs.mkdir |   +mount | fs.img_mount |   +umount | fs.img_umount |   +find | fs.find |   +findnot | fs.find_not |   +fget | fs.write_file |   +fset | fs.write_file |   +sha | fs.hash_file OR fs.verify_with_sha_file | hash_file simply returns a hash, verify_with_sha_file compares it with a corresponding .sha file +shaget | fs.hash_file |   +dumptxt | fs.write_file | Use "end" for offset to append data +fixcmac | fs.fix_cmacs |   +verify | fs.verify |   +decrypt | title.decrypt |   +encrypt | title.encrypt |   +buildcia | title.build_cia |   +install | title.install |   +extrcode | title.extract_code |   +cmprcode | title.compress_code |   +sdump | fs.key_dump |   +applyips | target.apply_ips |   +applybps | target.apply_bps |   +applybpm | target.apply_bpm |   +textview | fs.show_file_text_viewer | fs.show_text_viewer can be used to show text from a variable +cartdump | fs.cart_dump |   +isdir | fs.is_dir |   +exist | fs.exists |   +boot | sys.boot |   +switchsd | fs.sd_switch |   +nextemu | sys.next_emu |   +reboot | sys.reboot |   +poweroff | sys.power_off |   +bkpt | bkpt |   + +#### PREVIEW_MODE variable + +Unlike the `PREVIEW_MODE` GM9Script variable, this has been split into multiple functions. + +Setting | Lua +-- | -- +“quick” and “full” | (There is no alternative to view a Lua script as it’s running.) +“off” | ui.clear +text | ui.show_text +png file | ui.show_png +game icon | ui.show_game_info + +#### Other constants + +GM9 | Lua | Notes +-- | -- | -- +DATESTAMP | util.get_datestamp() | Formatted like “241202”, equivalent to os.date("%y%m%d") +TIMESTAMP | util.get_timestamp() | Formatted like “010828”, equivalent to os.date("%H%M%S") +SYSID0 | sys.sys_id0 |   +EMUID0 | sys.emu_id0 |   +EMUBASE | sys.emu_base |   +SERIAL | sys.serial |   +REGION | sys.region |   +SDSIZE | fs.stat_fs("0:/").total | int instead of string (use util.format_bytes to format it) +SDFREE | fs.stat_fs("0:/").free | int instead of string (use util.format_bytes to format it) +NANDSIZE | NANDSIZE | int instead of string (use util.format_bytes to format it) +GM9OUT | GM9OUT |   +CURRDIR | CURRDIR | nil instead of “(null)” if it can’t be found +ONTYPE | CONSOLE_TYPE | “O3DS” or “N3DS” +RDTYPE | IS_DEVKIT | boolean instead of a string +HAX | HAX |   +GM9VER | GM9VER |   + +## Comparisons with standard Lua + +These original Lua 5.4 modules are fully available: +* [Basic functions](https://www.lua.org/manual/5.4/manual.html#6.1) + * `print` will replace the top screen with an output log. Currently it does not implement some features such as line wrapping. + * dofile and loadfile don't work yet. +* [coroutine](https://www.lua.org/manual/5.4/manual.html#6.2) +* [debug](https://www.lua.org/manual/5.4/manual.html#6.10) +* [math](https://www.lua.org/manual/5.4/manual.html#6.7) +* [string](https://www.lua.org/manual/5.4/manual.html#6.4) +* [table](https://www.lua.org/manual/5.4/manual.html#6.6) +* [utf8](https://www.lua.org/manual/5.4/manual.html#6.5) + +These modules are partially available: +* [os](https://www.lua.org/manual/5.4/manual.html#6.9) + * Only `os.clock`, `os.time`, `os.date`, `os.difftime`, and `os.remove` +* [io](https://www.lua.org/manual/5.4/manual.html#6.8) + * Only `io.open`; for open files, all but `file:setvbuf` and `file:lines` + * This is a custom compatibility module that uses `fs` functions. If there are differences compared to the original `io` implementation, please report them as issues. + +These modules work differently: +* [package](https://www.lua.org/manual/5.4/manual.html#6.3) + * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. + +## API reference + +### Constants + +#### GM9VER +The version such as `v2.1.1-159-gff2cb913`, the same string that is shown on the main screen. + +#### SCRIPT +Path to the executed script, such as `0:/gm9/luascripts/myscript.lua`. + +#### CURRDIR +Directory of the executed script, such as `0:/gm9/luascripts`. + +#### GM9OUT +The value `0:/gm9/out`. + +#### HAX +> [!WARNING] +> This needs checking if it's accurate. +One of three values: +* "ntrboot" if started from an ntrboot cart +* "sighax" if booted directly from a FIRM partition +* Empty string otherwise + +#### NANDSIZE +Total size of SysNAND in bytes. + +#### CONSOLE_TYPE +The string `"O3DS"` or `"N3DS"`. + +#### IS_DEVKIT +`true` if the console is a developer unit. + +### `ui` module + +> [!NOTE] +> This assumes the default build is used, where the bottom screen is the main screen. If GodMode9 is compiled with `SWITCH_SCREENS=1`, then every instance where something appears on the bottom screen will actually be on the top screen and vice versa. + +#### ui.echo + +`void ui.echo(string text)` + +Display text on the bottom screen and wait for the user to press A. + +* **Arguments** + * `text` - Text to show the user + +#### ui.ask + +`bool ui.ask(string text)` + +Prompt the user with a yes/no question. + +* **Arguments** + * `text` - Text to ask the user +* **Returns:** `true` if the user accepts + +#### ui.ask_hex + +`int ui.ask_hex(string text, int initial, int n_digits)` + +Ask the user to input a hex number. + +* **Arguments** + * `text` - Text to ask the user + * `initial` - Starting value + * `n_digits` - Amount of hex digits allowed +* **Returns:** the number the user entered, or `nil` if canceled + +#### ui.ask_number + +`int ui.ask_number(string text, int initial)` + +Ask the user to input a number. + +* **Arguments** + * `text` - Text to ask the user + * `initial` - Starting value +* **Returns:** the number the user entered, or `nil` if canceled + +#### ui.ask_text + +`string ui.ask_text(string prompt, string initial, int max_length)` + +Ask the user to input text. + +* **Arguments** + * `prompt` - Text to ask the user + * `initial` - Starting value + * `max_length` - Maximum length of the string +* **Returns:** the text the user entered, or `nil` if canceled + +#### ui.ask_selection + +`int ui.ask_selection(string prompt, array options)` + +Ask the user to choose an option from a list. A maximum of 256 options are allowed. + +* **Arguments** + * `prompt` - Text to ask the user + * `options` - Table of options +* **Returns:** index of selected option, or `nil` if canceled + +#### ui.clear + +`void ui.clear()` + +Clears the top screen. + +#### ui.show_png + +`void ui.show_png(string path)` + +Displays a PNG file on the top screen. + +The image must not be larger than 400 pixels horizontal or 240 pixels vertical. If `SWITCH_SCREENS=1` is used, it must not be larger than 320 pixels horizontal. + +* **Arguments** + * `path` - Path to PNG file +* **Throws** + * `"Could not read (file)"` - file does not exist or there was another read error + * `"Invalid PNG file"` - file is not a valid PNG + * `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error + +#### ui.show_text + +`void ui.show_text(string text)` + +Displays text on the top screen. + +* **Arguments** + * `text` - Text to show the user + +#### ui.show_game_info + +`void ui.show_game_info(string path)` + +Shows game file info. Accepts any files that include an SMDH, a DS game file, or GBA file. + +* **Arguments** + * `path` - Path to game file (CIA, CCI/".3ds", SMDH, TMD, TIE (DSiWare export), Ticket, NDS, GBA) +* **Throws** + * `"ShowGameFileIcon failed on (path)"` - failed to get game info from path + +#### ui.show_qr + +`void ui.show_qr(string text, string data)` + +Displays a QR code on the top screen, and a prompt on the bottom screen, and waits for the user to press A. + +* **Arguments** + * `text` - Text to show the user + * `data` - Data to encode into the QR code +* **Throws** + * `"could not allocate memory"` - out-of-memory error when attempting to generate the QR code + +#### ui.show_text_viewer + +`void ui.show_text_viewer(string text)` + +Display a scrollable text viewer. + +* **Arguments** + * `text` - Text to display +* **Throws** + * `"text validation failed"` - given string contains invalid characters + * `"failed to run MemTextViewer"` - internal memory viewer error + +#### ui.show_file_text_viewer + +`void ui.show_file_text_viewer(string path)` + +Display a scrollable text viewer from a text file. + +* **Arguments** + * `path` - Path to text file +* **Throws** + * `"could not allocate memory"` - out-of-memory error when attempting to create the text buffer + * `"text validation failed"` - text file contains invalid characters + * `"failed to run MemTextViewer"` - internal memory viewer error + +#### ui.format_bytes + +`string ui.format_bytes(int bytes)` + +Format a number with `Byte`, `kB`, `MB`, or `GB`. + +> [!NOTE] +> This is affected by localization and may return different text if the language is not English. + +* **Arguments** + * `bytes` - size to format +* **Returns:** formatted string + +#### ui.check_key + +`bool ui.check_key(string key)` + +Checks if the user is holding down a key. + +* **Arguments** + * `key` - a button string: `"A"`, `"B"`, `"SELECT"`, `"START"`, `"RIGHT"`, `"LEFT"`, `"UP"`, `"DOWN"`, `"R"`, `"L"`, `"X"`, `"Y"` +* **Returns:** `true` if currently held, `false` if not From bdf569a8574cb0e82c31845ed34bb24201a15894 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 02:42:25 -0600 Subject: [PATCH 101/124] formatting fix --- resources/lua-doc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 6ffd103c4..24276536c 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -90,7 +90,7 @@ reboot | sys.reboot |   poweroff | sys.power_off |   bkpt | bkpt |   -#### PREVIEW_MODE variable +### PREVIEW_MODE variable Unlike the `PREVIEW_MODE` GM9Script variable, this has been split into multiple functions. @@ -102,7 +102,7 @@ text | ui.show_text png file | ui.show_png game icon | ui.show_game_info -#### Other constants +### Other constants GM9 | Lua | Notes -- | -- | -- From 6ef14b619536b4253e341ba40b4dea728358979d Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 02:52:33 -0600 Subject: [PATCH 102/124] up heading level for all sections --- resources/lua-doc.md | 66 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 24276536c..1efb7c1b1 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -5,7 +5,7 @@ GodMode9 includes a Lua 5.4.7 implementation. -## Running scripts +# Running scripts There are four ways to run Lua scripts: @@ -15,7 +15,7 @@ There are four ways to run Lua scripts: * This takes precedence over `0:/gm9/luascripts` * Place it at `data/autorun.lua`, then build GodMode9 from source with `SCRIPT_RUNNER=1` to automatically run it at launch -## Packages +# Packages Lua scripts can load custom modules. The default search path (defined as `package.path`) is: @@ -30,11 +30,11 @@ For example, when a script calls `require("foobar")`, the package searcher will * `V:/luapackages/foobar.lua` * `V:/luapackages/foobar/init.lua` -## Comparison with GM9Script +# Comparison with GM9Script These tables are provided to assist with converting from GM9Script to Lua. -### Commands +## Commands GM9 | Lua | Notes -- | -- | -- @@ -90,7 +90,7 @@ reboot | sys.reboot |   poweroff | sys.power_off |   bkpt | bkpt |   -### PREVIEW_MODE variable +## PREVIEW_MODE variable Unlike the `PREVIEW_MODE` GM9Script variable, this has been split into multiple functions. @@ -102,7 +102,7 @@ text | ui.show_text png file | ui.show_png game icon | ui.show_game_info -### Other constants +## Other constants GM9 | Lua | Notes -- | -- | -- @@ -123,7 +123,7 @@ RDTYPE | IS_DEVKIT | boolean instead of a string HAX | HAX |   GM9VER | GM9VER |   -## Comparisons with standard Lua +# Comparisons with standard Lua These original Lua 5.4 modules are fully available: * [Basic functions](https://www.lua.org/manual/5.4/manual.html#6.1) @@ -147,23 +147,23 @@ These modules work differently: * [package](https://www.lua.org/manual/5.4/manual.html#6.3) * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. -## API reference +# API reference -### Constants +## Constants -#### GM9VER +### GM9VER The version such as `v2.1.1-159-gff2cb913`, the same string that is shown on the main screen. -#### SCRIPT +### SCRIPT Path to the executed script, such as `0:/gm9/luascripts/myscript.lua`. -#### CURRDIR +### CURRDIR Directory of the executed script, such as `0:/gm9/luascripts`. -#### GM9OUT +### GM9OUT The value `0:/gm9/out`. -#### HAX +### HAX > [!WARNING] > This needs checking if it's accurate. One of three values: @@ -171,21 +171,21 @@ One of three values: * "sighax" if booted directly from a FIRM partition * Empty string otherwise -#### NANDSIZE +### NANDSIZE Total size of SysNAND in bytes. -#### CONSOLE_TYPE +### CONSOLE_TYPE The string `"O3DS"` or `"N3DS"`. -#### IS_DEVKIT +### IS_DEVKIT `true` if the console is a developer unit. -### `ui` module +## `ui` module > [!NOTE] > This assumes the default build is used, where the bottom screen is the main screen. If GodMode9 is compiled with `SWITCH_SCREENS=1`, then every instance where something appears on the bottom screen will actually be on the top screen and vice versa. -#### ui.echo +### ui.echo `void ui.echo(string text)` @@ -194,7 +194,7 @@ Display text on the bottom screen and wait for the user to press A. * **Arguments** * `text` - Text to show the user -#### ui.ask +### ui.ask `bool ui.ask(string text)` @@ -204,7 +204,7 @@ Prompt the user with a yes/no question. * `text` - Text to ask the user * **Returns:** `true` if the user accepts -#### ui.ask_hex +### ui.ask_hex `int ui.ask_hex(string text, int initial, int n_digits)` @@ -216,7 +216,7 @@ Ask the user to input a hex number. * `n_digits` - Amount of hex digits allowed * **Returns:** the number the user entered, or `nil` if canceled -#### ui.ask_number +### ui.ask_number `int ui.ask_number(string text, int initial)` @@ -227,7 +227,7 @@ Ask the user to input a number. * `initial` - Starting value * **Returns:** the number the user entered, or `nil` if canceled -#### ui.ask_text +### ui.ask_text `string ui.ask_text(string prompt, string initial, int max_length)` @@ -239,7 +239,7 @@ Ask the user to input text. * `max_length` - Maximum length of the string * **Returns:** the text the user entered, or `nil` if canceled -#### ui.ask_selection +### ui.ask_selection `int ui.ask_selection(string prompt, array options)` @@ -250,13 +250,13 @@ Ask the user to choose an option from a list. A maximum of 256 options are allow * `options` - Table of options * **Returns:** index of selected option, or `nil` if canceled -#### ui.clear +### ui.clear `void ui.clear()` Clears the top screen. -#### ui.show_png +### ui.show_png `void ui.show_png(string path)` @@ -271,7 +271,7 @@ The image must not be larger than 400 pixels horizontal or 240 pixels vertical. * `"Invalid PNG file"` - file is not a valid PNG * `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error -#### ui.show_text +### ui.show_text `void ui.show_text(string text)` @@ -280,7 +280,7 @@ Displays text on the top screen. * **Arguments** * `text` - Text to show the user -#### ui.show_game_info +### ui.show_game_info `void ui.show_game_info(string path)` @@ -291,7 +291,7 @@ Shows game file info. Accepts any files that include an SMDH, a DS game file, or * **Throws** * `"ShowGameFileIcon failed on (path)"` - failed to get game info from path -#### ui.show_qr +### ui.show_qr `void ui.show_qr(string text, string data)` @@ -303,7 +303,7 @@ Displays a QR code on the top screen, and a prompt on the bottom screen, and wai * **Throws** * `"could not allocate memory"` - out-of-memory error when attempting to generate the QR code -#### ui.show_text_viewer +### ui.show_text_viewer `void ui.show_text_viewer(string text)` @@ -315,7 +315,7 @@ Display a scrollable text viewer. * `"text validation failed"` - given string contains invalid characters * `"failed to run MemTextViewer"` - internal memory viewer error -#### ui.show_file_text_viewer +### ui.show_file_text_viewer `void ui.show_file_text_viewer(string path)` @@ -328,7 +328,7 @@ Display a scrollable text viewer from a text file. * `"text validation failed"` - text file contains invalid characters * `"failed to run MemTextViewer"` - internal memory viewer error -#### ui.format_bytes +### ui.format_bytes `string ui.format_bytes(int bytes)` @@ -341,7 +341,7 @@ Format a number with `Byte`, `kB`, `MB`, or `GB`. * `bytes` - size to format * **Returns:** formatted string -#### ui.check_key +### ui.check_key `bool ui.check_key(string key)` From 376968f373beafcd928d208f298d29a7df6ca16d Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 07:41:47 -0600 Subject: [PATCH 103/124] Revert "up heading level for all sections" This reverts commit 6ef14b619536b4253e341ba40b4dea728358979d. --- resources/lua-doc.md | 66 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 1efb7c1b1..24276536c 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -5,7 +5,7 @@ GodMode9 includes a Lua 5.4.7 implementation. -# Running scripts +## Running scripts There are four ways to run Lua scripts: @@ -15,7 +15,7 @@ There are four ways to run Lua scripts: * This takes precedence over `0:/gm9/luascripts` * Place it at `data/autorun.lua`, then build GodMode9 from source with `SCRIPT_RUNNER=1` to automatically run it at launch -# Packages +## Packages Lua scripts can load custom modules. The default search path (defined as `package.path`) is: @@ -30,11 +30,11 @@ For example, when a script calls `require("foobar")`, the package searcher will * `V:/luapackages/foobar.lua` * `V:/luapackages/foobar/init.lua` -# Comparison with GM9Script +## Comparison with GM9Script These tables are provided to assist with converting from GM9Script to Lua. -## Commands +### Commands GM9 | Lua | Notes -- | -- | -- @@ -90,7 +90,7 @@ reboot | sys.reboot |   poweroff | sys.power_off |   bkpt | bkpt |   -## PREVIEW_MODE variable +### PREVIEW_MODE variable Unlike the `PREVIEW_MODE` GM9Script variable, this has been split into multiple functions. @@ -102,7 +102,7 @@ text | ui.show_text png file | ui.show_png game icon | ui.show_game_info -## Other constants +### Other constants GM9 | Lua | Notes -- | -- | -- @@ -123,7 +123,7 @@ RDTYPE | IS_DEVKIT | boolean instead of a string HAX | HAX |   GM9VER | GM9VER |   -# Comparisons with standard Lua +## Comparisons with standard Lua These original Lua 5.4 modules are fully available: * [Basic functions](https://www.lua.org/manual/5.4/manual.html#6.1) @@ -147,23 +147,23 @@ These modules work differently: * [package](https://www.lua.org/manual/5.4/manual.html#6.3) * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. -# API reference +## API reference -## Constants +### Constants -### GM9VER +#### GM9VER The version such as `v2.1.1-159-gff2cb913`, the same string that is shown on the main screen. -### SCRIPT +#### SCRIPT Path to the executed script, such as `0:/gm9/luascripts/myscript.lua`. -### CURRDIR +#### CURRDIR Directory of the executed script, such as `0:/gm9/luascripts`. -### GM9OUT +#### GM9OUT The value `0:/gm9/out`. -### HAX +#### HAX > [!WARNING] > This needs checking if it's accurate. One of three values: @@ -171,21 +171,21 @@ One of three values: * "sighax" if booted directly from a FIRM partition * Empty string otherwise -### NANDSIZE +#### NANDSIZE Total size of SysNAND in bytes. -### CONSOLE_TYPE +#### CONSOLE_TYPE The string `"O3DS"` or `"N3DS"`. -### IS_DEVKIT +#### IS_DEVKIT `true` if the console is a developer unit. -## `ui` module +### `ui` module > [!NOTE] > This assumes the default build is used, where the bottom screen is the main screen. If GodMode9 is compiled with `SWITCH_SCREENS=1`, then every instance where something appears on the bottom screen will actually be on the top screen and vice versa. -### ui.echo +#### ui.echo `void ui.echo(string text)` @@ -194,7 +194,7 @@ Display text on the bottom screen and wait for the user to press A. * **Arguments** * `text` - Text to show the user -### ui.ask +#### ui.ask `bool ui.ask(string text)` @@ -204,7 +204,7 @@ Prompt the user with a yes/no question. * `text` - Text to ask the user * **Returns:** `true` if the user accepts -### ui.ask_hex +#### ui.ask_hex `int ui.ask_hex(string text, int initial, int n_digits)` @@ -216,7 +216,7 @@ Ask the user to input a hex number. * `n_digits` - Amount of hex digits allowed * **Returns:** the number the user entered, or `nil` if canceled -### ui.ask_number +#### ui.ask_number `int ui.ask_number(string text, int initial)` @@ -227,7 +227,7 @@ Ask the user to input a number. * `initial` - Starting value * **Returns:** the number the user entered, or `nil` if canceled -### ui.ask_text +#### ui.ask_text `string ui.ask_text(string prompt, string initial, int max_length)` @@ -239,7 +239,7 @@ Ask the user to input text. * `max_length` - Maximum length of the string * **Returns:** the text the user entered, or `nil` if canceled -### ui.ask_selection +#### ui.ask_selection `int ui.ask_selection(string prompt, array options)` @@ -250,13 +250,13 @@ Ask the user to choose an option from a list. A maximum of 256 options are allow * `options` - Table of options * **Returns:** index of selected option, or `nil` if canceled -### ui.clear +#### ui.clear `void ui.clear()` Clears the top screen. -### ui.show_png +#### ui.show_png `void ui.show_png(string path)` @@ -271,7 +271,7 @@ The image must not be larger than 400 pixels horizontal or 240 pixels vertical. * `"Invalid PNG file"` - file is not a valid PNG * `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error -### ui.show_text +#### ui.show_text `void ui.show_text(string text)` @@ -280,7 +280,7 @@ Displays text on the top screen. * **Arguments** * `text` - Text to show the user -### ui.show_game_info +#### ui.show_game_info `void ui.show_game_info(string path)` @@ -291,7 +291,7 @@ Shows game file info. Accepts any files that include an SMDH, a DS game file, or * **Throws** * `"ShowGameFileIcon failed on (path)"` - failed to get game info from path -### ui.show_qr +#### ui.show_qr `void ui.show_qr(string text, string data)` @@ -303,7 +303,7 @@ Displays a QR code on the top screen, and a prompt on the bottom screen, and wai * **Throws** * `"could not allocate memory"` - out-of-memory error when attempting to generate the QR code -### ui.show_text_viewer +#### ui.show_text_viewer `void ui.show_text_viewer(string text)` @@ -315,7 +315,7 @@ Display a scrollable text viewer. * `"text validation failed"` - given string contains invalid characters * `"failed to run MemTextViewer"` - internal memory viewer error -### ui.show_file_text_viewer +#### ui.show_file_text_viewer `void ui.show_file_text_viewer(string path)` @@ -328,7 +328,7 @@ Display a scrollable text viewer from a text file. * `"text validation failed"` - text file contains invalid characters * `"failed to run MemTextViewer"` - internal memory viewer error -### ui.format_bytes +#### ui.format_bytes `string ui.format_bytes(int bytes)` @@ -341,7 +341,7 @@ Format a number with `Byte`, `kB`, `MB`, or `GB`. * `bytes` - size to format * **Returns:** formatted string -### ui.check_key +#### ui.check_key `bool ui.check_key(string key)` From 11082c91698da29413706903e65fa242f5067959 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 07:44:05 -0600 Subject: [PATCH 104/124] separators --- resources/lua-doc.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 24276536c..8e6961df6 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -147,6 +147,8 @@ These modules work differently: * [package](https://www.lua.org/manual/5.4/manual.html#6.3) * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. +--- + ## API reference ### Constants @@ -180,6 +182,8 @@ The string `"O3DS"` or `"N3DS"`. #### IS_DEVKIT `true` if the console is a developer unit. +--- + ### `ui` module > [!NOTE] From 87f9d159726ee1cfe69f887ee5f5438b32296f02 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 08:08:53 -0600 Subject: [PATCH 105/124] fix name and error for fs.move --- arm9/source/lua/gm9internalfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 0bdecb1bf..7e588e221 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -35,7 +35,7 @@ static void CreateStatTable(lua_State* L, FILINFO* fno) { } static int internalfs_move(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.rename"); + bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.move"); const char* path_src = luaL_checkstring(L, 1); const char* path_dst = luaL_checkstring(L, 2); FILINFO fno; @@ -46,7 +46,7 @@ static int internalfs_move(lua_State* L) { } if (!(flags & OVERWRITE_ALL) && (fvx_stat(path_dst, &fno) == FR_OK)) { - return luaL_error(L, "destination already exists on %s -> %s and {overwrite_all=true} was not used", path_src, path_dst); + return luaL_error(L, "destination already exists on %s -> %s and {overwrite=true} was not used", path_src, path_dst); } if (!(PathMoveCopy(path_dst, path_src, &flags, true))) { From 81254fa700c2fd3102c36f230ef1d589d1cd42ca Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 08:17:34 -0600 Subject: [PATCH 106/124] do explicit permission checks in fs.move --- arm9/source/lua/gm9internalfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 7e588e221..b0f911194 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -40,6 +40,9 @@ static int internalfs_move(lua_State* L) { const char* path_dst = luaL_checkstring(L, 2); FILINFO fno; + CheckWritePermissionsLuaError(L, path_src); + CheckWritePermissionsLuaError(L, path_dst); + u32 flags = BUILD_PATH; if (extra) { flags = GetFlagsFromTable(L, 3, flags, NO_CANCEL | SILENT | OVERWRITE_ALL | SKIP_ALL); From f1c022a5bf23fc8bde1cd8e91fd560fdda137a2e Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 08:29:58 -0600 Subject: [PATCH 107/124] fix error string for fs.copy --- arm9/source/lua/gm9internalfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index b0f911194..81c8f983d 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -102,7 +102,7 @@ static int internalfs_copy(lua_State* L) { } if (!(flags & OVERWRITE_ALL) && (fvx_stat(path_dst, &fno) == FR_OK)) { - return luaL_error(L, "destination already exists on %s -> %s and {overwrite_all=true} was not used", path_src, path_dst); + return luaL_error(L, "destination already exists on %s -> %s and {overwrite=true} was not used", path_src, path_dst); } if (!(PathMoveCopy(path_dst, path_src, &flags, false))) { From aa672a84562c0c4db069f8dcf07326d0f1a81f2d Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 08:59:54 -0600 Subject: [PATCH 108/124] fix function name for fs.dir_info --- arm9/source/lua/gm9internalfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 81c8f983d..3666a564e 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -199,7 +199,7 @@ static int internalfs_stat_fs(lua_State* L) { } static int internalfs_dir_info(lua_State* L) { - CheckLuaArgCount(L, 1, "_fs.stat_fs"); + CheckLuaArgCount(L, 1, "_fs.dir_info"); const char* path = luaL_checkstring(L, 1); u64 tsize = 0; From e31f69cda7efd1cce12360e9003f549b4eae5ec5 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 10:04:54 -0600 Subject: [PATCH 109/124] fix error string for fs.find and fs.find_not --- arm9/source/lua/gm9internalfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 3666a564e..756a07f4c 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -286,7 +286,7 @@ static int internalfs_find(lua_State* L) { u8 mode = (flags & FIND_FIRST) ? FN_LOWEST : FN_HIGHEST; FRESULT res = fvx_findpath(path, pattern, mode); if (res != FR_OK) { - return luaL_error(L, "failed to find %s (%d)", path, res); + return luaL_error(L, "failed to find %s (%d)", pattern, res); } lua_pushstring(L, path); @@ -300,7 +300,7 @@ static int internalfs_find_not(lua_State* L) { FRESULT res = fvx_findnopath(path, pattern); if (res != FR_OK) { - return luaL_error(L, "failed to find %s (%d)", path, res); + return luaL_error(L, "failed to find %s (%d)", pattern, res); } lua_pushstring(L, path); From 92f83a5a6cf3f7e26c0b93cffbe957968d9069ec Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 10:22:39 -0600 Subject: [PATCH 110/124] fix function name for fs.img_umount --- arm9/source/lua/gm9internalfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 756a07f4c..a6cdc7663 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -470,7 +470,7 @@ static int internalfs_img_umount(lua_State* L) { } static int internalfs_get_img_mount(lua_State* L) { - CheckLuaArgCount(L, 0, "_fs.img_umount"); + CheckLuaArgCount(L, 0, "_fs.get_img_mount"); char path[256] = { 0 }; strncpy(path, GetMountPath(), 256); From f8447f3b7ef7a6d5d2107558701916d0d828c0b1 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 10:48:59 -0600 Subject: [PATCH 111/124] partial fs doc --- resources/lua-doc.md | 363 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 319 insertions(+), 44 deletions(-) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 8e6961df6..a51cc37b7 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -12,7 +12,7 @@ There are four ways to run Lua scripts: * Select it in the file browser and choose "`Execute Lua script`" * Place it in `0:/gm9/luascripts`, then open the HOME menu, choose "`Lua scripts...`", then choose the script * Place it in `data/luascripts`, then build GodMode9 from source - * This takes precedence over `0:/gm9/luascripts` + * This takes precedence over `0:/gm9/luascripts` * Place it at `data/autorun.lua`, then build GodMode9 from source with `SCRIPT_RUNNER=1` to automatically run it at launch ## Packages @@ -96,8 +96,8 @@ Unlike the `PREVIEW_MODE` GM9Script variable, this has been split into multiple Setting | Lua -- | -- -“quick” and “full” | (There is no alternative to view a Lua script as it’s running.) -“off” | ui.clear +“quick" and “full" | (There is no alternative to view a Lua script as it’s running.) +“off" | ui.clear text | ui.show_text png file | ui.show_png game icon | ui.show_game_info @@ -106,8 +106,8 @@ game icon | ui.show_game_info GM9 | Lua | Notes -- | -- | -- -DATESTAMP | util.get_datestamp() | Formatted like “241202”, equivalent to os.date("%y%m%d") -TIMESTAMP | util.get_timestamp() | Formatted like “010828”, equivalent to os.date("%H%M%S") +DATESTAMP | util.get_datestamp() | Formatted like “241202", equivalent to os.date("%y%m%d") +TIMESTAMP | util.get_timestamp() | Formatted like “010828", equivalent to os.date("%H%M%S") SYSID0 | sys.sys_id0 |   EMUID0 | sys.emu_id0 |   EMUBASE | sys.emu_base |   @@ -117,8 +117,8 @@ SDSIZE | fs.stat_fs("0:/").total | int instead of string (use util.format_bytes SDFREE | fs.stat_fs("0:/").free | int instead of string (use util.format_bytes to format it) NANDSIZE | NANDSIZE | int instead of string (use util.format_bytes to format it) GM9OUT | GM9OUT |   -CURRDIR | CURRDIR | nil instead of “(null)” if it can’t be found -ONTYPE | CONSOLE_TYPE | “O3DS” or “N3DS” +CURRDIR | CURRDIR | nil instead of “(null)" if it can’t be found +ONTYPE | CONSOLE_TYPE | “O3DS" or “N3DS" RDTYPE | IS_DEVKIT | boolean instead of a string HAX | HAX |   GM9VER | GM9VER |   @@ -127,8 +127,8 @@ GM9VER | GM9VER |   These original Lua 5.4 modules are fully available: * [Basic functions](https://www.lua.org/manual/5.4/manual.html#6.1) - * `print` will replace the top screen with an output log. Currently it does not implement some features such as line wrapping. - * dofile and loadfile don't work yet. + * `print` will replace the top screen with an output log. Currently it does not implement some features such as line wrapping. + * dofile and loadfile don't work yet. * [coroutine](https://www.lua.org/manual/5.4/manual.html#6.2) * [debug](https://www.lua.org/manual/5.4/manual.html#6.10) * [math](https://www.lua.org/manual/5.4/manual.html#6.7) @@ -138,14 +138,14 @@ These original Lua 5.4 modules are fully available: These modules are partially available: * [os](https://www.lua.org/manual/5.4/manual.html#6.9) - * Only `os.clock`, `os.time`, `os.date`, `os.difftime`, and `os.remove` + * Only `os.clock`, `os.time`, `os.date`, `os.difftime`, and `os.remove` * [io](https://www.lua.org/manual/5.4/manual.html#6.8) - * Only `io.open`; for open files, all but `file:setvbuf` and `file:lines` - * This is a custom compatibility module that uses `fs` functions. If there are differences compared to the original `io` implementation, please report them as issues. + * Only `io.open`; for open files, all but `file:setvbuf` and `file:lines` + * This is a custom compatibility module that uses `fs` functions. If there are differences compared to the original `io` implementation, please report them as issues. These modules work differently: * [package](https://www.lua.org/manual/5.4/manual.html#6.3) - * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. + * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. --- @@ -196,7 +196,7 @@ The string `"O3DS"` or `"N3DS"`. Display text on the bottom screen and wait for the user to press A. * **Arguments** - * `text` - Text to show the user + * `text` - Text to show the user #### ui.ask @@ -205,7 +205,7 @@ Display text on the bottom screen and wait for the user to press A. Prompt the user with a yes/no question. * **Arguments** - * `text` - Text to ask the user + * `text` - Text to ask the user * **Returns:** `true` if the user accepts #### ui.ask_hex @@ -215,9 +215,9 @@ Prompt the user with a yes/no question. Ask the user to input a hex number. * **Arguments** - * `text` - Text to ask the user - * `initial` - Starting value - * `n_digits` - Amount of hex digits allowed + * `text` - Text to ask the user + * `initial` - Starting value + * `n_digits` - Amount of hex digits allowed * **Returns:** the number the user entered, or `nil` if canceled #### ui.ask_number @@ -227,8 +227,8 @@ Ask the user to input a hex number. Ask the user to input a number. * **Arguments** - * `text` - Text to ask the user - * `initial` - Starting value + * `text` - Text to ask the user + * `initial` - Starting value * **Returns:** the number the user entered, or `nil` if canceled #### ui.ask_text @@ -238,9 +238,9 @@ Ask the user to input a number. Ask the user to input text. * **Arguments** - * `prompt` - Text to ask the user - * `initial` - Starting value - * `max_length` - Maximum length of the string + * `prompt` - Text to ask the user + * `initial` - Starting value + * `max_length` - Maximum length of the string * **Returns:** the text the user entered, or `nil` if canceled #### ui.ask_selection @@ -250,8 +250,8 @@ Ask the user to input text. Ask the user to choose an option from a list. A maximum of 256 options are allowed. * **Arguments** - * `prompt` - Text to ask the user - * `options` - Table of options + * `prompt` - Text to ask the user + * `options` - Table of options * **Returns:** index of selected option, or `nil` if canceled #### ui.clear @@ -269,11 +269,11 @@ Displays a PNG file on the top screen. The image must not be larger than 400 pixels horizontal or 240 pixels vertical. If `SWITCH_SCREENS=1` is used, it must not be larger than 320 pixels horizontal. * **Arguments** - * `path` - Path to PNG file + * `path` - Path to PNG file * **Throws** - * `"Could not read (file)"` - file does not exist or there was another read error - * `"Invalid PNG file"` - file is not a valid PNG - * `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error + * `"Could not read (file)"` - file does not exist or there was another read error + * `"Invalid PNG file"` - file is not a valid PNG + * `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error #### ui.show_text @@ -282,7 +282,7 @@ The image must not be larger than 400 pixels horizontal or 240 pixels vertical. Displays text on the top screen. * **Arguments** - * `text` - Text to show the user + * `text` - Text to show the user #### ui.show_game_info @@ -291,9 +291,9 @@ Displays text on the top screen. Shows game file info. Accepts any files that include an SMDH, a DS game file, or GBA file. * **Arguments** - * `path` - Path to game file (CIA, CCI/".3ds", SMDH, TMD, TIE (DSiWare export), Ticket, NDS, GBA) + * `path` - Path to game file (CIA, CCI/".3ds", SMDH, TMD, TIE (DSiWare export), Ticket, NDS, GBA) * **Throws** - * `"ShowGameFileIcon failed on (path)"` - failed to get game info from path + * `"ShowGameFileIcon failed on "` - failed to get game info from path #### ui.show_qr @@ -302,10 +302,10 @@ Shows game file info. Accepts any files that include an SMDH, a DS game file, or Displays a QR code on the top screen, and a prompt on the bottom screen, and waits for the user to press A. * **Arguments** - * `text` - Text to show the user - * `data` - Data to encode into the QR code + * `text` - Text to show the user + * `data` - Data to encode into the QR code * **Throws** - * `"could not allocate memory"` - out-of-memory error when attempting to generate the QR code + * `"could not allocate memory"` - out-of-memory error when attempting to generate the QR code #### ui.show_text_viewer @@ -314,10 +314,10 @@ Displays a QR code on the top screen, and a prompt on the bottom screen, and wai Display a scrollable text viewer. * **Arguments** - * `text` - Text to display + * `text` - Text to display * **Throws** - * `"text validation failed"` - given string contains invalid characters - * `"failed to run MemTextViewer"` - internal memory viewer error + * `"text validation failed"` - given string contains invalid characters + * `"failed to run MemTextViewer"` - internal memory viewer error #### ui.show_file_text_viewer @@ -326,11 +326,11 @@ Display a scrollable text viewer. Display a scrollable text viewer from a text file. * **Arguments** - * `path` - Path to text file + * `path` - Path to text file * **Throws** - * `"could not allocate memory"` - out-of-memory error when attempting to create the text buffer - * `"text validation failed"` - text file contains invalid characters - * `"failed to run MemTextViewer"` - internal memory viewer error + * `"could not allocate memory"` - out-of-memory error when attempting to create the text buffer + * `"text validation failed"` - text file contains invalid characters + * `"failed to run MemTextViewer"` - internal memory viewer error #### ui.format_bytes @@ -342,7 +342,7 @@ Format a number with `Byte`, `kB`, `MB`, or `GB`. > This is affected by localization and may return different text if the language is not English. * **Arguments** - * `bytes` - size to format + * `bytes` - Size to format * **Returns:** formatted string #### ui.check_key @@ -352,5 +352,280 @@ Format a number with `Byte`, `kB`, `MB`, or `GB`. Checks if the user is holding down a key. * **Arguments** - * `key` - a button string: `"A"`, `"B"`, `"SELECT"`, `"START"`, `"RIGHT"`, `"LEFT"`, `"UP"`, `"DOWN"`, `"R"`, `"L"`, `"X"`, `"Y"` + * `key` - A button string: `"A"`, `"B"`, `"SELECT"`, `"START"`, `"RIGHT"`, `"LEFT"`, `"UP"`, `"DOWN"`, `"R"`, `"L"`, `"X"`, `"Y"` * **Returns:** `true` if currently held, `false` if not + +--- + +### `fs` module + +#### fs.move + +`void fs.move(string src, string dst[, table opts {bool no_cancel, bool silent, bool overwrite, bool skip_all}])` + +Moves or renames a file or directory. + +* **Arguments** + * `src` - Item to move + * `dst` - Destination name + * `opts` (optional) - Option flags + * `no_cancel` - Don’t allow user to cancel + * `silent` - Don’t show progress + * `overwrite` - Overwrite files + * `skip_all` - Skip existing files +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"destination already exists on -> and {overwrite=true} was not used"` - attempted to move an item over an existing one without using `overwrite` + * `"PathMoveCopy failed on -> "` - error when moving, or user canceled + +#### fs.remove + +`void fs.remove(string path[, table opts {bool recursive}])` + +Delete a file or directory. + +* **Arguments** + * `path` - Path to delete + * `opts` (optional) - Option flags + * `recursive` - Remove directories recursively +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"requested directory remove without {recursive=true} on "` - attempted to delete a directory without using `recursive` + * `"PathDelete failed on %s"` - error when deleting + +#### fs.copy + +`void fs.copy(string src, string dst[, table opts {bool calc_sha, bool sha1, bool no_cancel, bool silent, bool overwrite, bool skip_all, bool append, bool recursive}])` + +Copy a file or directory. + +* **Arguments** + * `src` - Item to copy + * `dst` - Destination name + * `opts` (optional) - Option flags + * `calc_sha` - Write `.sha` files containing a SHA-256 (default) or SHA-1 hash + * `sha1` - Use SHA-1 + * `no_cancel` - Don’t allow user to cancel + * `silent` - Don’t show progress + * `overwrite` - Overwrite files + * `skip_all` - Skip existing files + * `append` - Append to the end of existing files instead of overwriting + * `recursive` - Copy directories recursively +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"requested directory copy without {recursive=true} on -> "` - attempted to copy a directory without using `recursive` + * `"destination already exists on -> and {overwrite=true} was not used"` - attempted to copy an item over an existing one without using `overwrite` + * `"PathMoveCopy failed on -> "` - error when copying, or user canceled + +#### fs.mkdir + +`void fs.mkdir(string path)` + +Create a directory. This creates intermediate directories as required, so `fs.mkdir("a/b/c")` would create `a`, then `b`, then `c`. + +* **Arguments** + * `path` - Directory to create +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"could not mkdir ()"` - error when creating directories + +#### fs.stat + +`array fs.stat(string path)` + +Get information about a file or directory. The result is a stat table with these keys: + +* **Arguments** + * `path` - Directory to stat +* **Returns:** A stat table with keys: + * `name` (string) + * `type` (string) - `"dir"` or `"file"` + * `size` (number) + * `read_only` (bool) +* **Throws** + * `"could not stat (##)"` - error when attempting to stat item, with FatFs error number + +#### fs.list_dir + +`array fs.list_dir(string path)` + +Get the contents of a directory. The result is a list of stat tables with these keys: +* `name` (string) +* `type` (string) - `"dir"` or `"file"` +* `size` (number) +* `read_only` (bool) + +* **Arguments** + * `path` - Directory to list +* **Returns:** A list of stat tables, each with keys: + * `name` (string) + * `type` (string) - `"dir"` or `"file"` + * `size` (number) + * `read_only` (bool) +* **Throws** + * `"could not opendir (##)"` - error when attempting to open directory, with FatFs error number + * `"could not readdir (##)"` - error when attempting to read directory, with FatFs error number + +#### fs.stat_fs + +`array fs.stat_fs(string path)` + +Get information about a filesystem. + +> [!NOTE] +> This function can take several seconds before it returns an answer. + +* **Arguments** + * `path` - Filesystem to stat +* **Returns:** A stat table with keys: + * `free` (number) + * `total` (number) + * `used` (number) + +#### fs.dir_info + +`array fs.dir_info(string path)` + +Get information about a directory. + +> [!NOTE] +> This function can take several seconds before it returns an answer. + +* **Arguments** + * `path` - Directory to check +* **Returns:** An info table with keys: + * `size` (number) + * `dirs` (number) + * `files` (number) +* **Throws** + * `"error when running DirInfo"` - error when scanning directory + +#### fs.ask_select_file + +`string fs.ask_select_file(string prompt, string path[, bool opts {bool include_dirs, bool explorer}])` + +Prompt the user to select a file based on a pattern. Accepts a wildcard pattern like `"0:/gm9/in/*_ctrtransfer_n3ds.bin"`. + +* **Arguments** + * `prompt` - Text to ask the user + * `path` - Wildcard pattern to search for + * `opts` (optional) - Option flags + * `include_dirs` - Include directories in selection + * `explorer` - Use file explorer, including navigating subdirectories +* **Returns:** path selected, or `nil` if user canceled +* **Throws** + * `"forbidden drive"` - attempted search on `Z:` + * `"invalid path"` - could not find `/` in path + +#### fs.ask_select_dir + +`string fs.ask_select_dir(string prompt, string path[, bool opts {bool explorer}])` + +Prompt the user to select a directory. + +* **Arguments** + * `prompt` - Text to ask the user + * `path` - Directory to search + * `opts` (optional) - Option flags + * `explorer` - Use file explorer, including navigating subdirectories +* **Returns:** path selected, or `nil` if user canceled +* **Throws** + * `"forbidden drive"` - attempted search on `Z:` + +#### fs.find + +`string fs.find(string pattern[, bool opts {bool first}])` + +Searches for a file based on a wildcard pattern. Returns the last result, unless `first` is specified. + +Pattern can use `?` for search values, for example `00017?02` will match `00017002`, `00017102`, etc. Wildcards are also accepted. + +* **Arguments** + * `pattern` - Pattern to search for + * `opts` (optional) - Option flags + * `first` - Return first result instead of last +* **Returns:** found file +* **Throws** + * `"failed to find (##)"` - error when attempting to find path, with FatFs error number + +#### fs.find_not + +`string fs.find(string pattern)` + +Searches for a free filename based on a pattern. + +Pattern can use `?` for search values, for example `nand_??.bin` will check to see if `nand_00.bin` exists. If it doesn't, it returns this string. Otherwise, it checks if `nand_01.bin` exists and keeps going until an unused filename can be found. + +* **Arguments** + * `pattern` - Pattern to search for +* **Returns:** found file +* **Throws** + * `"failed to find (##)"` - error when attempting to find path, with FatFs error number + +#### fs.find_all + +`string fs.find_all(string pattern)` + +> [!WARNING] +> Not implemented. + +#### fs.allow + +`bool fs.allow(string path[, table flags {bool ask_all}])` + +Check for and request permission to write to a sensitive path. + +* **Arguments** + * `path` - Path to request permission for + * `opts` (optional) - Option flags + * `ask_all` - Request to write to all files in directory +* **Returns:** `true` if granted, `false` if user declines + +#### fs.img_mount + +`void fs.img_mount(string path)` + +Mount an image file. Can be anything mountable through the file browser. + +* **Arguments** + * `path` - Path to image file +* **Throws** + * `"failed to mount "` - not a valid image file + +#### fs.img_umount + +`void fs.img_umount()` + +Unmount the currently mounted image file. + +#### fs.get_img_mount + +`string fs.get_img_mount()` + +Get the currently mounted image file. + +* **Returns:** path to file, or `nil` if none is mounted + +#### fs.hash_file + +`string fs.hash_file(string path, int offset, int size[, table opts {bool sha1}])` + +Calculate the hash for a file. Uses SHA-256 unless `sha1` is specified. To hash an entire file, `size` should be `0`. + +> [!TIP] +> * Use `fs.verify_with_sha_file` to compare with a corresponding `.sha` file. +> * Use `util.bytes_to_hex` to convert the result to printable hex characters. + +> [!NOTE] +> Using an offset that is not `0`, with a size of `0` (to hash to end of file), is currently undefined behavior. In the future this should work properly. + +* **Arguments** + * `path` - File to hash + * `offset` - Data offset + * `size` - Amount of data to hash, or `0` to hash to end of file + * `opts` (optional) - Option flags + * `sha1` - Use SHA-1 +* **Returns:** SHA-256 or SHA-1 hash as byte string +* **Throws** + * `"failed to stat "` - could not stat file to get size + * `"FileGetSha failed on "` - could not read file or user canceled \ No newline at end of file From 7cf69000d89f8d6e881ba7fd88951f7c92f97940 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 12:22:34 -0600 Subject: [PATCH 112/124] finished fs doc, string fixes for fs module --- arm9/source/lua/gm9internalfs.c | 10 +- resources/lua-doc.md | 217 +++++++++++++++++++++++++++++++- 2 files changed, 222 insertions(+), 5 deletions(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index a6cdc7663..237e52f84 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -167,13 +167,14 @@ static int internalfs_stat(lua_State* L) { return 1; } +// TODO: make this manually check for permissions recursively static int internalfs_fix_cmacs(lua_State* L) { CheckLuaArgCount(L, 1, "_fs.fix_cmacs"); const char* path = luaL_checkstring(L, 1); ShowString("%s", STR_FIXING_CMACS_PLEASE_WAIT); if (RecursiveFixFileCmac(path) != 0) { - return luaL_error(L, "%s", STR_SCRIPTERR_FIXCMAC_FAILED); + return luaL_error(L, "fixcmac failed"); } return 0; @@ -420,7 +421,7 @@ static int internalfs_make_dummy_file(lua_State* L) { } static int internalfs_truncate(lua_State* L) { - CheckLuaArgCount(L, 2, "_fs.write_file"); + CheckLuaArgCount(L, 2, "_fs.truncate"); const char* path = luaL_checkstring(L, 1); lua_Integer size = luaL_checkinteger(L, 2); FIL fp; @@ -484,6 +485,7 @@ static int internalfs_get_img_mount(lua_State* L) { return 1; } +// TODO: what if someone does offset != 0 but size = 0 (end of file)? static int internalfs_hash_file(lua_State* L) { bool extra = CheckLuaArgCountPlusExtra(L, 3, "_fs.hash_file"); const char* path = luaL_checkstring(L, 1); @@ -545,7 +547,7 @@ static int internalfs_hash_data(lua_State* L) { } static int internalfs_allow(lua_State* L) { - bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.img_mount"); + bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.allow"); const char* path = luaL_checkstring(L, 1); u32 flags = 0; bool allowed; @@ -608,7 +610,7 @@ static int internalfs_sd_switch(lua_State* L) { while (!((pad_state = InputWait(0)) & (BUTTON_B|SD_INSERT))); } if (pad_state & BUTTON_B) { - return luaL_error(L, "%s", STR_SCRIPTERR_USER_ABORT); + return luaL_error(L, "user canceled"); } InitSDCardFS(); diff --git a/resources/lua-doc.md b/resources/lua-doc.md index a51cc37b7..c72c7a22b 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -628,4 +628,219 @@ Calculate the hash for a file. Uses SHA-256 unless `sha1` is specified. To hash * **Returns:** SHA-256 or SHA-1 hash as byte string * **Throws** * `"failed to stat "` - could not stat file to get size - * `"FileGetSha failed on "` - could not read file or user canceled \ No newline at end of file + * `"FileGetSha failed on "` - could not read file or user canceled + +#### fs.hash_data + +`string fs.hash_data(string data[, table opts {bool sha1}])` + +Calculate the hash for some data. Uses SHA-256 unless `sha1` is specified. + +> [!TIP] +> * Use `util.bytes_to_hex` to convert the result to printable hex characters. + +* **Arguments** + * `data` - Data to hash + * `opts` (optional) - Option flags + * `sha1` - Use SHA-1 +* **Returns:** SHA-256 or SHA-1 hash as byte string + +#### fs.verify + +`bool fs.verify(string path)` + +Verify the integrity of a file. + +> [!NOTE] +> This is for files that have their own hashes built-in. For verifying against a corresponding `.sha` file, use `fs.verify_with_sha_file`. + +* **Arguments** + * `path` - File to verify +* **Returns:** `true` if successful, `false` if failed or not verifiable + +#### fs.verify_with_sha_file + +`bool fs.verify_with_sha_file(string path)` + +Calculate the hash of a file and compare it with a corresponding `.sha` file. + +> [!IMPORTANT] +> This currently assumes SHA-256. In the future this may automatically use SHA-1 when appropriate, based on the `.sha` file size. + +TODO: add errors for fs.read_file here + +* **Argumens** + * `path` - File to hash +* **Returns:** `true` if successful, `false` if failed, `nil` if `.sha` file could not be read +* **Throws** + * `"failed to stat "` - could not stat file to get size + * `"FileGetSha failed on "` - could not read file or user canceled + +#### fs.exists + +`bool fs.exists(string path)` + +Check if an item exists. + +* **Arguments** + * `path` - Path to file or directory +* **Returns:** `true` if exists, `false` otherwise + +#### fs.is_dir + +`bool fs.is_dir(string path)` + +Check if an item exists, and is a directory. + +* **Arguments** + * `path` - Path to directory +* **Returns:** `true` if exists and is a directory, `false` otherwise + +#### fs.is_file + +`bool fs.is_file(string path)` + +Check if an item exists, and is a file. + +* **Arguments** + * `path` - Path to file +* **Returns:** `true` if exists and is a file, `false` otherwise + +#### fs.sd_is_mounted + +`bool fs.sd_is_mounted()` + +Check if the SD card is mounted. + +* **Returns:** `true` if SD card is mounted + +#### fs.sd_switch + +`void fs.sd_switch([string message])` + +Prompt the user to remove and insert an SD card. + +* **Arguments** + * `message` (optional) - Text to prompt the user, defaults to `"Please switch the SD card now."` +* **Throws** + * `"user canceled"` - user canceled the switch + +#### fs.fix_cmacs + +`void fs.fix_cmacs(string path)` + +Fix CMACs for a directory. + +* **Arguments** + * `path` - Path to recursively fix CMACs for +* **Throws** + * `fixcmac failed` - user denied permission, or fixing failed + +#### fs.read_file + +`string fs.read_file(string path, int offset, int/string size)` + +Read data from a file. + +* **Arguments** + * `path` - File to read + * `offset` - Data offset + * `size` - Amount of data to read +* **Returns:** string of data +* **Throws** + * `"could not allocate memory to read file"` - out-of-memory error when attempting to create the data buffer + * `"could not read (##)"` - error when attempting to read file, with FatFs error number + +#### fs.write_file + +`int fs.write_file(string path, int offset, string data)1 + +Write data to a file. + +* **Arguments** + * `path` - File to write + * `offset` - Offset to write to, or the string `"end"` to write at the end of file + * `data` - Data to write +* **Returns:** amount of bytes written +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"error writing (##)"` - error when attempting to write file, with FatFs error number + +#### fs.fill_file + +`void fs.fill_file(string path, int offset, int size, int byte)` + +Fill a file with a specified byte. + +* **Arguments** + * `path` - File to write + * `offset` - Offset to write to + * `size` - Amount of data to write + * `byte` - Number between `0x00` and `0xFF` (`0` and `255`) indicating the byte to write + * `opts` (optional) - Option flags + * `no_cancel` - Don’t allow user to cancel +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"byte is not between 0x00 and 0xFF (got: ##)"` - byte value is not a single byte + * `"FileSetByte failed on "` - writing failed or user canceled + +#### fs.make_dummy_file + +`void fs.make_dummy_file(string path, int size)` + +Create a dummy file. + +> [!NOTE] +> The file will contain data from the unused parts of the filesystem. If you need to ensure it's clean, use `fs.fill_file`. + +* **Arguments** + * `path` - File to create + * `size` - File size to set +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"FileCreateDummy failed on "` - dummy creation failed + +#### fs.truncate + +`void fs.truncate(string path, int size)` + +Truncate a file to a specific size. + +> [!IMPORTANT] +> Does not work for virtual filesystems. + +* **Arguments** + * `path` - File to create + * `size` - File size to set +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"failed to open (note: this only works on FAT filesystems, not virtual)"` - opening file failed, or a virtual filesystem was used + * `"failed to seek on "` - seeking file failed + * `"failed to truncate on "` - truncating file failed + +#### fs.key_dump + +`void fs.key_dump(string file[, table opts {bool overwrite}])` + +Dumps title keys or seeds. Taken from both SysNAND and EmuNAND. The resulting file is saved to `0:/gm9/out`. + +* **Arguments** + * `file` - One of three supported filenames: `seeddb.bin`, `encTitleKeys.bin`, `decTitleKeys.bin` + * `opts` (optional) - Option flags + * `overwrite` - Overwrite files +* **Throws** + * `"building failed"` - building failed or file already exists and `overwrite` was not used + +#### fs.cart_dump + +`void fs.cart_dump(string path, int size[, table opts {bool encrypted}])` + +Dump the raw data from the inserted game card. No modifications are made to the data. This means for example, Card2 games will not have the save area cleared. + +* **Arguments** + * `path` - File to write data to + * `size` - Amount of data to read +* **Throws** + * `"out of memory"` - out-of-memory error when attempting to create the data buffer + * `"cart init fail"` - card is not inserted or some other failure when attempting to initialize + * `"cart dump failed or canceled"` - cart read failed or used canceled From 0996219efe4ef26df32112d770ed19536ee934b4 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 13:29:55 -0600 Subject: [PATCH 113/124] document fs.cart_dump encrypted opt, remove stat from fs.verify_with_sha_file --- data/luapackages/fs.lua | 5 +---- resources/lua-doc.md | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua index 9004b05a7..dcea14733 100644 --- a/data/luapackages/fs.lua +++ b/data/luapackages/fs.lua @@ -100,14 +100,11 @@ end function fs.verify_with_sha_file(path) local success, file_hash, sha_data, path_sha path_sha = path..".sha" - -- this error should be propagated if the main file cannot be read - stat = fs.stat(path) success, sha_data = pcall(fs.read_file, path_sha, 0, 0x20) if not success then return nil end - -- TODO: make hash_file accept an "end" parameter for size - file_hash = fs.hash_file(path, 0, stat.size) + file_hash = fs.hash_file(path, 0, 0) return file_hash == sha_data end diff --git a/resources/lua-doc.md b/resources/lua-doc.md index c72c7a22b..48e4f29ef 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -12,7 +12,7 @@ There are four ways to run Lua scripts: * Select it in the file browser and choose "`Execute Lua script`" * Place it in `0:/gm9/luascripts`, then open the HOME menu, choose "`Lua scripts...`", then choose the script * Place it in `data/luascripts`, then build GodMode9 from source - * This takes precedence over `0:/gm9/luascripts` + * This takes precedence over `0:/gm9/luascripts` * Place it at `data/autorun.lua`, then build GodMode9 from source with `SCRIPT_RUNNER=1` to automatically run it at launch ## Packages @@ -840,6 +840,8 @@ Dump the raw data from the inserted game card. No modifications are made to the * **Arguments** * `path` - File to write data to * `size` - Amount of data to read + * `opts` (optional) - Option flags + * `encrypted` - Dump game encrypted, only for DS/DSi games? * **Throws** * `"out of memory"` - out-of-memory error when attempting to create the data buffer * `"cart init fail"` - card is not inserted or some other failure when attempting to initialize From 3eef799836afd2a3a37cae62e277b08026b6c040 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 13:59:15 -0600 Subject: [PATCH 114/124] title doc --- resources/lua-doc.md | 95 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 48e4f29ef..c7f71ce8c 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -154,16 +154,16 @@ These modules work differently: ### Constants #### GM9VER -The version such as `v2.1.1-159-gff2cb913`, the same string that is shown on the main screen. +The version such as `"v2.1.1-159-gff2cb913"`, the same string that is shown on the main screen. #### SCRIPT -Path to the executed script, such as `0:/gm9/luascripts/myscript.lua`. +Path to the executed script, such as `"0:/gm9/luascripts/myscript.lua"`. #### CURRDIR -Directory of the executed script, such as `0:/gm9/luascripts`. +Directory of the executed script, such as `"0:/gm9/luascripts"`. #### GM9OUT -The value `0:/gm9/out`. +The value `"0:/gm9/out"`. #### HAX > [!WARNING] @@ -846,3 +846,90 @@ Dump the raw data from the inserted game card. No modifications are made to the * `"out of memory"` - out-of-memory error when attempting to create the data buffer * `"cart init fail"` - card is not inserted or some other failure when attempting to initialize * `"cart dump failed or canceled"` - cart read failed or used canceled + +--- + +### `title` module + +#### title.decrypt, title.encrypt + +* `void title.decrypt(string path)` +* `void title.decrypt(string path)` + +Decrypt or encrypt a title or key database, done in-place. + +* **Arguments** + * `path` - Path to title or key database +* **Throws** + * `"CryptAesKeyDb failed on "` - could not crypt key database + * `"CryptGameFile failed on "` - could not crypt title + +#### title.install + +`void title.install(string path[, table opts {bool to_emunand}])` + +Install a title. + +* **Arguments** + * `path` - File to install + * `opts` (optional) - Option flags + * `to_emunand` - Install to EmuNAND +* **Throws** + * `"InstallGameFile failed on "` - install failed or user canceled + +#### title.build_cia + +`void title.build_cia(string path[, table opts {bool legit}])` + +Build title as a CIA. Resulting file is put in `0:/gm9/out`. + +* **Arguments** + * `path` - File to build as CIA + * `opts` (optional) - Option flags + * `legit` - Build as legit CIA if possible +* **Throws** + * `"BuildCiaFromGameFile failed on "` - build failed or user canceled + +#### title.extract_code + +`void title.extract_code(string src, string dst)` + +Extracts the `.code` from a title and decompresses it. + +* **Arguments** + * `src` - File containing `.code` + * `dst` - Destination to write decompressed `.code` +* **Throws** + * `"writing not allowed: "` - user denied permission + * `" does not have code"` - invalid file type + * `"failed to extract code from "` - extraction failed + +#### title.compress_code + +`void title.compress_code(string src, string dst)` + +Compress a `.code` file. + +* **Arguments** + * `src` - Extracted `.code`, like from `title.extract_code` + * `dst` - Destination to write compressed `.code` +* **Throws** + * `"writing not allowed: "` - user denied permission + * `"failed to compress code from "` - compression failed + +#### title.apply_ips, title.apply_bps, title.apply_bpm + +* `void title.apply_ips(string patch, string src, string target)` +* `void title.apply_bps(string patch, string src, string target)` +* `void title.apply_bpm(string patch, string src, string target)` + +Apply an IPS, BPS, or BPM patch. + +* **Arguments** + * `patch` - IPS, BPS, or BPM patch file + * `src` - File to patch + * `target` - Destination to write patched file to +* **Throws** + * `"ApplyIPSPatch failed"` - failed to apply IPS patch + * `"ApplyBPSPatch failed"` - failed to apply BPS patch + * `"ApplyBPMPatch failed"` - failed to apply BPM patch \ No newline at end of file From 814933972731589e3226ae147baf58ef358555a4 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 14:27:20 -0600 Subject: [PATCH 115/124] sys doc, error string updates --- arm9/source/lua/gm9internalsys.c | 4 +- resources/lua-doc.md | 301 +++++++++++++++++++++---------- 2 files changed, 211 insertions(+), 94 deletions(-) diff --git a/arm9/source/lua/gm9internalsys.c b/arm9/source/lua/gm9internalsys.c index b73d215e9..28c2a5c4e 100644 --- a/arm9/source/lua/gm9internalsys.c +++ b/arm9/source/lua/gm9internalsys.c @@ -20,12 +20,12 @@ static int internalsys_boot(lua_State* L) { u8* firm = (u8*) malloc(FIRM_MAX_SIZE); if (!firm) { - return luaL_error(L, STR_SCRIPTERR_OUT_OF_MEMORY); + return luaL_error(L, "out of memory"); } size_t firm_size = FileGetData(path, firm, FIRM_MAX_SIZE, 0); if (!(firm_size && IsBootableFirm(firm, firm_size))) { - return luaL_error(L, STR_SCRIPTERR_NOT_A_BOOTABLE_FIRM); + return luaL_error(L, "not a bootable firm"); } char fixpath[256] = { 0 }; diff --git a/resources/lua-doc.md b/resources/lua-doc.md index c7f71ce8c..c5d416a75 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -12,7 +12,7 @@ There are four ways to run Lua scripts: * Select it in the file browser and choose "`Execute Lua script`" * Place it in `0:/gm9/luascripts`, then open the HOME menu, choose "`Lua scripts...`", then choose the script * Place it in `data/luascripts`, then build GodMode9 from source - * This takes precedence over `0:/gm9/luascripts` + * This takes precedence over `0:/gm9/luascripts` * Place it at `data/autorun.lua`, then build GodMode9 from source with `SCRIPT_RUNNER=1` to automatically run it at launch ## Packages @@ -127,8 +127,8 @@ GM9VER | GM9VER |   These original Lua 5.4 modules are fully available: * [Basic functions](https://www.lua.org/manual/5.4/manual.html#6.1) - * `print` will replace the top screen with an output log. Currently it does not implement some features such as line wrapping. - * dofile and loadfile don't work yet. + * `print` will replace the top screen with an output log. Currently it does not implement some features such as line wrapping. + * dofile and loadfile don't work yet. * [coroutine](https://www.lua.org/manual/5.4/manual.html#6.2) * [debug](https://www.lua.org/manual/5.4/manual.html#6.10) * [math](https://www.lua.org/manual/5.4/manual.html#6.7) @@ -138,14 +138,14 @@ These original Lua 5.4 modules are fully available: These modules are partially available: * [os](https://www.lua.org/manual/5.4/manual.html#6.9) - * Only `os.clock`, `os.time`, `os.date`, `os.difftime`, and `os.remove` + * Only `os.clock`, `os.time`, `os.date`, `os.difftime`, and `os.remove` * [io](https://www.lua.org/manual/5.4/manual.html#6.8) - * Only `io.open`; for open files, all but `file:setvbuf` and `file:lines` - * This is a custom compatibility module that uses `fs` functions. If there are differences compared to the original `io` implementation, please report them as issues. + * Only `io.open`; for open files, all but `file:setvbuf` and `file:lines` + * This is a custom compatibility module that uses `fs` functions. If there are differences compared to the original `io` implementation, please report them as issues. These modules work differently: * [package](https://www.lua.org/manual/5.4/manual.html#6.3) - * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. + * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. --- @@ -191,150 +191,150 @@ The string `"O3DS"` or `"N3DS"`. #### ui.echo -`void ui.echo(string text)` +* `void ui.echo(string text)` Display text on the bottom screen and wait for the user to press A. * **Arguments** - * `text` - Text to show the user + * `text` - Text to show the user #### ui.ask -`bool ui.ask(string text)` +* `bool ui.ask(string text)` Prompt the user with a yes/no question. * **Arguments** - * `text` - Text to ask the user + * `text` - Text to ask the user * **Returns:** `true` if the user accepts #### ui.ask_hex -`int ui.ask_hex(string text, int initial, int n_digits)` +* `int ui.ask_hex(string text, int initial, int n_digits)` Ask the user to input a hex number. * **Arguments** - * `text` - Text to ask the user - * `initial` - Starting value - * `n_digits` - Amount of hex digits allowed + * `text` - Text to ask the user + * `initial` - Starting value + * `n_digits` - Amount of hex digits allowed * **Returns:** the number the user entered, or `nil` if canceled #### ui.ask_number -`int ui.ask_number(string text, int initial)` +* `int ui.ask_number(string text, int initial)` Ask the user to input a number. * **Arguments** - * `text` - Text to ask the user - * `initial` - Starting value + * `text` - Text to ask the user + * `initial` - Starting value * **Returns:** the number the user entered, or `nil` if canceled #### ui.ask_text -`string ui.ask_text(string prompt, string initial, int max_length)` +* `string ui.ask_text(string prompt, string initial, int max_length)` Ask the user to input text. * **Arguments** - * `prompt` - Text to ask the user - * `initial` - Starting value - * `max_length` - Maximum length of the string + * `prompt` - Text to ask the user + * `initial` - Starting value + * `max_length` - Maximum length of the string * **Returns:** the text the user entered, or `nil` if canceled #### ui.ask_selection -`int ui.ask_selection(string prompt, array options)` +* `int ui.ask_selection(string prompt, array options)` Ask the user to choose an option from a list. A maximum of 256 options are allowed. * **Arguments** - * `prompt` - Text to ask the user - * `options` - Table of options + * `prompt` - Text to ask the user + * `options` - Table of options * **Returns:** index of selected option, or `nil` if canceled #### ui.clear -`void ui.clear()` +* `void ui.clear()` Clears the top screen. #### ui.show_png -`void ui.show_png(string path)` +* `void ui.show_png(string path)` Displays a PNG file on the top screen. The image must not be larger than 400 pixels horizontal or 240 pixels vertical. If `SWITCH_SCREENS=1` is used, it must not be larger than 320 pixels horizontal. * **Arguments** - * `path` - Path to PNG file + * `path` - Path to PNG file * **Throws** - * `"Could not read (file)"` - file does not exist or there was another read error - * `"Invalid PNG file"` - file is not a valid PNG - * `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error + * `"Could not read (file)"` - file does not exist or there was another read error + * `"Invalid PNG file"` - file is not a valid PNG + * `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error #### ui.show_text -`void ui.show_text(string text)` +* `void ui.show_text(string text)` Displays text on the top screen. * **Arguments** - * `text` - Text to show the user + * `text` - Text to show the user #### ui.show_game_info -`void ui.show_game_info(string path)` +* `void ui.show_game_info(string path)` Shows game file info. Accepts any files that include an SMDH, a DS game file, or GBA file. * **Arguments** - * `path` - Path to game file (CIA, CCI/".3ds", SMDH, TMD, TIE (DSiWare export), Ticket, NDS, GBA) + * `path` - Path to game file (CIA, CCI/".3ds", SMDH, TMD, TIE (DSiWare export), Ticket, NDS, GBA) * **Throws** - * `"ShowGameFileIcon failed on "` - failed to get game info from path + * `"ShowGameFileIcon failed on "` - failed to get game info from path #### ui.show_qr -`void ui.show_qr(string text, string data)` +* `void ui.show_qr(string text, string data)` Displays a QR code on the top screen, and a prompt on the bottom screen, and waits for the user to press A. * **Arguments** - * `text` - Text to show the user - * `data` - Data to encode into the QR code + * `text` - Text to show the user + * `data` - Data to encode into the QR code * **Throws** - * `"could not allocate memory"` - out-of-memory error when attempting to generate the QR code + * `"could not allocate memory"` - out-of-memory error when attempting to generate the QR code #### ui.show_text_viewer -`void ui.show_text_viewer(string text)` +* `void ui.show_text_viewer(string text)` Display a scrollable text viewer. * **Arguments** - * `text` - Text to display + * `text` - Text to display * **Throws** - * `"text validation failed"` - given string contains invalid characters - * `"failed to run MemTextViewer"` - internal memory viewer error + * `"text validation failed"` - given string contains invalid characters + * `"failed to run MemTextViewer"` - internal memory viewer error #### ui.show_file_text_viewer -`void ui.show_file_text_viewer(string path)` +* `void ui.show_file_text_viewer(string path)` Display a scrollable text viewer from a text file. * **Arguments** - * `path` - Path to text file + * `path` - Path to text file * **Throws** - * `"could not allocate memory"` - out-of-memory error when attempting to create the text buffer - * `"text validation failed"` - text file contains invalid characters - * `"failed to run MemTextViewer"` - internal memory viewer error + * `"could not allocate memory"` - out-of-memory error when attempting to create the text buffer + * `"text validation failed"` - text file contains invalid characters + * `"failed to run MemTextViewer"` - internal memory viewer error #### ui.format_bytes -`string ui.format_bytes(int bytes)` +* `string ui.format_bytes(int bytes)` Format a number with `Byte`, `kB`, `MB`, or `GB`. @@ -342,17 +342,17 @@ Format a number with `Byte`, `kB`, `MB`, or `GB`. > This is affected by localization and may return different text if the language is not English. * **Arguments** - * `bytes` - Size to format + * `bytes` - Size to format * **Returns:** formatted string #### ui.check_key -`bool ui.check_key(string key)` +* `bool ui.check_key(string key)` Checks if the user is holding down a key. * **Arguments** - * `key` - A button string: `"A"`, `"B"`, `"SELECT"`, `"START"`, `"RIGHT"`, `"LEFT"`, `"UP"`, `"DOWN"`, `"R"`, `"L"`, `"X"`, `"Y"` + * `key` - A button string: `"A"`, `"B"`, `"SELECT"`, `"START"`, `"RIGHT"`, `"LEFT"`, `"UP"`, `"DOWN"`, `"R"`, `"L"`, `"X"`, `"Y"` * **Returns:** `true` if currently held, `false` if not --- @@ -361,7 +361,7 @@ Checks if the user is holding down a key. #### fs.move -`void fs.move(string src, string dst[, table opts {bool no_cancel, bool silent, bool overwrite, bool skip_all}])` +* `void fs.move(string src, string dst[, table opts {bool no_cancel, bool silent, bool overwrite, bool skip_all}])` Moves or renames a file or directory. @@ -380,7 +380,7 @@ Moves or renames a file or directory. #### fs.remove -`void fs.remove(string path[, table opts {bool recursive}])` +* `void fs.remove(string path[, table opts {bool recursive}])` Delete a file or directory. @@ -395,7 +395,7 @@ Delete a file or directory. #### fs.copy -`void fs.copy(string src, string dst[, table opts {bool calc_sha, bool sha1, bool no_cancel, bool silent, bool overwrite, bool skip_all, bool append, bool recursive}])` +* `void fs.copy(string src, string dst[, table opts {bool calc_sha, bool sha1, bool no_cancel, bool silent, bool overwrite, bool skip_all, bool append, bool recursive}])` Copy a file or directory. @@ -419,7 +419,7 @@ Copy a file or directory. #### fs.mkdir -`void fs.mkdir(string path)` +* `void fs.mkdir(string path)` Create a directory. This creates intermediate directories as required, so `fs.mkdir("a/b/c")` would create `a`, then `b`, then `c`. @@ -431,7 +431,7 @@ Create a directory. This creates intermediate directories as required, so `fs.mk #### fs.stat -`array fs.stat(string path)` +* `array fs.stat(string path)` Get information about a file or directory. The result is a stat table with these keys: @@ -447,7 +447,7 @@ Get information about a file or directory. The result is a stat table with these #### fs.list_dir -`array fs.list_dir(string path)` +* `array fs.list_dir(string path)` Get the contents of a directory. The result is a list of stat tables with these keys: * `name` (string) @@ -468,7 +468,7 @@ Get the contents of a directory. The result is a list of stat tables with these #### fs.stat_fs -`array fs.stat_fs(string path)` +* `array fs.stat_fs(string path)` Get information about a filesystem. @@ -484,7 +484,7 @@ Get information about a filesystem. #### fs.dir_info -`array fs.dir_info(string path)` +* `array fs.dir_info(string path)` Get information about a directory. @@ -502,7 +502,7 @@ Get information about a directory. #### fs.ask_select_file -`string fs.ask_select_file(string prompt, string path[, bool opts {bool include_dirs, bool explorer}])` +* `string fs.ask_select_file(string prompt, string path[, bool opts {bool include_dirs, bool explorer}])` Prompt the user to select a file based on a pattern. Accepts a wildcard pattern like `"0:/gm9/in/*_ctrtransfer_n3ds.bin"`. @@ -519,7 +519,7 @@ Prompt the user to select a file based on a pattern. Accepts a wildcard pattern #### fs.ask_select_dir -`string fs.ask_select_dir(string prompt, string path[, bool opts {bool explorer}])` +* `string fs.ask_select_dir(string prompt, string path[, bool opts {bool explorer}])` Prompt the user to select a directory. @@ -534,7 +534,7 @@ Prompt the user to select a directory. #### fs.find -`string fs.find(string pattern[, bool opts {bool first}])` +* `string fs.find(string pattern[, bool opts {bool first}])` Searches for a file based on a wildcard pattern. Returns the last result, unless `first` is specified. @@ -550,7 +550,7 @@ Pattern can use `?` for search values, for example `00017?02` will match `000170 #### fs.find_not -`string fs.find(string pattern)` +* `string fs.find(string pattern)` Searches for a free filename based on a pattern. @@ -564,14 +564,14 @@ Pattern can use `?` for search values, for example `nand_??.bin` will check to s #### fs.find_all -`string fs.find_all(string pattern)` +* `string fs.find_all(string pattern)` > [!WARNING] > Not implemented. #### fs.allow -`bool fs.allow(string path[, table flags {bool ask_all}])` +* `bool fs.allow(string path[, table flags {bool ask_all}])` Check for and request permission to write to a sensitive path. @@ -583,7 +583,7 @@ Check for and request permission to write to a sensitive path. #### fs.img_mount -`void fs.img_mount(string path)` +* `void fs.img_mount(string path)` Mount an image file. Can be anything mountable through the file browser. @@ -594,13 +594,13 @@ Mount an image file. Can be anything mountable through the file browser. #### fs.img_umount -`void fs.img_umount()` +* `void fs.img_umount()` Unmount the currently mounted image file. #### fs.get_img_mount -`string fs.get_img_mount()` +* `string fs.get_img_mount()` Get the currently mounted image file. @@ -608,7 +608,7 @@ Get the currently mounted image file. #### fs.hash_file -`string fs.hash_file(string path, int offset, int size[, table opts {bool sha1}])` +* `string fs.hash_file(string path, int offset, int size[, table opts {bool sha1}])` Calculate the hash for a file. Uses SHA-256 unless `sha1` is specified. To hash an entire file, `size` should be `0`. @@ -632,7 +632,7 @@ Calculate the hash for a file. Uses SHA-256 unless `sha1` is specified. To hash #### fs.hash_data -`string fs.hash_data(string data[, table opts {bool sha1}])` +* `string fs.hash_data(string data[, table opts {bool sha1}])` Calculate the hash for some data. Uses SHA-256 unless `sha1` is specified. @@ -647,7 +647,7 @@ Calculate the hash for some data. Uses SHA-256 unless `sha1` is specified. #### fs.verify -`bool fs.verify(string path)` +* `bool fs.verify(string path)` Verify the integrity of a file. @@ -660,7 +660,7 @@ Verify the integrity of a file. #### fs.verify_with_sha_file -`bool fs.verify_with_sha_file(string path)` +* `bool fs.verify_with_sha_file(string path)` Calculate the hash of a file and compare it with a corresponding `.sha` file. @@ -678,7 +678,7 @@ TODO: add errors for fs.read_file here #### fs.exists -`bool fs.exists(string path)` +* `bool fs.exists(string path)` Check if an item exists. @@ -688,7 +688,7 @@ Check if an item exists. #### fs.is_dir -`bool fs.is_dir(string path)` +* `bool fs.is_dir(string path)` Check if an item exists, and is a directory. @@ -698,7 +698,7 @@ Check if an item exists, and is a directory. #### fs.is_file -`bool fs.is_file(string path)` +* `bool fs.is_file(string path)` Check if an item exists, and is a file. @@ -708,7 +708,7 @@ Check if an item exists, and is a file. #### fs.sd_is_mounted -`bool fs.sd_is_mounted()` +* `bool fs.sd_is_mounted()` Check if the SD card is mounted. @@ -716,7 +716,7 @@ Check if the SD card is mounted. #### fs.sd_switch -`void fs.sd_switch([string message])` +* `void fs.sd_switch([string message])` Prompt the user to remove and insert an SD card. @@ -727,7 +727,7 @@ Prompt the user to remove and insert an SD card. #### fs.fix_cmacs -`void fs.fix_cmacs(string path)` +* `void fs.fix_cmacs(string path)` Fix CMACs for a directory. @@ -738,7 +738,7 @@ Fix CMACs for a directory. #### fs.read_file -`string fs.read_file(string path, int offset, int/string size)` +* `string fs.read_file(string path, int offset, int/string size)` Read data from a file. @@ -753,7 +753,7 @@ Read data from a file. #### fs.write_file -`int fs.write_file(string path, int offset, string data)1 +* `int fs.write_file(string path, int offset, string data)1 Write data to a file. @@ -768,7 +768,7 @@ Write data to a file. #### fs.fill_file -`void fs.fill_file(string path, int offset, int size, int byte)` +* `void fs.fill_file(string path, int offset, int size, int byte)` Fill a file with a specified byte. @@ -786,7 +786,7 @@ Fill a file with a specified byte. #### fs.make_dummy_file -`void fs.make_dummy_file(string path, int size)` +* `void fs.make_dummy_file(string path, int size)` Create a dummy file. @@ -802,7 +802,7 @@ Create a dummy file. #### fs.truncate -`void fs.truncate(string path, int size)` +* `void fs.truncate(string path, int size)` Truncate a file to a specific size. @@ -820,7 +820,7 @@ Truncate a file to a specific size. #### fs.key_dump -`void fs.key_dump(string file[, table opts {bool overwrite}])` +* `void fs.key_dump(string file[, table opts {bool overwrite}])` Dumps title keys or seeds. Taken from both SysNAND and EmuNAND. The resulting file is saved to `0:/gm9/out`. @@ -833,7 +833,7 @@ Dumps title keys or seeds. Taken from both SysNAND and EmuNAND. The resulting fi #### fs.cart_dump -`void fs.cart_dump(string path, int size[, table opts {bool encrypted}])` +* `void fs.cart_dump(string path, int size[, table opts {bool encrypted}])` Dump the raw data from the inserted game card. No modifications are made to the data. This means for example, Card2 games will not have the save area cleared. @@ -866,7 +866,7 @@ Decrypt or encrypt a title or key database, done in-place. #### title.install -`void title.install(string path[, table opts {bool to_emunand}])` +* `void title.install(string path[, table opts {bool to_emunand}])` Install a title. @@ -879,7 +879,7 @@ Install a title. #### title.build_cia -`void title.build_cia(string path[, table opts {bool legit}])` +* `void title.build_cia(string path[, table opts {bool legit}])` Build title as a CIA. Resulting file is put in `0:/gm9/out`. @@ -892,7 +892,7 @@ Build title as a CIA. Resulting file is put in `0:/gm9/out`. #### title.extract_code -`void title.extract_code(string src, string dst)` +* `void title.extract_code(string src, string dst)` Extracts the `.code` from a title and decompresses it. @@ -906,7 +906,7 @@ Extracts the `.code` from a title and decompresses it. #### title.compress_code -`void title.compress_code(string src, string dst)` +* `void title.compress_code(string src, string dst)` Compress a `.code` file. @@ -932,4 +932,121 @@ Apply an IPS, BPS, or BPM patch. * **Throws** * `"ApplyIPSPatch failed"` - failed to apply IPS patch * `"ApplyBPSPatch failed"` - failed to apply BPS patch - * `"ApplyBPMPatch failed"` - failed to apply BPM patch \ No newline at end of file + * `"ApplyBPMPatch failed"` - failed to apply BPM patch + +--- + +### `sys` module + +#### sys.boot + +* `void sys.boot(string path)` + +Boot a FIRM file. + +* **Arguments** + * `path` - FIRM file +* **Throws** + * `"out of memory"` - out-of-memory error when attempting to create the data buffer + * `"not a bootable firm"` - file cannot be booted + +#### sys.reboot + +* `void sys.reboot()` + +Reboots the console. + +#### sys.power_off + +* `void sys.power_off()` + +Powers off the console. + +#### sys.region + +* `string sys.region` + +System region, based on SysNAND's `SecureInfo_[AB]` file. Not affected by `SecureInfo_C`. + +Possible values: `"JPN"`, `"USA"`, `"EUR"`, `"AUS"`, `"CHN"`, `"KOR"`, `"TWN"`, `nil` (if it does not exist, or the region byte is invalid) + +#### sys.serial + +* `string sys.serial` + +Serial number, based on SysNAND's `SecureInfo_[AB]` file. Not affected by `SecureInfo_C`. + +Can be `nil` if the file does not exist. + +#### sys.secureinfo_letter + +* `string sys.secureinfo_letter` + +The letter at the end of the console's `SecureInfo_[AB]` file. + +Possible values: `"A"`, `"B"`, `nil` (if it does not exist) + +#### sys.sys_id0 + +* `string sys.sys_id0` + +ID0 of SysNAND as a printable hex string. + +Can be `nil` if the file does not exist. + +#### sys.emu_id0 + +* `string sys.emu_id0` + +ID0 of EmuNAND as a printable hex string. + +Can be `nil` if the file does not exist. + +#### sys.emu_base + +* `int sys.emu_base` + +Current EmuNAND base. + +#### sys.refresh_info + +* `void sys.refresh_info()` + +Refresh the following variables: + +* `sys.region` +* `sys.serial` +* `sys.secureinfo_letter` +* `sys.sys_id0` +* `sys.emu_id0` +* `sys.emu_base` + +This function is automatically called at the beginning of Lua's execution, but errors are caught to prevent a premature exit. This could leave some of the variables at `nil`. + +* **Throws** + * `"could not read SecureInfo"` - `SecureInfo_[AB]` is missing + * `"SecureInfo region byte is invalid"` - `SecureInfo_[AB]` is not between 0 and 6 inclusive + +#### sys.next_emu + +* `void sys.next_emu()` + +Switch to the next available EmuNAND. + +This will automatically call `sys.refresh_info`. + +#### sys.check_embedded_backup + +* `bool sys.check_embedded_backup()` + +Check if `essential.exefs` is embedded into SysNAND, and prompts the user if it isn't. This is the same as the check that is done at boot for GodMode9, but this can be called in a build compiled with `SCRIPT_RUNNER=1` for autorun scripts that require `essential.exefs` to exist. + +* **Returns:** `true` if it exists or was created, `false` if user declines, `nil` if console fails genuine NCSD check (modified partition table) + +#### sys.check_raw_rtc + +* `bool sys.check_raw_rtc()` + +Check if the Raw RTC is set correctly, and prompts the user if it isn't. This is the same as the check that is done at boot for GodMode9, but this can be called in a build compiled with `SCRIPT_RUNNER=1` for autorun scripts that require `essential.exefs` to exist. + +* **Returns:** `true` if set, `false` if user declines \ No newline at end of file From 328751c4cc522c72424facc7e074a4306e53b25b Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Fri, 13 Dec 2024 14:36:40 -0600 Subject: [PATCH 116/124] util doc --- resources/lua-doc.md | 59 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index c5d416a75..42813fb81 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -1049,4 +1049,61 @@ Check if `essential.exefs` is embedded into SysNAND, and prompts the user if it Check if the Raw RTC is set correctly, and prompts the user if it isn't. This is the same as the check that is done at boot for GodMode9, but this can be called in a build compiled with `SCRIPT_RUNNER=1` for autorun scripts that require `essential.exefs` to exist. -* **Returns:** `true` if set, `false` if user declines \ No newline at end of file +* **Returns:** `true` if set, `false` if user declines + +--- + +### `util` module + +#### util.bytes_to_hex + +* `string util.bytes_to_hex(string data)` + +Convert a byte string to printable hex characters. + +Example: `"test\xaa\xbb\xcc\xdd"` -> `"74657374aabbccdd"` + +* **Arguments** + * `data` - Data to convert +* **Returns:** Hex characters + +#### util.hex_to_bytes + +* `string util.hex_to_bytes(string hexstring)` + +Convert hex characters to a byte string. + +Example: `"74657374aabbccdd"` -> `"test\xaa\xbb\xcc\xdd"` + +* **Arguments** + * `hexstring` - Hex characters to convert +* **Returns:** Byte string +* **Throws** + * `"bad argument #1 to 'char' (number expected, got nil)"` - invalid hex characters + +#### util.get_datestamp + +* `string util.get_datestamp()` + +Returns a date stamp formatted like "241202" for 2024 December 02. Equivalent to `os.date("%y%m%d")`. + +* **Returns:** Date stamp + +#### util.get_timestamp + +* `string util.get_timestamp()` + +Returns a date stamp formatted like "010828" for 01:08:28. Equivalent to `os.date("%H%M%S")`. + +* **Returns:** Time stamp + +#### util.running_as_module + +* `bool util.running_as_module()` + +Determines if the currently executing script was directly run, or was imported by another script. Useful for scripts that want to be usable directly, while also importable. + +> [!NOTE] +> This is not well tested. + +* **Returns:** `true` if the current script was imported as a module \ No newline at end of file From 5c51e93cd1860ddbce99be3b38d7cc8392b70b08 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sat, 14 Dec 2024 02:03:50 -0600 Subject: [PATCH 117/124] add json.lua to lua-doc --- resources/lua-doc.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 42813fb81..311278fb0 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -147,6 +147,14 @@ These modules work differently: * [package](https://www.lua.org/manual/5.4/manual.html#6.3) * `package.cpath` and `package.loadlib` are nonfunctional due to GM9 having no ability to load dynamic libraries. +## Third-party libraries + +rxi's [json.lua](https://github.com/rxi/json.lua) is pre-included. To use it, first load it: + +```lua +local json = require("json") +``` + --- ## API reference @@ -1106,4 +1114,4 @@ Determines if the currently executing script was directly run, or was imported b > [!NOTE] > This is not well tested. -* **Returns:** `true` if the current script was imported as a module \ No newline at end of file +* **Returns:** `true` if the current script was imported as a module From 10cfae4bd4a9713b613c38f68055614f5f5431c1 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 15 Dec 2024 00:43:44 -0600 Subject: [PATCH 118/124] add fs.find_all --- arm9/source/lua/gm9internalfs.c | 42 +++++++++++++++++++++++++++++++++ arm9/source/utils/scripting.h | 2 ++ data/luapackages/fs.lua | 1 + 3 files changed, 45 insertions(+) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 237e52f84..3015ebf9e 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -10,6 +10,8 @@ #include "game.h" #include "gamecart.h" +#define _MAX_FOR_DEPTH 16 + static u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH }; static u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH }; @@ -294,6 +296,45 @@ static int internalfs_find(lua_State* L) { return 1; } +// this should probably be rewritten +static int internalfs_find_all(lua_State* L) { + bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.find_all"); + const char* dir = luaL_checkstring(L, 1); + const char* pattern = luaL_checkstring(L, 2); + + u32 flags = 0; + if (extra) { + flags = GetFlagsFromTable(L, 3, flags, RECURSIVE); + } + + char forpath[_VAR_CNT_LEN] = { 0 }; + + bool forstatus = for_handler(NULL, dir, pattern, flags & RECURSIVE); + if (!forstatus) { + return luaL_error(L, "could not open directory"); + } + + lua_newtable(L); + int i = 1; + + while (true) { + forstatus = for_handler(forpath, NULL, NULL, false); + if (!forstatus) { + forpath[0] = '\0'; + } + if (!forpath[0]) { + // finish for_handler + for_handler(NULL, NULL, NULL, false); + break; + } else { + lua_pushstring(L, forpath); + lua_seti(L, -2, i++); + } + } + + return 1; +} + static int internalfs_find_not(lua_State* L) { CheckLuaArgCount(L, 1, "_fs.find_not"); const char* pattern = luaL_checkstring(L, 1); @@ -715,6 +756,7 @@ static const luaL_Reg internalfs_lib[] = { {"ask_select_file", internalfs_ask_select_file}, {"ask_select_dir", internalfs_ask_select_dir}, {"find", internalfs_find}, + {"find_all", internalfs_find_all}, {"find_not", internalfs_find_not}, {"exists", internalfs_exists}, {"is_dir", internalfs_is_dir}, diff --git a/arm9/source/utils/scripting.h b/arm9/source/utils/scripting.h index ad1aa5267..c76d07627 100644 --- a/arm9/source/utils/scripting.h +++ b/arm9/source/utils/scripting.h @@ -5,6 +5,8 @@ #define SCRIPT_EXT "gm9" #define SCRIPT_MAX_SIZE STD_BUFFER_SIZE +bool for_handler(char* path, const char* dir, const char* pattern, bool recursive); + bool ValidateText(const char* text, u32 size); bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script); bool MemToCViewer(const char* text, u32 len, const char* title); diff --git a/data/luapackages/fs.lua b/data/luapackages/fs.lua index dcea14733..e223beb85 100644 --- a/data/luapackages/fs.lua +++ b/data/luapackages/fs.lua @@ -11,6 +11,7 @@ fs.dir_info = _fs.dir_info fs.ask_select_file = _fs.ask_select_file fs.ask_select_dir = _fs.ask_select_dir fs.find = _fs.find +fs.find_all = _fs.find_all fs.find_not = _fs.find_not fs.exists = _fs.exists fs.is_dir = _fs.is_dir From 359961234ef49cec4d6da1f27bedecd4a27aa2b6 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 15 Dec 2024 00:43:59 -0600 Subject: [PATCH 119/124] add 3dstool to flake --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index 6d4eeb866..8c8d9ada7 100644 --- a/flake.nix +++ b/flake.nix @@ -15,6 +15,7 @@ ( python3Packages.callPackage ./firmtool.nix { } ) lua54Packages.lua hax-nur.packages.x86_64-linux.ctrtool + hax-nur.packages.x86_64-linux._3dstool ]; inherit (pkgs.devkitNix.devkitARM) shellHook; From b716e460d4f2b8b3332d4c1e25c846fb5f0cdaf8 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 15 Dec 2024 01:07:45 -0600 Subject: [PATCH 120/124] make fs.find_all recursive actually recursive, document fs.find_all --- arm9/source/lua/gm9internalfs.c | 14 +++++++++++--- resources/lua-doc.md | 12 +++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 3015ebf9e..9843a44e4 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -309,13 +309,17 @@ static int internalfs_find_all(lua_State* L) { char forpath[_VAR_CNT_LEN] = { 0 }; - bool forstatus = for_handler(NULL, dir, pattern, flags & RECURSIVE); + // without re-implementing for_handler, i need to give it a "*" pattern + // and then manually compare each filename to see if it matches + // so that a recursive search actually works + bool forstatus = for_handler(NULL, dir, "*", flags & RECURSIVE); if (!forstatus) { return luaL_error(L, "could not open directory"); } lua_newtable(L); int i = 1; + char* slash; while (true) { forstatus = for_handler(forpath, NULL, NULL, false); @@ -327,8 +331,12 @@ static int internalfs_find_all(lua_State* L) { for_handler(NULL, NULL, NULL, false); break; } else { - lua_pushstring(L, forpath); - lua_seti(L, -2, i++); + slash = strrchr(forpath, '/'); + if (!slash) bkpt; // this should never, ever happen + if (fvx_match_name(slash+1, pattern) == FR_OK) { + lua_pushstring(L, forpath); + lua_seti(L, -2, i++); + } } } diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 311278fb0..98eed5396 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -572,10 +572,16 @@ Pattern can use `?` for search values, for example `nand_??.bin` will check to s #### fs.find_all -* `string fs.find_all(string pattern)` +* `string fs.find_all(string dir, string pattern[, table opts {bool recursive}])` -> [!WARNING] -> Not implemented. +Search for all files that match a pattern. +* **Arguments** + * `dir` - Directory to search + * `pattern` - Filename pattern + * `opts` (optional) - Option flags + * `recursive` - Remove directories recursively +* **Throws** + * `"could not open directory"` - failed to open directory #### fs.allow From 5da469da75411a44e6cf442072f794bf876b8a64 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 15 Dec 2024 01:16:19 -0600 Subject: [PATCH 121/124] add "for" to comparison table --- resources/lua-doc.md | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 98eed5396..145bcde6b 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -40,6 +40,7 @@ GM9 | Lua | Notes -- | -- | -- goto | http://lua-users.org/wiki/GotoStatement |   labelsel | ui.ask_selection |   +for | fs.find_all | Recursive searching does not work exactly the same, it will search for the filename in all directories keychk | ui.check_key |   echo | ui.echo |   qr | ui.show_qr |   From 7308ddc172897b0ce8e0e8c360c98325497b4b1d Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 15 Dec 2024 16:25:56 -0600 Subject: [PATCH 122/124] change fs.find to return nil if no path was found, instead of raising an error --- arm9/source/lua/gm9internalfs.c | 8 +++++--- resources/lua-doc.md | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/arm9/source/lua/gm9internalfs.c b/arm9/source/lua/gm9internalfs.c index 9843a44e4..104195586 100644 --- a/arm9/source/lua/gm9internalfs.c +++ b/arm9/source/lua/gm9internalfs.c @@ -288,11 +288,13 @@ static int internalfs_find(lua_State* L) { u8 mode = (flags & FIND_FIRST) ? FN_LOWEST : FN_HIGHEST; FRESULT res = fvx_findpath(path, pattern, mode); - if (res != FR_OK) { + if (res == FR_NO_PATH) { + lua_pushnil(L); + } else if (res != FR_OK) { return luaL_error(L, "failed to find %s (%d)", pattern, res); + } else { + lua_pushstring(L, path); } - - lua_pushstring(L, path); return 1; } diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 145bcde6b..8832b1965 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -545,7 +545,7 @@ Prompt the user to select a directory. * `string fs.find(string pattern[, bool opts {bool first}])` -Searches for a file based on a wildcard pattern. Returns the last result, unless `first` is specified. +Searches for a file based on a wildcard pattern. Returns the last result, unless `first` is specified, or `nil` if nothing was found. Pattern can use `?` for search values, for example `00017?02` will match `00017002`, `00017102`, etc. Wildcards are also accepted. @@ -553,7 +553,7 @@ Pattern can use `?` for search values, for example `00017?02` will match `000170 * `pattern` - Pattern to search for * `opts` (optional) - Option flags * `first` - Return first result instead of last -* **Returns:** found file +* **Returns:** found file, or `nil` if nothing is found * **Throws** * `"failed to find (##)"` - error when attempting to find path, with FatFs error number From 2524e7707708e9818162c31f9f004b6301a3061b Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 15 Dec 2024 19:17:26 -0600 Subject: [PATCH 123/124] change ui.echo to automatically word wrap (untested) --- arm9/source/lua/gm9ui.c | 15 +++++++++++++-- resources/lua-doc.md | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index 09df39529..cbea77e35 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -41,9 +41,20 @@ void RenderOutputBuffer(void) { static int ui_echo(lua_State* L) { CheckLuaArgCount(L, 1, "ui.echo"); - const char* text = lua_tostring(L, 1); + size_t textlen = 0; + const char* text = lua_tolstring(L, 1, &textlen); + + char* textbuf = malloc(textlen); + if (!textbuf) { + // huh??? + return luaL_error(L, "out of memory"); + } + strncpy(textbuf, text, textlen); + + WordWrapString(textbuf, 0); - ShowPrompt(false, "%s", text); + ShowPrompt(false, "%s", textbuf); + free(textbuf); return 0; } diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 8832b1965..8b652d40d 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -202,7 +202,7 @@ The string `"O3DS"` or `"N3DS"`. * `void ui.echo(string text)` -Display text on the bottom screen and wait for the user to press A. +Display text on the bottom screen and wait for the user to press A. Text is automatically wrapped. * **Arguments** * `text` - Text to show the user From 01e2b536f78829fd97acf77cb9d16f60599e5c79 Mon Sep 17 00:00:00 2001 From: ihaveahax Date: Sun, 15 Dec 2024 19:22:13 -0600 Subject: [PATCH 124/124] Revert "change ui.echo to automatically word wrap (untested)" This reverts commit 2524e7707708e9818162c31f9f004b6301a3061b. --- arm9/source/lua/gm9ui.c | 15 ++------------- resources/lua-doc.md | 2 +- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/arm9/source/lua/gm9ui.c b/arm9/source/lua/gm9ui.c index cbea77e35..09df39529 100644 --- a/arm9/source/lua/gm9ui.c +++ b/arm9/source/lua/gm9ui.c @@ -41,20 +41,9 @@ void RenderOutputBuffer(void) { static int ui_echo(lua_State* L) { CheckLuaArgCount(L, 1, "ui.echo"); - size_t textlen = 0; - const char* text = lua_tolstring(L, 1, &textlen); - - char* textbuf = malloc(textlen); - if (!textbuf) { - // huh??? - return luaL_error(L, "out of memory"); - } - strncpy(textbuf, text, textlen); - - WordWrapString(textbuf, 0); + const char* text = lua_tostring(L, 1); - ShowPrompt(false, "%s", textbuf); - free(textbuf); + ShowPrompt(false, "%s", text); return 0; } diff --git a/resources/lua-doc.md b/resources/lua-doc.md index 8b652d40d..8832b1965 100644 --- a/resources/lua-doc.md +++ b/resources/lua-doc.md @@ -202,7 +202,7 @@ The string `"O3DS"` or `"N3DS"`. * `void ui.echo(string text)` -Display text on the bottom screen and wait for the user to press A. Text is automatically wrapped. +Display text on the bottom screen and wait for the user to press A. * **Arguments** * `text` - Text to show the user