//-----------------------------------------------------------------------------

#include "stdafx.h"
#include "msitoddf.h"

static PTCHAR GetFileLongName( PTCHAR szFileName );
static PTCHAR GetErrorMsg( UINT iRet );
static bool CreateCABDDF( void );
static bool FileExists( PTCHAR szFileName );
static bool CreateCabsTableDefinitionFile( void );
static bool LookupComponentTargetFolder( PTCHAR szComponent, PTCHAR szComponentFolder );
static void Log( LPCTSTR fmt, ... );

PMSIHANDLE g_hDatabase = NULL;
PMSIHANDLE g_hInstall = NULL;
UINT g_iFileCount;

TCHAR g_MSIFile[ MAX_PATH ];
TCHAR g_LogFile[ MAX_PATH ];
TCHAR g_DDFFile[ MAX_PATH ];
TCHAR g_CABFile[ MAX_PATH ];
TCHAR g_RootDir[ MAX_PATH ];

//--------------------------------------------------------------

int __cdecl _tmain( int argc, TCHAR* argv[], TCHAR* envp[] )
{
	UINT iRet = 0;

	if( argc < 2 )
	{
		_tprintf( TEXT( "Extracts the file list from an MSI installer database and creates a DDF file\n\n" ) );
		_tprintf( TEXT( "Usage: %s <MSI file path> [-L <error log file path>]\n\n" ), argv[ 0 ] );
		return 1;
	}

	//MessageBox( NULL, TEXT( "UDDI" ), TEXT( "UDDI" ), MB_OK) ;

	_tcscpy( g_MSIFile, argv[ 1 ] );

	TCHAR szDrive[ _MAX_DRIVE ];
	TCHAR szPath[ _MAX_DIR ];
	TCHAR szFileName[ _MAX_FNAME ];
	_tsplitpath( g_MSIFile, szDrive, szPath, szFileName, NULL );

	_stprintf( g_DDFFile, TEXT( "%s%s%s.DDF" ), szDrive, szPath, szFileName );
	_stprintf( g_CABFile, TEXT( "%s.CAB" ), szFileName );
	_stprintf( g_RootDir, TEXT( "%s%s" ), szDrive, szPath );

	if( argc > 3 && 0 == _tcsicmp( argv[ 2 ], TEXT( "-L" ) ) )
	{
		_tcsncpy( g_LogFile, argv[ 3 ], MAX_PATH );
	}
	else
	{
		*g_LogFile = NULL;
	}

	UINT MainFn();
	iRet = MainFn();

	//
	// 4chan: bugfix: app won't exit if this handle isn't closed, under win10 at least
	//
	if ( g_hInstall != NULL )
	{
		MsiCloseHandle(g_hInstall);
	}
	return iRet;
}

//
// 4chan: wrap main code in seperate function, so we can always call MsiCloseHandle above despite whatever it returns
//
UINT MainFn()
{
	//
	// open the database
	//
	if( !FileExists( g_MSIFile ) )
	{
		Log(  TEXT( "*** Error: MSI File does not exist: %s" ), g_MSIFile );
		return 1;
	}

	UINT iRet = MsiOpenDatabase( g_MSIFile, MSIDBOPEN_READONLY, &g_hDatabase );
	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiOpenDatabase Error: %s" ), GetErrorMsg( iRet ) );
		return 1;
	}

	//
	// get a database handle
	//
	TCHAR szDBHandle[ 10 ];
	_stprintf( szDBHandle, TEXT( "#%d" ), (DWORD) (MSIHANDLE) g_hDatabase );

	g_hInstall = NULL;
	iRet = MsiOpenPackage( szDBHandle, &g_hInstall );
	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiOpenPackage Error: %s" ), GetErrorMsg( iRet ) );
		g_hInstall = NULL;
	}
	else
	{
		if( ERROR_SUCCESS != MsiDoAction( g_hInstall, TEXT( "CostInitialize" ) ) )
			return 1;
		if( ERROR_SUCCESS != MsiDoAction( g_hInstall, TEXT( "FileCost" ) ) )
			return 1;
		if( ERROR_SUCCESS != MsiDoAction( g_hInstall, TEXT( "CostFinalize" ) ) )
			return 1;
		if( !CreateCABDDF() )
			return 1;
	}

	//
	// commit the changes
	//
	iRet = MsiDatabaseCommit( g_hDatabase );
	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiDatabaseCommit Error: %s" ), GetErrorMsg( iRet ) );
		return 1;
	}

	return 0;
}

