/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include <stdio.h>
#include <direct.h>		// windows directory handling fxns (for chdir)
#include <process.h>

//#define DETACH_EXE  // Microsoft trick to force loading of exe to memory 
#ifdef DETACH_EXE
	#include "dmdfm.h"		// Windows Load EXE into memory suff
#endif


#include "HeadSpin.h"
#include "hsStream.h"
#include "hsUtils.h"
#include "plClient.h"
#include "../plNetClient/plNetClientMgr.h"
#include "../plNetClient/plNetLinkingMgr.h"
#include "../plInputCore/plInputManager.h"
#include "../plUnifiedTime/plUnifiedTime.h"
#include "plPipeline.h"
#include "../plResMgr/plResManager.h"
#include "../plResMgr/plLocalization.h"
#include "../plFile/plEncryptedStream.h"

#include "../plStatusLog/plStatusLog.h"
#include "../pnProduct/pnProduct.h"
#include "../plNetGameLib/plNetGameLib.h"
#include "../plFile/plFileUtils.h"

#include "../plPhysX/plSimulationMgr.h"

#include "res\resource.h"

#include <shellapi.h>
#include "WinHttp.h"
//
// Defines
//

#define	CLASSNAME	"Plasma"	// Used in WinInit()
#define PARABLE_NORMAL_EXIT		0	// i.e. exited WinMain normally

#define TIMER_UNITS_PER_SEC (float)1e3
#define UPDATE_STATUSMSG_SECONDS 30
#define WM_USER_SETSTATUSMSG WM_USER+1

#if BUILD_TYPE == BUILD_TYPE_DEV
	#define STATUS_PATH L"www2.cyanworlds.com"
#else
	#define STATUS_PATH L"support.cyanworlds.com"
#endif

//
// Globals
//
hsBool gHasMouse = false;

extern hsBool gDataServerLocal;
extern hsBool gUseBackgroundDownloader;

enum
{
	kArgToDni,
	kArgSkipLoginDialog,
	kArgAuthSrv,
	kArgFileSrv,
	kArgGateKeeperSrv,
	kArgLocalData,
	kArgBackgroundDownloader,
};

static const CmdArgDef s_cmdLineArgs[] = {
	{ kCmdArgFlagged  | kCmdTypeBool,		L"ToDni",			kArgToDni	},
	{ kCmdArgFlagged  | kCmdTypeBool,		L"SkipLoginDialog",	kArgSkipLoginDialog	},
	{ kCmdArgFlagged  | kCmdTypeString,		L"AuthSrv",			kArgAuthSrv	},
	{ kCmdArgFlagged  | kCmdTypeString,		L"FileSrv",			kArgFileSrv	},
	{ kCmdArgFlagged  | kCmdTypeString,		L"GateKeeperSrv",	kArgGateKeeperSrv },
	{ kCmdArgFlagged  | kCmdTypeBool,		L"LocalData",		kArgLocalData	},
	{ kCmdArgFlagged  | kCmdTypeBool,		L"BGDownload",		kArgBackgroundDownloader	},
};

/// Made globals now, so we can set them to zero if we take the border and 
///	caption styles out ala fullscreen (8.11.2000 mcn)
int gWinBorderDX	= GetSystemMetrics( SM_CXSIZEFRAME );
int gWinBorderDY	= GetSystemMetrics( SM_CYSIZEFRAME );
int gWinMenuDY		= GetSystemMetrics( SM_CYCAPTION );

//#include "global.h"
plClient		*gClient;
bool			gPendingActivate = false;
bool			gPendingActivateFlag = false;

static bool		s_loginDlgRunning = false;
static CEvent   s_statusEvent(kEventManualReset);

FILE *errFP = nil;
HINSTANCE				gHInst = NULL;		// Instance of this app

static const unsigned	AUTH_LOGIN_TIMER	= 1;
static const unsigned	AUTH_FAILED_TIMER	= 2;

#define FAKE_PASS_STRING "********"

//============================================================================
// External patcher file
//============================================================================
#ifdef PLASMA_EXTERNAL_RELEASE

static wchar s_patcherExeName[] = L"UruLauncher.exe";

//============================================================================
// Internal patcher file
//============================================================================
#else

static wchar s_patcherExeName[] = L"plUruLauncher.exe";

#endif // PLASMA_EXTERNAL_RELEASE

//============================================================================
// PhysX installer
//============================================================================
static wchar s_physXSetupExeName[] = L"PhysX_Setup.exe";

//============================================================================
// TRANSGAMING detection  & dialog replacement
//============================================================================
typedef BOOL (WINAPI *IsTransgaming) (void);
typedef const char * (WINAPI *TGGetOS) (void);
typedef LPVOID (WINAPI *TGLaunchUNIXApp) (const char *pPath, const char *pMode);
typedef BOOL (WINAPI *TGUNIXAppReadLine) (LPVOID pApp, char *pBuf, int bufSize);
typedef BOOL (WINAPI *TGUNIXAppWriteLine) (LPVOID pApp, const char *pLine);
typedef BOOL (WINAPI *TGUNIXAppClose) (LPVOID pApp);

static bool TGIsCider = false;
static TGLaunchUNIXApp pTGLaunchUNIXApp;
static TGUNIXAppReadLine pTGUNIXAppReadLine;
static TGUNIXAppWriteLine pTGUNIXAppWriteLine;
static TGUNIXAppClose pTGUNIXAppClose;

#define TG_NEW_LOGIN_PATH "C:\\Program Files\\Uru Live\\Cider\\URU Live Login.app"
#define TG_NEW_LOGIN_POPEN_PATH "/transgaming/c_drive/Program Files/Uru Live/Cider/URU Live Login.app/Contents/MacOS/URU Live Login"
#define TG_OLD_LOGIN_POPEN_PATH "/URU Live Login.app/Contents/MacOS/URU Live Login"

#define TG_NEW_EULA_PATH "C:\\Program Files\\Uru Live\\Cider\\URU Live EULA.app"
#define TG_NEW_EULA_POPEN_PATH "/transgaming/c_drive/Program Files/Uru Live/Cider/URU Live EULA.app/Contents/MacOS/URU Live EULA"
#define TG_OLD_EULA_POPEN_PATH "/URU Live EULA.app/Contents/MacOS/URU Live EULA"

//============================================================================
// LoginDialogParam
//============================================================================
struct LoginDialogParam {
	bool		fromGT;
	ENetError	authError;
	wchar		accountName[kMaxAccountNameLength];
};

bool AuthenticateNetClientComm(ENetError* result, HWND parentWnd);
bool IsExpired();
void GetCryptKey(UInt32* cryptKey, unsigned size);
static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHash, bool remember_password,
								  bool fromGT);
static void LoadUserPass (const wchar *accountName, char *username, ShaDigest *pNamePassHash, bool *pRemember,
								  bool fromGT, int *pFocus);
static void AuthFailedStrings (ENetError authError, bool fromGT,
										 const char **ppStr1, const char **ppStr2,
										 const wchar **ppWStr);

#if 0
// For networking
const GUID NEXUS_GUID = { 
	 0x5bfdb060, 0x6a4, 0x11d0, 0x9c, 0x4f, 0x0, 0xa0, 0xc9, 0x5, 0x42, 0x5e};
#endif


// Detect whether we're running under TRANSGAMING Cider
//==============================================================================
static void TGDoCiderDetection ()
{
	HMODULE hMod = GetModuleHandle ("ntdll");
	if (!hMod)
		return;

	IsTransgaming pIsTg = (IsTransgaming)GetProcAddress (hMod, "IsTransgaming");
	if (!pIsTg || !pIsTg ())
		return;

	TGGetOS pTGOS = (TGGetOS)GetProcAddress (hMod, "TGGetOS");
	const char *pOS = NULL;
	if (pTGOS)
		pOS = pTGOS ();
	if (!pOS || strcmp (pOS, "MacOSX"))
		return;

	TGIsCider = true;
	pTGLaunchUNIXApp = (TGLaunchUNIXApp)GetProcAddress (hMod, "TGLaunchUNIXApp");
	pTGUNIXAppReadLine = (TGUNIXAppReadLine)GetProcAddress (hMod, "TGUNIXAppReadLine");
	pTGUNIXAppWriteLine = (TGUNIXAppWriteLine)GetProcAddress (hMod, "TGUNIXAppWriteLine");
	pTGUNIXAppClose = (TGUNIXAppClose)GetProcAddress (hMod, "TGUNIXAppClose");
}

