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
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetCurrentDirectoryW ( byte * lpPathName ) ;
[DllImport("kernel32.dll", SetLastError=true)]
static extern uint GetCurrentDirectoryW ( uint nBufferLength , byte * pBuffer ) ;
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-03-20 05:01:12 +00:00
string target = $"{_currentDirectory}\\" ;
2016-02-01 01:54:48 +00:00
2017-04-14 19:59:01 +00:00
// 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?
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-05-18 10:17:02 +00:00
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
2019-03-20 05:01:12 +00:00
fixed ( byte * pstr = & System . Text . Encoding . Unicode . GetBytes ( $"{target}\0" ) [ 0 ] )
2017-04-14 19:59:01 +00:00
return SetCurrentDirectoryW ( pstr ) ;
2019-05-18 10:17:02 +00:00
}
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 ;
}
}
2016-02-01 01:54:48 +00:00
}
2017-05-17 18:18:26 +00:00
private string CoolGetCurrentDirectory ( )
2016-02-01 01:54:48 +00:00
{
2019-05-18 10:17:02 +00:00
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
2017-04-14 19:59:01 +00:00
var buf = new byte [ 32768 ] ;
2019-05-18 10:17:02 +00:00
fixed ( byte * pBuf = & buf [ 0 ] )
return System . Text . Encoding . Unicode . GetString ( buf , 0 , 2 * ( int ) GetCurrentDirectoryW ( 32767 , pBuf ) ) ;
}
else
{
2017-04-14 19:59:01 +00:00
return Environment . CurrentDirectory ;
2019-05-18 10:17:02 +00:00
}
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 ;
}
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 )
{
LuaSandbox sandbox ;
if ( SandboxForThread . TryGetValue ( thread , out sandbox ) )
{
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
}
}
}