//--------------------------------------------------------------

static bool CreateCABDDF( void )
{
	UINT iRet;
	PMSIHANDLE hView;
	PMSIHANDLE hRecord = 0;

	//
	// the install dir will give us the root of the path that we
	// can use later and trim it off
	//
	TCHAR szInstallDir[ _MAX_PATH ];
	DWORD dwSize = _MAX_PATH;
	iRet = MsiGetProperty( g_hInstall, TEXT("uddi"), szInstallDir, &dwSize );
	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiGetProperty Error: %s" ), GetErrorMsg( iRet ) );
		return false;
	}

	//
	// open a view and submit the SQL query
	//
	TCHAR szSQLQuery[ 256 ];
	_stprintf( szSQLQuery, TEXT( "select File, FileName, Component_, Sequence from File order by Sequence" ) );

	iRet = MsiDatabaseOpenView( g_hDatabase, szSQLQuery , &hView );

	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiDatabaseOpenView Error: %s" ), GetErrorMsg( iRet ) );
		return false;
	}

	//
	// execute the SQL query
	//
	iRet = MsiViewExecute( hView, hRecord );

	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiViewExecute Error: %s" ), GetErrorMsg( iRet ) );
		return false;
	}

	//
	// open the DDF file
	//
	FILE *file = _tfopen( g_DDFFile, TEXT( "w" ) );
	if( NULL == file )
	{
		Log(  TEXT( "*** Error: Unable to open output file: %s, Error code %d" ), g_DDFFile, GetLastError() );
		return false;
	}

	//
	// header in DDF file used by makecab.exe
	//
	_fputts( TEXT( ".Option Explicit\n" ), file );
	_fputts( TEXT( ".Set DiskDirectoryTemplate=.\n" ), file );
	_fputts( TEXT( ".Set CabinetName1=" ), file );
	_fputts( g_CABFile, file );
	_fputts( TEXT( "\n" ), file );
	_fputts( TEXT( ".Set RptFilename=nul\n" ), file );
	_fputts( TEXT( ".Set InfFileName=nul\n" ), file );
	_fputts( TEXT( ".Set InfAttr=\n" ), file );
	_fputts( TEXT( ".Set MaxDiskSize=CDROM\n" ), file );
	_fputts( TEXT( ".Set CompressionType=LZX\n" ), file );
	_fputts( TEXT( ".Set CompressionMemory=21\n" ), file );
	_fputts( TEXT( ".Set CompressionLevel=1\n" ), file );
	_fputts( TEXT( ".Set Compress=ON\n" ), file );
	_fputts( TEXT( ".Set Cabinet=ON\n" ), file );
	_fputts( TEXT( ".Set UniqueFiles=ON\n" ), file );
	_fputts( TEXT( ".Set FolderSizeThreshold=1000000\n" ), file );
	_fputts( TEXT( ".Set MaxErrors=300\n" ), file );

	//
	// fetch the data
	//
	TCHAR szFile[ 256 ];
	TCHAR szFileName[ 256 ];
	TCHAR szComponent[ 256 ];
	TCHAR szComponentFolder[ 256 ];
	TCHAR szFilePath[ 256 ];
	int iSequence;
	DWORD cchValueBuf;
	g_iFileCount = 0;

	while( ERROR_NO_MORE_ITEMS != MsiViewFetch( hView, &hRecord ) )
	{
		g_iFileCount++;
		cchValueBuf = 256;
		MsiRecordGetString( hRecord, 1, szFile, &cchValueBuf );

		cchValueBuf = 256;
		MsiRecordGetString( hRecord, 2, szFileName, &cchValueBuf );

		cchValueBuf = 256;
		MsiRecordGetString( hRecord, 3, szComponent, &cchValueBuf );

		iSequence = MsiRecordGetInteger( hRecord, 4 );

		//
		// query the MSI for the target folder for this file.
		// this will tell us where it is located in the \binaries folders.
		//
		if( !LookupComponentTargetFolder( szComponent, szComponentFolder ) )
		{
			Log(  TEXT( "**ERROR: Unable to find directory for component: %s" ), szComponent );
			return false;
		}

		TCHAR szWindowsDir[ MAX_PATH ];
		if( 0 == ExpandEnvironmentStrings( TEXT( "%systemroot%" ), szWindowsDir, MAX_PATH ) )
			return false;

		//_tprintf( TEXT( "windir=%s\n" ), szWindowsDir );
		//_tprintf( TEXT( "szComponentFolder=%s\n" ), szComponentFolder );

		//
		// look for files that are in the windows folder
		//
		if( 0 == _tcsnicmp( szComponentFolder, szWindowsDir, _tcslen( szWindowsDir ) ) )
		{
			//
			// 4chan: replace SysWOW64 in the path with System32, as 64-bit machines may return that
			//
			TCHAR* sysWowPos     = szComponentFolder + _tcslen( szWindowsDir ) + 1;
			TCHAR* sysWowTest	 = TEXT("syswow64");
			TCHAR* sysWowReplace = TEXT("system32");
			if( 0 == _tcsnicmp( sysWowPos, sysWowTest, _tcslen( sysWowTest ) ) )
			{
				for( size_t i = 0; i < _tcslen( sysWowReplace ); i++ ) {
					sysWowPos[i] = sysWowReplace[i];
				}
			}

			//
			// create the full path to the file in \binaries folder. e.g. \binaries\uddi\windows\system32
			//
			_stprintf( szFilePath, TEXT( "%s%s%s" ),
				g_RootDir,											// start with the root (c:\binariesx\uddi\)
				szComponentFolder + _tcslen( szWindowsDir ) + 1,
				GetFileLongName( szFileName ) );					// add file name
		}
		else
		{
			//
			// create the full path to the file in \binaries folder
			//
			_stprintf( szFilePath, TEXT( "%s%s%s" ),
				g_RootDir,										// start with the root (c:\binariesx\uddi)
				&szComponentFolder[ _tcslen( szInstallDir ) ],	// strip installdir prefix and append
				GetFileLongName( szFileName ) );				// add file name
		}

		//
		// if it exists (and it should), trim the c:\binaries\... part on put the file
		// name into the DDF file
		//
		if( FileExists( szFilePath ) )
		{
			_fputts( TEXT( "\"" ), file );
			_fputts( &szFilePath[ _tcslen( g_RootDir ) ], file ); // trim the root dir
			_fputts( TEXT( "\"\t\"" ), file );
			_fputts( szFile, file );
			_fputts( TEXT( "\"\n" ), file );
		}
		else
		{
			fclose( file );
			Log( TEXT( "*** ERROR: Source File does not exist: %s" ), szFilePath );
			Log( TEXT( "File=%s, %s" ), szFile, szFileName );
			Log( TEXT( "Component=%s" ), szComponent );
			Log( TEXT( "Component Folder=%s" ), szComponentFolder );
			Log( TEXT( "systemroot=%s" ), szWindowsDir );
			return false;
		}
	}

	fclose( file );

	return true;
}

