diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.Background.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.Background.cs new file mode 100644 index 000000000..c7f18056b --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.Background.cs @@ -0,0 +1,193 @@ +using CollapseLauncher.Helper.Background; +using CollapseLauncher.Helper.Image; +using CollapseLauncher.Helper.Metadata; +using Hi3Helper; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using static CollapseLauncher.InnerLauncherConfig; +using static CollapseLauncher.Statics.GamePropertyVault; +using static Hi3Helper.Logger; +using static Hi3Helper.Shared.Region.LauncherConfig; + +// ReSharper disable CheckNamespace +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Local +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable IdentifierTypo +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault + +namespace CollapseLauncher; + +public partial class MainPage : Page +{ + private static void BackgroundImg_IsImageHideEvent(object sender, bool e) + { + if (e) CurrentBackgroundHandler?.Dimm(); + else CurrentBackgroundHandler?.Undimm(); + } + + private readonly HashSet _processingBackground = []; + private async void CustomBackgroundChanger_Event(object sender, BackgroundImgProperty e) + { + if (_processingBackground.Contains(e.ImgPath)) + { + LogWriteLine($"Background {e.ImgPath} is already being processed!", LogType.Warning, true); + return; + } + + try + { + _processingBackground.Add(e.ImgPath); + var gameLauncherApi = LauncherMetadataHelper.CurrentMetadataConfig?.GameLauncherApi; + if (gameLauncherApi == null) + { + return; + } + + gameLauncherApi.GameBackgroundImgLocal = e.ImgPath; + IsCustomBG = e.IsCustom; + + if (!File.Exists(gameLauncherApi.GameBackgroundImgLocal)) + { + LogWriteLine($"Custom background file {e.ImgPath} is missing!", LogType.Warning, true); + gameLauncherApi.GameBackgroundImgLocal = AppDefaultBG; + } + + var mType = BackgroundMediaUtility.GetMediaType(gameLauncherApi.GameBackgroundImgLocal); + switch (mType) + { + case BackgroundMediaUtility.MediaType.Media: + BackgroundNewMediaPlayerGrid.Visibility = Visibility.Visible; + BackgroundNewBackGrid.Visibility = Visibility.Collapsed; + break; + case BackgroundMediaUtility.MediaType.StillImage: + FileStream imgStream = + await ImageLoaderHelper.LoadImage(gameLauncherApi.GameBackgroundImgLocal); + BackgroundMediaUtility.SetAlternativeFileStream(imgStream); + BackgroundNewMediaPlayerGrid.Visibility = Visibility.Collapsed; + BackgroundNewBackGrid.Visibility = Visibility.Visible; + break; + case BackgroundMediaUtility.MediaType.Unknown: + default: + throw new InvalidCastException(); + } + + await InitBackgroundHandler(); + CurrentBackgroundHandler?.LoadBackground(gameLauncherApi.GameBackgroundImgLocal, e.IsRequestInit, + e.IsForceRecreateCache, ex => + { + gameLauncherApi.GameBackgroundImgLocal = + AppDefaultBG; + LogWriteLine($"An error occured while loading background {e.ImgPath}\r\n{ex}", + LogType.Error, true); + ErrorSender.SendException(ex); + }, e.ActionAfterLoaded); + } + catch (Exception ex) + { + LogWriteLine($"An error occured while loading background {e.ImgPath}\r\n{ex}", + LogType.Error, true); + ErrorSender.SendException(new Exception($"An error occured while loading background {e.ImgPath}", ex)); + } + finally + { + _processingBackground.Remove(e.ImgPath); + } + } + + internal async Task ChangeBackgroundImageAsRegionAsync(bool ShowLoadingMsg = false) + { + var gameLauncherApi = LauncherMetadataHelper.CurrentMetadataConfig?.GameLauncherApi; + if (gameLauncherApi == null) + { + return; + } + + GamePresetProperty currentGameProperty = GetCurrentGameProperty(); + bool isUseCustomPerRegionBg = currentGameProperty.GameSettings?.SettingsCollapseMisc?.UseCustomRegionBG ?? false; + + IsCustomBG = GetAppConfigValue("UseCustomBG").ToBool(); + bool isAPIBackgroundAvailable = + !string.IsNullOrEmpty(gameLauncherApi.GameBackgroundImg); + + var posterBg = currentGameProperty.GameVersion.GameType switch + { + GameNameType.Honkai => Path.Combine(AppExecutableDir, + @"Assets\Images\GameBackground\honkai.webp"), + GameNameType.Genshin => Path.Combine(AppExecutableDir, + @"Assets\Images\GameBackground\genshin.webp"), + GameNameType.StarRail => Path.Combine(AppExecutableDir, + @"Assets\Images\GameBackground\starrail.webp"), + GameNameType.Zenless => Path.Combine(AppExecutableDir, + @"Assets\Images\GameBackground\zzz.webp"), + _ => AppDefaultBG + }; + + // Check if Regional Custom BG is enabled and available + if (isUseCustomPerRegionBg) + { + var regionBgPath = currentGameProperty.GameSettings?.SettingsCollapseMisc?.CustomRegionBGPath; + if (!string.IsNullOrEmpty(regionBgPath) && File.Exists(regionBgPath)) + { + if (BackgroundMediaUtility.GetMediaType(regionBgPath) == BackgroundMediaUtility.MediaType.StillImage) + { + FileStream imgStream = await ImageLoaderHelper.LoadImage(regionBgPath); + BackgroundMediaUtility.SetAlternativeFileStream(imgStream); + } + + gameLauncherApi.GameBackgroundImgLocal = regionBgPath; + } + } + // If not, then check for global Custom BG + else + { + var BGPath = IsCustomBG ? GetAppConfigValue("CustomBGPath").ToString() : null; + if (!string.IsNullOrEmpty(BGPath)) + { + gameLauncherApi.GameBackgroundImgLocal = BGPath; + } + // If it's still not, then check if API gives any background + else if (isAPIBackgroundAvailable) + { + try + { + await DownloadBackgroundImage(CancellationToken.None); + return; // Return after successfully loading + } + catch (Exception ex) + { + ErrorSender.SendException(ex); + LogWriteLine($"Failed while downloading default background image!\r\n{ex}", LogType.Error, true); + gameLauncherApi.GameBackgroundImgLocal = AppDefaultBG; + } + } + // IF ITS STILL NOT THERE, then use fallback game poster, IF ITS STILL NOT THEREEEE!! use paimon cute deadge pic :) + else + { + gameLauncherApi.GameBackgroundImgLocal = posterBg; + } + } + + // Use default background if the API background is empty (in-case HoYo did something catchy) + if (!isAPIBackgroundAvailable && !IsCustomBG && LauncherMetadataHelper.CurrentMetadataConfig is { GameLauncherApi: not null }) + gameLauncherApi.GameBackgroundImgLocal ??= posterBg; + + // If the custom per region is enabled, then execute below + BackgroundImgChanger.ChangeBackground(gameLauncherApi.GameBackgroundImgLocal, + () => + { + IsFirstStartup = false; + ColorPaletteUtility.ReloadPageTheme(this, CurrentAppTheme); + }, + IsCustomBG || isUseCustomPerRegionBg, true, true); + } +} diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.KeyboardShortcut.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.KeyboardShortcut.cs new file mode 100644 index 000000000..6ffab8de3 --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.KeyboardShortcut.cs @@ -0,0 +1,483 @@ +using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.Pages; +using Hi3Helper; +using Hi3Helper.SentryHelper; +using Hi3Helper.Shared.ClassStruct; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.System; +using static CollapseLauncher.Dialogs.KeyboardShortcuts; +using static CollapseLauncher.InnerLauncherConfig; +using static Hi3Helper.Data.ConverterTool; +using static Hi3Helper.Locale; +using static Hi3Helper.Logger; +using static Hi3Helper.Shared.Region.LauncherConfig; + +// ReSharper disable CheckNamespace +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Local +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable IdentifierTypo +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault + +namespace CollapseLauncher; + +using KeybindAction = TypedEventHandler; + +public partial class MainPage : Page +{ + private readonly string ExplorerPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"); + + private void InitKeyboardShortcuts() + { + if (GetAppConfigValue("EnableShortcuts").ToBoolNullable() == null) + { + SetAndSaveConfigValue("EnableShortcuts", true); + KbShortcutList = null; + + SpawnNotificationPush( + Lang._AppNotification.NotifKbShortcutTitle, + Lang._AppNotification.NotifKbShortcutSubtitle, + NotifSeverity.Informational, + -20, + true, + false, + null, + NotificationPush.GenerateNotificationButton("", Lang._AppNotification.NotifKbShortcutBtn, (_, _) => ShowKeybinds_Invoked(null, null)), + true, + true, + true + ); + } + + if (AreShortcutsEnabled) CreateKeyboardShortcutHandlers(); + } + + private void CreateKeyboardShortcutHandlers() + { + try + { + if (KbShortcutList == null || KbShortcutList.Count == 0) + LoadKbShortcuts(); + + int numIndex = 0; + if (KbShortcutList != null) + { + VirtualKeyModifiers keyModifier = KbShortcutList["GameSelection"].Modifier; + for (; numIndex <= LauncherMetadataHelper.CurrentGameNameCount; numIndex++) + { + KeyboardAccelerator keystroke = new KeyboardAccelerator + { + Modifiers = keyModifier, + Key = VirtualKey.Number1 + numIndex + }; + keystroke.Invoked += KeyboardGameShortcut_Invoked; + KeyboardHandler.KeyboardAccelerators.Add(keystroke); + + KeyboardAccelerator keystrokeNP = new KeyboardAccelerator + { + Key = VirtualKey.NumberPad1 + numIndex + }; + keystrokeNP.Invoked += KeyboardGameShortcut_Invoked; + KeyboardHandler.KeyboardAccelerators.Add(keystrokeNP); + } + + numIndex = 0; + keyModifier = KbShortcutList["RegionSelection"].Modifier; + while (numIndex < LauncherMetadataHelper.CurrentGameRegionMaxCount) + { + KeyboardAccelerator keystroke = new KeyboardAccelerator + { + Modifiers = keyModifier, + Key = VirtualKey.Number1 + numIndex++ + }; + keystroke.Invoked += KeyboardGameRegionShortcut_Invoked; + KeyboardHandler.KeyboardAccelerators.Add(keystroke); + } + } + + KeyboardAccelerator keystrokeF5 = new KeyboardAccelerator + { + Key = VirtualKey.F5 + }; + keystrokeF5.Invoked += RefreshPage_Invoked; + KeyboardHandler.KeyboardAccelerators.Add(keystrokeF5); + + Dictionary actions = new() + { + // General + { "KbShortcutsMenu", ShowKeybinds_Invoked }, + { "HomePage", GoHome_Invoked }, + { "SettingsPage", GoSettings_Invoked }, + { "NotificationPanel", OpenNotify_Invoked }, + + // Game Related + { "ScreenshotFolder", OpenScreenshot_Invoked}, + { "GameFolder", OpenGameFolder_Invoked }, + { "CacheFolder", OpenGameCacheFolder_Invoked }, + { "ForceCloseGame", ForceCloseGame_Invoked }, + + { "RepairPage", GoGameRepair_Invoked }, + { "GameSettingsPage", GoGameSettings_Invoked }, + { "CachesPage", GoGameCaches_Invoked }, + + { "ReloadRegion", RefreshPage_Invoked } + }; + + foreach (KeyValuePair func in actions) + { + if (KbShortcutList == null) + { + continue; + } + + KeyboardAccelerator kbfunc = new KeyboardAccelerator + { + Modifiers = KbShortcutList[func.Key].Modifier, + Key = KbShortcutList[func.Key].Key + }; + kbfunc.Invoked += func.Value; + KeyboardHandler.KeyboardAccelerators.Add(kbfunc); + } + } + catch (Exception error) + { + SentryHelper.ExceptionHandler(error); + LogWriteLine(error.ToString()); + KbShortcutList = null; + CreateKeyboardShortcutHandlers(); + } + } + + private void RefreshPage_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (CannotUseKbShortcuts || !IsLoadRegionComplete) + return; + + switch (PreviousTag) + { + case "launcher": + RestoreCurrentRegion(); + ChangeRegionNoWarning(IsShowRegionChangeWarning ? ChangeRegionConfirmBtn : ChangeRegionConfirmBtnNoWarning, null); + return; + case "settings": + return; + default: + string itemTag = PreviousTag; + PreviousTag = "Empty"; + NavigateInnerSwitch(itemTag); + if (LauncherFrame != null && LauncherFrame.BackStack is { Count: > 0 }) + LauncherFrame.BackStack.RemoveAt(LauncherFrame.BackStack.Count - 1); + if (PreviousTagString is { Count: > 0 }) + PreviousTagString.RemoveAt(PreviousTagString.Count - 1); + return; + } + } + + private void DeleteKeyboardShortcutHandlers() => KeyboardHandler.KeyboardAccelerators.Clear(); + + private static async Task DisableKbShortcuts(int time = 500, CancellationToken token = default) + { + try + { + CannotUseKbShortcuts = true; + await Task.Delay(time, token); + CannotUseKbShortcuts = false; + } + catch + { + // Ignore warnings + } + } + + private void RestoreCurrentRegion() + { + string gameName = GetAppConfigValue("GameCategory")!; + #nullable enable + List? gameNameCollection = LauncherMetadataHelper.GetGameNameCollection(); + _ = LauncherMetadataHelper.GetGameRegionCollection(gameName); + + var indexCategory = gameNameCollection?.IndexOf(gameName) ?? -1; + if (indexCategory < 0) indexCategory = 0; + + var indexRegion = LauncherMetadataHelper.GetPreviousGameRegion(gameName); + + ComboBoxGameCategory.SelectedIndex = indexCategory; + ComboBoxGameRegion.SelectedIndex = indexRegion; + } + + private void KeyboardGameShortcut_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + int index = (int)sender.Key; index -= index < 96 ? 49 : 97; + + DisableInstantRegionChange = true; + RestoreCurrentRegion(); + + if (CannotUseKbShortcuts || !IsLoadRegionComplete + || index >= ComboBoxGameCategory.Items.Count + || ComboBoxGameCategory.SelectedValue == ComboBoxGameCategory.Items[index] + ) + { + DisableInstantRegionChange = false; + return; + } + + ComboBoxGameCategory.SelectedValue = ComboBoxGameCategory.Items[index]; + ComboBoxGameRegion.SelectedIndex = GetIndexOfRegionStringOrDefault(GetComboBoxGameRegionValue(ComboBoxGameCategory.SelectedValue)); + ChangeRegionNoWarning(ChangeRegionConfirmBtn, null); + ChangeRegionConfirmBtn.IsEnabled = false; + ChangeRegionConfirmBtnNoWarning.IsEnabled = false; + CannotUseKbShortcuts = true; + DisableInstantRegionChange = false; + } + + private void KeyboardGameRegionShortcut_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + int index = (int)sender.Key; index -= index < 96 ? 49 : 97; + + DisableInstantRegionChange = true; + RestoreCurrentRegion(); + + + if (CannotUseKbShortcuts || !IsLoadRegionComplete + || index >= ComboBoxGameRegion.Items.Count + || ComboBoxGameRegion.SelectedValue == ComboBoxGameRegion.Items[index]) + { + DisableInstantRegionChange = false; + return; + } + + ComboBoxGameRegion.SelectedValue = ComboBoxGameRegion.Items[index]; + ChangeRegionNoWarning(ChangeRegionConfirmBtn, null); + ChangeRegionConfirmBtn.IsEnabled = false; + ChangeRegionConfirmBtnNoWarning.IsEnabled = false; + CannotUseKbShortcuts = true; + DisableInstantRegionChange = false; + } + + private async void ShowKeybinds_Invoked(KeyboardAccelerator? sender, KeyboardAcceleratorInvokedEventArgs? args) + { + if (CannotUseKbShortcuts) return; + + await Dialog_ShowKbShortcuts(this); + } + + private void GoHome_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (!IsLoadRegionComplete || CannotUseKbShortcuts) return; + + if (NavigationViewControl.SelectedItem == NavigationViewControl.MenuItems[0]) return; + + _ = DisableKbShortcuts(); + NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems[0]; + NavigateInnerSwitch("launcher"); + + } + + private void GoSettings_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (!IsLoadRegionComplete || CannotUseKbShortcuts) return; + + if (NavigationViewControl.SelectedItem == NavigationViewControl.SettingsItem) return; + + _ = DisableKbShortcuts(); + NavigationViewControl.SelectedItem = NavigationViewControl.SettingsItem; + Navigate(typeof(SettingsPage), "settings"); + } + + private void OpenNotify_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + ToggleNotificationPanelBtn.IsChecked = !ToggleNotificationPanelBtn.IsChecked; + ToggleNotificationPanelBtnClick(null, null); + } + + private string GameDirPath => CurrentGameProperty.GameVersion.GameDirPath; + private void OpenScreenshot_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (!IsGameInstalled()) return; + + string ScreenshotFolder = Path.Combine(NormalizePath(GameDirPath), CurrentGameProperty.GameVersion.GamePreset.GameType switch + { + GameNameType.StarRail => $"{Path.GetFileNameWithoutExtension(CurrentGameProperty.GameVersion.GamePreset.GameExecutableName)}_Data\\ScreenShots", + _ => "ScreenShot" + }); + + LogWriteLine($"Opening Screenshot Folder:\r\n\t{ScreenshotFolder}"); + + if (!Directory.Exists(ScreenshotFolder)) + Directory.CreateDirectory(ScreenshotFolder); + + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = ExplorerPath, + Arguments = ScreenshotFolder + } + }.Start(); + } + + private async void OpenGameFolder_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + try + { + if (!IsGameInstalled()) return; + + string GameFolder = NormalizePath(GameDirPath); + LogWriteLine($"Opening Game Folder:\r\n\t{GameFolder}"); + await Task.Run(() => + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = ExplorerPath, + Arguments = GameFolder + } + }.Start()); + } + catch (Exception ex) + { + LogWriteLine($"Failed when trying to open game folder!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); + } + } + + private async void OpenGameCacheFolder_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + try + { + if (!IsGameInstalled()) return; + + string gameFolder = CurrentGameProperty.GameVersion.GameDirAppDataPath; + LogWriteLine($"Opening Game Folder:\r\n\t{gameFolder}"); + await Task.Run(() => + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = ExplorerPath, + Arguments = gameFolder + } + }.Start()); + } + catch (Exception ex) + { + LogWriteLine($"Failed when trying to open game cache folder!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); + } + } + + private void ForceCloseGame_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (!CurrentGameProperty.IsGameRunning) return; + + PresetConfig gamePreset = CurrentGameProperty.GameVersion.GamePreset; + string? gamePresetExecName = gamePreset.GameExecutableName; + if (string.IsNullOrEmpty(gamePresetExecName)) + { + return; + } + + try + { + Process[] gameProcess = Process.GetProcessesByName(gamePresetExecName.Split('.')[0]); + foreach (var p in gameProcess) + { + LogWriteLine($"Trying to stop game process {gamePresetExecName.Split('.')[0]} at PID {p.Id}", LogType.Scheme, true); + p.Kill(); + } + } + catch (Win32Exception ex) + { + SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"There is a problem while trying to stop Game with Region: {gamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); + } + } + private void GoGameRepair_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (!IsLoadRegionComplete || CannotUseKbShortcuts) return; + + if (NavigationViewControl.SelectedItem == NavigationViewControl.MenuItems[2]) return; + + _ = DisableKbShortcuts(); + NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems[2]; + NavigateInnerSwitch("repair"); + } + + private void GoGameCaches_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (!IsLoadRegionComplete || CannotUseKbShortcuts) + return; + if (NavigationViewControl.SelectedItem == NavigationViewControl.MenuItems[3]) + return; + + _ = DisableKbShortcuts(); + NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems[3]; + NavigateInnerSwitch("caches"); + } + + private void GoGameSettings_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (!IsLoadRegionComplete || CannotUseKbShortcuts) + return; + + if (NavigationViewControl.SelectedItem == NavigationViewControl.FooterMenuItems.Last()) + return; + + _ = DisableKbShortcuts(); + NavigationViewControl.SelectedItem = NavigationViewControl.FooterMenuItems.Last(); + switch (CurrentGameProperty.GamePreset) + { + case { GameType: GameNameType.Honkai }: + Navigate(typeof(HonkaiGameSettingsPage), "honkaigamesettings"); + break; + case { GameType: GameNameType.Genshin }: + Navigate(typeof(GenshinGameSettingsPage), "genshingamesettings"); + break; + case { GameType: GameNameType.StarRail }: + Navigate(typeof(StarRailGameSettingsPage), "starrailgamesettings"); + break; + case { GameType: GameNameType.Zenless }: + Navigate(typeof(ZenlessGameSettingsPage), "zenlessgamesettings"); + break; + } + } + + private static bool AreShortcutsEnabled + { + get => GetAppConfigValue("EnableShortcuts").ToBool(true); + } + + private void SettingsPage_KeyboardShortcutsEvent(object sender, int e) + { + switch (e) + { + case 0: + CreateKeyboardShortcutHandlers(); + break; + case 1: + DeleteKeyboardShortcutHandlers(); + CreateKeyboardShortcutHandlers(); + break; + case 2: + DeleteKeyboardShortcutHandlers(); + break; + } + } +} \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs new file mode 100644 index 000000000..d1e545bd9 --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs @@ -0,0 +1,423 @@ +using CollapseLauncher.Extension; +using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.Interfaces; +using CollapseLauncher.Pages; +using CommunityToolkit.WinUI; +using Hi3Helper; +using Microsoft.UI; +using Microsoft.UI.Input; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Animation; +using System; +using System.Linq; +using System.Threading.Tasks; +using static CollapseLauncher.InnerLauncherConfig; +using static CollapseLauncher.Statics.GamePropertyVault; +using static Hi3Helper.Logger; +using static Hi3Helper.Shared.Region.LauncherConfig; + +// ReSharper disable CheckNamespace +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Local +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable IdentifierTypo +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault + +namespace CollapseLauncher; + +public partial class MainPage : Page +{ + private void InitializeNavigationItems(bool ResetSelection = true) + { + DispatcherQueue.TryEnqueue(() => + { + NavigationViewControl.IsSettingsVisible = true; + NavigationViewControl.MenuItems.Clear(); + NavigationViewControl.FooterMenuItems.Clear(); + + IGameVersion CurrentGameVersionCheck = GetCurrentGameProperty().GameVersion; + + FontIcon IconLauncher = new FontIcon { Glyph = "" }; + FontIcon IconRepair = new FontIcon { Glyph = "" }; + FontIcon IconCaches = new FontIcon { Glyph = m_isWindows11 ? "" : "" }; + FontIcon IconGameSettings = new FontIcon { Glyph = "" }; + FontIcon IconAppSettings = new FontIcon { Glyph = "" }; + + if (m_appMode == AppMode.Hi3CacheUpdater) + { + if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) + { + NavigationViewControl.MenuItems.Add(new NavigationViewItem + { Icon = IconCaches, Tag = "caches" } + .BindNavigationViewItemText("_CachesPage", "PageTitle")); + } + return; + } + + NavigationViewControl.MenuItems.Add(new NavigationViewItem + { Icon = IconLauncher, Tag = "launcher" } + .BindNavigationViewItemText("_HomePage", "PageTitle")); + + NavigationViewControl.MenuItems.Add(new NavigationViewItemHeader() + .BindNavigationViewItemText("_MainPage", "NavigationUtilities")); + + if (CurrentGameVersionCheck.GamePreset.IsRepairEnabled ?? false) + { + NavigationViewControl.MenuItems.Add(new NavigationViewItem + { Icon = IconRepair, Tag = "repair" } + .BindNavigationViewItemText("_GameRepairPage", "PageTitle")); + } + + if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) + { + NavigationViewControl.MenuItems.Add(new NavigationViewItem + { Icon = IconCaches, Tag = "caches" } + .BindNavigationViewItemText("_CachesPage", "PageTitle")); + } + + switch (CurrentGameVersionCheck.GameType) + { + case GameNameType.Honkai: + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem + { Icon = IconGameSettings, Tag = "honkaigamesettings" } + .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); + break; + case GameNameType.StarRail: + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem + { Icon = IconGameSettings, Tag = "starrailgamesettings" } + .BindNavigationViewItemText("_StarRailGameSettingsPage", "PageTitle")); + break; + case GameNameType.Genshin: + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem + { Icon = IconGameSettings, Tag = "genshingamesettings" } + .BindNavigationViewItemText("_GenshinGameSettingsPage", "PageTitle")); + break; + case GameNameType.Zenless: + NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem + { Icon = IconGameSettings, Tag = "zenlessgamesettings" } + .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); + break; + } + + if (NavigationViewControl.SettingsItem is NavigationViewItem SettingsItem) + { + SettingsItem.Icon = IconAppSettings; + _ = SettingsItem.BindNavigationViewItemText("_SettingsPage", "PageTitle"); + } + + foreach (var dependency in NavigationViewControl.FindDescendants().OfType()) + { + // Avoid any icons to have shadow attached if it's not from this page + if (dependency.BaseUri.AbsolutePath != BaseUri.AbsolutePath) + { + continue; + } + + switch (dependency) + { + case FontIcon icon: + AttachShadowNavigationPanelItem(icon); + break; + case AnimatedIcon animIcon: + AttachShadowNavigationPanelItem(animIcon); + break; + } + } + AttachShadowNavigationPanelItem(IconAppSettings); + + if (ResetSelection) + { + NavigationViewControl.SelectedItem = (NavigationViewItem)NavigationViewControl.MenuItems[0]; + } + + NavigationViewControl.ApplyNavigationViewItemLocaleTextBindings(); + + InputSystemCursor handCursor = InputSystemCursor.Create(InputSystemCursorShape.Hand); + MainPageGrid.SetAllControlsCursorRecursive(handCursor); + }); + } + + private static void AttachShadowNavigationPanelItem(FrameworkElement element) + { + bool isAppLight = IsAppThemeLight; + Windows.UI.Color shadowColor = isAppLight ? Colors.White : Colors.Black; + double shadowBlurRadius = isAppLight ? 20 : 15; + double shadowOpacity = isAppLight ? 0.5 : 0.3; + + element.ApplyDropShadow(shadowColor, shadowBlurRadius, shadowOpacity); + } + + private void NavView_Loaded(object sender, RoutedEventArgs e) + { + foreach (NavigationViewItemBase item in NavigationViewControl.MenuItems) + { + if (item is not NavigationViewItem || item.Tag.ToString() != "launcher") + { + continue; + } + + NavigationViewControl.SelectedItem = item; + break; + } + + NavViewPaneBackground.OpacityTransition = new ScalarTransition + { + Duration = TimeSpan.FromMilliseconds(150) + }; + NavViewPaneBackground.TranslationTransition = new Vector3Transition + { + Duration = TimeSpan.FromMilliseconds(150) + }; + + var paneMainGrid = NavigationViewControl.FindDescendant("PaneContentGrid"); + if (paneMainGrid is Grid paneMainGridAsGrid) + { + paneMainGridAsGrid.PointerEntered += NavView_PanePointerEntered; + paneMainGridAsGrid.PointerExited += NavView_PanePointerExited; + } + + // The toggle button is not a part of pane. Why Microsoft!!! + var paneToggleButtonGrid = (Grid)NavigationViewControl.FindDescendant("PaneToggleButtonGrid"); + if (paneToggleButtonGrid != null) + { + paneToggleButtonGrid.PointerEntered += NavView_PanePointerEntered; + paneToggleButtonGrid.PointerExited += NavView_PanePointerExited; + } + + // var backIcon = NavigationViewControl.FindDescendant("NavigationViewBackButton")?.FindDescendant(); + // backIcon?.ApplyDropShadow(Colors.Gray, 20); + + var toggleIcon = NavigationViewControl.FindDescendant("TogglePaneButton")?.FindDescendant(); + toggleIcon?.ApplyDropShadow(Colors.Gray, 20); + } + + private void NavView_PanePointerEntered(object sender, PointerRoutedEventArgs e) + { + IsCursorInNavBarHoverArea = true; + NavViewPaneBackground.Opacity = 1; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); + } + + private bool IsCursorInNavBarHoverArea; + + private void NavView_PanePointerExited(object sender, PointerRoutedEventArgs e) + { + PointerPoint pointerPoint = e.GetCurrentPoint(NavViewPaneBackgroundHoverArea); + IsCursorInNavBarHoverArea = pointerPoint.Position.X <= NavViewPaneBackgroundHoverArea.Width - 8 && pointerPoint.Position.X > 4; + + switch (IsCursorInNavBarHoverArea) + { + case false when !NavigationViewControl.IsPaneOpen: + NavViewPaneBackground.Opacity = 0; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); + break; + case true when !NavigationViewControl.IsPaneOpen: + NavViewPaneBackground.Opacity = 1; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); + break; + } + } + + private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) + { + if (!IsLoadFrameCompleted) return; + if (args.IsSettingsInvoked && PreviousTag != "settings") Navigate(typeof(SettingsPage), "settings"); + + #nullable enable + NavigationViewItem? item = sender.MenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); + item ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); + if (item == null) return; + #nullable restore + + string itemTag = (string)item.Tag; + + NavigateInnerSwitch(itemTag); + } + + private void NavigateInnerSwitch(string itemTag) + { + if (itemTag == PreviousTag) return; + switch (itemTag) + { + case "launcher": + Navigate(typeof(HomePage), itemTag); + break; + + case "repair": + if (!(GetCurrentGameProperty().GameVersion.GamePreset.IsRepairEnabled ?? false)) + Navigate(typeof(UnavailablePage), itemTag); + else + Navigate(IsGameInstalled() ? typeof(RepairPage) : typeof(NotInstalledPage), itemTag); + break; + + case "caches": + if (GetCurrentGameProperty().GameVersion.GamePreset.IsCacheUpdateEnabled ?? false) + Navigate(IsGameInstalled() || (m_appMode == AppMode.Hi3CacheUpdater && GetCurrentGameProperty().GameVersion.GamePreset.GameType == GameNameType.Honkai) ? typeof(CachesPage) : typeof(NotInstalledPage), itemTag); + else + Navigate(typeof(UnavailablePage), itemTag); + break; + + case "honkaigamesettings": + Navigate(IsGameInstalled() ? typeof(HonkaiGameSettingsPage) : typeof(NotInstalledPage), itemTag); + break; + + case "starrailgamesettings": + Navigate(IsGameInstalled() ? typeof(StarRailGameSettingsPage) : typeof(NotInstalledPage), itemTag); + break; + + case "genshingamesettings": + Navigate(IsGameInstalled() ? typeof(GenshinGameSettingsPage) : typeof(NotInstalledPage), itemTag); + break; + + case "zenlessgamesettings": + Navigate(IsGameInstalled() ? typeof(ZenlessGameSettingsPage) : typeof(NotInstalledPage), itemTag); + break; + } + } + + private void Navigate(Type sourceType, string tagStr) + { + MainFrameChanger.ChangeMainFrame(sourceType, new DrillInNavigationTransitionInfo()); + PreviousTag = tagStr; + PreviousTagString.Add(tagStr); + LogWriteLine($"Page changed to {sourceType.Name} with Tag: {tagStr}", LogType.Scheme); + } + + internal void InvokeMainPageNavigateByTag(string tagStr) + { + NavigationViewItem item = NavigationViewControl.MenuItems.OfType().FirstOrDefault(x => x.Tag is string tag && tag == tagStr); + if (item == null) + { + return; + } + + NavigationViewControl.SelectedItem = item; + string tag = (string)item.Tag; + NavigateInnerSwitch(tag); + } + + private void ToggleNotificationPanelBtnClick(object sender, RoutedEventArgs e) + { + IsNotificationPanelShow = ToggleNotificationPanelBtn.IsChecked ?? false; + ShowHideNotificationPanel(); + } + + private void ShowHideNotificationPanel() + { + NewNotificationCountBadge.Value = 0; + NewNotificationCountBadge.Visibility = Visibility.Collapsed; + Thickness lastMargin = NotificationPanel.Margin; + lastMargin.Right = IsNotificationPanelShow ? 0 : NotificationPanel.ActualWidth * -1; + NotificationPanel.Margin = lastMargin; + + ShowHideNotificationLostFocusBackground(IsNotificationPanelShow); + } + + private async void ShowHideNotificationLostFocusBackground(bool show) + { + if (show) + { + NotificationLostFocusBackground.Visibility = Visibility.Visible; + NotificationLostFocusBackground.Opacity = 0.3; + NotificationPanel.Translation += Shadow48; + ToggleNotificationPanelBtn.Translation -= Shadow16; + ((FontIcon)ToggleNotificationPanelBtn.Content).FontFamily = FontCollections.FontAwesomeSolid; + } + else + { + NotificationLostFocusBackground.Opacity = 0; + NotificationPanel.Translation -= Shadow48; + ToggleNotificationPanelBtn.Translation += Shadow16; + ((FontIcon)ToggleNotificationPanelBtn.Content).FontFamily = FontCollections.FontAwesomeRegular; + await Task.Delay(200); + NotificationLostFocusBackground.Visibility = Visibility.Collapsed; + } + } + + private void NotificationContainerBackground_PointerPressed(object sender, PointerRoutedEventArgs e) + { + IsNotificationPanelShow = false; + ToggleNotificationPanelBtn.IsChecked = false; + ShowHideNotificationPanel(); + } + + private void NavigationViewControl_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) + { + if (!LauncherFrame.CanGoBack || !IsLoadFrameCompleted) + { + return; + } + + LauncherFrame.GoBack(); + if (PreviousTagString.Count < 1) return; + + string lastPreviousTag = PreviousTagString[^1]; + string currentNavigationItemTag = (string)((NavigationViewItem)sender.SelectedItem).Tag; + + if (!string.Equals(lastPreviousTag, currentNavigationItemTag, StringComparison.CurrentCultureIgnoreCase)) + { + return; + } + + string goLastPreviousTag = PreviousTagString[^2]; + + #nullable enable + NavigationViewItem? goPreviousNavigationItem = sender.MenuItems.OfType().FirstOrDefault(x => goLastPreviousTag == (string)x.Tag); + goPreviousNavigationItem ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => goLastPreviousTag == (string)x.Tag); + #nullable restore + + if (goLastPreviousTag == "settings") + { + PreviousTag = goLastPreviousTag; + PreviousTagString.RemoveAt(PreviousTagString.Count - 1); + sender.SelectedItem = sender.SettingsItem; + return; + } + + if (goPreviousNavigationItem == null) + { + return; + } + + PreviousTag = goLastPreviousTag; + PreviousTagString.RemoveAt(PreviousTagString.Count - 1); + sender.SelectedItem = goPreviousNavigationItem; + } + + private void NavigationPanelOpening_Event(NavigationView sender, object args) + { + Thickness curMargin = GridBG_Icon.Margin; + curMargin.Left = 48; + GridBG_Icon.Margin = curMargin; + IsTitleIconForceShow = true; + ToggleTitleIcon(false); + + NavViewPaneBackgroundHoverArea.Width = NavigationViewControl.OpenPaneLength; + } + + private async void NavigationPanelClosing_Event(NavigationView sender, NavigationViewPaneClosingEventArgs args) + { + Thickness curMargin = GridBG_Icon.Margin; + curMargin.Left = 58; + GridBG_Icon.Margin = curMargin; + IsTitleIconForceShow = false; + ToggleTitleIcon(true); + + NavViewPaneBackgroundHoverArea.Width = NavViewPaneBackground.Width; + + await Task.Delay(200); + if (IsCursorInNavBarHoverArea) + { + return; + } + + NavViewPaneBackground.Opacity = 0; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); + } +} diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.Notification.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.Notification.cs new file mode 100644 index 000000000..b3b751de0 --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.Notification.cs @@ -0,0 +1,500 @@ +using CollapseLauncher.Extension; +using CollapseLauncher.Helper; +using CollapseLauncher.Helper.StreamUtility; +using CollapseLauncher.Helper.Update; +using CollapseLauncher.Pages.OOBE; +using Hi3Helper; +using Hi3Helper.SentryHelper; +using Hi3Helper.Shared.ClassStruct; +using Hi3Helper.Win32.ToastCOM.Notification; +using InnoSetupHelper; +using Microsoft.UI.Text; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Windows.Foundation; +using static CollapseLauncher.InnerLauncherConfig; +using static CollapseLauncher.Statics.GamePropertyVault; +using static Hi3Helper.Locale; +using static Hi3Helper.Logger; +using static Hi3Helper.Shared.Region.LauncherConfig; +using UIElementExtensions = CollapseLauncher.Extension.UIElementExtensions; +// ReSharper disable CheckNamespace +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Local +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable IdentifierTypo +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault + +namespace CollapseLauncher; + +public partial class MainPage : Page +{ + private void NotificationInvoker_EventInvoker(object sender, NotificationInvokerProp e) + { + if (e.IsCustomNotif) + { + if (e.CustomNotifAction == NotificationCustomAction.Add) + { + SpawnNotificationoUI(e.Notification.MsgId, e.OtherContent as InfoBar); + } + else + { + RemoveNotificationUI(e.Notification.MsgId); + } + return; + } + + SpawnNotificationPush(e.Notification.Title, e.Notification.Message, e.Notification.Severity, + e.Notification.MsgId, e.Notification.IsClosable ?? true, e.Notification.IsDisposable ?? true, e.CloseAction, + e.OtherContent, e.IsAppNotif, e.Notification.Show, e.Notification.IsForceShowNotificationPanel); + } + + private async Task FetchNotificationFeed() + { + try + { + NotificationData = new NotificationPush(); + IsLoadNotifComplete = false; + CancellationTokenSource TokenSource = new CancellationTokenSource(); + RunTimeoutCancel(TokenSource); + + await using BridgedNetworkStream networkStream = await FallbackCDNUtil.TryGetCDNFallbackStream(string.Format(AppNotifURLPrefix, IsPreview ? "preview" : "stable"), TokenSource.Token); + NotificationData = await networkStream.DeserializeAsync(NotificationPushJsonContext.Default.NotificationPush, token: TokenSource.Token); + IsLoadNotifComplete = true; + + NotificationData?.EliminatePushList(); + } + catch (Exception ex) + { + await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"Failed to load notification push!\r\n{ex}", LogType.Warning, true); + } + } + + private async Task GenerateLocalAppNotification() + { + NotificationData?.AppPush.Add(new NotificationProp + { + Show = true, + MsgId = 0, + IsDisposable = false, + Severity = NotifSeverity.Success, + Title = Lang._AppNotification.NotifFirstWelcomeTitle, + Message = string.Format(Lang._AppNotification.NotifFirstWelcomeSubtitle, Lang._AppNotification.NotifFirstWelcomeBtn), + OtherUIElement = GenerateNotificationButtonStartProcess( + "", + "https://github.com/CollapseLauncher/Collapse/wiki", + Lang._AppNotification.NotifFirstWelcomeBtn) + }); + + if (IsPreview) + { + NotificationData?.AppPush.Add(new NotificationProp + { + Show = true, + MsgId = -1, + IsDisposable = true, + Severity = NotifSeverity.Informational, + Title = Lang._AppNotification.NotifPreviewBuildUsedTitle, + Message = string.Format(Lang._AppNotification.NotifPreviewBuildUsedSubtitle, Lang._AppNotification.NotifPreviewBuildUsedBtn), + OtherUIElement = GenerateNotificationButtonStartProcess( + "", + "https://github.com/CollapseLauncher/Collapse/issues", + Lang._AppNotification.NotifPreviewBuildUsedBtn) + }); + } + + if (!IsNotificationPanelShow && IsFirstInstall) + { + await ForceShowNotificationPanel(); + } + } + + private static Button GenerateNotificationButtonStartProcess(string IconGlyph, string PathOrURL, string Text, bool IsUseShellExecute = true) + { + return NotificationPush.GenerateNotificationButton(IconGlyph, Text, (_, _) => + { + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = IsUseShellExecute, + FileName = PathOrURL + } + }.Start(); + }); + } + + private async void RunTimeoutCancel(CancellationTokenSource Token) + { + await Task.Delay(10000); + if (IsLoadNotifComplete) + { + return; + } + + LogWriteLine("Cancel to load notification push! > 10 seconds", LogType.Error, true); + await Token.CancelAsync(); + } + + private async Task SpawnPushAppNotification() + { + if (NotificationData?.AppPush == null) return; + foreach (NotificationProp Entry in NotificationData.AppPush.ToList()) + { + // Check for Close Action for certain MsgIds + TypedEventHandler ClickCloseAction = Entry.MsgId switch + { + 0 => (_, _) => + { + NotificationData?.AddIgnoredMsgIds(0); + SaveLocalNotificationData(); + }, + _ => null + }; + + GameVersion? ValidForVerBelow = Entry.ValidForVerBelow != null ? new GameVersion(Entry.ValidForVerBelow) : null; + GameVersion? ValidForVerAbove = Entry.ValidForVerAbove != null ? new GameVersion(Entry.ValidForVerAbove) : null; + + if (Entry.ValidForVerBelow == null && IsNotificationTimestampValid(Entry) + || (LauncherUpdateHelper.LauncherCurrentVersion.Compare(ValidForVerBelow) + && ValidForVerAbove.Compare(LauncherUpdateHelper.LauncherCurrentVersion)) + || LauncherUpdateHelper.LauncherCurrentVersion.Compare(ValidForVerBelow)) + { + if (Entry.ActionProperty != null) + { + Entry.OtherUIElement = Entry.ActionProperty.GetFrameworkElement(); + } + + SpawnNotificationPush(Entry.Title, Entry.Message, Entry.Severity, Entry.MsgId, Entry.IsClosable ?? true, + Entry.IsDisposable ?? true, ClickCloseAction, (FrameworkElement)Entry.OtherUIElement, true, Entry.Show, Entry.IsForceShowNotificationPanel); + } + await Task.Delay(250); + } + } + + private async Task SpawnAppUpdatedNotification() + { + try + { + FileInfo updateNotifFile = new FileInfo(Path.Combine(AppDataFolder, "_NewVer")) + .EnsureCreationOfDirectory() + .EnsureNoReadOnly(out bool isUpdateNotifFileExist); + FileInfo needInnoUpdateFile = new FileInfo(Path.Combine(AppDataFolder, "_NeedInnoLogUpdate")) + .EnsureCreationOfDirectory() + .EnsureNoReadOnly(out bool isNeedInnoUpdateFileExist); + FileInfo innoLogFile = new FileInfo(Path.Combine(Path.GetDirectoryName(AppExecutableDir) ?? string.Empty, "unins000.dat")) + .EnsureNoReadOnly(out bool isInnoLogFileExist); + + + void ClickClose(InfoBar infoBar, object o) + { + _ = updateNotifFile.TryDeleteFile(); + } + + // If the update was handled by squirrel module, and if it needs Inno Setup Log file to get updated, then do the routine + if (isNeedInnoUpdateFileExist) + { + try + { + if (isInnoLogFileExist) + { + InnoSetupLogUpdate.UpdateInnoSetupLog(innoLogFile.FullName); + } + needInnoUpdateFile.TryDeleteFile(); + } + catch (Exception ex) + { + await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"Something wrong while opening the \"unins000.dat\" or deleting the \"_NeedInnoLogUpdate\" file\r\n{ex}", LogType.Error, true); + } + } + + if (!isUpdateNotifFileExist) + { + return; + } + + string[] verStrings = await File.ReadAllLinesAsync(updateNotifFile.FullName); + string verString = string.Empty; + if (verStrings.Length > 0 && GameVersion.TryParse(verStrings[0], out GameVersion? version)) + { + verString = version?.VersionString; + SpawnNotificationPush(Lang._Misc.UpdateCompleteTitle, + string.Format(Lang._Misc.UpdateCompleteSubtitle, version?.VersionString, IsPreview ? "Preview" : "Stable"), + NotifSeverity.Success, + 0xAF, + true, + false, + ClickClose, + null, + true, + true, + true + ); + } + + DirectoryInfo fold = new DirectoryInfo(Path.Combine(AppExecutableDir, "_Temp")); + if (fold.Exists) + { + foreach (FileInfo file in fold.EnumerateFiles().EnumerateNoReadOnly()) + { + if (!file.Name.StartsWith("ApplyUpdate")) + { + continue; + } + + var target = new FileInfo(Path.Combine(AppExecutableDir, file.Name)); + file.TryMoveTo(target); + } + + fold.Delete(true); + } + + try + { + // Remove update notif mark file to avoid it showing the same notification again. + updateNotifFile.TryDeleteFile(); + + // Get current game property, including game preset + GamePresetProperty currentGameProperty = GetCurrentGameProperty(); + (_, string heroImage) = OOBESelectGame.GetLogoAndHeroImgPath(currentGameProperty.GamePreset); + + // Create notification + NotificationContent toastContent = NotificationContent.Create() + .SetTitle(Lang._NotificationToast + .LauncherUpdated_NotifTitle) + .SetContent( + string + .Format(Lang._NotificationToast.LauncherUpdated_NotifSubtitle, + verString + (IsPreview + ? "-preview" + : ""), + Lang._SettingsPage + .PageTitle, + Lang._SettingsPage + .Update_SeeChangelog) + ) + .AddAppHeroImagePath(heroImage); + + // Get notification service + Windows.UI.Notifications.ToastNotification notificationService = + WindowUtility.CurrentToastNotificationService?.CreateToastNotification(toastContent); + + // Spawn notification service + Windows.UI.Notifications.ToastNotifier notifier = + WindowUtility.CurrentToastNotificationService?.CreateToastNotifier(); + notifier?.Show(notificationService); + } + catch (Exception ex) + { + LogWriteLine($"[SpawnAppUpdatedNotification] Failed to spawn toast notification!\r\n{ex}", + LogType.Error, true); + await SentryHelper.ExceptionHandlerAsync(ex); + } + } + catch + { + // ignored + } + } + + private static InfoBarSeverity NotifSeverity2InfoBarSeverity(NotifSeverity inp) + { + return inp switch + { + NotifSeverity.Success => InfoBarSeverity.Success, + NotifSeverity.Warning => InfoBarSeverity.Warning, + NotifSeverity.Error => InfoBarSeverity.Error, + _ => InfoBarSeverity.Informational + }; + } + + private void SpawnNotificationPush(string Title, string TextContent, NotifSeverity Severity, int MsgId = 0, bool IsClosable = true, + bool Disposable = false, TypedEventHandler CloseClickHandler = null, FrameworkElement OtherContent = null, bool IsAppNotif = true, + bool? Show = false, bool ForceShowNotificationPanel = false) + { + if (!(Show ?? false)) return; + if (NotificationData?.CurrentShowMsgIds.Contains(MsgId) ?? false) return; + + if (NotificationData?.IsMsgIdIgnored(MsgId) ?? false) return; + + NotificationData?.CurrentShowMsgIds.Add(MsgId); + + DispatcherQueue?.TryEnqueue(() => + { + StackPanel OtherContentContainer = UIElementExtensions.CreateStackPanel().WithMargin(0d, -4d, 0d, 8d); + + InfoBar Notification = new InfoBar + { + Title = Title, + Message = TextContent, + Severity = NotifSeverity2InfoBarSeverity(Severity), + IsClosable = IsClosable, + IsIconVisible = true, + Shadow = SharedShadow, + IsOpen = true + } + .WithMargin(4d, 4d, 4d, 0d).WithWidth(600) + .WithCornerRadius(8).WithHorizontalAlignment(HorizontalAlignment.Right); + + Notification.Translation += Shadow32; + + if (OtherContent != null) + OtherContentContainer.AddElementToStackPanel(OtherContent); + + if (Disposable) + { + CheckBox NeverAskNotif = new CheckBox + { + Content = new TextBlock { Text = Lang._MainPage.NotifNeverAsk, FontWeight = FontWeights.Medium }, + Tag = $"{MsgId},{IsAppNotif}" + }; + NeverAskNotif.Checked += NeverAskNotif_Checked; + NeverAskNotif.Unchecked += NeverAskNotif_Unchecked; + OtherContentContainer.AddElementToStackPanel(NeverAskNotif); + } + + if (Disposable || OtherContent != null) + Notification.Content = OtherContentContainer; + + Notification.Tag = MsgId; + Notification.CloseButtonClick += CloseClickHandler; + + SpawnNotificationoUI(MsgId, Notification); + + if (ForceShowNotificationPanel && !IsNotificationPanelShow) + { + _ = this.ForceShowNotificationPanel(); + } + }); + } + + private void SpawnNotificationoUI(int tagID, InfoBar Notification) + { + Grid Container = UIElementExtensions.CreateGrid().WithTag(tagID); + Notification.Loaded += (_, _) => + { + NoNotificationIndicator.Opacity = NotificationContainer.Children.Count > 0 ? 0f : 1f; + NewNotificationCountBadge.Visibility = Visibility.Visible; + NewNotificationCountBadge.Value++; + + NotificationPanelClearAllGrid.Visibility = NotificationContainer.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed; + }; + + Notification.Closed += (s, _) => + { + s.Translation -= Shadow32; + s.SetHeight(0d); + s.SetMargin(0d); + int msg = (int)s.Tag; + + if (NotificationData?.CurrentShowMsgIds.Contains(msg) ?? false) + { + NotificationData?.CurrentShowMsgIds.Remove(msg); + } + NotificationContainer.Children.Remove(Container); + NoNotificationIndicator.Opacity = NotificationContainer.Children.Count > 0 ? 0f : 1f; + + if (NewNotificationCountBadge.Value > 0) + { + NewNotificationCountBadge.Value--; + } + NoNotificationIndicator.Opacity = NotificationContainer.Children.Count > 0 ? 0f : 1f; + NewNotificationCountBadge.Visibility = NewNotificationCountBadge.Value > 0 ? Visibility.Visible : Visibility.Collapsed; + NotificationPanelClearAllGrid.Visibility = NotificationContainer.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed; + }; + + Container.AddElementToGridRowColumn(Notification); + NotificationContainer.AddElementToStackPanel(Container); + } + + private void RemoveNotificationUI(int tagID) + { + Grid notif = NotificationContainer.Children.OfType().FirstOrDefault(x => (int)x.Tag == tagID); + if (notif != null) + { + NotificationContainer.Children.Remove(notif); + InfoBar notifBar = notif.Children.OfType().FirstOrDefault(); + if (notifBar != null && notifBar.IsClosable) + notifBar.IsOpen = false; + } + } + + private async void ClearAllNotification(object sender, RoutedEventArgs args) + { + Button button = sender as Button; + if (button != null) button.IsEnabled = false; + + int stackIndex = 0; + for (; stackIndex < NotificationContainer.Children.Count;) + { + if (NotificationContainer.Children[stackIndex] is not Grid container + || container.Children == null || container.Children.Count == 0 + || container.Children[0] is not InfoBar { IsClosable: true } notifBar) + { + ++stackIndex; + continue; + } + + NotificationContainer.Children.RemoveAt(stackIndex); + notifBar.IsOpen = false; + await Task.Delay(100); + } + + if (NotificationContainer.Children.Count == 0) + { + await Task.Delay(500); + ToggleNotificationPanelBtn.IsChecked = false; + IsNotificationPanelShow = false; + ShowHideNotificationPanel(); + } + + if (button != null) button.IsEnabled = true; + } + + private static void NeverAskNotif_Checked(object sender, RoutedEventArgs e) + { + string[] Data = (sender as CheckBox)?.Tag.ToString()?.Split(','); + if (Data == null) + { + return; + } + + NotificationData?.AddIgnoredMsgIds(int.Parse(Data[0]), bool.Parse(Data[1])); + SaveLocalNotificationData(); + } + + private static void NeverAskNotif_Unchecked(object sender, RoutedEventArgs e) + { + string[] Data = (sender as CheckBox)?.Tag.ToString()?.Split(','); + if (Data == null) + { + return; + } + + NotificationData?.RemoveIgnoredMsgIds(int.Parse(Data[0]), bool.Parse(Data[1])); + SaveLocalNotificationData(); + } + + private async Task ForceShowNotificationPanel() + { + ToggleNotificationPanelBtn.IsChecked = true; + IsNotificationPanelShow = true; + ShowHideNotificationPanel(); + await Task.Delay(250); + double currentVOffset = NotificationContainer.ActualHeight; + + NotificationPanelScrollViewer.ScrollToVerticalOffset(currentVOffset); + } +} diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs index 7161ddbf8..6b46128eb 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml.cs @@ -7,18 +7,11 @@ using CollapseLauncher.Helper.Background; using CollapseLauncher.Helper.Image; using CollapseLauncher.Helper.Metadata; -using CollapseLauncher.Helper.StreamUtility; using CollapseLauncher.Helper.Update; -using CollapseLauncher.Interfaces; using CollapseLauncher.Pages; -using CollapseLauncher.Pages.OOBE; -using CommunityToolkit.WinUI; using Hi3Helper; using Hi3Helper.SentryHelper; using Hi3Helper.Shared.ClassStruct; -using Hi3Helper.Win32.ToastCOM.Notification; -using InnoSetupHelper; -using Microsoft.UI; using Microsoft.UI.Input; using Microsoft.UI.Text; using Microsoft.UI.Xaml; @@ -28,21 +21,16 @@ using Microsoft.UI.Xaml.Media.Animation; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Principal; using System.Text.Json; -using System.Threading; using System.Threading.Tasks; using Windows.Foundation; using Windows.Graphics; -using Windows.System; using static CollapseLauncher.Dialogs.KeyboardShortcuts; using static CollapseLauncher.InnerLauncherConfig; -using static CollapseLauncher.Statics.GamePropertyVault; -using static Hi3Helper.Data.ConverterTool; using static Hi3Helper.Locale; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; @@ -60,8 +48,6 @@ namespace CollapseLauncher { - using KeybindAction = TypedEventHandler; - public partial class MainPage : Page { #region Properties @@ -75,7 +61,7 @@ public partial class MainPage : Page private int CurrentGameCategory = -1; private int CurrentGameRegion = -1; - internal static List PreviousTagString = []; + internal static List PreviousTagString = []; #nullable enable internal static BackgroundMediaUtility? CurrentBackgroundHandler; @@ -394,7 +380,7 @@ private void ChangeTitleDragAreaInvoker_TitleBarEvent(object sender, ChangeTitle #endregion #region Admin Checks - public static async Task CheckForAdminAccess(UIElement root) + private static async Task CheckForAdminAccess(UIElement root) { if (!IsPrincipalHasNoAdministratorAccess()) return true; @@ -450,7 +436,7 @@ private static bool IsPrincipalHasNoAdministratorAccess() #endregion #region Theme Methods - public void SetThemeParameters() + private void SetThemeParameters() { if (m_windowSupportCustomTitle) { @@ -463,174 +449,6 @@ public void SetThemeParameters() } #endregion - #region Background Image - private static void BackgroundImg_IsImageHideEvent(object sender, bool e) - { - if (e) CurrentBackgroundHandler?.Dimm(); - else CurrentBackgroundHandler?.Undimm(); - } - - private readonly HashSet _processingBackground = []; - private async void CustomBackgroundChanger_Event(object sender, BackgroundImgProperty e) - { - if (_processingBackground.Contains(e.ImgPath)) - { - LogWriteLine($"Background {e.ImgPath} is already being processed!", LogType.Warning, true); - return; - } - - try - { - _processingBackground.Add(e.ImgPath); - var gameLauncherApi = LauncherMetadataHelper.CurrentMetadataConfig?.GameLauncherApi; - if (gameLauncherApi == null) - { - return; - } - - gameLauncherApi.GameBackgroundImgLocal = e.ImgPath; - IsCustomBG = e.IsCustom; - - // if (e.IsCustom) - // SetAndSaveConfigValue("CustomBGPath", - // gameLauncherApi.GameBackgroundImgLocal); - - if (!File.Exists(gameLauncherApi.GameBackgroundImgLocal)) - { - LogWriteLine($"Custom background file {e.ImgPath} is missing!", LogType.Warning, true); - gameLauncherApi.GameBackgroundImgLocal = AppDefaultBG; - } - - var mType = BackgroundMediaUtility.GetMediaType(gameLauncherApi.GameBackgroundImgLocal); - switch (mType) - { - case BackgroundMediaUtility.MediaType.Media: - BackgroundNewMediaPlayerGrid.Visibility = Visibility.Visible; - BackgroundNewBackGrid.Visibility = Visibility.Collapsed; - break; - case BackgroundMediaUtility.MediaType.StillImage: - FileStream imgStream = - await ImageLoaderHelper.LoadImage(gameLauncherApi.GameBackgroundImgLocal); - BackgroundMediaUtility.SetAlternativeFileStream(imgStream); - BackgroundNewMediaPlayerGrid.Visibility = Visibility.Collapsed; - BackgroundNewBackGrid.Visibility = Visibility.Visible; - break; - case BackgroundMediaUtility.MediaType.Unknown: - default: - throw new InvalidCastException(); - } - - await InitBackgroundHandler(); - CurrentBackgroundHandler?.LoadBackground(gameLauncherApi.GameBackgroundImgLocal, e.IsRequestInit, - e.IsForceRecreateCache, ex => - { - gameLauncherApi.GameBackgroundImgLocal = - AppDefaultBG; - LogWriteLine($"An error occured while loading background {e.ImgPath}\r\n{ex}", - LogType.Error, true); - ErrorSender.SendException(ex); - }, e.ActionAfterLoaded); - } - catch (Exception ex) - { - LogWriteLine($"An error occured while loading background {e.ImgPath}\r\n{ex}", - LogType.Error, true); - ErrorSender.SendException(new Exception($"An error occured while loading background {e.ImgPath}", ex)); - } - finally - { - _processingBackground.Remove(e.ImgPath); - } - } - - internal async Task ChangeBackgroundImageAsRegionAsync(bool ShowLoadingMsg = false) - { - var gameLauncherApi = LauncherMetadataHelper.CurrentMetadataConfig?.GameLauncherApi; - if (gameLauncherApi == null) - { - return; - } - - GamePresetProperty currentGameProperty = GetCurrentGameProperty(); - bool isUseCustomPerRegionBg = currentGameProperty.GameSettings?.SettingsCollapseMisc?.UseCustomRegionBG ?? false; - - IsCustomBG = GetAppConfigValue("UseCustomBG").ToBool(); - bool isAPIBackgroundAvailable = - !string.IsNullOrEmpty(gameLauncherApi.GameBackgroundImg); - - var posterBg = currentGameProperty.GameVersion.GameType switch - { - GameNameType.Honkai => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\honkai.webp"), - GameNameType.Genshin => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\genshin.webp"), - GameNameType.StarRail => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\starrail.webp"), - GameNameType.Zenless => Path.Combine(AppExecutableDir, - @"Assets\Images\GameBackground\zzz.webp"), - _ => AppDefaultBG - }; - - // Check if Regional Custom BG is enabled and available - if (isUseCustomPerRegionBg) - { - var regionBgPath = currentGameProperty.GameSettings?.SettingsCollapseMisc?.CustomRegionBGPath; - if (!string.IsNullOrEmpty(regionBgPath) && File.Exists(regionBgPath)) - { - if (BackgroundMediaUtility.GetMediaType(regionBgPath) == BackgroundMediaUtility.MediaType.StillImage) - { - FileStream imgStream = await ImageLoaderHelper.LoadImage(regionBgPath); - BackgroundMediaUtility.SetAlternativeFileStream(imgStream); - } - - gameLauncherApi.GameBackgroundImgLocal = regionBgPath; - } - } - // If not, then check for global Custom BG - else - { - var BGPath = IsCustomBG ? GetAppConfigValue("CustomBGPath").ToString() : null; - if (!string.IsNullOrEmpty(BGPath)) - { - gameLauncherApi.GameBackgroundImgLocal = BGPath; - } - // If it's still not, then check if API gives any background - else if (isAPIBackgroundAvailable) - { - try - { - await DownloadBackgroundImage(default); - return; // Return after successfully loading - } - catch (Exception ex) - { - ErrorSender.SendException(ex); - LogWriteLine($"Failed while downloading default background image!\r\n{ex}", LogType.Error, true); - gameLauncherApi.GameBackgroundImgLocal = AppDefaultBG; - } - } - // IF ITS STILL NOT THERE, then use fallback game poster, IF ITS STILL NOT THEREEEE!! use paimon cute deadge pic :) - else - { - gameLauncherApi.GameBackgroundImgLocal = posterBg; - } - } - - // Use default background if the API background is empty (in-case HoYo did something catchy) - if (!isAPIBackgroundAvailable && !IsCustomBG && LauncherMetadataHelper.CurrentMetadataConfig is { GameLauncherApi: not null }) - gameLauncherApi.GameBackgroundImgLocal ??= posterBg; - - // If the custom per region is enabled, then execute below - BackgroundImgChanger.ChangeBackground(gameLauncherApi.GameBackgroundImgLocal, - () => - { - IsFirstStartup = false; - ColorPaletteUtility.ReloadPageTheme(this, CurrentAppTheme); - }, - IsCustomBG || isUseCustomPerRegionBg, true, true); - } - #endregion - #region Events private void SubscribeEvents() { @@ -715,467 +533,6 @@ private async Task RunBackgroundCheck() } #endregion - #region Notification - private void NotificationInvoker_EventInvoker(object sender, NotificationInvokerProp e) - { - if (e.IsCustomNotif) - { - if (e.CustomNotifAction == NotificationCustomAction.Add) - { - SpawnNotificationoUI(e.Notification.MsgId, e.OtherContent as InfoBar); - } - else - { - RemoveNotificationUI(e.Notification.MsgId); - } - return; - } - - SpawnNotificationPush(e.Notification.Title, e.Notification.Message, e.Notification.Severity, - e.Notification.MsgId, e.Notification.IsClosable ?? true, e.Notification.IsDisposable ?? true, e.CloseAction, - e.OtherContent, e.IsAppNotif, e.Notification.Show, e.Notification.IsForceShowNotificationPanel); - } - - private async Task FetchNotificationFeed() - { - try - { - NotificationData = new NotificationPush(); - IsLoadNotifComplete = false; - CancellationTokenSource TokenSource = new CancellationTokenSource(); - RunTimeoutCancel(TokenSource); - - await using BridgedNetworkStream networkStream = await FallbackCDNUtil.TryGetCDNFallbackStream(string.Format(AppNotifURLPrefix, IsPreview ? "preview" : "stable"), TokenSource.Token); - NotificationData = await networkStream.DeserializeAsync(NotificationPushJsonContext.Default.NotificationPush, token: TokenSource.Token); - IsLoadNotifComplete = true; - - NotificationData?.EliminatePushList(); - } - catch (Exception ex) - { - await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"Failed to load notification push!\r\n{ex}", LogType.Warning, true); - } - } - - private async Task GenerateLocalAppNotification() - { - NotificationData?.AppPush.Add(new NotificationProp - { - Show = true, - MsgId = 0, - IsDisposable = false, - Severity = NotifSeverity.Success, - Title = Lang._AppNotification.NotifFirstWelcomeTitle, - Message = string.Format(Lang._AppNotification.NotifFirstWelcomeSubtitle, Lang._AppNotification.NotifFirstWelcomeBtn), - OtherUIElement = GenerateNotificationButtonStartProcess( - "", - "https://github.com/CollapseLauncher/Collapse/wiki", - Lang._AppNotification.NotifFirstWelcomeBtn) - }); - - if (IsPreview) - { - NotificationData?.AppPush.Add(new NotificationProp - { - Show = true, - MsgId = -1, - IsDisposable = true, - Severity = NotifSeverity.Informational, - Title = Lang._AppNotification.NotifPreviewBuildUsedTitle, - Message = string.Format(Lang._AppNotification.NotifPreviewBuildUsedSubtitle, Lang._AppNotification.NotifPreviewBuildUsedBtn), - OtherUIElement = GenerateNotificationButtonStartProcess( - "", - "https://github.com/CollapseLauncher/Collapse/issues", - Lang._AppNotification.NotifPreviewBuildUsedBtn) - }); - } - - if (!IsNotificationPanelShow && IsFirstInstall) - { - await ForceShowNotificationPanel(); - } - } - - private static Button GenerateNotificationButtonStartProcess(string IconGlyph, string PathOrURL, string Text, bool IsUseShellExecute = true) - { - return NotificationPush.GenerateNotificationButton(IconGlyph, Text, (_, _) => - { - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = IsUseShellExecute, - FileName = PathOrURL - } - }.Start(); - }); - } - - private async void RunTimeoutCancel(CancellationTokenSource Token) - { - await Task.Delay(10000); - if (IsLoadNotifComplete) - { - return; - } - - LogWriteLine("Cancel to load notification push! > 10 seconds", LogType.Error, true); - await Token.CancelAsync(); - } - - private async Task SpawnPushAppNotification() - { - if (NotificationData?.AppPush == null) return; - foreach (NotificationProp Entry in NotificationData.AppPush.ToList()) - { - // Check for Close Action for certain MsgIds - TypedEventHandler ClickCloseAction = Entry.MsgId switch - { - 0 => (_, _) => - { - NotificationData?.AddIgnoredMsgIds(0); - SaveLocalNotificationData(); - }, - _ => null - }; - - GameVersion? ValidForVerBelow = Entry.ValidForVerBelow != null ? new GameVersion(Entry.ValidForVerBelow) : null; - GameVersion? ValidForVerAbove = Entry.ValidForVerAbove != null ? new GameVersion(Entry.ValidForVerAbove) : null; - - if (Entry.ValidForVerBelow == null && IsNotificationTimestampValid(Entry) - || (LauncherUpdateHelper.LauncherCurrentVersion.Compare(ValidForVerBelow) - && ValidForVerAbove.Compare(LauncherUpdateHelper.LauncherCurrentVersion)) - || LauncherUpdateHelper.LauncherCurrentVersion.Compare(ValidForVerBelow)) - { - if (Entry.ActionProperty != null) - { - Entry.OtherUIElement = Entry.ActionProperty.GetFrameworkElement(); - } - - SpawnNotificationPush(Entry.Title, Entry.Message, Entry.Severity, Entry.MsgId, Entry.IsClosable ?? true, - Entry.IsDisposable ?? true, ClickCloseAction, (FrameworkElement)Entry.OtherUIElement, true, Entry.Show, Entry.IsForceShowNotificationPanel); - } - await Task.Delay(250); - } - } - - private async Task SpawnAppUpdatedNotification() - { - try - { - FileInfo updateNotifFile = new FileInfo(Path.Combine(AppDataFolder, "_NewVer")) - .EnsureCreationOfDirectory() - .EnsureNoReadOnly(out bool isUpdateNotifFileExist); - FileInfo needInnoUpdateFile = new FileInfo(Path.Combine(AppDataFolder, "_NeedInnoLogUpdate")) - .EnsureCreationOfDirectory() - .EnsureNoReadOnly(out bool isNeedInnoUpdateFileExist); - FileInfo innoLogFile = new FileInfo(Path.Combine(Path.GetDirectoryName(AppExecutableDir) ?? string.Empty, "unins000.dat")) - .EnsureNoReadOnly(out bool isInnoLogFileExist); - - - void ClickClose(InfoBar infoBar, object o) - { - _ = updateNotifFile.TryDeleteFile(); - } - - // If the update was handled by squirrel module, and if it needs Inno Setup Log file to get updated, then do the routine - if (isNeedInnoUpdateFileExist) - { - try - { - if (isInnoLogFileExist) - { - InnoSetupLogUpdate.UpdateInnoSetupLog(innoLogFile.FullName); - } - needInnoUpdateFile.TryDeleteFile(); - } - catch (Exception ex) - { - await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"Something wrong while opening the \"unins000.dat\" or deleting the \"_NeedInnoLogUpdate\" file\r\n{ex}", LogType.Error, true); - } - } - - if (!isUpdateNotifFileExist) - { - return; - } - - string[] verStrings = await File.ReadAllLinesAsync(updateNotifFile.FullName); - string verString = string.Empty; - if (verStrings.Length > 0 && GameVersion.TryParse(verStrings[0], out GameVersion? version)) - { - verString = version?.VersionString; - SpawnNotificationPush(Lang._Misc.UpdateCompleteTitle, - string.Format(Lang._Misc.UpdateCompleteSubtitle, version?.VersionString, IsPreview ? "Preview" : "Stable"), - NotifSeverity.Success, - 0xAF, - true, - false, - ClickClose, - null, - true, - true, - true - ); - } - - DirectoryInfo fold = new DirectoryInfo(Path.Combine(AppExecutableDir, "_Temp")); - if (fold.Exists) - { - foreach (FileInfo file in fold.EnumerateFiles().EnumerateNoReadOnly()) - { - if (!file.Name.StartsWith("ApplyUpdate")) - { - continue; - } - - var target = new FileInfo(Path.Combine(AppExecutableDir, file.Name)); - file.TryMoveTo(target); - } - - fold.Delete(true); - } - - try - { - // Remove update notif mark file to avoid it showing the same notification again. - updateNotifFile.TryDeleteFile(); - - // Get current game property, including game preset - GamePresetProperty currentGameProperty = GetCurrentGameProperty(); - (_, string heroImage) = OOBESelectGame.GetLogoAndHeroImgPath(currentGameProperty.GamePreset); - - // Create notification - NotificationContent toastContent = NotificationContent.Create() - .SetTitle(Lang._NotificationToast - .LauncherUpdated_NotifTitle) - .SetContent( - string - .Format(Lang._NotificationToast.LauncherUpdated_NotifSubtitle, - verString + (IsPreview - ? "-preview" - : ""), - Lang._SettingsPage - .PageTitle, - Lang._SettingsPage - .Update_SeeChangelog) - ) - .AddAppHeroImagePath(heroImage); - - // Get notification service - Windows.UI.Notifications.ToastNotification notificationService = - WindowUtility.CurrentToastNotificationService?.CreateToastNotification(toastContent); - - // Spawn notification service - Windows.UI.Notifications.ToastNotifier notifier = - WindowUtility.CurrentToastNotificationService?.CreateToastNotifier(); - notifier?.Show(notificationService); - } - catch (Exception ex) - { - LogWriteLine($"[SpawnAppUpdatedNotification] Failed to spawn toast notification!\r\n{ex}", - LogType.Error, true); - await SentryHelper.ExceptionHandlerAsync(ex); - } - } - catch - { - // ignored - } - } - - private static InfoBarSeverity NotifSeverity2InfoBarSeverity(NotifSeverity inp) - { - return inp switch - { - NotifSeverity.Success => InfoBarSeverity.Success, - NotifSeverity.Warning => InfoBarSeverity.Warning, - NotifSeverity.Error => InfoBarSeverity.Error, - _ => InfoBarSeverity.Informational - }; - } - - private void SpawnNotificationPush(string Title, string TextContent, NotifSeverity Severity, int MsgId = 0, bool IsClosable = true, - bool Disposable = false, TypedEventHandler CloseClickHandler = null, FrameworkElement OtherContent = null, bool IsAppNotif = true, - bool? Show = false, bool ForceShowNotificationPanel = false) - { - if (!(Show ?? false)) return; - if (NotificationData?.CurrentShowMsgIds.Contains(MsgId) ?? false) return; - - if (NotificationData?.IsMsgIdIgnored(MsgId) ?? false) return; - - NotificationData?.CurrentShowMsgIds.Add(MsgId); - - DispatcherQueue?.TryEnqueue(() => - { - StackPanel OtherContentContainer = UIElementExtensions.CreateStackPanel().WithMargin(0d, -4d, 0d, 8d); - - InfoBar Notification = new InfoBar - { - Title = Title, - Message = TextContent, - Severity = NotifSeverity2InfoBarSeverity(Severity), - IsClosable = IsClosable, - IsIconVisible = true, - Shadow = SharedShadow, - IsOpen = true - } - .WithMargin(4d, 4d, 4d, 0d).WithWidth(600) - .WithCornerRadius(8).WithHorizontalAlignment(HorizontalAlignment.Right); - - Notification.Translation += Shadow32; - - if (OtherContent != null) - OtherContentContainer.AddElementToStackPanel(OtherContent); - - if (Disposable) - { - CheckBox NeverAskNotif = new CheckBox - { - Content = new TextBlock { Text = Lang._MainPage.NotifNeverAsk, FontWeight = FontWeights.Medium }, - Tag = $"{MsgId},{IsAppNotif}" - }; - NeverAskNotif.Checked += NeverAskNotif_Checked; - NeverAskNotif.Unchecked += NeverAskNotif_Unchecked; - OtherContentContainer.AddElementToStackPanel(NeverAskNotif); - } - - if (Disposable || OtherContent != null) - Notification.Content = OtherContentContainer; - - Notification.Tag = MsgId; - Notification.CloseButtonClick += CloseClickHandler; - - SpawnNotificationoUI(MsgId, Notification); - - if (ForceShowNotificationPanel && !IsNotificationPanelShow) - { - _ = this.ForceShowNotificationPanel(); - } - }); - } - - private void SpawnNotificationoUI(int tagID, InfoBar Notification) - { - Grid Container = UIElementExtensions.CreateGrid().WithTag(tagID); - Notification.Loaded += (_, _) => - { - NoNotificationIndicator.Opacity = NotificationContainer.Children.Count > 0 ? 0f : 1f; - NewNotificationCountBadge.Visibility = Visibility.Visible; - NewNotificationCountBadge.Value++; - - NotificationPanelClearAllGrid.Visibility = NotificationContainer.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed; - }; - - Notification.Closed += (s, _) => - { - s.Translation -= Shadow32; - s.SetHeight(0d); - s.SetMargin(0d); - int msg = (int)s.Tag; - - if (NotificationData?.CurrentShowMsgIds.Contains(msg) ?? false) - { - NotificationData?.CurrentShowMsgIds.Remove(msg); - } - NotificationContainer.Children.Remove(Container); - NoNotificationIndicator.Opacity = NotificationContainer.Children.Count > 0 ? 0f : 1f; - - if (NewNotificationCountBadge.Value > 0) - { - NewNotificationCountBadge.Value--; - } - NoNotificationIndicator.Opacity = NotificationContainer.Children.Count > 0 ? 0f : 1f; - NewNotificationCountBadge.Visibility = NewNotificationCountBadge.Value > 0 ? Visibility.Visible : Visibility.Collapsed; - NotificationPanelClearAllGrid.Visibility = NotificationContainer.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed; - }; - - Container.AddElementToGridRowColumn(Notification); - NotificationContainer.AddElementToStackPanel(Container); - } - - private void RemoveNotificationUI(int tagID) - { - Grid notif = NotificationContainer.Children.OfType().FirstOrDefault(x => (int)x.Tag == tagID); - if (notif != null) - { - NotificationContainer.Children.Remove(notif); - InfoBar notifBar = notif.Children.OfType().FirstOrDefault(); - if (notifBar != null && notifBar.IsClosable) - notifBar.IsOpen = false; - } - } - - private async void ClearAllNotification(object sender, RoutedEventArgs args) - { - Button button = sender as Button; - if (button != null) button.IsEnabled = false; - - int stackIndex = 0; - for (; stackIndex < NotificationContainer.Children.Count;) - { - if (NotificationContainer.Children[stackIndex] is not Grid container - || container.Children == null || container.Children.Count == 0 - || container.Children[0] is not InfoBar { IsClosable: true } notifBar) - { - ++stackIndex; - continue; - } - - NotificationContainer.Children.RemoveAt(stackIndex); - notifBar.IsOpen = false; - await Task.Delay(100); - } - - if (NotificationContainer.Children.Count == 0) - { - await Task.Delay(500); - ToggleNotificationPanelBtn.IsChecked = false; - IsNotificationPanelShow = false; - ShowHideNotificationPanel(); - } - - if (button != null) button.IsEnabled = true; - } - - private static void NeverAskNotif_Checked(object sender, RoutedEventArgs e) - { - string[] Data = (sender as CheckBox)?.Tag.ToString()?.Split(','); - if (Data == null) - { - return; - } - - NotificationData?.AddIgnoredMsgIds(int.Parse(Data[0]), bool.Parse(Data[1])); - SaveLocalNotificationData(); - } - - private static void NeverAskNotif_Unchecked(object sender, RoutedEventArgs e) - { - string[] Data = (sender as CheckBox)?.Tag.ToString()?.Split(','); - if (Data == null) - { - return; - } - - NotificationData?.RemoveIgnoredMsgIds(int.Parse(Data[0]), bool.Parse(Data[1])); - SaveLocalNotificationData(); - } - - private async Task ForceShowNotificationPanel() - { - ToggleNotificationPanelBtn.IsChecked = true; - IsNotificationPanelShow = true; - ShowHideNotificationPanel(); - await Task.Delay(250); - double currentVOffset = NotificationContainer.ActualHeight; - - NotificationPanelScrollViewer.ScrollToVerticalOffset(currentVOffset); - } - #endregion - #region Game Selector Method private async Task<(PresetConfig, string, string)> LoadSavedGameSelection() { @@ -1344,429 +701,21 @@ private async ValueTask CheckMetadataUpdateInBackground() } #endregion - #region Navigation - private void InitializeNavigationItems(bool ResetSelection = true) + #region Icons + private void ToggleTitleIcon(bool hide) { - DispatcherQueue.TryEnqueue(() => + if (!hide) { - NavigationViewControl.IsSettingsVisible = true; - NavigationViewControl.MenuItems.Clear(); - NavigationViewControl.FooterMenuItems.Clear(); + GridBG_IconTitle.Width = double.NaN; + GridBG_IconTitle.Opacity = 1d; + GridBG_IconImg.Opacity = 1d; + return; + } - IGameVersion CurrentGameVersionCheck = GetCurrentGameProperty().GameVersion; - - FontIcon IconLauncher = new FontIcon { Glyph = "" }; - FontIcon IconRepair = new FontIcon { Glyph = "" }; - FontIcon IconCaches = new FontIcon { Glyph = m_isWindows11 ? "" : "" }; - FontIcon IconGameSettings = new FontIcon { Glyph = "" }; - FontIcon IconAppSettings = new FontIcon { Glyph = "" }; - - if (m_appMode == AppMode.Hi3CacheUpdater) - { - if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) - { - NavigationViewControl.MenuItems.Add(new NavigationViewItem - { Icon = IconCaches, Tag = "caches" } - .BindNavigationViewItemText("_CachesPage", "PageTitle")); - } - return; - } - - NavigationViewControl.MenuItems.Add(new NavigationViewItem - { Icon = IconLauncher, Tag = "launcher" } - .BindNavigationViewItemText("_HomePage", "PageTitle")); - - NavigationViewControl.MenuItems.Add(new NavigationViewItemHeader() - .BindNavigationViewItemText("_MainPage", "NavigationUtilities")); - - if (CurrentGameVersionCheck.GamePreset.IsRepairEnabled ?? false) - { - NavigationViewControl.MenuItems.Add(new NavigationViewItem - { Icon = IconRepair, Tag = "repair" } - .BindNavigationViewItemText("_GameRepairPage", "PageTitle")); - } - - if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) - { - NavigationViewControl.MenuItems.Add(new NavigationViewItem - { Icon = IconCaches, Tag = "caches" } - .BindNavigationViewItemText("_CachesPage", "PageTitle")); - } - - switch (CurrentGameVersionCheck.GameType) - { - case GameNameType.Honkai: - NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem - { Icon = IconGameSettings, Tag = "honkaigamesettings" } - .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); - break; - case GameNameType.StarRail: - NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem - { Icon = IconGameSettings, Tag = "starrailgamesettings" } - .BindNavigationViewItemText("_StarRailGameSettingsPage", "PageTitle")); - break; - case GameNameType.Genshin: - NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem - { Icon = IconGameSettings, Tag = "genshingamesettings" } - .BindNavigationViewItemText("_GenshinGameSettingsPage", "PageTitle")); - break; - case GameNameType.Zenless: - NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem - { Icon = IconGameSettings, Tag = "zenlessgamesettings" } - .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); - break; - } - - if (NavigationViewControl.SettingsItem is NavigationViewItem SettingsItem) - { - SettingsItem.Icon = IconAppSettings; - _ = SettingsItem.BindNavigationViewItemText("_SettingsPage", "PageTitle"); - } - - foreach (var dependency in NavigationViewControl.FindDescendants().OfType()) - { - // Avoid any icons to have shadow attached if it's not from this page - if (dependency.BaseUri.AbsolutePath != BaseUri.AbsolutePath) - { - continue; - } - - switch (dependency) - { - case FontIcon icon: - AttachShadowNavigationPanelItem(icon); - break; - case AnimatedIcon animIcon: - AttachShadowNavigationPanelItem(animIcon); - break; - } - } - AttachShadowNavigationPanelItem(IconAppSettings); - - if (ResetSelection) - { - NavigationViewControl.SelectedItem = (NavigationViewItem)NavigationViewControl.MenuItems[0]; - } - - NavigationViewControl.ApplyNavigationViewItemLocaleTextBindings(); - - InputSystemCursor handCursor = InputSystemCursor.Create(InputSystemCursorShape.Hand); - MainPageGrid.SetAllControlsCursorRecursive(handCursor); - }); - } - - public static void AttachShadowNavigationPanelItem(FrameworkElement element) - { - bool isAppLight = IsAppThemeLight; - Windows.UI.Color shadowColor = isAppLight ? Colors.White : Colors.Black; - double shadowBlurRadius = isAppLight ? 20 : 15; - double shadowOpacity = isAppLight ? 0.5 : 0.3; - - element.ApplyDropShadow(shadowColor, shadowBlurRadius, shadowOpacity); - } - - private void NavView_Loaded(object sender, RoutedEventArgs e) - { - foreach (NavigationViewItemBase item in NavigationViewControl.MenuItems) - { - if (item is not NavigationViewItem || item.Tag.ToString() != "launcher") - { - continue; - } - - NavigationViewControl.SelectedItem = item; - break; - } - - NavViewPaneBackground.OpacityTransition = new ScalarTransition - { - Duration = TimeSpan.FromMilliseconds(150) - }; - NavViewPaneBackground.TranslationTransition = new Vector3Transition - { - Duration = TimeSpan.FromMilliseconds(150) - }; - - var paneMainGrid = NavigationViewControl.FindDescendant("PaneContentGrid"); - if (paneMainGrid is Grid paneMainGridAsGrid) - { - paneMainGridAsGrid.PointerEntered += NavView_PanePointerEntered; - paneMainGridAsGrid.PointerExited += NavView_PanePointerExited; - } - - // The toggle button is not a part of pane. Why Microsoft!!! - var paneToggleButtonGrid = (Grid)NavigationViewControl.FindDescendant("PaneToggleButtonGrid"); - if (paneToggleButtonGrid != null) - { - paneToggleButtonGrid.PointerEntered += NavView_PanePointerEntered; - paneToggleButtonGrid.PointerExited += NavView_PanePointerExited; - } - - // var backIcon = NavigationViewControl.FindDescendant("NavigationViewBackButton")?.FindDescendant(); - // backIcon?.ApplyDropShadow(Colors.Gray, 20); - - var toggleIcon = NavigationViewControl.FindDescendant("TogglePaneButton")?.FindDescendant(); - toggleIcon?.ApplyDropShadow(Colors.Gray, 20); - } - - private void NavView_PanePointerEntered(object sender, PointerRoutedEventArgs e) - { - IsCursorInNavBarHoverArea = true; - NavViewPaneBackground.Opacity = 1; - NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); - /* - if (!NavigationViewControl.IsPaneOpen) - { - var duration = TimeSpan.FromSeconds(0.25); - var current = (float)NavViewPaneBackground.Opacity; - var animation = NavViewPaneBackground.GetElementCompositor()! - .CreateScalarKeyFrameAnimation("Opacity", 1, current); - await NavViewPaneBackground.StartAnimation(duration, animation); - } - */ - } - - private bool IsCursorInNavBarHoverArea; - - private void NavView_PanePointerExited(object sender, PointerRoutedEventArgs e) - { - PointerPoint pointerPoint = e.GetCurrentPoint(NavViewPaneBackgroundHoverArea); - IsCursorInNavBarHoverArea = pointerPoint.Position.X <= NavViewPaneBackgroundHoverArea.Width - 8 && pointerPoint.Position.X > 4; - - switch (IsCursorInNavBarHoverArea) - { - case false when !NavigationViewControl.IsPaneOpen: - NavViewPaneBackground.Opacity = 0; - NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); - break; - case true when !NavigationViewControl.IsPaneOpen: - NavViewPaneBackground.Opacity = 1; - NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); - break; - } - - /* - var duration = TimeSpan.FromSeconds(0.25); - var current = (float)NavViewPaneBackground.Opacity; - var animation = NavViewPaneBackground.GetElementCompositor()! - .CreateScalarKeyFrameAnimation("Opacity", 0, current); - await NavViewPaneBackground.StartAnimation(duration, animation); - */ - } - - private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) - { - if (!IsLoadFrameCompleted) return; - if (args.IsSettingsInvoked && PreviousTag != "settings") Navigate(typeof(SettingsPage), "settings"); - -#nullable enable - NavigationViewItem? item = sender.MenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); - item ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); - if (item == null) return; -#nullable restore - - string itemTag = (string)item.Tag; - - NavigateInnerSwitch(itemTag); - } - - private void NavigateInnerSwitch(string itemTag) - { - if (itemTag == PreviousTag) return; - switch (itemTag) - { - case "launcher": - Navigate(typeof(HomePage), itemTag); - break; - - case "repair": - if (!(GetCurrentGameProperty().GameVersion.GamePreset.IsRepairEnabled ?? false)) - Navigate(typeof(UnavailablePage), itemTag); - else - Navigate(IsGameInstalled() ? typeof(RepairPage) : typeof(NotInstalledPage), itemTag); - break; - - case "caches": - if (GetCurrentGameProperty().GameVersion.GamePreset.IsCacheUpdateEnabled ?? false) - Navigate(IsGameInstalled() || (m_appMode == AppMode.Hi3CacheUpdater && GetCurrentGameProperty().GameVersion.GamePreset.GameType == GameNameType.Honkai) ? typeof(CachesPage) : typeof(NotInstalledPage), itemTag); - else - Navigate(typeof(UnavailablePage), itemTag); - break; - - case "honkaigamesettings": - Navigate(IsGameInstalled() ? typeof(HonkaiGameSettingsPage) : typeof(NotInstalledPage), itemTag); - break; - - case "starrailgamesettings": - Navigate(IsGameInstalled() ? typeof(StarRailGameSettingsPage) : typeof(NotInstalledPage), itemTag); - break; - - case "genshingamesettings": - Navigate(IsGameInstalled() ? typeof(GenshinGameSettingsPage) : typeof(NotInstalledPage), itemTag); - break; - - case "zenlessgamesettings": - Navigate(IsGameInstalled() ? typeof(ZenlessGameSettingsPage) : typeof(NotInstalledPage), itemTag); - break; - } - } - - private void Navigate(Type sourceType, string tagStr) - { - MainFrameChanger.ChangeMainFrame(sourceType, new DrillInNavigationTransitionInfo()); - PreviousTag = tagStr; - PreviousTagString.Add(tagStr); - LogWriteLine($"Page changed to {sourceType.Name} with Tag: {tagStr}", LogType.Scheme); - } - - internal void InvokeMainPageNavigateByTag(string tagStr) - { - NavigationViewItem item = NavigationViewControl.MenuItems.OfType().FirstOrDefault(x => x.Tag is string tag && tag == tagStr); - if (item == null) - { - return; - } - - NavigationViewControl.SelectedItem = item; - string tag = (string)item.Tag; - NavigateInnerSwitch(tag); - } - - private void ToggleNotificationPanelBtnClick(object sender, RoutedEventArgs e) - { - IsNotificationPanelShow = ToggleNotificationPanelBtn.IsChecked ?? false; - ShowHideNotificationPanel(); - } - - private void ShowHideNotificationPanel() - { - NewNotificationCountBadge.Value = 0; - NewNotificationCountBadge.Visibility = Visibility.Collapsed; - Thickness lastMargin = NotificationPanel.Margin; - lastMargin.Right = IsNotificationPanelShow ? 0 : NotificationPanel.ActualWidth * -1; - NotificationPanel.Margin = lastMargin; - - ShowHideNotificationLostFocusBackground(IsNotificationPanelShow); - } - - private async void ShowHideNotificationLostFocusBackground(bool show) - { - if (show) - { - NotificationLostFocusBackground.Visibility = Visibility.Visible; - NotificationLostFocusBackground.Opacity = 0.3; - NotificationPanel.Translation += Shadow48; - ToggleNotificationPanelBtn.Translation -= Shadow16; - ((FontIcon)ToggleNotificationPanelBtn.Content).FontFamily = FontCollections.FontAwesomeSolid; - } - else - { - NotificationLostFocusBackground.Opacity = 0; - NotificationPanel.Translation -= Shadow48; - ToggleNotificationPanelBtn.Translation += Shadow16; - ((FontIcon)ToggleNotificationPanelBtn.Content).FontFamily = FontCollections.FontAwesomeRegular; - await Task.Delay(200); - NotificationLostFocusBackground.Visibility = Visibility.Collapsed; - } - } - - private void NotificationContainerBackground_PointerPressed(object sender, PointerRoutedEventArgs e) - { - IsNotificationPanelShow = false; - ToggleNotificationPanelBtn.IsChecked = false; - ShowHideNotificationPanel(); - } - - private void NavigationViewControl_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) - { - if (!LauncherFrame.CanGoBack || !IsLoadFrameCompleted) - { - return; - } - - LauncherFrame.GoBack(); - if (PreviousTagString.Count < 1) return; - - string lastPreviousTag = PreviousTagString[^1]; - string currentNavigationItemTag = (string)((NavigationViewItem)sender.SelectedItem).Tag; - - if (!string.Equals(lastPreviousTag, currentNavigationItemTag, StringComparison.CurrentCultureIgnoreCase)) - { - return; - } - - string goLastPreviousTag = PreviousTagString[^2]; - - #nullable enable - NavigationViewItem? goPreviousNavigationItem = sender.MenuItems.OfType().FirstOrDefault(x => goLastPreviousTag == (string)x.Tag); - goPreviousNavigationItem ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => goLastPreviousTag == (string)x.Tag); - #nullable restore - - if (goLastPreviousTag == "settings") - { - PreviousTag = goLastPreviousTag; - PreviousTagString.RemoveAt(PreviousTagString.Count - 1); - sender.SelectedItem = sender.SettingsItem; - return; - } - - if (goPreviousNavigationItem == null) - { - return; - } - - PreviousTag = goLastPreviousTag; - PreviousTagString.RemoveAt(PreviousTagString.Count - 1); - sender.SelectedItem = goPreviousNavigationItem; - } - - private void NavigationPanelOpening_Event(NavigationView sender, object args) - { - Thickness curMargin = GridBG_Icon.Margin; - curMargin.Left = 48; - GridBG_Icon.Margin = curMargin; - IsTitleIconForceShow = true; - ToggleTitleIcon(false); - - NavViewPaneBackgroundHoverArea.Width = NavigationViewControl.OpenPaneLength; - } - - private async void NavigationPanelClosing_Event(NavigationView sender, NavigationViewPaneClosingEventArgs args) - { - Thickness curMargin = GridBG_Icon.Margin; - curMargin.Left = 58; - GridBG_Icon.Margin = curMargin; - IsTitleIconForceShow = false; - ToggleTitleIcon(true); - - NavViewPaneBackgroundHoverArea.Width = NavViewPaneBackground.Width; - - await Task.Delay(200); - if (IsCursorInNavBarHoverArea) - { - return; - } - - NavViewPaneBackground.Opacity = 0; - NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); - } - #endregion - - #region Icons - private void ToggleTitleIcon(bool hide) - { - if (!hide) - { - GridBG_IconTitle.Width = double.NaN; - GridBG_IconTitle.Opacity = 1d; - GridBG_IconImg.Opacity = 1d; - return; - } - - GridBG_IconTitle.Width = 0d; - GridBG_IconTitle.Opacity = 0d; - GridBG_IconImg.Opacity = 0.8d; - } + GridBG_IconTitle.Width = 0d; + GridBG_IconTitle.Opacity = 0d; + GridBG_IconImg.Opacity = 0.8d; + } private void GridBG_Icon_PointerEntered(object sender, PointerRoutedEventArgs e) { @@ -1812,480 +761,8 @@ private void GridBG_Icon_Click(object sender, RoutedEventArgs e) } } #endregion - - #region Misc Methods - private static bool IsGameInstalled() => GameInstallationState - is GameInstallStateEnum.Installed - or GameInstallStateEnum.InstalledHavePreload - or GameInstallStateEnum.InstalledHavePlugin - or GameInstallStateEnum.NeedsUpdate; - - private void SpawnWebView2Panel(Uri URL) - { - try - { - WebView2FramePage.WebView2URL = URL; - WebView2Frame.Navigate(typeof(WebView2FramePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromBottom }); - } - catch (Exception ex) - { - SentryHelper.ExceptionHandler(ex); - LogWriteLine($"Error while initialize EdgeWebView2. Opening browser instead!\r\n{ex}", LogType.Error, true); - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = URL.ToString() - } - }.Start(); - } - } - #endregion - - #region Keyboard Shortcuts Methods - private void InitKeyboardShortcuts() - { - if (GetAppConfigValue("EnableShortcuts").ToBoolNullable() == null) - { - SetAndSaveConfigValue("EnableShortcuts", true); - KbShortcutList = null; - - SpawnNotificationPush( - Lang._AppNotification.NotifKbShortcutTitle, - Lang._AppNotification.NotifKbShortcutSubtitle, - NotifSeverity.Informational, - -20, - true, - false, - null, - NotificationPush.GenerateNotificationButton("", Lang._AppNotification.NotifKbShortcutBtn, (_, _) => ShowKeybinds_Invoked(null, null)), - true, - true, - true - ); - } - - if (AreShortcutsEnabled) CreateKeyboardShortcutHandlers(); - } - - private void CreateKeyboardShortcutHandlers() - { - try - { - if (KbShortcutList == null || KbShortcutList.Count == 0) - LoadKbShortcuts(); - - int numIndex = 0; - if (KbShortcutList != null) - { - VirtualKeyModifiers keyModifier = KbShortcutList["GameSelection"].Modifier; - for (; numIndex <= LauncherMetadataHelper.CurrentGameNameCount; numIndex++) - { - KeyboardAccelerator keystroke = new KeyboardAccelerator - { - Modifiers = keyModifier, - Key = VirtualKey.Number1 + numIndex - }; - keystroke.Invoked += KeyboardGameShortcut_Invoked; - KeyboardHandler.KeyboardAccelerators.Add(keystroke); - - KeyboardAccelerator keystrokeNP = new KeyboardAccelerator - { - Key = VirtualKey.NumberPad1 + numIndex - }; - keystrokeNP.Invoked += KeyboardGameShortcut_Invoked; - KeyboardHandler.KeyboardAccelerators.Add(keystrokeNP); - } - - numIndex = 0; - keyModifier = KbShortcutList["RegionSelection"].Modifier; - while (numIndex < LauncherMetadataHelper.CurrentGameRegionMaxCount) - { - KeyboardAccelerator keystroke = new KeyboardAccelerator - { - Modifiers = keyModifier, - Key = VirtualKey.Number1 + numIndex++ - }; - keystroke.Invoked += KeyboardGameRegionShortcut_Invoked; - KeyboardHandler.KeyboardAccelerators.Add(keystroke); - } - } - - KeyboardAccelerator keystrokeF5 = new KeyboardAccelerator - { - Key = VirtualKey.F5 - }; - keystrokeF5.Invoked += RefreshPage_Invoked; - KeyboardHandler.KeyboardAccelerators.Add(keystrokeF5); - - Dictionary actions = new() - { - // General - { "KbShortcutsMenu", ShowKeybinds_Invoked }, - { "HomePage", GoHome_Invoked }, - { "SettingsPage", GoSettings_Invoked }, - { "NotificationPanel", OpenNotify_Invoked }, - - // Game Related - { "ScreenshotFolder", OpenScreenshot_Invoked}, - { "GameFolder", OpenGameFolder_Invoked }, - { "CacheFolder", OpenGameCacheFolder_Invoked }, - { "ForceCloseGame", ForceCloseGame_Invoked }, - - { "RepairPage", GoGameRepair_Invoked }, - { "GameSettingsPage", GoGameSettings_Invoked }, - { "CachesPage", GoGameCaches_Invoked }, - - { "ReloadRegion", RefreshPage_Invoked } - }; - - foreach (KeyValuePair func in actions) - { - if (KbShortcutList == null) - { - continue; - } - - KeyboardAccelerator kbfunc = new KeyboardAccelerator - { - Modifiers = KbShortcutList[func.Key].Modifier, - Key = KbShortcutList[func.Key].Key - }; - kbfunc.Invoked += func.Value; - KeyboardHandler.KeyboardAccelerators.Add(kbfunc); - } - } - catch (Exception error) - { - SentryHelper.ExceptionHandler(error); - LogWriteLine(error.ToString()); - KbShortcutList = null; - CreateKeyboardShortcutHandlers(); - } - } - - private void RefreshPage_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (CannotUseKbShortcuts || !IsLoadRegionComplete) - return; - - switch (PreviousTag) - { - case "launcher": - RestoreCurrentRegion(); - ChangeRegionNoWarning(IsShowRegionChangeWarning ? ChangeRegionConfirmBtn : ChangeRegionConfirmBtnNoWarning, null); - return; - case "settings": - return; - default: - string itemTag = PreviousTag; - PreviousTag = "Empty"; - NavigateInnerSwitch(itemTag); - if (LauncherFrame != null && LauncherFrame.BackStack is { Count: > 0 }) - LauncherFrame.BackStack.RemoveAt(LauncherFrame.BackStack.Count - 1); - if (PreviousTagString is { Count: > 0 }) - PreviousTagString.RemoveAt(PreviousTagString.Count - 1); - return; - } - } - - private void DeleteKeyboardShortcutHandlers() => KeyboardHandler.KeyboardAccelerators.Clear(); - - private static async Task DisableKbShortcuts(int time = 500, CancellationToken token = default) - { - try - { - CannotUseKbShortcuts = true; - await Task.Delay(time, token); - CannotUseKbShortcuts = false; - } - catch - { - // Ignore warnings - } - } - - private void RestoreCurrentRegion() - { - string gameName = GetAppConfigValue("GameCategory")!; - #nullable enable - List? gameNameCollection = LauncherMetadataHelper.GetGameNameCollection(); - _ = LauncherMetadataHelper.GetGameRegionCollection(gameName); - - var indexCategory = gameNameCollection?.IndexOf(gameName) ?? -1; - if (indexCategory < 0) indexCategory = 0; - - var indexRegion = LauncherMetadataHelper.GetPreviousGameRegion(gameName); - - ComboBoxGameCategory.SelectedIndex = indexCategory; - ComboBoxGameRegion.SelectedIndex = indexRegion; - } - - private void KeyboardGameShortcut_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - int index = (int)sender.Key; index -= index < 96 ? 49 : 97; - - DisableInstantRegionChange = true; - RestoreCurrentRegion(); - - if (CannotUseKbShortcuts || !IsLoadRegionComplete - || index >= ComboBoxGameCategory.Items.Count - || ComboBoxGameCategory.SelectedValue == ComboBoxGameCategory.Items[index] - ) - { - DisableInstantRegionChange = false; - return; - } - - ComboBoxGameCategory.SelectedValue = ComboBoxGameCategory.Items[index]; - ComboBoxGameRegion.SelectedIndex = GetIndexOfRegionStringOrDefault(GetComboBoxGameRegionValue(ComboBoxGameCategory.SelectedValue)); - ChangeRegionNoWarning(ChangeRegionConfirmBtn, null); - ChangeRegionConfirmBtn.IsEnabled = false; - ChangeRegionConfirmBtnNoWarning.IsEnabled = false; - CannotUseKbShortcuts = true; - DisableInstantRegionChange = false; - } - - private void KeyboardGameRegionShortcut_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - int index = (int)sender.Key; index -= index < 96 ? 49 : 97; - - DisableInstantRegionChange = true; - RestoreCurrentRegion(); - - - if (CannotUseKbShortcuts || !IsLoadRegionComplete - || index >= ComboBoxGameRegion.Items.Count - || ComboBoxGameRegion.SelectedValue == ComboBoxGameRegion.Items[index]) - { - DisableInstantRegionChange = false; - return; - } - - ComboBoxGameRegion.SelectedValue = ComboBoxGameRegion.Items[index]; - ChangeRegionNoWarning(ChangeRegionConfirmBtn, null); - ChangeRegionConfirmBtn.IsEnabled = false; - ChangeRegionConfirmBtnNoWarning.IsEnabled = false; - CannotUseKbShortcuts = true; - DisableInstantRegionChange = false; - } - - private async void ShowKeybinds_Invoked(KeyboardAccelerator? sender, KeyboardAcceleratorInvokedEventArgs? args) - { - if (CannotUseKbShortcuts) return; - - await Dialog_ShowKbShortcuts(this); - } - - private void GoHome_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (!IsLoadRegionComplete || CannotUseKbShortcuts) return; - - if (NavigationViewControl.SelectedItem == NavigationViewControl.MenuItems[0]) return; - - _ = DisableKbShortcuts(); - NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems[0]; - NavigateInnerSwitch("launcher"); - - } - - private void GoSettings_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (!IsLoadRegionComplete || CannotUseKbShortcuts) return; - - if (NavigationViewControl.SelectedItem == NavigationViewControl.SettingsItem) return; - - _ = DisableKbShortcuts(); - NavigationViewControl.SelectedItem = NavigationViewControl.SettingsItem; - Navigate(typeof(SettingsPage), "settings"); - } - - private void OpenNotify_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - ToggleNotificationPanelBtn.IsChecked = !ToggleNotificationPanelBtn.IsChecked; - ToggleNotificationPanelBtnClick(null, null); - } - - private string GameDirPath { get => CurrentGameProperty.GameVersion.GameDirPath; } - private void OpenScreenshot_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (!IsGameInstalled()) return; - - string ScreenshotFolder = Path.Combine(NormalizePath(GameDirPath), CurrentGameProperty.GameVersion.GamePreset.GameType switch - { - GameNameType.StarRail => $"{Path.GetFileNameWithoutExtension(CurrentGameProperty.GameVersion.GamePreset.GameExecutableName)}_Data\\ScreenShots", - _ => "ScreenShot" - }); - - LogWriteLine($"Opening Screenshot Folder:\r\n\t{ScreenshotFolder}"); - - if (!Directory.Exists(ScreenshotFolder)) - Directory.CreateDirectory(ScreenshotFolder); - - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = "explorer.exe", - Arguments = ScreenshotFolder - } - }.Start(); - } - - private async void OpenGameFolder_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - try - { - if (!IsGameInstalled()) return; - - string GameFolder = NormalizePath(GameDirPath); - LogWriteLine($"Opening Game Folder:\r\n\t{GameFolder}"); - await Task.Run(() => - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = "explorer.exe", - Arguments = GameFolder - } - }.Start()); - } - catch (Exception ex) - { - LogWriteLine($"Failed when trying to open game folder!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - } - - private async void OpenGameCacheFolder_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - try - { - if (!IsGameInstalled()) return; - - string gameFolder = CurrentGameProperty.GameVersion.GameDirAppDataPath; - LogWriteLine($"Opening Game Folder:\r\n\t{gameFolder}"); - await Task.Run(() => - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = "explorer.exe", - Arguments = gameFolder - } - }.Start()); - } - catch (Exception ex) - { - LogWriteLine($"Failed when trying to open game cache folder!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - } - - private void ForceCloseGame_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (!CurrentGameProperty.IsGameRunning) return; - - PresetConfig gamePreset = CurrentGameProperty.GameVersion.GamePreset; - string? gamePresetExecName = gamePreset.GameExecutableName; - if (string.IsNullOrEmpty(gamePresetExecName)) - { - return; - } - - try - { - Process[] gameProcess = Process.GetProcessesByName(gamePresetExecName.Split('.')[0]); - foreach (var p in gameProcess) - { - LogWriteLine($"Trying to stop game process {gamePresetExecName.Split('.')[0]} at PID {p.Id}", LogType.Scheme, true); - p.Kill(); - } - } - catch (Win32Exception ex) - { - SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"There is a problem while trying to stop Game with Region: {gamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); - } - } - private void GoGameRepair_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (!IsLoadRegionComplete || CannotUseKbShortcuts) return; - - if (NavigationViewControl.SelectedItem == NavigationViewControl.MenuItems[2]) return; - - _ = DisableKbShortcuts(); - NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems[2]; - NavigateInnerSwitch("repair"); - } - - private void GoGameCaches_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (!IsLoadRegionComplete || CannotUseKbShortcuts) - return; - if (NavigationViewControl.SelectedItem == NavigationViewControl.MenuItems[3]) - return; - - _ = DisableKbShortcuts(); - NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems[3]; - NavigateInnerSwitch("caches"); - } - - private void GoGameSettings_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (!IsLoadRegionComplete || CannotUseKbShortcuts) - return; - - if (NavigationViewControl.SelectedItem == NavigationViewControl.FooterMenuItems.Last()) - return; - - _ = DisableKbShortcuts(); - NavigationViewControl.SelectedItem = NavigationViewControl.FooterMenuItems.Last(); - switch (CurrentGameProperty.GamePreset) - { - case { GameType: GameNameType.Honkai }: - Navigate(typeof(HonkaiGameSettingsPage), "honkaigamesettings"); - break; - case { GameType: GameNameType.Genshin }: - Navigate(typeof(GenshinGameSettingsPage), "genshingamesettings"); - break; - case { GameType: GameNameType.StarRail }: - Navigate(typeof(StarRailGameSettingsPage), "starrailgamesettings"); - break; - case { GameType: GameNameType.Zenless }: - Navigate(typeof(ZenlessGameSettingsPage), "zenlessgamesettings"); - break; - } - } - - private static bool AreShortcutsEnabled - { - get => GetAppConfigValue("EnableShortcuts").ToBool(true); - } - - private void SettingsPage_KeyboardShortcutsEvent(object sender, int e) - { - switch (e) - { - case 0: - CreateKeyboardShortcutHandlers(); - break; - case 1: - DeleteKeyboardShortcutHandlers(); - CreateKeyboardShortcutHandlers(); - break; - case 2: - DeleteKeyboardShortcutHandlers(); - break; - } - } - #endregion - #region AppActivation + #nullable enable private static bool SetActivatedRegion() { var args = m_arguments.StartGame; @@ -2366,5 +843,35 @@ public void OpenAppActivation() DispatcherQueue?.TryEnqueue(ChangeToActivatedRegion); } #endregion + + #region Misc Methods + private static bool IsGameInstalled() => GameInstallationState + is GameInstallStateEnum.Installed + or GameInstallStateEnum.InstalledHavePreload + or GameInstallStateEnum.InstalledHavePlugin + or GameInstallStateEnum.NeedsUpdate; + + private void SpawnWebView2Panel(Uri URL) + { + try + { + WebView2FramePage.WebView2URL = URL; + WebView2Frame.Navigate(typeof(WebView2FramePage), null, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromBottom }); + } + catch (Exception ex) + { + SentryHelper.ExceptionHandler(ex); + LogWriteLine($"Error while initialize EdgeWebView2. Opening browser instead!\r\n{ex}", LogType.Error, true); + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = URL.ToString() + } + }.Start(); + } + } + #endregion } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameLauncher.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameLauncher.cs new file mode 100644 index 000000000..6d2c8e10a --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameLauncher.cs @@ -0,0 +1,883 @@ +#if !DISABLEDISCORD +using CollapseLauncher.DiscordPresence; +#endif +using CollapseLauncher.Helper; +using CollapseLauncher.Helper.Animation; +using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.Interfaces; +using H.NotifyIcon; +using Hi3Helper; +using Hi3Helper.EncTool.WindowTool; +using Hi3Helper.SentryHelper; +using Hi3Helper.Win32.Native.ManagedTools; +using Hi3Helper.Win32.Screen; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static CollapseLauncher.InnerLauncherConfig; +using static Hi3Helper.Data.ConverterTool; +using static Hi3Helper.Locale; +using static Hi3Helper.Logger; +using static Hi3Helper.Shared.Region.LauncherConfig; +using Size = System.Drawing.Size; + +// ReSharper disable InconsistentNaming +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault +// ReSharper disable CheckNamespace + +namespace CollapseLauncher.Pages; + +public partial class HomePage +{ + #region Game Start/Stop Method + + private CancellationTokenSource WatchOutputLog = new(); + private CancellationTokenSource ResizableWindowHookToken; + private async void StartGame(object sender, RoutedEventArgs e) + { + // Initialize values + IGameSettingsUniversal _Settings = CurrentGameProperty!.GameSettings!.AsIGameSettingsUniversal(); + PresetConfig _gamePreset = CurrentGameProperty!.GameVersion!.GamePreset!; + + var isGenshin = CurrentGameProperty!.GameVersion.GameType == GameNameType.Genshin; + var giForceHDR = false; + + try + { + if (!await CheckMediaPackInstalled()) return; + + if (isGenshin) + { + giForceHDR = GetAppConfigValue("ForceGIHDREnable").ToBool(); + if (giForceHDR) GenshinHDREnforcer(); + } + + if (_Settings is { SettingsCollapseMisc: { UseAdvancedGameSettings: true, UseGamePreLaunchCommand: true } }) + { + var delay = _Settings.SettingsCollapseMisc.GameLaunchDelay; + PreLaunchCommand(_Settings); + if (delay > 0) + await Task.Delay(delay); + } + + int? height = _Settings.SettingsScreen.height; + int? width = _Settings.SettingsScreen.width; + + Process proc = new Process(); + proc.StartInfo.FileName = Path.Combine(NormalizePath(GameDirPath)!, _gamePreset.GameExecutableName!); + proc.StartInfo.UseShellExecute = true; + proc.StartInfo.Arguments = GetLaunchArguments(_Settings)!; + LogWriteLine($"[HomePage::StartGame()] Running game with parameters:\r\n{proc.StartInfo.Arguments}"); + if (File.Exists(Path.Combine(GameDirPath!, "@AltLaunchMode"))) + { + LogWriteLine("[HomePage::StartGame()] Using alternative launch method!", LogType.Warning, true); + proc.StartInfo.WorkingDirectory = (CurrentGameProperty!.GameVersion.GamePreset!.ZoneName == "Bilibili" || + (isGenshin && giForceHDR) ? NormalizePath(GameDirPath) : + Path.GetDirectoryName(NormalizePath(GameDirPath))!)!; + } + else + { + proc.StartInfo.WorkingDirectory = NormalizePath(GameDirPath)!; + } + proc.StartInfo.UseShellExecute = false; + proc.StartInfo.Verb = "runas"; + proc.Start(); + + if (GetAppConfigValue("EnableConsole").ToBool()) + { + WatchOutputLog = new CancellationTokenSource(); + ReadOutputLog(); + } + + if (_Settings.SettingsCollapseScreen.UseCustomResolution && height != 0 && width != 0) + { + SetBackScreenSettings(_Settings, (int)height, (int)width, CurrentGameProperty); + } + + // Stop update check + IsSkippingUpdateCheck = true; + + // Start the resizable window payload (also use the same token as PlaytimeToken) + StartResizableWindowPayload( + _gamePreset.GameExecutableName, + _Settings, + _gamePreset.GameType, height, width); + GameRunningWatcher(_Settings); + + switch (GetAppConfigValue("GameLaunchedBehavior").ToString()) + { + case "Minimize": + WindowUtility.WindowMinimize(); + break; + case "ToTray": + WindowUtility.ToggleToTray_MainWindow(); + break; + case "Nothing": + break; + default: + WindowUtility.WindowMinimize(); + break; + } + + CurrentGameProperty.GamePlaytime.StartSession(proc); + + if (GetAppConfigValue("LowerCollapsePrioOnGameLaunch").ToBool()) CollapsePrioControl(proc); + + // Set game process priority to Above Normal when GameBoost is on + if (_Settings.SettingsCollapseMisc is { UseGameBoost: true }) + #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Run(() => Task.FromResult(_ = GameBoost_Invoke(CurrentGameProperty))); + #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + // Run game process watcher + CheckRunningGameInstance(PageToken.Token); + } + catch (Win32Exception ex) + { + LogWriteLine($"There is a problem while trying to launch Game with Region: {_gamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); + ErrorSender.SendException(new Win32Exception($"There was an error while trying to launch the game!\r\tThrow: {ex}", ex)); + IsSkippingUpdateCheck = false; + } + } + + // Use this method to do something when game is closed + private async void GameRunningWatcher(IGameSettingsUniversal _settings) + { + ArgumentNullException.ThrowIfNull(_settings); + + await Task.Delay(5000); + while (_cachedIsGameRunning) + { + await Task.Delay(3000); + } + + LogWriteLine($"{new string('=', barWidth)} GAME STOPPED {new string('=', barWidth)}", LogType.Warning, true); + + if (ResizableWindowHookToken != null) + { + await ResizableWindowHookToken.CancelAsync(); + ResizableWindowHookToken.Dispose(); + } + + // Stopping GameLogWatcher + if (GetAppConfigValue("EnableConsole").ToBool()) + { + if (WatchOutputLog == null) return; + await WatchOutputLog.CancelAsync(); + } + + // Stop PreLaunchCommand process + if (_settings.SettingsCollapseMisc!.GamePreLaunchExitOnGameStop) PreLaunchCommand_ForceClose(); + + // Window manager on game closed + switch (GetAppConfigValue("GameLaunchedBehavior").ToString()) + { + case "Minimize": + WindowUtility.WindowRestore(); + break; + case "ToTray": + WindowUtility.CurrentWindow!.Show(); + WindowUtility.WindowRestore(); + break; + case "Nothing": + break; + default: + WindowUtility.WindowRestore(); + break; + } + + // Run Post Launch Command + if (_settings.SettingsCollapseMisc.UseAdvancedGameSettings && _settings.SettingsCollapseMisc.UseGamePostExitCommand) PostExitCommand(_settings); + + // Re-enable update check + IsSkippingUpdateCheck = false; + } + + private static void StopGame(PresetConfig gamePreset) + { + ArgumentNullException.ThrowIfNull(gamePreset); + try + { + Process[] gameProcess = Process.GetProcessesByName(gamePreset.GameExecutableName!.Split('.')[0]); + foreach (var p in gameProcess) + { + LogWriteLine($"Trying to stop game process {gamePreset.GameExecutableName.Split('.')[0]} at PID {p.Id}", LogType.Scheme, true); + p.Kill(); + } + } + catch (Win32Exception ex) + { + SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"There is a problem while trying to stop Game with Region: {gamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); + } + } + #endregion + + #region Game Launch Argument Builder + + private bool RequireWindowExclusivePayload; + + private string GetLaunchArguments(IGameSettingsUniversal _Settings) + { + StringBuilder parameter = new StringBuilder(); + + switch (CurrentGameProperty.GameVersion.GameType) + { + case GameNameType.Honkai: + { + if (_Settings.SettingsCollapseScreen.UseExclusiveFullscreen) + { + parameter.Append("-window-mode exclusive "); + RequireWindowExclusivePayload = true; + } + + Size screenSize = _Settings.SettingsScreen.sizeRes; + + byte apiID = _Settings.SettingsCollapseScreen.GameGraphicsAPI; + + if (apiID == 4) + { + LogWriteLine("You are going to use DX12 mode in your game.\r\n\tUsing CustomScreenResolution or FullscreenExclusive value may break the game!", LogType.Warning); + if (_Settings.SettingsCollapseScreen.UseCustomResolution && _Settings.SettingsScreen.isfullScreen) + { + var size = ScreenProp.CurrentResolution; + parameter.Append($"-screen-width {size.Width} -screen-height {size.Height} "); + } + else + parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + } + else + parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + + switch (apiID) + { + case 0: + parameter.Append("-force-feature-level-10-1 "); + break; + // case 1 is default + default: + parameter.Append("-force-feature-level-11-0 -force-d3d11-no-singlethreaded "); + break; + case 2: + parameter.Append("-force-feature-level-11-1 "); + break; + case 3: + parameter.Append("-force-feature-level-11-1 -force-d3d11-no-singlethreaded "); + break; + case 4: + parameter.Append("-force-d3d12 "); + break; + } + + break; + } + case GameNameType.StarRail: + { + if (_Settings.SettingsCollapseScreen.UseExclusiveFullscreen) + { + parameter.Append("-window-mode exclusive -screen-fullscreen 1 "); + RequireWindowExclusivePayload = true; + } + + // Enable mobile mode + if (_Settings.SettingsCollapseMisc.LaunchMobileMode) + { + const string regLoc = GameSettings.StarRail.Model.ValueName; + var regRoot = GameSettings.Base.SettingsBase.RegistryRoot; + + if (regRoot != null || !string.IsNullOrEmpty(regLoc)) + { + var regModel = (byte[])regRoot!.GetValue(regLoc, null); + + if (regModel != null) + { + string regB64 = Convert.ToBase64String(regModel); + parameter.Append($"-is_cloud 1 -platform_type CLOUD_WEB_TOUCH -graphics_setting {regB64} "); + } + else + { + LogWriteLine("Failed enabling MobileMode for HSR: regModel is null.", LogType.Error, true); + } + } + else + { + LogWriteLine("Failed enabling MobileMode for HSR: regRoot/regLoc is unexpectedly uninitialized.", + LogType.Error, true); + } + } + + Size screenSize = _Settings.SettingsScreen.sizeRes; + + byte apiID = _Settings.SettingsCollapseScreen.GameGraphicsAPI; + + if (apiID == 4) + { + LogWriteLine("You are going to use DX12 mode in your game.\r\n\tUsing CustomScreenResolution or FullscreenExclusive value may break the game!", LogType.Warning); + if (_Settings.SettingsCollapseScreen.UseCustomResolution && _Settings.SettingsScreen.isfullScreen) + { + var size = ScreenProp.CurrentResolution; + parameter.Append($"-screen-width {size.Width} -screen-height {size.Height} "); + } + else + parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + } + else + parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + + break; + } + case GameNameType.Genshin: + { + if (_Settings.SettingsCollapseScreen.UseExclusiveFullscreen) + { + parameter.Append("-window-mode exclusive -screen-fullscreen 1 "); + RequireWindowExclusivePayload = true; + LogWriteLine("Exclusive mode is enabled in Genshin Impact, stability may suffer!\r\nTry not to Alt+Tab when game is on its loading screen :)", LogType.Warning, true); + } + + // Enable mobile mode + if (_Settings.SettingsCollapseMisc.LaunchMobileMode) + parameter.Append("use_mobile_platform -is_cloud 1 -platform_type CLOUD_THIRD_PARTY_MOBILE "); + + Size screenSize = _Settings.SettingsScreen.sizeRes; + + byte apiID = _Settings.SettingsCollapseScreen.GameGraphicsAPI; + + if (apiID == 4) + { + LogWriteLine("You are going to use DX12 mode in your game.\r\n\tUsing CustomScreenResolution or FullscreenExclusive value may break the game!", LogType.Warning); + if (_Settings.SettingsCollapseScreen.UseCustomResolution && _Settings.SettingsScreen.isfullScreen) + { + var size = ScreenProp.CurrentResolution; + parameter.Append($"-screen-width {size.Width} -screen-height {size.Height} "); + } + else + parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + } + else + parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + + break; + } + case GameNameType.Zenless: + { + // does not support exclusive mode at all + // also doesn't properly support dx12 or dx11 st + + if (_Settings.SettingsCollapseScreen.UseCustomResolution) + { + Size screenSize = _Settings.SettingsScreen.sizeRes; + parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); + } + + break; + } + } + + if (_Settings.SettingsCollapseScreen.UseBorderlessScreen) + { + parameter.Append("-popupwindow "); + } + + if (!_Settings.SettingsCollapseMisc.UseCustomArguments) + { + return parameter.ToString(); + } + + string customArgs = _Settings.SettingsCustomArgument.CustomArgumentValue; + if (!string.IsNullOrEmpty(customArgs)) + parameter.Append(customArgs); + + return parameter.ToString(); + } + + public string CustomArgsValue + { + get => CurrentGameProperty?.GameSettings?.SettingsCustomArgument.CustomArgumentValue; + set + { + if (CurrentGameProperty.GameSettings == null) + return; + + CurrentGameProperty.GameSettings.SettingsCustomArgument.CustomArgumentValue = value; + } + } + + public bool UseCustomArgs + { + get => CurrentGameProperty?.GameSettings?.SettingsCollapseMisc.UseCustomArguments ?? false; + set + { + if (CurrentGameProperty.GameSettings == null) + return; + + CustomArgsTextBox.IsEnabled = CustomStartupArgsSwitch.IsOn; + CurrentGameProperty.GameSettings.SettingsCollapseMisc.UseCustomArguments = value; + } + + } + + public bool UseCustomBGRegion + { + get + { + bool value = CurrentGameProperty?.GameSettings?.SettingsCollapseMisc?.UseCustomRegionBG ?? false; + ChangeGameBGButton.IsEnabled = value; + string path = CurrentGameProperty?.GameSettings?.SettingsCollapseMisc?.CustomRegionBGPath ?? ""; + BGPathDisplay.Text = Path.GetFileName(path); + return value; + } + set + { + ChangeGameBGButton.IsEnabled = value; + + if (CurrentGameProperty?.GameSettings == null) + return; + + var regionBgPath = CurrentGameProperty.GameSettings.SettingsCollapseMisc.CustomRegionBGPath; + if (string.IsNullOrEmpty(regionBgPath) || !File.Exists(regionBgPath)) + { + regionBgPath = Path.GetFileName(GetAppConfigValue("CustomBGPath").ToString()); + CurrentGameProperty.GameSettings.SettingsCollapseMisc + .CustomRegionBGPath = regionBgPath; + } + + CurrentGameProperty.GameSettings.SettingsCollapseMisc.UseCustomRegionBG = value; + CurrentGameProperty.GameSettings.SaveBaseSettings(); + _ = m_mainPage?.ChangeBackgroundImageAsRegionAsync(); + + BGPathDisplay.Text = Path.GetFileName(regionBgPath); + } + } + #endregion + + #region Game Log Method + private async void ReadOutputLog() + { + var saveGameLog = GetAppConfigValue("IncludeGameLogs").ToBool(); + InitializeConsoleValues(); + + // JUST IN CASE + // Sentry issue ref : COLLAPSE-LAUNCHER-55; Event ID: 13059407 + if (int.IsNegative(barWidth)) barWidth = 30; + + LogWriteLine($"{new string('=', barWidth)} GAME STARTED {new string('=', barWidth)}", LogType.Warning, + true); + LogWriteLine($"Are Game logs getting saved to Collapse logs: {saveGameLog}", LogType.Scheme, true); + + try + { + string logPath = Path.Combine(CurrentGameProperty.GameVersion.GameDirAppDataPath, + CurrentGameProperty.GameVersion.GameOutputLogName); + if (!Directory.Exists(Path.GetDirectoryName(logPath))) + Directory.CreateDirectory(Path.GetDirectoryName(logPath)!); + + if (CurrentGameProperty.GamePreset.GameType == GameNameType.Zenless) + { + var logDir = Path.Combine(CurrentGameProperty.GameVersion.GameDirPath, + @"ZenlessZoneZero_Data\Persistent\LogDir\"); + + _ = Directory.CreateDirectory(logDir); // Always ensure that the LogDir will always be created. + + var newLog = await FileUtility.WaitForNewFileAsync(logDir, 20000); + if (!newLog) + { + LogWriteLine("Cannot get Zenless' log file due to timeout! Your computer too fast XD", + LogType.Warning, saveGameLog); + return; + } + + var logPat = FileUtility.GetLatestFile(logDir, "NAP_*.log"); + + if (!string.IsNullOrEmpty(logPat)) logPath = logPat; + } + else + { + // If the log file exist beforehand, move it and make a new one + if (File.Exists(logPath)) + { + FileUtility.RenameFileWithPrefix(logPath, "-old", true); + } + } + + LogWriteLine($"Reading Game's log file from {logPath}", LogType.Default, saveGameLog); + + await using FileStream fs = + new FileStream(logPath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite); + using StreamReader reader = new StreamReader(fs); + while (true) + { + while (!reader.EndOfStream) + { + var line = await reader.ReadLineAsync(WatchOutputLog.Token); + if (RequireWindowExclusivePayload && line == "MoleMole.MonoGameEntry:Awake()") + { + StartExclusiveWindowPayload(); + RequireWindowExclusivePayload = false; + } + + LogWriteLine(line!, LogType.Game, saveGameLog); + } + + await Task.Delay(100, WatchOutputLog.Token); + } + } + catch (OperationCanceledException) + { + // Ignore when cancelled + } + catch (Exception ex) + { + await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"There were a problem in Game Log Reader\r\n{ex}", LogType.Error); + } + } + #endregion + + #region Exclusive Window Payload + private async void StartExclusiveWindowPayload() + { + IntPtr _windowPtr = ProcessChecker.GetProcessWindowHandle(CurrentGameProperty.GameVersion.GamePreset.GameExecutableName ?? ""); + await Task.Delay(1000); + Windowing.HideWindow(_windowPtr); + await Task.Delay(1000); + Windowing.ShowWindow(_windowPtr); + } + #endregion + + #region Game Resizable Window Payload + private async void StartResizableWindowPayload(string executableName, IGameSettingsUniversal settings, + GameNameType gameType, int? height, int? width) + { + try + { + // Check if the game is using Resizable Window settings + if (!settings.SettingsCollapseScreen.UseResizableWindow) return; + ResizableWindowHookToken = new CancellationTokenSource(); + + executableName = Path.GetFileNameWithoutExtension(executableName); + string gameExecutableDirectory = CurrentGameProperty.GameVersion.GameDirPath; + ResizableWindowHook resizableWindowHook = new ResizableWindowHook(); + + // Set the pos + size reinitialization to true if the game is Honkai: Star Rail + // This is required for Honkai: Star Rail since the game will reset its pos + size. Making + // it impossible to use custom resolution (but since you are using Collapse, it's now + // possible :teriStare:) + bool isNeedToResetPos = gameType == GameNameType.StarRail; + await Task.Run(() => resizableWindowHook.StartHook(executableName, height, width, ResizableWindowHookToken.Token, + isNeedToResetPos, ILoggerHelper.GetILogger(), gameExecutableDirectory)); + } + catch (Exception ex) + { + LogWriteLine($"Error while initializing Resizable Window payload!\r\n{ex}"); + ErrorSender.SendException(ex, ErrorType.GameError); + } + } + + private async void SetBackScreenSettings(IGameSettingsUniversal settingsUniversal, int height, int width, + GamePresetProperty gameProp) + { + // Wait for the game to fully initialize + await Task.Delay(20000); + try + { + settingsUniversal.SettingsScreen.height = height; + settingsUniversal.SettingsScreen.width = width; + settingsUniversal.SettingsScreen.Save(); + + // For those stubborn game + // Kinda unneeded but :FRICK: + switch (gameProp.GamePreset.GameType) + { + case GameNameType.Zenless: + var screenManagerZ = GameSettings.Zenless.ScreenManager.Load(); + screenManagerZ.width = width; + screenManagerZ.height = height; + screenManagerZ.Save(); + break; + + case GameNameType.Honkai: + var screenManagerH = GameSettings.Honkai.ScreenSettingData.Load(); + screenManagerH.width = width; + screenManagerH.height = height; + screenManagerH.Save(); + break; + } + + LogWriteLine($"[SetBackScreenSettings] Completed task! {width}x{height}", LogType.Scheme, true); + } + catch(Exception ex) + { + await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"[SetBackScreenSettings] Failed to set Screen Settings!\r\n{ex}", LogType.Error, true); + } + + } + #endregion + + #region Pre/Post Game Launch Command + + private static readonly string CmdPath = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"); + + private Process _procPreGLC; + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private async void PreLaunchCommand(IGameSettingsUniversal settings) + { + try + { + var preGameLaunchCommand = settings?.SettingsCollapseMisc?.GamePreLaunchCommand; + if (string.IsNullOrEmpty(preGameLaunchCommand)) return; + + LogWriteLine($"Using Pre-launch command : {preGameLaunchCommand}\r\n" + + $"Game launch is delayed by {settings.SettingsCollapseMisc.GameLaunchDelay} ms\r\n\t" + + $"BY USING THIS, NO SUPPORT IS PROVIDED IF SOMETHING HAPPENED TO YOUR ACCOUNT, GAME, OR SYSTEM!", + LogType.Warning, true); + + _procPreGLC = new Process(); + + _procPreGLC.StartInfo.FileName = CmdPath; + _procPreGLC.StartInfo.Arguments = "/S /C " + "\"" + preGameLaunchCommand + "\""; + _procPreGLC.StartInfo.CreateNoWindow = true; + _procPreGLC.StartInfo.UseShellExecute = false; + _procPreGLC.StartInfo.RedirectStandardOutput = true; + _procPreGLC.StartInfo.RedirectStandardError = true; + + _procPreGLC.OutputDataReceived += GLC_OutputHandler; + _procPreGLC.ErrorDataReceived += GLC_ErrorHandler; + + _procPreGLC.Start(); + + _procPreGLC.BeginOutputReadLine(); + _procPreGLC.BeginErrorReadLine(); + + await _procPreGLC.WaitForExitAsync(); + + _procPreGLC.OutputDataReceived -= GLC_OutputHandler; + _procPreGLC.ErrorDataReceived -= GLC_ErrorHandler; + } + catch (Win32Exception ex) + { + LogWriteLine($"There is a problem while trying to launch Pre-Game Command with Region: " + + $"{CurrentGameProperty.GameVersion.GamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); + ErrorSender.SendException(new Win32Exception($"There was an error while trying to launch Pre-Launch command!\r\tThrow: {ex}", ex)); + } + finally + { + _procPreGLC?.Dispose(); + } + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private void PreLaunchCommand_ForceClose() + { + try + { + if (_procPreGLC == null || _procPreGLC.HasExited || _procPreGLC.Id == 0 ) return; + + // Kill main and child processes + var taskKill = new Process(); + taskKill.StartInfo.FileName = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "taskkill.exe"); + + taskKill.StartInfo.Arguments = $"/F /T /PID {_procPreGLC.Id}"; + taskKill.Start(); + taskKill.WaitForExit(); + + LogWriteLine("Pre-launch command has been forced to close!", LogType.Warning, true); + } + // Ignore external errors + catch (InvalidOperationException ioe) + { + SentryHelper.ExceptionHandler(ioe); + } + catch (Win32Exception we) + { + SentryHelper.ExceptionHandler(we); + } + catch (Exception ex) + { + SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"Error when trying to close Pre-GLC!\r\n{ex}", LogType.Error, true); + } + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private static async void PostExitCommand(IGameSettingsUniversal settings) + { + try + { + var postGameExitCommand = settings?.SettingsCollapseMisc?.GamePostExitCommand; + if (string.IsNullOrEmpty(postGameExitCommand)) return; + + LogWriteLine($"Using Post-launch command : {postGameExitCommand}\r\n\t" + + $"BY USING THIS, NO SUPPORT IS PROVIDED IF SOMETHING HAPPENED TO YOUR ACCOUNT, GAME, OR SYSTEM!", + LogType.Warning, true); + + Process procPostGLC = new Process(); + + procPostGLC.StartInfo.FileName = CmdPath; + procPostGLC.StartInfo.Arguments = "/S /C " + "\"" + postGameExitCommand + "\""; + procPostGLC.StartInfo.CreateNoWindow = true; + procPostGLC.StartInfo.UseShellExecute = false; + procPostGLC.StartInfo.RedirectStandardOutput = true; + procPostGLC.StartInfo.RedirectStandardError = true; + + procPostGLC.OutputDataReceived += GLC_OutputHandler; + procPostGLC.ErrorDataReceived += GLC_ErrorHandler; + + procPostGLC.Start(); + procPostGLC.BeginOutputReadLine(); + procPostGLC.BeginErrorReadLine(); + + await procPostGLC.WaitForExitAsync(); + + procPostGLC.OutputDataReceived -= GLC_OutputHandler; + procPostGLC.ErrorDataReceived -= GLC_ErrorHandler; + } + catch (Win32Exception ex) + { + LogWriteLine($"There is a problem while trying to launch Post-Game Command with command:\r\n\t" + + $"{settings?.SettingsCollapseMisc?.GamePostExitCommand}\r\n" + + $"Traceback: {ex}", LogType.Error, true); + ErrorSender.SendException(new Win32Exception($"There was an error while trying to launch Post-Exit command\r\tThrow: {ex}", ex)); + } + } + + private static void GLC_OutputHandler(object _, DataReceivedEventArgs e) + { + if (!string.IsNullOrEmpty(e.Data)) LogWriteLine(e.Data, LogType.GLC, true); + } + + private static void GLC_ErrorHandler(object _, DataReceivedEventArgs e) + { + if (!string.IsNullOrEmpty(e.Data)) LogWriteLine($"ERROR RECEIVED!\r\n\t" + $"{e.Data}", LogType.GLC, true); + } + #endregion + + #region Game Running State + private async void CheckRunningGameInstance(CancellationToken token) + { + TextBlock startGameBtnText = (StartGameBtn.Content as Grid)!.Children.OfType().FirstOrDefault(); + FontIcon startGameBtnIcon = (StartGameBtn.Content as Grid)!.Children.OfType().FirstOrDefault(); + Grid startGameBtnAnimatedIconGrid = (StartGameBtn.Content as Grid)!.Children.OfType().FirstOrDefault(); + // AnimatedVisualPlayer StartGameBtnAnimatedIcon = StartGameBtnAnimatedIconGrid!.Children.OfType().FirstOrDefault(); + string startGameBtnIconGlyph = startGameBtnIcon!.Glyph; + const string startGameBtnRunningIconGlyph = ""; + + startGameBtnIcon.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); + startGameBtnAnimatedIconGrid.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); + + try + { + while (CurrentGameProperty.IsGameRunning) + { + _cachedIsGameRunning = true; + + StartGameBtn.IsEnabled = false; + if (startGameBtnText != null && startGameBtnAnimatedIconGrid != null) + { + startGameBtnText.Text = Lang._HomePage.StartBtnRunning; + startGameBtnIcon.Glyph = startGameBtnRunningIconGlyph; + startGameBtnAnimatedIconGrid.Opacity = 0; + startGameBtnIcon.Opacity = 1; + + startGameBtnText.UpdateLayout(); + + RepairGameButton.IsEnabled = false; + UninstallGameButton.IsEnabled = false; + ConvertVersionButton.IsEnabled = false; + CustomArgsTextBox.IsEnabled = false; + MoveGameLocationButton.IsEnabled = false; + StopGameButton.IsEnabled = true; + + PlaytimeIdleStack.Visibility = Visibility.Collapsed; + PlaytimeRunningStack.Visibility = Visibility.Visible; + + if (CurrentGameProperty.TryGetGameProcessIdWithActiveWindow(out var processId, out _)) + { + using Process currentGameProcess = Process.GetProcessById(processId); + + // HACK: For some reason, the text still unchanged. + // Make sure the start game button text also changed. + startGameBtnText.Text = Lang._HomePage.StartBtnRunning; + var fromActivityOffset = currentGameProcess.StartTime; + var gameSettings = CurrentGameProperty!.GameSettings!.AsIGameSettingsUniversal(); + var gamePreset = CurrentGameProperty.GamePreset; + + #if !DISABLEDISCORD + if (ToggleRegionPlayingRpc) + AppDiscordPresence?.SetActivity(ActivityType.Play, fromActivityOffset.ToUniversalTime()); + #endif + + CurrentGameProperty!.GamePlaytime!.StartSession(currentGameProcess); + + int? height = gameSettings.SettingsScreen.height; + int? width = gameSettings.SettingsScreen.width; + + // Start the resizable window payload + StartResizableWindowPayload(gamePreset.GameExecutableName, + gameSettings, + gamePreset.GameType, height, width); + + await currentGameProcess.WaitForExitAsync(token); + } + } + + await Task.Delay(RefreshRate, token); + } + + _cachedIsGameRunning = false; + + StartGameBtn.IsEnabled = true; + startGameBtnText!.Text = Lang._HomePage.StartBtn; + startGameBtnIcon.Glyph = startGameBtnIconGlyph; + if (startGameBtnAnimatedIconGrid != null) + { + startGameBtnAnimatedIconGrid.Opacity = 1; + } + + startGameBtnIcon.Opacity = 0; + + GameStartupSetting.IsEnabled = true; + RepairGameButton.IsEnabled = true; + MoveGameLocationButton.IsEnabled = true; + UninstallGameButton.IsEnabled = true; + ConvertVersionButton.IsEnabled = true; + CustomArgsTextBox.IsEnabled = true; + StopGameButton.IsEnabled = false; + + PlaytimeIdleStack.Visibility = Visibility.Visible; + PlaytimeRunningStack.Visibility = Visibility.Collapsed; + + #if !DISABLEDISCORD + AppDiscordPresence?.SetActivity(ActivityType.Idle); + #endif + } + catch (TaskCanceledException) + { + // Ignore + LogWriteLine("Game run watcher has been terminated!"); + } + catch (Exception ex) + { + await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"Error when checking if game is running!\r\n{ex}", LogType.Error, true); + } + } + #endregion +} \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameManagement.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameManagement.cs new file mode 100644 index 000000000..30b76cac4 --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.GameManagement.cs @@ -0,0 +1,908 @@ +using CollapseLauncher.CustomControls; +using CollapseLauncher.Dialogs; +using CollapseLauncher.Helper; +using CollapseLauncher.Helper.Animation; +using CollapseLauncher.Helper.Image; +using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.InstallManager.Base; +using CollapseLauncher.Statics; +using CommunityToolkit.WinUI.Animations; +using Hi3Helper; +using Hi3Helper.SentryHelper; +using Hi3Helper.Shared.ClassStruct; +using Hi3Helper.Win32.FileDialogCOM; +using Hi3Helper.Win32.Native.ManagedTools; +using Microsoft.UI.Composition; +using Microsoft.UI.Text; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using System.Threading.Tasks; +using Microsoft.UI.Xaml.Media; +using static CollapseLauncher.Dialogs.SimpleDialogs; +using static CollapseLauncher.InnerLauncherConfig; +using static CollapseLauncher.Helper.Background.BackgroundMediaUtility; +using static Hi3Helper.Data.ConverterTool; +using static Hi3Helper.Locale; +using static Hi3Helper.Logger; +using static Hi3Helper.Shared.Region.LauncherConfig; +using UIElementExtensions = CollapseLauncher.Extension.UIElementExtensions; +// ReSharper disable InconsistentNaming +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault +// ReSharper disable CheckNamespace + +namespace CollapseLauncher.Pages; + +public sealed partial class HomePage +{ + #region Preload + private async void SpawnPreloadBox() + { + if (CurrentGameProperty.GameInstall.IsUseSophon) + { + DownloadModeLabelPreload.Visibility = Visibility.Visible; + DownloadModeLabelPreloadText.Text = Lang._Misc.DownloadModeLabelSophon; + } + + if (CurrentGameProperty.GameInstall.IsRunning) + { + // TODO + PauseDownloadPreBtn.Visibility = Visibility.Visible; + ResumeDownloadPreBtn.Visibility = Visibility.Collapsed; + PreloadDialogBox.IsClosable = false; + + IsSkippingUpdateCheck = true; + DownloadPreBtn.Visibility = Visibility.Collapsed; + if (CurrentGameProperty.GameInstall.IsUseSophon) + { + ProgressPreSophonStatusGrid.Visibility = Visibility.Visible; + ProgressPreStatusGrid.Visibility = Visibility.Collapsed; + } + else + { + ProgressPreStatusGrid.Visibility = Visibility.Visible; + } + ProgressPreButtonGrid.Visibility = Visibility.Visible; + PreloadDialogBox.Title = Lang._HomePage.PreloadDownloadNotifbarTitle; + PreloadDialogBox.Message = Lang._HomePage.PreloadDownloadNotifbarSubtitle; + + CurrentGameProperty.GameInstall.ProgressChanged += PreloadDownloadProgress; + CurrentGameProperty.GameInstall.StatusChanged += PreloadDownloadStatus; + SpawnPreloadDialogBox(); + return; + } + + string ver = CurrentGameProperty.GameVersion.GetGameVersionApiPreload()?.VersionString; + + try + { + if (CurrentGameProperty.GameVersion.IsGameHasDeltaPatch()) + { + PreloadDialogBox.Title = string.Format(Lang._HomePage.PreloadNotifDeltaDetectTitle, ver); + PreloadDialogBox.Message = Lang._HomePage.PreloadNotifDeltaDetectSubtitle; + DownloadPreBtn.Visibility = Visibility.Collapsed; + SpawnPreloadDialogBox(); + return; + } + + if (!await CurrentGameProperty.GameInstall.IsPreloadCompleted(PageToken.Token)) + { + PreloadDialogBox.Title = string.Format(Lang._HomePage.PreloadNotifTitle, ver); + } + else + { + PreloadDialogBox.Title = Lang._HomePage.PreloadNotifCompleteTitle; + PreloadDialogBox.Message = string.Format(Lang._HomePage.PreloadNotifCompleteSubtitle, ver); + PreloadDialogBox.IsClosable = true; + DownloadPreBtn.Content = UIElementExtensions.CreateIconTextGrid( + text: Lang._HomePage.PreloadNotifIntegrityCheckBtn, + iconGlyph: "", + iconFontFamily: "FontAwesomeSolid", + textWeight: FontWeights.Medium + ); + } + SpawnPreloadDialogBox(); + } + catch (Exception ex) + { + await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"An error occured while trying to determine delta-patch availability\r\n{ex}", LogType.Error, true); + } + } + + private async void SpawnPreloadDialogBox() + { + PreloadDialogBox.IsOpen = true; + PreloadDialogBox.Translation = new Vector3(0, 0, 16); + Compositor compositor = CompositionTarget.GetCompositorForCurrentThread(); + + PreloadDialogBox.Opacity = 0.0f; + const float toScale = 0.98f; + Vector3 toTranslate = new Vector3(-((float)(PreloadDialogBox?.ActualWidth ?? 0) * (toScale - 1f) / 2), + -((float)(PreloadDialogBox?.ActualHeight ?? 0) * (toScale - 1f)) - 16, 0); + + await PreloadDialogBox.StartAnimation(TimeSpan.FromSeconds(0.5), + compositor.CreateScalarKeyFrameAnimation("Opacity", 1.0f, 0.0f), + compositor.CreateVector3KeyFrameAnimation("Scale", + new Vector3(1.0f, 1.0f, PreloadDialogBox!.Translation.Z), + new Vector3(toScale, toScale, + PreloadDialogBox.Translation.Z)), + compositor.CreateVector3KeyFrameAnimation("Translation", PreloadDialogBox.Translation, toTranslate) + ); + } + + private async void PredownloadDialog(object sender, RoutedEventArgs e) + { + ((Button)sender).IsEnabled = false; + + PauseDownloadPreBtn.Visibility = Visibility.Visible; + ResumeDownloadPreBtn.Visibility = Visibility.Collapsed; + PreloadDialogBox.IsClosable = false; + + try + { + MainWindow.IsCriticalOpInProgress = true; + // Prevent device from sleep + Sleep.PreventSleep(ILoggerHelper.GetILogger()); + // Set the notification trigger to "Running" state + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Running); + + IsSkippingUpdateCheck = true; + DownloadPreBtn.Visibility = Visibility.Collapsed; + if (CurrentGameProperty.GameInstall.IsUseSophon) + { + ProgressPreSophonStatusGrid.Visibility = Visibility.Visible; + ProgressPreStatusGrid.Visibility = Visibility.Collapsed; + } + else + { + ProgressPreStatusGrid.Visibility = Visibility.Visible; + } + ProgressPreButtonGrid.Visibility = Visibility.Visible; + PreloadDialogBox.Title = Lang._HomePage.PreloadDownloadNotifbarTitle; + PreloadDialogBox.Message = Lang._HomePage.PreloadDownloadNotifbarSubtitle; + + CurrentGameProperty.GameInstall.ProgressChanged += PreloadDownloadProgress; + CurrentGameProperty.GameInstall.StatusChanged += PreloadDownloadStatus; + + int verifResult = 0; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + while (verifResult != 1) + { + await CurrentGameProperty.GameInstall.StartPackageDownload(true); + + PauseDownloadPreBtn.IsEnabled = false; + PreloadDialogBox.Title = Lang._HomePage.PreloadDownloadNotifbarVerifyTitle; + + verifResult = await CurrentGameProperty.GameInstall.StartPackageVerification(); + + // Restore sleep before the dialog + // so system won't be stuck when download is finished because of the download verified dialog + Sleep.RestoreSleep(); + + switch (verifResult) + { + case -1: + ReturnToHomePage(); + return; + case 1: + await Dialog_PreDownloadPackageVerified(this); + ReturnToHomePage(); + return; + } + } + + // Set the notification trigger to "Completed" state + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Completed); + + // If the current window is not in focus, then spawn the notification toast + if (WindowUtility.IsCurrentWindowInFocus()) + { + return; + } + + string gameNameLocale = LauncherMetadataHelper.GetTranslatedCurrentGameTitleRegionString(); + + WindowUtility.Tray_ShowNotification( + string.Format(Lang._NotificationToast.GamePreloadCompleted_Title, gameNameLocale), + Lang._NotificationToast.GenericClickNotifToGoBack_Subtitle + ); + } + catch (OperationCanceledException) + { + LogWriteLine("Pre-Download paused!", LogType.Warning); + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + } + catch (Exception ex) + { + LogWriteLine($"An error occurred while starting preload process: {ex}", LogType.Error, true); + ErrorSender.SendException(ex); + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + } + finally + { + IsSkippingUpdateCheck = false; + CurrentGameProperty.GameInstall.ProgressChanged -= PreloadDownloadProgress; + CurrentGameProperty.GameInstall.StatusChanged -= PreloadDownloadStatus; + CurrentGameProperty.GameInstall.Flush(); + + // Turn the sleep back on + Sleep.RestoreSleep(); + MainWindow.IsCriticalOpInProgress = false; + } + } + + private void PreloadDownloadStatus(object sender, TotalPerFileStatus e) + { + DispatcherQueue?.TryEnqueue(() => ProgressPrePerFileStatusFooter.Text = e.ActivityStatus); + } + + private void PreloadDownloadProgress(object sender, TotalPerFileProgress e) + { + DispatcherQueue?.TryEnqueue(() => + { + string installDownloadSpeedString = SummarizeSizeSimple(e.ProgressAllSpeed); + string installDownloadSizeString = SummarizeSizeSimple(e.ProgressAllSizeCurrent); + string installDownloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileSizeCurrent); + string downloadSizeString = SummarizeSizeSimple(e.ProgressAllSizeTotal); + string downloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileSizeTotal); + + ProgressPreStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, installDownloadSizeString, downloadSizeString); + ProgressPrePerFileStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, installDownloadPerSizeString, downloadPerSizeString); + ProgressPreStatusFooter.Text = string.Format(Lang._Misc.Speed, installDownloadSpeedString); + ProgressPreTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); + progressPreBar.Value = Math.Round(e.ProgressAllPercentage, 2); + progressPrePerFileBar.Value = Math.Round(e.ProgressPerFilePercentage, 2); + progressPreBar.IsIndeterminate = false; + progressPrePerFileBar.IsIndeterminate = false; + }); + } + #endregion + + #region Game Install + private async void InstallGameDialog(object sender, RoutedEventArgs e) + { + bool isUseSophon = CurrentGameProperty.GameInstall.IsUseSophon; + try + { + MainWindow.IsCriticalOpInProgress = true; + // Prevent device from sleep + Sleep.PreventSleep(ILoggerHelper.GetILogger()); + // Set the notification trigger to "Running" state + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Running); + + IsSkippingUpdateCheck = true; + + HideImageCarousel(true); + + progressRing.Value = 0; + progressRing.IsIndeterminate = true; + InstallGameBtn.Visibility = Visibility.Collapsed; + CancelDownloadBtn.Visibility = Visibility.Visible; + ProgressTimeLeft.Visibility = Visibility.Visible; + + if (isUseSophon) + { + SophonProgressStatusGrid.Visibility = Visibility.Visible; + SophonProgressStatusSizeDownloadedGrid.Visibility = Visibility.Collapsed; + CurrentGameProperty.GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged += GameInstallSophon_StatusChanged; + } + else + { + ProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty.GameInstall.ProgressChanged += GameInstall_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged += GameInstall_StatusChanged; + } + + int dialogResult = await CurrentGameProperty.GameInstall.GetInstallationPath(); + switch (dialogResult) + { + case < 0: + return; + case 0: + CurrentGameProperty.GameInstall.ApplyGameConfig(); + return; + } + + if (CurrentGameProperty.GameInstall.IsUseSophon) + { + DownloadModeLabel.Visibility = Visibility.Visible; + DownloadModeLabelText.Text = Lang._Misc.DownloadModeLabelSophon; + } + + int verifResult; + bool skipDialog = false; + while ((verifResult = await CurrentGameProperty.GameInstall.StartPackageVerification()) == 0) + { + await CurrentGameProperty.GameInstall.StartPackageDownload(skipDialog); + skipDialog = true; + } + + if (verifResult == -1) + { + CurrentGameProperty.GameInstall.ApplyGameConfig(true); + return; + } + + await CurrentGameProperty.GameInstall.StartPackageInstallation(); + CurrentGameProperty.GameInstall.ApplyGameConfig(true); + if (CurrentGameProperty.GameInstall.StartAfterInstall && + CurrentGameProperty.GameVersion.IsGameInstalled()) + StartGame(null, null); + + // Set the notification trigger to "Completed" state + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Completed); + + // If the current window is not in focus, then spawn the notification toast + if (WindowUtility.IsCurrentWindowInFocus()) + { + return; + } + + string gameNameLocale = LauncherMetadataHelper.GetTranslatedCurrentGameTitleRegionString(); + WindowUtility.Tray_ShowNotification( + string.Format(Lang._NotificationToast.GameInstallCompleted_Title, + gameNameLocale), + string + .Format(Lang._NotificationToast.GameInstallCompleted_Subtitle, + gameNameLocale) + ); + } + catch (TaskCanceledException) + { + LogWriteLine($"Installation cancelled for game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}"); + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + } + catch (OperationCanceledException) + { + LogWriteLine($"Installation cancelled for game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}"); + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + } + catch (NotSupportedException ex) + { + await SentryHelper.ExceptionHandlerAsync(ex); + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + + IsPageUnload = true; + LogWriteLine($"Error while installing game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}\r\n{ex}", + LogType.Error, true); + + await SpawnDialog(Lang._HomePage.InstallFolderRootTitle, + Lang._HomePage.InstallFolderRootSubtitle, + Content, + Lang._Misc.Close, + null, null, ContentDialogButton.Close, ContentDialogTheme.Error); + } + catch (NullReferenceException ex) + { + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + + IsPageUnload = true; + LogWriteLine($"Error while installing game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}\r\n{ex}", + LogType.Error, true); + ErrorSender.SendException(new + NullReferenceException("Collapse was not able to complete post-installation tasks, but your game has been successfully updated.\r\t" + + $"Please report this issue to our GitHub here: https://github.com/CollapseLauncher/Collapse/issues/new or come back to the launcher and make sure to use Repair Game in Game Settings button later.\r\nThrow: {ex}", + ex)); + } + catch (TimeoutException ex) + { + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + IsPageUnload = true; + string exMessage = $"Timeout occurred when trying to install {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}.\r\n\t" + + $"Check stability of your internet! If your internet speed is slow, please lower the download thread count.\r\n\t" + + $"**WARNING** Changing download thread count WILL reset your download from 0, and you have to delete the existing download chunks manually!" + + $"\r\n{ex}"; + + string exTitleLocalized = string.Format(Lang._HomePage.Exception_DownloadTimeout1, CurrentGameProperty.GameVersion.GamePreset.ZoneFullname); + string exMessageLocalized = string.Format($"{exTitleLocalized}\r\n\t" + + $"{Lang._HomePage.Exception_DownloadTimeout2}\r\n\t" + + $"{Lang._HomePage.Exception_DownloadTimeout3}"); + + LogWriteLine($"{exMessage}", LogType.Error, true); + Exception newEx = new TimeoutException(exMessageLocalized, ex); + ErrorSender.SendException(newEx); + } + catch (Exception ex) + { + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + + IsPageUnload = true; + LogWriteLine($"Error while installing game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}.\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); + } + finally + { + IsSkippingUpdateCheck = false; + CurrentGameProperty.GameInstall.StartAfterInstall = false; + + CurrentGameProperty.GameInstall.ProgressChanged -= isUseSophon ? + GameInstallSophon_ProgressChanged : + GameInstall_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged -= isUseSophon ? + GameInstallSophon_StatusChanged : + GameInstall_StatusChanged; + + await Task.Delay(200); + CurrentGameProperty.GameInstall.Flush(); + ReturnToHomePage(); + + // Turn the sleep back on + Sleep.RestoreSleep(); + MainWindow.IsCriticalOpInProgress = false; + } + } + + private void GameInstall_StatusChanged(object sender, TotalPerFileStatus e) + { + if (DispatcherQueue.HasThreadAccess) + GameInstall_StatusChanged_Inner(e); + else + DispatcherQueue?.TryEnqueue(() => GameInstall_StatusChanged_Inner(e)); + } + + private void GameInstall_StatusChanged_Inner(TotalPerFileStatus e) + { + ProgressStatusTitle.Text = e.ActivityStatus; + progressPerFile.Visibility = e.IsIncludePerFileIndicator ? Visibility.Visible : Visibility.Collapsed; + + progressRing.IsIndeterminate = e.IsProgressAllIndetermined; + progressRingPerFile.IsIndeterminate = e.IsProgressPerFileIndetermined; + } + + private void GameInstall_ProgressChanged(object sender, TotalPerFileProgress e) + { + if (DispatcherQueue.HasThreadAccess) + GameInstall_ProgressChanged_Inner(e); + else + DispatcherQueue?.TryEnqueue(() => GameInstall_ProgressChanged_Inner(e)); + } + + private void GameInstall_ProgressChanged_Inner(TotalPerFileProgress e) + { + progressRing.Value = e.ProgressAllPercentage; + progressRingPerFile.Value = e.ProgressPerFilePercentage; + ProgressStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressAllSizeCurrent), SummarizeSizeSimple(e.ProgressAllSizeTotal)); + ProgressStatusFooter.Text = string.Format(Lang._Misc.Speed, SummarizeSizeSimple(e.ProgressAllSpeed)); + ProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); + } + + private void GameInstallSophon_StatusChanged(object sender, TotalPerFileStatus e) + { + if (DispatcherQueue.HasThreadAccess) + GameInstallSophon_StatusChanged_Inner(e); + else + DispatcherQueue?.TryEnqueue(() => GameInstallSophon_StatusChanged_Inner(e)); + } + + private void GameInstallSophon_ProgressChanged(object sender, TotalPerFileProgress e) + { + if (DispatcherQueue.HasThreadAccess) + GameInstallSophon_ProgressChanged_Inner(e); + else + DispatcherQueue?.TryEnqueue(() => GameInstallSophon_ProgressChanged_Inner(e)); + } + + private void GameInstallSophon_StatusChanged_Inner(TotalPerFileStatus e) + { + SophonProgressStatusTitleText.Text = e.ActivityStatus; + SophonProgressPerFile.Visibility = e.IsIncludePerFileIndicator ? Visibility.Visible : Visibility.Collapsed; + + SophonProgressRing.IsIndeterminate = e.IsProgressAllIndetermined; + SophonProgressRingPerFile.IsIndeterminate = e.IsProgressPerFileIndetermined; + } + + private void GameInstallSophon_ProgressChanged_Inner(TotalPerFileProgress e) + { + SophonProgressRing.Value = e.ProgressAllPercentage; + SophonProgressRingPerFile.Value = e.ProgressPerFilePercentage; + + SophonProgressStatusSizeTotalText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressAllSizeCurrent), SummarizeSizeSimple(e.ProgressAllSizeTotal)); + SophonProgressStatusSizeDownloadedText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressPerFileSizeCurrent), SummarizeSizeSimple(e.ProgressPerFileSizeTotal)); + + SophonProgressStatusSpeedTotalText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(Math.Max(e.ProgressAllSpeed, 0))); + SophonProgressStatusSpeedDownloadedText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(Math.Max(e.ProgressPerFileSpeed, 0))); + + SophonProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); + } + + private void CancelInstallationProcedure(object sender, RoutedEventArgs e) + { + switch (GameInstallationState) + { + case GameInstallStateEnum.NeedsUpdate: + case GameInstallStateEnum.InstalledHavePlugin: + CancelUpdateDownload(); + break; + case GameInstallStateEnum.InstalledHavePreload: + CancelPreDownload(); + break; + case GameInstallStateEnum.NotInstalled: + case GameInstallStateEnum.GameBroken: + case GameInstallStateEnum.Installed: + CancelInstallationDownload(); + break; + } + } + #endregion + + #region Game Management Buttons + private void RepairGameButton_Click(object sender, RoutedEventArgs e) + { + m_mainPage!.InvokeMainPageNavigateByTag("repair"); + } + + private async void UninstallGameButton_Click(object sender, RoutedEventArgs e) + { + if (await CurrentGameProperty.GameInstall.UninstallGame()) + { + MainFrameChanger.ChangeMainFrame(typeof(HomePage)); + } + } + + private void ConvertVersionButton_Click(object sender, RoutedEventArgs e) + { + MainFrameChanger.ChangeWindowFrame(typeof(InstallationConvert)); + } + + private async void StopGameButton_Click(object sender, RoutedEventArgs e) + { + if (await Dialog_StopGame(this) != ContentDialogResult.Primary) return; + StopGame(CurrentGameProperty.GameVersion.GamePreset); + } + + private async void ChangeGameBGButton_Click(object sender, RoutedEventArgs e) + { + var file = await FileDialogNative.GetFilePicker(ImageLoaderHelper.SupportedImageFormats); + if (string.IsNullOrEmpty(file)) return; + + var currentMediaType = GetMediaType(file); + + if (currentMediaType == MediaType.StillImage) + { + FileStream croppedImage = await ImageLoaderHelper.LoadImage(file, true, true); + + if (croppedImage == null) return; + SetAlternativeFileStream(croppedImage); + } + + if (CurrentGameProperty?.GameSettings?.SettingsCollapseMisc != null) + { + CurrentGameProperty.GameSettings.SettingsCollapseMisc.CustomRegionBGPath = file; + CurrentGameProperty.GameSettings.SaveBaseSettings(); + } + _ = m_mainPage?.ChangeBackgroundImageAsRegionAsync(); + + BGPathDisplay.Text = Path.GetFileName(file); + } + + private async void MoveGameLocationButton_Click(object sender, RoutedEventArgs e) + { + try + { + if (!await CurrentGameProperty.GameInstall.MoveGameLocation()) + { + return; + } + + CurrentGameProperty.GameInstall.ApplyGameConfig(); + ReturnToHomePage(); + } + catch (NotSupportedException ex) + { + LogWriteLine($"Error has occurred while running Move Game Location tool!\r\n{ex}", LogType.Error, true); + ex = new NotSupportedException(Lang._HomePage.GameSettings_Panel2MoveGameLocationGame_SamePath, ex); + ErrorSender.SendException(ex, ErrorType.Warning); + } + catch (Exception ex) + { + LogWriteLine($"Error has occurred while running Move Game Location tool!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); + } + } + #endregion + + #region Game State + private async ValueTask GetCurrentGameState() + { + Visibility repairGameButtonVisible = CurrentGameProperty.GameVersion.GamePreset.IsRepairEnabled ?? false ? + Visibility.Visible : Visibility.Collapsed; + + if (!(CurrentGameProperty.GameVersion.GamePreset.IsConvertible ?? false) + || CurrentGameProperty.GameVersion.GameType != GameNameType.Honkai) + ConvertVersionButton.Visibility = Visibility.Collapsed; + + // Clear the _CommunityToolsProperty statics + PageStatics.CommunityToolsProperty?.Clear(); + + // Check if the _CommunityToolsProperty has the official tool list for current game type + if (PageStatics.CommunityToolsProperty? + .OfficialToolsDictionary? + .TryGetValue(CurrentGameProperty.GameVersion.GameType, + out List officialEntryList) ?? false) + { + // If yes, then iterate it and add it to the list, to then getting read by the + // DataTemplate from HomePage + for (int index = officialEntryList.Count - 1; index >= 0; index--) + { + CommunityToolsEntry iconProperty = officialEntryList[index]; + if (iconProperty.Profiles?.Contains(CurrentGameProperty.GamePreset.ProfileName) ?? false) + { + PageStatics.CommunityToolsProperty.OfficialToolsList?.Add(iconProperty); + } + } + } + + // Check if the _CommunityToolsProperty has the community tool list for current game type + if (PageStatics.CommunityToolsProperty? + .CommunityToolsDictionary? + .TryGetValue(CurrentGameProperty.GameVersion.GameType, + out List communityEntryList) ?? false) + { + // If yes, then iterate it and add it to the list, to then getting read by the + // DataTemplate from HomePage + for (int index = communityEntryList.Count - 1; index >= 0; index--) + { + CommunityToolsEntry iconProperty = communityEntryList[index]; + if (iconProperty.Profiles?.Contains(CurrentGameProperty.GamePreset.ProfileName) ?? false) + { + PageStatics.CommunityToolsProperty.CommunityToolsList?.Add(iconProperty); + } + } + } + + if (CurrentGameProperty.GameVersion.GameType == GameNameType.Genshin) OpenCacheFolderButton.Visibility = Visibility.Collapsed; + GameInstallationState = await CurrentGameProperty.GameVersion.GetGameState(); + + switch (GameInstallationState) + { + case GameInstallStateEnum.Installed: + { + RepairGameButton.Visibility = repairGameButtonVisible; + InstallGameBtn.Visibility = Visibility.Collapsed; + StartGameBtn.Visibility = Visibility.Visible; + CustomStartupArgs.Visibility = Visibility.Visible; + } + break; + case GameInstallStateEnum.InstalledHavePreload: + { + RepairGameButton.Visibility = repairGameButtonVisible; + CustomStartupArgs.Visibility = Visibility.Visible; + InstallGameBtn.Visibility = Visibility.Collapsed; + StartGameBtn.Visibility = Visibility.Visible; + //NeedShowEventIcon = false; + SpawnPreloadBox(); + } + break; + case GameInstallStateEnum.NeedsUpdate: + case GameInstallStateEnum.InstalledHavePlugin: + { + RepairGameButton.Visibility = repairGameButtonVisible; + RepairGameButton.IsEnabled = false; + CleanupFilesButton.IsEnabled = false; + UpdateGameBtn.Visibility = Visibility.Visible; + StartGameBtn.Visibility = Visibility.Collapsed; + InstallGameBtn.Visibility = Visibility.Collapsed; + } + break; + default: + { + UninstallGameButton.IsEnabled = false; + RepairGameButton.IsEnabled = false; + OpenGameFolderButton.IsEnabled = false; + CleanupFilesButton.IsEnabled = false; + OpenCacheFolderButton.IsEnabled = false; + ConvertVersionButton.IsEnabled = false; + CustomArgsTextBox.IsEnabled = false; + OpenScreenshotFolderButton.IsEnabled = false; + ConvertVersionButton.Visibility = Visibility.Collapsed; + RepairGameButton.Visibility = Visibility.Collapsed; + UninstallGameButton.Visibility = Visibility.Collapsed; + MoveGameLocationButton.Visibility = Visibility.Collapsed; + } + break; + } + + if (CurrentGameProperty.GameInstall.IsRunning) + RaiseBackgroundInstallationStatus(GameInstallationState); + } + + private void RaiseBackgroundInstallationStatus(GameInstallStateEnum gameInstallationState) + { + if (gameInstallationState != GameInstallStateEnum.NeedsUpdate + && gameInstallationState != GameInstallStateEnum.InstalledHavePlugin + && gameInstallationState != GameInstallStateEnum.GameBroken + && gameInstallationState != GameInstallStateEnum.NotInstalled) + { + return; + } + + HideImageCarousel(true); + + progressRing.Value = 0; + progressRing.IsIndeterminate = true; + + InstallGameBtn.Visibility = Visibility.Collapsed; + UpdateGameBtn.Visibility = Visibility.Collapsed; + CancelDownloadBtn.Visibility = Visibility.Visible; + ProgressTimeLeft.Visibility = Visibility.Visible; + + bool isUseSophon = CurrentGameProperty.GameInstall.IsUseSophon; + if (isUseSophon) + { + SophonProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty.GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged += GameInstallSophon_StatusChanged; + } + else + { + ProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty.GameInstall.ProgressChanged += GameInstall_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged += GameInstall_StatusChanged; + } + } + #endregion + + #region Game Update Dialog + private async void UpdateGameDialog(object sender, RoutedEventArgs e) + { + bool isUseSophon = CurrentGameProperty.GameInstall.IsUseSophon; + + HideImageCarousel(true); + + try + { + MainWindow.IsCriticalOpInProgress = true; + // Prevent device from sleep + Sleep.PreventSleep(ILoggerHelper.GetILogger()); + // Set the notification trigger to "Running" state + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Running); + + IsSkippingUpdateCheck = true; + + UpdateGameBtn.Visibility = Visibility.Collapsed; + CancelDownloadBtn.Visibility = Visibility.Visible; + + if (isUseSophon) + { + SophonProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty.GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged += GameInstallSophon_StatusChanged; + } + else + { + ProgressStatusGrid.Visibility = Visibility.Visible; + CurrentGameProperty.GameInstall.ProgressChanged += GameInstall_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged += GameInstall_StatusChanged; + } + + int verifResult; + bool skipDialog = false; + while ((verifResult = await CurrentGameProperty.GameInstall.StartPackageVerification()) == 0) + { + await CurrentGameProperty.GameInstall.StartPackageDownload(skipDialog); + skipDialog = true; + } + if (verifResult == -1) + { + return; + } + + await CurrentGameProperty.GameInstall.StartPackageInstallation(); + CurrentGameProperty.GameInstall.ApplyGameConfig(true); + if (CurrentGameProperty.GameInstall.StartAfterInstall && CurrentGameProperty.GameVersion.IsGameInstalled()) + StartGame(null, null); + + // Set the notification trigger to "Completed" state + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Completed); + + // If the current window is not in focus, then spawn the notification toast + if (WindowUtility.IsCurrentWindowInFocus()) + { + return; + } + + string gameNameLocale = LauncherMetadataHelper.GetTranslatedCurrentGameTitleRegionString(); + string gameVersionString = CurrentGameProperty.GameVersion.GetGameVersionApi()?.VersionString; + + WindowUtility.Tray_ShowNotification( + string.Format(Lang._NotificationToast.GameUpdateCompleted_Title, gameNameLocale), + string.Format(Lang._NotificationToast.GameUpdateCompleted_Subtitle, gameNameLocale, gameVersionString) + ); + } + catch (TaskCanceledException) + { + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + LogWriteLine("Update cancelled!", LogType.Warning); + } + catch (OperationCanceledException) + { + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + LogWriteLine("Update cancelled!", LogType.Warning); + } + catch (NullReferenceException ex) + { + await SentryHelper.ExceptionHandlerAsync(ex); + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + + IsPageUnload = true; + LogWriteLine($"Update error on {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname} game!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(new NullReferenceException("Oops, the launcher cannot finalize the installation but don't worry, your game has been totally updated.\r\t" + + $"Please report this issue to our GitHub here: https://github.com/CollapseLauncher/Collapse/issues/new or come back to the launcher and make sure to use Repair Game in Game Settings button later.\r\nThrow: {ex}", ex)); + } + catch (Exception ex) + { + // Set the notification trigger + CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + + IsPageUnload = true; + LogWriteLine($"Update error on {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname} game!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); + } + finally + { + IsSkippingUpdateCheck = false; + CurrentGameProperty.GameInstall.StartAfterInstall = false; + + CurrentGameProperty.GameInstall.ProgressChanged -= isUseSophon ? + GameInstallSophon_ProgressChanged : + GameInstall_ProgressChanged; + CurrentGameProperty.GameInstall.StatusChanged -= isUseSophon ? + GameInstallSophon_StatusChanged : + GameInstall_StatusChanged; + + await Task.Delay(200); + CurrentGameProperty.GameInstall.Flush(); + ReturnToHomePage(); + + // Turn the sleep back on + Sleep.RestoreSleep(); + MainWindow.IsCriticalOpInProgress = false; + } + } + + private async void ProgressSettingsButton_OnClick(object sender, RoutedEventArgs e) + => await Dialog_DownloadSettings(this, CurrentGameProperty); + #endregion + + #region Download Cancellation + private void CancelPreDownload() + { + CurrentGameProperty.GameInstall.CancelRoutine(); + + PauseDownloadPreBtn.Visibility = Visibility.Collapsed; + ResumeDownloadPreBtn.Visibility = Visibility.Visible; + ResumeDownloadPreBtn.IsEnabled = true; + } + + private void CancelUpdateDownload() + { + CurrentGameProperty.GameInstall.CancelRoutine(); + } + + private void CancelInstallationDownload() + { + CurrentGameProperty.GameInstall.CancelRoutine(); + } + #endregion +} \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Misc.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Misc.cs new file mode 100644 index 000000000..d41084e22 --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Misc.cs @@ -0,0 +1,303 @@ +using CollapseLauncher.CustomControls; +using CollapseLauncher.GameSettings.Genshin; +using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.ShortcutUtils; +using Hi3Helper; +using Hi3Helper.SentryHelper; +using Hi3Helper.Win32.FileDialogCOM; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.Win32; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using static CollapseLauncher.Dialogs.SimpleDialogs; +using static Hi3Helper.Data.ConverterTool; +using static Hi3Helper.Locale; +using static Hi3Helper.Logger; +using static Hi3Helper.Shared.Region.LauncherConfig; + +// ReSharper disable InconsistentNaming +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault +// ReSharper disable CheckNamespace + +namespace CollapseLauncher.Pages; + +public partial class HomePage +{ + private bool? _regionPlayingRpc; + private bool ToggleRegionPlayingRpc + { + get => _regionPlayingRpc ??= CurrentGameProperty.GameSettings?.AsIGameSettingsUniversal() + .SettingsCollapseMisc.IsPlayingRpc ?? false; + set + { + if (CurrentGameProperty.GameSettings == null) + return; + + CurrentGameProperty.GameSettings.AsIGameSettingsUniversal() + .SettingsCollapseMisc.IsPlayingRpc = value; + _regionPlayingRpc = value; + CurrentGameProperty.GameSettings.SaveSettings(); + } + } + + private async void CollapsePrioControl(Process proc) + { + try + { + using (Process collapseProcess = Process.GetCurrentProcess()) + { + collapseProcess.PriorityBoostEnabled = false; + collapseProcess.PriorityClass = ProcessPriorityClass.BelowNormal; + LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Below Normal, " + + $"PriorityBoost is off, carousel is temporarily stopped", LogType.Default, true); + } + + await CarouselStopScroll(); + await proc.WaitForExitAsync(); + + using (Process collapseProcess = Process.GetCurrentProcess()) + { + collapseProcess.PriorityBoostEnabled = true; + collapseProcess.PriorityClass = ProcessPriorityClass.Normal; + LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Normal, " + + $"PriorityBoost is on, carousel is started", LogType.Default, true); + } + + await CarouselRestartScroll(); + } + catch (Exception ex) + { + await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"Error in Collapse Priority Control module!\r\n{ex}", LogType.Error, true); + } + } + + private static void GenshinHDREnforcer() + { + WindowsHDR GenshinHDR = new WindowsHDR(); + try + { + WindowsHDR.Load(); + GenshinHDR.isHDR = true; + GenshinHDR.Save(); + LogWriteLine("Successfully forced Genshin HDR settings on!", LogType.Scheme, true); + } + catch (Exception ex) + { + SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); + LogWriteLine($"There was an error trying to force enable HDR on Genshin!\r\n{ex}", LogType.Error, true); + } + } + + private int GameBoostInvokeTryCount { get; set; } + private async Task GameBoost_Invoke(GamePresetProperty gameProp) + { + #nullable enable + string processName = gameProp.GameExecutableName; + int processId = -1; + try + { + while (!gameProp.TryGetGameProcessIdWithActiveWindow(out processId, out _)) + { + await Task.Delay(1000); // Waiting the process to be found + } + + LogWriteLine($"[HomePage::GameBoost_Invoke] Found target process! Waiting 10 seconds for process initialization...\r\n\t" + + $"Target Process : {processName} [{processId}]", LogType.Default, true); + + // Wait 20 (or 10 if it's not first try) seconds before applying + if (GameBoostInvokeTryCount == 0) + { + await Task.Delay(20000); + } + else + { + await Task.Delay(10000); + } + + // Check early exit + if (!gameProp.GetIsGameProcessRunning(processId)) + { + LogWriteLine($"[HomePage::GameBoost_Invoke] Game process {processName} [{processId}] has exited!", + LogType.Warning, true); + return; + } + + // Assign the priority to the process and write a log (just for displaying any info) + if (!gameProp.TrySetGameProcessPriority(processId, Hi3Helper.Win32.Native.Enums.PriorityClass.ABOVE_NORMAL_PRIORITY_CLASS)) + { + throw new Win32Exception(); + } + GameBoostInvokeTryCount = 0; + LogWriteLine($"[HomePage::GameBoost_Invoke] Game process {processName} " + + $"[{processId}] priority is boosted to above normal!", LogType.Warning, true); + } + catch (Exception ex) when (GameBoostInvokeTryCount < 5) + { + LogWriteLine($"[HomePage::GameBoost_Invoke] (Try #{GameBoostInvokeTryCount})" + + $"There has been error while boosting game priority to Above Normal! Retrying...\r\n" + + $"\tTarget Process : {processName} [{processId}]\r\n{ex}", + LogType.Error, true); + GameBoostInvokeTryCount++; + _ = Task.Run(async () => { await GameBoost_Invoke(gameProp); }); + } + catch (Exception ex) + { + LogWriteLine($"[HomePage::GameBoost_Invoke] There has been error while boosting game priority to Above Normal!\r\n" + + $"\tTarget Process : {processName} [{processId}]\r\n{ex}", + LogType.Error, true); + } + #nullable restore + } + + private async Task CheckUserAccountControlStatus() + { + try + { + var skipChecking = GetAppConfigValue("SkipCheckingUAC").ToBool(); + if (skipChecking) + return; + + var enabled = + (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", + "EnableLUA", 1)!; + if (enabled != 1) + { + var result = await SpawnDialog(Lang._Dialogs.UACWarningTitle, Lang._Dialogs.UACWarningContent, this, Lang._Misc.Close, + Lang._Dialogs.UACWarningLearnMore, Lang._Dialogs.UACWarningDontShowAgain, + ContentDialogButton.Close, ContentDialogTheme.Warning); + switch (result) + { + case ContentDialogResult.Primary: + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = "https://learn.microsoft.com/windows/security/application-security/application-control/user-account-control/settings-and-configuration?tabs=reg" + } + }.Start(); + break; + case ContentDialogResult.Secondary: + SetAndSaveConfigValue("SkipCheckingUAC", true); + break; + } + } + } + catch (Exception) + { + // ignore + } + } + + #region Media Pack + private async Task CheckMediaPackInstalled() + { + if (CurrentGameProperty.GameVersion.GameType != GameNameType.Honkai) return true; + + RegistryKey reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\WindowsFeatures\WindowsMediaVersion"); + if (reg != null) + return true; + + LogWriteLine($"Media pack is not installed!\r\n\t" + + $"If you encounter the 'cry_ware_unity' error, run this script as an administrator:\r\n\t" + + $"{Path.Combine(AppExecutableDir, "Misc", "InstallMediaPack.cmd")}", LogType.Warning, true); + + // Skip dialog if user asked before + if (GetAppConfigValue("HI3IgnoreMediaPack").ToBool()) + return true; + + switch (await Dialog_NeedInstallMediaPackage(Content)) + { + case ContentDialogResult.Primary: + TryInstallMediaPack(); + break; + case ContentDialogResult.Secondary: + SetAndSaveConfigValue("HI3IgnoreMediaPack", true); + return true; + } + return false; + } + + private async void TryInstallMediaPack() + { + try + { + Process proc = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = Path.Combine(AppExecutableDir, "Misc", "InstallMediaPack.cmd"), + UseShellExecute = true, + Verb = "runas" + } + }; + + ShowLoadingPage.ShowLoading(Lang._Dialogs.InstallingMediaPackTitle, + Lang._Dialogs.InstallingMediaPackSubtitle); + MainFrameChanger.ChangeMainFrame(typeof(BlankPage)); + proc.Start(); + await proc.WaitForExitAsync(); + ShowLoadingPage.ShowLoading(Lang._Dialogs.InstallingMediaPackTitle, + Lang._Dialogs.InstallingMediaPackSubtitleFinished); + await Dialog_InstallMediaPackageFinished(Content); + MainFrameChanger.ChangeWindowFrame(typeof(MainPage)); + } + catch + { + // ignore + } + } + #endregion + + #region Shortcut Creation + private async void AddToSteamButton_Click(object sender, RoutedEventArgs e) + { + GameStartupSettingFlyout.Hide(); + + Tuple result = await Dialog_SteamShortcutCreationConfirm(this); + + if (result.Item1 != ContentDialogResult.Primary) + return; + + if (await ShortcutCreator.AddToSteam(CurrentGameProperty.GamePreset, result.Item2)) + { + await Dialog_SteamShortcutCreationSuccess(this, result.Item2); + return; + } + + await Dialog_SteamShortcutCreationFailure(this); + } + + private async void ShortcutButton_Click(object sender, RoutedEventArgs e) + { + string folder = await FileDialogNative.GetFolderPicker(Lang._HomePage.CreateShortcut_FolderPicker); + + if (string.IsNullOrEmpty(folder)) + return; + + if (!IsUserHasPermission(folder)) + { + await Dialog_InsufficientWritePermission(sender as UIElement, folder); + return; + } + + Tuple result = await Dialog_ShortcutCreationConfirm(this, folder); + + if (result.Item1 != ContentDialogResult.Primary) + return; + + ShortcutCreator.CreateShortcut(folder, CurrentGameProperty.GamePreset, result.Item2); + await Dialog_ShortcutCreationSuccess(this, folder, result.Item2); + } + #endregion +} diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Playtime.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Playtime.cs new file mode 100644 index 000000000..aca43d7b9 --- /dev/null +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.Playtime.cs @@ -0,0 +1,152 @@ +using CollapseLauncher.GamePlaytime; +using Hi3Helper; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Animation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.UI.Xaml.Media; +using static CollapseLauncher.Dialogs.SimpleDialogs; +using static Hi3Helper.Locale; +using static Hi3Helper.Logger; + +// ReSharper disable InconsistentNaming +// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault +// ReSharper disable AsyncVoidMethod +// ReSharper disable StringLiteralTypo +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault +// ReSharper disable CheckNamespace + +namespace CollapseLauncher.Pages; + +public partial class HomePage +{ + private void ForceUpdatePlaytimeButton_Click(object sender, RoutedEventArgs e) + { + if (_cachedIsGameRunning) return; + + UpdatePlaytime(null, CurrentGameProperty.GamePlaytime.CollapsePlaytime); + PlaytimeFlyout.ShowAt(PlaytimeBtn); + } + + private async void ChangePlaytimeButton_Click(object sender, RoutedEventArgs e) + { + if (await Dialog_ChangePlaytime(this) != ContentDialogResult.Primary) return; + + int mins = int.Parse("0" + MinutePlaytimeTextBox.Text); + int hours = int.Parse("0" + HourPlaytimeTextBox.Text); + + TimeSpan time = TimeSpan.FromMinutes(hours * 60 + mins); + if (time.Hours > 99999) time = new TimeSpan(99999, 59, 0); + + CurrentGameProperty.GamePlaytime.Update(time, true); + PlaytimeFlyout.Hide(); + } + + private async void ResetPlaytimeButton_Click(object sender, RoutedEventArgs e) + { + if (await Dialog_ResetPlaytime(this) != ContentDialogResult.Primary) return; + + CurrentGameProperty.GamePlaytime.Reset(); + PlaytimeFlyout.Hide(); + } + + private async void SyncDbPlaytimeButton_Click(object sender, RoutedEventArgs e) + { + Button button = sender as Button; + if (sender != null) + if (button != null) + button.IsEnabled = false; + + try + { + SyncDbPlaytimeBtnGlyph.Glyph = "\uf110"; // Loading + SyncDbPlaytimeBtnText.Text = Lang._HomePage.GamePlaytime_Idle_SyncDbSyncing; + await CurrentGameProperty.GamePlaytime.CheckDb(true); + + await Task.Delay(500); + + SyncDbPlaytimeBtnGlyph.Glyph = "\uf00c"; // Completed (check) + SyncDbPlaytimeBtnText.Text = Lang._Misc.Completed + "!"; + await Task.Delay(1000); + + SyncDbPlaytimeBtnGlyph.Glyph = "\uf021"; // Default + SyncDbPlaytimeBtnText.Text = Lang._HomePage.GamePlaytime_Idle_SyncDb; + } + catch (Exception ex) + { + LogWriteLine($"Failed when trying to sync playtime to database!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); + + SyncDbPlaytimeBtnGlyph.Glyph = "\uf021"; // Default + SyncDbPlaytimeBtnText.Text = Lang._HomePage.GamePlaytime_Idle_SyncDb; + } + finally + { + if (sender != null) + if (button != null) + button.IsEnabled = true; + } + } + + private void NumberValidationTextBox(TextBox sender, TextBoxBeforeTextChangingEventArgs args) + { + sender.MaxLength = sender == HourPlaytimeTextBox ? 5 : 3; + args.Cancel = args.NewText.Any(c => !char.IsDigit(c)); + } + + private void UpdatePlaytime(object sender, CollapsePlaytime playtime) + { + DispatcherQueue.TryEnqueue(() => + { + PlaytimeMainBtn.Text = FormatTimeStamp(playtime.TotalPlaytime); + HourPlaytimeTextBox.Text = (playtime.TotalPlaytime.Days * 24 + playtime.TotalPlaytime.Hours).ToString(); + MinutePlaytimeTextBox.Text = playtime.TotalPlaytime.Minutes.ToString(); + + string lastPlayed = Lang._HomePage.GamePlaytime_Stats_NeverPlayed; + if (playtime.LastPlayed != null) + { + DateTime? last = playtime.LastPlayed?.ToLocalTime(); + lastPlayed = string.Format(Lang._HomePage.GamePlaytime_DateDisplay, last?.Day, + last?.Month, last?.Year, last?.Hour, last?.Minute); + } + + PlaytimeStatsDaily.Text = FormatTimeStamp(playtime.DailyPlaytime); + PlaytimeStatsWeekly.Text = FormatTimeStamp(playtime.WeeklyPlaytime); + PlaytimeStatsMonthly.Text = FormatTimeStamp(playtime.MonthlyPlaytime); + PlaytimeStatsLastSession.Text = FormatTimeStamp(playtime.LastSession); + PlaytimeStatsLastPlayed.Text = lastPlayed; + }); + return; + + static string FormatTimeStamp(TimeSpan time) => string.Format(Lang._HomePage.GamePlaytime_Display, time.Days * 24 + time.Hours, time.Minutes); + } + + private void ShowPlaytimeStatsFlyout(object sender, RoutedEventArgs e) + { + FlyoutBase.ShowAttachedFlyout(PlaytimeBtn); + } + + private void HidePlaytimeStatsFlyout(object sender, PointerRoutedEventArgs e) + { + PlaytimeStatsFlyout.Hide(); + PlaytimeStatsToolTip.IsOpen = false; + } + + private void PlaytimeStatsFlyout_OnOpened(object sender, object e) + { + // Match PlaytimeStatsFlyout and set its transition animation offset to 0 (but keep animation itself) + IReadOnlyList popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(PlaytimeBtn.XamlRoot); + foreach (var popup in popups.Where(x => x.Child is FlyoutPresenter {Content: Grid {Tag: "PlaytimeStatsFlyoutGrid"}})) + { + var transition = popup.ChildTransitions[0] as PopupThemeTransition; + transition!.FromVerticalOffset = 0; + } + } +} \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs index b149bb8cb..b5e7d6b20 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml.cs @@ -3,57 +3,40 @@ #endif using CollapseLauncher.CustomControls; using CollapseLauncher.Helper.LauncherApiLoader.Legacy; -using CollapseLauncher.Dialogs; using CollapseLauncher.Extension; -using CollapseLauncher.GamePlaytime; -using CollapseLauncher.GameSettings.Genshin; using CollapseLauncher.Helper; using CollapseLauncher.Helper.Animation; using CollapseLauncher.Helper.Database; using CollapseLauncher.Helper.Image; using CollapseLauncher.Helper.Metadata; -using CollapseLauncher.InstallManager.Base; -using CollapseLauncher.Interfaces; -using CollapseLauncher.ShortcutUtils; using CollapseLauncher.Statics; using CommunityToolkit.WinUI; using CommunityToolkit.WinUI.Animations; -using H.NotifyIcon; using Hi3Helper; -using Hi3Helper.EncTool.WindowTool; using Hi3Helper.SentryHelper; using Hi3Helper.Shared.ClassStruct; using Hi3Helper.Win32.FileDialogCOM; -using Hi3Helper.Win32.Native.ManagedTools; -using Hi3Helper.Win32.Screen; using Microsoft.UI.Composition; using Microsoft.UI.Input; -using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Imaging; -using Microsoft.Win32; using PhotoSauce.MagicScaler; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.IO.Hashing; using System.Linq; using System.Numerics; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; using System.Threading.Tasks; using Microsoft.UI.Xaml.Media; using static CollapseLauncher.Dialogs.SimpleDialogs; using static CollapseLauncher.InnerLauncherConfig; -using static CollapseLauncher.Helper.Background.BackgroundMediaUtility; using static Hi3Helper.Data.ConverterTool; using static Hi3Helper.Locale; using static Hi3Helper.Logger; @@ -61,7 +44,6 @@ using Brush = Microsoft.UI.Xaml.Media.Brush; using Image = Microsoft.UI.Xaml.Controls.Image; using Point = Windows.Foundation.Point; -using Size = System.Drawing.Size; using UIElementExtensions = CollapseLauncher.Extension.UIElementExtensions; // ReSharper disable InconsistentNaming // ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault @@ -72,6 +54,19 @@ // ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault // ReSharper disable CheckNamespace +// HomePage class is pretty big, so it's split into multiple files. +// This file contains the main class definition and the PageMethod region and also anything related to XAML and its styling +// You can split other methods that is not directly related to XAML into other files +// Currently separated files: +// - HomePage.Playtime.cs +// Contains method related to playtime tracking UI and its handler +// - HomePage.GameLauncher.cs +// Contains method related to game launching and its surrounding like arguments, GLC, etc. +// - HomePage.GameManagement.cs +// Contains method related to game management like installation, update, etc. +// - HomePage.Misc.cs +// Contains miscelanous method that doesn't fit into other categories and is not big enough to be in its own file + namespace CollapseLauncher.Pages { public sealed partial class HomePage @@ -924,2254 +919,197 @@ private async Task TagPropertyAction_OpenExternalApp(string propertiesStri } #endregion - #region Game State - private async ValueTask GetCurrentGameState() + #region Community Button + private void OpenCommunityButtonLink(object sender, RoutedEventArgs e) { - Visibility repairGameButtonVisible = CurrentGameProperty.GameVersion.GamePreset.IsRepairEnabled ?? false ? - Visibility.Visible : Visibility.Collapsed; - - if (!(CurrentGameProperty.GameVersion.GamePreset.IsConvertible ?? false) - || CurrentGameProperty.GameVersion.GameType != GameNameType.Honkai) - ConvertVersionButton.Visibility = Visibility.Collapsed; - - // Clear the _CommunityToolsProperty statics - PageStatics.CommunityToolsProperty?.Clear(); - - // Check if the _CommunityToolsProperty has the official tool list for current game type - if (PageStatics.CommunityToolsProperty? - .OfficialToolsDictionary? - .TryGetValue(CurrentGameProperty.GameVersion.GameType, - out List officialEntryList) ?? false) - { - // If yes, then iterate it and add it to the list, to then getting read by the - // DataTemplate from HomePage - for (int index = officialEntryList.Count - 1; index >= 0; index--) - { - CommunityToolsEntry iconProperty = officialEntryList[index]; - if (iconProperty.Profiles?.Contains(CurrentGameProperty.GamePreset.ProfileName) ?? false) - { - PageStatics.CommunityToolsProperty.OfficialToolsList?.Add(iconProperty); - } - } - } - - // Check if the _CommunityToolsProperty has the community tool list for current game type - if (PageStatics.CommunityToolsProperty? - .CommunityToolsDictionary? - .TryGetValue(CurrentGameProperty.GameVersion.GameType, - out List communityEntryList) ?? false) - { - // If yes, then iterate it and add it to the list, to then getting read by the - // DataTemplate from HomePage - for (int index = communityEntryList.Count - 1; index >= 0; index--) - { - CommunityToolsEntry iconProperty = communityEntryList[index]; - if (iconProperty.Profiles?.Contains(CurrentGameProperty.GamePreset.ProfileName) ?? false) - { - PageStatics.CommunityToolsProperty.CommunityToolsList?.Add(iconProperty); - } - } - } - - if (CurrentGameProperty.GameVersion.GameType == GameNameType.Genshin) OpenCacheFolderButton.Visibility = Visibility.Collapsed; - GameInstallationState = await CurrentGameProperty.GameVersion.GetGameState(); - - switch (GameInstallationState) - { - case GameInstallStateEnum.Installed: - { - RepairGameButton.Visibility = repairGameButtonVisible; - InstallGameBtn.Visibility = Visibility.Collapsed; - StartGameBtn.Visibility = Visibility.Visible; - CustomStartupArgs.Visibility = Visibility.Visible; - } - break; - case GameInstallStateEnum.InstalledHavePreload: - { - RepairGameButton.Visibility = repairGameButtonVisible; - CustomStartupArgs.Visibility = Visibility.Visible; - InstallGameBtn.Visibility = Visibility.Collapsed; - StartGameBtn.Visibility = Visibility.Visible; - //NeedShowEventIcon = false; - SpawnPreloadBox(); - } - break; - case GameInstallStateEnum.NeedsUpdate: - case GameInstallStateEnum.InstalledHavePlugin: - { - RepairGameButton.Visibility = repairGameButtonVisible; - RepairGameButton.IsEnabled = false; - CleanupFilesButton.IsEnabled = false; - UpdateGameBtn.Visibility = Visibility.Visible; - StartGameBtn.Visibility = Visibility.Collapsed; - InstallGameBtn.Visibility = Visibility.Collapsed; - } - break; - default: - { - UninstallGameButton.IsEnabled = false; - RepairGameButton.IsEnabled = false; - OpenGameFolderButton.IsEnabled = false; - CleanupFilesButton.IsEnabled = false; - OpenCacheFolderButton.IsEnabled = false; - ConvertVersionButton.IsEnabled = false; - CustomArgsTextBox.IsEnabled = false; - OpenScreenshotFolderButton.IsEnabled = false; - ConvertVersionButton.Visibility = Visibility.Collapsed; - RepairGameButton.Visibility = Visibility.Collapsed; - UninstallGameButton.Visibility = Visibility.Collapsed; - MoveGameLocationButton.Visibility = Visibility.Collapsed; - } - break; - } - - if (CurrentGameProperty.GameInstall.IsRunning) - RaiseBackgroundInstallationStatus(GameInstallationState); + CommunityToolsBtn.Flyout.Hide(); + OpenButtonLinkFromTag(sender, e); } + #endregion + + #region Open Button Method - private void RaiseBackgroundInstallationStatus(GameInstallStateEnum gameInstallationState) + private readonly string ExplorerPath = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"); + + private async void OpenGameFolderButton_Click(object sender, RoutedEventArgs e) { - if (gameInstallationState != GameInstallStateEnum.NeedsUpdate - && gameInstallationState != GameInstallStateEnum.InstalledHavePlugin - && gameInstallationState != GameInstallStateEnum.GameBroken - && gameInstallationState != GameInstallStateEnum.NotInstalled) + try { - return; - } - - HideImageCarousel(true); - - progressRing.Value = 0; - progressRing.IsIndeterminate = true; - - InstallGameBtn.Visibility = Visibility.Collapsed; - UpdateGameBtn.Visibility = Visibility.Collapsed; - CancelDownloadBtn.Visibility = Visibility.Visible; - ProgressTimeLeft.Visibility = Visibility.Visible; + string gameFolder = NormalizePath(GameDirPath); + LogWriteLine($"Opening Game Folder:\r\n\t{gameFolder}"); - bool isUseSophon = CurrentGameProperty.GameInstall.IsUseSophon; - if (isUseSophon) - { - SophonProgressStatusGrid.Visibility = Visibility.Visible; - CurrentGameProperty.GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged += GameInstallSophon_StatusChanged; + await Task.Run(() => + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = ExplorerPath, + Arguments = gameFolder + } + }.Start()); } - else + catch (Exception ex) { - ProgressStatusGrid.Visibility = Visibility.Visible; - CurrentGameProperty.GameInstall.ProgressChanged += GameInstall_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged += GameInstall_StatusChanged; + LogWriteLine($"Failed when trying to open game folder!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); } } - private async void CheckRunningGameInstance(CancellationToken token) + private async void OpenCacheFolderButton_Click(object sender, RoutedEventArgs e) { - TextBlock startGameBtnText = (StartGameBtn.Content as Grid)!.Children.OfType().FirstOrDefault(); - FontIcon startGameBtnIcon = (StartGameBtn.Content as Grid)!.Children.OfType().FirstOrDefault(); - Grid startGameBtnAnimatedIconGrid = (StartGameBtn.Content as Grid)!.Children.OfType().FirstOrDefault(); - // AnimatedVisualPlayer StartGameBtnAnimatedIcon = StartGameBtnAnimatedIconGrid!.Children.OfType().FirstOrDefault(); - string startGameBtnIconGlyph = startGameBtnIcon!.Glyph; - const string startGameBtnRunningIconGlyph = ""; - - startGameBtnIcon.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); - startGameBtnAnimatedIconGrid.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); - + string cacheFolder = CurrentGameProperty.GameVersion.GameDirAppDataPath; + LogWriteLine($"Opening Game Folder:\r\n\t{cacheFolder}"); try { - while (CurrentGameProperty.IsGameRunning) - { - _cachedIsGameRunning = true; - - StartGameBtn.IsEnabled = false; - if (startGameBtnText != null && startGameBtnAnimatedIconGrid != null) + await Task.Run(() => + new Process { - startGameBtnText.Text = Lang._HomePage.StartBtnRunning; - startGameBtnIcon.Glyph = startGameBtnRunningIconGlyph; - startGameBtnAnimatedIconGrid.Opacity = 0; - startGameBtnIcon.Opacity = 1; - - startGameBtnText.UpdateLayout(); - - RepairGameButton.IsEnabled = false; - UninstallGameButton.IsEnabled = false; - ConvertVersionButton.IsEnabled = false; - CustomArgsTextBox.IsEnabled = false; - MoveGameLocationButton.IsEnabled = false; - StopGameButton.IsEnabled = true; - - PlaytimeIdleStack.Visibility = Visibility.Collapsed; - PlaytimeRunningStack.Visibility = Visibility.Visible; - - if (CurrentGameProperty.TryGetGameProcessIdWithActiveWindow(out var processId, out _)) + StartInfo = new ProcessStartInfo { - using Process currentGameProcess = Process.GetProcessById(processId); - - // HACK: For some reason, the text still unchanged. - // Make sure the start game button text also changed. - startGameBtnText.Text = Lang._HomePage.StartBtnRunning; - var fromActivityOffset = currentGameProcess.StartTime; - var gameSettings = CurrentGameProperty!.GameSettings!.AsIGameSettingsUniversal(); - var gamePreset = CurrentGameProperty.GamePreset; - -#if !DISABLEDISCORD - if (ToggleRegionPlayingRpc) - AppDiscordPresence?.SetActivity(ActivityType.Play, fromActivityOffset.ToUniversalTime()); -#endif - - CurrentGameProperty!.GamePlaytime!.StartSession(currentGameProcess); - - int? height = gameSettings.SettingsScreen.height; - int? width = gameSettings.SettingsScreen.width; - - // Start the resizable window payload - StartResizableWindowPayload(gamePreset.GameExecutableName, - gameSettings, - gamePreset.GameType, height, width); - - await currentGameProcess.WaitForExitAsync(token); + UseShellExecute = true, + FileName = ExplorerPath, + Arguments = cacheFolder } - } - - await Task.Delay(RefreshRate, token); - } - - _cachedIsGameRunning = false; - - StartGameBtn.IsEnabled = true; - startGameBtnText!.Text = Lang._HomePage.StartBtn; - startGameBtnIcon.Glyph = startGameBtnIconGlyph; - if (startGameBtnAnimatedIconGrid != null) - { - startGameBtnAnimatedIconGrid.Opacity = 1; - } - - startGameBtnIcon.Opacity = 0; - - GameStartupSetting.IsEnabled = true; - RepairGameButton.IsEnabled = true; - MoveGameLocationButton.IsEnabled = true; - UninstallGameButton.IsEnabled = true; - ConvertVersionButton.IsEnabled = true; - CustomArgsTextBox.IsEnabled = true; - StopGameButton.IsEnabled = false; - - PlaytimeIdleStack.Visibility = Visibility.Visible; - PlaytimeRunningStack.Visibility = Visibility.Collapsed; - - #if !DISABLEDISCORD - AppDiscordPresence?.SetActivity(ActivityType.Idle); - #endif - } - catch (TaskCanceledException) - { - // Ignore - LogWriteLine("Game run watcher has been terminated!"); + }.Start()); } catch (Exception ex) { - await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"Error when checking if game is running!\r\n{ex}", LogType.Error, true); + LogWriteLine($"Failed when trying to open game cache folder!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); } } - #endregion - - #region Community Button - private void OpenCommunityButtonLink(object sender, RoutedEventArgs e) - { - CommunityToolsBtn.Flyout.Hide(); - OpenButtonLinkFromTag(sender, e); - } - #endregion - #region Preload - private async void SpawnPreloadBox() + private async void OpenScreenshotFolderButton_Click(object sender, RoutedEventArgs e) { - if (CurrentGameProperty.GameInstall.IsUseSophon) - { - DownloadModeLabelPreload.Visibility = Visibility.Visible; - DownloadModeLabelPreloadText.Text = Lang._Misc.DownloadModeLabelSophon; - } - - if (CurrentGameProperty.GameInstall.IsRunning) + string ScreenshotFolder = Path.Combine(NormalizePath(GameDirPath), CurrentGameProperty.GameVersion.GamePreset.GameType switch { - // TODO - PauseDownloadPreBtn.Visibility = Visibility.Visible; - ResumeDownloadPreBtn.Visibility = Visibility.Collapsed; - PreloadDialogBox.IsClosable = false; - - IsSkippingUpdateCheck = true; - DownloadPreBtn.Visibility = Visibility.Collapsed; - if (CurrentGameProperty.GameInstall.IsUseSophon) - { - ProgressPreSophonStatusGrid.Visibility = Visibility.Visible; - ProgressPreStatusGrid.Visibility = Visibility.Collapsed; - } - else - { - ProgressPreStatusGrid.Visibility = Visibility.Visible; - } - ProgressPreButtonGrid.Visibility = Visibility.Visible; - PreloadDialogBox.Title = Lang._HomePage.PreloadDownloadNotifbarTitle; - PreloadDialogBox.Message = Lang._HomePage.PreloadDownloadNotifbarSubtitle; + GameNameType.StarRail => $"{Path.GetFileNameWithoutExtension(CurrentGameProperty.GameVersion.GamePreset.GameExecutableName)}_Data\\ScreenShots", + _ => "ScreenShot" + }); - CurrentGameProperty.GameInstall.ProgressChanged += PreloadDownloadProgress; - CurrentGameProperty.GameInstall.StatusChanged += PreloadDownloadStatus; - SpawnPreloadDialogBox(); - return; - } + LogWriteLine($"Opening Screenshot Folder:\r\n\t{ScreenshotFolder}"); - string ver = CurrentGameProperty.GameVersion.GetGameVersionApiPreload()?.VersionString; + if (!Directory.Exists(ScreenshotFolder)) + Directory.CreateDirectory(ScreenshotFolder); try { - if (CurrentGameProperty.GameVersion.IsGameHasDeltaPatch()) - { - PreloadDialogBox.Title = string.Format(Lang._HomePage.PreloadNotifDeltaDetectTitle, ver); - PreloadDialogBox.Message = Lang._HomePage.PreloadNotifDeltaDetectSubtitle; - DownloadPreBtn.Visibility = Visibility.Collapsed; - SpawnPreloadDialogBox(); - return; - } - - if (!await CurrentGameProperty.GameInstall.IsPreloadCompleted(PageToken.Token)) - { - PreloadDialogBox.Title = string.Format(Lang._HomePage.PreloadNotifTitle, ver); - } - else - { - PreloadDialogBox.Title = Lang._HomePage.PreloadNotifCompleteTitle; - PreloadDialogBox.Message = string.Format(Lang._HomePage.PreloadNotifCompleteSubtitle, ver); - PreloadDialogBox.IsClosable = true; - DownloadPreBtn.Content = UIElementExtensions.CreateIconTextGrid( - text: Lang._HomePage.PreloadNotifIntegrityCheckBtn, - iconGlyph: "", - iconFontFamily: "FontAwesomeSolid", - textWeight: FontWeights.Medium - ); - } - SpawnPreloadDialogBox(); + await Task.Run(() => + new Process + { + StartInfo = new ProcessStartInfo + { + UseShellExecute = true, + FileName = ExplorerPath, + Arguments = ScreenshotFolder + } + }.Start()); } catch (Exception ex) { - await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"An error occured while trying to determine delta-patch availability\r\n{ex}", LogType.Error, true); + LogWriteLine($"Failed when trying to open game screenshot folder!\r\n{ex}", LogType.Error, true); + ErrorSender.SendException(ex); } } - private async void PredownloadDialog(object sender, RoutedEventArgs e) + private async void CleanupFilesButton_Click(object sender, RoutedEventArgs e) { - ((Button)sender).IsEnabled = false; - - PauseDownloadPreBtn.Visibility = Visibility.Visible; - ResumeDownloadPreBtn.Visibility = Visibility.Collapsed; - PreloadDialogBox.IsClosable = false; - try { - MainWindow.IsCriticalOpInProgress = true; - // Prevent device from sleep - Sleep.PreventSleep(ILoggerHelper.GetILogger()); - // Set the notification trigger to "Running" state - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Running); - - IsSkippingUpdateCheck = true; - DownloadPreBtn.Visibility = Visibility.Collapsed; - if (CurrentGameProperty.GameInstall.IsUseSophon) - { - ProgressPreSophonStatusGrid.Visibility = Visibility.Visible; - ProgressPreStatusGrid.Visibility = Visibility.Collapsed; - } - else - { - ProgressPreStatusGrid.Visibility = Visibility.Visible; - } - ProgressPreButtonGrid.Visibility = Visibility.Visible; - PreloadDialogBox.Title = Lang._HomePage.PreloadDownloadNotifbarTitle; - PreloadDialogBox.Message = Lang._HomePage.PreloadDownloadNotifbarSubtitle; - - CurrentGameProperty.GameInstall.ProgressChanged += PreloadDownloadProgress; - CurrentGameProperty.GameInstall.StatusChanged += PreloadDownloadStatus; - - int verifResult = 0; - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - while (verifResult != 1) - { - await CurrentGameProperty.GameInstall.StartPackageDownload(true); - - PauseDownloadPreBtn.IsEnabled = false; - PreloadDialogBox.Title = Lang._HomePage.PreloadDownloadNotifbarVerifyTitle; - - verifResult = await CurrentGameProperty.GameInstall.StartPackageVerification(); - - // Restore sleep before the dialog - // so system won't be stuck when download is finished because of the download verified dialog - Sleep.RestoreSleep(); - - switch (verifResult) - { - case -1: - ReturnToHomePage(); - return; - case 1: - await Dialog_PreDownloadPackageVerified(this); - ReturnToHomePage(); - return; - } - } - - // Set the notification trigger to "Completed" state - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Completed); - - // If the current window is not in focus, then spawn the notification toast - if (WindowUtility.IsCurrentWindowInFocus()) - { - return; - } - - string gameNameLocale = LauncherMetadataHelper.GetTranslatedCurrentGameTitleRegionString(); - - WindowUtility.Tray_ShowNotification( - string.Format(Lang._NotificationToast.GamePreloadCompleted_Title, gameNameLocale), - Lang._NotificationToast.GenericClickNotifToGoBack_Subtitle - ); - } - catch (OperationCanceledException) - { - LogWriteLine("Pre-Download paused!", LogType.Warning); - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); + GameStartupSetting.Flyout.Hide(); + if (CurrentGameProperty is not null) + await CurrentGameProperty.GameInstall.CleanUpGameFiles(); } catch (Exception ex) { - LogWriteLine($"An error occurred while starting preload process: {ex}", LogType.Error, true); ErrorSender.SendException(ex); - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - } - finally - { - IsSkippingUpdateCheck = false; - CurrentGameProperty.GameInstall.ProgressChanged -= PreloadDownloadProgress; - CurrentGameProperty.GameInstall.StatusChanged -= PreloadDownloadStatus; - CurrentGameProperty.GameInstall.Flush(); - - // Turn the sleep back on - Sleep.RestoreSleep(); - MainWindow.IsCriticalOpInProgress = false; } } + #endregion + #region Set Hand Cursor + private void SetHandCursor(object sender, RoutedEventArgs e = null) => + (sender as UIElement)?.SetCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand)); + #endregion - private void PreloadDownloadStatus(object sender, TotalPerFileStatus e) + #region Hyper Link Color + private void HyperLink_OnPointerEntered(object sender, PointerRoutedEventArgs e) { - DispatcherQueue?.TryEnqueue(() => ProgressPrePerFileStatusFooter.Text = e.ActivityStatus); + TextBlock textBlock = null; + switch (sender) + { + case Grid grid when grid.Children[0] is TextBlock: + textBlock = (TextBlock)grid.Children[0]; + break; + case Grid grid when grid.Children[0] is CompressedTextBlock compressedTextBlock: + compressedTextBlock.Foreground = (Brush)Application.Current.Resources["AccentColor"]; + return; + case TextBlock block: + textBlock = block; + break; + } + if (textBlock != null) + textBlock.Foreground = UIElementExtensions.GetApplicationResource("AccentColor"); } - private void PreloadDownloadProgress(object sender, TotalPerFileProgress e) + private void HyperLink_OnPointerExited(object sender, PointerRoutedEventArgs e) { - DispatcherQueue?.TryEnqueue(() => + TextBlock textBlock = null; + switch (sender) { - string installDownloadSpeedString = SummarizeSizeSimple(e.ProgressAllSpeed); - string installDownloadSizeString = SummarizeSizeSimple(e.ProgressAllSizeCurrent); - string installDownloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileSizeCurrent); - string downloadSizeString = SummarizeSizeSimple(e.ProgressAllSizeTotal); - string downloadPerSizeString = SummarizeSizeSimple(e.ProgressPerFileSizeTotal); - - ProgressPreStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, installDownloadSizeString, downloadSizeString); - ProgressPrePerFileStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, installDownloadPerSizeString, downloadPerSizeString); - ProgressPreStatusFooter.Text = string.Format(Lang._Misc.Speed, installDownloadSpeedString); - ProgressPreTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); - progressPreBar.Value = Math.Round(e.ProgressAllPercentage, 2); - progressPrePerFileBar.Value = Math.Round(e.ProgressPerFilePercentage, 2); - progressPreBar.IsIndeterminate = false; - progressPrePerFileBar.IsIndeterminate = false; - }); + case Grid grid when grid.Children[0] is TextBlock: + textBlock = (TextBlock)grid.Children[0]; + break; + case Grid grid when grid.Children[0] is CompressedTextBlock compressedTextBlock: + compressedTextBlock.Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]; + return; + case TextBlock block: + textBlock = block; + break; + } + if (textBlock != null) + textBlock.Foreground = UIElementExtensions.GetApplicationResource("TextFillColorPrimaryBrush"); } #endregion + + #region Side Panel + private bool IsPointerInsideSidePanel; + private bool IsSidePanelCurrentlyScaledOut; - #region Game Install - private async void InstallGameDialog(object sender, RoutedEventArgs e) + private async void SidePanelScaleOutHoveredPointerEntered(object sender, PointerRoutedEventArgs e) { - bool isUseSophon = CurrentGameProperty.GameInstall.IsUseSophon; - try + if (!IsEventsPanelScaleUp) return; + + IsPointerInsideSidePanel = true; + if (sender is not FrameworkElement elementPanel) { - MainWindow.IsCriticalOpInProgress = true; - // Prevent device from sleep - Sleep.PreventSleep(ILoggerHelper.GetILogger()); - // Set the notification trigger to "Running" state - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Running); + return; + } - IsSkippingUpdateCheck = true; + await Task.Delay(TimeSpan.FromSeconds(0.2)); + if (IsSidePanelCurrentlyScaledOut) return; + if (!IsPointerInsideSidePanel) return; - HideImageCarousel(true); + var toScale = WindowSize.WindowSize.CurrentWindowSize.PostEventPanelScaleFactor; + var storyboard = new Storyboard(); + var transform = (CompositeTransform)elementPanel.RenderTransform; + transform.CenterY = elementPanel.ActualHeight + 8; + var cubicEaseOut = new CubicEase + { + EasingMode = EasingMode.EaseOut + }; - progressRing.Value = 0; - progressRing.IsIndeterminate = true; - InstallGameBtn.Visibility = Visibility.Collapsed; - CancelDownloadBtn.Visibility = Visibility.Visible; - ProgressTimeLeft.Visibility = Visibility.Visible; - - if (isUseSophon) - { - SophonProgressStatusGrid.Visibility = Visibility.Visible; - SophonProgressStatusSizeDownloadedGrid.Visibility = Visibility.Collapsed; - CurrentGameProperty.GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged += GameInstallSophon_StatusChanged; - } - else - { - ProgressStatusGrid.Visibility = Visibility.Visible; - CurrentGameProperty.GameInstall.ProgressChanged += GameInstall_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged += GameInstall_StatusChanged; - } - - int dialogResult = await CurrentGameProperty.GameInstall.GetInstallationPath(); - switch (dialogResult) - { - case < 0: - return; - case 0: - CurrentGameProperty.GameInstall.ApplyGameConfig(); - return; - } - - if (CurrentGameProperty.GameInstall.IsUseSophon) - { - DownloadModeLabel.Visibility = Visibility.Visible; - DownloadModeLabelText.Text = Lang._Misc.DownloadModeLabelSophon; - } - - int verifResult; - bool skipDialog = false; - while ((verifResult = await CurrentGameProperty.GameInstall.StartPackageVerification()) == 0) - { - await CurrentGameProperty.GameInstall.StartPackageDownload(skipDialog); - skipDialog = true; - } - - if (verifResult == -1) - { - CurrentGameProperty.GameInstall.ApplyGameConfig(true); - return; - } - - await CurrentGameProperty.GameInstall.StartPackageInstallation(); - CurrentGameProperty.GameInstall.ApplyGameConfig(true); - if (CurrentGameProperty.GameInstall.StartAfterInstall && - CurrentGameProperty.GameVersion.IsGameInstalled()) - StartGame(null, null); - - // Set the notification trigger to "Completed" state - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Completed); - - // If the current window is not in focus, then spawn the notification toast - if (WindowUtility.IsCurrentWindowInFocus()) - { - return; - } - - string gameNameLocale = LauncherMetadataHelper.GetTranslatedCurrentGameTitleRegionString(); - WindowUtility.Tray_ShowNotification( - string.Format(Lang._NotificationToast.GameInstallCompleted_Title, - gameNameLocale), - string - .Format(Lang._NotificationToast.GameInstallCompleted_Subtitle, - gameNameLocale) - ); - } - catch (TaskCanceledException) - { - LogWriteLine($"Installation cancelled for game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}"); - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - } - catch (OperationCanceledException) - { - LogWriteLine($"Installation cancelled for game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}"); - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - } - catch (NotSupportedException ex) - { - await SentryHelper.ExceptionHandlerAsync(ex); - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - - IsPageUnload = true; - LogWriteLine($"Error while installing game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}\r\n{ex}", - LogType.Error, true); - - await SpawnDialog(Lang._HomePage.InstallFolderRootTitle, - Lang._HomePage.InstallFolderRootSubtitle, - Content, - Lang._Misc.Close, - null, null, ContentDialogButton.Close, ContentDialogTheme.Error); - } - catch (NullReferenceException ex) - { - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - - IsPageUnload = true; - LogWriteLine($"Error while installing game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}\r\n{ex}", - LogType.Error, true); - ErrorSender.SendException(new - NullReferenceException("Collapse was not able to complete post-installation tasks, but your game has been successfully updated.\r\t" + - $"Please report this issue to our GitHub here: https://github.com/CollapseLauncher/Collapse/issues/new or come back to the launcher and make sure to use Repair Game in Game Settings button later.\r\nThrow: {ex}", - ex)); - } - catch (TimeoutException ex) - { - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - IsPageUnload = true; - string exMessage = $"Timeout occurred when trying to install {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}.\r\n\t" + - $"Check stability of your internet! If your internet speed is slow, please lower the download thread count.\r\n\t" + - $"**WARNING** Changing download thread count WILL reset your download from 0, and you have to delete the existing download chunks manually!" + - $"\r\n{ex}"; - - string exTitleLocalized = string.Format(Lang._HomePage.Exception_DownloadTimeout1, CurrentGameProperty.GameVersion.GamePreset.ZoneFullname); - string exMessageLocalized = string.Format($"{exTitleLocalized}\r\n\t" + - $"{Lang._HomePage.Exception_DownloadTimeout2}\r\n\t" + - $"{Lang._HomePage.Exception_DownloadTimeout3}"); - - LogWriteLine($"{exMessage}", LogType.Error, true); - Exception newEx = new TimeoutException(exMessageLocalized, ex); - ErrorSender.SendException(newEx); - } - catch (Exception ex) - { - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - - IsPageUnload = true; - LogWriteLine($"Error while installing game {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname}.\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - finally - { - IsSkippingUpdateCheck = false; - CurrentGameProperty.GameInstall.StartAfterInstall = false; - - CurrentGameProperty.GameInstall.ProgressChanged -= isUseSophon ? - GameInstallSophon_ProgressChanged : - GameInstall_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged -= isUseSophon ? - GameInstallSophon_StatusChanged : - GameInstall_StatusChanged; - - await Task.Delay(200); - CurrentGameProperty.GameInstall.Flush(); - ReturnToHomePage(); - - // Turn the sleep back on - Sleep.RestoreSleep(); - MainWindow.IsCriticalOpInProgress = false; - } - } - - private void GameInstall_StatusChanged(object sender, TotalPerFileStatus e) - { - if (DispatcherQueue.HasThreadAccess) - GameInstall_StatusChanged_Inner(e); - else - DispatcherQueue?.TryEnqueue(() => GameInstall_StatusChanged_Inner(e)); - } - - private void GameInstall_StatusChanged_Inner(TotalPerFileStatus e) - { - ProgressStatusTitle.Text = e.ActivityStatus; - progressPerFile.Visibility = e.IsIncludePerFileIndicator ? Visibility.Visible : Visibility.Collapsed; - - progressRing.IsIndeterminate = e.IsProgressAllIndetermined; - progressRingPerFile.IsIndeterminate = e.IsProgressPerFileIndetermined; - } - - private void GameInstall_ProgressChanged(object sender, TotalPerFileProgress e) - { - if (DispatcherQueue.HasThreadAccess) - GameInstall_ProgressChanged_Inner(e); - else - DispatcherQueue?.TryEnqueue(() => GameInstall_ProgressChanged_Inner(e)); - } - - private void GameInstall_ProgressChanged_Inner(TotalPerFileProgress e) - { - progressRing.Value = e.ProgressAllPercentage; - progressRingPerFile.Value = e.ProgressPerFilePercentage; - ProgressStatusSubtitle.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressAllSizeCurrent), SummarizeSizeSimple(e.ProgressAllSizeTotal)); - ProgressStatusFooter.Text = string.Format(Lang._Misc.Speed, SummarizeSizeSimple(e.ProgressAllSpeed)); - ProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); - } - - private void GameInstallSophon_StatusChanged(object sender, TotalPerFileStatus e) - { - if (DispatcherQueue.HasThreadAccess) - GameInstallSophon_StatusChanged_Inner(e); - else - DispatcherQueue?.TryEnqueue(() => GameInstallSophon_StatusChanged_Inner(e)); - } - - private void GameInstallSophon_ProgressChanged(object sender, TotalPerFileProgress e) - { - if (DispatcherQueue.HasThreadAccess) - GameInstallSophon_ProgressChanged_Inner(e); - else - DispatcherQueue?.TryEnqueue(() => GameInstallSophon_ProgressChanged_Inner(e)); - } - - private void GameInstallSophon_StatusChanged_Inner(TotalPerFileStatus e) - { - SophonProgressStatusTitleText.Text = e.ActivityStatus; - SophonProgressPerFile.Visibility = e.IsIncludePerFileIndicator ? Visibility.Visible : Visibility.Collapsed; - - SophonProgressRing.IsIndeterminate = e.IsProgressAllIndetermined; - SophonProgressRingPerFile.IsIndeterminate = e.IsProgressPerFileIndetermined; - } - - private void GameInstallSophon_ProgressChanged_Inner(TotalPerFileProgress e) - { - SophonProgressRing.Value = e.ProgressAllPercentage; - SophonProgressRingPerFile.Value = e.ProgressPerFilePercentage; - - SophonProgressStatusSizeTotalText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressAllSizeCurrent), SummarizeSizeSimple(e.ProgressAllSizeTotal)); - SophonProgressStatusSizeDownloadedText.Text = string.Format(Lang._Misc.PerFromTo, SummarizeSizeSimple(e.ProgressPerFileSizeCurrent), SummarizeSizeSimple(e.ProgressPerFileSizeTotal)); - - SophonProgressStatusSpeedTotalText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(Math.Max(e.ProgressAllSpeed, 0))); - SophonProgressStatusSpeedDownloadedText.Text = string.Format(Lang._Misc.SpeedPerSec, SummarizeSizeSimple(Math.Max(e.ProgressPerFileSpeed, 0))); - - SophonProgressTimeLeft.Text = string.Format(Lang._Misc.TimeRemainHMSFormat, e.ProgressAllTimeLeft); - } - - private void CancelInstallationProcedure(object sender, RoutedEventArgs e) - { - switch (GameInstallationState) - { - case GameInstallStateEnum.NeedsUpdate: - case GameInstallStateEnum.InstalledHavePlugin: - CancelUpdateDownload(); - break; - case GameInstallStateEnum.InstalledHavePreload: - CancelPreDownload(); - break; - case GameInstallStateEnum.NotInstalled: - case GameInstallStateEnum.GameBroken: - case GameInstallStateEnum.Installed: - CancelInstallationDownload(); - break; - } - } - #endregion - - #region Download Cancellation - private void CancelPreDownload() - { - CurrentGameProperty.GameInstall.CancelRoutine(); - - PauseDownloadPreBtn.Visibility = Visibility.Collapsed; - ResumeDownloadPreBtn.Visibility = Visibility.Visible; - ResumeDownloadPreBtn.IsEnabled = true; - } - - private void CancelUpdateDownload() - { - CurrentGameProperty.GameInstall.CancelRoutine(); - } - - private void CancelInstallationDownload() - { - CurrentGameProperty.GameInstall.CancelRoutine(); - } - #endregion - - #region Game Start/Stop Method - - private CancellationTokenSource WatchOutputLog = new(); - private CancellationTokenSource ResizableWindowHookToken; - private async void StartGame(object sender, RoutedEventArgs e) - { - // Initialize values - IGameSettingsUniversal _Settings = CurrentGameProperty!.GameSettings!.AsIGameSettingsUniversal(); - PresetConfig _gamePreset = CurrentGameProperty!.GameVersion!.GamePreset!; - - var isGenshin = CurrentGameProperty!.GameVersion.GameType == GameNameType.Genshin; - var giForceHDR = false; - - try - { - if (!await CheckMediaPackInstalled()) return; - - if (isGenshin) - { - giForceHDR = GetAppConfigValue("ForceGIHDREnable").ToBool(); - if (giForceHDR) GenshinHDREnforcer(); - } - - if (_Settings is { SettingsCollapseMisc: { UseAdvancedGameSettings: true, UseGamePreLaunchCommand: true } }) - { - var delay = _Settings.SettingsCollapseMisc.GameLaunchDelay; - PreLaunchCommand(_Settings); - if (delay > 0) - await Task.Delay(delay); - } - - int? height = _Settings.SettingsScreen.height; - int? width = _Settings.SettingsScreen.width; - - Process proc = new Process(); - proc.StartInfo.FileName = Path.Combine(NormalizePath(GameDirPath)!, _gamePreset.GameExecutableName!); - proc.StartInfo.UseShellExecute = true; - proc.StartInfo.Arguments = GetLaunchArguments(_Settings)!; - LogWriteLine($"[HomePage::StartGame()] Running game with parameters:\r\n{proc.StartInfo.Arguments}"); - if (File.Exists(Path.Combine(GameDirPath!, "@AltLaunchMode"))) - { - LogWriteLine("[HomePage::StartGame()] Using alternative launch method!", LogType.Warning, true); - proc.StartInfo.WorkingDirectory = (CurrentGameProperty!.GameVersion.GamePreset!.ZoneName == "Bilibili" || - (isGenshin && giForceHDR) ? NormalizePath(GameDirPath) : - Path.GetDirectoryName(NormalizePath(GameDirPath))!)!; - } - else - { - proc.StartInfo.WorkingDirectory = NormalizePath(GameDirPath)!; - } - proc.StartInfo.UseShellExecute = false; - proc.StartInfo.Verb = "runas"; - proc.Start(); - - if (GetAppConfigValue("EnableConsole").ToBool()) - { - WatchOutputLog = new CancellationTokenSource(); - ReadOutputLog(); - } - - if (_Settings.SettingsCollapseScreen.UseCustomResolution && height != 0 && width != 0) - { - SetBackScreenSettings(_Settings, (int)height, (int)width, CurrentGameProperty); - } - - // Stop update check - IsSkippingUpdateCheck = true; - - // Start the resizable window payload (also use the same token as PlaytimeToken) - StartResizableWindowPayload( - _gamePreset.GameExecutableName, - _Settings, - _gamePreset.GameType, height, width); - GameRunningWatcher(_Settings); - - switch (GetAppConfigValue("GameLaunchedBehavior").ToString()) - { - case "Minimize": - WindowUtility.WindowMinimize(); - break; - case "ToTray": - WindowUtility.ToggleToTray_MainWindow(); - break; - case "Nothing": - break; - default: - WindowUtility.WindowMinimize(); - break; - } - - CurrentGameProperty.GamePlaytime.StartSession(proc); - - if (GetAppConfigValue("LowerCollapsePrioOnGameLaunch").ToBool()) CollapsePrioControl(proc); - - // Set game process priority to Above Normal when GameBoost is on - if (_Settings.SettingsCollapseMisc is { UseGameBoost: true }) - #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => Task.FromResult(_ = GameBoost_Invoke(CurrentGameProperty))); - #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - - // Run game process watcher - CheckRunningGameInstance(PageToken.Token); - } - catch (Win32Exception ex) - { - LogWriteLine($"There is a problem while trying to launch Game with Region: {_gamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); - ErrorSender.SendException(new Win32Exception($"There was an error while trying to launch the game!\r\tThrow: {ex}", ex)); - IsSkippingUpdateCheck = false; - } - } - - // Use this method to do something when game is closed - private async void GameRunningWatcher(IGameSettingsUniversal _settings) - { - ArgumentNullException.ThrowIfNull(_settings); - - await Task.Delay(5000); - while (_cachedIsGameRunning) - { - await Task.Delay(3000); - } - - LogWriteLine($"{new string('=', barWidth)} GAME STOPPED {new string('=', barWidth)}", LogType.Warning, true); - - if (ResizableWindowHookToken != null) - { - await ResizableWindowHookToken.CancelAsync(); - ResizableWindowHookToken.Dispose(); - } - - // Stopping GameLogWatcher - if (GetAppConfigValue("EnableConsole").ToBool()) - { - if (WatchOutputLog == null) return; - await WatchOutputLog.CancelAsync(); - } - - // Stop PreLaunchCommand process - if (_settings.SettingsCollapseMisc!.GamePreLaunchExitOnGameStop) PreLaunchCommand_ForceClose(); - - // Window manager on game closed - switch (GetAppConfigValue("GameLaunchedBehavior").ToString()) - { - case "Minimize": - WindowUtility.WindowRestore(); - break; - case "ToTray": - WindowUtility.CurrentWindow!.Show(); - WindowUtility.WindowRestore(); - break; - case "Nothing": - break; - default: - WindowUtility.WindowRestore(); - break; - } - - // Run Post Launch Command - if (_settings.SettingsCollapseMisc.UseAdvancedGameSettings && _settings.SettingsCollapseMisc.UseGamePostExitCommand) PostExitCommand(_settings); - - // Re-enable update check - IsSkippingUpdateCheck = false; - } - - private static void StopGame(PresetConfig gamePreset) - { - ArgumentNullException.ThrowIfNull(gamePreset); - try - { - Process[] gameProcess = Process.GetProcessesByName(gamePreset.GameExecutableName!.Split('.')[0]); - foreach (var p in gameProcess) - { - LogWriteLine($"Trying to stop game process {gamePreset.GameExecutableName.Split('.')[0]} at PID {p.Id}", LogType.Scheme, true); - p.Kill(); - } - } - catch (Win32Exception ex) - { - SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"There is a problem while trying to stop Game with Region: {gamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); - } - } - #endregion - - #region Game Resizable Window Payload - internal async void StartResizableWindowPayload(string executableName, IGameSettingsUniversal settings, - GameNameType gameType, int? height, int? width) - { - try - { - // Check if the game is using Resizable Window settings - if (!settings.SettingsCollapseScreen.UseResizableWindow) return; - ResizableWindowHookToken = new CancellationTokenSource(); - - executableName = Path.GetFileNameWithoutExtension(executableName); - string gameExecutableDirectory = CurrentGameProperty.GameVersion.GameDirPath; - ResizableWindowHook resizableWindowHook = new ResizableWindowHook(); - - // Set the pos + size reinitialization to true if the game is Honkai: Star Rail - // This is required for Honkai: Star Rail since the game will reset its pos + size. Making - // it impossible to use custom resolution (but since you are using Collapse, it's now - // possible :teriStare:) - bool isNeedToResetPos = gameType == GameNameType.StarRail; - await Task.Run(() => resizableWindowHook.StartHook(executableName, height, width, ResizableWindowHookToken.Token, - isNeedToResetPos, ILoggerHelper.GetILogger(), gameExecutableDirectory)); - } - catch (Exception ex) - { - LogWriteLine($"Error while initializing Resizable Window payload!\r\n{ex}"); - ErrorSender.SendException(ex, ErrorType.GameError); - } - } - - private async void SetBackScreenSettings(IGameSettingsUniversal settingsUniversal, int height, int width, - GamePresetProperty gameProp) - { - // Wait for the game to fully initialize - await Task.Delay(20000); - try - { - settingsUniversal.SettingsScreen.height = height; - settingsUniversal.SettingsScreen.width = width; - settingsUniversal.SettingsScreen.Save(); - - // For those stubborn game - // Kinda unneeded but :FRICK: - switch (gameProp.GamePreset.GameType) - { - case GameNameType.Zenless: - var screenManagerZ = GameSettings.Zenless.ScreenManager.Load(); - screenManagerZ.width = width; - screenManagerZ.height = height; - screenManagerZ.Save(); - break; - - case GameNameType.Honkai: - var screenManagerH = GameSettings.Honkai.ScreenSettingData.Load(); - screenManagerH.width = width; - screenManagerH.height = height; - screenManagerH.Save(); - break; - } - - LogWriteLine($"[SetBackScreenSettings] Completed task! {width}x{height}", LogType.Scheme, true); - } - catch(Exception ex) - { - await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"[SetBackScreenSettings] Failed to set Screen Settings!\r\n{ex}", LogType.Error, true); - } - - } - #endregion - - #region Game Launch Argument Builder - - private bool RequireWindowExclusivePayload; - - internal string GetLaunchArguments(IGameSettingsUniversal _Settings) - { - StringBuilder parameter = new StringBuilder(); - - switch (CurrentGameProperty.GameVersion.GameType) - { - case GameNameType.Honkai: - { - if (_Settings.SettingsCollapseScreen.UseExclusiveFullscreen) - { - parameter.Append("-window-mode exclusive "); - RequireWindowExclusivePayload = true; - } - - Size screenSize = _Settings.SettingsScreen.sizeRes; - - byte apiID = _Settings.SettingsCollapseScreen.GameGraphicsAPI; - - if (apiID == 4) - { - LogWriteLine("You are going to use DX12 mode in your game.\r\n\tUsing CustomScreenResolution or FullscreenExclusive value may break the game!", LogType.Warning); - if (_Settings.SettingsCollapseScreen.UseCustomResolution && _Settings.SettingsScreen.isfullScreen) - { - var size = ScreenProp.CurrentResolution; - parameter.Append($"-screen-width {size.Width} -screen-height {size.Height} "); - } - else - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); - } - else - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); - - switch (apiID) - { - case 0: - parameter.Append("-force-feature-level-10-1 "); - break; - // case 1 is default - default: - parameter.Append("-force-feature-level-11-0 -force-d3d11-no-singlethreaded "); - break; - case 2: - parameter.Append("-force-feature-level-11-1 "); - break; - case 3: - parameter.Append("-force-feature-level-11-1 -force-d3d11-no-singlethreaded "); - break; - case 4: - parameter.Append("-force-d3d12 "); - break; - } - - break; - } - case GameNameType.StarRail: - { - if (_Settings.SettingsCollapseScreen.UseExclusiveFullscreen) - { - parameter.Append("-window-mode exclusive -screen-fullscreen 1 "); - RequireWindowExclusivePayload = true; - } - - // Enable mobile mode - if (_Settings.SettingsCollapseMisc.LaunchMobileMode) - { - const string regLoc = GameSettings.StarRail.Model.ValueName; - var regRoot = GameSettings.Base.SettingsBase.RegistryRoot; - - if (regRoot != null || !string.IsNullOrEmpty(regLoc)) - { - var regModel = (byte[])regRoot!.GetValue(regLoc, null); - - if (regModel != null) - { - string regB64 = Convert.ToBase64String(regModel); - parameter.Append($"-is_cloud 1 -platform_type CLOUD_WEB_TOUCH -graphics_setting {regB64} "); - } - else - { - LogWriteLine("Failed enabling MobileMode for HSR: regModel is null.", LogType.Error, true); - } - } - else - { - LogWriteLine("Failed enabling MobileMode for HSR: regRoot/regLoc is unexpectedly uninitialized.", - LogType.Error, true); - } - } - - Size screenSize = _Settings.SettingsScreen.sizeRes; - - byte apiID = _Settings.SettingsCollapseScreen.GameGraphicsAPI; - - if (apiID == 4) - { - LogWriteLine("You are going to use DX12 mode in your game.\r\n\tUsing CustomScreenResolution or FullscreenExclusive value may break the game!", LogType.Warning); - if (_Settings.SettingsCollapseScreen.UseCustomResolution && _Settings.SettingsScreen.isfullScreen) - { - var size = ScreenProp.CurrentResolution; - parameter.Append($"-screen-width {size.Width} -screen-height {size.Height} "); - } - else - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); - } - else - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); - - break; - } - case GameNameType.Genshin: - { - if (_Settings.SettingsCollapseScreen.UseExclusiveFullscreen) - { - parameter.Append("-window-mode exclusive -screen-fullscreen 1 "); - RequireWindowExclusivePayload = true; - LogWriteLine("Exclusive mode is enabled in Genshin Impact, stability may suffer!\r\nTry not to Alt+Tab when game is on its loading screen :)", LogType.Warning, true); - } - - // Enable mobile mode - if (_Settings.SettingsCollapseMisc.LaunchMobileMode) - parameter.Append("use_mobile_platform -is_cloud 1 -platform_type CLOUD_THIRD_PARTY_MOBILE "); - - Size screenSize = _Settings.SettingsScreen.sizeRes; - - byte apiID = _Settings.SettingsCollapseScreen.GameGraphicsAPI; - - if (apiID == 4) - { - LogWriteLine("You are going to use DX12 mode in your game.\r\n\tUsing CustomScreenResolution or FullscreenExclusive value may break the game!", LogType.Warning); - if (_Settings.SettingsCollapseScreen.UseCustomResolution && _Settings.SettingsScreen.isfullScreen) - { - var size = ScreenProp.CurrentResolution; - parameter.Append($"-screen-width {size.Width} -screen-height {size.Height} "); - } - else - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); - } - else - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); - - break; - } - case GameNameType.Zenless: - { - // does not support exclusive mode at all - // also doesn't properly support dx12 or dx11 st - - if (_Settings.SettingsCollapseScreen.UseCustomResolution) - { - Size screenSize = _Settings.SettingsScreen.sizeRes; - parameter.Append($"-screen-width {screenSize.Width} -screen-height {screenSize.Height} "); - } - - break; - } - } - - if (_Settings.SettingsCollapseScreen.UseBorderlessScreen) - { - parameter.Append("-popupwindow "); - } - - if (!_Settings.SettingsCollapseMisc.UseCustomArguments) - { - return parameter.ToString(); - } - - string customArgs = _Settings.SettingsCustomArgument.CustomArgumentValue; - if (!string.IsNullOrEmpty(customArgs)) - parameter.Append(customArgs); - - return parameter.ToString(); - } - - public string CustomArgsValue - { - get => CurrentGameProperty?.GameSettings?.SettingsCustomArgument.CustomArgumentValue; - set - { - if (CurrentGameProperty.GameSettings == null) - return; - - CurrentGameProperty.GameSettings.SettingsCustomArgument.CustomArgumentValue = value; - } - } - - public bool UseCustomArgs - { - get => CurrentGameProperty?.GameSettings?.SettingsCollapseMisc.UseCustomArguments ?? false; - set - { - if (CurrentGameProperty.GameSettings == null) - return; - - CustomArgsTextBox.IsEnabled = CustomStartupArgsSwitch.IsOn; - CurrentGameProperty.GameSettings.SettingsCollapseMisc.UseCustomArguments = value; - } - - } - - public bool UseCustomBGRegion - { - get - { - bool value = CurrentGameProperty?.GameSettings?.SettingsCollapseMisc?.UseCustomRegionBG ?? false; - ChangeGameBGButton.IsEnabled = value; - string path = CurrentGameProperty?.GameSettings?.SettingsCollapseMisc?.CustomRegionBGPath ?? ""; - BGPathDisplay.Text = Path.GetFileName(path); - return value; - } - set - { - ChangeGameBGButton.IsEnabled = value; - - if (CurrentGameProperty?.GameSettings == null) - return; - - var regionBgPath = CurrentGameProperty.GameSettings.SettingsCollapseMisc.CustomRegionBGPath; - if (string.IsNullOrEmpty(regionBgPath) || !File.Exists(regionBgPath)) - { - regionBgPath = Path.GetFileName(GetAppConfigValue("CustomBGPath").ToString()); - CurrentGameProperty.GameSettings.SettingsCollapseMisc - .CustomRegionBGPath = regionBgPath; - } - - CurrentGameProperty.GameSettings.SettingsCollapseMisc.UseCustomRegionBG = value; - CurrentGameProperty.GameSettings.SaveBaseSettings(); - _ = m_mainPage?.ChangeBackgroundImageAsRegionAsync(); - - BGPathDisplay.Text = Path.GetFileName(regionBgPath); - } - } - #endregion - - #region Media Pack - public async Task CheckMediaPackInstalled() - { - if (CurrentGameProperty.GameVersion.GameType != GameNameType.Honkai) return true; - - RegistryKey reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\WindowsFeatures\WindowsMediaVersion"); - if (reg != null) - return true; - - LogWriteLine($"Media pack is not installed!\r\n\t" + - $"If you encounter the 'cry_ware_unity' error, run this script as an administrator:\r\n\t" + - $"{Path.Combine(AppExecutableDir, "Misc", "InstallMediaPack.cmd")}", LogType.Warning, true); - - // Skip dialog if user asked before - if (GetAppConfigValue("HI3IgnoreMediaPack").ToBool()) - return true; - - switch (await Dialog_NeedInstallMediaPackage(Content)) - { - case ContentDialogResult.Primary: - TryInstallMediaPack(); - break; - case ContentDialogResult.Secondary: - SetAndSaveConfigValue("HI3IgnoreMediaPack", true); - return true; - } - return false; - } - - public async void TryInstallMediaPack() - { - try - { - Process proc = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = Path.Combine(AppExecutableDir, "Misc", "InstallMediaPack.cmd"), - UseShellExecute = true, - Verb = "runas" - } - }; - - ShowLoadingPage.ShowLoading(Lang._Dialogs.InstallingMediaPackTitle, - Lang._Dialogs.InstallingMediaPackSubtitle); - MainFrameChanger.ChangeMainFrame(typeof(BlankPage)); - proc.Start(); - await proc.WaitForExitAsync(); - ShowLoadingPage.ShowLoading(Lang._Dialogs.InstallingMediaPackTitle, - Lang._Dialogs.InstallingMediaPackSubtitleFinished); - await Dialog_InstallMediaPackageFinished(Content); - MainFrameChanger.ChangeWindowFrame(typeof(MainPage)); - } - catch - { - // ignore - } - } - #endregion - - #region Exclusive Window Payload - public async void StartExclusiveWindowPayload() - { - IntPtr _windowPtr = ProcessChecker.GetProcessWindowHandle(CurrentGameProperty.GameVersion.GamePreset.GameExecutableName ?? ""); - await Task.Delay(1000); - Windowing.HideWindow(_windowPtr); - await Task.Delay(1000); - Windowing.ShowWindow(_windowPtr); - } - #endregion - - #region Game Log Method - private async void ReadOutputLog() - { - var saveGameLog = GetAppConfigValue("IncludeGameLogs").ToBool(); - InitializeConsoleValues(); - - // JUST IN CASE - // Sentry issue ref : COLLAPSE-LAUNCHER-55; Event ID: 13059407 - if (int.IsNegative(barWidth)) barWidth = 30; - - LogWriteLine($"{new string('=', barWidth)} GAME STARTED {new string('=', barWidth)}", LogType.Warning, - true); - LogWriteLine($"Are Game logs getting saved to Collapse logs: {saveGameLog}", LogType.Scheme, true); - - try - { - string logPath = Path.Combine(CurrentGameProperty.GameVersion.GameDirAppDataPath, - CurrentGameProperty.GameVersion.GameOutputLogName); - if (!Directory.Exists(Path.GetDirectoryName(logPath))) - Directory.CreateDirectory(Path.GetDirectoryName(logPath)!); - - if (CurrentGameProperty.GamePreset.GameType == GameNameType.Zenless) - { - var logDir = Path.Combine(CurrentGameProperty.GameVersion.GameDirPath, - @"ZenlessZoneZero_Data\Persistent\LogDir\"); - - _ = Directory.CreateDirectory(logDir); // Always ensure that the LogDir will always be created. - - var newLog = await FileUtility.WaitForNewFileAsync(logDir, 20000); - if (!newLog) - { - LogWriteLine("Cannot get Zenless' log file due to timeout! Your computer too fast XD", - LogType.Warning, saveGameLog); - return; - } - - var logPat = FileUtility.GetLatestFile(logDir, "NAP_*.log"); - - if (!string.IsNullOrEmpty(logPat)) logPath = logPat; - } - else - { - // If the log file exist beforehand, move it and make a new one - if (File.Exists(logPath)) - { - FileUtility.RenameFileWithPrefix(logPath, "-old", true); - } - } - - LogWriteLine($"Reading Game's log file from {logPath}", LogType.Default, saveGameLog); - - await using FileStream fs = - new FileStream(logPath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite); - using StreamReader reader = new StreamReader(fs); - while (true) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync(WatchOutputLog.Token); - if (RequireWindowExclusivePayload && line == "MoleMole.MonoGameEntry:Awake()") - { - StartExclusiveWindowPayload(); - RequireWindowExclusivePayload = false; - } - - LogWriteLine(line!, LogType.Game, saveGameLog); - } - - await Task.Delay(100, WatchOutputLog.Token); - } - } - catch (OperationCanceledException) - { - // Ignore when cancelled - } - catch (Exception ex) - { - await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"There were a problem in Game Log Reader\r\n{ex}", LogType.Error); - } - } - #endregion - - #region Open Button Method - private async void OpenGameFolderButton_Click(object sender, RoutedEventArgs e) - { - try - { - string gameFolder = NormalizePath(GameDirPath); - LogWriteLine($"Opening Game Folder:\r\n\t{gameFolder}"); - - await Task.Run(() => - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = "explorer.exe", - Arguments = gameFolder - } - }.Start()); - } - catch (Exception ex) - { - LogWriteLine($"Failed when trying to open game folder!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - } - - private async void OpenCacheFolderButton_Click(object sender, RoutedEventArgs e) - { - string cacheFolder = CurrentGameProperty.GameVersion.GameDirAppDataPath; - LogWriteLine($"Opening Game Folder:\r\n\t{cacheFolder}"); - try - { - await Task.Run(() => - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = "explorer.exe", - Arguments = cacheFolder - } - }.Start()); - } - catch (Exception ex) - { - LogWriteLine($"Failed when trying to open game cache folder!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - } - - private async void OpenScreenshotFolderButton_Click(object sender, RoutedEventArgs e) - { - string ScreenshotFolder = Path.Combine(NormalizePath(GameDirPath), CurrentGameProperty.GameVersion.GamePreset.GameType switch - { - GameNameType.StarRail => $"{Path.GetFileNameWithoutExtension(CurrentGameProperty.GameVersion.GamePreset.GameExecutableName)}_Data\\ScreenShots", - _ => "ScreenShot" - }); - - LogWriteLine($"Opening Screenshot Folder:\r\n\t{ScreenshotFolder}"); - - if (!Directory.Exists(ScreenshotFolder)) - Directory.CreateDirectory(ScreenshotFolder); - - try - { - await Task.Run(() => - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = "explorer.exe", - Arguments = ScreenshotFolder - } - }.Start()); - } - catch (Exception ex) - { - LogWriteLine($"Failed when trying to open game screenshot folder!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - } - - private async void CleanupFilesButton_Click(object sender, RoutedEventArgs e) - { - try - { - GameStartupSetting.Flyout.Hide(); - if (CurrentGameProperty is not null) - await CurrentGameProperty.GameInstall.CleanUpGameFiles(); - } - catch (Exception ex) - { - ErrorSender.SendException(ex); - } - } - #endregion - - #region Game Management Buttons - private void RepairGameButton_Click(object sender, RoutedEventArgs e) - { - m_mainPage!.InvokeMainPageNavigateByTag("repair"); - } - - private async void UninstallGameButton_Click(object sender, RoutedEventArgs e) - { - if (await CurrentGameProperty.GameInstall.UninstallGame()) - { - MainFrameChanger.ChangeMainFrame(typeof(HomePage)); - } - } - - private void ConvertVersionButton_Click(object sender, RoutedEventArgs e) - { - MainFrameChanger.ChangeWindowFrame(typeof(InstallationConvert)); - } - - private async void StopGameButton_Click(object sender, RoutedEventArgs e) - { - if (await Dialog_StopGame(this) != ContentDialogResult.Primary) return; - StopGame(CurrentGameProperty.GameVersion.GamePreset); - } - - private async void ChangeGameBGButton_Click(object sender, RoutedEventArgs e) - { - var file = await FileDialogNative.GetFilePicker(ImageLoaderHelper.SupportedImageFormats); - if (string.IsNullOrEmpty(file)) return; - - var currentMediaType = GetMediaType(file); - - if (currentMediaType == MediaType.StillImage) - { - FileStream croppedImage = await ImageLoaderHelper.LoadImage(file, true, true); - - if (croppedImage == null) return; - SetAlternativeFileStream(croppedImage); - } - - if (CurrentGameProperty?.GameSettings?.SettingsCollapseMisc != null) - { - CurrentGameProperty.GameSettings.SettingsCollapseMisc.CustomRegionBGPath = file; - CurrentGameProperty.GameSettings.SaveBaseSettings(); - } - _ = m_mainPage?.ChangeBackgroundImageAsRegionAsync(); - - BGPathDisplay.Text = Path.GetFileName(file); - } - - private async void MoveGameLocationButton_Click(object sender, RoutedEventArgs e) - { - try - { - if (!await CurrentGameProperty.GameInstall.MoveGameLocation()) - { - return; - } - - CurrentGameProperty.GameInstall.ApplyGameConfig(); - ReturnToHomePage(); - } - catch (NotSupportedException ex) - { - LogWriteLine($"Error has occurred while running Move Game Location tool!\r\n{ex}", LogType.Error, true); - ex = new NotSupportedException(Lang._HomePage.GameSettings_Panel2MoveGameLocationGame_SamePath, ex); - ErrorSender.SendException(ex, ErrorType.Warning); - } - catch (Exception ex) - { - LogWriteLine($"Error has occurred while running Move Game Location tool!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - } - #endregion - - #region Playtime - private void ForceUpdatePlaytimeButton_Click(object sender, RoutedEventArgs e) - { - if (_cachedIsGameRunning) return; - - UpdatePlaytime(null, CurrentGameProperty.GamePlaytime.CollapsePlaytime); - PlaytimeFlyout.ShowAt(PlaytimeBtn); - } - - private async void ChangePlaytimeButton_Click(object sender, RoutedEventArgs e) - { - if (await Dialog_ChangePlaytime(this) != ContentDialogResult.Primary) return; - - int mins = int.Parse("0" + MinutePlaytimeTextBox.Text); - int hours = int.Parse("0" + HourPlaytimeTextBox.Text); - - TimeSpan time = TimeSpan.FromMinutes(hours * 60 + mins); - if (time.Hours > 99999) time = new TimeSpan(99999, 59, 0); - - CurrentGameProperty.GamePlaytime.Update(time, true); - PlaytimeFlyout.Hide(); - } - - private async void ResetPlaytimeButton_Click(object sender, RoutedEventArgs e) - { - if (await Dialog_ResetPlaytime(this) != ContentDialogResult.Primary) return; - - CurrentGameProperty.GamePlaytime.Reset(); - PlaytimeFlyout.Hide(); - } - - private async void SyncDbPlaytimeButton_Click(object sender, RoutedEventArgs e) - { - Button button = sender as Button; - if (sender != null) - if (button != null) - button.IsEnabled = false; - - try - { - SyncDbPlaytimeBtnGlyph.Glyph = "\uf110"; // Loading - SyncDbPlaytimeBtnText.Text = Lang._HomePage.GamePlaytime_Idle_SyncDbSyncing; - await CurrentGameProperty.GamePlaytime.CheckDb(true); - - await Task.Delay(500); - - SyncDbPlaytimeBtnGlyph.Glyph = "\uf00c"; // Completed (check) - SyncDbPlaytimeBtnText.Text = Lang._Misc.Completed + "!"; - await Task.Delay(1000); - - SyncDbPlaytimeBtnGlyph.Glyph = "\uf021"; // Default - SyncDbPlaytimeBtnText.Text = Lang._HomePage.GamePlaytime_Idle_SyncDb; - } - catch (Exception ex) - { - LogWriteLine($"Failed when trying to sync playtime to database!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - - SyncDbPlaytimeBtnGlyph.Glyph = "\uf021"; // Default - SyncDbPlaytimeBtnText.Text = Lang._HomePage.GamePlaytime_Idle_SyncDb; - } - finally - { - if (sender != null) - if (button != null) - button.IsEnabled = true; - } - } - - private void NumberValidationTextBox(TextBox sender, TextBoxBeforeTextChangingEventArgs args) - { - sender.MaxLength = sender == HourPlaytimeTextBox ? 5 : 3; - args.Cancel = args.NewText.Any(c => !char.IsDigit(c)); - } - - private void UpdatePlaytime(object sender, CollapsePlaytime playtime) - { - DispatcherQueue.TryEnqueue(() => - { - PlaytimeMainBtn.Text = FormatTimeStamp(playtime.TotalPlaytime); - HourPlaytimeTextBox.Text = (playtime.TotalPlaytime.Days * 24 + playtime.TotalPlaytime.Hours).ToString(); - MinutePlaytimeTextBox.Text = playtime.TotalPlaytime.Minutes.ToString(); - - string lastPlayed = Lang._HomePage.GamePlaytime_Stats_NeverPlayed; - if (playtime.LastPlayed != null) - { - DateTime? last = playtime.LastPlayed?.ToLocalTime(); - lastPlayed = string.Format(Lang._HomePage.GamePlaytime_DateDisplay, last?.Day, - last?.Month, last?.Year, last?.Hour, last?.Minute); - } - - PlaytimeStatsDaily.Text = FormatTimeStamp(playtime.DailyPlaytime); - PlaytimeStatsWeekly.Text = FormatTimeStamp(playtime.WeeklyPlaytime); - PlaytimeStatsMonthly.Text = FormatTimeStamp(playtime.MonthlyPlaytime); - PlaytimeStatsLastSession.Text = FormatTimeStamp(playtime.LastSession); - PlaytimeStatsLastPlayed.Text = lastPlayed; - }); - return; - - static string FormatTimeStamp(TimeSpan time) => string.Format(Lang._HomePage.GamePlaytime_Display, time.Days * 24 + time.Hours, time.Minutes); - } - - private void ShowPlaytimeStatsFlyout(object sender, RoutedEventArgs e) - { - FlyoutBase.ShowAttachedFlyout(PlaytimeBtn); - } - - private void HidePlaytimeStatsFlyout(object sender, PointerRoutedEventArgs e) - { - PlaytimeStatsFlyout.Hide(); - PlaytimeStatsToolTip.IsOpen = false; - } - - private void PlaytimeStatsFlyout_OnOpened(object sender, object e) - { - // Match PlaytimeStatsFlyout and set its transition animation offset to 0 (but keep animation itself) - IReadOnlyList popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(PlaytimeBtn.XamlRoot); - foreach (var popup in popups.Where(x => x.Child is FlyoutPresenter {Content: Grid {Tag: "PlaytimeStatsFlyoutGrid"}})) - { - var transition = popup.ChildTransitions[0] as PopupThemeTransition; - transition!.FromVerticalOffset = 0; - } - } -#nullable restore - #endregion - - #region Game Update Dialog - private async void UpdateGameDialog(object sender, RoutedEventArgs e) - { - bool isUseSophon = CurrentGameProperty.GameInstall.IsUseSophon; - - HideImageCarousel(true); - - try - { - MainWindow.IsCriticalOpInProgress = true; - // Prevent device from sleep - Sleep.PreventSleep(ILoggerHelper.GetILogger()); - // Set the notification trigger to "Running" state - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Running); - - IsSkippingUpdateCheck = true; - - UpdateGameBtn.Visibility = Visibility.Collapsed; - CancelDownloadBtn.Visibility = Visibility.Visible; - - if (isUseSophon) - { - SophonProgressStatusGrid.Visibility = Visibility.Visible; - CurrentGameProperty.GameInstall.ProgressChanged += GameInstallSophon_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged += GameInstallSophon_StatusChanged; - } - else - { - ProgressStatusGrid.Visibility = Visibility.Visible; - CurrentGameProperty.GameInstall.ProgressChanged += GameInstall_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged += GameInstall_StatusChanged; - } - - int verifResult; - bool skipDialog = false; - while ((verifResult = await CurrentGameProperty.GameInstall.StartPackageVerification()) == 0) - { - await CurrentGameProperty.GameInstall.StartPackageDownload(skipDialog); - skipDialog = true; - } - if (verifResult == -1) - { - return; - } - - await CurrentGameProperty.GameInstall.StartPackageInstallation(); - CurrentGameProperty.GameInstall.ApplyGameConfig(true); - if (CurrentGameProperty.GameInstall.StartAfterInstall && CurrentGameProperty.GameVersion.IsGameInstalled()) - StartGame(null, null); - - // Set the notification trigger to "Completed" state - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Completed); - - // If the current window is not in focus, then spawn the notification toast - if (WindowUtility.IsCurrentWindowInFocus()) - { - return; - } - - string gameNameLocale = LauncherMetadataHelper.GetTranslatedCurrentGameTitleRegionString(); - string gameVersionString = CurrentGameProperty.GameVersion.GetGameVersionApi()?.VersionString; - - WindowUtility.Tray_ShowNotification( - string.Format(Lang._NotificationToast.GameUpdateCompleted_Title, gameNameLocale), - string.Format(Lang._NotificationToast.GameUpdateCompleted_Subtitle, gameNameLocale, gameVersionString) - ); - } - catch (TaskCanceledException) - { - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - LogWriteLine("Update cancelled!", LogType.Warning); - } - catch (OperationCanceledException) - { - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - LogWriteLine("Update cancelled!", LogType.Warning); - } - catch (NullReferenceException ex) - { - await SentryHelper.ExceptionHandlerAsync(ex); - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - - IsPageUnload = true; - LogWriteLine($"Update error on {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname} game!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(new NullReferenceException("Oops, the launcher cannot finalize the installation but don't worry, your game has been totally updated.\r\t" + - $"Please report this issue to our GitHub here: https://github.com/CollapseLauncher/Collapse/issues/new or come back to the launcher and make sure to use Repair Game in Game Settings button later.\r\nThrow: {ex}", ex)); - } - catch (Exception ex) - { - // Set the notification trigger - CurrentGameProperty.GameInstall.UpdateCompletenessStatus(CompletenessStatus.Cancelled); - - IsPageUnload = true; - LogWriteLine($"Update error on {CurrentGameProperty.GameVersion.GamePreset.ZoneFullname} game!\r\n{ex}", LogType.Error, true); - ErrorSender.SendException(ex); - } - finally - { - IsSkippingUpdateCheck = false; - CurrentGameProperty.GameInstall.StartAfterInstall = false; - - CurrentGameProperty.GameInstall.ProgressChanged -= isUseSophon ? - GameInstallSophon_ProgressChanged : - GameInstall_ProgressChanged; - CurrentGameProperty.GameInstall.StatusChanged -= isUseSophon ? - GameInstallSophon_StatusChanged : - GameInstall_StatusChanged; - - await Task.Delay(200); - CurrentGameProperty.GameInstall.Flush(); - ReturnToHomePage(); - - // Turn the sleep back on - Sleep.RestoreSleep(); - MainWindow.IsCriticalOpInProgress = false; - } - } - #endregion - - #region Set Hand Cursor - private void SetHandCursor(object sender, RoutedEventArgs e = null) => - (sender as UIElement)?.SetCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand)); - #endregion - - #region Hyper Link Color - private void HyperLink_OnPointerEntered(object sender, PointerRoutedEventArgs e) - { - TextBlock textBlock = null; - switch (sender) - { - case Grid grid when grid.Children[0] is TextBlock: - textBlock = (TextBlock)grid.Children[0]; - break; - case Grid grid when grid.Children[0] is CompressedTextBlock compressedTextBlock: - compressedTextBlock.Foreground = (Brush)Application.Current.Resources["AccentColor"]; - return; - case TextBlock block: - textBlock = block; - break; - } - if (textBlock != null) - textBlock.Foreground = UIElementExtensions.GetApplicationResource("AccentColor"); - } - - private void HyperLink_OnPointerExited(object sender, PointerRoutedEventArgs e) - { - TextBlock textBlock = null; - switch (sender) - { - case Grid grid when grid.Children[0] is TextBlock: - textBlock = (TextBlock)grid.Children[0]; - break; - case Grid grid when grid.Children[0] is CompressedTextBlock compressedTextBlock: - compressedTextBlock.Foreground = (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"]; - return; - case TextBlock block: - textBlock = block; - break; - } - if (textBlock != null) - textBlock.Foreground = UIElementExtensions.GetApplicationResource("TextFillColorPrimaryBrush"); - } - #endregion - - #region Misc Methods - private async void CollapsePrioControl(Process proc) - { - try - { - using (Process collapseProcess = Process.GetCurrentProcess()) - { - collapseProcess.PriorityBoostEnabled = false; - collapseProcess.PriorityClass = ProcessPriorityClass.BelowNormal; - LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Below Normal, " + - $"PriorityBoost is off, carousel is temporarily stopped", LogType.Default, true); - } - - await CarouselStopScroll(); - await proc.WaitForExitAsync(); - - using (Process collapseProcess = Process.GetCurrentProcess()) - { - collapseProcess.PriorityBoostEnabled = true; - collapseProcess.PriorityClass = ProcessPriorityClass.Normal; - LogWriteLine($"Collapse process [PID {collapseProcess.Id}] priority is set to Normal, " + - $"PriorityBoost is on, carousel is started", LogType.Default, true); - } - - await CarouselRestartScroll(); - } - catch (Exception ex) - { - await SentryHelper.ExceptionHandlerAsync(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"Error in Collapse Priority Control module!\r\n{ex}", LogType.Error, true); - } - } - - private static void GenshinHDREnforcer() - { - WindowsHDR GenshinHDR = new WindowsHDR(); - try - { - WindowsHDR.Load(); - GenshinHDR.isHDR = true; - GenshinHDR.Save(); - LogWriteLine("Successfully forced Genshin HDR settings on!", LogType.Scheme, true); - } - catch (Exception ex) - { - SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"There was an error trying to force enable HDR on Genshin!\r\n{ex}", LogType.Error, true); - } - } - - private int GameBoostInvokeTryCount { get; set; } - private async Task GameBoost_Invoke(GamePresetProperty gameProp) - { -#nullable enable - string processName = gameProp.GameExecutableName; - int processId = -1; - try - { - // Try catching the non-zero MainWindowHandle pointer and assign it to "toTargetProc" variable by using GetGameProcessWithActiveWindow() - while (!gameProp.TryGetGameProcessIdWithActiveWindow(out processId, out _)) - { - await Task.Delay(1000); // Waiting the process to be found and assigned to "toTargetProc" variable. - // This is where the magic happen. When the "toTargetProc" doesn't meet the comparison to be compared as null, - // it will instead return a non-null value and assign it to "toTargetProc" variable, - // which it will break the loop and execute the next code below it. - } - - LogWriteLine($"[HomePage::GameBoost_Invoke] Found target process! Waiting 10 seconds for process initialization...\r\n\t" + - $"Target Process : {processName} [{processId}]", LogType.Default, true); - - // Wait 20 (or 10 if its first try) seconds before applying - if (GameBoostInvokeTryCount == 0) - { - await Task.Delay(20000); - } - else - { - await Task.Delay(10000); - } - - // Check early exit - if (!gameProp.GetIsGameProcessRunning(processId)) - { - LogWriteLine($"[HomePage::GameBoost_Invoke] Game process {processName} [{processId}] has exited!", - LogType.Warning, true); - return; - } - - // Assign the priority to the process and write a log (just for displaying any info) - if (!gameProp.TrySetGameProcessPriority(processId, Hi3Helper.Win32.Native.Enums.PriorityClass.ABOVE_NORMAL_PRIORITY_CLASS)) - { - throw new Win32Exception(); - } - GameBoostInvokeTryCount = 0; - LogWriteLine($"[HomePage::GameBoost_Invoke] Game process {processName} " + - $"[{processId}] priority is boosted to above normal!", LogType.Warning, true); - } - catch (Exception ex) when (GameBoostInvokeTryCount < 5) - { - LogWriteLine($"[HomePage::GameBoost_Invoke] (Try #{GameBoostInvokeTryCount})" + - $"There has been error while boosting game priority to Above Normal! Retrying...\r\n" + - $"\tTarget Process : {processName} [{processId}]\r\n{ex}", - LogType.Error, true); - GameBoostInvokeTryCount++; - _ = Task.Run(async () => { await GameBoost_Invoke(gameProp); }); - } - catch (Exception ex) - { - LogWriteLine($"[HomePage::GameBoost_Invoke] There has been error while boosting game priority to Above Normal!\r\n" + - $"\tTarget Process : {processName} [{processId}]\r\n{ex}", - LogType.Error, true); - } -#nullable restore - } - #endregion - - #region Pre/Post Game Launch Command - private Process _procPreGLC; - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - private async void PreLaunchCommand(IGameSettingsUniversal settings) - { - try - { - var preGameLaunchCommand = settings?.SettingsCollapseMisc?.GamePreLaunchCommand; - if (string.IsNullOrEmpty(preGameLaunchCommand)) return; - - LogWriteLine($"Using Pre-launch command : {preGameLaunchCommand}\r\n" + - $"Game launch is delayed by {settings.SettingsCollapseMisc.GameLaunchDelay} ms\r\n\t" + - $"BY USING THIS, NO SUPPORT IS PROVIDED IF SOMETHING HAPPENED TO YOUR ACCOUNT, GAME, OR SYSTEM!", - LogType.Warning, true); - - _procPreGLC = new Process(); - - _procPreGLC.StartInfo.FileName = "cmd.exe"; - _procPreGLC.StartInfo.Arguments = "/S /C " + "\"" + preGameLaunchCommand + "\""; - _procPreGLC.StartInfo.CreateNoWindow = true; - _procPreGLC.StartInfo.UseShellExecute = false; - _procPreGLC.StartInfo.RedirectStandardOutput = true; - _procPreGLC.StartInfo.RedirectStandardError = true; - - _procPreGLC.OutputDataReceived += GLC_OutputHandler; - _procPreGLC.ErrorDataReceived += GLC_ErrorHandler; - - _procPreGLC.Start(); - - _procPreGLC.BeginOutputReadLine(); - _procPreGLC.BeginErrorReadLine(); - - await _procPreGLC.WaitForExitAsync(); - - _procPreGLC.OutputDataReceived -= GLC_OutputHandler; - _procPreGLC.ErrorDataReceived -= GLC_ErrorHandler; - } - catch (Win32Exception ex) - { - LogWriteLine($"There is a problem while trying to launch Pre-Game Command with Region: " + - $"{CurrentGameProperty.GameVersion.GamePreset.ZoneName}\r\nTraceback: {ex}", LogType.Error, true); - ErrorSender.SendException(new Win32Exception($"There was an error while trying to launch Pre-Launch command!\r\tThrow: {ex}", ex)); - } - finally - { - _procPreGLC?.Dispose(); - } - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - private void PreLaunchCommand_ForceClose() - { - try - { - if (_procPreGLC == null || _procPreGLC.HasExited || _procPreGLC.Id == 0) return; - - // Kill main and child processes - var taskKill = new Process(); - taskKill.StartInfo.FileName = "taskkill"; - taskKill.StartInfo.Arguments = $"/F /T /PID {_procPreGLC.Id}"; - taskKill.Start(); - taskKill.WaitForExit(); - - LogWriteLine("Pre-launch command has been forced to close!", LogType.Warning, true); - } - // Ignore external errors - catch (InvalidOperationException ioe) - { - SentryHelper.ExceptionHandler(ioe); - } - catch (Win32Exception we) - { - SentryHelper.ExceptionHandler(we); - } - catch (Exception ex) - { - SentryHelper.ExceptionHandler(ex, SentryHelper.ExceptionType.UnhandledOther); - LogWriteLine($"Error when trying to close Pre-GLC!\r\n{ex}", LogType.Error, true); - } - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - private static async void PostExitCommand(IGameSettingsUniversal settings) - { - try - { - var postGameExitCommand = settings?.SettingsCollapseMisc?.GamePostExitCommand; - if (string.IsNullOrEmpty(postGameExitCommand)) return; - - LogWriteLine($"Using Post-launch command : {postGameExitCommand}\r\n\t" + - $"BY USING THIS, NO SUPPORT IS PROVIDED IF SOMETHING HAPPENED TO YOUR ACCOUNT, GAME, OR SYSTEM!", - LogType.Warning, true); - - Process procPostGLC = new Process(); - - procPostGLC.StartInfo.FileName = "cmd.exe"; - procPostGLC.StartInfo.Arguments = "/S /C " + "\"" + postGameExitCommand + "\""; - procPostGLC.StartInfo.CreateNoWindow = true; - procPostGLC.StartInfo.UseShellExecute = false; - procPostGLC.StartInfo.RedirectStandardOutput = true; - procPostGLC.StartInfo.RedirectStandardError = true; - - procPostGLC.OutputDataReceived += GLC_OutputHandler; - procPostGLC.ErrorDataReceived += GLC_ErrorHandler; - - procPostGLC.Start(); - procPostGLC.BeginOutputReadLine(); - procPostGLC.BeginErrorReadLine(); - - await procPostGLC.WaitForExitAsync(); - - procPostGLC.OutputDataReceived -= GLC_OutputHandler; - procPostGLC.ErrorDataReceived -= GLC_ErrorHandler; - } - catch (Win32Exception ex) - { - LogWriteLine($"There is a problem while trying to launch Post-Game Command with command:\r\n\t" + - $"{settings?.SettingsCollapseMisc?.GamePostExitCommand}\r\n" + - $"Traceback: {ex}", LogType.Error, true); - ErrorSender.SendException(new Win32Exception($"There was an error while trying to launch Post-Exit command\r\tThrow: {ex}", ex)); - } - } - - private static void GLC_OutputHandler(object _, DataReceivedEventArgs e) - { - if (!string.IsNullOrEmpty(e.Data)) LogWriteLine(e.Data, LogType.GLC, true); - } - - private static void GLC_ErrorHandler(object _, DataReceivedEventArgs e) - { - if (!string.IsNullOrEmpty(e.Data)) LogWriteLine($"ERROR RECEIVED!\r\n\t" + $"{e.Data}", LogType.GLC, true); - } - #endregion - - #region Shortcut Creation - private async void AddToSteamButton_Click(object sender, RoutedEventArgs e) - { - GameStartupSettingFlyout.Hide(); - - Tuple result = await Dialog_SteamShortcutCreationConfirm(this); - - if (result.Item1 != ContentDialogResult.Primary) - return; - - if (await ShortcutCreator.AddToSteam(CurrentGameProperty.GamePreset, result.Item2)) - { - await Dialog_SteamShortcutCreationSuccess(this, result.Item2); - return; - } - - await Dialog_SteamShortcutCreationFailure(this); - } - - private async void ShortcutButton_Click(object sender, RoutedEventArgs e) - { - string folder = await FileDialogNative.GetFolderPicker(Lang._HomePage.CreateShortcut_FolderPicker); - - if (string.IsNullOrEmpty(folder)) - return; - - if (!IsUserHasPermission(folder)) - { - await Dialog_InsufficientWritePermission(sender as UIElement, folder); - return; - } - - Tuple result = await Dialog_ShortcutCreationConfirm(this, folder); - - if (result.Item1 != ContentDialogResult.Primary) - return; - - ShortcutCreator.CreateShortcut(folder, CurrentGameProperty.GamePreset, result.Item2); - await Dialog_ShortcutCreationSuccess(this, folder, result.Item2); - } - #endregion - - private async void ProgressSettingsButton_OnClick(object sender, RoutedEventArgs e) => await Dialog_DownloadSettings(this, CurrentGameProperty); - - private void ApplyShadowToImageElement(object sender, RoutedEventArgs e) - { - if (sender is not ButtonBase { Content: Panel panel }) - { - return; - } - - bool isStart = true; - foreach (Image imageElement in panel.Children.OfType()) - { - imageElement.ApplyDropShadow(opacity: 0.5f); - if (!isStart) - { - continue; - } - - imageElement.Opacity = 0.0f; - imageElement.Loaded += (_, _) => - { - Compositor compositor = imageElement.GetElementCompositor(); - imageElement.StartAnimationDetached(TimeSpan.FromSeconds(0.25f), - compositor.CreateScalarKeyFrameAnimation("Opacity", 1.0f)); - }; - isStart = false; - } - - foreach (ImageEx.ImageEx imageElement in panel.Children.OfType()) - { - imageElement.ApplyDropShadow(opacity: 0.5f); - if (!isStart) - { - continue; - } - - imageElement.Opacity = 0.0f; - imageElement.Loaded += (_, _) => - { - Compositor compositor = imageElement.GetElementCompositor(); - imageElement.StartAnimationDetached(TimeSpan.FromSeconds(0.25f), - compositor.CreateScalarKeyFrameAnimation("Opacity", 1.0f)); - }; - isStart = false; - } - } - - private bool IsPointerInsideSidePanel; - private bool IsSidePanelCurrentlyScaledOut; - - private async void SidePanelScaleOutHoveredPointerEntered(object sender, PointerRoutedEventArgs e) - { - if (!IsEventsPanelScaleUp) return; - - IsPointerInsideSidePanel = true; - if (sender is not FrameworkElement elementPanel) - { - return; - } - - await Task.Delay(TimeSpan.FromSeconds(0.2)); - if (IsSidePanelCurrentlyScaledOut) return; - if (!IsPointerInsideSidePanel) return; - - var toScale = WindowSize.WindowSize.CurrentWindowSize.PostEventPanelScaleFactor; - var storyboard = new Storyboard(); - var transform = (CompositeTransform)elementPanel.RenderTransform; - transform.CenterY = elementPanel.ActualHeight + 8; - var cubicEaseOut = new CubicEase - { - EasingMode = EasingMode.EaseOut - }; - - var scaleXAnim = new DoubleAnimation - { - From = transform.ScaleX, - To = toScale, - Duration = new Duration(TimeSpan.FromSeconds(0.2)), - EasingFunction = cubicEaseOut - }; - Storyboard.SetTarget(scaleXAnim, transform); - Storyboard.SetTargetProperty(scaleXAnim, "ScaleX"); - storyboard.Children.Add(scaleXAnim); + var scaleXAnim = new DoubleAnimation + { + From = transform.ScaleX, + To = toScale, + Duration = new Duration(TimeSpan.FromSeconds(0.2)), + EasingFunction = cubicEaseOut + }; + Storyboard.SetTarget(scaleXAnim, transform); + Storyboard.SetTargetProperty(scaleXAnim, "ScaleX"); + storyboard.Children.Add(scaleXAnim); var scaleYAnim = new DoubleAnimation { @@ -3239,7 +1177,10 @@ private async void SidePanelScaleInHoveredPointerExited(object sender, PointerRo await storyboard.BeginAsync(); IsSidePanelCurrentlyScaledOut = false; } + + #endregion + #region Element Scale private void ElementScaleOutHoveredPointerEntered(object sender, PointerRoutedEventArgs e) { if (sender is FrameworkElement elementPanel) @@ -3297,82 +1238,51 @@ await element.StartAnimation( compositor.CreateVector3KeyFrameAnimation("Scale", new Vector3(1.0f)) ); } + + #endregion - private async void SpawnPreloadDialogBox() - { - PreloadDialogBox.IsOpen = true; - PreloadDialogBox.Translation = new Vector3(0, 0, 16); - Compositor compositor = CompositionTarget.GetCompositorForCurrentThread(); - - PreloadDialogBox.Opacity = 0.0f; - const float toScale = 0.98f; - Vector3 toTranslate = new Vector3(-((float)(PreloadDialogBox?.ActualWidth ?? 0) * (toScale - 1f) / 2), - -((float)(PreloadDialogBox?.ActualHeight ?? 0) * (toScale - 1f)) - 16, 0); - - await PreloadDialogBox.StartAnimation(TimeSpan.FromSeconds(0.5), - compositor.CreateScalarKeyFrameAnimation("Opacity", 1.0f, 0.0f), - compositor.CreateVector3KeyFrameAnimation("Scale", - new Vector3(1.0f, 1.0f, PreloadDialogBox!.Translation.Z), - new Vector3(toScale, toScale, - PreloadDialogBox.Translation.Z)), - compositor.CreateVector3KeyFrameAnimation("Translation", PreloadDialogBox.Translation, toTranslate) - ); - } - - private bool? _regionPlayingRpc; - private bool ToggleRegionPlayingRpc + private void ApplyShadowToImageElement(object sender, RoutedEventArgs e) { - get => _regionPlayingRpc ??= CurrentGameProperty.GameSettings?.AsIGameSettingsUniversal() - .SettingsCollapseMisc.IsPlayingRpc ?? false; - set + if (sender is not ButtonBase { Content: Panel panel }) { - if (CurrentGameProperty.GameSettings == null) - return; - - CurrentGameProperty.GameSettings.AsIGameSettingsUniversal() - .SettingsCollapseMisc.IsPlayingRpc = value; - _regionPlayingRpc = value; - CurrentGameProperty.GameSettings.SaveSettings(); + return; } - } - private async Task CheckUserAccountControlStatus() - { - try + bool isStart = true; + foreach (Image imageElement in panel.Children.OfType()) { - var skipChecking = GetAppConfigValue("SkipCheckingUAC").ToBool(); - if (skipChecking) - return; - - var enabled = - (int)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", - "EnableLUA", 1)!; - if (enabled != 1) + imageElement.ApplyDropShadow(opacity: 0.5f); + if (!isStart) { - var result = await SpawnDialog(Lang._Dialogs.UACWarningTitle, Lang._Dialogs.UACWarningContent, this, Lang._Misc.Close, - Lang._Dialogs.UACWarningLearnMore, Lang._Dialogs.UACWarningDontShowAgain, - ContentDialogButton.Close, ContentDialogTheme.Warning); - switch (result) - { - case ContentDialogResult.Primary: - new Process - { - StartInfo = new ProcessStartInfo - { - UseShellExecute = true, - FileName = "https://learn.microsoft.com/windows/security/application-security/application-control/user-account-control/settings-and-configuration?tabs=reg" - } - }.Start(); - break; - case ContentDialogResult.Secondary: - SetAndSaveConfigValue("SkipCheckingUAC", true); - break; - } + continue; } + + imageElement.Opacity = 0.0f; + imageElement.Loaded += (_, _) => + { + Compositor compositor = imageElement.GetElementCompositor(); + imageElement.StartAnimationDetached(TimeSpan.FromSeconds(0.25f), + compositor.CreateScalarKeyFrameAnimation("Opacity", 1.0f)); + }; + isStart = false; } - catch (Exception) + + foreach (ImageEx.ImageEx imageElement in panel.Children.OfType()) { - // ignore + imageElement.ApplyDropShadow(opacity: 0.5f); + if (!isStart) + { + continue; + } + + imageElement.Opacity = 0.0f; + imageElement.Loaded += (_, _) => + { + Compositor compositor = imageElement.GetElementCompositor(); + imageElement.StartAnimationDetached(TimeSpan.FromSeconds(0.25f), + compositor.CreateScalarKeyFrameAnimation("Opacity", 1.0f)); + }; + isStart = false; } } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs index fc10fe908..8033aa815 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs @@ -66,6 +66,9 @@ public sealed partial class SettingsPage : Page private const string RepoUrl = "https://github.com/CollapseLauncher/Collapse/commit/"; + private readonly string ExplorerPath = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"); + #endregion #region Settings Page Handler @@ -216,8 +219,8 @@ private void OpenAppDataFolder(object sender, RoutedEventArgs e) StartInfo = new ProcessStartInfo { UseShellExecute = true, - FileName = "explorer.exe", - Arguments = AppGameFolder + FileName = ExplorerPath, + Arguments = AppGameFolder } }.Start(); } @@ -1543,7 +1546,7 @@ await Task.Run(() => { ProcessStartInfo psi = new ProcessStartInfo { - FileName = "explorer.exe", + FileName = ExplorerPath, Arguments = "https://aka.ms/vs/17/release/vc_redist.x64.exe", UseShellExecute = true, Verb = "runas"