Files
SingularityViewer/indra/newview/llappviewerwin32.cpp
Liru Færs 432bf03f0a Port Crashpad support from alchemy, with some small tweaks
Moved initCrashReporting into LLAppViewer, everything we use there is
crossplatform enough that duplicating code is silly

Removes unused gCrashSettings
Adds MBFatalError, that's right, Crash Loop is now translatable!
Adds consent notification prompt to first login... enjoy that, everyone.
2019-10-21 16:25:29 -04:00

636 lines
17 KiB
C++

/**
* @file llappviewerwin32.cpp
* @brief The LLAppViewerWin32 class definitions
*
* $LicenseInfo:firstyear=2007&license=viewergpl$
*
* Copyright (c) 2007-2009, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llwindowwin32.h" // *FIX: for setting gIconResource.
#include "llappviewerwin32.h"
#include "llgl.h"
#include "res/resource.h" // *FIX: for setting gIconResource.
#include <fcntl.h> //_O_APPEND
#include <io.h> //_open_osfhandle()
#include <process.h> // _spawnl()
#include <tchar.h> // For TCHAR support
#include <Werapi.h>
#include "llviewercontrol.h"
#include "lldxhardware.h"
#ifdef USE_NVAPI
#include "nvapi/nvapi.h"
#include "nvapi/NvApiDriverSettings.h"
#endif
#include <stdlib.h>
#include "llweb.h"
#include "llsecondlifeurls.h"
#include "llwindebug.h"
#include "llviewernetwork.h"
#include "llmd5.h"
#include "llfindlocale.h"
#include "llcommandlineparser.h"
#include "lltrans.h"
const std::string LLAppViewerWin32::sWindowClass = "Second Life";
// Create app mutex creates a unique global windows object.
// If the object can be created it returns true, otherwise
// it returns false. The false result can be used to determine
// if another instance of a second life app (this vers. or later)
// is running.
// *NOTE: Do not use this method to run a single instance of the app.
// This is intended to help debug problems with the cross-platform
// locked file method used for that purpose.
bool create_app_mutex()
{
bool result = true;
LPCWSTR unique_mutex_name = L"SecondLifeAppMutex";
HANDLE hMutex;
hMutex = CreateMutex(nullptr, TRUE, unique_mutex_name);
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
result = false;
}
return result;
}
#ifdef USE_NVAPI
#define NVAPI_APPNAME L"Second Life"
/*
This function is used to print to the command line a text message
describing the nvapi error and quits
*/
void nvapi_error(NvAPI_Status status)
{
NvAPI_ShortString szDesc = { 0 };
NvAPI_GetErrorMessage(status, szDesc);
LL_WARNS() << szDesc << LL_ENDL;
//should always trigger when asserts are enabled
//llassert(status == NVAPI_OK);
}
void ll_nvapi_init(NvDRSSessionHandle hSession)
{
// (2) load all the system settings into the session
NvAPI_Status status = NvAPI_DRS_LoadSettings(hSession);
if (status != NVAPI_OK)
{
nvapi_error(status);
return;
}
NvAPI_UnicodeString wsz = { 0 };
memcpy_s(wsz, sizeof(wsz), NVAPI_APPNAME, sizeof(NVAPI_APPNAME));
status = NvAPI_DRS_SetCurrentGlobalProfile(hSession, wsz);
if (status != NVAPI_OK)
{
nvapi_error(status);
return;
}
// (3) Obtain the current profile.
NvDRSProfileHandle hProfile = 0;
status = NvAPI_DRS_GetCurrentGlobalProfile(hSession, &hProfile);
if (status != NVAPI_OK)
{
nvapi_error(status);
return;
}
// load settings for querying
status = NvAPI_DRS_LoadSettings(hSession);
if (status != NVAPI_OK)
{
nvapi_error(status);
return;
}
//get the preferred power management mode for Second Life
NVDRS_SETTING drsSetting = {0};
drsSetting.version = NVDRS_SETTING_VER;
status = NvAPI_DRS_GetSetting(hSession, hProfile, PREFERRED_PSTATE_ID, &drsSetting);
if (status == NVAPI_SETTING_NOT_FOUND)
{ //only override if the user hasn't specifically set this setting
// (4) Specify that we want the VSYNC disabled setting
// first we fill the NVDRS_SETTING struct, then we call the function
drsSetting.version = NVDRS_SETTING_VER;
drsSetting.settingId = PREFERRED_PSTATE_ID;
drsSetting.settingType = NVDRS_DWORD_TYPE;
drsSetting.u32CurrentValue = PREFERRED_PSTATE_PREFER_MAX;
status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting);
if (status != NVAPI_OK)
{
nvapi_error(status);
return;
}
// (5) Now we apply (or save) our changes to the system
status = NvAPI_DRS_SaveSettings(hSession);
if (status != NVAPI_OK)
{
nvapi_error(status);
return;
}
}
else if (status != NVAPI_OK)
{
nvapi_error(status);
return;
}
}
#endif
//#define DEBUGGING_SEH_FILTER 1
#if DEBUGGING_SEH_FILTER
# define WINMAIN DebuggingWinMain
#else
# define WINMAIN WinMain
#endif
int APIENTRY WINMAIN(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
const S32 MAX_HEAPS = 255;
DWORD heap_enable_lfh_error[MAX_HEAPS];
S32 num_heaps = 0;
#if WINDOWS_CRT_MEM_CHECKS && !INCLUDE_VLD
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); // dump memory leaks on exit
#elif 1
// Experimental - enable the low fragmentation heap
// This results in a 2-3x improvement in opening a new Inventory window (which uses a large numebr of allocations)
// Note: This won't work when running from the debugger unless the _NO_DEBUG_HEAP environment variable is set to 1
_CrtSetDbgFlag(0); // default, just making explicit
ULONG ulEnableLFH = 2;
HANDLE* hHeaps = new HANDLE[MAX_HEAPS];
num_heaps = GetProcessHeaps(MAX_HEAPS, hHeaps);
for(S32 i = 0; i < num_heaps; i++)
{
bool success = HeapSetInformation(hHeaps[i], HeapCompatibilityInformation, &ulEnableLFH, sizeof(ulEnableLFH));
if (success)
heap_enable_lfh_error[i] = 0;
else
heap_enable_lfh_error[i] = GetLastError();
}
#endif
// *FIX: global
gIconResource = MAKEINTRESOURCE(IDI_LL_ICON);
LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(lpCmdLine);
#if !defined(USE_CRASHPAD)
viewer_app_ptr->setErrorHandler(LLAppViewer::handleViewerCrash);
#endif
// Set a debug info flag to indicate if multiple instances are running.
bool found_other_instance = !create_app_mutex();
gDebugInfo["FoundOtherInstanceAtStartup"] = LLSD::Boolean(found_other_instance);
bool ok = viewer_app_ptr->init();
if(!ok)
{
LL_WARNS() << "Application init failed." << LL_ENDL;
return -1;
}
#ifdef USE_NVAPI
NvAPI_Status status;
// Initialize NVAPI
status = NvAPI_Initialize();
NvDRSSessionHandle hSession = 0;
if (status == NVAPI_OK)
{
// Create the session handle to access driver settings
status = NvAPI_DRS_CreateSession(&hSession);
if (status != NVAPI_OK)
{
nvapi_error(status);
}
else
{
//override driver setting as needed
ll_nvapi_init(hSession);
}
}
#endif
// Have to wait until after logging is initialized to display LFH info
if (num_heaps > 0)
{
LL_INFOS() << "Attempted to enable LFH for " << num_heaps << " heaps." << LL_ENDL;
for(S32 i = 0; i < num_heaps; i++)
{
if (heap_enable_lfh_error[i])
{
LL_INFOS() << " Failed to enable LFH for heap: " << i << " Error: " << heap_enable_lfh_error[i] << LL_ENDL;
}
}
}
// Run the application main loop
if(!LLApp::isQuitting())
{
viewer_app_ptr->mainLoop();
}
if (!LLApp::isError())
{
//
// We don't want to do cleanup here if the error handler got called -
// the assumption is that the error handler is responsible for doing
// app cleanup if there was a problem.
//
#if WINDOWS_CRT_MEM_CHECKS
LL_INFOS() << "CRT Checking memory:" << LL_ENDL;
if (!_CrtCheckMemory())
{
LL_WARNS() << "_CrtCheckMemory() failed at prior to cleanup!" << LL_ENDL;
}
else
{
LL_INFOS() << " No corruption detected." << LL_ENDL;
}
#endif
gGLActive = TRUE;
viewer_app_ptr->cleanup();
#if WINDOWS_CRT_MEM_CHECKS
LL_INFOS() << "CRT Checking memory:" << LL_ENDL;
if (!_CrtCheckMemory())
{
LL_WARNS() << "_CrtCheckMemory() failed after cleanup!" << LL_ENDL;
}
else
{
LL_INFOS() << " No corruption detected." << LL_ENDL;
}
#endif
}
delete viewer_app_ptr;
viewer_app_ptr = nullptr;
//start updater
if(LLAppViewer::sUpdaterInfo)
{
_spawnl(_P_NOWAIT, LLAppViewer::sUpdaterInfo->mUpdateExePath.c_str(), LLAppViewer::sUpdaterInfo->mUpdateExePath.c_str(), LLAppViewer::sUpdaterInfo->mParams.str().c_str(), NULL);
delete LLAppViewer::sUpdaterInfo ;
LLAppViewer::sUpdaterInfo = NULL ;
}
#ifdef USE_NVAPI
// (NVAPI) (6) We clean up. This is analogous to doing a free()
if (hSession)
{
NvAPI_DRS_DestroySession(hSession);
hSession = 0;
}
#endif
return 0;
}
#if DEBUGGING_SEH_FILTER
// The compiler doesn't like it when you use __try/__except blocks
// in a method that uses object destructors. Go figure.
// This winmain just calls the real winmain inside __try.
// The __except calls our exception filter function. For debugging purposes.
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
__try
{
WINMAIN(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
__except( viewer_windows_exception_handler( GetExceptionInformation() ) )
{
_tprintf( _T("Exception handled.\n") );
}
}
#endif
void LLAppViewerWin32::disableWinErrorReporting()
{
std::string const& executable_name = gDirUtilp->getExecutableFilename();
if( S_OK == WerAddExcludedApplication( utf8str_to_utf16str(executable_name).c_str(), FALSE ) )
{
LL_INFOS() << "WerAddExcludedApplication() succeeded for " << executable_name << LL_ENDL;
}
else
{
LL_INFOS() << "WerAddExcludedApplication() failed for " << executable_name << LL_ENDL;
}
}
const S32 MAX_CONSOLE_LINES = 500;
static bool create_console()
{
CONSOLE_SCREEN_BUFFER_INFO coninfo;
// allocate a console for this app
const bool isConsoleAllocated = AllocConsole();
// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
coninfo.dwSize.Y = MAX_CONSOLE_LINES;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
// Redirect the CRT standard input, output, and error handles to the console
freopen("CONIN$", "r", stdin);
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
setvbuf( stdin, nullptr, _IONBF, 0 );
setvbuf( stdout, nullptr, _IONBF, 0 );
setvbuf( stderr, nullptr, _IONBF, 0 );
return isConsoleAllocated;
}
LLAppViewerWin32::LLAppViewerWin32(const char* cmd_line) :
mCmdLine(cmd_line),
mIsConsoleAllocated(false)
{
}
LLAppViewerWin32::~LLAppViewerWin32()
{
}
bool LLAppViewerWin32::init()
{
// Platform specific initialization.
// Turn off Windows Error Reporting
// (Don't send our data to Microsoft--at least until we are Logo approved and have a way
// of getting the data back from them.)
//
// LL_INFOS() << "Turning off Windows error reporting." << LL_ENDL;
disableWinErrorReporting();
#ifndef LL_RELEASE_FOR_DOWNLOAD
LLWinDebug::instance().init();
#endif
bool success = LLAppViewer::init();
return success;
}
bool LLAppViewerWin32::cleanup()
{
bool result = LLAppViewer::cleanup();
gDXHardware.cleanup();
if (mIsConsoleAllocated)
{
FreeConsole();
mIsConsoleAllocated = false;
}
return result;
}
void LLAppViewerWin32::initLoggingAndGetLastDuration()
{
LLAppViewer::initLoggingAndGetLastDuration();
}
void LLAppViewerWin32::initConsole()
{
// pop up debug console
mIsConsoleAllocated = create_console();
return LLAppViewer::initConsole();
}
void write_debug_dx(const char* str)
{
std::string value = gDebugInfo["DXInfo"].asString();
value += str;
gDebugInfo["DXInfo"] = value;
}
void write_debug_dx(const std::string& str)
{
write_debug_dx(str.c_str());
}
bool LLAppViewerWin32::initHardwareTest()
{
//
// Do driver verification and initialization based on DirectX
// hardware polling and driver versions
//
if (FALSE == gSavedSettings.getBOOL("NoHardwareProbe"))
{
BOOL vram_only = !gSavedSettings.getBOOL("ProbeHardwareOnStartup");
// per DEV-11631 - disable hardware probing for everything
// but vram.
vram_only = TRUE;
LLSplashScreen::update(LLTrans::getString("StartupDetectingHardware"));
LL_DEBUGS("AppInit") << "Attempting to poll DirectX for hardware info" << LL_ENDL;
gDXHardware.setWriteDebugFunc(write_debug_dx);
S32Megabytes system_ram = gSysMemory.getPhysicalMemoryKB();
BOOL probe_ok = gDXHardware.getInfo(vram_only, system_ram);
if (!probe_ok
&& gSavedSettings.getWarning("AboutDirectX9"))
{
LL_WARNS("AppInit") << "DirectX probe failed, alerting user." << LL_ENDL;
// Warn them that runnin without DirectX 9 will
// not allow us to tell them about driver issues
std::ostringstream msg;
msg << LLTrans::getString ("MBNoDirectX");
S32 button = OSMessageBox(
msg.str(),
LLTrans::getString("MBWarning"),
OSMB_YESNO);
if (OSBTN_NO== button)
{
LL_INFOS("AppInit") << "User quitting after failed DirectX 9 detection" << LL_ENDL;
LLWeb::loadURLExternal(DIRECTX_9_URL, false);
return false;
}
gSavedSettings.setWarning("AboutDirectX9", FALSE);
}
LL_DEBUGS("AppInit") << "Done polling DirectX for hardware info" << LL_ENDL;
// Only probe once after installation
gSavedSettings.setBOOL("ProbeHardwareOnStartup", FALSE);
// Disable so debugger can work
std::string splash_msg;
LLStringUtil::format_map_t args;
args["[APP_NAME]"] = LLAppViewer::instance()->getSecondLifeTitle();
splash_msg = LLTrans::getString("StartupLoading", args);
LLSplashScreen::update(splash_msg);
}
if (!restoreErrorTrap())
{
LL_WARNS("AppInit") << " Someone took over my exception handler (post hardware probe)!" << LL_ENDL;
}
if (gGLManager.mVRAM == 0)
{
gGLManager.mVRAM = gDXHardware.getVRAM();
LL_WARNS("AppInit") << "gGLManager.mVRAM: " << gGLManager.mVRAM << LL_ENDL;
}
LL_INFOS("AppInit") << "Detected VRAM: " << gGLManager.mVRAM << LL_ENDL;
return true;
}
bool LLAppViewerWin32::initParseCommandLine(LLCommandLineParser& clp)
{
if (!clp.parseCommandLineString(mCmdLine))
{
return false;
}
// Find the system language.
FL_Locale *locale = nullptr;
FL_Success success = FL_FindLocale(&locale, FL_MESSAGES);
if (success != 0)
{
if (success >= 2 && locale->lang) // confident!
{
LL_INFOS("AppInit") << "Language: " << ll_safe_string(locale->lang) << LL_ENDL;
LL_INFOS("AppInit") << "Location: " << ll_safe_string(locale->country) << LL_ENDL;
LL_INFOS("AppInit") << "Variant: " << ll_safe_string(locale->variant) << LL_ENDL;
LLControlVariable* c = gSavedSettings.getControl("SystemLanguage");
if(c)
{
c->setValue(std::string(locale->lang), false);
}
}
}
FL_FreeLocale(&locale);
return true;
}
bool LLAppViewerWin32::restoreErrorTrap()
{
return true;
}
//virtual
bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
{
wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars.
mbstowcs(window_class, sWindowClass.c_str(), 255);
window_class[255] = 0;
// Use the class instead of the window name.
HWND other_window = FindWindow(window_class, nullptr);
if (other_window != nullptr)
{
LL_DEBUGS() << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL;
COPYDATASTRUCT cds;
const S32 SLURL_MESSAGE_TYPE = 0;
cds.dwData = SLURL_MESSAGE_TYPE;
cds.cbData = url.length() + 1;
cds.lpData = (void*)url.c_str();
LRESULT msg_result = SendMessage(other_window, WM_COPYDATA, NULL, (LPARAM)&cds);
LL_DEBUGS() << "SendMessage(WM_COPYDATA) to other window '"
<< getWindowTitle() << "' returned " << msg_result << LL_ENDL;
return true;
}
return false;
}
std::string LLAppViewerWin32::generateSerialNumber()
{
char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore
serial_md5[0] = 0;
DWORD serial = 0;
DWORD flags = 0;
BOOL success = GetVolumeInformation(
TEXT("C:\\"),
nullptr, // volume name buffer
0, // volume name buffer size
&serial, // volume serial
nullptr, // max component length
&flags, // file system flags
nullptr, // file system name buffer
0); // file system name buffer size
if (success)
{
LLMD5 md5;
md5.update( (unsigned char*)&serial, sizeof(DWORD));
md5.finalize();
md5.hex_digest(serial_md5);
}
else
{
LL_WARNS() << "GetVolumeInformation failed" << LL_ENDL;
}
return serial_md5;
}