static bool TGRunLoginDialog (const wchar *accountName, bool fromGT)
{
	ShaDigest NamePassHash;
	char Username[kMaxAccountNameLength + 5];
	int Focus;
	bool bRemember = false;

	LoadUserPass (accountName, Username, &NamePassHash, &bRemember, fromGT, &Focus);

	while (true)
	{
		LPVOID pApp;
		if (GetFileAttributes (TG_NEW_LOGIN_PATH) != INVALID_FILE_ATTRIBUTES)
			pApp = pTGLaunchUNIXApp (TG_NEW_LOGIN_POPEN_PATH, "r+");
		else
			pApp = pTGLaunchUNIXApp (TG_OLD_LOGIN_POPEN_PATH, "r+");

		if (!pApp)
		{
			hsMessageBox ("Incomplete or corrupted installation!\nUnable to locate Login dialog",
				"Error", hsMessageBoxNormal);
			return false;
		}

		// Send user/pwd/remember
		pTGUNIXAppWriteLine (pApp, Username);
		if (bRemember)
		  pTGUNIXAppWriteLine (pApp, FAKE_PASS_STRING);
		else
		  pTGUNIXAppWriteLine (pApp, "");
		if (bRemember)
		  pTGUNIXAppWriteLine (pApp, "y");
		else
		  pTGUNIXAppWriteLine (pApp, "n");

		if (!pTGUNIXAppReadLine (pApp, Username, sizeof (Username)))
		{
			pTGUNIXAppClose (pApp);
			hsMessageBox ("Incomplete or corrupted installation!\nUnable to locate Login dialog",
				"Error", hsMessageBoxNormal);
			return false;
		}

		// Check if user selected 'Cancel'
		if (StrCmp (Username, "text:", 5) != 0)
		{
			pTGUNIXAppClose (pApp);
			return false;
		}
		memmove (Username, Username + 5, StrLen (Username) - 5);
		Username[StrLen (Username) - 5] = '\0';

		char Password[kMaxPasswordLength];
		if (!pTGUNIXAppReadLine (pApp, Password, sizeof (Password)))
		{
			pTGUNIXAppClose (pApp);
			hsMessageBox ("Incomplete or corrupted installation!\nLogin dialog not found or working",
				"Error", hsMessageBoxNormal);
			return false;
		}

		char Remember[16];
		if (!pTGUNIXAppReadLine (pApp, Remember, sizeof (Remember)))
		{
			pTGUNIXAppClose (pApp);
			hsMessageBox ("Incomplete or corrupted installation!\nLogin dialog not found or working",
				"Error", hsMessageBoxNormal);
			return false;
		}

		pTGUNIXAppClose (pApp);

		bRemember = false;
		if (Remember[0] == 'y')
		  bRemember = true;

		SaveUserPass (Username, Password, &NamePassHash, bRemember, fromGT);

		// Do login & see if it failed
		ENetError auth;
		bool cancelled = AuthenticateNetClientComm(&auth, NULL);

		if (IS_NET_SUCCESS (auth) && !cancelled)
			break;

		if (!cancelled)
		  {
				const char *pStr1, *pStr2;
				const wchar *pWStr;
				unsigned int Len;
				char *pTmpStr;

				AuthFailedStrings (auth, fromGT, &pStr1, &pStr2, &pWStr);

				Len = StrLen (pStr1) + 1;
				if (pStr2)
				  Len += StrLen (pStr2) + 2;
				if (pWStr)
				  Len += StrLen (pWStr) + 2;

				pTmpStr = TRACKED_NEW char[Len];
				StrCopy (pTmpStr, pStr1, StrLen (pStr1));
				if (pStr2)
				  {
					 StrCopy (pTmpStr + StrLen (pTmpStr), "\n\n", 2);
					 StrCopy (pTmpStr + StrLen (pTmpStr), pStr2, StrLen (pStr2));
				  }
				if (pWStr)
				  {
					 StrCopy (pTmpStr + StrLen (pTmpStr), "\n\n", 2);
					 StrToAnsi (pTmpStr + StrLen (pTmpStr), pWStr, StrLen (pWStr));
				  }

				hsMessageBox (pTmpStr, "Error", hsMessageBoxNormal);
				delete [] pTmpStr;
		  }
		else
		    NetCommDisconnect();
	};

	return true;
}

bool TGRunTOSDialog ()
{
	char Buf[16];
	LPVOID pApp;

	if (GetFileAttributes (TG_NEW_EULA_PATH) != INVALID_FILE_ATTRIBUTES)
		pApp = pTGLaunchUNIXApp (TG_NEW_EULA_POPEN_PATH, "r");
	else
		pApp = pTGLaunchUNIXApp (TG_OLD_EULA_POPEN_PATH, "r");

	if (!pApp)
	{
		hsMessageBox ("Incomplete or corrupted installation!\nTOS dialog not found or working",
				"Error", hsMessageBoxNormal);
		return false;
	}

	if (!pTGUNIXAppReadLine (pApp, Buf, sizeof (Buf)))
	{
		hsMessageBox ("Incomplete or corrupted installation!\nTOS dialog not found or working",
				"Error", hsMessageBoxNormal);
		pTGUNIXAppClose (pApp);
		return false;
	}

	pTGUNIXAppClose (pApp);

	return (StrCmp (Buf, "accepted") == 0);
}

void GetMouseCoords(HWND hWnd, WPARAM wParam, LPARAM lParam, int* xPos, int* yPos, int* fwKeys)
{
	POINT pt;
	pt.x=LOWORD(lParam);
	pt.y=HIWORD(lParam);
#if 0
	if (ClientToScreen(hWnd, &pt) == false)
		HSDebugProc("Error converting client mouse coords to screen");
#endif

	if (xPos)
		*xPos = pt.x;  // horizontal position of cursor 
	if (yPos)
		*yPos = pt.y;  // vertical position of cursor 
 
#if 0
	char str[128];
	sprintf(str, "mx=%d my=%d\n", pt.x, pt.y);
	hsStatusMessage(str);
#endif

	if (fwKeys)
		*fwKeys = wParam;        // key flags 
 
	// key flag bits
	// MK_CONTROL  Set if the CTRL key is down. 
 	// MK_LBUTTON  Set if the left mouse button is down. 
 	// MK_MBUTTON  Set if the middle mouse button is down. 
 	// MK_RBUTTON  Set if the right mouse button is down. 
 	// MK_SHIFT  Set if the SHIFT key is down. 
}

void DebugMsgF(const char* format, ...);

