/** * @file llappviewer.cpp * @brief The LLAppViewer class definitions * * $LicenseInfo:firstyear=2007&license=viewergpl$ * * Copyright (c) 2007, 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://secondlife.com/developers/opensource/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://secondlife.com/developers/opensource/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 "llappviewer.h" #include "llversionviewer.h" #include "llfeaturemanager.h" #include "llvieweruictrlfactory.h" #include "llalertdialog.h" #include "llerrorcontrol.h" #include "llviewerimagelist.h" #include "llgroupmgr.h" #include "llagent.h" #include "llwindow.h" #include "llviewerstats.h" #include "llmd5.h" #include "llpumpio.h" #include "llfloateractivespeakers.h" #include "llimpanel.h" #include "llstartup.h" #include "llfocusmgr.h" #include "llviewerjoystick.h" #include "llcurl.h" #include "llfloatersnapshot.h" #include "llviewerwindow.h" #include "llviewerdisplay.h" #include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llworldmap.h" #include "llmutelist.h" #include "llurldispatcher.h" #include "llweb.h" #include "llsecondlifeurls.h" #if LL_WINDOWS #include "llwindebug.h" #endif #if LL_WINDOWS # include // For _SH_DENYWR in initMarkerFile #else # include // For initMarkerFile support #endif #include "llnotify.h" #include "llmediaengine.h" #include "llviewerkeyboard.h" #include "lllfsthread.h" #include "llworkerthread.h" #include "lltexturecache.h" #include "lltexturefetch.h" #include "llimageworker.h" // The files below handle dependencies from cleanup. #include "llkeyframemotion.h" #include "llworldmap.h" #include "llhudmanager.h" #include "lltoolmgr.h" #include "llassetstorage.h" #include "llpolymesh.h" #include "lleconomy.h" #include "llcachename.h" #include "audioengine.h" #include "llviewermenu.h" #include "llselectmgr.h" #include "lltracker.h" #include "llmozlib.h" #include "llviewerparcelmgr.h" #include "llworldmapview.h" #include "lldebugview.h" #include "llconsole.h" #include "llcontainerview.h" #include "llhoverview.h" #include "llsdserialize.h" #if LL_WINDOWS && LL_LCD_COMPILE #include "lllcd.h" #endif #if LL_QUICKTIME_ENABLED #if LL_DARWIN #include #else // quicktime specific includes #include "MacTypes.h" #include "QTML.h" #include "Movies.h" #include "FixMath.h" #endif #endif #include "llworld.h" #include "llhudeffecttrail.h" #include "llvectorperfoptions.h" #include "llurlsimstring.h" // Included so that constants/settings might be initialized // in save_settings_to_globals() #include "llbutton.h" #include "llcombobox.h" #include "llstatusbar.h" #include "llsurface.h" #include "llvosky.h" #include "llvotree.h" #include "llvoavatar.h" #include "llfolderview.h" #include "lltoolbar.h" #include "llframestats.h" #include "llagentpilot.h" #include "llsrv.h" // includes for idle() idleShutdown() #include "llviewercontrol.h" #include "lleventnotifier.h" #include "llcallbacklist.h" #include "pipeline.h" #include "llgesturemgr.h" #include "llsky.h" #include "llvlmanager.h" #include "llviewercamera.h" #include "lldrawpoolbump.h" #include "llvieweraudio.h" #include "llimview.h" #include "llviewerthrottle.h" // #include "llinventoryview.h" // *FIX: Remove these once the command line params thing is figured out. // Yuck! static int gTempArgC = 0; static char** gTempArgV; // *FIX: These extern globals should be cleaned up. // The globals either represent state/config/resource-storage of either // this app, or another 'component' of the viewer. App globals should be // moved into the app class, where as the other globals should be // moved out of here. // If a global symbol reference seems valid, it will be included // via header files above. //---------------------------------------------------------------------------- // llviewernetwork.h #include "llviewernetwork.h" // extern EGridInfo gGridChoice; //---------------------------------------------------------------------------- // viewer.cpp - these are only used in viewer, should be easily moved. extern void disable_win_error_reporting(); //#define APPLE_PREVIEW // Define this if you're doing a preview build on the Mac #if LL_RELEASE_FOR_DOWNLOAD // Default userserver for production builds is agni #ifndef APPLE_PREVIEW static EGridInfo GridDefaultChoice = GRID_INFO_AGNI; #else static EGridInfo GridDefaultChoice = GRID_INFO_ADITI; #endif #else // Default userserver for development builds is dmz static EGridInfo GridDefaultChoice = GRID_INFO_DMZ; #endif #if LL_WINDOWS extern void create_console(); #endif #if LL_DARWIN #include extern void init_apple_menu(const char* product); extern OSErr AEGURLHandler(const AppleEvent *messagein, AppleEvent *reply, long refIn); extern OSErr AEQuitHandler(const AppleEvent *messagein, AppleEvent *reply, long refIn); extern OSStatus simpleDialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata); extern OSStatus DisplayReleaseNotes(void); #include #endif // LL_DARWIN #include "moviemaker.h" extern BOOL gbCapturing; #if !LL_SOLARIS extern MovieMaker gMovieMaker; #endif extern BOOL gRandomizeFramerate; extern BOOL gPeriodicSlowFrame; #if LL_GSTREAMER_ENABLED void UnloadGStreamer(); #endif extern void send_stats(); //////////////////////////////////////////////////////////// // All from the last globals push... bool gVerifySSLCert = true; BOOL gHandleKeysAsync = FALSE; BOOL gProbeHardware = TRUE; // Use DirectX 9 to probe for hardware S32 gYieldMS = 0; // set in parse_args, used in mainLoop BOOL gYieldTime = FALSE; const F32 DEFAULT_AFK_TIMEOUT = 5.f * 60.f; // time with no input before user flagged as Away From Keyboard F32 gSimLastTime; // Used in LLAppViewer::init and send_stats() F32 gSimFrames; LLString gDisabledMessage; // Set in LLAppViewer::initConfiguration used in idle_startup BOOL gHideLinks = FALSE; // Set in LLAppViewer::initConfiguration, used externally BOOL gInProductionGrid = FALSE; BOOL gAllowIdleAFK = TRUE; F32 gAFKTimeout = DEFAULT_AFK_TIMEOUT; BOOL gShowObjectUpdates = FALSE; BOOL gLogMessages = FALSE; std::string gChannelName = LL_CHANNEL; BOOL gUseAudio = TRUE; LLString gCmdLineFirstName; LLString gCmdLineLastName; LLString gCmdLinePassword; BOOL gAutoLogin = FALSE; const char* DEFAULT_SETTINGS_FILE = "settings.xml"; BOOL gRequestInventoryLibrary = TRUE; BOOL gGodConnect = FALSE; BOOL gAcceptTOS = FALSE; BOOL gAcceptCriticalMessage = FALSE; LLUUID gViewerDigest; // MD5 digest of the viewer's executable file. BOOL gLastExecFroze = FALSE; LLSD gDebugInfo; U32 gFrameCount = 0; U32 gForegroundFrameCount = 0; // number of frames that app window was in foreground LLPumpIO* gServicePump = NULL; BOOL gPacificDaylightTime = FALSE; U64 gFrameTime = 0; F32 gFrameTimeSeconds = 0.f; F32 gFrameIntervalSeconds = 0.f; F32 gFPSClamped = 10.f; // Pretend we start at target rate. F32 gFrameDTClamped = 0.f; // Time between adjacent checks to network for packets U64 gStartTime = 0; // gStartTime is "private", used only to calculate gFrameTimeSeconds LLTimer gRenderStartTime; LLFrameTimer gForegroundTime; LLTimer gLogoutTimer; static const F32 LOGOUT_REQUEST_TIME = 6.f; // this will be cut short by the LogoutReply msg. F32 gLogoutMaxTime = LOGOUT_REQUEST_TIME; LLUUID gInventoryLibraryOwner; LLUUID gInventoryLibraryRoot; BOOL gDisableVoice = FALSE; BOOL gDisconnected = FALSE; // Map scale in pixels per region F32 gMapScale = 128.f; F32 gMiniMapScale = 128.f; // used to restore texture state after a mode switch LLFrameTimer gRestoreGLTimer; BOOL gRestoreGL = FALSE; BOOL gUseWireframe = FALSE; F32 gMouseSensitivity = 3.f; BOOL gInvertMouse = FALSE; // VFS globals - see llappviewer.h LLVFS* gStaticVFS = NULL; LLMemoryInfo gSysMemory; bool gPreloadImages = true; bool gPreloadSounds = true; LLString gLastVersionChannel; LLVector3 gWindVec(3.0, 3.0, 0.0); LLVector3 gRelativeWindVec(0.0, 0.0, 0.0); U32 gPacketsIn = 0; BOOL gPrintMessagesThisFrame = FALSE; BOOL gUseConsole = TRUE; BOOL gRandomizeFramerate = FALSE; BOOL gPeriodicSlowFrame = FALSE; BOOL gQAMode = FALSE; //////////////////////////////////////////////////////////// // Internal globals... that should be removed. static F32 gQuitAfterSeconds = 0.f; static BOOL gRotateRight = FALSE; static BOOL gIgnorePixelDepth = FALSE; // Allow multiple viewers in ReleaseForDownload #if LL_RELEASE_FOR_DOWNLOAD static BOOL gMultipleViewersOK = FALSE; #else static BOOL gMultipleViewersOK = TRUE; #endif static std::map gCommandLineSettings; static std::map gCommandLineForcedSettings; static LLString gArgs; static LLString gOldSettingsFileName; static const char* LEGACY_DEFAULT_SETTINGS_FILE = "settings.ini"; static BOOL gDoDisconnect = FALSE; static LLString gLaunchFileOnQuit; //---------------------------------------------------------------------------- // File scope definitons const char *VFS_DATA_FILE_BASE = "data.db2.x."; const char *VFS_INDEX_FILE_BASE = "index.db2.x."; static LLString gSecondLife; static LLString gWindowTitle; #ifdef LL_WINDOWS static char sWindowClass[] = "Second Life"; #endif std::vector gLoginURIs; static std::string gHelperURI; static const char USAGE[] = "\n" "usage:\tviewer [options]\n" "options:\n" " -login log in as a user\n" " -autologin log in as last saved user\n" " -loginuri login server and CGI script to use\n" " -helperuri helper web CGI prefix to use\n" " -settings specify the filename of a\n" " configuration file\n" " default is settings.xml\n" " -setdefault specify the value of a particular\n" " configuration variable which can be\n" " overridden by settings.xml\n" " -set specify the value of a particular\n" " configuration variable that\n" " overrides all other settings\n" " -user specify userserver in dotted quad\n" #if !LL_RELEASE_FOR_DOWNLOAD " -sim specify the simulator ip address\n" #endif " -god log in as god if you have god access\n" " -purge delete files in cache\n" " -safe reset preferences, run in safe mode\n" " -noutc logs in local time, not UTC\n" " -nothread run vfs in single thread\n" " -noinvlib Do not request inventory library\n" " -multiple allow multiple viewers\n" " -nomultiple block multiple viewers\n" " -novoice disable voice\n" " -ignorepixeldepth ignore pixel depth settings\n" " -cooperative [ms] yield some idle time to local host\n" " -skin ui/branding skin folder to use\n" #if LL_WINDOWS " -noprobe disable hardware probe\n" #endif " -noquicktime disable QuickTime movies, speeds startup\n" " -nopreload don't preload UI images or sounds, speeds startup\n" // these seem to be unused //" -noenv turn off environmental effects\n" //" -proxy specify the proxy ip address\n" "\n"; void idle_afk_check() { // check idle timers if (gAllowIdleAFK && (gAwayTriggerTimer.getElapsedTimeF32() > gAFKTimeout)) { gAgent.setAFK(); } } // A callback set in LLAppViewer::init() static void ui_audio_callback(const LLUUID& uuid) { if (gAudiop) { F32 volume = gSavedSettings.getF32("AudioLevelUI"); gAudiop->triggerSound(uuid, gAgent.getID(), volume); } } void request_initial_instant_messages() { static BOOL requested = FALSE; if (!requested && gMuteListp && gMuteListp->isLoaded() && gAgent.getAvatarObject()) { // Auto-accepted inventory items may require the avatar object // to build a correct name. Likewise, inventory offers from // muted avatars require the mute list to properly mute. LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RetrieveInstantMessages); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); gAgent.sendReliableMessage(); requested = TRUE; } } // Use these strictly for things that are constructed at startup, // or for things that are performance critical. JC static void saved_settings_to_globals() { LLBUTTON_H_PAD = gSavedSettings.getS32("ButtonHPad"); LLBUTTON_V_PAD = gSavedSettings.getS32("ButtonVPad"); BTN_HEIGHT_SMALL = gSavedSettings.getS32("ButtonHeightSmall"); BTN_HEIGHT = gSavedSettings.getS32("ButtonHeight"); MENU_BAR_HEIGHT = gSavedSettings.getS32("MenuBarHeight"); MENU_BAR_WIDTH = gSavedSettings.getS32("MenuBarWidth"); STATUS_BAR_HEIGHT = gSavedSettings.getS32("StatusBarHeight"); LLCOMBOBOX_HEIGHT = BTN_HEIGHT - 2; LLCOMBOBOX_WIDTH = 128; LLSurface::setTextureSize(gSavedSettings.getU32("RegionTextureSize")); LLVOSky::sNighttimeBrightness = gSavedSettings.getF32("RenderNightBrightness"); LLImageGL::sGlobalUseAnisotropic = gSavedSettings.getBOOL("RenderAnisotropic"); LLVOVolume::sLODFactor = gSavedSettings.getF32("RenderVolumeLODFactor"); LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f; LLVolumeImplFlexible::sUpdateFactor = gSavedSettings.getF32("RenderFlexTimeFactor"); LLVOTree::sTreeFactor = gSavedSettings.getF32("RenderTreeLODFactor"); LLVOAvatar::sLODFactor = gSavedSettings.getF32("RenderAvatarLODFactor"); LLVOAvatar::sMaxVisible = gSavedSettings.getS32("RenderAvatarMaxVisible"); LLVOAvatar::sVisibleInFirstPerson = gSavedSettings.getBOOL("FirstPersonAvatarVisible"); // clamp auto-open time to some minimum usable value LLFolderView::sAutoOpenTime = llmax(0.25f, gSavedSettings.getF32("FolderAutoOpenDelay")); LLToolBar::sInventoryAutoOpenTime = gSavedSettings.getF32("InventoryAutoOpenDelay"); LLSelectMgr::sRectSelectInclusive = gSavedSettings.getBOOL("RectangleSelectInclusive"); LLSelectMgr::sRenderHiddenSelections = gSavedSettings.getBOOL("RenderHiddenSelections"); LLSelectMgr::sRenderLightRadius = gSavedSettings.getBOOL("RenderLightRadius"); gFrameStats.setTrackStats(gSavedSettings.getBOOL("StatsSessionTrackFrameStats")); gAgentPilot.mNumRuns = gSavedSettings.getS32("StatsNumRuns"); gAgentPilot.mQuitAfterRuns = gSavedSettings.getBOOL("StatsQuitAfterRuns"); gAgent.mHideGroupTitle = gSavedSettings.getBOOL("RenderHideGroupTitle"); gDebugWindowProc = gSavedSettings.getBOOL("DebugWindowProc"); gAllowIdleAFK = gSavedSettings.getBOOL("AllowIdleAFK"); gAFKTimeout = gSavedSettings.getF32("AFKTimeout"); gMouseSensitivity = gSavedSettings.getF32("MouseSensitivity"); gInvertMouse = gSavedSettings.getBOOL("InvertMouse"); gShowObjectUpdates = gSavedSettings.getBOOL("ShowObjectUpdates"); gMapScale = gSavedSettings.getF32("MapScale"); gMiniMapScale = gSavedSettings.getF32("MiniMapScale"); gHandleKeysAsync = gSavedSettings.getBOOL("AsyncKeyboard"); LLHoverView::sShowHoverTips = gSavedSettings.getBOOL("ShowHoverTips"); #if LL_VECTORIZE if (gSysCPU.hasAltivec()) { gSavedSettings.setBOOL("VectorizeEnable", TRUE ); gSavedSettings.setU32("VectorizeProcessor", 0 ); } else if (gSysCPU.hasSSE2()) { gSavedSettings.setBOOL("VectorizeEnable", TRUE ); gSavedSettings.setU32("VectorizeProcessor", 2 ); } else if (gSysCPU.hasSSE()) { gSavedSettings.setBOOL("VectorizeEnable", TRUE ); gSavedSettings.setU32("VectorizeProcessor", 1 ); } else { // Don't bother testing or running if CPU doesn't support it. JC gSavedSettings.setBOOL("VectorizePerfTest", FALSE ); gSavedSettings.setBOOL("VectorizeEnable", FALSE ); gSavedSettings.setU32("VectorizeProcessor", 0 ); gSavedSettings.setBOOL("VectorizeSkin", FALSE); } #else // This build target doesn't support SSE, don't test/run. gSavedSettings.setBOOL("VectorizePerfTest", FALSE ); gSavedSettings.setBOOL("VectorizeEnable", FALSE ); gSavedSettings.setU32("VectorizeProcessor", 0 ); gSavedSettings.setBOOL("VectorizeSkin", FALSE); #endif // propagate push to talk preference to current status gSavedSettings.setBOOL("PTTCurrentlyEnabled", gSavedSettings.getBOOL("EnablePushToTalk")); settings_setup_listeners(); // gAgent.init() also loads from saved settings. } int parse_args(int argc, char **argv) { // Sometimes IP addresses passed in on the command line have leading // or trailing white space. Use LLString to clean that up. LLString ip_string; S32 j; for (j = 1; j < argc; j++) { gArgs += argv[j]; gArgs += " "; LLString argument = argv[j]; if ((!strcmp(argv[j], "-port")) && (++j < argc)) { sscanf(argv[j], "%u", &(gAgent.mViewerPort)); } else if ((!strcmp(argv[j], "-drop")) && (++j < argc)) { sscanf(argv[j], "%f", &gPacketDropPercentage); } else if ((!strcmp(argv[j], "-inbw")) && (++j < argc)) { sscanf(argv[j], "%f", &gInBandwidth); } else if ((!strcmp(argv[j], "-outbw")) && (++j < argc)) { sscanf(argv[j], "%f", &gOutBandwidth); } else if (!strcmp(argv[j], "--aditi")) { gGridChoice = GRID_INFO_ADITI; snprintf(gGridName, MAX_STRING, "%s", gGridInfo[gGridChoice].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--agni")) { gGridChoice = GRID_INFO_AGNI; snprintf(gGridName, MAX_STRING, "%s", gGridInfo[gGridChoice].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--dmz")) { gGridChoice = GRID_INFO_DMZ; snprintf(gGridName, MAX_STRING, "%s", gGridInfo[gGridChoice].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--siva")) { gGridChoice = GRID_INFO_SIVA; snprintf(gGridName, MAX_STRING, "%s", gGridInfo[gGridChoice].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--shakti")) { gGridChoice = GRID_INFO_SHAKTI; snprintf(gGridName, MAX_STRING, "%s", gGridInfo[gGridChoice].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--durga")) { gGridChoice = GRID_INFO_DURGA; snprintf(gGridName, MAX_STRING, "%s", gGridInfo[gGridChoice].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--soma")) { gGridChoice = GRID_INFO_SOMA; snprintf(gGridName, MAX_STRING, "%s", gGridInfo[gGridChoice].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--ganga")) { gGridChoice = GRID_INFO_GANGA; sprintf(gGridName,"%s", gGridInfo[gGridChoice].mName); } else if (!strcmp(argv[j], "--vaak")) { gGridChoice = GRID_INFO_VAAK; sprintf(gGridName,"%s", gGridInfo[gGridChoice].mName); } else if (!strcmp(argv[j], "--uma")) { gGridChoice = GRID_INFO_UMA; sprintf(gGridName,"%s", gGridInfo[gGridChoice].mName); } else if (!strcmp(argv[j], "-user") && (++j < argc)) { if (!strcmp(argv[j], "-")) { gGridChoice = GRID_INFO_LOCAL; snprintf(gGridName, MAX_STRING, "%s", LOOPBACK_ADDRESS_STRING); // Flawfinder: ignore } else { gGridChoice = GRID_INFO_OTHER; ip_string.assign( argv[j] ); LLString::trim(ip_string); snprintf(gGridName, MAX_STRING, "%s", ip_string.c_str()); // Flawfinder: ignore } } else if (!strcmp(argv[j], "-loginuri") && (++j < argc)) { LLAppViewer::instance()->addLoginURI(utf8str_trim(argv[j])); } else if (!strcmp(argv[j], "-helperuri") && (++j < argc)) { LLAppViewer::instance()->setHelperURI(utf8str_trim(argv[j])); } else if (!strcmp(argv[j], "-debugviews")) { LLView::sDebugRects = TRUE; } else if (!strcmp(argv[j], "-skin") && (++j < argc)) { std::string folder(argv[j]); gDirUtilp->setSkinFolder(folder); } else if (!strcmp(argv[j], "-autologin") || !strcmp(argv[j], "--autologin")) // keep --autologin for compatibility { gAutoLogin = TRUE; } else if (!strcmp(argv[j], "-quitafter") && (++j < argc)) { gQuitAfterSeconds = (F32)atof(argv[j]); } else if (!strcmp(argv[j], "-rotate")) { gRotateRight = TRUE; } // else if (!strcmp(argv[j], "-noenv")) // { //turn OFF environmental effects for slow machines/video cards // gRequestParaboloidMap = FALSE; // } else if (!strcmp(argv[j], "-noaudio")) { gUseAudio = FALSE; } else if (!strcmp(argv[j], "-nosound")) // tends to be popular cmdline on Linux. { gUseAudio = FALSE; } else if (!strcmp(argv[j], "-noprobe")) { gProbeHardware = FALSE; } else if (!strcmp(argv[j], "-noquicktime")) { // Developers can log in faster if they don't load all the // quicktime dlls. gUseQuickTime = false; } else if (!strcmp(argv[j], "-nopreload")) { // Developers can log in faster if they don't decode sounds // or images on startup, ~5 seconds faster. gPreloadSounds = false; gPreloadImages = false; } else if (!strcmp(argv[j], "-purge")) { LLAppViewer::instance()->purgeCache(); } else if(!strcmp(argv[j], "-noinvlib")) { gRequestInventoryLibrary = FALSE; } else if (!strcmp(argv[j], "-log")) { gLogMessages = TRUE; continue; } else if (!strcmp(argv[j], "-logfile") && (++j < argc)) { // *NOTE: This buffer size is hard coded into scanf() below. char logfile[256]; // Flawfinder: ignore sscanf(argv[j], "%255s", logfile); // Flawfinder: ignore llinfos << "Setting log file to " << logfile << llendl; LLFile::remove(logfile); LLError::logToFile(logfile); } else if (!strcmp(argv[j], "-settings") && (++j < argc)) { gSettingsFileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, argv[j]); } else if (!strcmp(argv[j], "-setdefault") && (j + 2 < argc)) { std::string control_name; std::string control_value; j++; if (argv[j]) control_name = std::string(argv[j]); j++; if (argv[j]) control_value = std::string(argv[j]); // grab control name and value if (!control_name.empty()) { gCommandLineSettings[control_name] = control_value; } } else if (!strcmp(argv[j], "-set") && (j + 2 < argc)) { std::string control_name; std::string control_value; j++; if (argv[j]) control_name = std::string(argv[j]); j++; if (argv[j]) control_value = std::string(argv[j]); // grab control name and value if (!control_name.empty()) { gCommandLineForcedSettings[control_name] = control_value; } } else if (!strcmp(argv[j], "-login")) { if (j + 3 < argc) { j++; gCmdLineFirstName = argv[j]; j++; gCmdLineLastName = argv[j]; j++; gCmdLinePassword = argv[j]; } else { // only works if -login is last parameter on command line llerrs << "Not enough parameters to -login. Did you mean -loginuri?" << llendl; } } else if (!strcmp(argv[j], "-god")) { gGodConnect = TRUE; } else if (!strcmp(argv[j], "-noconsole")) { gUseConsole = FALSE; } else if (!strcmp(argv[j], "-safe")) { llinfos << "Setting viewer feature table to run in safe mode, resetting prefs" << llendl; gFeatureManagerp->setSafe(TRUE); } else if (!strcmp(argv[j], "-multiple")) { gMultipleViewersOK = TRUE; } else if (!strcmp(argv[j], "-nomultiple")) { gMultipleViewersOK = FALSE; } else if (!strcmp(argv[j], "-novoice")) { gDisableVoice = TRUE; } else if (!strcmp(argv[j], "-nothread")) { LLVFile::ALLOW_ASYNC = FALSE; llinfos << "Running VFS in nothread mode" << llendl; } // some programs don't respect the command line options in protocol handlers (I'm looking at you, Opera) // so this allows us to parse the URL straight off the command line without a "-url" paramater else if (LLURLDispatcher::isSLURL(argv[j]) || !strcmp(argv[j], "-url") && (++j < argc)) { std::string slurl = argv[j]; if (LLURLDispatcher::isSLURLCommand(slurl)) { LLStartUp::sSLURLCommand = slurl; } else { LLURLSimString::setString(slurl); } // *NOTE: After setting the url, bail. What can happen is // that someone can use IE (or potentially other browsers) // and do the rough equivalent of command injection and // steal passwords. Phoenix. SL-55321 } else if (!strcmp(argv[j], "-ignorepixeldepth")) { gIgnorePixelDepth = TRUE; } else if (!strcmp(argv[j], "-cooperative")) { S32 ms_to_yield = 0; if(++j < argc) { S32 rv = sscanf(argv[j], "%d", &ms_to_yield); if(0 == rv) { --j; } } else { --j; } gYieldMS = ms_to_yield; gYieldTime = TRUE; } else if (!strcmp(argv[j], "-no-verify-ssl-cert")) { gVerifySSLCert = false; } else if ( (!strcmp(argv[j], "--channel") || !strcmp(argv[j], "-channel")) && (++j < argc)) { gChannelName = argv[j]; } #if LL_DARWIN else if (!strncmp(argv[j], "-psn_", 5)) { // this is the Finder passing the process session number // we ignore this } #endif else if(!strncmp(argv[j], "-qa", 3)) { gQAMode = TRUE; } else { // DBC - Mac OS X passes some stuff by default on the command line (e.g. psn). // Second Life URLs are passed this way as well? llwarns << "Possible unknown keyword " << argv[j] << llendl; // print usage information llinfos << USAGE << llendl; // return 1; } } return 0; } bool send_url_to_other_instance(const std::string& url) { #if LL_WINDOWS wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars. mbstowcs(window_class, sWindowClass, 255); window_class[255] = 0; // Use the class instead of the window name. HWND other_window = FindWindow(window_class, NULL); if (other_window != NULL) { lldebugs << "Found other window with the name '" << gWindowTitle << "'" << llendl; 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); lldebugs << "SendMessage(WM_COPYDATA) to other window '" << gWindowTitle << "' returned " << msg_result << llendl; return true; } #endif return false; } //---------------------------------------------------------------------------- // LLAppViewer definition // Static members. // The single viewer app. LLAppViewer* LLAppViewer::sInstance = NULL; LLTextureCache* LLAppViewer::sTextureCache = NULL; LLWorkerThread* LLAppViewer::sImageDecodeThread = NULL; LLTextureFetch* LLAppViewer::sTextureFetch = NULL; LLAppViewer::LLAppViewer() : mMarkerFile(NULL), mLastExecFroze(false), mCrashBehavior(CRASH_BEHAVIOR_ASK), mReportedCrash(false), mNumSessions(0), mPurgeCache(false), mPurgeOnExit(false), mSecondInstance(false), mSavedFinalSnapshot(false), mQuitRequested(false), mLogoutRequestSent(false) { if(NULL != sInstance) { llerrs << "Oh no! An instance of LLAppViewer already exists! LLAppViewer is sort of like a singleton." << llendl; } sInstance = this; } LLAppViewer::~LLAppViewer() { // If we got to this destructor somehow, the app didn't hang. removeMarkerFile(); } bool LLAppViewer::tempStoreCommandOptions(int argc, char** argv) { gTempArgC = argc; gTempArgV = argv; return true; } bool LLAppViewer::init() { // *NOTE:Mani - LLCurl::initClass is not thread safe. // Called before threads are created. LLCurl::initClass(); initThreads(); initEarlyConfiguration(); // // Start of the application // // IMPORTANT! Do NOT put anything that will write // into the log files during normal startup until AFTER // we run the "program crashed last time" error handler below. // // Need to do this initialization before we do anything else, since anything // that touches files should really go through the lldir API gDirUtilp->initAppDirs("SecondLife"); initLogging(); // // OK to write stuff to logs now, we've now crash reported if necessary // // Set up some defaults... gSettingsFileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, DEFAULT_SETTINGS_FILE); gOldSettingsFileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, LEGACY_DEFAULT_SETTINGS_FILE); if (!initConfiguration()) return false; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // *FIX: The following code isn't grouped into functions yet. // // Write system information into the debug log (CPU, OS, etc.) // writeSystemInfo(); // Build a string representing the current version number. gCurrentVersion = llformat("%d.%d.%d", LL_VERSION_MAJOR, LL_VERSION_MINOR, LL_VERSION_PATCH ); // // Load the feature tables // llinfos << "Loading feature tables." << llendl; gFeatureManagerp->loadFeatureTables(); gFeatureManagerp->initCPUFeatureMasks(); // Merge with the command line overrides gSavedSettings.applyOverrides(gCommandLineSettings); // Need to do this before calling parseAlerts gUICtrlFactory = new LLViewerUICtrlFactory(); // Pre-load alerts.xml to define the warnings settings (always loads from skins/xui/en-us/) // Do this *before* loading the settings file LLAlertDialog::parseAlerts("alerts.xml", &gSavedSettings, TRUE); // Overwrite default settings with user settings llinfos << "Loading configuration file " << gSettingsFileName << llendl; if (0 == gSavedSettings.loadFromFile(gSettingsFileName)) { llinfos << "Failed to load settings from " << gSettingsFileName << llendl; llinfos << "Loading legacy settings from " << gOldSettingsFileName << llendl; gSavedSettings.loadFromFileLegacy(gOldSettingsFileName); } // need to do this here - need to have initialized global settings first LLString nextLoginLocation = gSavedSettings.getString( "NextLoginLocation" ); if ( nextLoginLocation.length() ) { LLURLSimString::setString( nextLoginLocation.c_str() ); }; // Merge with the command line overrides gSavedSettings.applyOverrides(gCommandLineForcedSettings); gLastRunVersion = gSavedSettings.getString("LastRunVersion"); fixup_settings(); // Get the single value from the crash settings file, if it exists std::string crash_settings_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE); gCrashSettings.loadFromFile(crash_settings_filename.c_str()); ///////////////////////////////////////////////// // OS-specific login dialogs ///////////////////////////////////////////////// #if LL_WINDOWS /* // Display initial login screen, comes up quickly. JC { LLSplashScreen::hide(); INT_PTR result = DialogBox(hInstance, L"CONNECTBOX", NULL, login_dialog_func); if (result < 0) { llwarns << "Connect dialog box failed, returned " << result << llendl; return 1; } // success, result contains which button user clicked llinfos << "Connect dialog box clicked " << result << llendl; LLSplashScreen::show(); } */ #endif // track number of times that app has run mNumSessions = gSavedSettings.getS32("NumSessions"); mNumSessions++; gSavedSettings.setS32("NumSessions", mNumSessions); gSavedSettings.setString("HelpLastVisitedURL",gSavedSettings.getString("HelpHomeURL")); if (gSavedSettings.getBOOL("VerboseLogs")) { LLError::setPrintLocation(true); } #if !LL_RELEASE_FOR_DOWNLOAD if (gGridChoice == GRID_INFO_NONE) { // Development version: load last server choice by default (overridden by cmd line args) S32 server = gSavedSettings.getS32("ServerChoice"); if (server != 0) gGridChoice = (EGridInfo)llclamp(server, 0, (S32)GRID_INFO_COUNT - 1); if (server == GRID_INFO_OTHER) { LLString custom_server = gSavedSettings.getString("CustomServer"); if (custom_server.empty()) { snprintf(gGridName, MAX_STRING, "none"); /* Flawfinder: ignore */ } else { snprintf(gGridName, MAX_STRING, "%s", custom_server.c_str()); /* Flawfinder: ignore */ } } } #endif if (gGridChoice == GRID_INFO_NONE) { gGridChoice = GridDefaultChoice; } // Load art UUID information, don't require these strings to be declared in code. LLString viewer_art_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"viewerart.xml"); llinfos << "Loading art table from " << viewer_art_filename << llendl; gViewerArt.loadFromFile(viewer_art_filename.c_str(), FALSE); LLString textures_filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "textures", "textures.xml"); llinfos << "Loading art table from " << textures_filename << llendl; gViewerArt.loadFromFile(textures_filename.c_str(), FALSE); LLString colors_base_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "colors_base.xml"); llinfos << "Loading base colors from " << colors_base_filename << llendl; gColors.loadFromFile(colors_base_filename.c_str(), FALSE, TYPE_COL4U); // Load overrides from user colors file LLString user_colors_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "colors.xml"); llinfos << "Loading user colors from " << user_colors_filename << llendl; if (gColors.loadFromFile(user_colors_filename.c_str(), FALSE, TYPE_COL4U) == 0) { llinfos << "Failed to load user colors from " << user_colors_filename << llendl; LLString user_legacy_colors_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "colors.ini"); llinfos << "Loading legacy colors from " << user_legacy_colors_filename << llendl; gColors.loadFromFileLegacy(user_legacy_colors_filename.c_str(), FALSE, TYPE_COL4U); } // Widget construction depends on LLUI being initialized LLUI::initClass(&gSavedSettings, &gColors, &gViewerArt, &gImageList, ui_audio_callback, &LLUI::sGLScaleFactor); LLWeb::initClass(); // do this after LLUI gUICtrlFactory->setupPaths(); // update paths with correct language set ///////////////////////////////////////////////// // // Load settings files // // LLGroupMgr::parseRoleActions("role_actions.xml"); LLAgent::parseTeleportMessages("teleport_strings.xml"); mCrashBehavior = gCrashSettings.getS32(CRASH_BEHAVIOR_SETTING); LLVectorPerformanceOptions::initClass(); // Move certain saved settings into global variables for speed saved_settings_to_globals(); // Find partition serial number (Windows) or hardware serial (Mac) mSerialNumber = generateSerialNumber(); if(false == initHardwareTest()) { // Early out from user choice. return false; } // Always fetch the Ethernet MAC address, needed both for login // and password load. LLUUID::getNodeID(gMACAddress); // Prepare for out-of-memory situations, during which we will crash on // purpose and save a dump. #if LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP MemSetErrorHandler(first_mem_error_handler); #endif // LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP gViewerStats = new LLViewerStats(); // // Initialize the VFS, and gracefully handle initialization errors // if (!initCache()) { std::ostringstream msg; msg << gSecondLife << " is unable to access a file that it needs.\n" "\n" "This can be because you somehow have multiple copies running, " "or your system incorrectly thinks a file is open. " "If this message persists, restart your computer and try again. " "If it continues to persist, you may need to completely uninstall " << gSecondLife << " and reinstall it."; OSMessageBox( msg.str().c_str(), NULL, OSMB_OK); return 1; } #if LL_DARWIN // Display the release notes for the current version if(!gHideLinks && gCurrentVersion != gLastRunVersion) { // Current version and last run version don't match exactly. Display the release notes. DisplayReleaseNotes(); } #endif // // Initialize the window // initWindow(); #if LL_WINDOWS && LL_LCD_COMPILE // start up an LCD window on a logitech keyboard, if there is one HINSTANCE hInstance = GetModuleHandle(NULL); gLcdScreen = new llLCD(hInstance); CreateLCDDebugWindows(); #endif gGLManager.getGLInfo(gDebugInfo); llinfos << gGLManager.getGLInfoString() << llendl; //load key settings bind_keyboard_functions(); // Load Default bindings if (!gViewerKeyboard.loadBindings(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"keys.ini").c_str())) { llerrs << "Unable to open keys.ini" << llendl; } // Load Custom bindings (override defaults) gViewerKeyboard.loadBindings(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"custom_keys.ini").c_str()); // Calculate the digest for the executable (takes < 90ms on a fast machine). FILE* app_file = LLFile::fopen( gDirUtilp->getExecutablePathAndName().c_str(), "rb" ); /* Flawfinder: ignore */ if( app_file ) { LLMD5 app_md5; app_md5.update( app_file ); // Automatically closes the file app_md5.finalize(); app_md5.raw_digest( gViewerDigest.mData ); } llinfos << "Viewer Digest: " << gViewerDigest << llendl; // If we don't have the right GL requirements, exit. // BUG: This should just be changed to a generic GL "Not good enough" flag if (!gGLManager.mHasMultitexture && !gNoRender) { std::ostringstream msg; msg << "You do not appear to have the proper hardware requirements " "for " << gSecondLife << ". " << gSecondLife << " requires an OpenGL graphics " "card that has multitexture support. If this is the case, " "you may want to make sure that you have the latest drivers for " "your graphics card, and service packs and patches for your " "operating system.\n" "If you continue to have problems, please go to: " "www.secondlife.com/support "; OSMessageBox( msg.str().c_str(), NULL, OSMB_OK); return 0; } // Save the current version to the prefs file gSavedSettings.setString("LastRunVersion", gCurrentVersion); gSimLastTime = gRenderStartTime.getElapsedTimeF32(); gSimFrames = (F32)gFrameCount; return true; } bool LLAppViewer::mainLoop() { //------------------------------------------- // Run main loop until time to quit //------------------------------------------- // Create IO Pump to use for HTTP Requests. gServicePump = new LLPumpIO(gAPRPoolp); LLHTTPClient::setPump(*gServicePump); LLHTTPClient::setCABundle(gDirUtilp->getCAFile()); // initialize voice stuff here gLocalSpeakerMgr = new LLLocalSpeakerMgr(); gActiveChannelSpeakerMgr = new LLActiveSpeakerMgr(); LLVoiceChannel::initClass(); LLVoiceClient::init(gServicePump); LLMemType mt1(LLMemType::MTYPE_MAIN); LLTimer frameTimer,idleTimer; LLTimer debugTime; // Handle messages while (!LLApp::isExiting()) { LLFastTimer::reset(); // Should be outside of any timer instances { LLFastTimer t(LLFastTimer::FTM_FRAME); { LLFastTimer t2(LLFastTimer::FTM_MESSAGES); #if LL_WINDOWS if (!LLWinDebug::setupExceptionHandler()) { llwarns << " Someone took over my exception handler (post messagehandling)!" << llendl; } #endif gViewerWindow->mWindow->gatherInput(); } #if 1 && !RELEASE_FOR_DOWNLOAD // once per second debug info if (debugTime.getElapsedTimeF32() > 1.f) { debugTime.reset(); } #endif if (!LLApp::isExiting()) { // Scan keyboard for movement keys. Command keys and typing // are handled by windows callbacks. Don't do this until we're // done initializing. JC if (gViewerWindow->mWindow->getVisible() && gViewerWindow->getActive() && !gViewerWindow->mWindow->getMinimized() && LLStartUp::getStartupState() == STATE_STARTED && !gViewerWindow->getShowProgress() && !gFocusMgr.focusLocked()) { gKeyboard->scanKeyboard(); LLViewerJoystick::scanJoystick(); } // Update state based on messages, user input, object idle. { LLFastTimer t3(LLFastTimer::FTM_IDLE); idle(); LLCurl::process(); // this pump is necessary to make the login screen show up gServicePump->pump(); gServicePump->callback(); } if (gDoDisconnect && (LLStartUp::getStartupState() == STATE_STARTED)) { saveFinalSnapshot(); disconnectViewer(); } // Render scene. if (!LLApp::isExiting()) { display(); LLFloaterSnapshot::update(); // take snapshots #if !LL_SOLARIS if (gbCapturing) { gMovieMaker.Snap(); } #endif #if LL_WINDOWS && LL_LCD_COMPILE // update LCD Screen gLcdScreen->UpdateDisplay(); #endif } } // Sleep and run background threads { LLFastTimer t2(LLFastTimer::FTM_SLEEP); bool run_multiple_threads = gSavedSettings.getBOOL("RunMultipleThreads"); // yield some time to the os based on command line option if(gYieldTime) { ms_sleep(gYieldMS); } // yield cooperatively when not running as foreground window if ( gNoRender || !gViewerWindow->mWindow->getVisible() || !gFocusMgr.getAppHasFocus()) { // Sleep if we're not rendering, or the window is minimized. S32 milliseconds_to_sleep = llclamp(gSavedSettings.getS32("BackgroundYieldTime"), 0, 1000); // don't sleep when BackgroundYieldTime set to 0, since this will still yield to other threads // of equal priority on Windows if (milliseconds_to_sleep > 0) { ms_sleep(milliseconds_to_sleep); // also pause worker threads during this wait period LLAppViewer::getTextureCache()->pause(); LLAppViewer::getImageDecodeThread()->pause(); } } if (gRandomizeFramerate) { ms_sleep(rand() % 200); } if (gPeriodicSlowFrame && (gFrameCount % 10 == 0)) { llinfos << "Periodic slow frame - sleeping 500 ms" << llendl; ms_sleep(500); } const F64 min_frame_time = 0.0; //(.0333 - .0010); // max video frame rate = 30 fps const F64 min_idle_time = 0.0; //(.0010); // min idle time = 1 ms const F64 max_idle_time = run_multiple_threads ? min_idle_time : .005; // 5 ms idleTimer.reset(); while(1) { S32 work_pending = 0; S32 io_pending = 0; work_pending += LLAppViewer::getTextureCache()->update(1); // unpauses the texture cache thread work_pending += LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread work_pending += LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread io_pending += LLVFSThread::updateClass(1); io_pending += LLLFSThread::updateClass(1); if (io_pending > 1000) { ms_sleep(llmin(io_pending/100,100)); // give the vfs some time to catch up } F64 frame_time = frameTimer.getElapsedTimeF64(); F64 idle_time = idleTimer.getElapsedTimeF64(); if (frame_time >= min_frame_time && idle_time >= min_idle_time && (!work_pending || idle_time >= max_idle_time)) { break; } } frameTimer.reset(); // Prevent the worker threads from running while rendering. // if (LLThread::processorCount()==1) //pause() should only be required when on a single processor client... if (run_multiple_threads == FALSE) { LLAppViewer::getTextureCache()->pause(); LLAppViewer::getImageDecodeThread()->pause(); // LLAppViewer::getTextureFetch()->pause(); // Don't pause the fetch (IO) thread } //LLVFSThread::sLocal->pause(); // Prevent the VFS thread from running while rendering. //LLLFSThread::sLocal->pause(); // Prevent the LFS thread from running while rendering. } } } // Save snapshot for next time, if we made it through initialization if (STATE_STARTED == LLStartUp::getStartupState()) { saveFinalSnapshot(); } delete gServicePump; llinfos << "Exiting main_loop" << llendflush; return true; } bool LLAppViewer::cleanup() { //flag all elements as needing to be destroyed immediately // to ensure shutdown order LLMortician::setZealous(TRUE); LLVoiceClient::terminate(); disconnectViewer(); llinfos << "Viewer disconnected" << llendflush; display_cleanup(); release_start_screen(); // just in case LLError::logToFixedBuffer(NULL); llinfos << "Cleaning Up" << llendflush; LLKeyframeDataCache::clear(); // Must clean up texture references before viewer window is destroyed. LLHUDObject::cleanupHUDObjects(); llinfos << "HUD Objects cleaned up" << llendflush; // End TransferManager before deleting systems it depends on (Audio, VFS, AssetStorage) #if 0 // this seems to get us stuck in an infinite loop... gTransferManager.cleanup(); #endif // Clean up map data storage delete gWorldMap; gWorldMap = NULL; delete gHUDManager; gHUDManager = NULL; delete gToolMgr; gToolMgr = NULL; delete gAssetStorage; gAssetStorage = NULL; LLPolyMesh::freeAllMeshes(); delete gCacheName; gCacheName = NULL; delete gGlobalEconomy; gGlobalEconomy = NULL; delete gActiveChannelSpeakerMgr; gActiveChannelSpeakerMgr = NULL; delete gLocalSpeakerMgr; gLocalSpeakerMgr = NULL; LLNotifyBox::cleanup(); llinfos << "Global stuff deleted" << llendflush; #if !LL_RELEASE_FOR_DOWNLOAD if (gAudiop) { gAudiop->shutdown(); } #else // This hack exists because fmod likes to occasionally hang forever // when shutting down for no apparent reason. llwarns << "Hack, skipping audio engine cleanup" << llendflush; #endif // moved to main application shutdown for now because it's non-trivial and only needs to be done once // (even though it goes against the media framework design) LLMediaEngine::cleanupClass(); #if LL_QUICKTIME_ENABLED if (gQuickTimeInitialized) { // clean up media stuff llinfos << "Cleaning up QuickTime" << llendl; ExitMovies (); #if LL_WINDOWS // Only necessary/available on Windows. TerminateQTML (); #endif } llinfos << "Quicktime cleaned up" << llendflush; #endif #if LL_GSTREAMER_ENABLED llinfos << "Cleaning up GStreamer" << llendl; UnloadGStreamer(); llinfos << "GStreamer cleaned up" << llendflush; #endif llinfos << "Cleaning up feature manager" << llendflush; delete gFeatureManagerp; gFeatureManagerp = NULL; // Patch up settings for next time // Must do this before we delete the viewer window, // such that we can suck rectangle information out of // it. cleanupSavedSettings(); llinfos << "Settings patched up" << llendflush; delete gAudiop; gAudiop = NULL; // delete some of the files left around in the cache. removeCacheFiles("*.wav"); removeCacheFiles("*.tmp"); removeCacheFiles("*.lso"); removeCacheFiles("*.out"); removeCacheFiles("*.dsf"); removeCacheFiles("*.bodypart"); removeCacheFiles("*.clothing"); llinfos << "Cache files removed" << llendflush; cleanup_menus(); // Wait for any pending VFS IO while (1) { S32 pending = LLVFSThread::updateClass(0); pending += LLLFSThread::updateClass(0); if (!pending) { break; } llinfos << "Waiting for pending IO to finish: " << pending << llendflush; ms_sleep(100); } llinfos << "Shutting down." << llendflush; // Destroy Windows(R) window, and make sure we're not fullscreen // This may generate window reshape and activation events. // Therefore must do this before destroying the message system. delete gViewerWindow; gViewerWindow = NULL; llinfos << "ViewerWindow deleted" << llendflush; // viewer UI relies on keyboard so keep it aound until viewer UI isa gone delete gKeyboard; gKeyboard = NULL; // Clean up selection managers after UI is destroyed, as UI // may be observing them. LLSelectMgr::cleanupGlobals(); LLViewerObject::cleanupVOClasses(); LLTracker::cleanupInstance(); #if LL_LIBXUL_ENABLED // this must be done after floater cleanup (delete gViewerWindow) since // floaters potentially need the manager to destroy their contents. LLMozLib::getInstance()->reset(); #endif // *FIX: This is handled in LLAppViewerWin32::cleanup(). // I'm keeping the comment to remember its order in cleanup, // in case of unforseen dependency. //#if LL_WINDOWS // gDXHardware.cleanup(); //#endif // LL_WINDOWS #if LL_WINDOWS && LL_LCD_COMPILE // shut down the LCD window on a logitech keyboard, if there is one delete gLcdScreen; gLcdScreen = NULL; #endif if (!gVolumeMgr->cleanup()) { llwarns << "Remaining references in the volume manager!" << llendflush; } LLViewerParcelMgr::cleanupGlobals(); delete gViewerStats; gViewerStats = NULL; //end_messaging_system(); LLFollowCamMgr::cleanupClass(); LLVolumeMgr::cleanupClass(); LLWorldMapView::cleanupClass(); LLUI::cleanupClass(); // // Shut down the VFS's AFTER the decode manager cleans up (since it cleans up vfiles). // Also after viewerwindow is deleted, since it may have image pointers (which have vfiles) // Also after shutting down the messaging system since it has VFS dependencies // LLVFile::cleanupClass(); llinfos << "VFS cleaned up" << llendflush; // Store the time of our current logoff gSavedPerAccountSettings.setU32("LastLogoff", time_corrected()); // Must do this after all panels have been deleted because panels that have persistent rects // save their rects on delete. gSavedSettings.saveToFile(gSettingsFileName, TRUE); if (!gPerAccountSettingsFileName.empty()) { gSavedPerAccountSettings.saveToFile(gPerAccountSettingsFileName, TRUE); } llinfos << "Saved settings" << llendflush; std::string crash_settings_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE); // save all settings, even if equals defaults gCrashSettings.saveToFile(crash_settings_filename.c_str(), FALSE); delete gUICtrlFactory; gUICtrlFactory = NULL; gSavedSettings.cleanup(); gViewerArt.cleanup(); gColors.cleanup(); gCrashSettings.cleanup(); if (gMuteListp) { // save mute list gMuteListp->cache(gAgent.getID()); delete gMuteListp; gMuteListp = NULL; } if (mPurgeOnExit) { llinfos << "Purging all cache files on exit" << llendflush; char mask[LL_MAX_PATH]; /* Flawfinder: ignore */ snprintf(mask, LL_MAX_PATH, "%s*.*", gDirUtilp->getDirDelimiter().c_str()); /* Flawfinder: ignore */ gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,"").c_str(),mask); } removeMarkerFile(); // Any crashes from here on we'll just have to ignore closeDebug(); // Let threads finish LLTimer idleTimer; idleTimer.reset(); const F64 max_idle_time = 5.f; // 5 seconds while(1) { S32 pending = 0; pending += LLAppViewer::getTextureCache()->update(1); // unpauses the worker thread pending += LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread pending += LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread pending += LLVFSThread::updateClass(0); pending += LLLFSThread::updateClass(0); F64 idle_time = idleTimer.getElapsedTimeF64(); if (!pending || idle_time >= max_idle_time) { llwarns << "Quitting with pending background tasks." << llendl; break; } } // Delete workers first // shotdown all worker threads before deleting them in case of co-dependencies sTextureCache->shutdown(); sTextureFetch->shutdown(); sImageDecodeThread->shutdown(); delete sTextureCache; sTextureCache = NULL; delete sTextureFetch; sTextureFetch = NULL; delete sImageDecodeThread; sImageDecodeThread = NULL; gImageList.shutdown(); // shutdown again in case a callback added something // This should eventually be done in LLAppViewer LLImageJ2C::closeDSO(); LLImageFormatted::cleanupClass(); LLVFSThread::cleanupClass(); LLLFSThread::cleanupClass(); llinfos << "VFS Thread finished" << llendflush; #ifndef LL_RELEASE_FOR_DOWNLOAD llinfos << "Auditing VFS" << llendl; gVFS->audit(); #endif // For safety, the LLVFS has to be deleted *after* LLVFSThread. This should be cleaned up. // (LLVFS doesn't know about LLVFSThread so can't kill pending requests) -Steve delete gStaticVFS; gStaticVFS = NULL; delete gVFS; gVFS = NULL; end_messaging_system(); // *NOTE:Mani - The following call is not thread safe. LLCurl::cleanup(); // If we're exiting to launch an URL, do that here so the screen // is at the right resolution before we launch IE. if (!gLaunchFileOnQuit.empty()) { #if LL_WINDOWS // Indicate an application is starting. SetCursor(LoadCursor(NULL, IDC_WAIT)); #endif // HACK: Attempt to wait until the screen res. switch is complete. ms_sleep(1000); LLWeb::loadURLExternal( gLaunchFileOnQuit ); } llinfos << "Goodbye" << llendflush; // return 0; return true; } bool LLAppViewer::initEarlyConfiguration() { // *FIX: globals - This method sets a bunch of globals early in the init process. int argc = gTempArgC; char** argv = gTempArgV; // HACK! We REALLY want to know what grid they were trying to connect to if they // crashed hard. // So we walk through the command line args ONLY looking for the // userserver arguments first. And we don't do ANYTHING but set // the gGridName (which gets passed to the crash reporter). // We're assuming that they're trying to log into the same grid as last // time, which seems fairly reasonable. snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GridDefaultChoice].mName); // Flawfinder: ignore S32 j; for (j = 1; j < argc; j++) { if (!strcmp(argv[j], "--aditi")) { snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GRID_INFO_ADITI].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--agni")) { snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GRID_INFO_AGNI].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--dmz")) { snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GRID_INFO_DMZ].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--siva")) { snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GRID_INFO_SIVA].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--shakti")) { sprintf(gGridName,"%s", gGridInfo[GRID_INFO_SHAKTI].mName); } else if (!strcmp(argv[j], "--durga")) { snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GRID_INFO_DURGA].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--soma")) { snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GRID_INFO_SOMA].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--ganga")) { snprintf(gGridName, MAX_STRING, "%s", gGridInfo[GRID_INFO_GANGA].mName); // Flawfinder: ignore } else if (!strcmp(argv[j], "--vaak")) { sprintf(gGridName,"%s", gGridInfo[GRID_INFO_VAAK].mName); } else if (!strcmp(argv[j], "--uma")) { sprintf(gGridName,"%s", gGridInfo[GRID_INFO_UMA].mName); } else if (!strcmp(argv[j], "-user") && (++j < argc)) { if (!strcmp(argv[j], "-")) { snprintf(gGridName, MAX_STRING, "%s", LOOPBACK_ADDRESS_STRING); // Flawfinder: ignore } else { snprintf(gGridName, MAX_STRING, "%s", argv[j]); // Flawfinder: ignore } } else if (!strcmp(argv[j], "-multiple")) { // Hack to detect -multiple so we can disable the marker file check (which will always fail) gMultipleViewersOK = TRUE; } else if (!strcmp(argv[j], "-novoice")) { // May need to know this early also gDisableVoice = TRUE; } else if (!strcmp(argv[j], "-url") && (++j < argc)) { LLURLSimString::setString(argv[j]); } } return true; } bool LLAppViewer::initThreads() { #if MEM_TRACK_MEM static const bool enable_threads = false; #else static const bool enable_threads = true; #endif LLVFSThread::initClass(enable_threads && true); LLLFSThread::initClass(enable_threads && true); // Image decoding LLAppViewer::sImageDecodeThread = new LLWorkerThread("ImageDecode", enable_threads && true); LLAppViewer::sTextureCache = new LLTextureCache(enable_threads && true); LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), enable_threads && false); LLImageWorker::initClass(LLAppViewer::getImageDecodeThread()); LLImageJ2C::openDSO(); // *FIX: no error handling here! return true; } void errorCallback(const std::string &error_string) { #ifndef LL_RELEASE_FOR_DOWNLOAD OSMessageBox(error_string.c_str(), "Fatal Error", OSMB_OK); #endif LLError::crashAndLoop(error_string); } bool LLAppViewer::initLogging() { // // Set up logging defaults for the viewer // LLError::initForApplication( gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); LLError::setFatalFunction(errorCallback); // Remove the last ".old" log file. std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.old"); LLFile::remove(old_log_file.c_str()); // Rename current log file to ".old" std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.log"); LLFile::rename(log_file.c_str(), old_log_file.c_str()); // Set the log file to SecondLife.log LLError::logToFile(log_file); // *FIX:Mani no error handling here! return true; } bool LLAppViewer::initConfiguration() { // Ye olde parse_args()... if(!doConfigFromCommandLine()) { return false; } // XUI:translate gSecondLife = "Second Life"; // Read skin/branding settings if specified. if (! gDirUtilp->getSkinDir().empty() ) { std::string skin_def_file = gDirUtilp->getExpandedFilename(LL_PATH_TOP_SKIN, "skin.xml"); LLXmlTree skin_def_tree; if (!skin_def_tree.parseFile(skin_def_file)) { llerrs << "Failed to parse skin definition." << llendl; } LLXmlTreeNode* rootp = skin_def_tree.getRoot(); LLXmlTreeNode* disabled_message_node = rootp->getChildByName("disabled_message"); if (disabled_message_node) { gDisabledMessage = disabled_message_node->getContents(); } static LLStdStringHandle hide_links_string = LLXmlTree::addAttributeString("hide_links"); rootp->getFastAttributeBOOL(hide_links_string, gHideLinks); // Legacy string. This flag really meant we didn't want to expose references to "Second Life". // Just set gHideLinks instead. static LLStdStringHandle silent_string = LLXmlTree::addAttributeString("silent_update"); BOOL silent_update; rootp->getFastAttributeBOOL(silent_string, silent_update); gHideLinks = (gHideLinks || silent_update); } #if LL_DARWIN // Initialize apple menubar and various callbacks init_apple_menu(gSecondLife.c_str()); #if __ppc__ // If the CPU doesn't have Altivec (i.e. it's not at least a G4), don't go any further. // Only test PowerPC - all Intel Macs have SSE. if(!gSysCPU.hasAltivec()) { std::ostringstream msg; msg << gSecondLife << " requires a processor with AltiVec (G4 or later)."; OSMessageBox( msg.str().c_str(), NULL, OSMB_OK); removeMarkerFile(); return false; } #endif #endif // LL_DARWIN // Display splash screen. Must be after above check for previous // crash as this dialog is always frontmost. std::ostringstream splash_msg; splash_msg << "Loading " << gSecondLife << "..."; LLSplashScreen::show(); LLSplashScreen::update(splash_msg.str().c_str()); LLVolumeMgr::initClass(); // Initialize the feature manager // The feature manager is responsible for determining what features // are turned on/off in the app. gFeatureManagerp = new LLFeatureManager; gStartTime = totalTime(); //////////////////////////////////////// // // Process ini files // // declare all possible setting variables declare_settings(); #if !LL_RELEASE_FOR_DOWNLOAD // only write the defaults for non-release builds! gSavedSettings.saveToFile(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings_default.xml").c_str(), FALSE); #endif // // Set the name of the window // #if LL_RELEASE_FOR_DOWNLOAD gWindowTitle = gSecondLife; #elif LL_DEBUG gWindowTitle = gSecondLife + LLString(" [DEBUG] ") + gArgs; #else gWindowTitle = gSecondLife + LLString(" ") + gArgs; #endif LLString::truncate(gWindowTitle, 255); if (!gMultipleViewersOK) { // // Check for another instance of the app running // //RN: if we received a URL, hand it off to the existing instance // don't call anotherInstanceRunning() when doing URL handoff, as // it relies on checking a marker file which will not work when running // out of different directories std::string slurl; if (!LLStartUp::sSLURLCommand.empty()) { slurl = LLStartUp::sSLURLCommand; } else if (LLURLSimString::parse()) { slurl = LLURLSimString::getURL(); } if (!slurl.empty()) { if (send_url_to_other_instance(slurl)) { // successfully handed off URL to existing instance, exit return false; } } mSecondInstance = anotherInstanceRunning(); if (mSecondInstance) { std::ostringstream msg; msg << gSecondLife << " is already running.\n" "\n" "Check your task bar for a minimized copy of the program.\n" "If this message persists, restart your computer.", OSMessageBox( msg.str().c_str(), NULL, OSMB_OK); return false; } initMarkerFile(); #if LL_SEND_CRASH_REPORTS if (gLastExecFroze) { llinfos << "Last execution froze, requesting to send crash report." << llendl; // // Pop up a freeze or crash warning dialog // std::ostringstream msg; msg << gSecondLife << " appears to have frozen or crashed on the previous run.\n" << "Would you like to send a crash report?"; std::string alert; alert = gSecondLife; alert += " Alert"; S32 choice = OSMessageBox(msg.str().c_str(), alert.c_str(), OSMB_YESNO); if (OSBTN_YES == choice) { llinfos << "Sending crash report." << llendl; removeMarkerFile(); #if LL_WINDOWS std::string exe_path = gDirUtilp->getAppRODataDir(); exe_path += gDirUtilp->getDirDelimiter(); exe_path += "win_crash_logger.exe"; std::string arg_string = "-previous -user "; arg_string += gGridName; arg_string += " -name \""; arg_string += gSecondLife; arg_string += "\""; // Spawn crash logger. // NEEDS to wait until completion, otherwise log files will get smashed. _spawnl(_P_WAIT, exe_path.c_str(), exe_path.c_str(), arg_string.c_str(), NULL); #elif LL_DARWIN std::string command_str; command_str = "crashreporter.app/Contents/MacOS/crashreporter "; command_str += "-previous -user "; command_str += gGridName; // XXX -- We need to exit fullscreen mode for this to work. // XXX -- system() also doesn't wait for completion. Hmm... system(command_str.c_str()); /* Flawfinder: Ignore */ #elif LL_LINUX || LL_SOLARIS std::string cmd =gDirUtilp->getAppRODataDir(); cmd += gDirUtilp->getDirDelimiter(); #if LL_LINUX cmd += "linux-crash-logger.bin"; #else // LL_SOLARIS cmd += "bin/solaris-crash-logger"; #endif char* const cmdargv[] = {(char*)cmd.c_str(), (char*)"-previous", (char*)"-user", (char*)gGridName, (char*)"-name", (char*)gSecondLife.c_str(), NULL}; pid_t pid = fork(); if (pid == 0) { // child execv(cmd.c_str(), cmdargv); /* Flawfinder: Ignore */ llwarns << "execv failure when trying to start " << cmd << llendl; _exit(1); // avoid atexit() } else { if (pid > 0) { // wait for child proc to die int childExitStatus; waitpid(pid, &childExitStatus, 0); } else { llwarns << "fork failure." << llendl; } } #endif } else { llinfos << "Not sending crash report." << llendl; } } #endif // #if LL_SEND_CRASH_REPORTS } else { mSecondInstance = anotherInstanceRunning(); if (mSecondInstance) { gDisableVoice = TRUE; /* Don't start another instance if using -multiple //RN: if we received a URL, hand it off to the existing instance if (LLURLSimString::parse()) { LLURLSimString::send_to_other_instance(); return 1; } */ } initMarkerFile(); } return true; // Config was successful. } bool LLAppViewer::doConfigFromCommandLine() { // *FIX: This is what parse args used to do, minus the arg reading part. // Now the arg parsing is handled by LLApp::parseCommandOptions() and this // method need only interpret settings. Perhaps some day interested parties // can ask an app about a setting rather than have the app set // a gazzillion globals. ///////////////////////////////////////// // // Process command line arguments // S32 args_result = 0; #if LL_DARWIN { // On the Mac, read in arguments.txt (if it exists) and process it for additional arguments. LLString args; if(_read_file_into_string(args, "arguments.txt")) /* Flawfinder: ignore*/ { // The arguments file exists. // It should consist of command line arguments separated by newlines. // Split it into individual arguments and build a fake argv[] to pass to parse_args. std::vector arglist; arglist.push_back("newview"); llinfos << "Reading additional command line arguments from arguments.txt..." << llendl; typedef boost::tokenizer > tokenizer; boost::escaped_list_separator sep("\\", "\r\n ", "\"'"); tokenizer tokens(args, sep); tokenizer::iterator token_iter; for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) { llinfos << "argument: '" << (token_iter->c_str()) << "'" << llendl; arglist.push_back(*token_iter); } char **fakeargv = new char*[arglist.size()]; int i; for(i=0; i < arglist.size(); i++) fakeargv[i] = const_cast(arglist[i].c_str()); args_result = parse_args(arglist.size(), fakeargv); delete[] fakeargv; } // Get the user's preferred language string based on the Mac OS localization mechanism. // To add a new localization: // go to the "Resources" section of the project // get info on "language.txt" // in the "General" tab, click the "Add Localization" button // create a new localization for the language you're adding // set the contents of the new localization of the file to the string corresponding to our localization // (i.e. "en-us", "ja", etc. Use the existing ones as a guide.) CFURLRef url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("language"), CFSTR("txt"), NULL); char path[MAX_PATH]; if(CFURLGetFileSystemRepresentation(url, false, (UInt8 *)path, sizeof(path))) { LLString lang; if(_read_file_into_string(lang, path)) /* Flawfinder: ignore*/ { gCommandLineForcedSettings["SystemLanguage"] = lang; } } CFRelease(url); } #endif int argc = gTempArgC; char** argv = gTempArgV; // // Parse the command line arguments // args_result |= parse_args(argc, argv); if (args_result) { removeMarkerFile(); return false; } if (!strcmp(gGridName, gGridInfo[GRID_INFO_AGNI].mName)) { gInProductionGrid = TRUE; } return true; } bool LLAppViewer::initWindow() { llinfos << "Initializing window..." << llendl; // store setting in a global for easy access and modification gNoRender = gSavedSettings.getBOOL("DisableRendering"); // Hide the splash screen LLSplashScreen::hide(); // HACK: Need a non-const char * for stupid window name (propagated deep down) char window_title_str[256]; /* Flawfinder: ignore */ strncpy(window_title_str, gWindowTitle.c_str(), sizeof(window_title_str) - 1); /* Flawfinder: ignore */ window_title_str[sizeof(window_title_str) - 1] = '\0'; // always start windowed gViewerWindow = new LLViewerWindow(window_title_str, "Second Life", gSavedSettings.getS32("WindowX"), gSavedSettings.getS32("WindowY"), gSavedSettings.getS32("WindowWidth"), gSavedSettings.getS32("WindowHeight"), FALSE, gIgnorePixelDepth); if (gSavedSettings.getBOOL("FullScreen")) { gViewerWindow->toggleFullscreen(FALSE); // request to go full screen... which will be delayed until login } if (gSavedSettings.getBOOL("WindowMaximized")) { gViewerWindow->mWindow->maximize(); gViewerWindow->getWindow()->setNativeAspectRatio(gSavedSettings.getF32("FullScreenAspectRatio")); } LLUI::sWindow = gViewerWindow->getWindow(); LLAlertDialog::parseAlerts("alerts.xml"); LLNotifyBox::parseNotify("notify.xml"); LLMediaEngine::initClass(); // // Clean up the feature manager lookup table - settings were updated // in the LLViewerWindow constructor // gFeatureManagerp->cleanupFeatureTables(); // Show watch cursor gViewerWindow->setCursor(UI_CURSOR_WAIT); // Finish view initialization gViewerWindow->initBase(); // show viewer window gViewerWindow->mWindow->show(); return true; } void LLAppViewer::closeDebug() { std::string debug_filename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log"); llinfos << "Opening debug file " << debug_filename << llendl; std::ofstream out_file(debug_filename.c_str()); LLSDSerialize::toPrettyXML(gDebugInfo, out_file); out_file.close(); } void LLAppViewer::cleanupSavedSettings() { gSavedSettings.setBOOL("MouseSun", FALSE); gSavedSettings.setBOOL("FlyBtnState", FALSE); gSavedSettings.setBOOL("FirstPersonBtnState", FALSE); gSavedSettings.setBOOL("ThirdPersonBtnState", TRUE); gSavedSettings.setBOOL("BuildBtnState", FALSE); gSavedSettings.setBOOL("UseEnergy", TRUE); // force toggle to turn off, since sends message to simulator gSavedSettings.setBOOL("DebugWindowProc", gDebugWindowProc); gSavedSettings.setBOOL("AllowIdleAFK", gAllowIdleAFK); gSavedSettings.setBOOL("ShowObjectUpdates", gShowObjectUpdates); if (!gNoRender) { if (gDebugView) { gSavedSettings.setBOOL("ShowDebugConsole", gDebugView->mDebugConsolep->getVisible()); gSavedSettings.setBOOL("ShowDebugStats", gDebugView->mStatViewp->getVisible()); } } // save window position if not fullscreen // as we don't track it in callbacks BOOL fullscreen = gViewerWindow->mWindow->getFullscreen(); BOOL maximized = gViewerWindow->mWindow->getMaximized(); if (!fullscreen && !maximized) { LLCoordScreen window_pos; if (gViewerWindow->mWindow->getPosition(&window_pos)) { gSavedSettings.setS32("WindowX", window_pos.mX); gSavedSettings.setS32("WindowY", window_pos.mY); } } gSavedSettings.setF32("MapScale", gMapScale ); gSavedSettings.setF32("MiniMapScale", gMiniMapScale ); gSavedSettings.setBOOL("AsyncKeyboard", gHandleKeysAsync); gSavedSettings.setBOOL("ShowHoverTips", LLHoverView::sShowHoverTips); // Some things are cached in LLAgent. if (gAgent.mInitialized) { gSavedSettings.setF32("RenderFarClip", gAgent.mDrawDistance); } // *REMOVE: This is now done via LLAppViewer::setCrashBehavior() // Left vestigially in case I borked it. // gCrashSettings.setS32(CRASH_BEHAVIOR_SETTING, gCrashBehavior); } void LLAppViewer::removeCacheFiles(const char* file_mask) { char mask[LL_MAX_PATH]; /* Flawfinder: ignore */ snprintf(mask, LL_MAX_PATH, "%s%s", gDirUtilp->getDirDelimiter().c_str(), file_mask); /* Flawfinder: ignore */ gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "").c_str(), mask); } void LLAppViewer::writeSystemInfo() { gDebugInfo["SLLog"] = LLError::logFileName(); gDebugInfo["ClientInfo"]["Name"] = gSecondLife; gDebugInfo["ClientInfo"]["MajorVersion"] = LL_VERSION_MAJOR; gDebugInfo["ClientInfo"]["MinorVersion"] = LL_VERSION_MINOR; gDebugInfo["ClientInfo"]["PatchVersion"] = LL_VERSION_PATCH; gDebugInfo["ClientInfo"]["BuildVersion"] = LL_VERSION_BUILD; gDebugInfo["CPUInfo"]["CPUFamily"] = gSysCPU.getFamily(); gDebugInfo["CPUInfo"]["CPUMhz"] = gSysCPU.getMhz(); gDebugInfo["CPUInfo"]["CPUAltivec"] = gSysCPU.hasAltivec(); gDebugInfo["CPUInfo"]["CPUSSE"] = gSysCPU.hasSSE(); gDebugInfo["CPUInfo"]["CPUSSE2"] = gSysCPU.hasSSE2(); gDebugInfo["RAMInfo"] = llformat("%u", gSysMemory.getPhysicalMemoryKB()); gDebugInfo["OSInfo"] = mSysOSInfo.getOSString().c_str(); // Dump some debugging info llinfos << gSecondLife << " version " << LL_VERSION_MAJOR << "." << LL_VERSION_MINOR << "." << LL_VERSION_PATCH << llendl; // Dump the local time and time zone time_t now; time(&now); char tbuffer[256]; /* Flawfinder: ignore */ strftime(tbuffer, 256, "%Y-%m-%dT%H:%M:%S %Z", localtime(&now)); llinfos << "Local time: " << tbuffer << llendl; // query some system information llinfos << "CPU info:\n" << gSysCPU << llendl; llinfos << "Memory info:\n" << gSysMemory << llendl; llinfos << "OS info: " << getOSInfo() << llendl; } void LLAppViewer::handleViewerCrash() { LLAppViewer* pApp = LLAppViewer::instance(); if (pApp->beingDebugged()) { // This will drop us into the debugger. abort(); } // Returns whether a dialog was shown. // Only do the logic in here once if (pApp->mReportedCrash) { return; } pApp->mReportedCrash = TRUE; gDebugInfo["SettingsFilename"] = gSettingsFileName; gDebugInfo["CAFilename"] = gDirUtilp->getCAFile(); gDebugInfo["ViewerExePath"] = gDirUtilp->getExecutablePathAndName().c_str(); gDebugInfo["CurrentPath"] = gDirUtilp->getCurPath().c_str(); gDebugInfo["CurrentSimHost"] = gAgent.getRegionHost().getHostName(); if (gMessageSystem && gDirUtilp) { std::string filename; filename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "stats.log"); llofstream file(filename.c_str(), llofstream::binary); if(file.good()) { gMessageSystem->summarizeLogs(file); file.close(); } } if (gMessageSystem) { gMessageSystem->getCircuitInfo(gDebugInfo["CircuitInfo"]); gMessageSystem->stopLogging(); } if (gWorldp) { gWorldp->getInfo(gDebugInfo); } // Close the debug file pApp->closeDebug(); LLError::logToFile(""); // Call to pure virtual, handled by platform specifc llappviewer instance. pApp->handleCrashReporting(); return; } void LLAppViewer::setCrashBehavior(S32 cb) { mCrashBehavior = cb; gCrashSettings.setS32(CRASH_BEHAVIOR_SETTING, mCrashBehavior); } bool LLAppViewer::anotherInstanceRunning() { // We create a marker file when the program starts and remove the file when it finishes. // If the file is currently locked, that means another process is already running. std::string marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.exec_marker"); llinfos << "Checking marker file for lock..." << llendl; // If file doesn't exist, we create it // If file does exist, try to get writing privilages FILE* fMarker = LLFile::fopen(marker_file.c_str(), "rb"); // Flawfinder: ignore if (fMarker != NULL) { // File exists, try opening with write permissions fclose(fMarker); fMarker = LLFile::fopen(marker_file.c_str(), "wb"); // Flawfinder: ignore if (fMarker == NULL) { llinfos << "Marker file is locked." << llendl; return TRUE; } // *FIX:Mani - rather than have this exception here, // LLFile::fopen() have consistent behavior across platforms? #if LL_DARWIN // Try to lock it. On Mac, this is the only way to test if it's actually locked. if (flock(fileno(fMarker), LOCK_EX | LOCK_NB) == -1) { // Lock failed - somebody else has it. fclose(fMarker); llinfos << "Marker file is locked." << llendl; return TRUE; } #endif fclose(fMarker); } llinfos << "Marker file isn't locked." << llendl; return FALSE; } void LLAppViewer::initMarkerFile() { // *FIX:Mani - an actually cross platform LLFile lib would be nice. #if LL_SOLARIS struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; #endif // We create a marker file when the program starts and remove the file when it finishes. // If the file is currently locked, that means another process is already running. // If the file exists and isn't locked, we crashed on the last run. std::string marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.exec_marker"); llinfos << "Checking marker file for lock..." << llendl; FILE* fMarker = LLFile::fopen(marker_file.c_str(), "rb"); // Flawfinder: ignore if (fMarker != NULL) { // File exists, try opening with write permissions fclose(fMarker); fMarker = LLFile::fopen(marker_file.c_str(), "wb"); // Flawfinder: ignxore if (fMarker == NULL) { // Another instance is running. Skip the rest of these operations. llinfos << "Marker file is locked." << llendl; return; } #if LL_DARWIN // Try to lock it. On Mac, this is the only way to test if it's actually locked. if (flock(fileno(fMarker), LOCK_EX | LOCK_NB) == -1) { // Lock failed - somebody else has it. fclose(fMarker); llinfos << "Marker file is locked." << llendl; return; } #endif // No other instances; we'll lock this file now & delete on quit. fclose(fMarker); gLastExecFroze = TRUE; llinfos << "Exec marker found: program froze on previous execution" << llendl; } // Create the marker file for this execution & lock it // FILE *fp_executing_marker; #if LL_WINDOWS mMarkerFile = LLFile::_fsopen(marker_file.c_str(), "w", _SH_DENYWR); #else mMarkerFile = LLFile::fopen(marker_file.c_str(), "w"); // Flawfinder: ignore if (mMarkerFile) { int fd = fileno(mMarkerFile); // Attempt to lock #if LL_SOLARIS fl.l_type = F_WRLCK; if (fcntl(fd, F_SETLK, &fl) == -1) #else if (flock(fd, LOCK_EX | LOCK_NB) == -1) #endif { llinfos << "Failed to lock file." << llendl; } } #endif if (mMarkerFile) { llinfos << "Marker file created." << llendl; } else { llinfos << "Failed to create marker file." << llendl; } #if LL_WINDOWS // Clean up SecondLife.dmp files, to avoid confusion llinfos << "Removing SecondLife.dmp" << llendl; std::string dmp_filename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.dmp"); LLFile::remove(dmp_filename.c_str()); #endif // This is to keep the crash reporter from constantly sending stale message logs // We wipe the message file now. llinfos << "Removing message.log" << llendl; std::string message_filename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "message.log"); LLFile::remove(message_filename.c_str()); llinfos << "Exiting initMarkerFile()." << llendl; } void LLAppViewer::removeMarkerFile() { llinfos << "removeMarkerFile()" << llendl; if (mMarkerFile != NULL) { fclose(mMarkerFile); mMarkerFile = NULL; } if( gDirUtilp ) { LLString marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.exec_marker"); ll_apr_file_remove( marker_file ); } } void LLAppViewer::forceQuit() { LLApp::setQuitting(); } void LLAppViewer::requestQuit() { llinfos << "requestQuit" << llendl; LLViewerRegion* region = gAgent.getRegion(); if( (LLStartUp::getStartupState() < STATE_STARTED) || !region ) { // Quit immediately forceQuit(); return; } if (gHUDManager) { LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral*)gHUDManager->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, TRUE); effectp->setPositionGlobal(gAgent.getPositionGlobal()); effectp->setColor(LLColor4U(gAgent.getEffectColor())); gHUDManager->sendEffects(); } // Attempt to close all floaters that might be // editing things. if (gFloaterView) { // application is quitting gFloaterView->closeAllChildren(true); } send_stats(); gLogoutTimer.reset(); mQuitRequested = true; } static void finish_quit(S32 option, void *userdata) { if (option == 0) { LLAppViewer::instance()->requestQuit(); } } void LLAppViewer::userQuit() { gViewerWindow->alertXml("ConfirmQuit", finish_quit, NULL); } static void finish_early_exit(S32 option, void* userdata) { LLAppViewer::instance()->forceQuit(); } void LLAppViewer::earlyExit(const LLString& msg) { llwarns << "app_early_exit: " << msg << llendl; gDoDisconnect = TRUE; // LLStringBase::format_map_t args; // args["[MESSAGE]"] = mesg; // gViewerWindow->alertXml("AppEarlyExit", args, finish_early_exit); LLAlertDialog::showCritical(msg, finish_early_exit, NULL); } void LLAppViewer::forceExit(S32 arg) { removeMarkerFile(); // *FIX:Mani - This kind of exit hardly seems appropriate. exit(arg); } void LLAppViewer::abortQuit() { llinfos << "abortQuit()" << llendl; mQuitRequested = false; } bool LLAppViewer::initCache() { mPurgeCache = false; // Purge cache if user requested it if (gSavedSettings.getBOOL("PurgeCacheOnStartup") || gSavedSettings.getBOOL("PurgeCacheOnNextStartup")) { gSavedSettings.setBOOL("PurgeCacheOnNextStartup", false); mPurgeCache = true; } // Purge cache if it belongs to an old version else { static const S32 cache_version = 5; if (gSavedSettings.getS32("LocalCacheVersion") != cache_version) { mPurgeCache = true; gSavedSettings.setS32("LocalCacheVersion", cache_version); } } // Setup and verify the cache location LLString cache_location = gSavedSettings.getString("CacheLocation"); LLString new_cache_location = gSavedSettings.getString("NewCacheLocation"); if (new_cache_location != cache_location) { gDirUtilp->setCacheDir(gSavedSettings.getString("CacheLocation")); purgeCache(); // purge old cache gSavedSettings.setString("CacheLocation", new_cache_location); } if (!gDirUtilp->setCacheDir(gSavedSettings.getString("CacheLocation"))) { llwarns << "Unable to set cache location" << llendl; gSavedSettings.setString("CacheLocation", ""); } if (mPurgeCache) { LLSplashScreen::update("Clearing cache..."); purgeCache(); } LLSplashScreen::update("Initializing Texture Cache..."); // Init the texture cache // Allocate 80% of the cache size for textures BOOL read_only = mSecondInstance ? true : false; const S32 MB = 1024*1024; S64 cache_size = (S64)(gSavedSettings.getU32("CacheSize")) * MB; const S64 MAX_CACHE_SIZE = 1024*MB; cache_size = llmin(cache_size, MAX_CACHE_SIZE); S64 texture_cache_size = ((cache_size * 8)/10); S64 extra = LLAppViewer::getTextureCache()->initCache(LL_PATH_CACHE, texture_cache_size, read_only); texture_cache_size -= extra; LLSplashScreen::update("Initializing VFS..."); // Init the VFS S64 vfs_size = cache_size - texture_cache_size; const S64 MAX_VFS_SIZE = 1024 * MB; // 1 GB vfs_size = llmin(vfs_size, MAX_VFS_SIZE); vfs_size = (vfs_size / MB) * MB; // make sure it is MB aligned U32 vfs_size_u32 = (U32)vfs_size; U32 old_vfs_size = gSavedSettings.getU32("VFSOldSize") * MB; bool resize_vfs = (vfs_size_u32 != old_vfs_size); if (resize_vfs) { gSavedSettings.setU32("VFSOldSize", vfs_size_u32/MB); } llinfos << "VFS CACHE SIZE: " << vfs_size/(1024*1024) << " MB" << llendl; // This has to happen BEFORE starting the vfs //time_t ltime; srand(time(NULL)); // Flawfinder: ignore U32 old_salt = gSavedSettings.getU32("VFSSalt"); U32 new_salt; char old_vfs_data_file[LL_MAX_PATH]; // Flawfinder: ignore char old_vfs_index_file[LL_MAX_PATH]; // Flawfinder: ignore char new_vfs_data_file[LL_MAX_PATH]; // Flawfinder: ignore char new_vfs_index_file[LL_MAX_PATH]; // Flawfinder: ignore char static_vfs_index_file[LL_MAX_PATH]; // Flawfinder: ignore char static_vfs_data_file[LL_MAX_PATH]; // Flawfinder: ignore if (gMultipleViewersOK) { // don't mess with renaming the VFS in this case new_salt = old_salt; } else { do { new_salt = rand(); } while( new_salt == old_salt ); } snprintf(old_vfs_data_file, LL_MAX_PATH, "%s%u", // Flawfinder: ignore gDirUtilp->getExpandedFilename(LL_PATH_CACHE,VFS_DATA_FILE_BASE).c_str(), old_salt); // make sure this file exists llstat s; S32 stat_result = LLFile::stat(old_vfs_data_file, &s); if (stat_result) { // doesn't exist, look for a data file std::string mask; mask = gDirUtilp->getDirDelimiter(); mask += VFS_DATA_FILE_BASE; mask += "*"; std::string dir; dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""); std::string found_file; if (gDirUtilp->getNextFileInDir(dir, mask, found_file, false)) { snprintf(old_vfs_data_file, LL_MAX_PATH, "%s%s%s", dir.c_str(), gDirUtilp->getDirDelimiter().c_str(), found_file.c_str()); // Flawfinder: ignore S32 start_pos; S32 length = strlen(found_file.c_str()); /* Flawfinder: ignore*/ for (start_pos = length - 1; start_pos >= 0; start_pos--) { if (found_file[start_pos] == '.') { start_pos++; break; } } if (start_pos > 0) { sscanf(found_file.c_str() + start_pos, "%d", &old_salt); } llinfos << "Default vfs data file not present, found " << old_vfs_data_file << llendl; llinfos << "Old salt: " << old_salt << llendl; } } snprintf(old_vfs_index_file, LL_MAX_PATH, "%s%u", // Flawfinder: ignore gDirUtilp->getExpandedFilename(LL_PATH_CACHE,VFS_INDEX_FILE_BASE).c_str(), old_salt); stat_result = LLFile::stat(old_vfs_index_file, &s); if (stat_result) { // We've got a bad/missing index file, nukem! llwarns << "Bad or missing vfx index file " << old_vfs_index_file << llendl; llwarns << "Removing old vfs data file " << old_vfs_data_file << llendl; LLFile::remove(old_vfs_data_file); LLFile::remove(old_vfs_index_file); // Just in case, nuke any other old cache files in the directory. std::string dir; dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""); std::string mask; mask = gDirUtilp->getDirDelimiter(); mask += VFS_DATA_FILE_BASE; mask += "*"; gDirUtilp->deleteFilesInDir(dir, mask); mask = gDirUtilp->getDirDelimiter(); mask += VFS_INDEX_FILE_BASE; mask += "*"; gDirUtilp->deleteFilesInDir(dir, mask); } snprintf(new_vfs_data_file, LL_MAX_PATH, "%s%u", // Flawfinder: ignore gDirUtilp->getExpandedFilename(LL_PATH_CACHE,VFS_DATA_FILE_BASE).c_str(), new_salt); snprintf(new_vfs_index_file, LL_MAX_PATH, "%s%u", gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_INDEX_FILE_BASE).c_str(), // Flawfinder: ignore new_salt); strncpy(static_vfs_data_file, gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"static_data.db2").c_str(), LL_MAX_PATH -1); // Flawfinder: ignore static_vfs_data_file[LL_MAX_PATH -1] = '\0'; strncpy(static_vfs_index_file, gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"static_index.db2").c_str(), LL_MAX_PATH -1); // Flawfinder: ignore static_vfs_index_file[LL_MAX_PATH -1] = '\0'; if (resize_vfs) { llinfos << "Removing old vfs and re-sizing" << llendl; LLFile::remove(old_vfs_data_file); LLFile::remove(old_vfs_index_file); } else if (old_salt != new_salt) { // move the vfs files to a new name before opening llinfos << "Renaming " << old_vfs_data_file << " to " << new_vfs_data_file << llendl; llinfos << "Renaming " << old_vfs_index_file << " to " << new_vfs_index_file << llendl; LLFile::rename(old_vfs_data_file, new_vfs_data_file); LLFile::rename(old_vfs_index_file, new_vfs_index_file); } // Startup the VFS... gSavedSettings.setU32("VFSSalt", new_salt); // Don't remove VFS after viewer crashes. If user has corrupt data, they can reinstall. JC gVFS = new LLVFS(new_vfs_index_file, new_vfs_data_file, false, vfs_size_u32, false); if( VFSVALID_BAD_CORRUPT == gVFS->getValidState() ) { // Try again with fresh files // (The constructor deletes corrupt files when it finds them.) llwarns << "VFS corrupt, deleted. Making new VFS." << llendl; delete gVFS; gVFS = new LLVFS(new_vfs_index_file, new_vfs_data_file, false, vfs_size_u32, false); } gStaticVFS = new LLVFS(static_vfs_index_file, static_vfs_data_file, true, 0, false); BOOL success = gVFS->isValid() && gStaticVFS->isValid(); if( !success ) { return false; } else { LLVFile::initClass(); return true; } } void LLAppViewer::purgeCache() { llinfos << "Purging Texture Cache..." << llendl; LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE); llinfos << "Purging Cache..." << llendl; std::string mask = gDirUtilp->getDirDelimiter() + "*.*"; gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,"").c_str(),mask); } const LLString& LLAppViewer::getSecondLifeTitle() const { return gSecondLife; } const LLString& LLAppViewer::getWindowTitle() const { return gWindowTitle; } void LLAppViewer::resetURIs() const { // Clear URIs when picking a new server gLoginURIs.clear(); gHelperURI.clear(); } const std::vector& LLAppViewer::getLoginURIs() const { if (gLoginURIs.empty()) { // not specified on the command line, use value from table gLoginURIs.push_back(gGridInfo[gGridChoice].mLoginURI); } return gLoginURIs; } const std::string& LLAppViewer::getHelperURI() const { if (gHelperURI.empty()) { // not specified on the command line, use value from table gHelperURI = gGridInfo[gGridChoice].mHelperURI; } return gHelperURI; } void LLAppViewer::addLoginURI(const std::string& uri) { gLoginURIs.push_back(uri); } void LLAppViewer::setHelperURI(const std::string& uri) { gHelperURI = uri; } // Callback from a dialog indicating user was logged out. void finish_disconnect(S32 option, void* userdata) { if (1 == option) { LLAppViewer::instance()->forceQuit(); } } // Callback from an early disconnect dialog, force an exit void finish_forced_disconnect(S32 /* option */, void* /* userdata */) { LLAppViewer::instance()->forceQuit(); } void LLAppViewer::forceDisconnect(const LLString& mesg) { if (gDoDisconnect) { // Already popped up one of these dialogs, don't // do this again. return; } // Translate the message if possible LLString big_reason = LLAgent::sTeleportErrorMessages[mesg]; if ( big_reason.size() == 0 ) { big_reason = mesg; } LLStringBase::format_map_t args; gDoDisconnect = TRUE; if (LLStartUp::getStartupState() < STATE_STARTED) { // Tell users what happened args["[ERROR_MESSAGE]"] = big_reason; gViewerWindow->alertXml("ErrorMessage", args, finish_forced_disconnect); } else { args["[MESSAGE]"] = big_reason; gViewerWindow->alertXml("YouHaveBeenLoggedOut", args, finish_disconnect ); } } void LLAppViewer::badNetworkHandler() { // Dump the packet gMessageSystem->dumpPacketToLog(); // Flush all of our caches on exit in the case of disconnect due to // invalid packets. mPurgeOnExit = TRUE; #if LL_WINDOWS // Generates the minidump. LLWinDebug::handleException(NULL); #endif LLAppViewer::handleViewerCrash(); std::ostringstream message; message << "The viewer has detected mangled network data indicative\n" "of a bad upstream network connection or an incomplete\n" "local installation of " << LLAppViewer::instance()->getSecondLifeTitle() << ". \n" " \n" "Try uninstalling and reinstalling to see if this resolves \n" "the issue. \n" " \n" "If the problem continues, see the Tech Support FAQ at: \n" "www.secondlife.com/support"; forceDisconnect(message.str()); } // This routine may get called more than once during the shutdown process. // This can happen because we need to get the screenshot before the window // is destroyed. void LLAppViewer::saveFinalSnapshot() { if (!mSavedFinalSnapshot && !gNoRender) { gSavedSettings.setVector3d("FocusPosOnLogout", gAgent.calcFocusPositionTargetGlobal()); gSavedSettings.setVector3d("CameraPosOnLogout", gAgent.calcCameraPositionTargetGlobal()); gViewerWindow->setCursor(UI_CURSOR_WAIT); gAgent.changeCameraToThirdPerson( FALSE ); // don't animate, need immediate switch gSavedSettings.setBOOL("ShowParcelOwners", FALSE); idle(); LLString snap_filename = gDirUtilp->getLindenUserDir(); snap_filename += gDirUtilp->getDirDelimiter(); snap_filename += SCREEN_LAST_FILENAME; gViewerWindow->saveSnapshot(snap_filename, gViewerWindow->getWindowWidth(), gViewerWindow->getWindowHeight(), FALSE, TRUE); mSavedFinalSnapshot = TRUE; } } void LLAppViewer::loadNameCache() { if (!gCacheName) return; std::string name_cache; name_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "name.cache"); FILE* name_cache_fp = LLFile::fopen(name_cache.c_str(), "r"); // Flawfinder: ignore if (name_cache_fp) { gCacheName->importFile(name_cache_fp); fclose(name_cache_fp); } } void LLAppViewer::saveNameCache() { if (!gCacheName) return; std::string name_cache; name_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "name.cache"); FILE* name_cache_fp = LLFile::fopen(name_cache.c_str(), "w"); // Flawfinder: ignore if (name_cache_fp) { gCacheName->exportFile(name_cache_fp); fclose(name_cache_fp); } } /////////////////////////////////////////////////////// // idle() // // Called every time the window is not doing anything. // Receive packets, update statistics, and schedule a redisplay. /////////////////////////////////////////////////////// void LLAppViewer::idle() { // Update frame timers static LLTimer idle_timer; LLControlBase::updateAllListeners(); LLFrameTimer::updateFrameTime(); LLEventTimer::updateClass(); LLCriticalDamp::updateInterpolants(); LLMortician::updateClass(); F32 dt_raw = idle_timer.getElapsedTimeAndResetF32(); // Cap out-of-control frame times // Too low because in menus, swapping, debugger, etc. // Too high because idle called with no objects in view, etc. const F32 MIN_FRAME_RATE = 1.f; const F32 MAX_FRAME_RATE = 200.f; F32 frame_rate_clamped = 1.f / dt_raw; frame_rate_clamped = llclamp(frame_rate_clamped, MIN_FRAME_RATE, MAX_FRAME_RATE); gFrameDTClamped = 1.f / frame_rate_clamped; // Global frame timer // Smoothly weight toward current frame gFPSClamped = (frame_rate_clamped + (4.f * gFPSClamped)) / 5.f; if (gQuitAfterSeconds > 0.f) { if (gRenderStartTime.getElapsedTimeF32() > gQuitAfterSeconds) { LLAppViewer::instance()->forceQuit(); } } // Must wait until both have avatar object and mute list, so poll // here. request_initial_instant_messages(); /////////////////////////////////// // // Special case idle if still starting up // if (LLStartUp::getStartupState() < STATE_STARTED) { // Skip rest if idle startup returns false (essentially, no world yet) if (!idle_startup()) { return; } } F32 yaw = 0.f; // radians if (!gDisconnected) { LLFastTimer t(LLFastTimer::FTM_NETWORK); // Update spaceserver timeinfo gWorldp->setSpaceTimeUSec(gWorldp->getSpaceTimeUSec() + (U32)(dt_raw * SEC_TO_MICROSEC)); ////////////////////////////////////// // // Update simulator agent state // if (gRotateRight) { gAgent.moveYaw(-1.f); } // Handle automatic walking towards points gAgentPilot.updateTarget(); gAgent.autoPilot(&yaw); static LLFrameTimer agent_update_timer; static U32 last_control_flags; // When appropriate, update agent location to the simulator. F32 agent_update_time = agent_update_timer.getElapsedTimeF32(); BOOL flags_changed = gAgent.controlFlagsDirty() || (last_control_flags != gAgent.getControlFlags()); if (flags_changed || (agent_update_time > (1.0f / (F32) AGENT_UPDATES_PER_SECOND))) { // Send avatar and camera info last_control_flags = gAgent.getControlFlags(); send_agent_update(TRUE); agent_update_timer.reset(); } } ////////////////////////////////////// // // Manage statistics // // { static LLFrameTimer viewer_stats_timer; reset_statistics(); // Update session stats every large chunk of time // *FIX: (???) SAMANTHA if (viewer_stats_timer.getElapsedTimeF32() >= 300.f && !gDisconnected) { llinfos << "Transmitting sessions stats" << llendl; send_stats(); viewer_stats_timer.reset(); } // Print the object debugging stats static LLFrameTimer object_debug_timer; if (object_debug_timer.getElapsedTimeF32() > 5.f) { object_debug_timer.reset(); if (gObjectList.mNumDeadObjectUpdates) { llinfos << "Dead object updates: " << gObjectList.mNumDeadObjectUpdates << llendl; gObjectList.mNumDeadObjectUpdates = 0; } if (gObjectList.mNumUnknownKills) { llinfos << "Kills on unknown objects: " << gObjectList.mNumUnknownKills << llendl; gObjectList.mNumUnknownKills = 0; } if (gObjectList.mNumUnknownUpdates) { llinfos << "Unknown object updates: " << gObjectList.mNumUnknownUpdates << llendl; gObjectList.mNumUnknownUpdates = 0; } } gFrameStats.addFrameData(); } if (!gDisconnected) { LLFastTimer t(LLFastTimer::FTM_NETWORK); //////////////////////////////////////////////// // // Network processing // // NOTE: Starting at this point, we may still have pointers to "dead" objects // floating throughout the various object lists. // gFrameStats.start(LLFrameStats::IDLE_NETWORK); idleNetwork(); stop_glerror(); gFrameStats.start(LLFrameStats::AGENT_MISC); // Check for away from keyboard, kick idle agents. idle_afk_check(); // Update statistics for this frame update_statistics(gFrameCount); gViewerWindow->updateDebugText(); } //////////////////////////////////////// // // Handle the regular UI idle callbacks as well as // hover callbacks // { // LLFastTimer t(LLFastTimer::FTM_IDLE_CB); // Do event notifications if necessary. Yes, we may want to move this elsewhere. gEventNotifier.update(); gIdleCallbacks.callFunctions(); } if (gDisconnected) { return; } gViewerWindow->handlePerFrameHover(); /////////////////////////////////////// // Agent and camera movement // LLCoordGL current_mouse = gViewerWindow->getCurrentMouse(); // BOOL was_in_prelude = gAgent.inPrelude(); { //LLFastTimer t(LLFastTimer::FTM_TEMP1); // After agent and camera moved, figure out if we need to // deselect objects. gSelectMgr->deselectAllIfTooFar(); } { LLFastTimer t(LLFastTimer::FTM_RESET_DRAWORDER); ////////////////////////////////////////////// // // Clear draw orders // // Should actually be done after render, but handlePerFrameHover actually does a "render" // to do its selection. // gPipeline.resetDrawOrders(); } { // Handle pending gesture processing gGestureManager.update(); gAgent.updateAgentPosition(gFrameDTClamped, yaw, current_mouse.mX, current_mouse.mY); } { LLFastTimer t(LLFastTimer::FTM_OBJECTLIST_UPDATE); // Actually "object update" gFrameStats.start(LLFrameStats::OBJECT_UPDATE); if (!(logoutRequestSent() && hasSavedFinalSnapshot())) { gObjectList.update(gAgent, *gWorldp); } } { LLFastTimer t(LLFastTimer::FTM_UPDATE_SKY); gSky.updateSky(); } ////////////////////////////////////// // // Deletes objects... // Has to be done after doing idleUpdates (which can kill objects) // { LLFastTimer t(LLFastTimer::FTM_CLEANUP); gFrameStats.start(LLFrameStats::CLEAN_DEAD); gObjectList.cleanDeadObjects(); LLDrawable::cleanupDeadDrawables(); } // // After this point, in theory we should never see a dead object // in the various object/drawable lists. // ////////////////////////////////////// // // Update/send HUD effects // // At this point, HUD effects may clean up some references to // dead objects. // { //LLFastTimer t(LLFastTimer::FTM_TEMP3); gFrameStats.start(LLFrameStats::UPDATE_EFFECTS); gSelectMgr->updateEffects(); gHUDManager->cleanupEffects(); gHUDManager->sendEffects(); } stop_glerror(); //////////////////////////////////////// // // Unpack layer data that we've received // { LLFastTimer t(LLFastTimer::FTM_NETWORK); gVLManager.unpackData(); } ///////////////////////// // // Update surfaces, and surface textures as well. // gWorldp->updateVisibilities(); { const F32 max_region_update_time = .001f; // 1ms LLFastTimer t(LLFastTimer::FTM_REGION_UPDATE); gWorldp->updateRegions(max_region_update_time); } ///////////////////////// // // Update weather effects // if (!gNoRender) { gWorldp->updateClouds(gFrameDTClamped); gSky.propagateHeavenlyBodies(gFrameDTClamped); // moves sun, moon, and planets // Update wind vector LLVector3 wind_position_region; static LLVector3 average_wind; LLViewerRegion *regionp; regionp = gWorldp->resolveRegionGlobal(wind_position_region, gAgent.getPositionGlobal()); // puts agent's local coords into wind_position if (regionp) { gWindVec = regionp->mWind.getVelocity(wind_position_region); // Compute average wind and use to drive motion of water average_wind = regionp->mWind.getAverage(); F32 cloud_density = regionp->mCloudLayer.getDensityRegion(wind_position_region); gSky.setCloudDensityAtAgent(cloud_density); gSky.setWind(average_wind); //LLVOWater::setWind(average_wind); } else { gWindVec.setVec(0.0f, 0.0f, 0.0f); } } stop_glerror(); ////////////////////////////////////// // // Update images, using the image stats generated during object update/culling // // Can put objects onto the retextured list. // gFrameStats.start(LLFrameStats::IMAGE_UPDATE); LLFastTimer t(LLFastTimer::FTM_IMAGE_UPDATE); LLViewerImage::updateClass(gCamera->getVelocityStat()->getMean(), gCamera->getAngularVelocityStat()->getMean()); gBumpImageList.updateImages(); // must be called before gImageList version so that it's textures are thrown out first. const F32 max_image_decode_time = 0.005f; // 5 ms decode time gImageList.updateImages(max_image_decode_time); stop_glerror(); ////////////////////////////////////// // // Sort and cull in the new renderer are moved to pipeline.cpp // Here, particles are updated and drawables are moved. // if (!gNoRender) { gFrameStats.start(LLFrameStats::UPDATE_MOVE); gPipeline.updateMove(); gFrameStats.start(LLFrameStats::UPDATE_PARTICLES); gWorldp->updateParticles(); } stop_glerror(); if (!LLViewerJoystick::sOverrideCamera) { gAgent.updateCamera(); } else { LLViewerJoystick::updateCamera(); } // objects and camera should be in sync, do LOD calculations now { LLFastTimer t(LLFastTimer::FTM_LOD_UPDATE); gObjectList.updateApparentAngles(gAgent); } { gFrameStats.start(LLFrameStats::AUDIO); LLFastTimer t(LLFastTimer::FTM_AUDIO_UPDATE); if (gAudiop) { audio_update_volume(false); audio_update_listener(); audio_update_wind(false); // this line actually commits the changes we've made to source positions, etc. const F32 max_audio_decode_time = 0.002f; // 2 ms decode time gAudiop->idle(max_audio_decode_time); } } // Handle shutdown process, for example, // wait for floaters to close, send quit message, // forcibly quit if it has taken too long if (mQuitRequested) { idleShutdown(); } stop_glerror(); } void LLAppViewer::idleShutdown() { // Wait for all modal alerts to get resolved if (LLModalDialog::activeCount() > 0) { return; } // close IM interface if(gIMMgr) { gIMMgr->disconnectAllSessions(); } // Wait for all floaters to get resolved if (gFloaterView && !gFloaterView->allChildrenClosed()) { return; } static bool saved_snapshot = false; if (!saved_snapshot) { saved_snapshot = true; saveFinalSnapshot(); return; } const F32 SHUTDOWN_UPLOAD_SAVE_TIME = 5.f; S32 pending_uploads = gAssetStorage->getNumPendingUploads(); if (pending_uploads > 0 && gLogoutTimer.getElapsedTimeF32() < SHUTDOWN_UPLOAD_SAVE_TIME && !logoutRequestSent()) { static S32 total_uploads = 0; // Sometimes total upload count can change during logout. total_uploads = llmax(total_uploads, pending_uploads); gViewerWindow->setShowProgress(TRUE); S32 finished_uploads = total_uploads - pending_uploads; F32 percent = 100.f * finished_uploads / total_uploads; gViewerWindow->setProgressPercent(percent); char buffer[MAX_STRING]; // Flawfinder: ignore snprintf(buffer, MAX_STRING, "Saving final data..."); // Flawfinder: ignore gViewerWindow->setProgressString(buffer); return; } // All floaters are closed. Tell server we want to quit. if( !logoutRequestSent() ) { sendLogoutRequest(); // Wait for a LogoutReply message gViewerWindow->setShowProgress(TRUE); gViewerWindow->setProgressPercent(100.f); gViewerWindow->setProgressString("Logging out..."); return; } // Make sure that we quit if we haven't received a reply from the server. if( logoutRequestSent() && gLogoutTimer.getElapsedTimeF32() > gLogoutMaxTime ) { forceQuit(); return; } } void LLAppViewer::sendLogoutRequest() { if(!mLogoutRequestSent) { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_LogoutRequest); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); gAgent.sendReliableMessage(); gLogoutTimer.reset(); gLogoutMaxTime = LOGOUT_REQUEST_TIME; mLogoutRequestSent = TRUE; gVoiceClient->leaveChannel(); } } // // Handle messages, and all message related stuff // #define TIME_THROTTLE_MESSAGES #ifdef TIME_THROTTLE_MESSAGES #define CHECK_MESSAGES_DEFAULT_MAX_TIME .020f // 50 ms = 50 fps (just for messages!) static F32 CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; #endif void LLAppViewer::idleNetwork() { gObjectList.mNumNewObjects = 0; S32 total_decoded = 0; if (!gSavedSettings.getBOOL("SpeedTest")) { LLFastTimer t(LLFastTimer::FTM_IDLE_NETWORK); // decode // deal with any queued name requests and replies. gCacheName->processPending(); LLTimer check_message_timer; // Read all available packets from network stop_glerror(); const S64 frame_count = gFrameCount; // U32->S64 F32 total_time = 0.0f; while (gMessageSystem->checkAllMessages(frame_count, gServicePump)) { if (gDoDisconnect) { // We're disconnecting, don't process any more messages from the server // We're usually disconnecting due to either network corruption or a // server going down, so this is OK. break; } stop_glerror(); total_decoded++; gPacketsIn++; if (total_decoded > MESSAGE_MAX_PER_FRAME) { break; } #ifdef TIME_THROTTLE_MESSAGES // Prevent slow packets from completely destroying the frame rate. // This usually happens due to clumps of avatars taking huge amount // of network processing time (which needs to be fixed, but this is // a good limit anyway). total_time = check_message_timer.getElapsedTimeF32(); if (total_time >= CheckMessagesMaxTime) break; #endif } // Handle per-frame message system processing. gMessageSystem->processAcks(); #ifdef TIME_THROTTLE_MESSAGES if (total_time >= CheckMessagesMaxTime) { // Increase CheckMessagesMaxTime so that we will eventually catch up CheckMessagesMaxTime *= 1.035f; // 3.5% ~= x2 in 20 frames, ~8x in 60 frames } else { // Reset CheckMessagesMaxTime to default value CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; } #endif // we want to clear the control after sending out all necessary agent updates gAgent.resetControlFlags(); stop_glerror(); // Decode enqueued messages... S32 remaining_possible_decodes = MESSAGE_MAX_PER_FRAME - total_decoded; if( remaining_possible_decodes <= 0 ) { llinfos << "Maxed out number of messages per frame at " << MESSAGE_MAX_PER_FRAME << llendl; } if (gPrintMessagesThisFrame) { llinfos << "Decoded " << total_decoded << " msgs this frame!" << llendl; gPrintMessagesThisFrame = FALSE; } } gObjectList.mNumNewObjectsStat.addValue(gObjectList.mNumNewObjects); // Retransmit unacknowledged packets. gXferManager->retransmitUnackedPackets(); gAssetStorage->checkForTimeouts(); gViewerThrottle.updateDynamicThrottle(); } void LLAppViewer::disconnectViewer() { if (gDisconnected) { return; } // // Cleanup after quitting. // // Save snapshot for next time, if we made it through initialization llinfos << "Disconnecting viewer!" << llendl; // Dump our frame statistics gFrameStats.dump(); // Remember if we were flying gSavedSettings.setBOOL("FlyingAtExit", gAgent.getFlying() ); // Un-minimize all windows so they don't get saved minimized if (!gNoRender) { if (gFloaterView) { gFloaterView->restoreAll(); } } if (gSelectMgr) { gSelectMgr->deselectAll(); } if (!gNoRender) { // save inventory if appropriate gInventory.cache(gAgent.getInventoryRootID(), gAgent.getID()); if(gInventoryLibraryRoot.notNull() && gInventoryLibraryOwner.notNull()) { gInventory.cache(gInventoryLibraryRoot, gInventoryLibraryOwner); } } saveNameCache(); // close inventory interface, close all windows LLInventoryView::cleanup(); // Also writes cached agent settings to gSavedSettings gAgent.cleanup(); gObjectList.destroy(); delete gWorldp; gWorldp = NULL; cleanup_xfer_manager(); gDisconnected = TRUE; } void LLAppViewer::forceErrorLLError() { llerrs << "This is an llerror" << llendl; } void LLAppViewer::forceErrorBreakpoint() { #ifdef LL_WINDOWS DebugBreak(); #endif return; } void LLAppViewer::forceErrorBadMemoryAccess() { S32* crash = NULL; *crash = 0xDEADBEEF; return; } void LLAppViewer::forceErrorInifiniteLoop() { while(true) { ; } return; } void LLAppViewer::forceErrorSoftwareException() { // *FIX: Any way to insure it won't be handled? throw; }