already installed. If we don't get rid of the old theme, we'll have junk files accumulating that cause major problems. Keep track of what "packages" are included in an SMZIP, and offer to remove them when the SMZIP is installed. Don't derive it at extraction time; this way, we can still create "overlay" SMZIPs. Make sure we don't do anything stupid if something malicious like "..\..\.." is listed as a package name.
617 lines
15 KiB
C++
617 lines
15 KiB
C++
// SmpackageExportDlg.cpp : implementation file
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include "smpackage.h"
|
|
#include "SmpackageExportDlg.h"
|
|
#include "RageUtil.h"
|
|
#include "ZipArchive\ZipArchive.h"
|
|
#include "EnterName.h"
|
|
#include "EnterComment.h"
|
|
#include "smpackageUtil.h"
|
|
#include "EditInsallations.h"
|
|
#include "IniFile.h"
|
|
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <set>
|
|
using namespace std;
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CSmpackageExportDlg dialog
|
|
|
|
|
|
CSmpackageExportDlg::CSmpackageExportDlg(CWnd* pParent /*=NULL*/)
|
|
: CDialog(CSmpackageExportDlg::IDD, pParent)
|
|
{
|
|
//{{AFX_DATA_INIT(CSmpackageExportDlg)
|
|
// NOTE: the ClassWizard will add member initialization here
|
|
//}}AFX_DATA_INIT
|
|
}
|
|
|
|
|
|
void CSmpackageExportDlg::DoDataExchange(CDataExchange* pDX)
|
|
{
|
|
CDialog::DoDataExchange(pDX);
|
|
//{{AFX_DATA_MAP(CSmpackageExportDlg)
|
|
DDX_Control(pDX, IDC_COMBO_DIR, m_comboDir);
|
|
DDX_Control(pDX, IDC_BUTTON_EXPORT_AS_INDIVIDUAL, m_buttonExportAsIndividual);
|
|
DDX_Control(pDX, IDC_BUTTON_EXPORT_AS_ONE, m_buttonExportAsOne);
|
|
DDX_Control(pDX, IDC_TREE, m_tree);
|
|
//}}AFX_DATA_MAP
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CSmpackageExportDlg, CDialog)
|
|
//{{AFX_MSG_MAP(CSmpackageExportDlg)
|
|
ON_BN_CLICKED(IDC_BUTTON_EXPORT_AS_ONE, OnButtonExportAsOne)
|
|
ON_BN_CLICKED(IDC_BUTTON_EXPORT_AS_INDIVIDUAL, OnButtonExportAsIndividual)
|
|
ON_BN_CLICKED(IDC_BUTTON_PLAY, OnButtonPlay)
|
|
ON_BN_CLICKED(IDC_BUTTON_EDIT, OnButtonEdit)
|
|
ON_CBN_SELCHANGE(IDC_COMBO_DIR, OnSelchangeComboDir)
|
|
ON_BN_CLICKED(IDC_BUTTON_OPEN, OnButtonOpen)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CSmpackageExportDlg message handlers
|
|
|
|
BOOL CSmpackageExportDlg::OnInitDialog()
|
|
{
|
|
CDialog::OnInitDialog();
|
|
|
|
// TODO: Add extra initialization here
|
|
//
|
|
|
|
RefreshInstallationList();
|
|
|
|
RefreshTree();
|
|
|
|
return TRUE; // return TRUE unless you set the focus to a control
|
|
}
|
|
|
|
CString ReplaceInvalidFileNameChars( CString sOldFileName )
|
|
{
|
|
CString sNewFileName = sOldFileName;
|
|
const char charsToReplace[] = {
|
|
' ', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
|
|
'+', '=', '[', ']', '{', '}', '|', ':', '\"', '\\',
|
|
'<', '>', ',', '?', '/'
|
|
};
|
|
for( int i=0; i<sizeof(charsToReplace); i++ )
|
|
sNewFileName.Replace( charsToReplace[i], '_' );
|
|
return sNewFileName;
|
|
}
|
|
|
|
void GetFilePaths( CString sDirOrFile, vector<CString> &asPathToFilesOut )
|
|
{
|
|
vector<CString> asDirectoriesToExplore;
|
|
|
|
// HACK:
|
|
// Must use backslashes in the path, or else WinZip and WinRAR don't see the files.
|
|
// Not sure if this is ZipArchive's fault.
|
|
|
|
if( IsADirectory(sDirOrFile) && sDirOrFile.Right(1) != "\\" )
|
|
{
|
|
sDirOrFile += "\\";
|
|
sDirOrFile += "*.*";
|
|
}
|
|
|
|
if( IsAFile(sDirOrFile) )
|
|
{
|
|
asPathToFilesOut.push_back( sDirOrFile );
|
|
return;
|
|
}
|
|
|
|
|
|
GetDirListing( sDirOrFile, asPathToFilesOut, false, true );
|
|
GetDirListing( sDirOrFile, asDirectoriesToExplore, true, true );
|
|
while( asDirectoriesToExplore.size() > 0 )
|
|
{
|
|
GetDirListing( asDirectoriesToExplore[0] + "\\*.*", asPathToFilesOut, false, true );
|
|
GetDirListing( asDirectoriesToExplore[0] + "\\*.*", asDirectoriesToExplore, true, true );
|
|
asDirectoriesToExplore.erase( asDirectoriesToExplore.begin() );
|
|
}
|
|
}
|
|
|
|
CString GetDesktopPath()
|
|
{
|
|
static TCHAR strNull[2] = _T("");
|
|
static TCHAR strPath[MAX_PATH];
|
|
DWORD dwType;
|
|
DWORD dwSize = MAX_PATH;
|
|
HKEY hKey;
|
|
|
|
// Open the appropriate registry key
|
|
LONG lResult = RegOpenKeyEx( HKEY_CURRENT_USER,
|
|
_T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"),
|
|
0, KEY_READ, &hKey );
|
|
if( ERROR_SUCCESS != lResult )
|
|
return strNull;
|
|
|
|
lResult = RegQueryValueEx( hKey, _T("Desktop"), NULL,
|
|
&dwType, (BYTE*)strPath, &dwSize );
|
|
RegCloseKey( hKey );
|
|
|
|
if( ERROR_SUCCESS != lResult )
|
|
return strNull;
|
|
|
|
return strPath;
|
|
}
|
|
|
|
bool ExportPackage( CString sPackageName, const CStringArray& asDirectoriesToExport, CString sComment )
|
|
{
|
|
CZipArchive zip;
|
|
|
|
//
|
|
// Create the package zip file
|
|
//
|
|
const CString sPackagePath = GetDesktopPath() + "\\" + sPackageName;
|
|
try
|
|
{
|
|
zip.Open( sPackagePath, CZipArchive::zipCreate );
|
|
}
|
|
catch( CException* e )
|
|
{
|
|
e->ReportError();
|
|
zip.Close();
|
|
e->Delete();
|
|
return false;
|
|
}
|
|
|
|
|
|
zip.SetGlobalComment( sComment );
|
|
|
|
/* Find files to add to zip. */
|
|
unsigned i;
|
|
vector<CString> asFilePaths;
|
|
for( i=0; i<asDirectoriesToExport.size(); i++ )
|
|
GetFilePaths( asDirectoriesToExport[i], asFilePaths );
|
|
|
|
{
|
|
IniFile ini;
|
|
ini.SetValueI( "SMZIP", "Version", 1 );
|
|
|
|
set<CString> Directories;
|
|
for( i=0; i<asFilePaths.size(); i++ )
|
|
{
|
|
const CString name = GetPackageDirectory( asFilePaths[i] );
|
|
if( name != "" )
|
|
Directories.insert( name );
|
|
}
|
|
|
|
set<CString>::const_iterator it;
|
|
int num = 0;
|
|
for( it = Directories.begin(); it != Directories.end(); ++it )
|
|
ini.SetValue( "Packages", ssprintf("%i", num++), *it );
|
|
ini.SetValueI( "Packages", "NumPackages", num );
|
|
|
|
CString buf;
|
|
ini.WriteBuf(buf);
|
|
|
|
CZipMemFile control;
|
|
control.Write( buf.GetBuffer(0), buf.GetLength() );
|
|
|
|
control.Seek( 0, CZipAbstractFile::begin );
|
|
zip.AddNewFile( control, "smzip.ctl" );
|
|
}
|
|
|
|
//
|
|
// Add files to zip
|
|
//
|
|
for( unsigned j=0; j<asFilePaths.size(); j++ )
|
|
{
|
|
CString sFilePath = asFilePaths[j];
|
|
|
|
// don't export "thumbs.db" files or "CVS" folders
|
|
if( sFilePath.Find("CVS")!=-1 )
|
|
continue; // skip
|
|
if( sFilePath.Find("Thumbs.db")!=-1 )
|
|
continue; // skip
|
|
|
|
CString sDir, sFName, sExt;
|
|
splitrelpath( sFilePath, sDir, sFName, sExt );
|
|
bool bUseCompression = true;
|
|
if( sExt.CompareNoCase("avi")==0 ||
|
|
sExt.CompareNoCase("mpeg")==0 ||
|
|
sExt.CompareNoCase("mpg")==0 ||
|
|
sExt.CompareNoCase("ogg")==0 ||
|
|
sExt.CompareNoCase("gif")==0 ||
|
|
sExt.CompareNoCase("jpg")==0 ||
|
|
sExt.CompareNoCase("png")==0 )
|
|
bUseCompression = false;
|
|
|
|
try
|
|
{
|
|
zip.AddNewFile( sFilePath, bUseCompression?Z_BEST_COMPRESSION:Z_NO_COMPRESSION, true );
|
|
}
|
|
catch (CException* e)
|
|
{
|
|
AfxMessageBox( ssprintf("Error adding file '%s'.", sFilePath) );
|
|
zip.Close();
|
|
e->Delete();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
zip.Close();
|
|
return true;
|
|
}
|
|
|
|
bool CSmpackageExportDlg::MakeComment( CString &comment )
|
|
{
|
|
bool DontAskForComment;
|
|
if( GetPref("DontAskForComment", DontAskForComment) && DontAskForComment )
|
|
{
|
|
comment = "";
|
|
return true;
|
|
}
|
|
|
|
EnterComment commentDlg;
|
|
int nResponse = commentDlg.DoModal();
|
|
if( nResponse != IDOK )
|
|
return false; // cancelled
|
|
|
|
comment = commentDlg.m_sEnteredComment;
|
|
if( commentDlg.m_bDontAsk )
|
|
SetPref( "DontAskForComment", true );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSmpackageExportDlg::OnButtonExportAsOne()
|
|
{
|
|
CStringArray asPaths;
|
|
GetCheckedPaths( asPaths );
|
|
|
|
if( asPaths.size() == 0 )
|
|
{
|
|
AfxMessageBox( "No items are checked" );
|
|
return;
|
|
}
|
|
else if( asPaths.size() == 1 )
|
|
{
|
|
OnButtonExportAsIndividual();
|
|
return;
|
|
}
|
|
|
|
// Generate a package name
|
|
CString sPackageName;
|
|
EnterName nameDlg;
|
|
int nResponse = nameDlg.DoModal();
|
|
if( nResponse != IDOK )
|
|
return; // cancelled
|
|
sPackageName = nameDlg.m_sEnteredName;
|
|
sPackageName = ReplaceInvalidFileNameChars( sPackageName+".smzip" );
|
|
|
|
// Generate a comment
|
|
CString sComment;
|
|
if( !MakeComment(sComment) )
|
|
return; // cancelled
|
|
|
|
if( ExportPackage( sPackageName, asPaths, sComment ) )
|
|
AfxMessageBox( ssprintf("Successfully exported package '%s' to your Desktop.",sPackageName) );
|
|
}
|
|
|
|
void CSmpackageExportDlg::OnButtonExportAsIndividual()
|
|
{
|
|
CStringArray asPaths;
|
|
GetCheckedPaths( asPaths );
|
|
|
|
if( asPaths.size() == 0 )
|
|
{
|
|
AfxMessageBox( "No items are checked" );
|
|
return;
|
|
}
|
|
|
|
// Generate a comment
|
|
CString sComment;
|
|
if( !MakeComment(sComment) )
|
|
return; // cancelled
|
|
|
|
bool bAllSucceeded = true;
|
|
CStringArray asExportedPackages;
|
|
CStringArray asFailedPackages;
|
|
for( unsigned i=0; i<asPaths.size(); i++ )
|
|
{
|
|
// Generate a package name for every path
|
|
CString sPath = asPaths[i];
|
|
|
|
CString sPackageName;
|
|
CStringArray asPathBits;
|
|
split( sPath, "\\", asPathBits, true );
|
|
sPackageName = asPathBits[ asPathBits.size()-1 ] + ".smzip";
|
|
sPackageName = ReplaceInvalidFileNameChars( sPackageName );
|
|
|
|
CStringArray asPathsToExport;
|
|
asPathsToExport.push_back( sPath );
|
|
|
|
if( ExportPackage( sPackageName, asPathsToExport, sComment ) )
|
|
asExportedPackages.push_back( sPackageName );
|
|
else
|
|
asFailedPackages.push_back( sPackageName );
|
|
}
|
|
|
|
CString sMessage;
|
|
if( asFailedPackages.size() == 0 )
|
|
sMessage = ssprintf("Successfully exported the package%s '%s' to your Desktop.", asFailedPackages.size()>1?"s":"", join("', '",asExportedPackages) );
|
|
else
|
|
sMessage = ssprintf(" The packages %s failed to export.", join(", ",asFailedPackages) );
|
|
AfxMessageBox( sMessage );
|
|
}
|
|
|
|
void CSmpackageExportDlg::OnButtonPlay()
|
|
{
|
|
// TODO: Add your control notification handler code here
|
|
|
|
PROCESS_INFORMATION pi;
|
|
STARTUPINFO si;
|
|
ZeroMemory( &si, sizeof(si) );
|
|
|
|
CreateProcess(
|
|
NULL, // pointer to name of executable module
|
|
"stepmania.exe", // pointer to command line string
|
|
NULL, // process security attributes
|
|
NULL, // thread security attributes
|
|
false, // handle inheritance flag
|
|
0, // creation flags
|
|
NULL, // pointer to new environment block
|
|
NULL, // pointer to current directory name
|
|
&si, // pointer to STARTUPINFO
|
|
&pi // pointer to PROCESS_INFORMATION
|
|
);
|
|
|
|
exit(0);
|
|
}
|
|
|
|
void CSmpackageExportDlg::GetTreeItems( CArray<HTREEITEM,HTREEITEM>& aItemsOut )
|
|
{
|
|
CArray<HTREEITEM,HTREEITEM> aRootsToExplore;
|
|
|
|
// add all top-level roots
|
|
HTREEITEM item = m_tree.GetRootItem();
|
|
while( item != NULL )
|
|
{
|
|
aRootsToExplore.Add( item );
|
|
item = m_tree.GetNextSiblingItem( item );
|
|
}
|
|
|
|
while( aRootsToExplore.GetSize() > 0 )
|
|
{
|
|
HTREEITEM item = aRootsToExplore[0];
|
|
aRootsToExplore.RemoveAt( 0 );
|
|
aItemsOut.Add( item );
|
|
|
|
HTREEITEM child = m_tree.GetChildItem( item );
|
|
while( child != NULL )
|
|
{
|
|
aRootsToExplore.Add( child );
|
|
child = m_tree.GetNextSiblingItem( child );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSmpackageExportDlg::GetCheckedTreeItems( CArray<HTREEITEM,HTREEITEM>& aCheckedItemsOut )
|
|
{
|
|
CArray<HTREEITEM,HTREEITEM> aItems;
|
|
|
|
GetTreeItems( aItems );
|
|
for( int i=0; i<aItems.GetSize(); i++ )
|
|
if( m_tree.GetCheck(aItems[i]) )
|
|
aCheckedItemsOut.Add( aItems[i] );
|
|
}
|
|
|
|
void CSmpackageExportDlg::GetCheckedPaths( CStringArray& aPathsOut )
|
|
{
|
|
CArray<HTREEITEM,HTREEITEM> aItems;
|
|
|
|
GetCheckedTreeItems( aItems );
|
|
for( int i=0; i<aItems.GetSize(); i++ )
|
|
{
|
|
HTREEITEM item = aItems[i];
|
|
|
|
CString sPath;
|
|
|
|
while( item )
|
|
{
|
|
sPath = m_tree.GetItemText(item) + '\\' + sPath;
|
|
item = m_tree.GetParentItem(item);
|
|
}
|
|
|
|
sPath.TrimRight('\\'); // strip off last slash
|
|
|
|
aPathsOut.push_back( sPath );
|
|
}
|
|
}
|
|
|
|
|
|
void CSmpackageExportDlg::OnButtonEdit()
|
|
{
|
|
// TODO: Add your control notification handler code here
|
|
EditInsallations dlg;
|
|
int nResponse = dlg.DoModal();
|
|
if( nResponse == IDOK )
|
|
{
|
|
WriteStepManiaInstallDirs( dlg.m_asReturnedInstallDirs );
|
|
RefreshInstallationList();
|
|
RefreshTree();
|
|
}
|
|
}
|
|
|
|
void CSmpackageExportDlg::RefreshInstallationList()
|
|
{
|
|
m_comboDir.ResetContent();
|
|
|
|
CStringArray asInstallDirs;
|
|
GetStepManiaInstallDirs( asInstallDirs );
|
|
for( unsigned i=0; i<asInstallDirs.size(); i++ )
|
|
{
|
|
m_comboDir.AddString( asInstallDirs[i] );
|
|
}
|
|
m_comboDir.SetCurSel( 0 ); // guaranteed to be at least one item
|
|
}
|
|
|
|
void CSmpackageExportDlg::OnSelchangeComboDir()
|
|
{
|
|
// TODO: Add your control notification handler code here
|
|
RefreshTree();
|
|
}
|
|
|
|
void CSmpackageExportDlg::RefreshTree()
|
|
{
|
|
m_tree.DeleteAllItems();
|
|
|
|
CString sDir;
|
|
m_comboDir.GetWindowText( sDir );
|
|
|
|
SetCurrentDirectory( sDir );
|
|
|
|
// Add announcers
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "Announcers" );
|
|
GetDirListing( "Announcers\\*.*", as1, true, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
m_tree.InsertItem( as1[i], item1 );
|
|
}
|
|
|
|
// Add characters
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "Characters" );
|
|
GetDirListing( "Characters\\*.*", as1, true, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
m_tree.InsertItem( as1[i], item1 );
|
|
}
|
|
|
|
// Add themes
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "Themes" );
|
|
GetDirListing( "Themes\\*.*", as1, true, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
m_tree.InsertItem( as1[i], item1 );
|
|
}
|
|
|
|
// Add BGAnimations
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "BGAnimations" );
|
|
GetDirListing( "BGAnimations\\*.*", as1, true, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
m_tree.InsertItem( as1[i], item1 );
|
|
}
|
|
|
|
// Add RandomMovies
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "RandomMovies" );
|
|
GetDirListing( "RandomMovies\\*.avi", as1, false, false );
|
|
GetDirListing( "RandomMovies\\*.mpg", as1, false, false );
|
|
GetDirListing( "RandomMovies\\*.mpeg", as1, false, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
m_tree.InsertItem( as1[i], item1 );
|
|
}
|
|
|
|
// Add visualizations
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "Visualizations" );
|
|
GetDirListing( "Visualizations\\*.avi", as1, false, false );
|
|
GetDirListing( "Visualizations\\*.mpg", as1, false, false );
|
|
GetDirListing( "Visualizations\\*.mpeg", as1, false, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
m_tree.InsertItem( as1[i], item1 );
|
|
}
|
|
|
|
// Add courses
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "Courses" );
|
|
GetDirListing( "Courses\\*.crs", as1, false, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
{
|
|
as1[i] = as1[i].Left(as1[i].GetLength()-4); // strip off ".crs"
|
|
m_tree.InsertItem( as1[i], item1 );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Add NoteSkins
|
|
//
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "NoteSkins" );
|
|
GetDirListing( "NoteSkins\\*.*", as1, true, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
{
|
|
CStringArray as2;
|
|
HTREEITEM item2 = m_tree.InsertItem( as1[i], item1 );
|
|
GetDirListing( "NoteSkins\\" + as1[i] + "\\*.*", as2, true, false );
|
|
for( unsigned j=0; j<as2.size(); j++ )
|
|
m_tree.InsertItem( as2[j], item2 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add Songs
|
|
//
|
|
{
|
|
CStringArray as1;
|
|
HTREEITEM item1 = m_tree.InsertItem( "Songs" );
|
|
GetDirListing( "Songs\\*.*", as1, true, false );
|
|
for( unsigned i=0; i<as1.size(); i++ )
|
|
{
|
|
CStringArray as2;
|
|
HTREEITEM item2 = m_tree.InsertItem( as1[i], item1 );
|
|
GetDirListing( "Songs\\" + as1[i] + "\\*.*", as2, true, false );
|
|
for( unsigned j=0; j<as2.size(); j++ )
|
|
m_tree.InsertItem( as2[j], item2 );
|
|
}
|
|
}
|
|
|
|
|
|
// Strip out any "CVS" items
|
|
CArray<HTREEITEM,HTREEITEM> aItems;
|
|
GetTreeItems( aItems );
|
|
for( int i=0; i<aItems.GetSize(); i++ )
|
|
if( m_tree.GetItemText(aItems[i]).CompareNoCase("CVS")==0 )
|
|
m_tree.DeleteItem( aItems[i] );
|
|
}
|
|
|
|
void CSmpackageExportDlg::OnButtonOpen()
|
|
{
|
|
// TODO: Add your control notification handler code here
|
|
|
|
char szCurDir[MAX_PATH];
|
|
GetCurrentDirectory( MAX_PATH, szCurDir );
|
|
|
|
char szCommandToExecute[MAX_PATH] = "explorer ";
|
|
strcat( szCommandToExecute, szCurDir );
|
|
|
|
|
|
PROCESS_INFORMATION pi;
|
|
STARTUPINFO si;
|
|
ZeroMemory( &si, sizeof(si) );
|
|
|
|
CreateProcess(
|
|
NULL, // pointer to name of executable module
|
|
szCommandToExecute, // pointer to command line string
|
|
NULL, // process security attributes
|
|
NULL, // thread security attributes
|
|
false, // handle inheritance flag
|
|
0, // creation flags
|
|
NULL, // pointer to new environment block
|
|
NULL, // pointer to current directory name
|
|
&si, // pointer to STARTUPINFO
|
|
&pi // pointer to PROCESS_INFORMATION
|
|
);
|
|
}
|