// Handles all the windows messages we might receive
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{	
	static bool gDragging = false;
	static UInt32 keyState=0;

    // Handle messages
    switch (message) {		
		case WM_KEYDOWN :
		case WM_LBUTTONDOWN	:
		case WM_RBUTTONDOWN :
		case WM_LBUTTONDBLCLK :		// The left mouse button was double-clicked. 
		case WM_MBUTTONDBLCLK :		// The middle mouse button was double-clicked. 
		case WM_MBUTTONDOWN :		// The middle mouse button was pressed. 
		case WM_RBUTTONDBLCLK :		// The right mouse button was double-clicked. 
			// If they did anything but move the mouse, quit any intro movie playing.
			{
				if( gClient )
					gClient->SetQuitIntro(true);
			}
			// Fall through to other events
		case WM_KEYUP :
		case WM_LBUTTONUP :
		case WM_RBUTTONUP :
		case WM_MBUTTONUP :			// The middle mouse button was released. 
		case WM_MOUSEMOVE :
		case 0x020A:				// fuc&ing windows b.s...
			{
				if (gClient && gClient->WindowActive() && gClient->GetInputManager())
				{
					gClient->GetInputManager()->HandleWin32ControlEvent(message, wParam, lParam, hWnd);
				}
			}
			break;

#if 0
		case WM_KILLFOCUS:
			SetForegroundWindow(hWnd);
			break;
#endif

		case WM_SYSKEYUP:
		case WM_SYSKEYDOWN:
			{
				if (gClient && gClient->WindowActive() && gClient->GetInputManager())
				{
					gClient->GetInputManager()->HandleWin32ControlEvent(message, wParam, lParam, hWnd);
				}
				//DefWindowProc(hWnd, message, wParam, lParam);
			}
			break;
			
		case WM_SYSCOMMAND:
            switch (wParam) {
                // Trap ALT so it doesn't pause the app
                case SC_KEYMENU :
                //// disable screensavers and monitor power downs too.
                case SC_SCREENSAVE:
                case SC_MONITORPOWER:
                    return 0;
				case SC_CLOSE :
					// kill game if window is closed
					gClient->SetDone(TRUE);
					if (plNetClientMgr * mgr = plNetClientMgr::GetInstance())
						mgr->QueueDisableNet(false, nil);
					DestroyWindow(gClient->GetWindowHandle());
					break;
            }
			break;

        case WM_ACTIVATE:
			{
				bool active = (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE);
				bool minimized = (HIWORD(wParam) != 0);

				DebugMsgF("Got WM_ACTIVATE, active=%s, minimized=%s, clicked=%s",
					active ? "true" : "false",
					minimized ? "true" : "false",
					(LOWORD(wParam) == WA_CLICKACTIVE) ? "true" : "false");

				if (gClient && !minimized && !gClient->GetDone())
				{
					if (LOWORD(wParam) == WA_CLICKACTIVE)
					{
						// See if they've clicked on the frame, in which case they just want to
						// move, not activate, us.
						POINT pt;
						GetCursorPos(&pt);
						ScreenToClient(hWnd, &pt);

						RECT rect;
						GetClientRect(hWnd, &rect);

						if( (pt.x < rect.left)
							||(pt.x >= rect.right)
							||(pt.y < rect.top)
							||(pt.y >= rect.bottom) )
						{
							active = false;
						}
					}
					gClient->WindowActivate(active);
				}
				else
				{
					gPendingActivate = true;
					gPendingActivateFlag = active;
				}
			}
			break;

		// Let go of the mouse if the window is being moved.
        case WM_ENTERSIZEMOVE:
			DebugMsgF("Got WM_ENTERSIZEMOVE%s", gClient ? "" : ", no client, ignoring");
			gDragging = true;
			if( gClient )
				gClient->WindowActivate(false);
			break;

		// Redo the mouse capture if the window gets moved
        case WM_EXITSIZEMOVE:
			DebugMsgF("Got WM_EXITSIZEMOVE%s", gClient ? "" : ", no client, ignoring");
			gDragging = false;
			if( gClient )
				gClient->WindowActivate(true);
			break;

		// Redo the mouse capture if the window gets moved (special case for Colin
		// and his cool program that bumps windows out from under the taskbar)
		case WM_MOVE:
			if (!gDragging && gClient && gClient->GetInputManager())
			{
				gClient->GetInputManager()->Activate(true);
				DebugMsgF("Got WM_MOVE");
			}
			else
				DebugMsgF("Got WM_MOVE, but ignoring");
			break;

		/// Resize the window
		// (we do WM_SIZING here instead of WM_SIZE because, for some reason, WM_SIZE is
		//  sent to the window when we do fullscreen, and what's more, it gets sent BEFORE
		//  the fullscreen flag is sent. How does *that* happen? Anyway, WM_SIZING acts
		//  just like WM_SIZE, except that it ONLY gets sent when the user changes the window
		//  size, not when the window is minimized or restored)
		case WM_SIZING:
			{
				DebugMsgF("Got WM_SIZING");
				RECT r;
				::GetClientRect(hWnd, &r);
				gClient->GetPipeline()->Resize(r.right - r.left, r.bottom - r.top);
			}
			break;

		case WM_SIZE:
			// Let go of the mouse if the window is being minimized
			if (wParam == SIZE_MINIMIZED)
			{
				DebugMsgF("Got WM_SIZE, SIZE_MINIMIZED%s", gClient ? "" : ", but no client, ignoring");
				if (gClient)
					gClient->WindowActivate(false);
			}
			// Redo the mouse capture if the window gets restored
			else if (wParam == SIZE_RESTORED)
			{
				DebugMsgF("Got WM_SIZE, SIZE_RESTORED%s", gClient ?  "" : ", but no client, ignoring");
				if (gClient)
					gClient->WindowActivate(true);
			}
			break;
		
        case WM_CLOSE:
			gClient->SetDone(TRUE);
			if (plNetClientMgr * mgr = plNetClientMgr::GetInstance())
				mgr->QueueDisableNet(false, nil);
			DestroyWindow(gClient->GetWindowHandle());
			return TRUE;
        case WM_DESTROY:
			gClient->SetDone(TRUE);
			if (plNetClientMgr * mgr = plNetClientMgr::GetInstance())
				mgr->QueueDisableNet(false, nil);
            PostQuitMessage(0);
			return TRUE;
		case WM_CREATE:
			// Create renderer
			break;
    }
   return DefWindowProc(hWnd, message, wParam, lParam);
}
 
void	PumpMessageQueueProc( void )
{
	MSG	msg;

	// Look for a message
	while (PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
	{
		// Handle the message
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}
}

void InitNetClientComm()
{
    NetCommStartup();
}

BOOL CALLBACK AuthDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	static bool* cancelled = NULL;

	switch( uMsg )
	{
	case WM_INITDIALOG:
		cancelled = (bool*)lParam;
		SetTimer(hwndDlg, AUTH_LOGIN_TIMER, 10, NULL);
		return TRUE;

	case WM_TIMER:
		if (wParam == AUTH_LOGIN_TIMER)
		{
			if (NetCommIsLoginComplete())
				EndDialog(hwndDlg, 1);
			else
				NetCommUpdate();

			return TRUE;
		}

		return FALSE;

	case WM_NCHITTEST:
		SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION);
		return TRUE;

	case WM_DESTROY:
		KillTimer(hwndDlg, AUTH_LOGIN_TIMER);
		return TRUE;

	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDCANCEL)
		{
			*cancelled = true;
			EndDialog(hwndDlg, 1);
		}
		return TRUE;
	}
	
	return DefWindowProc(hwndDlg, uMsg, wParam, lParam);
}

bool AuthenticateNetClientComm(ENetError* result, HWND parentWnd)
{
	if (!NetCliAuthQueryConnected())
		NetCommConnect();

	bool cancelled = false;
	NetCommAuthenticate(nil);

	::DialogBoxParam(gHInst, MAKEINTRESOURCE( IDD_AUTHENTICATING ), parentWnd, AuthDialogProc, (LPARAM)&cancelled);

	if (!cancelled)
		*result = NetCommGetAuthResult();
	else
		*result = kNetSuccess;
	
	return cancelled;
}

void DeInitNetClientComm()
{
    NetCommShutdown();
}

BOOL CALLBACK WaitingForPhysXDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	switch( uMsg )
	{
	case WM_INITDIALOG:
		::SetDlgItemText( hwndDlg, IDC_STARTING_TEXT, "Waiting for PhysX install...");
		return true; 

	}
	return 0;
}

bool InitPhysX()
{
	bool physXInstalled = false;
	while (!physXInstalled)
	{
		plSimulationMgr::Init();
		if (!plSimulationMgr::GetInstance())
		{
			int ret = hsMessageBox("PhysX is not installed, or an older version is installed.\nInstall new version? (Game will exit if you click \"No\")",
				"Missing PhysX", hsMessageBoxYesNo);
			if (ret == hsMBoxNo) // exit if no
				return false;

			// launch the PhysX installer
			STARTUPINFOW startupInfo;
			PROCESS_INFORMATION processInfo; 
			ZERO(startupInfo);
			ZERO(processInfo);
			startupInfo.cb = sizeof(startupInfo);
			if(!CreateProcessW(NULL, s_physXSetupExeName, NULL,	NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo))
			{
				hsMessageBox("Failed to launch PhysX installer.\nPlease re-run URU to ensure you have the latest version.", "Error", hsMessageBoxNormal);
				return false;
			}

			// let the user know what's going on
			HWND waitingDialog = ::CreateDialog(gHInst, MAKEINTRESOURCE(IDD_LOADING), NULL, WaitingForPhysXDialogProc);

			// run a loop to wait for it to quit, pumping the windows message queue intermittently
			DWORD waitRet = WaitForSingleObject(processInfo.hProcess, 100);
			MSG msg;
			while (waitRet == WAIT_TIMEOUT)
			{
				if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
				waitRet = WaitForSingleObject(processInfo.hProcess, 100);
			}

			// cleanup
			CloseHandle(processInfo.hThread);
			CloseHandle(processInfo.hProcess);
			::DestroyWindow(waitingDialog);
		}
		else
		{
			plSimulationMgr::GetInstance()->Suspend();
			physXInstalled = true;
		}
	}
	return true;
}

