private static void Main() { // Set DPI Stuff before anything else, even the mutex SetProcessDPI(GlobalSettings.DpiScalingMethodSetting); if (IsMainThread) { SetThreadDPI(GlobalSettings.DpiScalingMethodSetting); } using (GlobalChummerMutex = new Mutex(false, @"Global\" + ChummerGuid, out bool blnIsNewInstance)) { try { try { // Chummer instance already exists, so switch to it instead of opening a new instance if (!blnIsNewInstance || !GlobalChummerMutex.WaitOne(TimeSpan.FromSeconds(2), false)) { // Try to get the main chummer process by fetching the Chummer process with the earliest start time Process objMainChummerProcess = MyProcess; foreach (Process objLoopProcess in Process.GetProcessesByName(MyProcess.ProcessName)) { if (objLoopProcess.StartTime.Ticks < objMainChummerProcess.StartTime.Ticks) { objMainChummerProcess = objLoopProcess; } } if (objMainChummerProcess != MyProcess) { NativeMethods.SendMessage(objMainChummerProcess.MainWindowHandle, NativeMethods.WM_SHOWME, 0, IntPtr.Zero); string strCommandLineArgumentsJoined = string.Join("<>", Environment.GetCommandLineArgs()); NativeMethods.CopyDataStruct objData = new NativeMethods.CopyDataStruct(); IntPtr ptrCommandLineArguments = IntPtr.Zero; try { // Allocate memory for the data and copy objData = NativeMethods.CopyDataFromString(CommandLineArgsDataTypeId, strCommandLineArgumentsJoined); ptrCommandLineArguments = Marshal.AllocCoTaskMem(Marshal.SizeOf(objData)); Marshal.StructureToPtr(objData, ptrCommandLineArguments, false); // Send the message NativeMethods.SendMessage(objMainChummerProcess.MainWindowHandle, NativeMethods.WM_COPYDATA, 0, ptrCommandLineArguments); } finally { // Free the allocated memory after the control has been returned if (ptrCommandLineArguments != IntPtr.Zero) { Marshal.FreeCoTaskMem(ptrCommandLineArguments); } if (objData.lpData != IntPtr.Zero) { Marshal.FreeHGlobal(objData.lpData); } } } return; } } catch (AbandonedMutexException e) { Log.Info(e); } //for some fun try out this command line parameter: chummer://plugin:SINners:Load:5ff55b9d-7d1c-4067-a2f5-774127346f4e PageViewTelemetry pvt = null; DateTimeOffset startTime = DateTimeOffset.UtcNow; // Set default cultures based on the currently set language CultureInfo.DefaultThreadCurrentCulture = GlobalSettings.CultureInfo; CultureInfo.DefaultThreadCurrentUICulture = GlobalSettings.CultureInfo; string strPostErrorMessage = string.Empty; string settingsDirectoryPath = Path.Combine(Utils.GetStartupPath, "settings"); if (!Directory.Exists(settingsDirectoryPath)) { try { Directory.CreateDirectory(settingsDirectoryPath); } catch (UnauthorizedAccessException ex) { string strMessage = LanguageManager.GetString("Message_Insufficient_Permissions_Warning", GlobalSettings.Language, false); if (string.IsNullOrEmpty(strMessage)) { strMessage = ex.ToString(); } strPostErrorMessage = strMessage; } catch (Exception ex) { strPostErrorMessage = ex.ToString(); } } IsMono = Type.GetType("Mono.Runtime") != null; // Delete old ProfileOptimization file because we don't want it anymore, instead we restart profiling for each newly generated assembly Utils.SafeDeleteFile(Path.Combine(Utils.GetStartupPath, "chummerprofile")); // We avoid weird issues with ProfileOptimization pointing JIT to the wrong place by checking for and removing all profile optimization files that // were made in an older version (i.e. an older assembly) string strProfileOptimizationName = "chummerprofile_" + Utils.CurrentChummerVersion + ".profile"; foreach (string strProfileFile in Directory.GetFiles(Utils.GetStartupPath, "*.profile", SearchOption.TopDirectoryOnly)) { if (!string.Equals(strProfileFile, strProfileOptimizationName, StringComparison.OrdinalIgnoreCase)) { Utils.SafeDeleteFile(strProfileFile); } } // Mono, non-Windows native stuff, and Win11 don't always play nice with ProfileOptimization, so it's better to just not bother with it when running under them if (!IsMono && Utils.HumanReadableOSVersion.StartsWith("Windows") && !Utils.HumanReadableOSVersion.StartsWith("Windows 11")) { ProfileOptimization.SetProfileRoot(Utils.GetStartupPath); ProfileOptimization.StartProfile(strProfileOptimizationName); } Stopwatch sw = Stopwatch.StartNew(); //If debugging and launched from other place (Bootstrap), launch debugger if (Environment.GetCommandLineArgs().Contains("/debug") && !Debugger.IsAttached) { Debugger.Launch(); } sw.TaskEnd("dbgchk"); //Various init stuff (that mostly "can" be removed as they serve //debugging more than function //Needs to be called before Log is setup, as it moves where log might be. FixCwd(); sw.TaskEnd("fixcwd"); AppDomain.CurrentDomain.FirstChanceException += ExceptionHeatMap.OnException; sw.TaskEnd("appdomain 2"); string strInfo = string.Format(GlobalSettings.InvariantCultureInfo, "Application Chummer5a build {0} started at {1} with command line arguments {2}", Utils.CurrentChummerVersion, DateTime.UtcNow, Environment.CommandLine); sw.TaskEnd("infogen"); sw.TaskEnd("infoprnt"); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); sw.TaskEnd("languagefreestartup"); void HandleCrash(object o, UnhandledExceptionEventArgs exa) { if (exa.ExceptionObject is Exception ex) { try { if (GlobalSettings.UseLoggingApplicationInsights >= UseAILogging.Crashes && ChummerTelemetryClient != null && !Utils.IsMilestoneVersion) { ExceptionTelemetry et = new ExceptionTelemetry(ex) { SeverityLevel = SeverityLevel.Critical }; //we have to enable the uploading of THIS message, so it isn't filtered out in the DropUserdataTelemetryProcessos foreach (DictionaryEntry d in ex.Data) { if ((d.Key != null) && (d.Value != null)) { et.Properties.Add(d.Key.ToString(), d.Value.ToString()); } } et.Properties.Add("IsCrash", bool.TrueString); CustomTelemetryInitializer ti = new CustomTelemetryInitializer(); ti.Initialize(et); ChummerTelemetryClient.TrackException(et); ChummerTelemetryClient.Flush(); } } catch (Exception ex1) { Log.Error(ex1); } #if !DEBUG CrashHandler.WebMiniDumpHandler(ex); #endif } } AppDomain.CurrentDomain.UnhandledException += HandleCrash; sw.TaskEnd("Startup"); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); if (!string.IsNullOrEmpty(LanguageManager.ManagerErrorMessage)) { // MainForm is null at the moment, so we have to show error box manually MessageBox.Show(LanguageManager.ManagerErrorMessage, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (!string.IsNullOrEmpty(GlobalSettings.ErrorMessage)) { // MainForm is null at the moment, so we have to show error box manually MessageBox.Show(GlobalSettings.ErrorMessage, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (!string.IsNullOrEmpty(strPostErrorMessage)) { // MainForm is null at the moment, so we have to show error box manually MessageBox.Show(strPostErrorMessage, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { TelemetryConfiguration.Active.InstrumentationKey = "012fd080-80dc-4c10-97df-4f2cf8c805d5"; LogManager.ThrowExceptions = true; if (IsMono) { //Mono Crashes because of Application Insights. Set Logging to local, when Mono Runtime is detected GlobalSettings.UseLoggingApplicationInsights = UseAILogging.OnlyLocal; } if (GlobalSettings.UseLoggingApplicationInsights > UseAILogging.OnlyMetric) { ConfigurationItemFactory.Default.Targets.RegisterDefinition( "ApplicationInsightsTarget", typeof(ApplicationInsightsTarget) ); } LogManager.ThrowExceptions = false; Log = LogManager.GetCurrentClassLogger(); if (GlobalSettings.UseLogging) { foreach (LoggingRule objRule in LogManager.Configuration.LoggingRules) { #if DEBUG //enable logging to EventLog when Debugging if (objRule.Levels.Count == 0 && objRule.RuleName == "ELChummer") { objRule.EnableLoggingForLevels(LogLevel.Trace, LogLevel.Fatal); } #endif //only change the loglevel, if it's off - otherwise it has been changed manually if (objRule.Levels.Count == 0) { objRule.EnableLoggingForLevels(LogLevel.Debug, LogLevel.Fatal); } } } if (Settings.Default.UploadClientId == Guid.Empty) { Settings.Default.UploadClientId = Guid.NewGuid(); Settings.Default.Save(); } if (!Utils.IsUnitTest && GlobalSettings.UseLoggingApplicationInsights >= UseAILogging.OnlyMetric) { #if DEBUG //If you set true as DeveloperMode (see above), you can see the sending telemetry in the debugging output window in IDE. TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = true; #else TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = false; #endif TelemetryConfiguration.Active.TelemetryInitializers.Add(new CustomTelemetryInitializer()); TelemetryConfiguration.Active.TelemetryProcessorChainBuilder.Use(next => new TranslateExceptionTelemetryProcessor(next)); TelemetryConfiguration.Active.TelemetryProcessorChainBuilder.Use(next => new DropUserdataTelemetryProcessor(next, Environment.UserName)); TelemetryConfiguration.Active.TelemetryProcessorChainBuilder.Build(); //for now lets disable live view.We may make another GlobalOption to enable it at a later stage... //var live = new LiveStreamProvider(ApplicationInsightsConfig); //live.Enable(); //Log an Event with AssemblyVersion and CultureInfo MetricIdentifier objMetricIdentifier = new MetricIdentifier("Chummer", "Program Start", "Version", "Culture", "AISetting", "OSVersion"); string strOSVersion = Utils.HumanReadableOSVersion; Metric objMetric = ChummerTelemetryClient.GetMetric(objMetricIdentifier); objMetric.TrackValue(1, Utils.CurrentChummerVersion.ToString(), CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, GlobalSettings.UseLoggingApplicationInsights.ToString(), strOSVersion); //Log a page view: pvt = new PageViewTelemetry("frmChummerMain()") { Name = "Chummer Startup: " + Utils.CurrentChummerVersion, Id = Settings.Default.UploadClientId.ToString(), Timestamp = startTime }; pvt.Context.Operation.Name = "Operation Program.Main()"; pvt.Properties.Add("parameters", Environment.CommandLine); UploadObjectAsMetric.UploadObject(ChummerTelemetryClient, typeof(GlobalSettings)); } else { TelemetryConfiguration.Active.DisableTelemetry = true; } Log.Info(strInfo); Log.Info("Logging options are set to " + GlobalSettings.UseLogging + " and Upload-Options are set to " + GlobalSettings.UseLoggingApplicationInsights + " (Installation-Id: " + Settings.Default.UploadClientId.ToString("D", GlobalSettings.InvariantCultureInfo) + ")."); //make sure the Settings are upgraded/preserved after an upgrade //see for details: https://stackoverflow.com/questions/534261/how-do-you-keep-user-config-settings-across-different-assembly-versions-in-net/534335#534335 if (Settings.Default.UpgradeRequired) { if (UnblockPath(AppDomain.CurrentDomain.BaseDirectory)) { Settings.Default.Upgrade(); Settings.Default.UpgradeRequired = false; Settings.Default.Save(); } else { Log.Warn("Files could not be unblocked in " + AppDomain.CurrentDomain.BaseDirectory); } } } catch (Exception e) { Console.WriteLine(e); Log.Error(e); #if DEBUG throw; #endif } //load the plugins and maybe work of any command line arguments //arguments come in the form of // /plugin:Name:Parameter:Argument // /plugin:SINners:RegisterUriScheme:0 bool showMainForm = !Utils.IsUnitTest; bool blnRestoreDefaultLanguage; try { // Make sure the default language has been loaded before attempting to open the Main Form. blnRestoreDefaultLanguage = !LanguageManager.LoadLanguage(GlobalSettings.Language); } // This to catch and handle an extremely strange issue where Chummer tries to load a language it shouldn't and ends up // dereferencing a null value that should be impossible by static code analysis. This code here is a failsafe so that // it at least keeps working in English instead of crashing. catch (NullReferenceException) { Utils.BreakIfDebug(); blnRestoreDefaultLanguage = true; } // Restore Chummer's language to en-US if we failed to load the default one. if (blnRestoreDefaultLanguage) { GlobalSettings.Language = GlobalSettings.DefaultLanguage; } MainForm = new ChummerMainForm(); try { PluginLoader.LoadPlugins(); } catch (ApplicationException) { showMainForm = false; } if (!Utils.IsUnitTest) { string[] strArgs = Environment.GetCommandLineArgs(); try { // Process plugin args synchronously because plugin load order can end up mattering foreach (string strArg in strArgs) { if (!strArg.Contains("/plugin")) { continue; } if (!GlobalSettings.PluginsEnabled) { const string strMessage = "Please enable Plugins to use command-line arguments invoking specific plugin-functions!"; Log.Warn(strMessage); MainForm.ShowMessageBox(strMessage, "Plugins not enabled", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } else { string strWhatPlugin = strArg.Substring(strArg.IndexOf("/plugin", StringComparison.Ordinal) + 8); //some external apps choose to add a '/' before a ':' even in the middle of an url... strWhatPlugin = strWhatPlugin.TrimStart(':'); int intEndPlugin = strWhatPlugin.IndexOf(':'); string strParameter = strWhatPlugin.Substring(intEndPlugin + 1); strWhatPlugin = strWhatPlugin.Substring(0, intEndPlugin); IPlugin objActivePlugin = PluginLoader.MyActivePlugins.Find(a => a.ToString() == strWhatPlugin); if (objActivePlugin == null) { if (PluginLoader.MyPlugins.All(a => a.ToString() != strWhatPlugin)) { string strMessage = "Plugin " + strWhatPlugin + " is not enabled in the options!" + Environment.NewLine + "If you want to use command-line arguments, please enable this plugin and restart the program."; Log.Warn(strMessage); MainForm.ShowMessageBox(strMessage, strWhatPlugin + " not enabled", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } else { showMainForm &= objActivePlugin.ProcessCommandLine(strParameter); } } } } catch (Exception e) { ExceptionTelemetry ex = new ExceptionTelemetry(e) { SeverityLevel = SeverityLevel.Warning }; ChummerTelemetryClient?.TrackException(ex); Log.Warn(e); } } // Delete the old executable if it exists (created by the update process). Utils.SafeClearDirectory(Utils.GetStartupPath, "*.old"); // Purge the temporary directory Utils.SafeClearDirectory(Utils.GetTempPath()); if (showMainForm) { MainForm.MyStartupPvt = pvt; Application.Run(MainForm); } PluginLoader?.Dispose(); Log.Info(ExceptionHeatMap.GenerateInfo()); if (GlobalSettings.UseLoggingApplicationInsights > UseAILogging.OnlyLocal && ChummerTelemetryClient != null) { ChummerTelemetryClient.Flush(); //we have to wait a bit to give it time to upload the data Console.WriteLine("Waiting a bit to flush logging data..."); Utils.SafeSleep(TimeSpan.FromSeconds(2)); } } finally { GlobalChummerMutex.ReleaseMutex(); } } }