2011-01-11 02:55:51 +00:00
using System ;
2017-04-10 00:59:23 +00:00
using System.Diagnostics ;
2012-10-29 08:37:22 +00:00
using System.IO ;
2013-07-27 00:30:08 +00:00
using System.Collections.Generic ;
2012-10-29 08:37:22 +00:00
using System.Reflection ;
using System.Runtime.InteropServices ;
2011-01-11 02:55:51 +00:00
using System.Windows.Forms ;
2019-05-18 10:17:02 +00:00
2011-05-23 00:33:05 +00:00
using Microsoft.VisualBasic.ApplicationServices ;
2011-01-11 02:55:51 +00:00
2013-10-27 22:07:40 +00:00
using BizHawk.Common ;
2013-10-25 00:57:23 +00:00
using BizHawk.Client.Common ;
2013-11-03 03:54:37 +00:00
namespace BizHawk.Client.EmuHawk
2011-01-11 02:55:51 +00:00
{
2019-05-18 05:14:08 +00:00
internal static class Program
2011-06-19 23:39:25 +00:00
{
2013-01-02 20:11:27 +00:00
static Program ( )
2011-06-19 23:39:25 +00:00
{
2016-05-05 14:01:01 +00:00
//this needs to be done before the warnings/errors show up
Application . EnableVisualStyles ( ) ;
Application . SetCompatibleTextRenderingDefault ( false ) ;
2019-05-18 05:38:51 +00:00
if ( EXE_PROJECT . OSTailoredCode . CurrentOS = = EXE_PROJECT . OSTailoredCode . DistinctOS . Windows )
2019-05-18 04:34:15 +00:00
{
2019-05-18 05:38:51 +00:00
var libLoader = EXE_PROJECT . OSTailoredCode . LinkedLibManager ;
2018-11-04 17:05:20 +00:00
2019-05-18 04:34:15 +00:00
//http://www.codeproject.com/Articles/310675/AppDomain-AssemblyResolve-Event-Tips
2018-11-04 17:05:20 +00:00
2019-05-18 04:34:15 +00:00
//try loading libraries we know we'll need
//something in the winforms, etc. code below will cause .net to popup a missing msvcr100.dll in case that one's missing
//but oddly it lets us proceed and we'll then catch it here
2019-10-13 05:23:14 +00:00
var d3dx9 = libLoader . LoadOrNull ( "d3dx9_43.dll" ) ;
var vc2015 = libLoader . LoadOrNull ( "vcruntime140.dll" ) ;
var vc2012 = libLoader . LoadOrNull ( "msvcr120.dll" ) ; //TODO - check version?
var vc2010 = libLoader . LoadOrNull ( "msvcr100.dll" ) ; //TODO - check version?
var vc2010p = libLoader . LoadOrNull ( "msvcp100.dll" ) ;
var reqPresent = vc2015 . HasValue & & vc2010 . HasValue & & vc2012 . HasValue & & vc2010p . HasValue ;
var optPresent = d3dx9 . HasValue ;
if ( ! reqPresent | | ! optPresent )
2019-05-18 04:34:15 +00:00
{
2019-05-18 05:14:08 +00:00
var alertLines = new [ ]
{
"[ OK ] .NET CLR (You wouldn't even get here without it)" ,
2019-10-13 05:23:14 +00:00
$"[{(d3dx9.HasValue ? " OK " : " WARN ")}] Direct3d 9" ,
$"[{(vc2010.HasValue && vc2010p.HasValue ? " OK " : " FAIL ")}] Visual C++ 2010 SP1 Runtime" ,
$"[{(vc2012.HasValue ? " OK " : " FAIL ")}] Visual C++ 2012 Runtime" ,
$"[{(vc2015.HasValue ? " OK " : " FAIL ")}] Visual C++ 2015 Runtime"
2019-05-18 05:14:08 +00:00
} ;
2019-10-13 05:23:14 +00:00
var box = new CustomControls . PrereqsAlert ( reqPresent )
2019-05-18 05:14:08 +00:00
{
2019-10-12 21:56:20 +00:00
textBox1 = { Text = string . Join ( Environment . NewLine , alertLines ) }
2019-05-18 05:14:08 +00:00
} ;
2019-05-18 04:34:15 +00:00
box . ShowDialog ( ) ;
2019-10-13 05:23:14 +00:00
if ( ! reqPresent ) Process . GetCurrentProcess ( ) . Kill ( ) ;
2019-05-18 04:34:15 +00:00
}
2016-01-14 07:50:41 +00:00
2019-10-13 05:23:14 +00:00
foreach ( var p in new [ ] { d3dx9 , vc2015 , vc2012 , vc2010 , vc2010p } )
if ( p . HasValue ) libLoader . FreeByPtr ( p . Value ) ;
2016-01-14 07:50:41 +00:00
2018-11-04 17:05:20 +00:00
// this will look in subdirectory "dll" to load pinvoked stuff
2019-05-18 05:14:08 +00:00
var dllDir = Path . Combine ( Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) , "dll" ) ;
2018-11-04 17:05:20 +00:00
SetDllDirectory ( dllDir ) ;
2013-07-27 00:30:08 +00:00
2018-11-04 17:05:20 +00:00
//in case assembly resolution fails, such as if we moved them into the dll subdiretory, this event handler can reroute to them
2019-05-18 05:14:08 +00:00
AppDomain . CurrentDomain . AssemblyResolve + = CurrentDomain_AssemblyResolve ;
2012-09-04 00:20:36 +00:00
2018-11-04 17:05:20 +00:00
//but before we even try doing that, whack the MOTW from everything in that directory (thats a dll)
//otherwise, some people will have crashes at boot-up due to .net security disliking MOTW.
//some people are getting MOTW through a combination of browser used to download bizhawk, and program used to dearchive it
WhackAllMOTW ( dllDir ) ;
2017-02-22 00:23:02 +00:00
2018-11-04 17:05:20 +00:00
//We need to do it here too... otherwise people get exceptions when externaltools we distribute try to startup
}
2013-01-02 20:11:27 +00:00
}
[STAThread]
2019-05-18 05:14:08 +00:00
private static int Main ( string [ ] args )
2013-01-02 20:11:27 +00:00
{
2019-05-18 10:25:33 +00:00
var exitCode = SubMain ( args ) ;
if ( EXE_PROJECT . OSTailoredCode . CurrentOS = = EXE_PROJECT . OSTailoredCode . DistinctOS . Linux )
{
Console . WriteLine ( "BizHawk has completed its shutdown routines, killing process..." ) ;
Process . GetCurrentProcess ( ) . Kill ( ) ;
}
return exitCode ;
2013-01-02 20:11:27 +00:00
}
//NoInlining should keep this code from getting jammed into Main() which would create dependencies on types which havent been setup by the resolver yet... or something like that
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
2019-05-18 05:14:08 +00:00
private static int SubMain ( string [ ] args )
2013-01-02 20:11:27 +00:00
{
2013-01-01 23:10:47 +00:00
// this check has to be done VERY early. i stepped through a debug build with wrong .dll versions purposely used,
// and there was a TypeLoadException before the first line of SubMain was reached (some static ColorType init?)
2012-12-25 20:36:04 +00:00
// zero 25-dec-2012 - only do for public builds. its annoying during development
2019-08-11 16:20:09 +00:00
// and don't bother when installed from a package manager i.e. not Windows --yoshi
if ( ! VersionInfo . DeveloperBuild & & EXE_PROJECT . OSTailoredCode . CurrentOS = = EXE_PROJECT . OSTailoredCode . DistinctOS . Windows )
2012-12-17 15:38:56 +00:00
{
var thisversion = typeof ( Program ) . Assembly . GetName ( ) . Version ;
2015-01-01 20:54:01 +00:00
var utilversion = Assembly . Load ( new AssemblyName ( "Bizhawk.Client.Common" ) ) . GetName ( ) . Version ;
var emulversion = Assembly . Load ( new AssemblyName ( "Bizhawk.Emulation.Cores" ) ) . GetName ( ) . Version ;
2012-12-17 15:38:56 +00:00
if ( thisversion ! = utilversion | | thisversion ! = emulversion )
{
MessageBox . Show ( "Conflicting revisions found! Don't mix .dll versions!" ) ;
2015-12-15 08:22:44 +00:00
return - 1 ;
2012-12-17 15:38:56 +00:00
}
}
2019-10-13 05:23:14 +00:00
TempFileManager . Start ( ) ;
2016-05-05 14:01:01 +00:00
2015-11-15 08:27:48 +00:00
HawkFile . ArchiveHandlerFactory = new SevenZipSharpArchiveHandler ( ) ;
2016-04-02 19:33:34 +00:00
2019-05-29 05:06:38 +00:00
string cmdConfigFile = ArgParser . GetCmdConfigFile ( args ) ;
if ( cmdConfigFile ! = null ) PathManager . SetDefaultIniPath ( cmdConfigFile ) ;
2018-03-22 19:55:29 +00:00
try
{
2018-12-23 05:23:57 +00:00
Global . Config = ConfigService . Load < Config > ( PathManager . DefaultIniPath ) ;
2018-03-22 19:55:29 +00:00
} catch ( Exception e ) {
new ExceptionBox ( e ) . ShowDialog ( ) ;
new ExceptionBox ( "Since your config file is corrupted, we're going to recreate it. Back it up before proceeding if you want to investigate further." ) . ShowDialog ( ) ;
2018-12-23 05:23:57 +00:00
File . Delete ( PathManager . DefaultIniPath ) ;
Global . Config = ConfigService . Load < Config > ( PathManager . DefaultIniPath ) ;
2018-03-22 19:55:29 +00:00
}
2013-08-11 19:55:13 +00:00
Global . Config . ResolveDefaults ( ) ;
2018-03-22 19:55:29 +00:00
2019-10-13 05:23:14 +00:00
StringLogUtil . DefaultToDisk = Global . Config . MoviesOnDisk ;
StringLogUtil . DefaultToAWE = Global . Config . MoviesInAWE ;
2013-10-27 22:07:40 +00:00
2017-05-22 16:16:15 +00:00
// super hacky! this needs to be done first. still not worth the trouble to make this system fully proper
2019-05-18 05:14:08 +00:00
if ( Array . Exists ( args , arg = > arg . StartsWith ( "--gdi" , StringComparison . InvariantCultureIgnoreCase ) ) )
2014-12-16 19:35:52 +00:00
{
2019-05-18 05:14:08 +00:00
Global . Config . DispMethod = Config . EDispMethod . GdiPlus ;
2014-12-16 19:35:52 +00:00
}
2017-05-22 16:16:15 +00:00
// create IGL context. we do this whether or not the user has selected OpenGL, so that we can run opengl-based emulator cores
2016-04-02 19:33:34 +00:00
GlobalWin . IGL_GL = new Bizware . BizwareGL . Drivers . OpenTK . IGL_TK ( 2 , 0 , false ) ;
2014-12-08 02:15:42 +00:00
2017-05-22 16:16:15 +00:00
// setup the GL context manager, needed for coping with multiple opengl cores vs opengl display method
2016-02-22 06:23:20 +00:00
GLManager . CreateInstance ( GlobalWin . IGL_GL ) ;
2015-03-06 03:05:46 +00:00
GlobalWin . GLManager = GLManager . Instance ;
2014-01-27 05:37:04 +00:00
2014-12-08 02:15:42 +00:00
//now create the "GL" context for the display method. we can reuse the IGL_TK context if opengl display method is chosen
2019-05-18 05:38:51 +00:00
if ( EXE_PROJECT . OSTailoredCode . CurrentOS ! = EXE_PROJECT . OSTailoredCode . DistinctOS . Windows )
2019-05-18 05:30:29 +00:00
Global . Config . DispMethod = Config . EDispMethod . GdiPlus ;
REDO_DISPMETHOD :
2014-12-08 02:15:42 +00:00
if ( Global . Config . DispMethod = = Config . EDispMethod . GdiPlus )
GlobalWin . GL = new Bizware . BizwareGL . Drivers . GdiPlus . IGL_GdiPlus ( ) ;
else if ( Global . Config . DispMethod = = Config . EDispMethod . SlimDX9 )
2016-05-05 14:01:01 +00:00
{
try
{
GlobalWin . GL = new Bizware . BizwareGL . Drivers . SlimDX . IGL_SlimDX9 ( ) ;
}
catch ( Exception ex )
{
2019-05-18 05:14:08 +00:00
new ExceptionBox ( new Exception ( "Initialization of Direct3d 9 Display Method failed; falling back to GDI+" , ex ) ) . ShowDialog ( ) ;
2017-05-22 16:16:15 +00:00
// fallback
2016-05-05 14:01:01 +00:00
Global . Config . DispMethod = Config . EDispMethod . GdiPlus ;
goto REDO_DISPMETHOD ;
}
}
2014-12-08 02:15:42 +00:00
else
2014-12-16 20:24:14 +00:00
{
2014-12-08 02:15:42 +00:00
GlobalWin . GL = GlobalWin . IGL_GL ;
2017-05-22 16:16:15 +00:00
// check the opengl version and dont even try to boot this crap up if its too old
2019-05-18 05:14:08 +00:00
if ( GlobalWin . IGL_GL . Version < 200 )
2014-12-16 20:24:14 +00:00
{
2017-05-22 16:16:15 +00:00
// fallback
2014-12-16 20:24:14 +00:00
Global . Config . DispMethod = Config . EDispMethod . GdiPlus ;
goto REDO_DISPMETHOD ;
}
}
2014-12-08 02:15:42 +00:00
2017-05-22 16:16:15 +00:00
// try creating a GUI Renderer. If that doesn't succeed. we fallback
2016-04-02 19:33:34 +00:00
try
{
2017-04-29 21:49:29 +00:00
using ( GlobalWin . GL . CreateRenderer ( ) ) { }
2016-04-02 19:33:34 +00:00
}
catch ( Exception ex )
{
2019-05-18 05:14:08 +00:00
new ExceptionBox ( new Exception ( "Initialization of Display Method failed; falling back to GDI+" , ex ) ) . ShowDialog ( ) ;
2016-04-02 19:33:34 +00:00
//fallback
Global . Config . DispMethod = Config . EDispMethod . GdiPlus ;
goto REDO_DISPMETHOD ;
}
2019-05-18 05:38:51 +00:00
if ( EXE_PROJECT . OSTailoredCode . CurrentOS = = EXE_PROJECT . OSTailoredCode . DistinctOS . Windows )
2017-04-10 00:59:23 +00:00
{
2018-11-04 17:05:20 +00:00
//WHY do we have to do this? some intel graphics drivers (ig7icd64.dll 10.18.10.3304 on an unknown chip on win8.1) are calling SetDllDirectory() for the process, which ruins stuff.
//The relevant initialization happened just before in "create IGL context".
//It isn't clear whether we need the earlier SetDllDirectory(), but I think we do.
//note: this is pasted instead of being put in a static method due to this initialization code being sensitive to things like that, and not wanting to cause it to break
//pasting should be safe (not affecting the jit order of things)
2019-05-18 05:14:08 +00:00
var dllDir = Path . Combine ( Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) , "dll" ) ;
2018-11-04 17:05:20 +00:00
SetDllDirectory ( dllDir ) ;
2015-03-01 19:29:33 +00:00
}
2015-10-12 02:03:09 +00:00
2019-05-18 04:27:50 +00:00
try
{
if ( Global . Config . SingleInstanceMode )
{
try
{
2019-05-18 05:14:08 +00:00
new SingleInstanceController ( args ) . Run ( ) ;
2019-05-18 04:27:50 +00:00
}
catch ( ObjectDisposedException )
{
// Eat it, MainForm disposed itself and Run attempts to dispose of itself. Eventually we would want to figure out a way to prevent that, but in the meantime it is harmless, so just eat the error
}
}
else
{
using ( var mf = new MainForm ( args ) )
{
var title = mf . Text ;
mf . Show ( ) ;
mf . Text = title ;
try
{
GlobalWin . ExitCode = mf . ProgramRunLoop ( ) ;
}
catch ( Exception e ) when ( Global . MovieSession . Movie . IsActive & & ! ( Debugger . IsAttached | | VersionInfo . DeveloperBuild ) )
{
var result = MessageBox . Show (
"EmuHawk has thrown a fatal exception and is about to close.\nA movie has been detected. Would you like to try to save?\n(Note: Depending on what caused this error, this may or may not succeed)" ,
$"Fatal error: {e.GetType().Name}" ,
MessageBoxButtons . YesNo ,
MessageBoxIcon . Exclamation
) ;
if ( result = = DialogResult . Yes )
{
Global . MovieSession . Movie . Save ( ) ;
}
}
}
}
}
catch ( Exception e ) when ( ! Debugger . IsAttached )
{
new ExceptionBox ( e ) . ShowDialog ( ) ;
}
finally
{
GlobalWin . Sound ? . Dispose ( ) ;
GlobalWin . Sound = null ;
GlobalWin . GL . Dispose ( ) ;
Input . Cleanup ( ) ;
}
2018-11-04 17:05:20 +00:00
2015-10-12 02:03:09 +00:00
//cleanup:
//cleanup IGL stuff so we can get better refcounts when exiting process, for debugging
//DOESNT WORK FOR SOME REASON
//GlobalWin.IGL_GL = new Bizware.BizwareGL.Drivers.OpenTK.IGL_TK();
//GLManager.Instance.Dispose();
//if (GlobalWin.IGL_GL != GlobalWin.GL)
// GlobalWin.GL.Dispose();
//((IDisposable)GlobalWin.IGL_GL).Dispose();
2015-12-15 08:22:44 +00:00
//return 0 assuming things have gone well, non-zero values could be used as error codes or for scripting purposes
return GlobalWin . ExitCode ;
2015-10-12 02:03:09 +00:00
} //SubMain
2011-05-23 00:33:05 +00:00
2019-05-18 05:14:08 +00:00
//declared here instead of a more usual place to avoid dependencies on the more usual place
2012-10-29 08:37:22 +00:00
[DllImport("kernel32.dll", SetLastError = true)]
2019-05-18 05:14:08 +00:00
private static extern uint SetDllDirectory ( string lpPathName ) ;
2013-07-27 00:30:08 +00:00
2017-03-21 16:09:28 +00:00
[DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
2019-05-18 05:14:08 +00:00
private static extern bool DeleteFileW ( [ MarshalAs ( UnmanagedType . LPWStr ) ] string lpFileName ) ;
2017-03-21 16:09:28 +00:00
2019-05-18 05:14:08 +00:00
private static void RemoveMOTW ( string path )
2017-03-21 16:09:28 +00:00
{
2019-03-18 14:06:37 +00:00
DeleteFileW ( $"{path}:Zone.Identifier" ) ;
2017-03-21 16:09:28 +00:00
}
2019-05-18 05:14:08 +00:00
private static void WhackAllMOTW ( string dllDir )
2013-07-27 00:30:08 +00:00
{
var todo = new Queue < DirectoryInfo > ( new [ ] { new DirectoryInfo ( dllDir ) } ) ;
while ( todo . Count > 0 )
{
var di = todo . Dequeue ( ) ;
foreach ( var disub in di . GetDirectories ( ) ) todo . Enqueue ( disub ) ;
foreach ( var fi in di . GetFiles ( "*.dll" ) )
2017-03-21 16:09:28 +00:00
RemoveMOTW ( fi . FullName ) ;
2014-11-06 19:04:51 +00:00
foreach ( var fi in di . GetFiles ( "*.exe" ) )
2017-03-21 16:09:28 +00:00
RemoveMOTW ( fi . FullName ) ;
2013-07-27 00:30:08 +00:00
}
}
2012-10-29 08:37:22 +00:00
2019-05-18 05:14:08 +00:00
private static Assembly CurrentDomain_AssemblyResolve ( object sender , ResolveEventArgs args )
2012-10-29 08:37:22 +00:00
{
2019-05-18 05:14:08 +00:00
var requested = args . Name ;
2017-07-14 06:02:15 +00:00
//mutate filename depending on selection of lua core. here's how it works
//1. we build NLua to the output/dll/lua directory. that brings KopiLua with it
//2. We reference it from there, but we tell it not to copy local; that way there's no NLua in the output/dll directory
//3. When NLua assembly attempts to load, it can't find it
//I. if LuaInterface is selected by the user, we switch to requesting that.
// (those DLLs are built into the output/DLL directory)
2019-05-18 05:14:08 +00:00
//II. if NLua is selected by the user, we skip over this part;
2017-07-14 06:02:15 +00:00
// later, we look for NLua or KopiLua assembly names and redirect them to files located in the output/DLL/nlua directory
if ( new AssemblyName ( requested ) . Name = = "NLua" )
{
2017-07-14 18:45:57 +00:00
//this method referencing Global.Config makes assemblies get loaded, which isnt smart from the assembly resolver.
//so.. we're going to resort to something really bad.
//avert your eyes.
2019-05-18 05:14:08 +00:00
var configPath = Path . Combine ( Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) , "config.ini" ) ;
2019-05-18 10:25:33 +00:00
if ( EXE_PROJECT . OSTailoredCode . CurrentOS = = EXE_PROJECT . OSTailoredCode . DistinctOS . Windows // LuaInterface is not currently working on Mono
& & File . Exists ( configPath )
2019-05-18 05:14:08 +00:00
& & ( Array . Find ( File . ReadAllLines ( configPath ) , line = > line . Contains ( " \"UseNLua\": " ) ) ? ? string . Empty )
. Contains ( "false" ) )
2017-07-14 18:45:57 +00:00
{
2019-05-18 05:14:08 +00:00
requested = "LuaInterface" ;
2017-07-14 18:45:57 +00:00
}
2017-07-14 06:02:15 +00:00
}
2013-01-02 20:11:27 +00:00
lock ( AppDomain . CurrentDomain )
{
2019-05-18 05:14:08 +00:00
var firstAsm = Array . Find ( AppDomain . CurrentDomain . GetAssemblies ( ) , asm = > asm . FullName = = requested ) ;
if ( firstAsm ! = null ) return firstAsm ;
2013-01-02 20:11:27 +00:00
//load missing assemblies by trying to find them in the dll directory
2019-05-18 05:14:08 +00:00
var dllname = $"{new AssemblyName(requested).Name}.dll" ;
var directory = Path . Combine ( Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) , "dll" ) ;
var simpleName = new AssemblyName ( requested ) . Name ;
2017-07-14 06:02:15 +00:00
if ( simpleName = = "NLua" | | simpleName = = "KopiLua" ) directory = Path . Combine ( directory , "nlua" ) ;
2019-05-18 05:14:08 +00:00
var fname = Path . Combine ( directory , dllname ) ;
//it is important that we use LoadFile here and not load from a byte array; otherwise mixed (managed/unmanaged) assemblies can't load
return File . Exists ( fname ) ? Assembly . LoadFile ( fname ) : null ;
2013-01-02 20:11:27 +00:00
}
2012-10-29 08:37:22 +00:00
}
2019-05-18 05:14:08 +00:00
private class SingleInstanceController : WindowsFormsApplicationBase
2011-06-19 23:39:25 +00:00
{
2019-05-18 05:14:08 +00:00
private readonly string [ ] cmdArgs ;
2011-06-19 23:39:25 +00:00
public SingleInstanceController ( string [ ] args )
{
cmdArgs = args ;
IsSingleInstance = true ;
StartupNextInstance + = this_StartupNextInstance ;
}
2019-05-18 05:14:08 +00:00
public void Run ( ) = > Run ( cmdArgs ) ;
private void this_StartupNextInstance ( object sender , StartupNextInstanceEventArgs e )
2011-06-19 23:39:25 +00:00
{
2014-12-29 04:20:47 +00:00
if ( e . CommandLine . Count > = 1 )
2019-05-18 05:14:08 +00:00
( ( MainForm ) MainForm ) . LoadRom ( e . CommandLine [ 0 ] , new MainForm . LoadRomArgs { OpenAdvanced = new OpenAdvanced_OpenRom ( ) } ) ;
2011-06-19 23:39:25 +00:00
}
2011-05-23 00:33:05 +00:00
2011-06-19 23:39:25 +00:00
protected override void OnCreateMainForm ( )
{
2013-08-25 22:43:34 +00:00
MainForm = new MainForm ( cmdArgs ) ;
var title = MainForm . Text ;
MainForm . Show ( ) ;
MainForm . Text = title ;
2019-05-18 05:14:08 +00:00
GlobalWin . ExitCode = ( ( MainForm ) MainForm ) . ProgramRunLoop ( ) ;
2015-03-01 19:29:33 +00:00
}
2011-06-19 23:39:25 +00:00
}
}
2011-01-11 02:55:51 +00:00
}