bool	InitClient( HWND hWnd )
{
	plResManager *resMgr = TRACKED_NEW plResManager;
	resMgr->SetDataPath("dat");
	hsgResMgr::Init(resMgr);
	gClient = TRACKED_NEW plClient;
	if( gClient == nil )
		return false;

	if (!InitPhysX())
		return false;

	gClient->SetWindowHandle( hWnd );

#ifdef DETACH_EXE
	hInstance = ((LPCREATESTRUCT) lParam)->hInstance;
#endif
	// If in fullscreen mode, get rid of the window borders.  Note: this won't take
	// effect until the next SetWindowPos call

#ifdef DETACH_EXE

	// This Function loads the EXE into Virtual memory...supposedly
    HRESULT hr = DetachFromMedium(hInstance, DMDFM_ALWAYS | DMDFM_ALLPAGES);
#endif

	if( gClient->InitPipeline() )
		gClient->SetDone(true);
	else
	{
		if( gClient->GetPipeline()->IsFullScreen() )
		{
			SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);
			SetWindowLong(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST);
            SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
			gWinBorderDX = gWinBorderDY = gWinMenuDY = 0;
		}
		else {
            SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPED | WS_CAPTION);
            SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
		}

		int goodWidth = gClient->GetPipeline()->Width() + gWinBorderDX * 2;
		int goodHeight = gClient->GetPipeline()->Height() + gWinBorderDY * 2 + gWinMenuDY;

		SetWindowPos(
			hWnd,
			nil,
			0,
			0,
			goodWidth,
			goodHeight,
			SWP_NOCOPYBITS 
				| SWP_NOMOVE
				| SWP_NOOWNERZORDER
				| SWP_NOZORDER
				| SWP_FRAMECHANGED
		);
	}
	
	if( gPendingActivate )
	{
		// We need this because the window gets a WM_ACTIVATE before we get to this function, so 
		// the above flag lets us know that we need to fake a late activate msg to the client
		gClient->WindowActivate( gPendingActivateFlag );
	}

	gClient->SetMessagePumpProc( PumpMessageQueueProc );

	return true;
}

// Initializes all that windows junk, creates class then shows main window
BOOL WinInit(HINSTANCE hInst, int nCmdShow)
{
    // Fill out WNDCLASS info
	WNDCLASS wndClass;
    wndClass.style              = CS_DBLCLKS;	// CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc        = WndProc;
    wndClass.cbClsExtra         = 0;
    wndClass.cbWndExtra         = 0;
    wndClass.hInstance          = hInst;
    wndClass.hIcon              = LoadIcon(hInst, MAKEINTRESOURCE(IDI_ICON_DIRT));

	wndClass.hCursor            = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground      = (struct HBRUSH__*) (GetStockObject(BLACK_BRUSH));
    wndClass.lpszMenuName       = CLASSNAME;
    wndClass.lpszClassName      = CLASSNAME;
    
	// can only run one at a time anyway, so just quit if another is running
    if (!RegisterClass(&wndClass)) 
		return FALSE;

	/// 8.11.2000 - Test for OpenGL fullscreen, and if so use no border, no caption;
	/// else, use our normal styles

	char windowName[256];
	wchar productString[256];
//#ifdef PLASMA_EXTERNAL_RELEASE	
#if 0	// Show the full product string in external build window title until we roll it into the options dialog -eap
	StrCopy(productString, ProductLongName(), arrsize(productString));
#else
	ProductString(productString, arrsize(productString));
#endif
	StrToAnsi(windowName, productString, arrsize(windowName));
	
    // Create a window
	HWND hWnd = CreateWindow(
		CLASSNAME, windowName, 
		WS_OVERLAPPEDWINDOW,
		0, 0, 
		800 + gWinBorderDX * 2,
        600 + gWinBorderDY * 2 + gWinMenuDY,
		 NULL, NULL, hInst, NULL
	);
//	gClient->SetWindowHandle((hsWindowHndl)

	if( !InitClient( hWnd ) )
		return FALSE;

    // Return false if window creation failed
    if (!gClient->GetWindowHandle())
	{
		OutputDebugString("Create window failed\n");
		return FALSE;
	}
	else
	{
		OutputDebugString("Create window OK\n");
	}
	return TRUE;
}

//
// For error logging
//
static FILE* gDebugFile=NULL;
void DebugMessageProc(const char* msg)
{
	OutputDebugString(msg);
	OutputDebugString("\n");
	if (gDebugFile != NULL)
	{
		fprintf(gDebugFile, "%s\n", msg);
		fflush(gDebugFile);
	}
}

void DebugMsgF(const char* format, ...)
{
#ifndef PLASMA_EXTERNAL_RELEASE
	va_list args;
	va_start(args, format);

	char buf[256];
	int numWritten = _vsnprintf(buf, sizeof(buf), format, args);
	hsAssert(numWritten > 0, "Buffer too small");

	va_end(args);

	DebugMessageProc(buf);
#endif
}

static bool IsMachineLittleEndian() {
   int i = 1;
   char *p = (char *) &i;
   if (p[0] == 1) // Lowest address contains the least significant byte
      return true;
   else
      return false;
}

inline static dword ToBigEndian (dword value) {
	return ((value) << 24) | ((value & 0x0000ff00) << 8) | ((value & 0x00ff0000) >> 8) | ((value) >> 24);
}

static void AuthFailedStrings (ENetError authError, bool fromGT,
										 const char **ppStr1, const char **ppStr2,
										 const wchar **ppWStr)
{
  *ppStr1 = NULL;
  *ppStr2 = NULL;
  *ppWStr = NULL;

	switch (plLocalization::GetLanguage())
	{
		case plLocalization::kFrench:
		case plLocalization::kGerman:
		case plLocalization::kJapanese:
			*ppStr1 = "Authentication Failed. Please try again.";
			break;

		default:
			*ppStr1 = "Authentication Failed. Please try again.";

			switch (authError)
			{
				case kNetErrAccountNotFound:
					*ppStr2 = "Account Not Found.";
					break;
				case kNetErrAccountNotActivated:
					*ppStr2 = "Account Not Activated.";
					break;
				case kNetErrConnectFailed:
					*ppStr2 = "Unable to connect to Myst Online.";
					break;
				case kNetErrDisconnected:
					*ppStr2 = "Disconnected from Myst Online.";
					break;
				case kNetErrAuthenticationFailed:
					if (fromGT)
						*ppStr2 = "GameTap authentication failed, please enter your GameTap username and password.";
					else
						*ppStr2 = "Incorrect password.\n\nMake sure CAPS LOCK is not on.";
					break;
				case kNetErrGTServerError:
				case kNetErrGameTapConnectionFailed:
					*ppStr2 = "Unable to connect to GameTap, please try again in a few minutes.";
					break;
				case kNetErrAccountBanned:
					*ppStr2 = "Your account has been banned from accessing Myst Online.  If you are unsure as to why this happened please contact customer support.";
					break;
				default:
 					*ppWStr =  NetErrorToString (authError);
					break;
			}
			break;
	}
}


BOOL CALLBACK AuthFailedDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	switch( uMsg )
	{
		case WM_INITDIALOG:
			{
				LoginDialogParam* loginParam = (LoginDialogParam*)lParam;
				const char *pStr1, *pStr2;
				const wchar *pWStr;

				AuthFailedStrings (loginParam->authError, loginParam->fromGT,
										 &pStr1, &pStr2, &pWStr);

				if (pStr1)
						::SetDlgItemText( hwndDlg, IDC_AUTH_TEXT, pStr1);
				if (pStr2)
						::SetDlgItemText( hwndDlg, IDC_AUTH_MESSAGE, pStr2);
				if (pWStr)
						::SetDlgItemTextW( hwndDlg, IDC_AUTH_MESSAGE, pWStr);
			}
			return TRUE;

		case WM_COMMAND:
			if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK)
			{
				EndDialog(hwndDlg, 1);
			}
			return TRUE;

		case WM_NCHITTEST:
			SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION);
			return TRUE;

	}
	return FALSE;
}

BOOL CALLBACK UruTOSDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	switch( uMsg )
	{
	case WM_INITDIALOG:
		{
			SetWindowText(hwndDlg, "End User License Agreement");
			SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon((HINSTANCE)lParam, MAKEINTRESOURCE(IDI_ICON_DIRT)));

			hsUNIXStream stream;
			if (stream.Open("TOS.txt", "rt"))
			{
				char* eulaData = NULL;
				unsigned dataLen = stream.GetSizeLeft();

				eulaData = TRACKED_NEW char[dataLen + 1];
				ZeroMemory(eulaData, dataLen + 1);

				stream.Read(dataLen, eulaData);

				SetDlgItemText(hwndDlg, IDC_URULOGIN_EULATEXT, eulaData);
				delete [] eulaData;
			}

			break;
		}
	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL))
		{
			bool ok = (LOWORD(wParam) == IDOK);
			EndDialog(hwndDlg, ok);
			return TRUE;
		}
		break;

	case WM_NCHITTEST:
		SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION);
		return TRUE;
	}
	return FALSE;
}

