mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-04-05 01:38:07 +00:00
Add mod development mode (#851)
With mod development mode on you can press the L bind while paused to reload the active mods. This reload will rescan the directories for the active modes and thus refresh their file caches. Mod development mode also enables live lua module reloading. Any time a lua module is updated, coop will live reload the functions that changed and do its best to maintain the previous variable states. --------- Co-authored-by: MysterD <myster@d>
This commit is contained in:
parent
01fd935807
commit
c68ee859ea
38 changed files with 772 additions and 40 deletions
|
|
@ -1277,6 +1277,8 @@
|
|||
--- @class ModFile
|
||||
--- @field public cachedPath string
|
||||
--- @field public dataHash integer[]
|
||||
--- @field public isLoadedLuaModule boolean
|
||||
--- @field public modifiedTimestamp integer
|
||||
--- @field public relativePath string
|
||||
--- @field public wroteBytes integer
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ const char* dynos_pack_get_name(s32 index);
|
|||
bool dynos_pack_get_enabled(s32 index);
|
||||
void dynos_pack_set_enabled(s32 index, bool value);
|
||||
bool dynos_pack_get_exists(s32 index);
|
||||
void dynos_generate_mod_pack(char* modPath);
|
||||
void dynos_generate_packs(const char* directory);
|
||||
|
||||
// -- geos -- //
|
||||
|
|
|
|||
|
|
@ -1013,6 +1013,7 @@ void DynOS_Gfx_ModShutdown();
|
|||
typedef s64 (*RDConstantFunc)(const String& _Arg, bool* found);
|
||||
|
||||
u32 DynOS_Lua_RememberVariable(GfxData* aGfxData, void* aPtr, const String& token);
|
||||
void DynOS_Gfx_GenerateModPacks(char* modPath);
|
||||
void DynOS_Gfx_GeneratePacks(const char* directory);
|
||||
s64 DynOS_RecursiveDescent_Parse(const char* expr, bool* success, RDConstantFunc func);
|
||||
void DynOS_Read_Source(GfxData *aGfxData, const SysPath &aFilename);
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ bool dynos_pack_get_exists(s32 index) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void dynos_generate_mod_pack(char* modPath) {
|
||||
DynOS_Gfx_GenerateModPacks(modPath);
|
||||
}
|
||||
|
||||
void dynos_generate_packs(const char* directory) {
|
||||
DynOS_Gfx_GeneratePacks(directory);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,37 @@ extern "C" {
|
|||
#include "pc/loading.h"
|
||||
}
|
||||
|
||||
#define MOD_PATH_LEN 1024
|
||||
|
||||
void DynOS_Gfx_GenerateModPacks(char* modPath) {
|
||||
// If pack folder exists, generate bins
|
||||
SysPath _LevelPackFolder = fstring("%s/levels", modPath);
|
||||
if (fs_sys_dir_exists(_LevelPackFolder.c_str())) {
|
||||
DynOS_Lvl_GeneratePack(_LevelPackFolder);
|
||||
}
|
||||
|
||||
SysPath _ActorPackFolder = fstring("%s/actors", modPath);
|
||||
if (fs_sys_dir_exists(_ActorPackFolder.c_str())) {
|
||||
DynOS_Actor_GeneratePack(_ActorPackFolder);
|
||||
}
|
||||
|
||||
SysPath _BehaviorPackFolder = fstring("%s/data", modPath);
|
||||
if (fs_sys_dir_exists(_BehaviorPackFolder.c_str())) {
|
||||
DynOS_Bhv_GeneratePack(_BehaviorPackFolder);
|
||||
}
|
||||
|
||||
SysPath _TexturePackFolder = fstring("%s", modPath);
|
||||
SysPath _TexturePackOutputFolder = fstring("%s/textures", modPath);
|
||||
if (fs_sys_dir_exists(_TexturePackFolder.c_str())) {
|
||||
DynOS_Tex_GeneratePack(_TexturePackFolder, _TexturePackOutputFolder, true);
|
||||
}
|
||||
}
|
||||
|
||||
void DynOS_Gfx_GeneratePacks(const char* directory) {
|
||||
if (configSkipPackGeneration) { return; }
|
||||
|
||||
|
||||
static char sModPath[MOD_PATH_LEN] = "";
|
||||
|
||||
LOADING_SCREEN_MUTEX(
|
||||
loading_screen_reset_progress_bar();
|
||||
snprintf(gCurrLoadingSegment.str, 256, "Generating DynOS Packs In Path:\n\\#808080\\%s", directory);
|
||||
|
|
@ -25,28 +53,11 @@ void DynOS_Gfx_GeneratePacks(const char* directory) {
|
|||
if (SysPath(dir->d_name) == ".") continue;
|
||||
if (SysPath(dir->d_name) == "..") continue;
|
||||
|
||||
// If pack folder exists, generate bins
|
||||
SysPath _LevelPackFolder = fstring("%s/%s/levels", directory, dir->d_name);
|
||||
if (fs_sys_dir_exists(_LevelPackFolder.c_str())) {
|
||||
DynOS_Lvl_GeneratePack(_LevelPackFolder);
|
||||
}
|
||||
|
||||
SysPath _ActorPackFolder = fstring("%s/%s/actors", directory, dir->d_name);
|
||||
if (fs_sys_dir_exists(_ActorPackFolder.c_str())) {
|
||||
DynOS_Actor_GeneratePack(_ActorPackFolder);
|
||||
}
|
||||
|
||||
SysPath _BehaviorPackFolder = fstring("%s/%s/data", directory, dir->d_name);
|
||||
if (fs_sys_dir_exists(_BehaviorPackFolder.c_str())) {
|
||||
DynOS_Bhv_GeneratePack(_BehaviorPackFolder);
|
||||
}
|
||||
|
||||
SysPath _TexturePackFolder = fstring("%s/%s", directory, dir->d_name);
|
||||
SysPath _TexturePackOutputFolder = fstring("%s/%s/textures", directory, dir->d_name);
|
||||
if (fs_sys_dir_exists(_TexturePackFolder.c_str())) {
|
||||
DynOS_Tex_GeneratePack(_TexturePackFolder, _TexturePackOutputFolder, true);
|
||||
}
|
||||
// build mod path
|
||||
snprintf(sModPath, MOD_PATH_LEN, "%s/%s", directory, dir->d_name);
|
||||
|
||||
// generate packs
|
||||
DynOS_Gfx_GenerateModPacks(sModPath);
|
||||
LOADING_SCREEN_MUTEX(gCurrLoadingSegment.percentage = (f32) i / (f32) pathCount);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1940,6 +1940,8 @@
|
|||
| ----- | ---- | ------ |
|
||||
| cachedPath | `string` | read-only |
|
||||
| dataHash | `Array` <`integer`> | read-only |
|
||||
| isLoadedLuaModule | `boolean` | read-only |
|
||||
| modifiedTimestamp | `integer` | read-only |
|
||||
| relativePath | `string` | read-only |
|
||||
| wroteBytes | `integer` | read-only |
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Přeskočit intro"
|
|||
ENABLE_CHEATS = "Zapnout cheaty"
|
||||
BUBBLE_ON_DEATH = "Bublina při smrti"
|
||||
NAMETAGS = "Nametags"
|
||||
MOD_DEV_MODE = "Režim vývoje modů"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Zapnuto (Omezeno)"
|
||||
BOUNCY_BOUNDS_ON = "Zapnuto"
|
||||
BOUNCY_BOUNDS_OFF = "Vypnuto"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Debug"
|
|||
LANGUAGE = "Jazyk"
|
||||
COOP_COMPATIBILITY = "Povolit kompatibilitu sm64ex-coop"
|
||||
R_BUTTON = "Tlačítko R - Možnosti"
|
||||
L_BUTTON = "Tlačítko L - Znovu načíst aktivní mody"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMACE"
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Introductie film overslaan"
|
|||
ENABLE_CHEATS = "Cheats aan zetten"
|
||||
BUBBLE_ON_DEATH = "Bubbelen op dood"
|
||||
NAMETAGS = "Nametags"
|
||||
MOD_DEV_MODE = "Modontwikkelingsmodus"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Aan (Begrensd)"
|
||||
BOUNCY_BOUNDS_ON = "Aan"
|
||||
BOUNCY_BOUNDS_OFF = "Uit"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Debug"
|
|||
LANGUAGE = "Taal"
|
||||
COOP_COMPATIBILITY = "Schakel sm64ex-coop compatibiliteit in"
|
||||
R_BUTTON = "R-knop - Opties"
|
||||
L_BUTTON = "L-knop - Actieve mods opnieuw laden"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMATIE"
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Skip Intro Cutscene"
|
|||
ENABLE_CHEATS = "Enable Cheats"
|
||||
BUBBLE_ON_DEATH = "Bubble On Death"
|
||||
NAMETAGS = "Nametags"
|
||||
MOD_DEV_MODE = "Mod Development Mode"
|
||||
BOUNCY_BOUNDS_ON_CAP = "On (Capped)"
|
||||
BOUNCY_BOUNDS_ON = "On"
|
||||
BOUNCY_BOUNDS_OFF = "Off"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Debug"
|
|||
LANGUAGE = "Language"
|
||||
COOP_COMPATIBILITY = "Enable sm64ex-coop Compatibility"
|
||||
R_BUTTON = "R Button - Options"
|
||||
L_BUTTON = "L Button - Reload Active Mods"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFO"
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Passer la cinématique d'intro"
|
|||
ENABLE_CHEATS = "Activer le mode triche"
|
||||
BUBBLE_ON_DEATH = "Bulles (mort)"
|
||||
NAMETAGS = "Afficher Pseudos"
|
||||
MOD_DEV_MODE = "Mode de développement de mods"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Activé (Limité)"
|
||||
BOUNCY_BOUNDS_ON = "Activé"
|
||||
BOUNCY_BOUNDS_OFF = "Désactivé"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Débogage"
|
|||
LANGUAGE = "Langue"
|
||||
COOP_COMPATIBILITY = "Activer la compatibilité sm64ex-coop"
|
||||
R_BUTTON = "Bouton R - Options"
|
||||
L_BUTTON = "Bouton L - Recharger les mods actifs"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMATION"
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Intro überspringen"
|
|||
ENABLE_CHEATS = "Cheats aktivieren"
|
||||
BUBBLE_ON_DEATH = "Base beim Tod"
|
||||
NAMETAGS = "Nametags"
|
||||
MOD_DEV_MODE = "Mod-Entwicklungsmodus"
|
||||
BOUNCY_BOUNDS_ON_CAP = "An (Gedrosselt)"
|
||||
BOUNCY_BOUNDS_ON = "An"
|
||||
BOUNCY_BOUNDS_OFF = "Aus"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Debug"
|
|||
LANGUAGE = "Sprache"
|
||||
COOP_COMPATIBILITY = "Aktiviere sm64ex-coop Kompatibilität"
|
||||
R_BUTTON = "R-Taste - Optionen"
|
||||
L_BUTTON = "L-Taste - Aktive Mods neu laden"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMATION"
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ SKIP_INTRO_CUTSCENE = "Salta la intro iniziale"
|
|||
ENABLE_CHEATS = "Abilita i trucchi"
|
||||
BUBBLE_ON_DEATH = "Bolla alla morte"
|
||||
NAMETAGS = "Nametags"
|
||||
MOD_DEV_MODE = "Modalità sviluppo mod"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Acceso (Limitato)"
|
||||
BOUNCY_BOUNDS_ON = "Acceso"
|
||||
BOUNCY_BOUNDS_OFF = "Spento"
|
||||
|
|
@ -319,6 +320,7 @@ DEBUG = "Debug"
|
|||
LANGUAGE = "Lingua"
|
||||
COOP_COMPATIBILITY = "Abilita la compatibilità sm64ex-coop"
|
||||
R_BUTTON = "Pulsante R - Opzioni"
|
||||
L_BUTTON = "Pulsante L - Ricarica mod attivi"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMAZIONE"
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@ SKIP_INTRO_CUTSCENE = "イントロをスキップ"
|
|||
ENABLE_CHEATS = "チートを有効にする"
|
||||
BUBBLE_ON_DEATH = "やられた時にシャボンで復活"
|
||||
NAMETAGS = "ネームタグを有効にする"
|
||||
MOD_DEV_MODE = "MOD開発モード"
|
||||
BOUNCY_BOUNDS_ON_CAP = "オン(制限付き)"
|
||||
BOUNCY_BOUNDS_ON = "オン"
|
||||
BOUNCY_BOUNDS_OFF = "オフ"
|
||||
|
|
@ -322,6 +323,7 @@ DEBUG = "デバッグ"
|
|||
LANGUAGE = "言語"
|
||||
COOP_COMPATIBILITY = "sm64ex-coopとの互換性を有効にする"
|
||||
R_BUTTON = "Rボタン - 設定"
|
||||
L_BUTTON = "Lボタン - アクティブなMODを再読み込み"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFO"
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Pomiń Przerywnik Intro"
|
|||
ENABLE_CHEATS = "Włącz Kody"
|
||||
BUBBLE_ON_DEATH = "Bańka po Śmierci"
|
||||
NAMETAGS = "Identyfikatory"
|
||||
MOD_DEV_MODE = "Tryb deweloperski modów"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Wł. (Ograniczone)"
|
||||
BOUNCY_BOUNDS_ON = "Włączone"
|
||||
BOUNCY_BOUNDS_OFF = "Wyłączone"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Debugowanie"
|
|||
LANGUAGE = "Język"
|
||||
COOP_COMPATIBILITY = "Włącz kompatybilność z sm64ex-coop"
|
||||
R_BUTTON = "Przycisk R - Opcje"
|
||||
L_BUTTON = "Przycisk L - Przeładuj aktywne mody"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMACJA"
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Pular cena de introdução"
|
|||
ENABLE_CHEATS = "Ativar trapaças"
|
||||
BUBBLE_ON_DEATH = "Bolha após a morte"
|
||||
NAMETAGS = "Etiquetas"
|
||||
MOD_DEV_MODE = "Modo de desenvolvimento de mods"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Ativado (Limitado)"
|
||||
BOUNCY_BOUNDS_ON = "Ativado"
|
||||
BOUNCY_BOUNDS_OFF = "Desativado"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Debug"
|
|||
LANGUAGE = "Idioma"
|
||||
COOP_COMPATIBILITY = "Ativar a compatibilidade com sm64ex-coop"
|
||||
R_BUTTON = "Botão R - Opções"
|
||||
L_BUTTON = "Botão L - Recarregar mods ativos"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMAÇÃO"
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ SKIP_INTRO_CUTSCENE = "Пропустить вступительный роли
|
|||
ENABLE_CHEATS = "Включить читы"
|
||||
BUBBLE_ON_DEATH = "Пузырик при смерти"
|
||||
NAMETAGS = "Этикетки"
|
||||
MOD_DEV_MODE = "Режим разработки модов"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Вкл. (Ограничено)"
|
||||
BOUNCY_BOUNDS_ON = "Вкл"
|
||||
BOUNCY_BOUNDS_OFF = "Выкл"
|
||||
|
|
@ -320,6 +321,7 @@ DEBUG = "Отладка"
|
|||
LANGUAGE = "Язык"
|
||||
COOP_COMPATIBILITY = "Включить совместимость sm64ex-coop"
|
||||
R_BUTTON = "Кнопка R - Опции"
|
||||
L_BUTTON = "Кнопка L - Перезагрузить активные моды"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMATION"
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ SKIP_INTRO_CUTSCENE = "Saltar cinemática de introducción"
|
|||
ENABLE_CHEATS = "Habilitar trucos"
|
||||
BUBBLE_ON_DEATH = "Burbuja al morir"
|
||||
NAMETAGS = "Etiquetas de nombre"
|
||||
MOD_DEV_MODE = "Modo de desarrollo de mods"
|
||||
BOUNCY_BOUNDS_ON_CAP = "Encendido (Limitado)"
|
||||
BOUNCY_BOUNDS_ON = "Encendido"
|
||||
BOUNCY_BOUNDS_OFF = "Apagado"
|
||||
|
|
@ -321,6 +322,7 @@ DEBUG = "Depuración"
|
|||
LANGUAGE = "Idioma"
|
||||
COOP_COMPATIBILITY = "Habilitar la compatibilidad con sm64ex-coop"
|
||||
R_BUTTON = "Botón R - Opciones"
|
||||
L_BUTTON = "Botón L - Recargar mods activos"
|
||||
|
||||
[INFORMATION]
|
||||
INFORMATION_TITLE = "INFORMACIÓN"
|
||||
|
|
|
|||
|
|
@ -3168,6 +3168,9 @@ s16 render_pause_courses_and_castle(void) {
|
|||
if (gPlayer1Controller->buttonPressed & R_TRIG) {
|
||||
djui_panel_pause_create(NULL);
|
||||
}
|
||||
if ((gPlayer1Controller->buttonPressed & L_TRIG) && network_allow_mod_dev_mode()) {
|
||||
network_mod_dev_mode_reload();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ unsigned int configPlayerInteraction = 1;
|
|||
unsigned int configPlayerKnockbackStrength = 25;
|
||||
unsigned int configStayInLevelAfterStar = 0;
|
||||
bool configNametags = true;
|
||||
bool configModDevMode = false;
|
||||
unsigned int configBouncyLevelBounds = 0;
|
||||
bool configSkipIntro = 0;
|
||||
bool configPauseAnywhere = false;
|
||||
|
|
@ -334,6 +335,7 @@ static const struct ConfigOption options[] = {
|
|||
{.name = "coop_player_knockback_strength", .type = CONFIG_TYPE_UINT, .uintValue = &configPlayerKnockbackStrength},
|
||||
{.name = "coop_stay_in_level_after_star", .type = CONFIG_TYPE_UINT, .uintValue = &configStayInLevelAfterStar},
|
||||
{.name = "coop_nametags", .type = CONFIG_TYPE_BOOL, .boolValue = &configNametags},
|
||||
{.name = "coop_mod_dev_mode", .type = CONFIG_TYPE_BOOL, .boolValue = &configModDevMode},
|
||||
{.name = "coop_bouncy_bounds", .type = CONFIG_TYPE_UINT, .uintValue = &configBouncyLevelBounds},
|
||||
{.name = "skip_intro", .type = CONFIG_TYPE_BOOL, .boolValue = &configSkipIntro},
|
||||
{.name = "pause_anywhere", .type = CONFIG_TYPE_BOOL, .boolValue = &configPauseAnywhere},
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ extern unsigned int configPlayerInteraction;
|
|||
extern unsigned int configPlayerKnockbackStrength;
|
||||
extern unsigned int configStayInLevelAfterStar;
|
||||
extern bool configNametags;
|
||||
extern bool configModDevMode;
|
||||
extern unsigned int configBouncyLevelBounds;
|
||||
extern bool configSkipIntro;
|
||||
extern bool configPauseAnywhere;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ static Gfx* sSavedDisplayListHead = NULL;
|
|||
|
||||
struct DjuiRoot* gDjuiRoot = NULL;
|
||||
struct DjuiText* gDjuiPauseOptions = NULL;
|
||||
struct DjuiText* gDjuiModReload = NULL;
|
||||
static struct DjuiText* sDjuiLuaError = NULL;
|
||||
static u32 sDjuiLuaErrorTimeout = 0;
|
||||
bool gDjuiInMainMenu = true;
|
||||
|
|
@ -39,8 +40,10 @@ void djui_shutdown(void) {
|
|||
|
||||
sSavedDisplayListHead = NULL;
|
||||
if (gDjuiPauseOptions) djui_base_destroy(&gDjuiPauseOptions->base);
|
||||
if (gDjuiModReload) djui_base_destroy(&gDjuiModReload->base);
|
||||
if (sDjuiLuaError) djui_base_destroy(&sDjuiLuaError->base);
|
||||
gDjuiPauseOptions = NULL;
|
||||
gDjuiModReload = NULL;
|
||||
sDjuiLuaError = NULL;
|
||||
sDjuiLuaErrorTimeout = 0;
|
||||
|
||||
|
|
@ -89,6 +92,15 @@ void djui_init(void) {
|
|||
djui_base_set_location(&gDjuiPauseOptions->base, 0, 16);
|
||||
djui_text_set_alignment(gDjuiPauseOptions, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER);
|
||||
|
||||
gDjuiModReload = djui_text_create(&sDjuiRootBehind->base, DLANG(MISC, L_BUTTON));
|
||||
djui_text_set_drop_shadow(gDjuiModReload, 0, 0, 0, 255);
|
||||
djui_base_set_color(&gDjuiModReload->base, 255, 32, 32, 255);
|
||||
djui_base_set_size_type(&gDjuiModReload->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&gDjuiModReload->base, 1.0f, 32);
|
||||
djui_base_set_location(&gDjuiModReload->base, 0, 64);
|
||||
djui_text_set_alignment(gDjuiModReload, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER);
|
||||
djui_base_set_visible(&gDjuiModReload->base, false);
|
||||
|
||||
sDjuiLuaError = djui_text_create(&gDjuiRoot->base, "");
|
||||
djui_base_set_size_type(&sDjuiLuaError->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
|
||||
djui_base_set_size(&sDjuiLuaError->base, 1.0f, 32);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
|
||||
extern struct DjuiRoot* gDjuiRoot;
|
||||
extern struct DjuiText* gDjuiPauseOptions;
|
||||
extern struct DjuiText* gDjuiModReload;
|
||||
extern bool gDjuiInMainMenu;
|
||||
extern bool gDjuiInPlayerMenu;
|
||||
extern bool gDjuiDisabled;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
static unsigned int sKnockbackIndex = 0;
|
||||
struct DjuiInputbox* sPlayerAmount = NULL;
|
||||
static bool sFalse = false;
|
||||
|
||||
static void djui_panel_host_settings_knockback_change(UNUSED struct DjuiBase* caller) {
|
||||
switch (sKnockbackIndex) {
|
||||
|
|
@ -69,6 +70,9 @@ void djui_panel_host_settings_create(struct DjuiBase* caller) {
|
|||
djui_checkbox_create(body, DLANG(HOST_SETTINGS, BUBBLE_ON_DEATH), &configBubbleDeath, NULL);
|
||||
djui_checkbox_create(body, DLANG(HOST_SETTINGS, NAMETAGS), &configNametags, NULL);
|
||||
|
||||
struct DjuiCheckbox* chkDevMode = djui_checkbox_create(body, DLANG(HOST_SETTINGS, MOD_DEV_MODE), (configNetworkSystem == NS_SOCKET) ? &configModDevMode : &sFalse, NULL);
|
||||
djui_base_set_enabled(&chkDevMode->base, configNetworkSystem == NS_SOCKET);
|
||||
|
||||
struct DjuiRect* rect1 = djui_rect_container_create(body, 32);
|
||||
{
|
||||
struct DjuiText* text1 = djui_text_create(&rect1->base, DLANG(HOST_SETTINGS, AMOUNT_OF_PLAYERS));
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ static void djui_panel_menu_options_djui_setting_change(UNUSED struct DjuiBase*
|
|||
|
||||
djui_text_set_font(gDjuiPauseOptions, gDjuiFonts[configDjuiThemeFont == 0 ? FONT_NORMAL : FONT_ALIASED]);
|
||||
djui_text_set_text(gDjuiPauseOptions, DLANG(MISC, R_BUTTON));
|
||||
|
||||
djui_text_set_font(gDjuiModReload, gDjuiFonts[configDjuiThemeFont == 0 ? FONT_NORMAL : FONT_ALIASED]);
|
||||
djui_text_set_text(gDjuiModReload, DLANG(MISC, L_BUTTON));
|
||||
}
|
||||
gDjuiChangingTheme = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "smlua.h"
|
||||
#include "pc/lua/smlua_require.h"
|
||||
#include "pc/lua/smlua_live_reload.h"
|
||||
#include "game/hardcoded.h"
|
||||
#include "pc/mods/mods.h"
|
||||
#include "pc/mods/mods_utils.h"
|
||||
|
|
@ -192,8 +193,10 @@ static bool smlua_check_binary_header(struct ModFile *file) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, bool isModInit) {
|
||||
if (!smlua_check_binary_header(file)) return;
|
||||
int smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, bool isModInit) {
|
||||
int rc = LUA_OK;
|
||||
if (!smlua_check_binary_header(file)) { return LUA_ERRMEM; }
|
||||
|
||||
lua_State* L = gLuaState;
|
||||
|
||||
s32 prevTop = lua_gettop(L);
|
||||
|
|
@ -207,7 +210,7 @@ void smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, b
|
|||
LOG_LUA("Failed to load lua script '%s': File not found.", file->cachedPath);
|
||||
gLuaInitializingScript = 0;
|
||||
lua_settop(L, prevTop);
|
||||
return;
|
||||
return LUA_ERRFILE;
|
||||
}
|
||||
|
||||
f_seek(f, 0, SEEK_END);
|
||||
|
|
@ -217,7 +220,7 @@ void smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, b
|
|||
LOG_LUA("Failed to load lua script '%s': Cannot allocate buffer.", file->cachedPath);
|
||||
gLuaInitializingScript = 0;
|
||||
lua_settop(L, prevTop);
|
||||
return;
|
||||
return LUA_ERRMEM;
|
||||
}
|
||||
|
||||
f_rewind(f);
|
||||
|
|
@ -225,18 +228,19 @@ void smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, b
|
|||
LOG_LUA("Failed to load lua script '%s': Unexpected early end of file.", file->cachedPath);
|
||||
gLuaInitializingScript = 0;
|
||||
lua_settop(L, prevTop);
|
||||
return;
|
||||
return LUA_ERRFILE;
|
||||
}
|
||||
f_close(f);
|
||||
f_delete(f);
|
||||
|
||||
if (luaL_loadbuffer(L, buffer, length, file->cachedPath) != LUA_OK) { // only run on success
|
||||
rc = luaL_loadbuffer(L, buffer, length, file->cachedPath);
|
||||
if (rc != 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);
|
||||
lua_settop(L, prevTop);
|
||||
return;
|
||||
return rc;
|
||||
}
|
||||
free(buffer);
|
||||
|
||||
|
|
@ -284,18 +288,21 @@ void smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, b
|
|||
lua_pop(L, 1);
|
||||
LOG_LUA("mod environment not found");
|
||||
lua_settop(L, prevTop);
|
||||
return;
|
||||
return LUA_ERRRUN;
|
||||
}
|
||||
lua_setupvalue(L, -2, 1); // set _ENV
|
||||
}
|
||||
|
||||
// run chunks
|
||||
LOG_INFO("Executing '%s'", file->relativePath);
|
||||
if (smlua_pcall(L, 0, 1, 0) != LUA_OK) {
|
||||
rc = smlua_pcall(L, 0, 1, 0);
|
||||
if (rc != LUA_OK) {
|
||||
LOG_LUA("Failed to execute lua script '%s'.", file->cachedPath);
|
||||
}
|
||||
|
||||
gLuaInitializingScript = 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void smlua_init(void) {
|
||||
|
|
@ -370,6 +377,8 @@ void smlua_update(void) {
|
|||
lua_State* L = gLuaState;
|
||||
if (L == NULL) { return; }
|
||||
|
||||
if (network_allow_mod_dev_mode()) { smlua_live_reload_update(L); }
|
||||
|
||||
audio_sample_destroy_pending_copies();
|
||||
|
||||
smlua_call_event_hooks(HOOK_UPDATE);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ int smlua_error_handler(UNUSED lua_State* L);
|
|||
int smlua_pcall(lua_State* L, int nargs, int nresults, int errfunc);
|
||||
void smlua_exec_file(const char* path);
|
||||
void smlua_exec_str(const char* str);
|
||||
void smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, bool isModInit);
|
||||
int smlua_load_script(struct Mod* mod, struct ModFile* file, u16 remoteIndex, bool isModInit);
|
||||
|
||||
void smlua_init(void);
|
||||
void smlua_update(void);
|
||||
|
|
|
|||
|
|
@ -1598,14 +1598,16 @@ static struct LuaObjectField sModAudioSampleCopiesFields[LUA_MOD_AUDIO_SAMPLE_CO
|
|||
// { "sound", LVT_???, offsetof(struct ModAudioSampleCopies, sound), false, LOT_???, 1, sizeof(ma_sound) }, <--- UNIMPLEMENTED
|
||||
};
|
||||
|
||||
#define LUA_MOD_FILE_FIELD_COUNT 4
|
||||
#define LUA_MOD_FILE_FIELD_COUNT 6
|
||||
static struct LuaObjectField sModFileFields[LUA_MOD_FILE_FIELD_COUNT] = {
|
||||
{ "cachedPath", LVT_STRING_P, offsetof(struct ModFile, cachedPath), true, LOT_NONE, 1, sizeof(char*) },
|
||||
{ "dataHash", LVT_U8, offsetof(struct ModFile, dataHash), true, LOT_NONE, 16, sizeof(u8) },
|
||||
// { "fp", LVT_???, offsetof(struct ModFile, fp), true, LOT_???, 1, sizeof(FILE*) }, <--- UNIMPLEMENTED
|
||||
{ "relativePath", LVT_STRING, offsetof(struct ModFile, relativePath), true, LOT_NONE, 1, sizeof(char) },
|
||||
// { "size", LVT_???, offsetof(struct ModFile, size), true, LOT_???, 1, sizeof(size_t) }, <--- UNIMPLEMENTED
|
||||
{ "wroteBytes", LVT_U64, offsetof(struct ModFile, wroteBytes), true, LOT_NONE, 1, sizeof(u64) },
|
||||
{ "cachedPath", LVT_STRING_P, offsetof(struct ModFile, cachedPath), true, LOT_NONE, 1, sizeof(char*) },
|
||||
{ "dataHash", LVT_U8, offsetof(struct ModFile, dataHash), true, LOT_NONE, 16, sizeof(u8) },
|
||||
// { "fp", LVT_???, offsetof(struct ModFile, fp), true, LOT_???, 1, sizeof(FILE*) }, <--- UNIMPLEMENTED
|
||||
{ "isLoadedLuaModule", LVT_BOOL, offsetof(struct ModFile, isLoadedLuaModule), true, LOT_NONE, 1, sizeof(bool) },
|
||||
{ "modifiedTimestamp", LVT_U64, offsetof(struct ModFile, modifiedTimestamp), true, LOT_NONE, 1, sizeof(u64) },
|
||||
{ "relativePath", LVT_STRING, offsetof(struct ModFile, relativePath), true, LOT_NONE, 1, sizeof(char) },
|
||||
// { "size", LVT_???, offsetof(struct ModFile, size), true, LOT_???, 1, sizeof(size_t) }, <--- UNIMPLEMENTED
|
||||
{ "wroteBytes", LVT_U64, offsetof(struct ModFile, wroteBytes), true, LOT_NONE, 1, sizeof(u64) },
|
||||
};
|
||||
|
||||
#define LUA_MODE_TRANSITION_INFO_FIELD_COUNT 6
|
||||
|
|
|
|||
|
|
@ -1456,6 +1456,53 @@ void smlua_call_mod_menu_element_hook(struct LuaHookedModMenuElement* hooked, in
|
|||
// misc //
|
||||
//////////
|
||||
|
||||
static void smlua_hook_replace_function_reference(lua_State* L, int* hookedReference, int oldReference, int newReference) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, *hookedReference); // stack: ..., hookedFunc
|
||||
int hookedIdx = lua_gettop(L);
|
||||
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, oldReference); // stack: ..., hookedFunc, oldFunc
|
||||
int oldIdx = lua_gettop(L);
|
||||
|
||||
if (lua_rawequal(L, hookedIdx, oldIdx)) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, *hookedReference);
|
||||
*hookedReference = newReference;
|
||||
}
|
||||
|
||||
lua_pop(L, 2);
|
||||
}
|
||||
|
||||
void smlua_hook_replace_function_references(lua_State* L, int oldReference, int newReference) {
|
||||
for (int i = 0; i < HOOK_MAX; i++) {
|
||||
struct LuaHookedEvent* hooked = &sHookedEvents[i];
|
||||
for (int j = 0; j < hooked->count; j++) {
|
||||
smlua_hook_replace_function_reference(L, &hooked->reference[j], oldReference, newReference);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < sHookedMarioActionsCount; i++) {
|
||||
struct LuaHookedMarioAction* hooked = &sHookedMarioActions[i];
|
||||
for (int j = 0; j < ACTION_HOOK_MAX; j++) {
|
||||
smlua_hook_replace_function_reference(L, &hooked->actionHookRefs[j], oldReference, newReference);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < sHookedChatCommandsCount; i++) {
|
||||
struct LuaHookedChatCommand* hooked = &sHookedChatCommands[i];
|
||||
smlua_hook_replace_function_reference(L, &hooked->reference, oldReference, newReference);
|
||||
}
|
||||
|
||||
for (int i = 0; i < gHookedModMenuElementsCount; i++) {
|
||||
struct LuaHookedModMenuElement* hooked = &gHookedModMenuElements[i];
|
||||
smlua_hook_replace_function_reference(L, &hooked->reference, oldReference, newReference);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sHookedBehaviorsCount; i++) {
|
||||
struct LuaHookedBehavior* hooked = &sHookedBehaviors[i];
|
||||
smlua_hook_replace_function_reference(L, &hooked->initReference, oldReference, newReference);
|
||||
smlua_hook_replace_function_reference(L, &hooked->loopReference, oldReference, newReference);
|
||||
}
|
||||
}
|
||||
|
||||
void smlua_clear_hooks(void) {
|
||||
for (int i = 0; i < HOOK_MAX; i++) {
|
||||
struct LuaHookedEvent* hooked = &sHookedEvents[i];
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ bool smlua_subcommand_exists(const char* maincommand, const char* subcommand);
|
|||
|
||||
void smlua_call_mod_menu_element_hook(struct LuaHookedModMenuElement* hooked, int index);
|
||||
|
||||
void smlua_hook_replace_function_references(lua_State* L, int oldReference, int newReference);
|
||||
void smlua_clear_hooks(void);
|
||||
void smlua_bind_hooks(void);
|
||||
|
||||
|
|
|
|||
460
src/pc/lua/smlua_live_reload.c
Normal file
460
src/pc/lua/smlua_live_reload.c
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
#include <stdbool.h>
|
||||
#include "smlua.h"
|
||||
#include "pc/mods/mods.h"
|
||||
#include "pc/mods/mods_utils.h"
|
||||
|
||||
#define LIVE_RELOAD_TICK_COUNT 15
|
||||
|
||||
typedef struct UpvalRecord {
|
||||
char *funcKeyStr; // the table key under which the function lived
|
||||
int funcKeyRef; // registry ref for the key
|
||||
char *name; // the upvalue's name
|
||||
int ref; // registry reference to the upvalue's value
|
||||
const void *id; // the opaque upvalue‐cell pointer
|
||||
int type; // the Lua type code of that value (LUA_T*)
|
||||
struct UpvalRecord *next;
|
||||
} UpvalRecord;
|
||||
|
||||
typedef struct UpvalReference {
|
||||
char *name;
|
||||
int reference;
|
||||
bool active;
|
||||
bool isOld;
|
||||
struct UpvalReference *next;
|
||||
} UpvalReference;
|
||||
|
||||
static UpvalReference *sUpvalReferences = NULL;
|
||||
|
||||
static void upval_references_free(lua_State *L) {
|
||||
UpvalReference *ref = sUpvalReferences;
|
||||
while (ref) {
|
||||
UpvalReference *next = ref->next;
|
||||
|
||||
if (ref->name) {
|
||||
free(ref->name);
|
||||
}
|
||||
|
||||
if (!ref->active) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, ref->reference);
|
||||
}
|
||||
|
||||
free(ref);
|
||||
ref = next;
|
||||
}
|
||||
sUpvalReferences = NULL;
|
||||
}
|
||||
|
||||
static int upval_references_deduplicate(lua_State *L, char* name, int reference, bool isOld) {
|
||||
// push the candidate function onto the stack
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, reference); // stack: ..., newFunc
|
||||
int newIdx = lua_gettop(L);
|
||||
|
||||
// iterate existing refs, compare each stored function
|
||||
for (UpvalReference *ref = sUpvalReferences; ref; ref = ref->next) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, ref->reference); // stack: ..., newFunc, existFunc
|
||||
int existIdx = lua_gettop(L);
|
||||
|
||||
// raw-equal tests pointer identity for tables/functions
|
||||
if (lua_rawequal(L, newIdx, existIdx)) {
|
||||
lua_pop(L, 2); // pop existFunc and newFunc
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, reference);
|
||||
return ref->reference;
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop only existFunc, leave newFunc for next iteration
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop newFunc
|
||||
|
||||
// allocate
|
||||
UpvalReference *ref = malloc(sizeof(struct UpvalReference));
|
||||
ref->name = name ? strdup(name) : NULL;
|
||||
ref->reference = reference;
|
||||
ref->active = false;
|
||||
ref->isOld = isOld;
|
||||
ref->next = sUpvalReferences;
|
||||
sUpvalReferences = ref;
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
static void upval_references_mark_active(int reference) {
|
||||
for (UpvalReference *ref = sUpvalReferences; ref; ref = ref->next) {
|
||||
if (ref->reference == reference) {
|
||||
ref->active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void upvalues_free(lua_State *L, UpvalRecord *upvalsHead) {
|
||||
UpvalRecord *cur = upvalsHead;
|
||||
while (cur) {
|
||||
UpvalRecord *next = cur->next;
|
||||
|
||||
// free the string
|
||||
if (cur->funcKeyStr) {
|
||||
free(cur->funcKeyStr);
|
||||
}
|
||||
|
||||
// unref the upvalue's value
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, cur->ref);
|
||||
|
||||
// free the upvalue name
|
||||
free(cur->name);
|
||||
|
||||
// finally free the record itself
|
||||
free(cur);
|
||||
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void upvalues_collect_from_function(lua_State *L, UpvalRecord **upvalsHead, bool isOld) {
|
||||
LUA_STACK_CHECK_BEGIN(L);
|
||||
|
||||
// stack: ..., key, val
|
||||
if (lua_type(L, -1) != LUA_TFUNCTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read key string
|
||||
static char sFuncKeyStr[128] = "";
|
||||
const char *kstr = lua_tostring(L, -2);
|
||||
if (kstr) {
|
||||
snprintf(sFuncKeyStr, 128, "%s", kstr);
|
||||
}
|
||||
// read key ref
|
||||
lua_pushvalue(L, -1); // duplicate the function closure (val)
|
||||
// stack: ..., key, val, val_copy
|
||||
|
||||
int refRegistered = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
// luaL_ref pops the copy, leaving the original 'val' in place
|
||||
// stack: ..., key, val
|
||||
|
||||
int funcKeyRef = upval_references_deduplicate(L, sFuncKeyStr, refRegistered, isOld);
|
||||
|
||||
int fnIdx = lua_gettop(L); // the stack index of the reference to the function we're processing
|
||||
|
||||
// walk its upvalues
|
||||
for (int uv = 1;; uv++) {
|
||||
const char *uvName = lua_getupvalue(L, fnIdx, uv);
|
||||
if (!uvName) { break; }
|
||||
|
||||
// get the upvalue‐cell identifier
|
||||
const void *upvalId = lua_upvalueid(L, fnIdx, uv);
|
||||
|
||||
// get its type
|
||||
int uvType = lua_type(L, -1);
|
||||
|
||||
// now on top of the stack is the upvalue's 'value'
|
||||
// we pin it in the registry
|
||||
lua_pushvalue(L, -1);
|
||||
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
// pop the original
|
||||
lua_pop(L, 1);
|
||||
|
||||
// allocate a new record
|
||||
UpvalRecord *rec = malloc(sizeof(struct UpvalRecord));
|
||||
rec->funcKeyStr = kstr ? strdup(sFuncKeyStr) : NULL;
|
||||
rec->funcKeyRef = funcKeyRef;
|
||||
rec->name = strdup(uvName);
|
||||
rec->ref = ref;
|
||||
rec->id = upvalId;
|
||||
rec->type = uvType;
|
||||
rec->next = *upvalsHead;
|
||||
*upvalsHead = rec;
|
||||
}
|
||||
|
||||
LUA_STACK_CHECK_END(L);
|
||||
}
|
||||
|
||||
static void upvalues_collect(lua_State *L, UpvalRecord **upvalsHead, int moduleIdx, bool isOld) {
|
||||
LUA_STACK_CHECK_BEGIN(L);
|
||||
|
||||
// make moduleIdx absolute so pushes don't shift it
|
||||
int absMod = lua_absindex(L, moduleIdx);
|
||||
|
||||
// iterate module metatable
|
||||
if (lua_getmetatable(L, absMod)) {
|
||||
lua_pushnil(L); // first key
|
||||
while (lua_next(L, -2) != 0) {
|
||||
upvalues_collect_from_function(L, upvalsHead, isOld);
|
||||
lua_pop(L, 1); // pop val, keep key
|
||||
}
|
||||
lua_pop(L, 1); // pop metatable
|
||||
}
|
||||
|
||||
// iterate module table
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, absMod) != 0) {
|
||||
upvalues_collect_from_function(L, upvalsHead, isOld);
|
||||
lua_pop(L, 1); // pop val, keep key
|
||||
}
|
||||
|
||||
LUA_STACK_CHECK_END(L);
|
||||
}
|
||||
|
||||
const UpvalRecord *upvalues_find(UpvalRecord *searchList, UpvalRecord *searchFor) {
|
||||
const UpvalRecord *best = NULL;
|
||||
int best_score = 0;
|
||||
|
||||
for (const UpvalRecord *cur = searchList; cur; cur = cur->next) {
|
||||
// check if upval names match
|
||||
if (strcmp(cur->name, searchFor->name) != 0) { continue; }
|
||||
|
||||
// if function names match too, we found it
|
||||
if (cur->funcKeyStr && searchFor->funcKeyStr && strcmp(cur->funcKeyStr, searchFor->funcKeyStr) == 0) {
|
||||
return (UpvalRecord *)cur;
|
||||
}
|
||||
|
||||
// if types match, that's a pretty good indicator
|
||||
int score = (cur->type == searchFor->type) ? 2 : 1;
|
||||
if (score > best_score) {
|
||||
best = cur;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
static void upval_record_push_key(lua_State *L, const UpvalRecord *rec) {
|
||||
if (rec->funcKeyStr) {
|
||||
lua_pushstring(L, rec->funcKeyStr);
|
||||
} else {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, rec->funcKeyRef);
|
||||
}
|
||||
}
|
||||
|
||||
static void upvalues_join(lua_State *L, UpvalRecord *upvalsOld, UpvalRecord *upvalsNew, int moduleIdxOld, int moduleIdxNew) {
|
||||
int absOld = lua_absindex(L, moduleIdxOld);
|
||||
int absNew = lua_absindex(L, moduleIdxNew);
|
||||
|
||||
for (UpvalRecord *newrec = upvalsNew; newrec; newrec = newrec->next) {
|
||||
const UpvalRecord *oldrec = upvalues_find(upvalsOld, newrec);
|
||||
if (!oldrec) { continue; }
|
||||
|
||||
// push closures
|
||||
upval_record_push_key(L, newrec); // key
|
||||
lua_gettable(L, absNew); // new closure
|
||||
int idxNewFn = lua_gettop(L);
|
||||
|
||||
upval_record_push_key(L, oldrec);
|
||||
lua_gettable(L, absOld); // old closure
|
||||
int idxOldFn = lua_gettop(L);
|
||||
|
||||
// only look through functions
|
||||
if (!lua_isfunction(L, idxNewFn) || !lua_isfunction(L, idxOldFn)) {
|
||||
lua_pop(L, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// locate upvalue slot on each
|
||||
int slotNew = -1;
|
||||
int slotOld = -1;
|
||||
|
||||
// find new slot
|
||||
for (int i = 1; ; ++i) {
|
||||
if (!lua_upvalueid(L, idxNewFn, i)) { break; }
|
||||
if (lua_upvalueid(L, idxNewFn, i) == newrec->id) {
|
||||
slotNew = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// find old slot
|
||||
for (int i = 1; ; ++i) {
|
||||
if (!lua_upvalueid(L, idxOldFn, i)) { break; }
|
||||
if (lua_upvalueid(L, idxOldFn, i) == oldrec->id) {
|
||||
slotOld = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// join the two upvalues
|
||||
if (slotNew > 0 && slotOld > 0) {
|
||||
lua_upvaluejoin(L, idxNewFn, slotNew, idxOldFn, slotOld);
|
||||
}
|
||||
|
||||
lua_pop(L, 2); // pop both closures
|
||||
}
|
||||
}
|
||||
|
||||
static void upvalues_replace_hooks(lua_State *L) {
|
||||
for (UpvalReference *newRef = sUpvalReferences; newRef; newRef = newRef->next) {
|
||||
if (newRef->isOld) { continue; }
|
||||
if (!newRef->name) { continue; }
|
||||
|
||||
for (UpvalReference *oldRef = newRef; oldRef; oldRef = oldRef->next) {
|
||||
if (!oldRef->isOld) { continue; }
|
||||
if (!oldRef->name) { continue; }
|
||||
|
||||
if (strcmp(newRef->name, oldRef->name) == 0) {
|
||||
smlua_hook_replace_function_references(L, oldRef->reference, newRef->reference);
|
||||
newRef->active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void upvalues_print(UpvalRecord *upvalsHead) {
|
||||
for (const UpvalRecord *cur = upvalsHead; cur; cur = cur->next) {
|
||||
LOG_INFO("upval: %s, %s, %d, %p", cur->funcKeyStr ? cur->funcKeyStr : "(non-string)", cur->name, cur->ref, cur->id);
|
||||
}
|
||||
}
|
||||
|
||||
static void overwrite_module_functions(lua_State *L, int dstIdx, int srcIdx) {
|
||||
srcIdx = lua_absindex(L, srcIdx);
|
||||
dstIdx = lua_absindex(L, dstIdx);
|
||||
|
||||
lua_pushnil(L); // first key for iteration
|
||||
while (lua_next(L, srcIdx) != 0) {
|
||||
// stack: ..., dstTable?, srcTable?, key, newVal
|
||||
|
||||
int typeNew = lua_type(L, -1);
|
||||
|
||||
// lookup oldVal = dstTable[key]
|
||||
lua_pushvalue(L, -2); // copy key
|
||||
lua_gettable(L, dstIdx); // push oldVal
|
||||
int typeOld = lua_type(L, -1);
|
||||
|
||||
bool shouldOverride =
|
||||
(typeNew == LUA_TFUNCTION && typeOld == LUA_TFUNCTION) ||
|
||||
(typeNew != LUA_TNIL && typeOld == LUA_TNIL);
|
||||
|
||||
if (shouldOverride) {
|
||||
int idxNewVal = lua_gettop(L) - 1; // index of newVal
|
||||
|
||||
// overwrite oldMod[key] = newVal
|
||||
lua_pushvalue(L, -3); // key
|
||||
lua_pushvalue(L, idxNewVal); // newVal
|
||||
lua_settable(L, dstIdx); // dstTable[key] = newVal
|
||||
}
|
||||
|
||||
// pop oldVal and newVal (leave key for next lua_next)
|
||||
lua_pop(L, 2);
|
||||
}
|
||||
}
|
||||
|
||||
static void smlua_reload_module(lua_State *L, struct Mod* mod, struct ModFile *file) {
|
||||
LUA_STACK_CHECK_BEGIN(L);
|
||||
|
||||
// only handle loaded Lua modules
|
||||
if (!file->isLoadedLuaModule) { return; }
|
||||
|
||||
// build registry key for this mod's loaded table
|
||||
char registryKey[SYS_MAX_PATH + 16];
|
||||
snprintf(registryKey, sizeof(registryKey), "mod_loaded_%s", mod->relativePath);
|
||||
|
||||
// get per-mod "loaded" table
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, registryKey); // ..., loadedTable
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// get the old module table: loadedTable[file->relativePath]
|
||||
lua_getfield(L, -1, file->relativePath); // ..., loadedTable, oldMod
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_pop(L, 2);
|
||||
return;
|
||||
}
|
||||
int moduleIdxOld = lua_gettop(L);
|
||||
|
||||
// load & execute the new script -> pushes new module table
|
||||
struct ModFile *prevFile = gLuaActiveModFile;
|
||||
gLuaActiveModFile = file;
|
||||
int rc = smlua_load_script(mod, file, mod->index, false); // ..., loadedTable, oldMod, newMod
|
||||
gLuaActiveModFile = prevFile;
|
||||
|
||||
// exit on error
|
||||
if (rc != LUA_OK) {
|
||||
lua_pop(L, 3);
|
||||
return;
|
||||
}
|
||||
|
||||
int moduleIdxNew = lua_gettop(L);
|
||||
|
||||
// merge functions and join upvalues
|
||||
if (lua_istable(L, moduleIdxOld) && lua_istable(L, moduleIdxNew)) {
|
||||
// collect old upvals
|
||||
UpvalRecord *upvalsOld = NULL;
|
||||
upvalues_collect(L, &upvalsOld, moduleIdxOld, true);
|
||||
|
||||
// collect new upvals
|
||||
UpvalRecord *upvalsNew = NULL;
|
||||
upvalues_collect(L, &upvalsNew, moduleIdxNew, false);
|
||||
|
||||
// join upvals
|
||||
upvalues_join(L, upvalsOld, upvalsNew, moduleIdxOld, moduleIdxNew);
|
||||
|
||||
// replace hooks
|
||||
upvalues_replace_hooks(L);
|
||||
|
||||
// free upval collections
|
||||
upvalues_free(L, upvalsOld);
|
||||
upvalues_free(L, upvalsNew);
|
||||
|
||||
// free upval references
|
||||
upval_references_free(L);
|
||||
|
||||
// overwrite any functions in oldMod with newMod equivalents
|
||||
overwrite_module_functions(L, moduleIdxOld, moduleIdxNew);
|
||||
|
||||
// now do the same for metatables
|
||||
if (lua_getmetatable(L, moduleIdxNew)) { // pushes newMod's metatable
|
||||
int mtNewIdx = lua_gettop(L);
|
||||
if (lua_getmetatable(L, moduleIdxOld)) { // pushes oldMod's metatable
|
||||
int mtOldIdx = lua_gettop(L);
|
||||
overwrite_module_functions(L, mtOldIdx, mtNewIdx);
|
||||
lua_pop(L, 1); // pop oldMod's mt
|
||||
}
|
||||
lua_pop(L, 1); // pop newMod's mt
|
||||
}
|
||||
|
||||
// cleanup: replace newMod on stack with oldMod as return value
|
||||
lua_pushvalue(L, moduleIdxOld); // duplicate oldMod
|
||||
lua_replace(L, moduleIdxNew); // overwrite newMod slot with oldMod
|
||||
lua_pop(L, 3); // pop loadedTable and extra oldMod
|
||||
} else {
|
||||
lua_pop(L, 3); // pop loadedTable, oldMod, newMod
|
||||
}
|
||||
|
||||
LUA_STACK_CHECK_END(L);
|
||||
}
|
||||
|
||||
void smlua_live_reload_update(lua_State* L) {
|
||||
// only refresh every LIVE_RELOAD_TICK_COUNT ticks
|
||||
static int refreshTimer = 0;
|
||||
refreshTimer++;
|
||||
if ((refreshTimer % LIVE_RELOAD_TICK_COUNT) != 0) { return; }
|
||||
|
||||
// cache the active mod/file
|
||||
struct Mod* prevMod = gLuaActiveMod;
|
||||
struct ModFile* prevModFile = gLuaActiveModFile;
|
||||
|
||||
// search for mod files to update
|
||||
for (int i = 0; i < gActiveMods.entryCount; i++) {
|
||||
struct Mod *mod = gActiveMods.entries[i];
|
||||
gLuaActiveMod = mod;
|
||||
|
||||
for (int j = 0; j < mod->fileCount; j++) {
|
||||
struct ModFile* file = &mod->files[j];
|
||||
|
||||
// check modified time
|
||||
u64 timestamp = mod_get_file_mtime_seconds(file);
|
||||
if (timestamp <= file->modifiedTimestamp) { continue; }
|
||||
|
||||
// update modified time and reload the module
|
||||
file->modifiedTimestamp = timestamp;
|
||||
gLuaActiveModFile = file;
|
||||
smlua_reload_module(L, mod, file);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// restore previous active mod/file
|
||||
gLuaActiveMod = prevMod;
|
||||
gLuaActiveModFile = prevModFile;
|
||||
}
|
||||
6
src/pc/lua/smlua_live_reload.h
Normal file
6
src/pc/lua/smlua_live_reload.h
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef SMLUA_LIVE_RELOAD_H
|
||||
#define SMLUA_LIVE_RELOAD_H
|
||||
|
||||
void smlua_live_reload_update(lua_State* L);
|
||||
|
||||
#endif
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
#include "pc/mods/mods_utils.h"
|
||||
#include "pc/fs/fmem.h"
|
||||
|
||||
|
||||
// table to track loaded modules per mod
|
||||
static void smlua_init_mod_loaded_table(lua_State* L, const char* modPath) {
|
||||
// Create a unique registry key for this mod's loaded table
|
||||
|
|
@ -136,6 +135,9 @@ static int smlua_custom_require(lua_State* L) {
|
|||
struct ModFile* prevModFile = gLuaActiveModFile;
|
||||
s32 prevTop = lua_gettop(L);
|
||||
|
||||
// tag it as a loaded lua module
|
||||
file->isLoadedLuaModule = true;
|
||||
|
||||
// load and execute
|
||||
gLuaActiveModFile = file;
|
||||
smlua_load_script(activeMod, file, activeMod->index, false);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
#include "smlua.h"
|
||||
|
||||
void smlua_require_update(lua_State* L);
|
||||
void smlua_bind_custom_require(lua_State* L);
|
||||
void smlua_reload_module(lua_State *L, struct Mod* mod, struct ModFile *file);
|
||||
void smlua_init_require_system(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -748,6 +748,24 @@ void smlua_dump_table(int index) {
|
|||
lua_State* L = gLuaState;
|
||||
printf("--------------\n");
|
||||
|
||||
if (lua_getmetatable(L, index)) {
|
||||
lua_pushnil(L); // first key
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
printf("[meta] %s - %s\n",
|
||||
lua_tostring(L, -2),
|
||||
lua_typename(L, lua_type(L, -1)));
|
||||
}
|
||||
else {
|
||||
printf("[meta] %s - %s\n",
|
||||
lua_typename(L, lua_type(L, -2)),
|
||||
lua_typename(L, lua_type(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// table is in the stack at index 't'
|
||||
lua_pushnil(L); // first key
|
||||
while (lua_next(L, index) != 0) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,39 @@
|
|||
#include "pc/utils/md5.h"
|
||||
#include "pc/debuglog.h"
|
||||
#include "pc/fs/fmem.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
u64 mod_get_file_mtime_seconds(struct ModFile* file) {
|
||||
#ifdef _WIN32
|
||||
WIN32_FILE_ATTRIBUTE_DATA fad;
|
||||
if (!GetFileAttributesExA(file->cachedPath, GetFileExInfoStandard, &fad)) {
|
||||
// error; you could also GetLastError() here
|
||||
return 0;
|
||||
}
|
||||
// FILETIME is 100-ns intervals since 1601-01-01 UTC
|
||||
ULARGE_INTEGER ull;
|
||||
ull.LowPart = fad.ftLastWriteTime.dwLowDateTime;
|
||||
ull.HighPart = fad.ftLastWriteTime.dwHighDateTime;
|
||||
|
||||
const u64 EPOCH_DIFF = 116444736000000000ULL; // 100-ns from 1601 to 1970
|
||||
u64 time100ns = ull.QuadPart;
|
||||
return (time100ns - EPOCH_DIFF) / 10000000ULL; // to seconds
|
||||
#else
|
||||
struct stat st;
|
||||
if (stat(file->cachedPath, &st) != 0) {
|
||||
// error; errno is set
|
||||
return 0;
|
||||
}
|
||||
return (u64)st.st_mtime;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t mod_get_lua_size(struct Mod* mod) {
|
||||
if (!mod) { return 0; }
|
||||
|
|
@ -145,6 +178,7 @@ void mod_activate(struct Mod* mod) {
|
|||
// activate dynos models
|
||||
for (int i = 0; i < mod->fileCount; i++) {
|
||||
struct ModFile* file = &mod->files[i];
|
||||
file->modifiedTimestamp = mod_get_file_mtime_seconds(file);
|
||||
mod_cache_add(mod, file, false);
|
||||
|
||||
// forcefully update md5 hash
|
||||
|
|
@ -485,6 +519,52 @@ static void mod_extract_fields(struct Mod* mod) {
|
|||
fclose(f);
|
||||
}
|
||||
|
||||
bool mod_refresh_files(struct Mod* mod) {
|
||||
if (!mod) { return false; }
|
||||
|
||||
// clear files
|
||||
if (mod->files) {
|
||||
for (int j = 0; j < mod->fileCount; j++) {
|
||||
struct ModFile* file = &mod->files[j];
|
||||
if (file->fp != NULL) {
|
||||
f_close(file->fp);
|
||||
f_delete(file->fp);
|
||||
file->fp = NULL;
|
||||
}
|
||||
if (file->cachedPath != NULL) {
|
||||
free((char*)file->cachedPath);
|
||||
file->cachedPath = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mod->files != NULL) {
|
||||
free(mod->files);
|
||||
mod->files = NULL;
|
||||
}
|
||||
|
||||
mod->fileCount = 0;
|
||||
mod->fileCapacity = 0;
|
||||
mod->size = 0;
|
||||
|
||||
// generate packs
|
||||
dynos_generate_mod_pack(mod->basePath);
|
||||
|
||||
// read files
|
||||
if (!mod_load_files(mod, mod->name, mod->basePath)) {
|
||||
LOG_ERROR("Failed to load mod files for '%s'", mod->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// update cache
|
||||
for (int i = 0; i < mod->fileCount; i++) {
|
||||
struct ModFile* file = &mod->files[i];
|
||||
mod_cache_add(mod, file, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mod_load(struct Mods* mods, char* basePath, char* modName) {
|
||||
bool valid = false;
|
||||
|
||||
|
|
@ -530,6 +610,7 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) {
|
|||
return false;
|
||||
}
|
||||
mods->entries[modIndex] = calloc(1, sizeof(struct Mod));
|
||||
|
||||
struct Mod* mod = mods->entries[modIndex];
|
||||
if (mod == NULL) {
|
||||
LOG_ERROR("Failed to allocate mod!");
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ struct Mods;
|
|||
struct ModFile {
|
||||
char relativePath[SYS_MAX_PATH];
|
||||
size_t size;
|
||||
u64 modifiedTimestamp;
|
||||
bool isLoadedLuaModule;
|
||||
|
||||
FILE* fp;
|
||||
u64 wroteBytes;
|
||||
|
|
@ -44,9 +46,11 @@ struct Mod {
|
|||
u8 customBehaviorIndex;
|
||||
};
|
||||
|
||||
u64 mod_get_file_mtime_seconds(struct ModFile* file);
|
||||
size_t mod_get_lua_size(struct Mod* mod);
|
||||
void mod_activate(struct Mod* mod);
|
||||
void mod_clear(struct Mod* mod);
|
||||
bool mod_refresh_files(struct Mod* mod);
|
||||
bool mod_load(struct Mods* mods, char* basePath, char* modName);
|
||||
|
||||
#endif
|
||||
|
|
@ -184,6 +184,8 @@ bool network_init(enum NetworkType inNetworkType, bool reconnecting) {
|
|||
}
|
||||
#endif
|
||||
|
||||
djui_base_set_visible(&gDjuiModReload->base, network_allow_mod_dev_mode());
|
||||
|
||||
LOG_INFO("initialized");
|
||||
|
||||
return true;
|
||||
|
|
@ -637,6 +639,33 @@ static inline void color_set(Color color, u8 r, u8 g, u8 b) {
|
|||
color[2] = b;
|
||||
}
|
||||
|
||||
bool network_allow_mod_dev_mode(void) {
|
||||
return (configModDevMode && gNetworkSystem == &gNetworkSystemSocket && gNetworkType == NT_SERVER);
|
||||
}
|
||||
|
||||
void network_mod_dev_mode_reload(void) {
|
||||
network_rehost_begin();
|
||||
|
||||
for (int i = 0; i < gLocalMods.entryCount; i++) {
|
||||
struct Mod* mod = gLocalMods.entries[i];
|
||||
if (mod->enabled) {
|
||||
mod_refresh_files(mod);
|
||||
}
|
||||
}
|
||||
|
||||
djui_lua_error_clear();
|
||||
|
||||
LOG_CONSOLE(" ");
|
||||
LOG_CONSOLE("===================================================");
|
||||
LOG_CONSOLE("===================================================");
|
||||
LOG_CONSOLE("===================================================");
|
||||
LOG_CONSOLE("===================== REFRESH =====================");
|
||||
LOG_CONSOLE("===================================================");
|
||||
LOG_CONSOLE("===================================================");
|
||||
LOG_CONSOLE("===================================================");
|
||||
}
|
||||
|
||||
|
||||
void network_shutdown(bool sendLeaving, bool exiting, bool popup, bool reconnecting) {
|
||||
smlua_call_event_hooks(HOOK_ON_EXIT);
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@ void network_reset_reconnect_and_rehost(void);
|
|||
void network_reconnect_begin(void);
|
||||
bool network_is_reconnecting(void);
|
||||
void network_rehost_begin(void);
|
||||
bool network_allow_mod_dev_mode(void);
|
||||
void network_mod_dev_mode_reload(void);
|
||||
void network_update(void);
|
||||
void network_shutdown(bool sendLeaving, bool exiting, bool popup, bool reconnecting);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue