// DenAgent.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "DenAgent.h"
#include "DenAgentDlg.h"

#include "TrayWnd.h"
#include <initguid.h>
#include "DenAgent_i.c"
#include "..\Inject\Inject.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//FILELIST g_FileList[] = { { "messages.xml", "messages.dlc" }, { "memlocs.xml", "messages.dlc" }, { "decalplugins.xml", "messages.dlc" } };

/////////////////////////////////////////////////////////////////////////////
// CDenAgentApp

BEGIN_MESSAGE_MAP(CDenAgentApp, CWinApp)
	//{{AFX_MSG_MAP(CDenAgentApp)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG
	ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDenAgentApp construction

CDenAgentApp::CDenAgentApp()
{
	// TODO: add construction code here,
	// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CDenAgentApp object

CDenAgentApp theApp;
LONG g_fAbortDownload;

/////////////////////////////////////////////////////////////////////////////
// CDenAgentApp initialization

bool CheckForHardwareMode ()
{
	RegKey key;

	if (key.Open (HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00", KEY_READ) != ERROR_SUCCESS)
	{
		return false;
	}

	DWORD dwValue = 0;

	if (key.QueryDWORDValue ("UseHardware", dwValue) != ERROR_SUCCESS)
	{
		return false;
	}

	return dwValue ? true : false;
}

bool CheckForIE5OrLater ()
{
	RegKey key;

	if (key.Open (HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Internet Explorer", KEY_READ) != ERROR_SUCCESS)
		return false;

	DWORD dwValue = 0;

	TCHAR szVersionBuffer[256];
	DWORD dwSize = sizeof (szVersionBuffer);

	if (key.QueryStringValue("Version", szVersionBuffer, &dwSize) != ERROR_SUCCESS)
		return false;

	DWORD dwMajor = 0, dwMinor = 0, dwBuild1 = 0, dwBuild2 = 0;

	if (::_stscanf (szVersionBuffer, _T("%ld.%ld.%ld.%ld"), &dwMajor, &dwMinor, &dwBuild1, &dwBuild2) != 4)
		return false;

	// IE 5.01 is build: 5.00.2919.6307
	return !( dwMajor < 5 || ( dwMajor == 5 && dwMinor == 0 ) && ( ( dwBuild1 < 2919 ) || ( dwBuild1 == 2919 && dwBuild2 < 6307 ) ) );
}

bool CDenAgentApp::getVersionString ( LPCTSTR szFilename, CString &strVersion )
{
   DWORD dwDummy, 
      dwVerSize = ::GetFileVersionInfoSize( const_cast< LPTSTR > ( szFilename ), &dwDummy );
   if( dwVerSize == 0 )
      return false;

   BYTE *pbVersionInfo = reinterpret_cast< BYTE * >( ::_alloca( dwVerSize ) );

   ::GetFileVersionInfo( const_cast< LPTSTR > ( szFilename ), 0, dwVerSize, pbVersionInfo );

   VS_FIXEDFILEINFO *vffi;
   UINT nLength = sizeof( VS_FIXEDFILEINFO );
   if( !::VerQueryValue( pbVersionInfo, _T( "\\" ), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
      return false;

   // Got it, so format it
   strVersion.FormatMessage ( IDS_VERSIONTEMPLATE, static_cast< int > ( HIWORD ( vffi->dwFileVersionMS ) ),
      static_cast< int > ( LOWORD ( vffi->dwFileVersionMS ) ), static_cast< int > ( HIWORD ( vffi->dwFileVersionLS ) ),
      static_cast< int > ( LOWORD ( vffi->dwFileVersionLS ) ) );

   return true;
}

bool CDenAgentApp::getVersionInfo ( LPCTSTR szFilename, int &iReleaseMajor, int &iReleaseMinor, int &iBuildMajor, int &iBuildMinor )
{
   DWORD dwDummy, 
      dwVerSize = ::GetFileVersionInfoSize( const_cast< LPTSTR > ( szFilename ), &dwDummy );
   if( dwVerSize == 0 )
      return false;

   BYTE *pbVersionInfo = reinterpret_cast< BYTE * >( ::_alloca( dwVerSize ) );

   ::GetFileVersionInfo( const_cast< LPTSTR > ( szFilename ), 0, dwVerSize, pbVersionInfo );

   VS_FIXEDFILEINFO *vffi;
   UINT nLength = sizeof( VS_FIXEDFILEINFO );
   if( !::VerQueryValue( pbVersionInfo, _T( "\\" ), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
      return false;

   // Got it, so format it
   iReleaseMajor = static_cast< int > ( HIWORD ( vffi->dwFileVersionMS ) );
   iReleaseMinor = static_cast< int > ( LOWORD ( vffi->dwFileVersionMS ) );
   iBuildMajor = static_cast< int > ( HIWORD ( vffi->dwFileVersionLS ) );
   iBuildMinor = static_cast< int > ( LOWORD ( vffi->dwFileVersionLS ) );

   return true;
}

bool CDenAgentApp::getACVersionString ( CString &strVersion )
{
   RegKey rk;
   if ( rk.Open ( HKEY_LOCAL_MACHINE, _T( "Software\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00" ) ) != ERROR_SUCCESS )
   {
      ::AfxMessageBox ( IDE_NOCLIENTEXE, MB_ICONWARNING );
      return false;
   }

   TCHAR szClientPath[ MAX_PATH ];
   DWORD dwPathLength = MAX_PATH;
   if ( rk.QueryStringValue ( _T( "path" ), szClientPath, &dwPathLength ) != ERROR_SUCCESS )
   {
      ::AfxMessageBox ( IDE_NOCLIENTEXE, MB_ICONWARNING );
      return false;
   }

   ::_tcscpy ( szClientPath + ( dwPathLength - 1 ), _T( "\\client.exe" ) );

   bool bHasVersion = getVersionString ( szClientPath, strVersion );

   if ( !bHasVersion )
      ::AfxMessageBox ( IDE_NOCLIENTVER, MB_ICONWARNING );

   return bHasVersion;
}

bool CDenAgentApp::getCOMObjectDLL ( REFCLSID rclsid, CString &strFilename )
{
   USES_CONVERSION;

   LPOLESTR oclsid;
   HRESULT hRes = ::StringFromCLSID ( rclsid, &oclsid );
   _ASSERTE ( SUCCEEDED ( hRes ) );
   LPCTSTR clsidName = OLE2T ( oclsid );
   ::CoTaskMemFree ( oclsid );

   TCHAR keyName[ MAX_PATH ];
   ::_stprintf ( keyName, _T( "CLSID\\%s\\InprocServer32" ), clsidName );

   RegKey rk;
   if ( rk.Open ( HKEY_CLASSES_ROOT, keyName ) != ERROR_SUCCESS )
      return false;

   DWORD dwPathSize = MAX_PATH;

   long regResult = rk.QueryStringValue ( NULL, strFilename.GetBuffer ( MAX_PATH ), &dwPathSize );

   strFilename.ReleaseBuffer();

   if (regResult != ERROR_SUCCESS)
      return false;

   // Check to see if the default KeyValue points to the MS.NET core DLL
   const char *dotNetProxy = "mscoree.dll";

   CString dotNetMatchString = strFilename.Right(strlen(dotNetProxy));
   
   if (dotNetMatchString.CompareNoCase(dotNetProxy) == 0)
   {
	  // Get CodeBase KeyValue - Should point to the Plugin DLL
   	  dwPathSize = MAX_PATH;	  
		if (rk.QueryStringValue ( "CodeBase", strFilename.GetBuffer ( MAX_PATH ), &dwPathSize ) == ERROR_SUCCESS)
		{
		 // Release extra buffer (Not doing this seem to cause the Left() and Delete() funcs to fail)
		 strFilename.ReleaseBuffer();
		 // Check for garbage in KeyValue and delete it
		 if (strFilename.Left(8) == "file:///")
			strFilename.Delete(0,8);
	  }
	  else
    {
		 // CodeBase isn't accessable - Possible that plugin is in GAC 
		 // Return Path to MS.NET core DLL
		 dwPathSize = MAX_PATH;
		 if (rk.QueryStringValue ( NULL, strFilename.GetBuffer ( MAX_PATH ), &dwPathSize ) != ERROR_SUCCESS)
		 {
			rk.Close();
			strFilename.ReleaseBuffer();
      return false;	  
		 }
    }
   }
   rk.Close();
   return true;
}

bool CDenAgentApp::getAgentPath ( LPCTSTR szFilename, CString &strPath )
{
   TCHAR *szAgentPath = strPath.GetBuffer ( MAX_PATH );
   ::GetModuleFileName ( NULL, szAgentPath, MAX_PATH );

   TCHAR *filePath = ::_tcsrchr ( szAgentPath, _T( '\\' ) );
   ::strcpy ( filePath + 1, szFilename );

   return true;
}

bool CDenAgentApp::checkXMLVersion ( CString &strClientVersion, CString &strXMLFile, CTrayWnd* pTrayWnd )
{
   MSXML::IXMLDOMDocumentPtr pDoc;

   try
   {
      pDoc.CreateInstance ( __uuidof ( MSXML::DOMDocument ), NULL, CLSCTX_INPROC_SERVER );
      pDoc->async = false;
      BOOL bSuccess = pDoc->load( static_cast< LPCTSTR > ( strXMLFile ) );

	  if( !bSuccess )
	  {
			std::string szXML;
			DecryptXML( static_cast< LPCSTR >( strXMLFile ), szXML );

			if( szXML != "" )
				bSuccess = pDoc->loadXML( _bstr_t( szXML.c_str() ) );
	  }

	  if( ! bSuccess )
	  {
         CString strMessage;
         strMessage.FormatMessage ( IDE_NOXMLDOC, static_cast< LPCTSTR > ( strXMLFile ) );

		 CString strUpdate;
		 strUpdate.LoadString( IDE_UPDATETEXT );
		 strMessage += strUpdate;

         if ( ::AfxMessageBox ( strMessage, MB_YESNO | MB_ICONERROR ) == IDYES )
			 pTrayWnd->UpdateXMLFiles( );

         return false;
      }

      // Read the client version string
      MSXML::IXMLDOMNodePtr pVersionNode = pDoc->selectSingleNode ( _T( "/*/@version" ) );
      if ( pVersionNode == NULL )
      {
         CString strError;

         strError.FormatMessage ( IDE_NOXMLVER, static_cast< LPCTSTR > ( strXMLFile ) );
         ::AfxMessageBox ( strError, MB_ICONWARNING );

         return false;
      }

      _variant_t vXMLVer = pVersionNode->text;
      if ( vXMLVer.vt != VT_BSTR )
      {
         CString strError;

         strError.FormatMessage ( IDE_NOXMLVER, static_cast< LPCTSTR > ( strXMLFile ) );
         ::AfxMessageBox ( strError, MB_ICONWARNING );

         return false;
      }

      CString strXMLVer ( vXMLVer.bstrVal );
      if ( strClientVersion.Compare ( strXMLVer ) != 0 )
      {
         CString str;
         str.FormatMessage ( IDE_XMLCLIENTVERSIONMISMATCH, static_cast< LPCTSTR > ( strXMLFile ),
            static_cast< LPCTSTR > ( strClientVersion ), static_cast< LPCTSTR > ( strXMLVer )  );
		 
		 CString strUpdate;
		 strUpdate.LoadString( IDE_UPDATETEXT );
		 str += strUpdate;

         if ( ::AfxMessageBox ( str, MB_YESNO | MB_ICONWARNING ) == IDYES )
			 pTrayWnd->UpdateXMLFiles( );

         return false;
      }

      return true;
   }
   catch ( _com_error & )
   {
      CString strMessage;
      strMessage.FormatMessage ( IDE_NOXMLDOC, static_cast< LPCTSTR > ( strXMLFile ) );
      ::AfxMessageBox ( strMessage, MB_ICONERROR );
   }

   return true;
}

void CDenAgentApp::DecryptXML( const char *szPath, std::string &szXML )
{
	if( szPath == NULL )
	{
		szXML = "";
		return;
	}

	FILE *f = fopen( szPath, "rb" );
	if( f == NULL )
	{
		szXML = "";
		return;
	}

	szXML.clear();
	unsigned char szBuffer[1025];

	try
	{
		CCryptProv crypt;
		if( crypt.Initialize( PROV_RSA_FULL, "Decal_Memlocs", MS_DEF_PROV ) == NTE_BAD_KEYSET )
			crypt.Initialize( PROV_RSA_FULL, "Decal_Memlocs", MS_DEF_PROV, CRYPT_NEWKEYSET );

		CCryptMD5Hash hash;
		hash.Initialize( crypt );
		hash.AddString( DECAL_KEY );

		CCryptDerivedKey key;
		key.Initialize( crypt, hash );

		DWORD dwDecLen = 0;

		while( ! feof(f) )
		{
			memset( szBuffer, 0, sizeof( szBuffer ) );
			dwDecLen = fread( szBuffer, 1, 1024, f );
			key.Decrypt( feof(f), (BYTE *) szBuffer, &dwDecLen );
			szXML += (char *)szBuffer;
		}

		key.Destroy();
		hash.Destroy();
		crypt.Release();
	}

	catch( ... )
	{
		// crap...
		szXML = "";
	}

	fclose( f );
}

bool CDenAgentApp::checkXMLVersions ( CTrayWnd* pTrayWnd )
{
   bool bOK = true;
   // Check the versions of the XML files
   CString strClientVersion;
   if ( getACVersionString ( strClientVersion ) )
   {
      CString strXMLFile;
      if ( getAgentPath ( _T( "memlocs.xml" ), strXMLFile ) )
         bOK = bOK && checkXMLVersion ( strClientVersion, strXMLFile, pTrayWnd );
      //if ( getAgentPath ( _T( "messages.xml" ), strXMLFile ) )
         //bOK = bOK && checkXMLVersion ( strClientVersion, strXMLFile );
   }

   return bOK;
}

void CDenAgentApp::getXMLBuilds ( cXMLBuild *pFirst, cXMLBuild *pLast )
{
   MSXML::IXMLDOMDocumentPtr pDoc;
   pDoc.CreateInstance ( __uuidof ( MSXML::DOMDocument ), NULL, CLSCTX_INPROC_SERVER );
   pDoc->async = false;

   for ( ; pFirst != pLast; ++ pFirst )
   {
      CString strPath;
      getAgentPath ( pFirst->XMLFile, strPath );

      try
      {
         BOOL bSuccess = pDoc->load( static_cast< LPCTSTR > ( pFirst->XMLFile ) );

		 if( ! bSuccess )
		 {
			std::string szXML;
			DecryptXML( static_cast< LPCSTR >( pFirst->XMLFile ), szXML );

			if( szXML != "" )
				bSuccess = pDoc->loadXML( _bstr_t( szXML.c_str() ) );
		}

		 if( ! bSuccess )
		 {
            CString strMessage;
            strMessage.FormatMessage( IDE_NOXMLDOC, pFirst->XMLFile );
            ::AfxMessageBox ( strMessage, MB_ICONERROR );

            pFirst->build = 0;
            continue;
         }

         // Read the client version string
         MSXML::IXMLDOMNodePtr pBuildNode = pDoc->selectSingleNode ( _T( "/*/@build" ) );
         if ( pBuildNode == NULL )
         {
            pFirst->build = 0;
            continue;
         }

         pFirst->build = pBuildNode->GetnodeValue ();
      }
      catch ( _com_error & )
      {
         pFirst->build = 0;
      }
   }
}

BOOL CDenAgentApp::InitInstance()
{
	HWND hDenAgent = FindWindow(NULL, "Decal Agent");

	if(hDenAgent)
	{
		::MessageBox(hDenAgent, "Decal is already running!", "Decal", 0);
		return FALSE;
	}

	if (!CheckForHardwareMode ())
	{
		::MessageBox
			(
			NULL,
			_T("Decal requires Asheron's Call to be installed with hardware 3d acceleration enabled!  Decal will run, but will be unable to display any UI in-game."),
			_T("Decal Agent Startup Warning"),
			MB_OK
			);
	}

	if (!CheckForIE5OrLater ())
	{
		::MessageBox
			(
			NULL,
			"Decal requires Internet Explorer 5.01 or later!",
			"Decal Agent Startup Error",
			MB_OK
			);

		return FALSE;
	}

	if (!InitATL())
		return FALSE;


	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);

	if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
	{
		return TRUE;
	}

	// Set our current directory to the path of DenAgent.exe
	// TODO: Fix all the code so that it doesn't rely on the current directory
	TCHAR szAppPath[MAX_PATH];
	::memset (szAppPath, 0, sizeof (szAppPath));
	::GetModuleFileName (0, szAppPath, MAX_PATH);

	TCHAR *szLastSlash = ::_tcsrchr (szAppPath, _T('\\'));
	if (szLastSlash)
	{
		*szLastSlash = 0;
		::SetCurrentDirectory (szAppPath);
	}

   // Move V1 plugins into the V2 registry format
   importV1Plugins ();

	// Standard initialization
	// If you are not using these features and wish to reduce the size
	//  of your final executable, you should remove from the following
	//  the specific initialization routines you do not need.
   m_pTrayWnd = new CTrayWnd;
   m_pMainWnd = m_pTrayWnd;

   m_pTrayWnd->CreateEx( 0, ::AfxRegisterWndClass( 0, NULL, NULL, NULL ), _T( "Decal Agent" ), WS_POPUP, 0, 0, 0, 0, NULL, NULL, NULL );
   
   checkXMLVersions ( m_pTrayWnd );
   
   return TRUE;
}

	
CDenAgentModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()

LONG CDenAgentModule::Unlock()
{
	AfxOleUnlockApp();
	return 0;
}

LONG CDenAgentModule::Lock()
{
	AfxOleLockApp();
	return 1;
}
LPCTSTR CDenAgentModule::FindOneOf(LPCTSTR p1, LPCTSTR p2)
{
	while (*p1 != NULL)
	{
		LPCTSTR p = p2;
		while (*p != NULL)
		{
			if (*p1 == *p)
				return CharNext(p1);
			p = CharNext(p);
		}
		p1++;
	}
	return NULL;
}

int CDenAgentApp::ExitInstance()
{
	delete m_pTrayWnd;

	if (m_bATLInited)
	{
		_Module.RevokeClassObjects();
		_Module.Term();
		CoUninitialize();
	}

	return CWinApp::ExitInstance();

}

void CDenAgentApp::importV1Plugins ()
{
   USES_CONVERSION;

   RegKey hkGroup;
   if ( hkGroup.Open ( HKEY_LOCAL_MACHINE, _T ( "Software\\Decal\\Plugins" ) ) != ERROR_SUCCESS )
      // No plugins - no problem
      return;

   DWORD dwValues = 0;
   ::RegQueryInfoKey ( hkGroup, NULL, NULL, NULL, NULL, NULL, NULL, &dwValues, NULL, NULL, NULL, NULL );

   // Now - iterate through the values backwards
   for ( int i = dwValues - 1; i >= 0; -- i )
   {
      TCHAR szCLSID[ 64 ];
      DWORD dwCLSID = sizeof ( szCLSID );
      DWORD dwEnabled;
      DWORD dwEnabledSize = sizeof ( dwEnabled );

      if ( ::RegEnumValue ( hkGroup, i, szCLSID, &dwCLSID, NULL, NULL, reinterpret_cast< BYTE * > ( &dwEnabled ), &dwEnabledSize ) != ERROR_SUCCESS )
         continue;
      
      // Try and convert the name to a CLSID
      CLSID clsidPlugin;
      HRESULT hRes = ::CLSIDFromString ( T2OLE ( szCLSID ), &clsidPlugin );
      if ( FAILED ( hRes ) )
         continue;

      // Delete the value and ask questions later
      hkGroup.DeleteValue ( szCLSID );

      // Try and create a key to extract the friendly name
      CComPtr< IPlugin > pPlugin;
      hRes = ::CoCreateInstance ( clsidPlugin, NULL, CLSCTX_INPROC_SERVER, __uuidof ( IPlugin ), 
         reinterpret_cast< LPVOID * > ( &pPlugin ) );

      if ( FAILED ( hRes ) )
         // Bad plugin - skip
         continue;

      CComBSTR strName;
      pPlugin->get_FriendlyName ( &strName );

      // Create the Real registry key
      // Key configuration copied from Decal/cActiveXSurrogate::Register
      RegKey hkeyItem;
      if ( hkeyItem.Create ( hkGroup, szCLSID ) != ERROR_SUCCESS )
         continue;

      // Friendly name
      hkeyItem.SetStringValue( NULL, OLE2T( strName ) );
      // Enabled by default
      hkeyItem.SetDWORDValue( _T( "Enabled" ), dwEnabled );
      // The V1 surrogate
      hkeyItem.SetStringValue( _T( "Surrogate" ), _T( "{3D837F6E-B5CA-4604-885F-7AB45FCFA62A}" ) );
      hkeyItem.Close();
   }
}

BOOL CDenAgentApp::InitATL()
{
	m_bATLInited = TRUE;

	HRESULT hRes = CoInitialize(NULL);

	if (FAILED(hRes))
	{
		m_bATLInited = FALSE;
		return FALSE;
	}

	_Module.Init(ObjectMap, AfxGetInstanceHandle());
	_Module.dwThreadID = GetCurrentThreadId();

	LPTSTR lpCmdLine = GetCommandLine(); //this line necessary for _ATL_MIN_CRT
	TCHAR szTokens[] = _T("-/");

	BOOL bRun = TRUE;
	LPCTSTR lpszToken = _Module.FindOneOf(lpCmdLine, szTokens);
	while (lpszToken != NULL)
	{
		if (lstrcmpi(lpszToken, _T("UnregServer"))==0)
		{
			_Module.UpdateRegistryFromResource(IDR_DENAGENT, FALSE);
			_Module.UnregisterServer(TRUE); //TRUE means typelib is unreg'd
			bRun = FALSE;
			break;
		}
		if (lstrcmpi(lpszToken, _T("RegServer"))==0)
		{
			_Module.UpdateRegistryFromResource(IDR_DENAGENT, TRUE);
			_Module.RegisterServer(TRUE);
			bRun = FALSE;
			break;
		}
		lpszToken = _Module.FindOneOf(lpszToken, szTokens);
	}

	if (!bRun)
	{
		m_bATLInited = FALSE;
		_Module.Term();
		CoUninitialize();
		return FALSE;
	}

	hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, 
		REGCLS_MULTIPLEUSE);
	if (FAILED(hRes))
	{
		m_bATLInited = FALSE;
		CoUninitialize();
		return FALSE;
	}	

	return TRUE;

}