static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHash, bool remember_password,
						  bool fromGT)
{
	UInt32 cryptKey[4];
	ZeroMemory(cryptKey, sizeof(cryptKey));
	GetCryptKey(cryptKey, arrsize(cryptKey));

	wchar wusername[kMaxAccountNameLength];
	wchar wpassword[kMaxPasswordLength];

	StrToUnicode(wusername, username, arrsize(wusername));

	// if the password field is the fake string then we've already
	// loaded the namePassHash from the file
	if (StrCmp(password, FAKE_PASS_STRING) != 0)
	{
		StrToUnicode(wpassword, password, arrsize(wpassword));

		wchar domain[15];
		PathSplitEmail(wusername, nil, 0, domain, arrsize(domain), nil, 0, nil, 0, 0);

		if (StrLen(domain) == 0 || StrCmpI(domain, L"gametap") == 0) {
			CryptDigest(
				kCryptSha1,
				pNamePassHash,
				StrLen(password) * sizeof(password[0]),
				password
				);

			if (IsMachineLittleEndian()) {
				pNamePassHash->data[0] = ToBigEndian(pNamePassHash->data[0]);
				pNamePassHash->data[1] = ToBigEndian(pNamePassHash->data[1]);
				pNamePassHash->data[2] = ToBigEndian(pNamePassHash->data[2]);
				pNamePassHash->data[3] = ToBigEndian(pNamePassHash->data[3]);
				pNamePassHash->data[4] = ToBigEndian(pNamePassHash->data[4]);
			}
		}
		else
			CryptHashPassword(wusername, wpassword, pNamePassHash);
	}

	NetCommSetAccountUsernamePassword(wusername, *pNamePassHash);
	if (TGIsCider)
		NetCommSetAuthTokenAndOS(nil, L"mac");
	else
		NetCommSetAuthTokenAndOS(nil, L"win");

	if (!fromGT) {
		wchar fileAndPath[MAX_PATH];
		PathGetInitDirectory(fileAndPath, arrsize(fileAndPath));
		PathAddFilename(fileAndPath, fileAndPath, L"login.dat", arrsize(fileAndPath));
#ifndef PLASMA_EXTERNAL_RELEASE
		// internal builds can use the local init directory
		wchar localFileAndPath[MAX_PATH];
		StrCopy(localFileAndPath, L"init\\login.dat", arrsize(localFileAndPath));
		if (PathDoesFileExist(localFileAndPath))
			StrCopy(fileAndPath, localFileAndPath, arrsize(localFileAndPath));
#endif
		hsStream* stream = plEncryptedStream::OpenEncryptedFileWrite(fileAndPath, cryptKey);
		if (stream)
		{
			stream->Write(sizeof(cryptKey), cryptKey);
			stream->WriteSafeString(username);
			stream->Writebool(remember_password);
			if (remember_password)
				stream->Write(sizeof(pNamePassHash->data), pNamePassHash->data);
			stream->Close();
			delete stream;
		}
	}
}


static void LoadUserPass (const wchar *accountName, char *username, ShaDigest *pNamePassHash, bool *pRemember,
						  bool fromGT, int *pFocus)
{
			UInt32 cryptKey[4];
			ZeroMemory(cryptKey, sizeof(cryptKey));
			GetCryptKey(cryptKey, arrsize(cryptKey));

			char* temp;
	*pRemember = false;
	username[0] = '\0';

	if (!fromGT)
			{
				wchar fileAndPath[MAX_PATH];
				PathGetInitDirectory(fileAndPath, arrsize(fileAndPath));
				PathAddFilename(fileAndPath, fileAndPath, L"login.dat", arrsize(fileAndPath));
#ifndef PLASMA_EXTERNAL_RELEASE
				// internal builds can use the local init directory
				wchar localFileAndPath[MAX_PATH];
				StrCopy(localFileAndPath, L"init\\login.dat", arrsize(localFileAndPath));
				if (PathDoesFileExist(localFileAndPath))
					StrCopy(fileAndPath, localFileAndPath, arrsize(localFileAndPath));
#endif
				hsStream* stream = plEncryptedStream::OpenEncryptedFile(fileAndPath, true, cryptKey);
				if (stream && !stream->AtEnd())
				{
					UInt32 savedKey[4];
					stream->Read(sizeof(savedKey), savedKey);

					if (memcmp(cryptKey, savedKey, sizeof(savedKey)) == 0)
					{
						temp = stream->ReadSafeString();

						if (temp)
						{
					StrCopy(username, temp, kMaxAccountNameLength);
							delete temp;
						}
						else
							username[0] = '\0';

				*pRemember = stream->Readbool();

				if (*pRemember)
						{
					stream->Read(sizeof(pNamePassHash->data), pNamePassHash->data);
					*pFocus = IDOK;
						}
						else
					*pFocus = IDC_URULOGIN_PASSWORD;
					}

					stream->Close();
					delete stream;
				}
			}
			else
			{
		StrToAnsi (username, accountName, kMaxAccountNameLength);
		*pFocus = IDC_URULOGIN_PASSWORD;
	}
}

void StatusCallback(void *param)
{
	HWND hwnd = (HWND)param;

	while(s_loginDlgRunning)
	{
		// get status message from webpage and display in status area.
		const wchar *path = BuildTypeServerStatusPath();  
		if(path)
		{
			HINTERNET hSession = 0;
			HINTERNET hConnect = 0;
			HINTERNET hRequest = 0;
			
			hSession = WinHttpOpen(
				L"UruClient/1.0",
				WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
				WINHTTP_NO_PROXY_NAME,
				WINHTTP_NO_PROXY_BYPASS, 0
			);
			if(hSession)
			{
				HINTERNET hConnect = WinHttpConnect( hSession, STATUS_PATH, INTERNET_DEFAULT_HTTP_PORT, 0);
				if(hConnect)
				{
					HINTERNET hRequest = WinHttpOpenRequest( 
						hConnect, 
						L"GET",
						path,
						NULL, 
						WINHTTP_NO_REFERER, 
						WINHTTP_DEFAULT_ACCEPT_TYPES,
						0
					);
					if(hRequest)
					{
						static char data[256] = {0};
						DWORD bytesRead;
						WinHttpSendRequest( 
							hRequest, 
							WINHTTP_NO_ADDITIONAL_HEADERS,
							0,
							WINHTTP_NO_REQUEST_DATA,
							0,
							0,
							0
						);
						WinHttpReceiveResponse(hRequest, 0);
						WinHttpReadData(hRequest, data, 255, &bytesRead);
						data[bytesRead] = 0;
						if(bytesRead)
							PostMessage(hwnd, WM_USER_SETSTATUSMSG, 0, (LPARAM) data);
					}
				}
			}
			WinHttpCloseHandle(hRequest);
			WinHttpCloseHandle(hConnect);
			WinHttpCloseHandle(hSession);
		}
		else 
			break;		// no status message
		
		for(unsigned i = 0; i < UPDATE_STATUSMSG_SECONDS && s_loginDlgRunning; ++i)
		{
			Sleep(1000);
		}
	}
	s_statusEvent.Signal();
}