//--------------------------------------------------------------

static bool LookupComponentTargetFolder( PTCHAR szComponent, PTCHAR szComponentFolder )
{
	UINT iRet;
	PMSIHANDLE hView;
	PMSIHANDLE hRecord = 0;
	//
	// open a view and submit the SQL query
	//
	TCHAR szSQLQuery[ 256 ];

	_stprintf( szSQLQuery, TEXT( "select Directory_ from Component where Component = '%s'" ), szComponent );

	iRet = MsiDatabaseOpenView( g_hDatabase, szSQLQuery , &hView );

	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiDatabaseOpenView Error: %s" ), GetErrorMsg( iRet ) );
		return false;
	}

	//
	// execute the SQL query
	//
	iRet = MsiViewExecute( hView, hRecord );

	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiViewExecute Error: %s" ), GetErrorMsg( iRet ) );
		return false;
	}

	//
	// fetch the data
	//
	TCHAR szDirectoryKey[ 256 ];
	DWORD cchValueBuf = 256;
	if( ERROR_SUCCESS != MsiViewFetch( hView, &hRecord ) )
	{
		Log(  TEXT( "*** ERROR: Directory not found for component [ %s ]" ), szComponent );
		return false;
	}

	// get the key into the Directory table
	MsiRecordGetString( hRecord, 1, szDirectoryKey, &cchValueBuf );

	// look up the full path
	DWORD cchPathBuf = 256;
	iRet = MsiGetTargetPath( g_hInstall, szDirectoryKey, szComponentFolder, &cchPathBuf );

	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: MsiGetSourcePath Error component [ %s ] %s" ), szComponent, GetErrorMsg( iRet ) );
		return false;
	}

	return true;
}

