sm64coopdx/src/pc/lua/smlua.c
Isaac0-dev 4aa2a20f72
fixes, clean up and some new features (#259)
- Only inited `smlua_audio_utils` if needed, since there will probably be plenty of people who play without ever using mods with custom audio.
- Fixed the pause anywhere setting - this ended up not being fully accurate to ex-coop, which it was originally intended to be.
- Stopped regenerating dynos collision bin on start up every time therefore giving a slight start up speed boost for some people.
- Added a config file setting that lets people choose to compress dynos bins on startup (disabled by default).
- Fixed a warning that shows on non macs during compiling.
- Properly fixed the chat box focus.
- Made the public lobby rules panel "temporary".
- Added a cleaner loading screen design.
- Added an ex-coop theme easter egg, can only be activated from the config file.
- Cleaned up the Lua traceback logging, now shows the folder and file the error occurred in, rather than the full path.
- Added a way to set `gCheckingSurfaceCollisionsForCamera`, so mods can specify to surface finding functions to ignore `SURFACE_FLAG_NO_CAM_COLLISION` internally.
- Rewrote the way smlua pushes CObjects/CPointers to Lua. Now using the C Lua API entirely to connect to Lua.
	- Fixed a use-after-free bug that could easily crash the game through Lua (explained further in one of my comments below).
- Fixed a common crash in `audio_sanity_check`.
2024-09-12 19:09:01 +10:00

380 lines
13 KiB
C

#include "smlua.h"
#include "smlua_cobject_map.h"
#include "game/hardcoded.h"
#include "pc/mods/mods.h"
#include "pc/mods/mods_utils.h"
#include "pc/crash_handler.h"
#include "pc/lua/utils/smlua_text_utils.h"
#include "pc/lua/utils/smlua_audio_utils.h"
#include "pc/lua/utils/smlua_model_utils.h"
#include "pc/lua/utils/smlua_level_utils.h"
#include "pc/lua/utils/smlua_anim_utils.h"
#include "pc/djui/djui.h"
#include "pc/fs/fmem.h"
lua_State* gLuaState = NULL;
u8 gLuaInitializingScript = 0;
u8 gSmLuaSuppressErrors = 0;
struct Mod* gLuaLoadingMod = NULL;
struct Mod* gLuaActiveMod = NULL;
struct Mod* gLuaLastHookMod = NULL;
void smlua_mod_error(void) {
struct Mod* mod = gLuaActiveMod;
if (mod == NULL) { mod = gLuaLastHookMod; }
if (mod == NULL) { return; }
char txt[255] = { 0 };
snprintf(txt, 254, "'%s\\#ff0000\\' has script errors!", mod->name);
static const struct DjuiColor color = { 255, 0, 0, 255 };
djui_lua_error(txt, color);
}
void smlua_mod_warning(void) {
struct Mod* mod = gLuaActiveMod;
if (mod == NULL) { mod = gLuaLastHookMod; }
if (mod == NULL) { return; }
if (mod->ignoreScriptWarnings) { return; }
char txt[255] = { 0 };
snprintf(txt, 254, "'%s\\#ffe600\\' has script warnings!", mod->name);
static const struct DjuiColor color = { 255, 230, 0, 255 };
djui_lua_error(txt, color);
}
int smlua_error_handler(lua_State* L) {
if (lua_type(L, -1) == LUA_TSTRING) {
LOG_LUA("%s", lua_tostring(L, -1));
}
smlua_logline();
smlua_dump_stack();
return 0;
}
int smlua_pcall(lua_State* L, int nargs, int nresults, UNUSED int errfunc) {
gSmLuaConvertSuccess = true;
lua_pushcfunction(L, smlua_error_handler);
int errorHandlerIndex = 1;
lua_insert(L, errorHandlerIndex);
int rc = lua_pcall(L, nargs, nresults, errorHandlerIndex);
lua_remove(L, errorHandlerIndex);
return rc;
}
void smlua_exec_file(const char* path) {
lua_State* L = gLuaState;
if (luaL_dofile(L, path) != LUA_OK) {
LOG_LUA("Failed to load lua file '%s'.", path);
LOG_LUA("%s", smlua_to_string(L, lua_gettop(L)));
}
lua_pop(L, lua_gettop(L));
}
void smlua_exec_str(const char* str) {
lua_State* L = gLuaState;
if (luaL_dostring(L, str) != LUA_OK) {
LOG_LUA("Failed to load lua string.");
LOG_LUA("%s", smlua_to_string(L, lua_gettop(L)));
}
lua_pop(L, lua_gettop(L));
}
#define LUA_BOM_11 0x0000000000005678llu
#define LUA_BOM_19 0x4077280000000000llu
static bool smlua_check_binary_header(struct ModFile *file) {
FILE *f = f_open_r(file->cachedPath);
if (f) {
// Read signature
char signature[sizeof(LUA_SIGNATURE)] = { 0 };
if (f_read(signature, 1, sizeof(LUA_SIGNATURE) - 1, f) != sizeof(LUA_SIGNATURE) - 1) {
LOG_LUA("Failed to load lua script '%s': File too short.", file->cachedPath);
f_close(f);
f_delete(f);
return false;
}
// Check signature
if (strcmp(signature, LUA_SIGNATURE) != 0) {
f_close(f);
return true; // Not a binary lua
}
// Read version number
u8 version;
if (f_read(&version, 1, 1, f) != 1) {
LOG_LUA("Failed to load lua script '%s': File too short.", file->cachedPath);
f_close(f);
f_delete(f);
return false;
}
// Check version number
u8 expectedVersion = strtoul(LUA_VERSION_MAJOR LUA_VERSION_MINOR, NULL, 16);
if (version != expectedVersion) {
LOG_LUA("Failed to load lua script '%s': Lua versions don't match (%X, expected %X).", file->cachedPath, version, expectedVersion);
f_close(f);
f_delete(f);
return false;
}
// Read the rest of the header
u8 header[28];
if (f_read(header, 1, 28, f) != 28) {
LOG_LUA("Failed to load lua script '%s': File too short.", file->cachedPath);
f_close(f);
f_delete(f);
return false;
}
// The following errors are silent (they're due to non-matching endianness/bitness and shouldn't prevent the rest of the mod from loading)
// Check endianness
u64 bom11 = *((u64 *) (header + 12));
u64 bom19 = *((u64 *) (header + 20));
if (bom11 != LUA_BOM_11) {
LOG_ERROR("Failed to load lua script '%s': BOM at offset 0x11 don't match (%016llX, expected %016llX).", file->cachedPath, bom11, LUA_BOM_11);
f_close(f);
f_delete(f);
return false;
}
if (bom19 != LUA_BOM_19) {
LOG_ERROR("Failed to load lua script '%s': BOM at offset 0x19 don't match (%016llX, expected %016llX).", file->cachedPath, bom19, LUA_BOM_19);
f_close(f);
f_delete(f);
return false;
}
// Check sizes
u8 sizeOfCInteger = header[7];
u8 sizeOfCPointer = header[8];
u8 sizeOfCFloat = header[9];
u8 sizeOfLuaInteger = header[10];
u8 sizeOfLuaNumber = header[11];
if (sizeOfCInteger != sizeof(int)) {
LOG_ERROR("Failed to load lua script '%s': sizes of C Integer don't match (%d, expected %llu).", file->cachedPath, sizeOfCInteger, (long long unsigned)sizeof(int));
f_close(f);
f_delete(f);
return false;
}
if (sizeOfCPointer != sizeof(void *)) { // 4 for 32-bit architectures, 8 for 64-bit
LOG_ERROR("Failed to load lua script '%s': sizes of C Pointer don't match (%d, expected %llu).", file->cachedPath, sizeOfCPointer, (long long unsigned)sizeof(void *));
f_close(f);
f_delete(f);
return false;
}
if (sizeOfCFloat != sizeof(float)) {
LOG_ERROR("Failed to load lua script '%s': sizes of C Float don't match (%d, expected %llu).", file->cachedPath, sizeOfCFloat, (long long unsigned)sizeof(float));
f_close(f);
f_delete(f);
return false;
}
if (sizeOfLuaInteger != sizeof(LUA_INTEGER)) {
LOG_ERROR("Failed to load lua script '%s': sizes of Lua Integer don't match (%d, expected %llu).", file->cachedPath, sizeOfLuaInteger, (long long unsigned)sizeof(LUA_INTEGER));
f_close(f);
f_delete(f);
return false;
}
if (sizeOfLuaNumber != sizeof(LUA_NUMBER)) {
LOG_ERROR("Failed to load lua script '%s': sizes of Lua Number don't match (%d, expected %llu).", file->cachedPath, sizeOfLuaNumber, (long long unsigned)sizeof(LUA_NUMBER));
f_close(f);
f_delete(f);
return false;
}
// All's good
f_close(f);
return true;
}
LOG_LUA("Failed to load lua script '%s': File not found.", file->cachedPath);
return false;
}
static void smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex) {
if (!smlua_check_binary_header(file)) return;
lua_State* L = gLuaState;
lua_settop(L, 0);
gSmLuaConvertSuccess = true;
gLuaInitializingScript = 1;
LOG_INFO("Loading lua script '%s'", file->cachedPath);
FILE *f = f_open_r(file->cachedPath);
if (!f) {
LOG_LUA("Failed to load lua script '%s': File not found.", file->cachedPath);
gLuaInitializingScript = 0;
return;
}
f_seek(f, 0, SEEK_END);
size_t length = f_tell(f);
void *buffer = calloc(length + 1, 1);
if (!buffer) {
LOG_LUA("Failed to load lua script '%s': Cannot allocate buffer.", file->cachedPath);
gLuaInitializingScript = 0;
return;
}
f_rewind(f);
if (f_read(buffer, 1, length, f) < length) {
LOG_LUA("Failed to load lua script '%s': Unexpected early end of file.", file->cachedPath);
gLuaInitializingScript = 0;
return;
}
f_close(f);
f_delete(f);
if (luaL_loadbuffer(L, buffer, length, file->cachedPath) != LUA_OK) { // only run on success
LOG_LUA("Failed to load lua script '%s'.", file->cachedPath);
LOG_LUA("%s", smlua_to_string(L, lua_gettop(L)));
gLuaInitializingScript = 0;
free(buffer);
return;
}
free(buffer);
// check if this is the first time this mod has been loaded
lua_getfield(L, LUA_REGISTRYINDEX, mod->relativePath);
bool firstInit = (lua_type(L, -1) == LUA_TNIL);
lua_pop(L, 1);
// create mod's "global" table
if (firstInit) {
lua_newtable(L); // create _ENV tables
lua_newtable(L); // create metatable
lua_getglobal(L, "_G"); // get global table
// remove certain default functions
lua_pushstring(L, "load"); lua_pushnil(L); lua_settable(L, -3);
lua_pushstring(L, "loadfile"); lua_pushnil(L); lua_settable(L, -3);
lua_pushstring(L, "loadstring"); lua_pushnil(L); lua_settable(L, -3);
lua_pushstring(L, "collectgarbage"); lua_pushnil(L); lua_settable(L, -3);
lua_pushstring(L, "dofile"); lua_pushnil(L); lua_settable(L, -3);
// set global as the metatable
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
// push to registry with path as name (must be unique)
lua_setfield(L, LUA_REGISTRYINDEX, mod->relativePath);
}
// load mod's "global" table
lua_getfield(L, LUA_REGISTRYINDEX, mod->relativePath);
lua_setupvalue(L, 1, 1); // set upvalue (_ENV)
// load per-file globals
if (firstInit) {
smlua_sync_table_init_globals(mod->relativePath, remoteIndex);
smlua_cobject_init_per_file_globals(mod->relativePath);
}
// run chunks
LOG_INFO("Executing '%s'", file->relativePath);
if (smlua_pcall(L, 0, LUA_MULTRET, 0) != LUA_OK) {
LOG_LUA("Failed to execute lua script '%s'.", file->cachedPath);
}
gLuaInitializingScript = 0;
}
void smlua_init(void) {
smlua_shutdown();
smlua_pointer_user_data_init();
gLuaState = luaL_newstate();
lua_State* L = gLuaState;
// load libraries
luaopen_base(L);
//luaopen_coroutine(L);
#if defined(DEVELOPMENT)
luaL_requiref(L, "debug", luaopen_debug, 1);
luaL_requiref(L, "io", luaopen_io, 1);
luaL_requiref(L, "os", luaopen_os, 1);
luaL_requiref(L, "package", luaopen_package, 1);
#endif
luaL_requiref(L, "math", luaopen_math, 1);
luaL_requiref(L, "string", luaopen_string, 1);
luaL_requiref(L, "table", luaopen_table, 1);
//luaopen_utf8(L);
smlua_bind_hooks();
smlua_bind_cobject();
smlua_bind_functions();
smlua_bind_functions_autogen();
smlua_bind_sync_table();
extern char gSmluaConstants[];
smlua_exec_str(gSmluaConstants);
smlua_cobject_init_globals();
// load scripts
mods_size_enforce(&gActiveMods);
LOG_INFO("Loading scripts:");
for (int i = 0; i < gActiveMods.entryCount; i++) {
struct Mod* mod = gActiveMods.entries[i];
LOG_INFO(" %s", mod->relativePath);
gLuaLoadingMod = mod;
gLuaActiveMod = mod;
gLuaLastHookMod = mod;
gLuaLoadingMod->customBehaviorIndex = 0;
gPcDebug.lastModRun = gLuaActiveMod;
for (int j = 0; j < mod->fileCount; j++) {
struct ModFile* file = &mod->files[j];
if (!(str_ends_with(file->relativePath, ".lua") || str_ends_with(file->relativePath, ".luac"))) {
continue;
}
smlua_load_script(mod, file, i);
}
gLuaActiveMod = NULL;
gLuaLoadingMod = NULL;
}
smlua_call_event_hooks(HOOK_ON_MODS_LOADED);
}
void smlua_update(void) {
lua_State* L = gLuaState;
if (L == NULL) { return; }
audio_sample_destroy_pending_copies();
smlua_call_event_hooks(HOOK_UPDATE);
// Collect our garbage after calling our hooks.
// If we don't, Lag can quickly build up from our mods.
/*lua_gc(L, LUA_GCSTEP, 1);
lua_gc(L, LUA_GCSTOP, 0);*/
// EDIT: uhh, it turns out that is not the case
// if we only do incremental garbage collection,
// eventually the garbage will build up so much
// that the game slows to a crawl. Messing with
// the GC setting is what caused Arena to get worse
// over time.
// The real fix would be to make smlua produce less
// garbage.
}
void smlua_shutdown(void) {
hardcoded_reset_default_values();
smlua_text_utils_reset_all();
smlua_audio_utils_reset_all();
audio_custom_shutdown();
smlua_pointer_user_data_shutdown();
smlua_clear_hooks();
smlua_model_util_clear();
smlua_level_util_reset();
smlua_anim_util_reset();
lua_State* L = gLuaState;
if (L != NULL) {
lua_close(L);
gLuaState = NULL;
}
gLuaLoadingMod = NULL;
gLuaActiveMod = NULL;
gLuaLastHookMod = NULL;
}