2017-04-14 19:59:01 +00:00
using System ;
using System.Runtime.InteropServices ;
2019-05-18 10:17:02 +00:00
using BizHawk.Common ;
2017-07-10 04:51:02 +00:00
using NLua ;
2017-04-14 19:59:01 +00:00
// TODO - evaluate for re-entrancy problems
namespace BizHawk.Client.Common
{
public unsafe class LuaSandbox
{
2017-05-18 19:27:22 +00:00
private static readonly System . Runtime . CompilerServices . ConditionalWeakTable < Lua , LuaSandbox > SandboxForThread = new System . Runtime . CompilerServices . ConditionalWeakTable < Lua , LuaSandbox > ( ) ;
2016-02-01 01:54:48 +00:00
2017-05-18 19:27:22 +00:00
public static Action < string > DefaultLogger { get ; set ; }
2016-01-31 08:40:48 +00:00
2016-02-01 01:54:48 +00:00
public void SetSandboxCurrentDirectory ( string dir )
2016-01-31 08:40:48 +00:00
{
2017-05-18 19:27:22 +00:00
_currentDirectory = dir ;
2016-01-31 08:40:48 +00:00
}
2017-05-18 19:27:22 +00:00
private string _currentDirectory ;
2016-02-01 01:54:48 +00:00
2017-05-18 19:27:22 +00:00
private bool CoolSetCurrentDirectory ( string path , string currDirSpeedHack = null )
2016-02-01 01:54:48 +00:00
{
2019-12-21 08:17:43 +00:00
static string CoolGetCurrentDirectory ( )
{
if ( OSTailoredCode . IsUnixHost ) return Environment . CurrentDirectory ;
//HACK to bypass Windows security checks triggered by *getting* the current directory (why), which only slow us down
var buf = new byte [ 32768 ] ;
fixed ( byte * pBuf = & buf [ 0 ] )
return System . Text . Encoding . Unicode . GetString ( buf , 0 , 2 * ( int ) Win32Imports . GetCurrentDirectoryW ( 32767 , pBuf ) ) ;
}
2019-03-20 05:01:12 +00:00
string target = $"{_currentDirectory}\\" ;
2016-02-01 01:54:48 +00:00
2019-11-29 22:35:21 +00:00
// first we'll bypass it with a general hack: don't do any setting if the value's already there (even at the OS level, setting the directory can be slow)
2017-04-14 19:59:01 +00:00
// 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?
2016-02-01 01:54:48 +00:00
if ( currDirSpeedHack = = null )
2017-05-17 18:18:26 +00:00
{
2016-02-01 01:54:48 +00:00
currDirSpeedHack = CoolGetCurrentDirectory ( ) ;
2017-05-17 18:18:26 +00:00
}
2016-02-01 01:54:48 +00:00
if ( currDirSpeedHack = = path )
2017-05-17 18:18:26 +00:00
{
2016-02-01 01:54:48 +00:00
return true ;
2017-05-17 18:18:26 +00:00
}
2016-02-01 01:54:48 +00:00
2019-11-04 04:30:05 +00:00
if ( OSTailoredCode . IsUnixHost )
2019-05-18 10:17:02 +00:00
{
2019-11-04 04:30:05 +00:00
if ( System . IO . Directory . Exists ( _currentDirectory ) ) //TODO is this necessary with Mono? extra TODO: is this necessary with .NET Core on Windows?
{
Environment . CurrentDirectory = _currentDirectory ;
return true ;
}
2019-11-03 22:04:42 +00:00
2019-11-04 04:30:05 +00:00
return false ;
2019-05-18 10:17:02 +00:00
}
2019-11-03 22:04:42 +00:00
2019-11-04 04:30:05 +00:00
//HACK to bypass Windows security checks triggered by setting the current directory, which only slow us down
fixed ( byte * pstr = & System . Text . Encoding . Unicode . GetBytes ( $"{target}\0" ) [ 0 ] )
2019-12-21 08:17:43 +00:00
return Win32Imports . SetCurrentDirectoryW ( pstr ) ;
2016-02-01 01:54:48 +00:00
}
2017-05-17 18:18:26 +00:00
private void Sandbox ( Action callback , Action exceptionCallback )
2016-02-01 01:54:48 +00:00
{
2017-04-14 19:59:01 +00:00
string savedEnvironmentCurrDir = null ;
try
{
2016-02-01 01:54:48 +00:00
savedEnvironmentCurrDir = Environment . CurrentDirectory ;
2017-05-18 19:27:22 +00:00
if ( _currentDirectory ! = null )
2017-05-17 18:18:26 +00:00
{
2017-05-18 19:27:22 +00:00
CoolSetCurrentDirectory ( _currentDirectory , savedEnvironmentCurrDir ) ;
2017-05-17 18:18:26 +00:00
}
2017-04-14 19:59:01 +00:00
EnvironmentSandbox . Sandbox ( callback ) ;
}
2017-07-10 04:51:02 +00:00
catch ( NLua . Exceptions . LuaException ex )
2016-03-31 19:11:20 +00:00
{
2017-04-14 19:59:01 +00:00
Console . WriteLine ( ex ) ;
2017-05-18 19:27:22 +00:00
DefaultLogger ( ex . ToString ( ) ) ;
2017-04-15 20:37:30 +00:00
exceptionCallback ? . Invoke ( ) ;
2017-04-14 19:59:01 +00:00
}
finally
2016-01-31 09:18:34 +00:00
{
2017-05-18 19:27:22 +00:00
if ( _currentDirectory ! = null )
2017-05-17 18:18:26 +00:00
{
2017-04-14 19:59:01 +00:00
CoolSetCurrentDirectory ( savedEnvironmentCurrDir ) ;
2017-05-17 18:18:26 +00:00
}
2017-04-14 19:59:01 +00:00
}
2016-02-01 01:54:48 +00:00
}
public static LuaSandbox CreateSandbox ( Lua thread , string initialDirectory )
{
var sandbox = new LuaSandbox ( ) ;
SandboxForThread . Add ( thread , sandbox ) ;
sandbox . SetSandboxCurrentDirectory ( initialDirectory ) ;
return sandbox ;
}
2019-12-31 22:24:46 +00:00
/// <exception cref="InvalidOperationException">could not get sandbox reference for thread (<see cref="CreateSandbox"/> has not been called)</exception>
2016-02-01 01:54:48 +00:00
public static LuaSandbox GetSandbox ( Lua thread )
{
2017-04-14 19:59:01 +00:00
// this is just placeholder.
// we shouldnt be calling a sandbox with no thread--construct a sandbox properly
2016-02-01 01:54:48 +00:00
if ( thread = = null )
{
return new LuaSandbox ( ) ;
}
lock ( SandboxForThread )
{
2019-11-29 22:35:21 +00:00
if ( SandboxForThread . TryGetValue ( thread , out var sandbox ) )
2016-02-01 01:54:48 +00:00
{
2017-05-17 18:18:26 +00:00
return sandbox ;
2016-02-01 01:54:48 +00:00
}
2017-05-17 18:18:26 +00:00
// for now: throw exception (I want to manually creating them)
// return CreateSandbox(thread);
throw new InvalidOperationException ( "HOARY GORILLA HIJINX" ) ;
2016-02-01 01:54:48 +00:00
}
}
public static void Sandbox ( Lua thread , Action callback , Action exceptionCallback = null )
{
GetSandbox ( thread ) . Sandbox ( callback , exceptionCallback ) ;
2017-04-14 19:59:01 +00:00
}
}
}