2015-12-31 12:21:44 +00:00
#region = = = = = = = = = = = = = = = = = = Copyright ( c ) 2007 Pascal vd Heiden
/ *
* Copyright ( c ) 2007 Pascal vd Heiden , www . codeimp . com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* /
#endregion
#region = = = = = = = = = = = = = = = = = = Namespaces
using System ;
using System.IO ;
using CodeImp.DoomBuilder.Data ;
using System.Diagnostics ;
using CodeImp.DoomBuilder.Actions ;
using System.Windows.Forms ;
using CodeImp.DoomBuilder.Windows ;
using CodeImp.DoomBuilder.IO ;
using CodeImp.DoomBuilder.Editing ;
2016-03-02 21:10:41 +00:00
using System.Security.Cryptography ;
2015-12-31 12:21:44 +00:00
#endregion
namespace CodeImp.DoomBuilder
{
internal class Launcher : IDisposable
{
#region = = = = = = = = = = = = = = = = = = Constants
#endregion
#region = = = = = = = = = = = = = = = = = = Variables
private string tempwad ;
private Process process ; //mxd
private bool isdisposed ;
delegate void EngineExitedCallback ( ) ; //mxd
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
public string TempWAD { get { return tempwad ; } }
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Destructor
// Constructor
public Launcher ( MapManager manager )
{
// Initialize
CleanTempFile ( manager ) ;
// Bind actions
General . Actions . BindMethods ( this ) ;
}
// Disposer
public void Dispose ( )
{
// Not yet disposed?
if ( ! isdisposed )
{
// Unbind actions
General . Actions . UnbindMethods ( this ) ;
//mxd. Terminate process?
if ( process ! = null )
{
process . CloseMainWindow ( ) ;
process . Close ( ) ;
}
// Remove temporary file
try { File . Delete ( tempwad ) ; }
catch ( Exception ) { }
// Done
isdisposed = true ;
}
}
#endregion
#region = = = = = = = = = = = = = = = = = = Parameters
// This takes the unconverted parameters (with placeholders) and converts it
// to parameters with full paths, names and numbers where placeholders were put.
// The tempfile must be the full path and filename to the PWAD file to test.
2016-01-30 15:55:15 +00:00
public string ConvertParameters ( string parameters , int skill , string skin , int gametype , bool shortpaths )
2015-12-31 12:21:44 +00:00
{
string outp = parameters ;
DataLocation iwadloc ;
string p_wp = "" , p_wf = "" ;
string p_ap = "" , p_apq = "" ;
2023-04-23 21:48:44 +00:00
string p_aa = "" , p_aaq = "" ;
string p_af = "" , p_afq = "" ;
2015-12-31 12:21:44 +00:00
string p_l1 = "" , p_l2 = "" ;
string p_nm = "" ;
string f = tempwad ;
// Make short path if needed
if ( shortpaths ) f = General . GetShortFilePath ( f ) ;
// Find the first IWAD file
if ( General . Map . Data . FindFirstIWAD ( out iwadloc ) )
{
// %WP and %WF result in IWAD file
p_wp = iwadloc . location ;
p_wf = Path . GetFileName ( p_wp ) ;
if ( shortpaths )
{
p_wp = General . GetShortFilePath ( p_wp ) ;
p_wf = General . GetShortFilePath ( p_wf ) ;
}
}
// Make a list of all data locations, including map location
DataLocation maplocation = new DataLocation ( DataLocation . RESOURCE_WAD , General . Map . FilePathName , false , false , false ) ;
DataLocationList locations = new DataLocationList ( ) ;
locations . AddRange ( General . Map . ConfigSettings . Resources ) ;
locations . AddRange ( General . Map . Options . Resources ) ;
if ( ! string . IsNullOrEmpty ( maplocation . location ) ) locations . Add ( maplocation ) ; //mxd. maplocation.location will be empty when a newly created map was not saved yet.
2016-03-02 21:10:41 +00:00
FileInfo fi = new FileInfo ( f ) ;
2015-12-31 12:21:44 +00:00
// Go for all data locations
foreach ( DataLocation dl in locations )
{
// Location not the IWAD file?
if ( ( dl . type ! = DataLocation . RESOURCE_WAD ) | | ( dl . location ! = iwadloc . location ) )
{
// Location not included?
if ( ! dl . notfortesting )
{
2023-04-03 19:46:51 +00:00
// hack to prevent another hack from crashing: don't check directories
if ( dl . type ! = DataLocation . RESOURCE_DIRECTORY & & FilesAreEqual ( fi , new FileInfo ( dl . location ) ) )
2016-03-02 21:10:41 +00:00
{
outp = outp . Replace ( "%f" , "%F" ) ;
outp = outp . Replace ( "\"%F\"" , "" ) ;
}
// Add to string of files
if ( shortpaths )
2015-12-31 12:21:44 +00:00
{
p_ap + = General . GetShortFilePath ( dl . location ) + " " ;
p_apq + = "\"" + General . GetShortFilePath ( dl . location ) + "\" " ;
2023-04-23 21:48:44 +00:00
if ( dl . type = = DataLocation . RESOURCE_WAD | | dl . type = = DataLocation . RESOURCE_PK3 )
{
p_aa + = General . GetShortFilePath ( dl . location ) + " " ;
p_aaq + = "\"" + General . GetShortFilePath ( dl . location ) + "\" " ;
}
else
{
p_af + = General . GetShortFilePath ( dl . location ) + " " ;
p_afq + = "\"" + General . GetShortFilePath ( dl . location ) + "\" " ;
}
2015-12-31 12:21:44 +00:00
}
else
{
p_ap + = dl . location + " " ;
p_apq + = "\"" + dl . location + "\" " ;
2023-04-23 21:48:44 +00:00
if ( dl . type = = DataLocation . RESOURCE_WAD | | dl . type = = DataLocation . RESOURCE_PK3 )
{
p_aa + = dl . location + " " ;
p_aaq + = "\"" + dl . location + "\" " ;
}
else
{
p_af + = dl . location + " " ;
p_afq + = "\"" + dl . location + "\" " ;
}
2015-12-31 12:21:44 +00:00
}
}
}
}
// Trim last space from resource file locations
p_ap = p_ap . TrimEnd ( ' ' ) ;
p_apq = p_apq . TrimEnd ( ' ' ) ;
2023-04-23 21:48:44 +00:00
p_aa = p_aa . TrimEnd ( ' ' ) ;
p_aaq = p_aaq . TrimEnd ( ' ' ) ;
p_af = p_af . TrimEnd ( ' ' ) ;
p_afq = p_afq . TrimEnd ( ' ' ) ;
2015-12-31 12:21:44 +00:00
// Try finding the L1 and L2 numbers from the map name
string numstr = "" ;
bool first = true ;
foreach ( char c in General . Map . Options . CurrentName )
{
// Character is a number?
if ( Configuration . NUMBERS . IndexOf ( c ) > - 1 )
{
// Include it
numstr + = c ;
}
else
{
// Store the number if we found one
if ( numstr . Length > 0 )
{
int num ;
int . TryParse ( numstr , out num ) ;
if ( first ) p_l1 = num . ToString ( ) ; else p_l2 = num . ToString ( ) ;
numstr = "" ;
first = false ;
}
}
}
// Store the number if we found one
if ( numstr . Length > 0 )
{
int num ;
int . TryParse ( numstr , out num ) ;
if ( first ) p_l1 = num . ToString ( ) ; else p_l2 = num . ToString ( ) ;
}
// No monsters?
if ( ! General . Settings . TestMonsters ) p_nm = "-nomonsters" ;
// Make sure all our placeholders are in uppercase
outp = outp . Replace ( "%f" , "%F" ) ;
outp = outp . Replace ( "%wp" , "%WP" ) ;
outp = outp . Replace ( "%wf" , "%WF" ) ;
outp = outp . Replace ( "%wP" , "%WP" ) ;
outp = outp . Replace ( "%wF" , "%WF" ) ;
outp = outp . Replace ( "%Wp" , "%WP" ) ;
outp = outp . Replace ( "%Wf" , "%WF" ) ;
outp = outp . Replace ( "%l1" , "%L1" ) ;
outp = outp . Replace ( "%l2" , "%L2" ) ;
outp = outp . Replace ( "%l" , "%L" ) ;
outp = outp . Replace ( "%ap" , "%AP" ) ;
outp = outp . Replace ( "%aP" , "%AP" ) ;
outp = outp . Replace ( "%Ap" , "%AP" ) ;
outp = outp . Replace ( "%s" , "%S" ) ;
outp = outp . Replace ( "%nM" , "%NM" ) ;
outp = outp . Replace ( "%Nm" , "%NM" ) ;
outp = outp . Replace ( "%nm" , "%NM" ) ;
2016-01-30 15:55:15 +00:00
// Replace placeholders with actual values
outp = outp . Replace ( "%F" , f ) ;
2015-12-31 12:21:44 +00:00
outp = outp . Replace ( "%WP" , p_wp ) ;
outp = outp . Replace ( "%WF" , p_wf ) ;
outp = outp . Replace ( "%L1" , p_l1 ) ;
outp = outp . Replace ( "%L2" , p_l2 ) ;
outp = outp . Replace ( "%L" , General . Map . Options . CurrentName ) ;
outp = outp . Replace ( "\"%AP\"" , p_apq ) ;
2023-04-23 21:48:44 +00:00
outp = outp . Replace ( "\"%AA\"" , p_aaq ) ;
outp = outp . Replace ( "\"%AF\"" , p_afq ) ;
2015-12-31 12:21:44 +00:00
outp = outp . Replace ( "%AP" , p_ap ) ;
2023-04-23 21:48:44 +00:00
outp = outp . Replace ( "%AA" , p_aa ) ;
outp = outp . Replace ( "%AF" , p_af ) ;
2015-12-31 12:21:44 +00:00
outp = outp . Replace ( "%S" , skill . ToString ( ) ) ;
outp = outp . Replace ( "%NM" , p_nm ) ;
2016-01-30 16:35:21 +00:00
outp = outp + " +skin " + skin . ToLowerInvariant ( ) ;
2016-01-30 15:55:15 +00:00
if ( gametype ! = - 1 )
outp = outp + " -server -gametype " + gametype . ToString ( ) ;
// Return result
return outp ;
2015-12-31 12:21:44 +00:00
}
//mxd
private bool AlreadyTesting ( )
{
if ( process ! = null )
{
2022-11-25 17:14:35 +00:00
General . ShowWarningMessage ( "Game engine is already running." + Environment . NewLine + "Please close \"" + process . MainModule . FileName + "\" first." , MessageBoxButtons . OK ) ;
2015-12-31 12:21:44 +00:00
return true ;
}
return false ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Test
// This saves the map to a temporary file and launches a test
[BeginAction("testmap")]
public void Test ( )
{
if ( AlreadyTesting ( ) | | ! General . Editing . Mode . OnMapTestBegin ( false ) ) return ; //mxd
TestAtSkill ( General . Map . ConfigSettings . TestSkill ) ;
General . Editing . Mode . OnMapTestEnd ( false ) ; //mxd
}
//mxd
[BeginAction("testmapfromview")]
public void TestFromView ( )
{
if ( AlreadyTesting ( ) | | ! General . Editing . Mode . OnMapTestBegin ( true ) ) return ;
TestAtSkill ( General . Map . ConfigSettings . TestSkill ) ;
General . Editing . Mode . OnMapTestEnd ( true ) ;
}
// This saves the map to a temporary file and launches a test with the given skill
public void TestAtSkill ( int skill )
{
Cursor oldcursor = Cursor . Current ;
2016-04-07 13:29:47 +00:00
// Check if configuration is OK
if ( string . IsNullOrEmpty ( General . Map . ConfigSettings . TestProgram ) | | ! File . Exists ( General . Map . ConfigSettings . TestProgram ) )
{
2015-12-31 12:21:44 +00:00
//mxd. Let's be more precise
string message ;
2016-04-07 13:29:47 +00:00
if ( String . IsNullOrEmpty ( General . Map . ConfigSettings . TestProgram ) )
2015-12-31 12:21:44 +00:00
message = "Your test program is not set for the current game configuration" ;
else
message = "Current test program has invalid path" ;
// Show message
Cursor . Current = Cursors . Default ;
DialogResult result = General . ShowWarningMessage ( message + ". Would you like to set up your test program now?" , MessageBoxButtons . YesNo ) ;
if ( result = = DialogResult . Yes )
{
// Show game configuration on the right page
General . MainWindow . ShowConfigurationPage ( 2 ) ;
}
return ;
}
// No custom parameters?
if ( ! General . Map . ConfigSettings . CustomParameters )
{
// Set parameters to the default ones
General . Map . ConfigSettings . TestParameters = General . Map . Config . TestParameters ;
General . Map . ConfigSettings . TestShortPaths = General . Map . Config . TestShortPaths ;
}
// Remove temporary file
try { File . Delete ( tempwad ) ; }
catch ( Exception ) { }
// Save map to temporary file
Cursor . Current = Cursors . WaitCursor ;
tempwad = General . MakeTempFilename ( General . Map . TempPath , "wad" ) ;
General . Plugins . OnMapSaveBegin ( SavePurpose . Testing ) ;
if ( General . Map . SaveMap ( tempwad , SavePurpose . Testing ) )
{
// No compiler errors?
if ( General . Map . Errors . Count = = 0 )
{
// Make arguments
2016-01-30 15:55:15 +00:00
string args = ConvertParameters ( General . Map . ConfigSettings . TestParameters , skill , General . Map . ConfigSettings . TestSkin , General . Map . ConfigSettings . TestGametype , General . Map . ConfigSettings . TestShortPaths ) ;
2015-12-31 12:21:44 +00:00
// Setup process info
ProcessStartInfo processinfo = new ProcessStartInfo ( ) ;
processinfo . Arguments = args ;
processinfo . FileName = General . Map . ConfigSettings . TestProgram ;
processinfo . CreateNoWindow = false ;
processinfo . ErrorDialog = false ;
processinfo . UseShellExecute = true ;
processinfo . WindowStyle = ProcessWindowStyle . Normal ;
processinfo . WorkingDirectory = Path . GetDirectoryName ( processinfo . FileName ) ;
// Output info
General . WriteLogLine ( "Running test program: " + processinfo . FileName ) ;
General . WriteLogLine ( "Program parameters: " + processinfo . Arguments ) ;
General . MainWindow . DisplayStatus ( StatusType . Info , "Launching " + processinfo . FileName + "..." ) ;
try
{
// Start the program
process = Process . Start ( processinfo ) ;
process . EnableRaisingEvents = true ; //mxd
process . Exited + = ProcessOnExited ; //mxd
Cursor . Current = oldcursor ; //mxd
}
catch ( Exception e )
{
// Unable to start the program
General . ShowErrorMessage ( "Unable to start the test program, " + e . GetType ( ) . Name + ": " + e . Message , MessageBoxButtons . OK ) ;
}
}
else
{
General . MainWindow . DisplayStatus ( StatusType . Warning , "Unable to test the map due to script errors." ) ;
}
}
}
2016-03-02 21:10:41 +00:00
//Check if two files are equal using MD5 hash. I can't believe I need this...
static bool FilesAreEqual ( FileInfo first , FileInfo second )
{
byte [ ] firstHash = MD5 . Create ( ) . ComputeHash ( first . OpenRead ( ) ) ;
byte [ ] secondHash = MD5 . Create ( ) . ComputeHash ( second . OpenRead ( ) ) ;
for ( int i = 0 ; i < firstHash . Length ; i + + )
{
if ( firstHash [ i ] ! = secondHash [ i ] )
return false ;
}
return true ;
}
//mxd
private void TestingFinished ( )
2015-12-31 12:21:44 +00:00
{
//Done
TimeSpan deltatime = TimeSpan . FromTicks ( process . ExitTime . Ticks - process . StartTime . Ticks ) ;
process = null ;
General . WriteLogLine ( "Test program has finished." ) ;
General . WriteLogLine ( "Run time: " + deltatime . TotalSeconds . ToString ( "###########0.00" ) + " seconds" ) ;
General . MainWindow . DisplayReady ( ) ;
// Clean up temp file
CleanTempFile ( General . Map ) ;
2016-04-06 11:44:38 +00:00
if ( General . Map ! = null )
{
// Device reset may be needed...
if ( General . Editing . Mode is ClassicMode )
{
General . Map . Graphics . Reset ( ) ;
General . MainWindow . RedrawDisplay ( ) ;
}
/ * else if ( General . Editing . Mode is VisualMode )
{
General . MainWindow . StopExclusiveMouseInput ( ) ;
General . MainWindow . StartExclusiveMouseInput ( ) ;
} * /
}
2015-12-31 12:21:44 +00:00
General . Plugins . OnMapSaveEnd ( SavePurpose . Testing ) ;
General . MainWindow . FocusDisplay ( ) ;
if ( General . Editing . Mode is ClassicMode ) General . MainWindow . RedrawDisplay ( ) ;
}
//mxd
private void ProcessOnExited ( object sender , EventArgs eventArgs )
{
General . MainWindow . Invoke ( new EngineExitedCallback ( TestingFinished ) ) ;
}
// This deletes the previous temp file and creates a new, empty temp file
private void CleanTempFile ( MapManager manager )
{
// Remove temporary file
try { File . Delete ( tempwad ) ; }
catch ( Exception ) { }
// Make new empty temp file
tempwad = General . MakeTempFilename ( manager . TempPath , "wad" ) ;
File . WriteAllText ( tempwad , "" ) ;
}
#endregion
}
}