BOOL CALLBACK UruLoginDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	static ShaDigest namePassHash;
	static LoginDialogParam* loginParam;
	static showAuthFailed = false;

	switch( uMsg )
	{
		case WM_INITDIALOG:
		{
			s_loginDlgRunning = true;
			_beginthread(StatusCallback, 0, hwndDlg);
			loginParam = (LoginDialogParam*)lParam;

			SetWindowText(hwndDlg, "Login");
			SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon(gHInst, MAKEINTRESOURCE(IDI_ICON_DIRT)));

			EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);

			char username[kMaxAccountNameLength];
			bool remember_password = false;

			int focus_control = IDC_URULOGIN_USERNAME;

			LoadUserPass (loginParam->accountName, username, &namePassHash, &remember_password, loginParam->fromGT, &focus_control);

			SetDlgItemText(hwndDlg, IDC_URULOGIN_USERNAME, username);
			CheckDlgButton(hwndDlg, IDC_URULOGIN_REMEMBERPASS, remember_password ? BST_CHECKED : BST_UNCHECKED);
			if (remember_password)
				SetDlgItemText(hwndDlg, IDC_URULOGIN_PASSWORD, FAKE_PASS_STRING);
			if (loginParam->fromGT)
				EnableWindow(GetDlgItem(hwndDlg, IDC_URULOGIN_REMEMBERPASS), FALSE);

			SetFocus(GetDlgItem(hwndDlg, focus_control));

			if (IS_NET_ERROR(loginParam->authError))
			{
				showAuthFailed = true;
			}

			char windowName[256];
			wchar productString[256];
			ProductString(productString, arrsize(productString));
			StrToAnsi(windowName, productString, arrsize(windowName));
			SendMessage(GetDlgItem(hwndDlg, IDC_PRODUCTSTRING), WM_SETTEXT, 0, (LPARAM) windowName);

			SetTimer(hwndDlg, AUTH_LOGIN_TIMER, 10, NULL);
			return FALSE;
		}

		case WM_USER_SETSTATUSMSG:
			 SendMessage(GetDlgItem(hwndDlg, IDC_STATUS_TEXT), WM_SETTEXT, 0, lParam);
			 return TRUE;

		case WM_DESTROY:
		{
			s_loginDlgRunning = false;
			s_statusEvent.Wait(kEventWaitForever);
			KillTimer(hwndDlg, AUTH_LOGIN_TIMER);
			return TRUE;
		}
	
		case WM_NCHITTEST:
		{
			SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, (LONG_PTR)HTCAPTION);
			return TRUE;
		}
	
		case WM_PAINT:
		{
			if (showAuthFailed)
			{
				SetTimer(hwndDlg, AUTH_FAILED_TIMER, 10, NULL);
				showAuthFailed = false;
			}
			return FALSE;
		}
	
		case WM_COMMAND:
		{
			if (HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL))
			{
				bool ok = (LOWORD(wParam) == IDOK);
				if (ok)
				{
					char username[kMaxAccountNameLength];
					char password[kMaxPasswordLength];
					bool remember_password = false;

					GetDlgItemText(hwndDlg, IDC_URULOGIN_USERNAME, username, kMaxAccountNameLength);
					GetDlgItemText(hwndDlg, IDC_URULOGIN_PASSWORD, password, kMaxPasswordLength);
					remember_password = (IsDlgButtonChecked(hwndDlg, IDC_URULOGIN_REMEMBERPASS) == BST_CHECKED);

					SaveUserPass (username, password, &namePassHash, remember_password, loginParam->fromGT);

					LoginDialogParam loginParam;
					MemSet(&loginParam, 0, sizeof(loginParam));
					bool cancelled = AuthenticateNetClientComm(&loginParam.authError, hwndDlg);

					if (IS_NET_SUCCESS(loginParam.authError) && !cancelled)
						EndDialog(hwndDlg, ok);
					else {
						if (!cancelled)
							::DialogBoxParam(gHInst, MAKEINTRESOURCE( IDD_AUTHFAILED ), hwndDlg, AuthFailedDialogProc, (LPARAM)&loginParam);
						else
						{
							NetCommDisconnect();
						}
					}
				}
				else
					EndDialog(hwndDlg, ok);

				return TRUE;
			}
			else if (HIWORD(wParam) == EN_CHANGE && LOWORD(wParam) == IDC_URULOGIN_USERNAME)
			{
				char username[kMaxAccountNameLength];
				GetDlgItemText(hwndDlg, IDC_URULOGIN_USERNAME, username, kMaxAccountNameLength);

				if (StrLen(username) == 0)
					EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE);
				else
					EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE);

				return TRUE;
			}
			else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_URULOGIN_GAMETAPLINK)
			{
				ShellExecute(NULL, "open", "http://www.mystonline.com/signup.html", NULL, NULL, SW_SHOWNORMAL);

				return TRUE;
			}
			break;
		}
	
		case WM_TIMER:
		{
			switch(wParam)
			{
			case AUTH_FAILED_TIMER:
				KillTimer(hwndDlg, AUTH_FAILED_TIMER);
				::DialogBoxParam(gHInst, MAKEINTRESOURCE( IDD_AUTHFAILED ), hwndDlg, AuthFailedDialogProc, (LPARAM)loginParam);
				return TRUE;

			case AUTH_LOGIN_TIMER:
				NetCommUpdate();
				return TRUE;
			}
			return FALSE;
		}
	}
	return FALSE;
}

BOOL CALLBACK SplashDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	switch( uMsg )
	{
		case WM_INITDIALOG:
			switch (plLocalization::GetLanguage())
			{
				case plLocalization::kFrench:
					::SetDlgItemText( hwndDlg, IDC_STARTING_TEXT, "D�marrage d'URU. Veuillez patienter...");
					break;
				case plLocalization::kGerman:
					::SetDlgItemText( hwndDlg, IDC_STARTING_TEXT, "Starte URU, bitte warten ...");
					break;
/*				case plLocalization::kSpanish:
					::SetDlgItemText( hwndDlg, IDC_STARTING_TEXT, "Iniciando URU, por favor espera...");
					break;
				case plLocalization::kItalian:
					::SetDlgItemText( hwndDlg, IDC_STARTING_TEXT, "Avvio di URU, attendere...");
					break;
*/				// default is English
				case plLocalization::kJapanese:
					::SetDlgItemText( hwndDlg, IDC_STARTING_TEXT, "...");
					break;
				default:
					::SetDlgItemText( hwndDlg, IDC_STARTING_TEXT, "Starting URU. Please wait...");
					break;
			}
			return true; 

	}
	return 0;
}

static char	sStackTraceMsg[ 10240 ] = "";
void printStackTrace( char* buffer, int bufferSize, unsigned long stackPtr = 0, unsigned long opPtr = 0 );
//void StackTraceFromContext( HANDLE hThread, CONTEXT *context, char *outputBuffer );

BOOL CALLBACK ExceptionDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	static char	*sLastMsg = nil;

	switch( uMsg )
	{
		case WM_INITDIALOG:
			sLastMsg = (char *)lParam;
			::SetDlgItemText( hwndDlg, IDC_CRASHINFO, sLastMsg );
			return true;

		case WM_COMMAND:
			if( wParam == IDC_COPY && sLastMsg != nil )
			{
				HGLOBAL	copyText = GlobalAlloc( GMEM_DDESHARE, sizeof( TCHAR ) * ( strlen( sLastMsg ) + 1 ) );
				if( copyText != nil )
				{
					LPTSTR	copyPtr = (LPTSTR)GlobalLock( copyText );
					memcpy( copyPtr, sLastMsg, ( strlen( sLastMsg ) + 1 ) * sizeof( TCHAR ) );
					GlobalUnlock( copyText );

					::OpenClipboard( hwndDlg );
					::EmptyClipboard();
					::SetClipboardData( CF_TEXT, copyText );
					::CloseClipboard();
				}
				return true;
			}
			else if( wParam == IDOK )
				EndDialog( hwndDlg, IDOK );
			else
				break;
	}
	return 0;
}