//--------------------------------------------------------------
// returns the long file name out of a string that has both
// short and long name separated by a pipe character
static PTCHAR GetFileLongName( PTCHAR szFileName )
{
	PTCHAR pStart = _tcschr( szFileName, TEXT( '|' ) );

	if( pStart )
	{
		return pStart + 1; // add 1 to skip over the pipe symbol
	}

	return szFileName;
}

//--------------------------------------------------------------

static bool FileExists( PTCHAR szFileName )
{
	FILE *file = _tfopen( szFileName, TEXT( "rt" ) );
	if( NULL == file )
	{
		return false;
	}

	fclose( file );

	return true;
}

//--------------------------------------------------------------------------

static bool SetProperty( PTCHAR szProperty, PTCHAR szValue )
{
	UINT iRet = MsiSetProperty( g_hInstall, szProperty, szValue );

	if( ERROR_SUCCESS != iRet )
	{
		Log(  TEXT( "*** Error: SetProperty Error: %s, Property %s, Value %s" ),
			GetErrorMsg( iRet ),
			szProperty,
			szValue );

		return false;
	}

	return true;
}

//--------------------------------------------------------------

static PTCHAR GetErrorMsg( UINT iRet )
{
	static TCHAR szErrMsg[ 100 ];

	FormatMessage( 
		FORMAT_MESSAGE_FROM_SYSTEM | 
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		iRet,
		MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language
		( LPTSTR ) szErrMsg,
		100,
		NULL);

	return szErrMsg;
}

//--------------------------------------------------------------------------

static void Log( LPCTSTR fmt, ... )
{
	TCHAR szTime[ 10 ];
	TCHAR szDate[ 10 ];
	::_tstrtime( szTime );
	::_tstrdate( szDate );

	va_list marker;
	TCHAR szBuf[1024];

	size_t cbSize = ( sizeof( szBuf ) / sizeof( TCHAR ) ) - 1; // one byte for null
	_sntprintf( szBuf, cbSize, TEXT( "%s %s: " ), szDate, szTime );
	szBuf[ 1023 ] = '\0';
	cbSize -= _tcslen( szBuf );

	va_start( marker, fmt );

	_vsntprintf( szBuf + _tcslen( szBuf ), cbSize, fmt, marker );
	szBuf[ 1023 ] = '\0';
	cbSize -= _tcslen( szBuf );

	va_end( marker );

	_tcsncat(szBuf, TEXT("\r\n"), cbSize );

	_tprintf( TEXT( "%s" ), szBuf );

	if( 0 == _tcslen( g_LogFile ) )
		return;

	// write the data out to the log file
	char szBufA[ 1024 ];
	WideCharToMultiByte( CP_ACP, 0, szBuf, -1, szBufA, 1024, NULL, NULL );

	HANDLE hFile = CreateFile(
		g_LogFile,                    // file name
		GENERIC_WRITE,                // open for writing 
		0,                            // do not share 
		NULL,                         // no security 
		OPEN_ALWAYS,                  // open and create if not exists
		FILE_ATTRIBUTE_NORMAL,        // normal file 
		NULL);                        // no attr. template 

	if( hFile == INVALID_HANDLE_VALUE )
	{ 
		return;
	}

	//
	// move the file pointer to the end so that we can append
	//
	SetFilePointer( hFile, 0, NULL, FILE_END );

	DWORD dwNumberOfBytesWritten;
	BOOL bOK = WriteFile(
		hFile,
		szBufA,
		(UINT) strlen( szBufA ),     // number of bytes to write
		&dwNumberOfBytesWritten,                       // number of bytes written
		NULL);                                         // overlapped buffer

	FlushFileBuffers ( hFile );
	CloseHandle( hFile );
}
