diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs index 6619ea5907..05188fe281 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs @@ -74,6 +74,11 @@ namespace BizHawk.Client.Common [LuaMethod("closerom", "Closes the loaded Rom")] public void CloseRom() { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("client.closerom() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsRebootingCore = true; APIs.EmuClient.CloseRom(); _luaLibsImpl.IsRebootingCore = false; @@ -102,6 +107,11 @@ namespace BizHawk.Client.Common [LuaMethod("seekframe", "Makes the emulator seek to the frame specified")] public void SeekFrame(int frame) { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("client.seekframe() is not allowed during input/memory callbacks"); + } + if (frame < Emulator.Frame) { Log("client.seekframe: cannot seek backwards"); @@ -200,6 +210,11 @@ namespace BizHawk.Client.Common [LuaMethod("openrom", "Loads a ROM from the given path. Returns true if the ROM was successfully loaded, otherwise false.")] public bool OpenRom(string path) { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("client.openrom() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsRebootingCore = true; var success = APIs.EmuClient.OpenRom(path); _luaLibsImpl.IsRebootingCore = false; @@ -235,6 +250,11 @@ namespace BizHawk.Client.Common [LuaMethod("reboot_core", "Reboots the currently loaded core")] public void RebootCore() { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("client.reboot_core() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsRebootingCore = true; APIs.EmuClient.RebootCore(); _luaLibsImpl.IsRebootingCore = false; diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/MemorySavestateLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/MemorySavestateLuaLibrary.cs index 5544a88017..58f9cec115 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/MemorySavestateLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/MemorySavestateLuaLibrary.cs @@ -14,21 +14,35 @@ namespace BizHawk.Client.Common [LuaMethodExample("local mmsvstsvcst = memorysavestate.savecorestate( );")] [LuaMethod("savecorestate", "creates a core savestate and stores it in memory. Note: a core savestate is only the raw data from the core, and not extras such as movie input logs, or framebuffers. Returns a unique identifer for the savestate")] public string SaveCoreStateToMemory() - => APIs.MemorySaveState.SaveCoreStateToMemory(); + { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("memorysavestate.savecorestate() is not allowed during input/memory callbacks"); + } + + return APIs.MemorySaveState!.SaveCoreStateToMemory(); + } [LuaMethodExample("memorysavestate.loadcorestate( \"3fcf120f-0778-43fd-b2c5-460fb7d34184\" );")] [LuaMethod("loadcorestate", "loads an in memory state with the given identifier")] public void LoadCoreStateFromMemory(string identifier) - => APIs.MemorySaveState.LoadCoreStateFromMemory(identifier); + { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("memorysavestate.loadcorestate() is not allowed during input/memory callbacks"); + } + + APIs.MemorySaveState!.LoadCoreStateFromMemory(identifier); + } [LuaMethodExample("memorysavestate.removestate( \"3fcf120f-0778-43fd-b2c5-460fb7d34184\" );")] [LuaMethod("removestate", "removes the savestate with the given identifier from memory")] public void DeleteState(string identifier) - => APIs.MemorySaveState.DeleteState(identifier); + => APIs.MemorySaveState!.DeleteState(identifier); [LuaMethodExample("memorysavestate.clearstatesfrommemory( );")] [LuaMethod("clearstatesfrommemory", "clears all savestates stored in memory")] public void ClearInMemoryStates() - => APIs.MemorySaveState.ClearInMemoryStates(); + => APIs.MemorySaveState!.ClearInMemoryStates(); } } diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/MovieLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/MovieLuaLibrary.cs index c77d116ed9..577f74999a 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/MovieLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/MovieLuaLibrary.cs @@ -71,6 +71,11 @@ namespace BizHawk.Client.Common [LuaMethod("play_from_start", "Resets the core to frame 0 with the currently loaded movie in playback mode. If a path to a movie is specified, attempts to load it, then continues with playback if it was successful. Returns true iff successful.")] public bool PlayFromStart(string path = "") { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("movie.play_from_start() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsRebootingCore = true; var success = APIs.Movie.PlayFromStart(path); _luaLibsImpl.IsRebootingCore = false; diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/SaveStateLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/SaveStateLuaLibrary.cs index 5d9c00e7c6..edca18f644 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/SaveStateLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/SaveStateLuaLibrary.cs @@ -13,6 +13,11 @@ namespace BizHawk.Client.Common [LuaMethod("load", "Loads a savestate with the given path. Returns true iff succeeded. If EmuHawk is deferring quicksaves, to TAStudio for example, that form will do what it likes (and the path is ignored).")] public bool Load(string path, bool suppressOSD = false) { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("savestate.load() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsUpdateSupressed = true; var success = APIs.SaveState.Load(path, suppressOSD); _luaLibsImpl.IsUpdateSupressed = false; @@ -23,6 +28,11 @@ namespace BizHawk.Client.Common [LuaMethod("loadslot", "Loads the savestate at the given slot number (must be an integer between 1 and 10). Returns true iff succeeded. If EmuHawk is deferring quicksaves, to TAStudio for example, that form will do what it likes with the slot number.")] public bool LoadSlot(int slotNum, bool suppressOSD = false) { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("savestate.loadslot() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsUpdateSupressed = true; var success = APIs.SaveState.LoadSlot(slotNum, suppressOSD: suppressOSD); _luaLibsImpl.IsUpdateSupressed = false; @@ -32,11 +42,25 @@ namespace BizHawk.Client.Common [LuaMethodExample("savestate.save( \"C:\\state.bin\" );")] [LuaMethod("save", "Saves a state at the given path. If EmuHawk is deferring quicksaves, to TAStudio for example, that form will do what it likes (and the path is ignored).")] public void Save(string path, bool suppressOSD = false) - => APIs.SaveState.Save(path, suppressOSD); + { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("savestate.save() is not allowed during input/memory callbacks"); + } + + APIs.SaveState.Save(path, suppressOSD); + } [LuaMethodExample("savestate.saveslot( 7 );")] [LuaMethod("saveslot", "Saves a state at the given save slot (must be an integer between 1 and 10). If EmuHawk is deferring quicksaves, to TAStudio for example, that form will do what it likes with the slot number.")] public void SaveSlot(int slotNum, bool suppressOSD = false) - => APIs.SaveState.SaveSlot(slotNum, suppressOSD); + { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("savestate.saveslot() is not allowed during input/memory callbacks"); + } + + APIs.SaveState.SaveSlot(slotNum, suppressOSD); + } } } diff --git a/src/BizHawk.Client.Common/lua/ILuaLibraries.cs b/src/BizHawk.Client.Common/lua/ILuaLibraries.cs index d03ec1dfc6..2a36e2df45 100644 --- a/src/BizHawk.Client.Common/lua/ILuaLibraries.cs +++ b/src/BizHawk.Client.Common/lua/ILuaLibraries.cs @@ -17,6 +17,9 @@ namespace BizHawk.Client.Common bool IsUpdateSupressed { get; set; } + /// not really sure if this is the right place to put it, multiple different places need this... + bool IsInInputOrMemoryCallback { get; set; } + LuaFunctionList RegisteredFunctions { get; } public PathEntryCollection PathEntries { get; } diff --git a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs index 324bed0036..d24657083c 100644 --- a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs +++ b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs @@ -31,12 +31,14 @@ namespace BizHawk.Client.Common private readonly LuaFunction _function; - public NamedLuaFunction(LuaFunction function, string theEvent, Action logCallback, LuaFile luaFile, Func createThreadCallback, string name = null) + public NamedLuaFunction(LuaFunction function, string theEvent, Action logCallback, LuaFile luaFile, + Func createThreadCallback, ILuaLibraries luaLibraries, string name = null) { _function = function; Name = name ?? "Anonymous"; Event = theEvent; CreateThreadCallback = createThreadCallback; + LuaLibraries = luaLibraries; // When would a file be null? // When a script is loaded with a callback, but no infinite loop so it closes @@ -66,8 +68,30 @@ namespace BizHawk.Client.Common logCallback($"error running function attached by the event {Event}\nError message: {ex.Message}"); } }; - InputCallback = () => Callback(Array.Empty()); - MemCallback = (addr, val, flags) => Callback(new object[] { addr, val, flags }); + InputCallback = () => + { + LuaLibraries.IsInInputOrMemoryCallback = true; + try + { + Callback(Array.Empty()); + } + finally + { + LuaLibraries.IsInInputOrMemoryCallback = false; + } + }; + MemCallback = (addr, val, flags) => + { + LuaLibraries.IsInInputOrMemoryCallback = true; + try + { + Callback(new object[] { addr, val, flags }); + } + finally + { + LuaLibraries.IsInInputOrMemoryCallback = false; + } + }; } public void DetachFromScript() @@ -87,6 +111,11 @@ namespace BizHawk.Client.Common public LuaFile LuaFile { get; private set; } + /// + /// HACK + /// + private ILuaLibraries LuaLibraries { get; } + private Func CreateThreadCallback { get; } public string Event { get; } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs index 739ad36370..fce1eca24a 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/TAStudioLuaLibrary.cs @@ -139,6 +139,11 @@ namespace BizHawk.Client.EmuHawk { if (Engaged()) { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("tastudio.setplayback() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsUpdateSupressed = true; int f; @@ -298,6 +303,11 @@ namespace BizHawk.Client.EmuHawk { if (Engaged()) { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("tastudio.applyinputchanges() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsUpdateSupressed = true; if (_changeList.Count > 0) @@ -424,6 +434,11 @@ namespace BizHawk.Client.EmuHawk { if (Engaged()) { + if (_luaLibsImpl.IsInInputOrMemoryCallback) + { + throw new InvalidOperationException("tastudio.loadbranch() is not allowed during input/memory callbacks"); + } + _luaLibsImpl.IsUpdateSupressed = true; Tastudio.LoadBranchByIndex(index); diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs index f09a97d571..1393e8ac86 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs @@ -165,6 +165,8 @@ namespace BizHawk.Client.EmuHawk public bool IsUpdateSupressed { get; set; } + public bool IsInInputOrMemoryCallback { get; set; } + private readonly IDictionary Libraries = new Dictionary(); private EventWaitHandle LuaWait; @@ -309,7 +311,7 @@ namespace BizHawk.Client.EmuHawk LuaFile luaFile, string name = null) { - var nlf = new NamedLuaFunction(function, theEvent, logCallback, luaFile, () => _lua.NewThread(), name); + var nlf = new NamedLuaFunction(function, theEvent, logCallback, luaFile, () => _lua.NewThread(), this, name); RegisteredFunctions.Add(nlf); return nlf; }