LONG WINAPI plCustomUnhandledExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo )
{
	const char *type = nil;
	switch( ExceptionInfo->ExceptionRecord->ExceptionCode )
	{
		case EXCEPTION_ACCESS_VIOLATION:			type = "Access violation"; break;
		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:		type = "Array bounds exceeded"; break;
		case EXCEPTION_BREAKPOINT:					type = "Breakpoint"; break;
		case EXCEPTION_DATATYPE_MISALIGNMENT:		type = "Datatype misalignment"; break;
		case EXCEPTION_FLT_DENORMAL_OPERAND:		type = "Floating operand denormal"; break;
		case EXCEPTION_FLT_DIVIDE_BY_ZERO:			type = "Floating-point divide-by-zero"; break;
		case EXCEPTION_FLT_INEXACT_RESULT:			type = "Floating-point inexact result"; break;
		case EXCEPTION_FLT_INVALID_OPERATION:		type = "Floating-point invalid operation"; break;
		case EXCEPTION_FLT_OVERFLOW:				type = "Floating-point overflow"; break;
		case EXCEPTION_FLT_STACK_CHECK:				type = "Floating-point stack error"; break;
		case EXCEPTION_FLT_UNDERFLOW:				type = "Floating-point underflow"; break;
		case EXCEPTION_ILLEGAL_INSTRUCTION:			type = "Illegal instruction"; break;
		case EXCEPTION_IN_PAGE_ERROR:				type = "Exception in page"; break;
		case EXCEPTION_INT_DIVIDE_BY_ZERO:			type = "Integer divide-by-zero"; break;
		case EXCEPTION_INT_OVERFLOW:				type = "Integer overflow"; break;
		case EXCEPTION_INVALID_DISPOSITION:			type = "Invalid disposition"; break;
		case EXCEPTION_NONCONTINUABLE_EXCEPTION:	type = "Noncontinuable exception"; break;
		case EXCEPTION_PRIV_INSTRUCTION:			type = "Private instruction"; break;
		case EXCEPTION_SINGLE_STEP:					type = "Single-step"; break;
		case EXCEPTION_STACK_OVERFLOW:				type = "Stack overflow"; break;
	}

	char prodName[256];
	wchar productString[256];
	ProductString(productString, arrsize(productString));
	StrToAnsi(prodName, productString, arrsize(prodName));

	sprintf( sStackTraceMsg, "%s\r\nException type: %s\r\n", prodName, ( type != nil ) ? type : "(unknown)" );

	printStackTrace( sStackTraceMsg, sizeof( sStackTraceMsg ), ExceptionInfo->ContextRecord->Ebp, (unsigned long)ExceptionInfo->ExceptionRecord->ExceptionAddress );

	/// Print the info out to a log file as well
	hsUNIXStream	log;
	wchar fileAndPath[MAX_PATH];
	PathGetLogDirectory(fileAndPath, arrsize(fileAndPath));
	PathAddFilename(fileAndPath, fileAndPath, L"stackDump.log", arrsize(fileAndPath));
	if( log.Open( fileAndPath, L"wt" ) )
	{
		log.WriteString( sStackTraceMsg );
		log.Close();
	}

	/// Hopefully we can access this resource, even given the exception (i.e. very-bad-error) we just experienced
	if(TGIsCider || (::DialogBoxParam( gHInst, MAKEINTRESOURCE( IDD_EXCEPTION ), ( gClient != nil ) ? gClient->GetWindowHandle() : nil,
							ExceptionDialogProc, (LPARAM)sStackTraceMsg ) == -1) )
	{
		// The dialog failed, so just fallback to a standard message box
		hsMessageBox( sStackTraceMsg, "UruExplorer Exception", hsMessageBoxNormal );
	}
	return EXCEPTION_EXECUTE_HANDLER;
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
	// Set global handle
	gHInst = hInst;

	CCmdParser cmdParser(s_cmdLineArgs, arrsize(s_cmdLineArgs));
	cmdParser.Parse();

	bool doIntroDialogs = true;
#ifndef PLASMA_EXTERNAL_RELEASE
	if(cmdParser.IsSpecified(kArgSkipLoginDialog))
		doIntroDialogs = false;
	if(cmdParser.IsSpecified(kArgLocalData))
		gDataServerLocal = true;
	if(cmdParser.IsSpecified(kArgBackgroundDownloader))
		gUseBackgroundDownloader = true;
#endif
	if(cmdParser.IsSpecified(kArgAuthSrv))
	{
		SetAuthSrvHostname(cmdParser.GetString(kArgAuthSrv));
	}

	if(cmdParser.IsSpecified(kArgFileSrv))
	{
		SetFileSrvHostname(cmdParser.GetString(kArgFileSrv));
	}

	if(cmdParser.IsSpecified(kArgGateKeeperSrv))
	{
		SetGateKeeperSrvHostname(cmdParser.GetString(kArgGateKeeperSrv));
	}

	// check to see if we were launched from the patcher
	bool eventExists = false;
	// we check to see if the event exists that the patcher should have created
	HANDLE hPatcherEvent = CreateEventW(nil, TRUE, FALSE, L"UruPatcherEvent");
	if (hPatcherEvent != NULL)
	{
		// successfully created it, check to see if it was already created
		if (GetLastError() == ERROR_ALREADY_EXISTS)
		{
			// it already existed, so the patcher is waiting, signal it so the patcher can die
			SetEvent(hPatcherEvent);
			eventExists = true;
		}
	}

	TGDoCiderDetection ();

#ifdef PLASMA_EXTERNAL_RELEASE
	// if the client was started directly, run the patcher, and shutdown
	STARTUPINFOW si;
	PROCESS_INFORMATION pi; 
	ZERO(si);
	ZERO(pi);
	si.cb = sizeof(si);
	wchar cmdLine[MAX_PATH];
	const wchar ** addrs;
	unsigned count;
	
	if (!eventExists) // if it is missing, assume patcher wasn't launched
	{
		StrCopy(cmdLine, s_patcherExeName, arrsize(cmdLine));
		count = GetAuthSrvHostnames(&addrs);
		if(count && AuthSrvHostnameOverride())
			StrPrintf(cmdLine, arrsize(cmdLine), L"%ws /AuthSrv=%ws", cmdLine, addrs[0]);

		count = GetFileSrvHostnames(&addrs);
		if(count && FileSrvHostnameOverride())
			StrPrintf(cmdLine, arrsize(cmdLine), L"%ws /FileSrv=%ws", cmdLine, addrs[0]);

		count = GetGateKeeperSrvHostnames(&addrs);
		if(count && GateKeeperSrvHostnameOverride())
			StrPrintf(cmdLine, arrsize(cmdLine), L"%ws /GateKeeperSrv=%ws", cmdLine, addrs[0]);

		if(!CreateProcessW(NULL, cmdLine, NULL,	NULL, FALSE, 0, NULL, NULL, &si, &pi))
		{
			hsMessageBox("Failed to launch patcher", "Error", hsMessageBoxNormal);
		}
		CloseHandle( pi.hThread );
		CloseHandle( pi.hProcess );
		return PARABLE_NORMAL_EXIT;
	}
#endif

	plLocalization::SetDefaultLanguage();
	// If another instance is running, exit.  We'll automatically release our
	// lock on the mutex when our process exits
	HANDLE hOneInstance = CreateMutex(nil, FALSE, "UruExplorer");
	if (WaitForSingleObject(hOneInstance,0) != WAIT_OBJECT_0)
	{
		switch (plLocalization::GetLanguage())
		{
			case plLocalization::kFrench:
				hsMessageBox("Une autre copie d'URU est d�j� en cours d'ex�cution", "Erreur", hsMessageBoxNormal);
				break;
			case plLocalization::kGerman:
				hsMessageBox("URU wird bereits in einer anderen Instanz ausgef�hrt", "Fehler", hsMessageBoxNormal);
				break;
/*			case plLocalization::kSpanish:
				hsMessageBox("En estos momentos se est� ejecutando otra copia de URU", "Error", hsMessageBoxNormal);
				break;
			case plLocalization::kItalian:
				hsMessageBox("Un'altra copia di URU � gi� aperta", "Errore", hsMessageBoxNormal);
				break;
*/			// default is English
			default:
				hsMessageBox("Another copy of URU is already running", "Error", hsMessageBoxNormal);
				break;
		}
		return PARABLE_NORMAL_EXIT;
	}

	if (IsExpired())
	{
		hsMessageBox("This client is over 30 days old.  You need to get a new one.", "Error", hsMessageBoxNormal);
		return PARABLE_NORMAL_EXIT;
	}

	NetCliAuthAutoReconnectEnable(false);

	NetCommSetReadIniAccountInfo(!doIntroDialogs);
	InitNetClientComm();

	wchar		acctName[kMaxAccountNameLength];

	// if we're being launched from gametap then don't use the intro dialogs
	if (StrStrI(lpCmdLine, "screenname=")) {
		doIntroDialogs = false;

		wchar		authToken[kMaxPublisherAuthKeyLength];
		wchar		os[kMaxGTOSIdLength];
		ShaDigest	emptyDigest;

		MemSet(acctName, 0, sizeof(acctName));
		MemSet(authToken, 0, sizeof(authToken));
		MemSet(os, 0, sizeof(os));

		const char* temp = lpCmdLine;
		char token[128];
		while (StrTokenize(&temp, token, arrsize(token), " =")) {
			if (StrCmpI(token, "screenname") == 0) {
				if (!StrTokenize(&temp, token, arrsize(token), " ="))
					break;

				StrToUnicode(acctName, token, arrsize(acctName));
			}
			else if (StrCmpI(token, "authtoken") == 0) {
				if (!StrTokenize(&temp, token, arrsize(token), " ="))
					break;

				StrToUnicode(authToken, token, arrsize(authToken));
			}
			else if (StrCmpI(token, "os") == 0) {
				if (!StrTokenize(&temp, token, arrsize(token), " ="))
					break;

				StrToUnicode(os, token, arrsize(os));
			}
		}

		NetCommSetAccountUsernamePassword(acctName, emptyDigest);
		NetCommSetAuthTokenAndOS(authToken, os);
	}

	bool				needExit = false;
	LoginDialogParam	loginParam;
	MemSet(&loginParam, 0, sizeof(loginParam));

	if (!doIntroDialogs) {
		ENetError auth;

		bool cancelled = AuthenticateNetClientComm(&auth, NULL);

		if (IS_NET_ERROR(auth) || cancelled) {
			doIntroDialogs = true;

			loginParam.fromGT = true;
			loginParam.authError = auth;
			StrCopy(loginParam.accountName, acctName, arrsize(loginParam.accountName));

			if (cancelled)
			{
				NetCommDisconnect();
			}
		}
	}

	if (doIntroDialogs) {
		if (TGIsCider)
			needExit = !TGRunLoginDialog (loginParam.accountName, loginParam.fromGT);
		else if (::DialogBoxParam( hInst, MAKEINTRESOURCE( IDD_URULOGIN_MAIN ), NULL, UruLoginDialogProc, (LPARAM)&loginParam ) <= 0)
			needExit = true;
	}

	if (doIntroDialogs && !needExit) {
		if (TGIsCider)
			needExit = !TGRunTOSDialog ();
		else
		{
		HINSTANCE hRichEdDll = LoadLibrary("RICHED20.DLL");
		INT_PTR val = ::DialogBoxParam( hInst, MAKEINTRESOURCE( IDD_URULOGIN_EULA ), NULL, UruTOSDialogProc, (LPARAM)hInst);
		FreeLibrary(hRichEdDll);
		if (val <= 0) {
			DWORD error = GetLastError();
			needExit = true;
		}
	}
	}

	if (needExit) {
		DeInitNetClientComm();
		return PARABLE_NORMAL_EXIT;
	}

	NetCliAuthAutoReconnectEnable(true);

	// VERY VERY FIRST--throw up our splash screen
	HWND splashDialog = ::CreateDialog( hInst, MAKEINTRESOURCE( IDD_LOADING ), NULL, SplashDialogProc );

	// Install our unhandled exception filter for trapping all those nasty crashes in release build
#ifndef HS_DEBUGGING
	LPTOP_LEVEL_EXCEPTION_FILTER oldFilter;
	oldFilter = SetUnhandledExceptionFilter( plCustomUnhandledExceptionFilter );
#endif

	//
	// Set up to log errors by using hsDebugMessage
	//
	gDebugFile = NULL;
	if ( !plStatusLog::fLoggingOff )
	{
		wchar fileAndPath[MAX_PATH];
		PathGetLogDirectory(fileAndPath, arrsize(fileAndPath));
		PathAddFilename(fileAndPath, fileAndPath, L"plasmalog.txt", arrsize(fileAndPath));
		gDebugFile = _wfopen(fileAndPath, L"wt");
		hsAssert(gDebugFile != NULL, "Error creating debug file plasmalog.txt");
		hsSetDebugMessageProc(DebugMessageProc);
		if (gDebugFile != NULL)
		{
			char prdName[256];
			wchar prdString[256];
			ProductString(prdString, arrsize(prdString));
			StrToAnsi(prdName, prdString, arrsize(prdName));
			fprintf(gDebugFile, "%s\n", prdName);
			fflush(gDebugFile);
		}
	}

	// log stackdump.log text if the log exists
	char stackDumpText[1024];
	wchar stackDumpTextW[1024];	
	memset(stackDumpText, 0, arrsize(stackDumpText));
	memset(stackDumpTextW, 0, arrsize(stackDumpTextW) * sizeof(wchar));
	wchar fileAndPath[MAX_PATH];
	PathGetLogDirectory(fileAndPath, arrsize(fileAndPath));
	PathAddFilename(fileAndPath, fileAndPath, L"stackDump.log", arrsize(fileAndPath));
 	FILE *stackDumpLog = _wfopen(fileAndPath, L"r");
	if(stackDumpLog)
	{
		fread(stackDumpText, 1, arrsize(stackDumpText) - 1, stackDumpLog);
		StrToUnicode(stackDumpTextW, stackDumpText, arrsize(stackDumpText));
		NetCliAuthLogStackDump (stackDumpTextW);
		fclose(stackDumpLog);
		plFileUtils::RemoveFile(fileAndPath);
	}

	for (;;) {
		// Create Window
		if (!WinInit(hInst, nCmdShow) || gClient->GetDone())
			break;

		// We don't have multiplayer localized assets for Italian or Spanish, so force them to English in that case.
	/*	if (!plNetClientMgr::GetInstance()->InOfflineMode() &&
			(plLocalization::GetLanguage() == plLocalization::kItalian || 
			plLocalization::GetLanguage() == plLocalization::kSpanish))
		{
			plLocalization::SetLanguage(plLocalization::kEnglish);
		}
	*/

		// Done with our splash now
		::DestroyWindow( splashDialog );

		if (!gClient)
			break;

		// Show the main window
		ShowWindow(gClient->GetWindowHandle(), SW_SHOW);

		gHasMouse = GetSystemMetrics(SM_MOUSEPRESENT);
			
		// Be really REALLY forceful about being in the front
		BringWindowToTop( gClient->GetWindowHandle() );

		// Update the window
		UpdateWindow(gClient->GetWindowHandle());

		// 
		// Init Application here
		//
		if( !gClient->StartInit() )
			break;
		
		// I want it on top! I mean it!
		BringWindowToTop( gClient->GetWindowHandle() );

		// initialize dinput here:
		if (gClient && gClient->GetInputManager())
			gClient->GetInputManager()->InitDInput(hInst, (HWND)gClient->GetWindowHandle());
		
		// Seriously!
		BringWindowToTop( gClient->GetWindowHandle() );
		
		//
		// Main loop
		//
		MSG msg;
		do
		{	
			gClient->MainLoop();

			if( gClient->GetDone() )
				break;

			// Look for a message
			while (PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ))
			{
				// Handle the message
				TranslateMessage( &msg );
				DispatchMessage( &msg );
			}
		} while (WM_QUIT != msg.message);

		break;
	}

