I'm trying to make a wrapper around
savestate.save with a return value that indicates whether the save happened or not. The obvious way of doing it, calling
savestate.save inside
pcall, works the way I expect when I type it into the input box of the Lua Console, but it doesn't work the same way inside a script. Inside a script,
pcall returns unexpected values, and the script then crashes with a
LuaScriptException anyway. This is with BizHawk 2.10 on Linux.
Saving a savestate to a file could fail for various reasons, such as insufficient permissions.
savestate.save calls
SaveStateLuaLibrary.Save, which doesn't have a return value; it relies on throwing an exception to signal error.
SaveStateLuaLibrary.Save in turn defers to
APIs.SaveState.Save and
_mainForm.SaveState.
In the Lua Console, if I try to save a savestate to a path to which I do not have permission, I get a
LuaScriptException stack trace (which is expected):
savestate.save("/root/foobar")
NLua.Exceptions.LuaScriptException: [string "input"]:1: A .NET exception occured in user-code
System.UnauthorizedAccessException: Access to the path "/root/foobar" is denied.
at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x001ef] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean isAsync, System.Boolean anonymous) [0x00000] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access) [0x00000] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
at (wrapper remoting-invoke-with-check) System.IO.FileStream..ctor(string,System.IO.FileMode,System.IO.FileAccess)
at BizHawk.Client.Common.FrameworkZipWriter..ctor (System.String path, System.Int32 compressionLevel) [0x00006] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.Common.ZipStateSaver..ctor (System.String path, System.Int32 compressionLevel) [0x00006] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.Common.SavestateFile.Create (System.String filename, BizHawk.Client.Common.SaveStateConfig config) [0x00007] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.EmuHawk.MainForm.SaveState (System.String path, System.String userFriendlyStateName, System.Boolean fromLua, System.Boolean suppressOSD) [0x00091] in <f3ae97cbaae74109853bdae22c2312c8>:0
at BizHawk.Client.Common.SaveStateApi.Save (System.String path, System.Boolean suppressOSD) [0x00000] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.Common.SaveStateLuaLibrary.Save (System.String path, System.Boolean suppressOSD) [0x00023] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0007c] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
In the Lua Console, wrapping the call in
pcall works as I expect. Saving the savestate fails, and
pcall returns
false and a string representation of the error:
pcall(savestate.save, "/root/foobar")
False NLua.Exceptions.LuaScriptException: A .NET exception occured in user-code
The above technique with
pcall would be adequate for my needs. The problem is, it doesn't work the same way inside a script. I create a file test.lua:
print("before")
print(pcall(savestate.save, "/root/foobar"))
print("after")
When I run the script with
./EmuHawkMono.sh --lua=test.lua ROMFILE, I get one line of output for the
"before", one line of output for the
pcall, and then a
LuaScriptException stack trace:
before
False /root/foobar
NLua.Exceptions.LuaScriptException: A .NET exception occured in user-code
System.UnauthorizedAccessException: Access to the path "/root/foobar" is denied.
at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x001ef] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean isAsync, System.Boolean anonymous) [0x00000] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access) [0x00000] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
at (wrapper remoting-invoke-with-check) System.IO.FileStream..ctor(string,System.IO.FileMode,System.IO.FileAccess)
at BizHawk.Client.Common.FrameworkZipWriter..ctor (System.String path, System.Int32 compressionLevel) [0x00006] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.Common.ZipStateSaver..ctor (System.String path, System.Int32 compressionLevel) [0x00006] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.Common.SavestateFile.Create (System.String filename, BizHawk.Client.Common.SaveStateConfig config) [0x00007] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.EmuHawk.MainForm.SaveState (System.String path, System.String userFriendlyStateName, System.Boolean fromLua, System.Boolean suppressOSD) [0x00091] in <f3ae97cbaae74109853bdae22c2312c8>:0
at BizHawk.Client.Common.SaveStateApi.Save (System.String path, System.Boolean suppressOSD) [0x00000] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at BizHawk.Client.Common.SaveStateLuaLibrary.Save (System.String path, System.Boolean suppressOSD) [0x00023] in <2abb4b338b4a4b62a6b02f91d843b06c>:0
at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0007c] in <12b418a7818c4ca0893feeaaf67f1e7f>:0
Important points to notice:
- The pcall function actually runs and returns. The first return value is false, as expected.
- However, the second return of pcall is not the expected LuaScriptException error message, but rather the argument to savestate.save, "/root/foobar".
- Even though pcall catches the error, the script crashes with a LuaScriptException stack trace anyway, before the print("after") line.
But get this: if I don't immediately
print the return values of
pcall, but instead store them in variables, then the stack trace does not occur until
after the
print("after") line:
print("before")
x, y = pcall(savestate.save, "/root/foobar")
print("after")
before
after
NLua.Exceptions.LuaScriptException: A .NET exception occured in user-code
System.UnauthorizedAccessException: Access to the path "/root/foobar" is denied.
...etc...
The
x and
y variables that were assigned to in the script remain set, and their values can still be seen in the Lua Console:
print(x, y)
False /root/foobar
I'm not sure if this is a bug in BizHawk, or if I am doing something wrong. The fact that
pcall returns one of its arguments, rather than the expected error messages, and that the script doesn't crash until the
next call to
print, makes me suspect something is getting mixed up with the Lua
virtual stack.
Below is the function I would like to be able to write. In fact, I can define the function in a script, and call it from the Lua Console, and it works as expected. But if I try to call it from the same script, it exhibits the inconsistencies described above.
function save_savestate_to_file(path)
local ok, err = pcall(savestate.save, path)
if ok then
return true
else
return nil, err
end
end