You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2011 lines
67 KiB

/*==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 <curl/curl.h>
#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>
//
// 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,
kArgServerIni,
kArgLocalData,
kArgBackgroundDownloader,
};
static const CmdArgDef s_cmdLineArgs[] = {
{ kCmdArgFlagged | kCmdTypeBool, L"ToDni", kArgToDni },
{ kCmdArgFlagged | kCmdTypeBool, L"SkipLoginDialog", kArgSkipLoginDialog },
{ kCmdArgFlagged | kCmdTypeString, L"ServerIni", kArgServerIni },
{ 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];
StrCopy(productString, ProductLongName(), arrsize(productString));
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;
}
}
static size_t CurlCallback(void *buffer, size_t size, size_t nmemb, void *param)
{
static char status[256];
HWND hwnd = (HWND)param;
strncpy(status, (const char *)buffer, std::min<size_t>(size * nmemb, 256));
status[255] = 0;
PostMessage(hwnd, WM_USER_SETSTATUSMSG, 0, (LPARAM) status);
return size * nmemb;
}
void StatusCallback(void *param)
{
HWND hwnd = (HWND)param;
char *statusUrl = hsWStringToString(GetServerStatusUrl());
CURL *hCurl = curl_easy_init();
// For reporting errors
char curlError[CURL_ERROR_SIZE];
curl_easy_setopt(hCurl, CURLOPT_ERRORBUFFER, curlError);
while(s_loginDlgRunning)
{
curl_easy_setopt(hCurl, CURLOPT_URL, statusUrl);
curl_easy_setopt(hCurl, CURLOPT_USERAGENT, "UruClient/1.0");
curl_easy_setopt(hCurl, CURLOPT_WRITEFUNCTION, &CurlCallback);
curl_easy_setopt(hCurl, CURLOPT_WRITEDATA, param);
if (curl_easy_perform(hCurl) != 0)
PostMessage(hwnd, WM_USER_SETSTATUSMSG, 0, (LPARAM) curlError);
for(unsigned i = 0; i < UPDATE_STATUSMSG_SECONDS && s_loginDlgRunning; ++i)
{
Sleep(1000);
}
}
delete [] statusUrl;
curl_easy_cleanup(hCurl);
s_statusEvent.Signal();
}
BOOL CALLBACK UruLoginDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
static ShaDigest namePassHash;
static LoginDialogParam* loginParam;
static bool 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)
{
const wchar *signupurl = GetServerSignupUrl();
ShellExecuteW(NULL, L"open", signupurl, 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<EFBFBD>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;
}
#include "pfConsoleCore/pfConsoleEngine.h"
PF_CONSOLE_LINK_ALL()
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
PF_CONSOLE_INIT_ALL()
// 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
const wchar *serverIni = L"server.ini";
if (cmdParser.IsSpecified(kArgServerIni))
serverIni = cmdParser.GetString(kArgServerIni);
// 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;
if (!eventExists) // if it is missing, assume patcher wasn't launched
{
StrCopy(cmdLine, s_patcherExeName, arrsize(cmdLine));
GetAuthSrvHostnames(&addrs);
if(wcslen(addrs[0]))
StrPrintf(cmdLine, arrsize(cmdLine), L"%ws /AuthSrv=%ws", cmdLine, addrs[0]);
GetFileSrvHostnames(&addrs);
if(wcslen(addrs[0]))
StrPrintf(cmdLine, arrsize(cmdLine), L"%ws /FileSrv=%ws", cmdLine, addrs[0]);
GetGateKeeperSrvHostnames(&addrs);
if(wcslen(addrs[0]))
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<EFBFBD>j<EFBFBD> en cours d'ex<EFBFBD>cution", "Erreur", hsMessageBoxNormal);
break;
case plLocalization::kGerman:
hsMessageBox("URU wird bereits in einer anderen Instanz ausgef<EFBFBD>hrt", "Fehler", hsMessageBoxNormal);
break;
/* case plLocalization::kSpanish:
hsMessageBox("En estos momentos se est<EFBFBD> ejecutando otra copia de URU", "Error", hsMessageBoxNormal);
break;
case plLocalization::kItalian:
hsMessageBox("Un'altra copia di URU <EFBFBD> gi<EFBFBD> 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;
}
FILE *serverini = _wfopen(serverIni, L"rb");
if (serverini)
{
fclose(serverini);
pfConsoleEngine tempConsole;
tempConsole.ExecuteFile(serverIni);
}
else
{
hsMessageBox("No server.ini file found. Please check your URU installation.", "Error", hsMessageBoxNormal);
return PARABLE_NORMAL_EXIT;
}
NetCliAuthAutoReconnectEnable(false);
NetCommSetReadIniAccountInfo(!doIntroDialogs);
InitNetClientComm();
curl_global_init(CURL_GLOBAL_ALL);
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
{
14 years ago
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;
}
}
}
curl_global_cleanup();
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))
{
const char* buildDateStart = strstr((const char*)desc, " - Built ");
if (buildDateStart)
{
buildDateStart += strlen(" - Built ");
const 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;
}
}
}