using System; using System.Runtime.InteropServices; using BizHawk.Common; using NLua; // TODO - evaluate for re-entrancy problems namespace BizHawk.Client.Common { public unsafe class LuaSandbox { private static readonly System.Runtime.CompilerServices.ConditionalWeakTable SandboxForThread = new System.Runtime.CompilerServices.ConditionalWeakTable(); public static Action DefaultLogger { get; set; } public void SetSandboxCurrentDirectory(string dir) { _currentDirectory = dir; } private string _currentDirectory; [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetCurrentDirectoryW(byte* lpPathName); [DllImport("kernel32.dll", SetLastError=true)] static extern uint GetCurrentDirectoryW(uint nBufferLength, byte* pBuffer); private bool CoolSetCurrentDirectory(string path, string currDirSpeedHack = null) { string target = $"{_currentDirectory}\\"; // first we'll bypass it with a general hack: dont do any setting if the value's already there (even at the OS level, setting the directory can be slow) // yeah I know, not the smoothest move to compare strings here, in case path normalization is happening at some point // but you got any better ideas? if (currDirSpeedHack == null) { currDirSpeedHack = CoolGetCurrentDirectory(); } if (currDirSpeedHack == path) { return true; } if (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows) { // WARNING: setting the current directory is SLOW!!! security checks for some reason. // so we're bypassing it with windows hacks fixed (byte* pstr = &System.Text.Encoding.Unicode.GetBytes($"{target}\0")[0]) return SetCurrentDirectoryW(pstr); } else { if (System.IO.Directory.Exists(_currentDirectory)) // race condition for great justice { Environment.CurrentDirectory = _currentDirectory; // thats right, you can't set a directory as current that doesnt exist because .net's got to do SENSELESS SLOW-ASS SECURITY CHECKS on it and it can't do that on a NONEXISTENT DIRECTORY return true; } else { return false; } } } private string CoolGetCurrentDirectory() { if (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows) { // GUESS WHAT! // .NET DOES A SECURITY CHECK ON THE DIRECTORY WE JUST RETRIEVED // AS IF ASKING FOR THE CURRENT DIRECTORY IS EQUIVALENT TO TRYING TO ACCESS IT // SCREW YOU var buf = new byte[32768]; fixed (byte* pBuf = &buf[0]) return System.Text.Encoding.Unicode.GetString(buf, 0, 2 * (int) GetCurrentDirectoryW(32767, pBuf)); } else { return Environment.CurrentDirectory; } } private void Sandbox(Action callback, Action exceptionCallback) { string savedEnvironmentCurrDir = null; try { savedEnvironmentCurrDir = Environment.CurrentDirectory; if (_currentDirectory != null) { CoolSetCurrentDirectory(_currentDirectory, savedEnvironmentCurrDir); } EnvironmentSandbox.Sandbox(callback); } catch (NLua.Exceptions.LuaException ex) { Console.WriteLine(ex); DefaultLogger(ex.ToString()); exceptionCallback?.Invoke(); } finally { if (_currentDirectory != null) { CoolSetCurrentDirectory(savedEnvironmentCurrDir); } } } public static LuaSandbox CreateSandbox(Lua thread, string initialDirectory) { var sandbox = new LuaSandbox(); SandboxForThread.Add(thread, sandbox); sandbox.SetSandboxCurrentDirectory(initialDirectory); return sandbox; } public static LuaSandbox GetSandbox(Lua thread) { // this is just placeholder. // we shouldnt be calling a sandbox with no thread--construct a sandbox properly if (thread == null) { return new LuaSandbox(); } lock (SandboxForThread) { LuaSandbox sandbox; if (SandboxForThread.TryGetValue(thread, out sandbox)) { return sandbox; } // for now: throw exception (I want to manually creating them) // return CreateSandbox(thread); throw new InvalidOperationException("HOARY GORILLA HIJINX"); } } public static void Sandbox(Lua thread, Action callback, Action exceptionCallback = null) { GetSandbox(thread).Sandbox(callback, exceptionCallback); } } }