#ifndef _DEBUG
	try
	{
#endif
		// 
		// Cleanup
		//
		if (gClient)
		{
			gClient->Shutdown(); // shuts down PhysX for us
			gClient = nil;
		}
		hsAssert(hsgResMgr::ResMgr()->RefCnt()==1, "resMgr has too many refs, expect mem leaks");
		hsgResMgr::Shutdown();	// deletes fResMgr
		DeInitNetClientComm();
#ifndef _DEBUG
	} catch (...)
	{
		// just catch all the crashes on exit... just to keep GameTap from complaining
		if (gDebugFile)
			fprintf(gDebugFile, "Crashed on shutdown.\n");
	}
#endif

	if (gDebugFile)
		fclose(gDebugFile);

	// Uninstall our unhandled exception filter, if we installed one
#ifndef HS_DEBUGGING
	SetUnhandledExceptionFilter( oldFilter );
#endif

    // Exit WinMain and terminate the app....
//    return msg.wParam;
	return PARABLE_NORMAL_EXIT;
}

bool IsExpired()
{
	bool expired = false;

#ifndef PLASMA_EXTERNAL_RELEASE
	char ourPath[MAX_PATH];
	GetModuleFileName(NULL, ourPath, sizeof(ourPath));
	DWORD ok = 0;
	DWORD size = GetFileVersionInfoSize(ourPath, &ok);
	if (size > 0)
	{
		void* data = TRACKED_NEW UInt8[size];
		GetFileVersionInfo(ourPath, ok, size, data);

		unsigned int descLen = 0;
		void* desc = nil;
		if (VerQueryValue(data, "\\StringFileInfo\\040904B0\\FileDescription", &desc, &descLen))
		{
			char* buildDateStart = strstr((const char*)desc, " - Built ");
			if (buildDateStart)
			{
				buildDateStart += strlen(" - Built ");
				char* buildDateEnd = strstr(buildDateStart, " at");
				if (buildDateEnd)
				{
					int len = buildDateEnd-buildDateStart;

					char buf[32];
					strncpy(buf, buildDateStart, len);
					buf[len] = '\0';

					int month = atoi(strtok(buf, "/"));
					int day = atoi(strtok(nil, "/"));
					int year = atoi(strtok(nil, "/"));

					SYSTEMTIME curTime, buildTime;
					GetLocalTime(&buildTime);
					GetLocalTime(&curTime);
					buildTime.wDay = day;
					buildTime.wMonth = month;
					buildTime.wYear = year;

					ULARGE_INTEGER iCurTime, iBuildTime;
					FILETIME ft;

					SystemTimeToFileTime(&curTime, &ft);
					iCurTime.LowPart = ft.dwLowDateTime;
					iCurTime.HighPart = ft.dwHighDateTime;

					SystemTimeToFileTime(&buildTime, &ft);
					iBuildTime.LowPart = ft.dwLowDateTime;
					iBuildTime.HighPart = ft.dwHighDateTime;

					int secsOld = (int)((iCurTime.QuadPart - iBuildTime.QuadPart) / 10000000);
					int daysOld = secsOld / (60 * 60 * 24);

					if (daysOld > 30)
						expired = true;
				}
			}
		}

		delete [] data;
	}
#endif

	return expired;
}

void GetCryptKey(UInt32* cryptKey, unsigned numElements)
{
	char volName[] = "C:\\";
	int index = 0;
	DWORD logicalDrives = GetLogicalDrives();

	for (int i = 0; i < 32; ++i)
	{
		if (logicalDrives & (1 << i))
		{
			volName[0] = ('C' + i);

			DWORD volSerialNum = 0;
			BOOL result = GetVolumeInformation(
				volName,		//LPCTSTR lpRootPathName,
				NULL,			//LPTSTR lpVolumeNameBuffer,
				0,				//DWORD nVolumeNameSize,
				&volSerialNum,	//LPDWORD lpVolumeSerialNumber,
				NULL,			//LPDWORD lpMaximumComponentLength,
				NULL,			//LPDWORD lpFileSystemFlags,
				NULL,			//LPTSTR lpFileSystemNameBuffer,
				0				//DWORD nFileSystemNameSize
			);

			cryptKey[index] = (cryptKey[index] ^ volSerialNum);

			index = (++index) % numElements;
		}
	}
}