/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. 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 #include // windows directory handling fxns (for chdir) #include //#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 "../plClientResMgr/plClientResMgr.h" #include "../plNetClient/plNetClientMgr.h" #include "../plNetClient/plNetLinkingMgr.h" #include "../plInputCore/plInputManager.h" #include "../plInputCore/plInputDevice.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 #include "WinHttp.h" #include "loginfix.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"account.mystonline.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]; }; // List of hash styles we are going to test against the server // ... this method of trying multiple hashes against the server has risks of being more compromised than just testing one hash. // ... So, if you know your unique client is only going to connect to your server then it would be wise to limit the testing to one hash. // ... Which can be done simply by setting FIRST_PASSWORD_HASH and LAST_PASSWORD_HASH to the hash that you use. enum { kPasswordHashSHA0, kPasswordHashSHA1 }; static const int FIRST_PASSWORD_HASH = kPasswordHashSHA1; static const int LAST_PASSWORD_HASH = kPasswordHashSHA0; bool AuthenticateNetClientComm(ENetError* result, HWND parentWnd); bool IsExpired(); bool GetDisksProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc); void GetOldCryptKey(UInt32* cryptKey, unsigned size); void GetCryptKey(UInt32* cryptKey, unsigned size); static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHash, bool remember_password, int whichHash); 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; // cycle through the hash types until we find one that matches or errors out ENetError auth; bool cancelled; for (int whichHash=FIRST_PASSWORD_HASH; whichHash >= LAST_PASSWORD_HASH; whichHash-- ) { SaveUserPass (Username, Password, &NamePassHash, bRemember, whichHash); // Do login & see if it failed cancelled = AuthenticateNetClientComm(&auth, NULL); // if the password was successful then go to the end processing if (IS_NET_SUCCESS(auth) && !cancelled) break; // if it was cancelled or any error other than wrong password then go to end processing if (cancelled || auth != kNetErrAuthenticationFailed) break; } 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 UInt8 mouse_down = 0; // Handle messages switch (message) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_MBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_RBUTTONDBLCLK: // Ensure we don't leave the client area during clicks if (!(mouse_down++)) SetCapture(hWnd); // fall through to old case case WM_KEYDOWN: // If they did anything but move the mouse, quit any intro movie playing. if (gClient) { gClient->SetQuitIntro(true); // normal input processing if (gClient->WindowActive() && gClient->GetInputManager()) gClient->GetInputManager()->HandleWin32ControlEvent(message, wParam, lParam, hWnd); } break; case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: // Stop hogging the cursor if (!(--mouse_down)) ReleaseCapture(); // fall through to input processing case WM_MOUSEWHEEL: case WM_KEYUP: if (gClient && gClient->WindowActive() && gClient->GetInputManager()) gClient->GetInputManager()->HandleWin32ControlEvent(message, wParam, lParam, hWnd); break; case WM_MOUSEMOVE: { if (gClient && 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_SETCURSOR: { static bool winCursor = true; if (LOWORD(lParam) == HTCLIENT) { if (winCursor) { winCursor = false; ShowCursor(FALSE); plMouseDevice::ShowCursor(); } } else { if (!winCursor) { winCursor = true; ShowCursor(TRUE); plMouseDevice::HideCursor(); } } return TRUE; } 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()) 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); if(!plFileUtils::FileExists("resource.dat")) { hsMessageBox("Required file 'resource.dat' not found.", "Error", hsMessageBoxNormal); return false; } plClientResMgr::Instance().ILoadResources("resource.dat"); gClient = TRACKED_NEW plClient; if( gClient == nil ) return false; if (!InitPhysX()) return false; gClient->SetWindowHandle( hWnd ); #ifdef DETACH_EXE hInstance = ((LPCREATESTRUCT) lParam)->hInstance; // 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 { gClient->ResizeDisplayDevice(gClient->GetPipeline()->Width(), gClient->GetPipeline()->Height(), !gClient->GetPipeline()->IsFullScreen()); } 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 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, int whichHash) { 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)); switch( whichHash ) { case kPasswordHashSHA1: CryptDigest( kCryptSha1, pNamePassHash, StrLen(password) * sizeof(password[0]), password ); // switch the endianness of the hash to big endian // NOTE: this is legacy from GameTap days to match GameTap's endianness pNamePassHash->data[0] = hsUNSWAP32(pNamePassHash->data[0]); pNamePassHash->data[1] = hsUNSWAP32(pNamePassHash->data[1]); pNamePassHash->data[2] = hsUNSWAP32(pNamePassHash->data[2]); pNamePassHash->data[3] = hsUNSWAP32(pNamePassHash->data[3]); pNamePassHash->data[4] = hsUNSWAP32(pNamePassHash->data[4]); break; case kPasswordHashSHA0: default: CryptHashPassword(wusername, wpassword, pNamePassHash); break; } } NetCommSetAccountUsernamePassword(wusername, *pNamePassHash); if (TGIsCider) NetCommSetAuthTokenAndOS(nil, L"mac"); else NetCommSetAuthTokenAndOS(nil, L"win"); 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)); UInt32 cryptKeyOld[4]; // The old cryptKey stuff ZeroMemory(cryptKeyOld, sizeof(cryptKeyOld)); GetOldCryptKey(cryptKeyOld, arrsize(cryptKeyOld)); char* temp; *pRemember = false; username[0] = '\0'; bool cryptKeyOk = false; bool cryptKeyOldOk = false; 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; cryptKeyOk = true; } 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; } if (!cryptKeyOk) // Try the old cryptKey { hsStream* stream = plEncryptedStream::OpenEncryptedFile(fileAndPath, true, cryptKeyOld); if (stream && !stream->AtEnd()) { UInt32 savedKey[4]; stream->Read(sizeof(savedKey), savedKey); if (memcmp(cryptKeyOld, savedKey, sizeof(savedKey)) == 0) { temp = stream->ReadSafeString(); if (temp) { StrCopy(username, temp, kMaxAccountNameLength); delete temp; cryptKeyOldOk = true; } 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; } // Done trying the old cryptKey } if (cryptKeyOldOk) // We need to re-write the login.dat file { hsStream* stream = plEncryptedStream::OpenEncryptedFileWrite(fileAndPath, cryptKey); if (stream) { stream->Write(sizeof(cryptKey), cryptKey); stream->WriteSafeString(username); stream->Writebool(*pRemember); if (*pRemember) stream->Write(sizeof(pNamePassHash->data), pNamePassHash->data); 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; if( WinHttpSendRequest( hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0 ) && WinHttpReceiveResponse(hRequest, 0) && WinHttpReadData(hRequest, data, 255, &bytesRead) && bytesRead ) { data[bytesRead] = 0; 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 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); // cycle through the hash types until we find one that matches or errors out LoginDialogParam loginParam; bool cancelled; for (int whichHash=FIRST_PASSWORD_HASH; whichHash >= LAST_PASSWORD_HASH; whichHash-- ) { SaveUserPass (username, password, &namePassHash, remember_password, whichHash); MemSet(&loginParam, 0, sizeof(loginParam)); cancelled = AuthenticateNetClientComm(&loginParam.authError, hwndDlg); // if the password was successful then go to the end processing if (IS_NET_SUCCESS(loginParam.authError) && !cancelled) break; // if it was cancelled or any error other than wrong password then go to end processing if (cancelled || loginParam.authError != kNetErrAuthenticationFailed) break; } 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; } else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_URULOGIN_GAMETAPLINK2) { ShellExecute(NULL, "open", "http://www.mystonline.com/donate", 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; // Reestablish exception handler after PhysX stole it SetUnhandledExceptionFilter( plCustomUnhandledExceptionFilter ); // 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 GetOldCryptKey(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; } } } void GetCryptKey(UInt32* cryptKey, unsigned numElements) { char volName[] = "C:\\"; char volID[] = "\\\\.\\C:"; // Need the drive ID in \\.\C: format for CreateFile() int index = 0; DWORD logicalDrives = GetLogicalDrives(); PSTORAGE_DEVICE_DESCRIPTOR pDevDesc; for (int i = 2; i < 26; ++i) // Drives A: B: prove nothing { if (logicalDrives & (1 << i)) { volName[0] = ('A' + i); // Previous base drive letter of C: was incorrect UINT driveType = GetDriveType( volName //LPCTSTR lpRootPathName ); if (driveType != DRIVE_FIXED) { continue; // next i } // We've got "Fixed" drive but still need to check // that it's not a USB (i.e. removable) drive. HANDLE hDevice; volID[4] = 'A' + i; hDevice = CreateFile( volID, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL ); if (hDevice != INVALID_HANDLE_VALUE) { pDevDesc = (PSTORAGE_DEVICE_DESCRIPTOR)new BYTE[sizeof(STORAGE_DEVICE_DESCRIPTOR) + 512 - 1]; if(pDevDesc != NULL) { pDevDesc->Size = sizeof(STORAGE_DEVICE_DESCRIPTOR) + 512 - 1; if (GetDisksProperty(hDevice, pDevDesc)) { if (pDevDesc->BusType == BusTypeUsb) // This is the ‘Check Point’!!! ;-) continue; // next i } delete pDevDesc; } CloseHandle(hDevice); } 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 ); if (!result) continue; // next i cryptKey[index] = (cryptKey[index] ^ volSerialNum); index = (++index) % numElements; } } } bool GetDisksProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc) { STORAGE_PROPERTY_QUERY Query; // input param for query DWORD dwOutBytes; // IOCTL output length BOOL bResult; // IOCTL return val // specify the query type Query.PropertyId = StorageDeviceProperty; Query.QueryType = PropertyStandardQuery; // Query using IOCTL_STORAGE_QUERY_PROPERTY bResult = ::DeviceIoControl(hDevice, // device handle IOCTL_STORAGE_QUERY_PROPERTY, // info of device property &Query, sizeof(STORAGE_PROPERTY_QUERY), // input data buffer pDevDesc, pDevDesc->Size, // output data buffer &dwOutBytes, // out's length (LPOVERLAPPED)NULL); return bResult; }