diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a268839a5c..6469141e43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,10 +15,10 @@ If you wish to add new language that isn't yet listed in the Transifex project, ## Tools Needed Below is a list of tools needed to contribute to this project: -1. **Visual Studio 2022 (Any Edition - latest version)** or **JetBrains Rider (Any Edition - latest version)** +1. **Visual Studio 2026 (Any Edition - latest version)** or **JetBrains Rider (Any Edition - latest version)** - Select .NET desktop development component 2. **Windows SDK (10.0.26100.0 ONLY)** via Visual Studio Installer -3. .NET 9 SDK: [**(9.0.4 or later)**](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) +3. .NET 10 SDK: [**(10.0.5 or later)**](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) > **Note**: > Make sure to always use the latest version of Visual Studio or Rider in order to be able to open the project. diff --git a/CollapseLauncher.slnx b/CollapseLauncher.slnx index b9c1c02b68..bfdd010376 100644 --- a/CollapseLauncher.slnx +++ b/CollapseLauncher.slnx @@ -69,9 +69,6 @@ - - - diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs index a1e5b7ce26..0ca8328779 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs @@ -54,14 +54,16 @@ internal static T BindNavigationViewItemText(this T element, object? localeOb element.BindTooltipToLocale(localeObjBinding, localePropertyName); } - internal static T BindTooltipToLocale(this T element, object? localeObjBinding, string localePropertyName) + internal static T BindTooltipToLocale(this T element, object? localeObjBinding, string localePropertyName, IValueConverter? converter = null, object? converterParameter = null) where T : DependencyObject { TextBlock tooltipTextBlock = new(); tooltipTextBlock.BindProperty(TextBlock.TextProperty, localeObjBinding, localePropertyName, - sourceTrigger: UpdateSourceTrigger.PropertyChanged); + sourceTrigger: UpdateSourceTrigger.PropertyChanged, + converter: converter, + converterParameter: converterParameter); ToolTipService.SetToolTip(element, tooltipTextBlock); return element; diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.CodecDetect.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.CodecDetect.cs index d8de20f64b..da68852c83 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.CodecDetect.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.CodecDetect.cs @@ -16,7 +16,7 @@ public partial class ImageBackgroundManager { #region Codec Checks - private async Task<(bool IsSupported, bool IsVideo)> CheckCodecOrSpawnDialog(Uri? fileUri) + private async ValueTask<(bool IsSupported, bool IsVideo)> CheckCodecOrSpawnDialog(Uri? fileUri) { // -- Cancel if null if (fileUri == null) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs index 6a6b3e0ce8..00ddef2048 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Control.cs @@ -52,10 +52,18 @@ public void Play(bool isUserRequest = true) { CurrentIsEnableBackgroundAutoPlay = true; } + + // Alter to load static image if it has one instead of pausing. + LayeredImageBackgroundContext? context = CurrentSelectedBackgroundContext; + bool hasStaticImage = !string.IsNullOrEmpty(context?.BackgroundImageStaticPath); + if (isUserRequest && hasStaticImage && context != null) + { + LoadImageAtIndex(CurrentSelectedBackgroundIndex, false, CancellationToken.None); + } // Force to restore autoplay status to true. CurrentBackgroundElement?.SetValue(LayeredBackgroundImage.IsVideoAutoplayProperty, true); - + Interlocked.Exchange(ref _isPausedByUser, false); CurrentBackgroundElement?.Play(); } @@ -67,6 +75,15 @@ public void Pause(bool isUserRequest = true) Interlocked.Exchange(ref _isPausedByUser, true); CurrentIsEnableBackgroundAutoPlay = false; } + + // Alter to load static image if it has one instead of pausing. + LayeredImageBackgroundContext? context = CurrentSelectedBackgroundContext; + bool hasStaticImage = !string.IsNullOrEmpty(context?.BackgroundImageStaticPath); + if (isUserRequest && hasStaticImage && context != null) + { + LoadImageAtIndex(CurrentSelectedBackgroundIndex, true, CancellationToken.None); + return; + } CurrentBackgroundElement?.Pause(); } diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FFmpeg.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FFmpeg.cs index 990586cbbe..842b398f0a 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FFmpeg.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.FFmpeg.cs @@ -1,11 +1,14 @@ using CollapseLauncher.Helper; +using CollapseLauncher.Helper.FFmpegPInvoke; using CollapseLauncher.Helper.StreamUtility; +using FFmpegInteropX; using Hi3Helper; using Hi3Helper.Shared.Region; using System; using System.Collections; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; // ReSharper disable StringLiteralTypo // ReSharper disable IdentifierTypo @@ -15,26 +18,51 @@ namespace CollapseLauncher.GameManagement.ImageBackground; -#region File Exclusive Fields -file static class Fields -{ - public const string DllNameAvcodec = "avcodec-61.dll"; - public const string DllNameAvdevice = "avdevice-61.dll"; - public const string DllNameAvfilter = "avfilter-10.dll"; - public const string DllNameAvformat = "avformat-61.dll"; - public const string DllNameAvutil = "avutil-59.dll"; - public const string DllNamePostproc = "postproc-58.dll"; - public const string DllNameSwresample = "swresample-5.dll"; - public const string DllNameSwscale = "swscale-8.dll"; -} -#endregion - public partial class ImageBackgroundManager { #region Shared/Static Properties and Fields - private const string GlobalIsUseFFmpegConfigKey = "GlobalIsUseFFmpeg"; - private const string GlobalFFmpegCustomPathConfigKey = "GlobalFFmpegCustomPath"; + private const string GlobalIsUseFFmpegConfigKey = "GlobalIsUseFFmpeg"; + private const string GlobalFFmpegVersionToUseConfigKey = "GlobalFFmpegVersionToUse"; + private const string GlobalFFmpegCustomPathConfigKey = "GlobalFFmpegCustomPath"; + private const string GlobalFFmpegDecodingModeConfigKey = "GlobalFFmpegDecodingMode"; + + public VideoDecoderMode[] AvailableFFmpegDecodingModes => field ??= Enum.GetValues(); + + public int GlobalFFmpegVersionToUse + { + get + { + int version = LauncherConfig.GetAppConfigValue(GlobalFFmpegVersionToUseConfigKey); + return FFmpegPInvoke.FFmpegVersionLibNames.ContainsKey(version) ? + version : + FFmpegPInvoke.FFmpegVersionLibNames.Keys.FirstOrDefault(); + } + set + { + if (!FFmpegPInvoke.FFmpegVersionLibNames.ContainsKey(value)) + { + return; + } + + LauncherConfig.SetAndSaveConfigValue(GlobalFFmpegVersionToUseConfigKey, value); + OnPropertyChanged(); + OnPropertyChanged(nameof(GlobalFFmpegLibraryNames)); // Notify FFmpeg library names update too. + } + } + + public FFmpegPInvoke.FFmpegLibraryNames GlobalFFmpegLibraryNames + { + get + { + if (FFmpegPInvoke.FFmpegVersionLibNames.TryGetValue(GlobalFFmpegVersionToUse, out var names)) + { + return names; + } + + return FFmpegPInvoke.FFmpegVersionLibNames.Values.FirstOrDefault(); + } + } public bool GlobalIsUseFFmpeg { @@ -56,7 +84,7 @@ public string? GlobalCustomFFmpegPath } } - public bool GlobalIsFFmpegAvailable => IsFFmpegAvailable(Directory.GetCurrentDirectory(), out _); + public bool GlobalIsFFmpegAvailable => IsFFmpegAvailable(Directory.GetCurrentDirectory(), GlobalFFmpegLibraryNames, out _); public bool GlobalIsFFmpegCurrentlyUsed { @@ -68,6 +96,31 @@ public bool GlobalIsFFmpegCurrentlyUsed } } + public VideoDecoderMode GlobalFFmpegDecodingMode + { + get + { + string? value = LauncherConfig.GetAppConfigValue(GlobalFFmpegDecodingModeConfigKey); + if (Enum.TryParse(value, out var result)) + { + return result; + } + + return default; + } + set + { + if (!Enum.IsDefined(value)) + { + value = default; + } + + string valueStr = value.ToString(); + LauncherConfig.SetAndSaveConfigValue(GlobalFFmpegDecodingModeConfigKey, valueStr); + OnPropertyChanged(); + } + } + #endregion public void RefreshFFmpegBinding() @@ -89,8 +142,10 @@ public bool TryRelinkFFmpegPath() return false; } + var names = GlobalFFmpegLibraryNames; + string curDir = Directory.GetCurrentDirectory(); - bool isFFmpegAvailable = IsFFmpegAvailable(curDir, out exception); + bool isFFmpegAvailable = IsFFmpegAvailable(curDir, names, out exception); string? customFFmpegDirPath = GlobalCustomFFmpegPath; if (isFFmpegAvailable) @@ -100,16 +155,16 @@ public bool TryRelinkFFmpegPath() // -- 1. Check from custom path first. If it exists, then pass. if (!string.IsNullOrEmpty(customFFmpegDirPath) && - IsFFmpegAvailable(customFFmpegDirPath, out exception) && - TryLinkFFmpegLibrary(customFFmpegDirPath, curDir, out exception)) + IsFFmpegAvailable(customFFmpegDirPath, names, out exception) && + TryLinkFFmpegLibrary(customFFmpegDirPath, curDir, names, out exception)) { return result = true; } // -- 2. Find one from environment variables. If it exists, then pass. // Otherwise, return false. - return result = TryFindFFmpegInstallFromEnvVar(out string? envVarPath, out exception) && - TryLinkFFmpegLibrary(envVarPath, curDir, out exception); + return result = TryFindFFmpegInstallFromEnvVar(names, out string ? envVarPath, out exception) && + TryLinkFFmpegLibrary(envVarPath, curDir, names, out exception); } finally { @@ -122,7 +177,7 @@ public bool TryRelinkFFmpegPath() } } - internal bool TryFindFFmpegInstallFromEnvVar([NotNullWhen(true)] out string? path, out Exception? exception) + internal bool TryFindFFmpegInstallFromEnvVar(FFmpegPInvoke.FFmpegLibraryNames libraries, [NotNullWhen(true)] out string? path, out Exception? exception) { return FindIn(EnvironmentVariableTarget.User, out path, out exception) || FindIn(EnvironmentVariableTarget.Machine, out path, out exception); @@ -152,7 +207,7 @@ bool FindIn(EnvironmentVariableTarget target, [NotNullWhen(true)] out string? in string thisPath = envVarPath.ToString(); if (!Path.IsPathFullyQualified(thisPath) || - !IsFFmpegAvailable(thisPath, out exception)) continue; + !IsFFmpegAvailable(thisPath, libraries, out exception)) continue; innerPath = thisPath; GlobalCustomFFmpegPath = thisPath; // Set as custom path @@ -165,6 +220,7 @@ bool FindIn(EnvironmentVariableTarget target, [NotNullWhen(true)] out string? in } internal static bool IsFFmpegAvailable(string? checkOnDirectory, + FFmpegPInvoke.FFmpegLibraryNames libraries, [NotNullWhen(false)] out Exception? exception) { @@ -176,13 +232,13 @@ internal static bool IsFFmpegAvailable(string? checkOnDirectory, checkOnDirectory = FileUtility.GetFullyQualifiedPath(checkOnDirectory); - string dllPathAvcodec = Path.Combine(checkOnDirectory, Fields.DllNameAvcodec); - string dllPathAvdevice = Path.Combine(checkOnDirectory, Fields.DllNameAvdevice); - string dllPathAvfilter = Path.Combine(checkOnDirectory, Fields.DllNameAvfilter); - string dllPathAvformat = Path.Combine(checkOnDirectory, Fields.DllNameAvformat); - string dllPathAvutil = Path.Combine(checkOnDirectory, Fields.DllNameAvutil); - string dllPathSwresample = Path.Combine(checkOnDirectory, Fields.DllNameSwresample); - string dllPathSwscale = Path.Combine(checkOnDirectory, Fields.DllNameSwscale); + string dllPathAvcodec = Path.Combine(checkOnDirectory, libraries.Codec); + string dllPathAvdevice = Path.Combine(checkOnDirectory, libraries.Device); + string dllPathAvfilter = Path.Combine(checkOnDirectory, libraries.Filter); + string dllPathAvformat = Path.Combine(checkOnDirectory, libraries.Format); + string dllPathAvutil = Path.Combine(checkOnDirectory, libraries.Util); + string dllPathSwresample = Path.Combine(checkOnDirectory, libraries.Resample); + string dllPathSwscale = Path.Combine(checkOnDirectory, libraries.Scale); return FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathAvcodec, out _, out exception) && FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathAvdevice, out _, out exception) && @@ -193,29 +249,32 @@ internal static bool IsFFmpegAvailable(string? checkOnDirectory, FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathSwscale, out _, out exception); } - internal static string[] GetFFmpegRequiredDllFilenames() => - [ - Fields.DllNameAvcodec, - Fields.DllNameAvdevice, - Fields.DllNameAvfilter, - Fields.DllNameAvformat, - Fields.DllNameAvutil, - Fields.DllNameSwresample, - Fields.DllNameSwscale - ]; - - internal static string? FindFFmpegInstallFolder(string checkOnDirectory) + internal static string[] GetFFmpegRequiredDllFilenames() + { + var names = Shared.GlobalFFmpegLibraryNames; + return [ + names.Codec, + names.Device, + names.Filter, + names.Format, + names.Util, + names.Resample, + names.Scale + ]; + } + + internal static string? FindFFmpegInstallFolder(string checkOnDirectory, FFmpegPInvoke.FFmpegLibraryNames libraries) { try { - if (IsFFmpegAvailable(checkOnDirectory, out _)) + if (IsFFmpegAvailable(checkOnDirectory, libraries, out _)) { return checkOnDirectory; } foreach (string dirPath in FileUtility.EnumerateDirectoryRecursive(checkOnDirectory)) { - if (IsFFmpegAvailable(dirPath, out _)) + if (IsFFmpegAvailable(dirPath, libraries, out _)) { return dirPath; } @@ -232,6 +291,7 @@ internal static string[] GetFFmpegRequiredDllFilenames() => public static bool TryLinkFFmpegLibrary( string? sourceDir, string? targetDir, + FFmpegPInvoke.FFmpegLibraryNames libraries, [NotNullWhen(false)] out Exception? exception) { @@ -251,14 +311,14 @@ public static bool TryLinkFFmpegLibrary( return false; } - string dllPathAvcodec = Path.Combine(sourceDir, Fields.DllNameAvcodec); - string dllPathAvdevice = Path.Combine(sourceDir, Fields.DllNameAvdevice); - string dllPathAvfilter = Path.Combine(sourceDir, Fields.DllNameAvfilter); - string dllPathAvformat = Path.Combine(sourceDir, Fields.DllNameAvformat); - string dllPathAvutil = Path.Combine(sourceDir, Fields.DllNameAvutil); - string dllPathPostproc = Path.Combine(sourceDir, Fields.DllNamePostproc); - string dllPathSwresample = Path.Combine(sourceDir, Fields.DllNameSwresample); - string dllPathSwscale = Path.Combine(sourceDir, Fields.DllNameSwscale); + string dllPathAvcodec = Path.Combine(sourceDir, libraries.Codec); + string dllPathAvdevice = Path.Combine(sourceDir, libraries.Device); + string dllPathAvfilter = Path.Combine(sourceDir, libraries.Filter); + string dllPathAvformat = Path.Combine(sourceDir, libraries.Format); + string dllPathAvutil = Path.Combine(sourceDir, libraries.Util); + string dllPathPostproc = Path.Combine(sourceDir, libraries.PostProc); + string dllPathSwresample = Path.Combine(sourceDir, libraries.Resample); + string dllPathSwscale = Path.Combine(sourceDir, libraries.Scale); bool result = CreateSymbolLink(dllPathAvcodec, targetDir, out exception) && diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs index c5c7652b9f..9c470f1bbe 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.ImageCropperAndConvert.cs @@ -98,6 +98,12 @@ public partial class ImageBackgroundManager ImageCropper cropper = UIElementExtensions .Create(SetImageCropperProperties); + // Skip spawning crop overlay when source dimensions do not meet minimum crop size. + if (!await CanOpenCropOverlay(backgroundImageUri, backgroundCodecType == ImageExternalCodecType.Svg, cropper, token)) + { + return (overlayUrlOrPath, backgroundUrlOrPath, false); + } + // -- Register to close dialog if cancellation is triggered outside the event. token.Register(dialog.Hide); if (token.IsCancellationRequested) @@ -186,6 +192,41 @@ void SetImageCropperProperties(ImageCropper element) } } + private static Task CanOpenCropOverlay(Uri imagePath, bool isSvg, ImageCropper cropper, CancellationToken token) + { + if (isSvg) + { + // SVG has no fixed source pixel size before rasterization, so keep existing behavior. + return Task.FromResult(true); + } + + TaskCompletionSource tcs = new(); + DispatcherQueueExtensions.CurrentDispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, Impl); + return tcs.Task; + + async void Impl() + { + try + { + await using Stream sourceStream = await OpenStreamFromFileOrUrl(imagePath, token); + using IRandomAccessStream randomStream = sourceStream.AsRandomAccessStream(true); + + WriteableBitmap sourceBitmap = new(1, 1); + await sourceBitmap.SetSourceAsync(randomStream); + + tcs.SetResult(cropper.CanCropSource(sourceBitmap.PixelWidth, sourceBitmap.PixelHeight)); + } + catch (OperationCanceledException) + { + tcs.SetCanceled(token); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + } + /// /// Load the image to instance in asynchronous detached manner. /// diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs index 70984e07bf..d736088107 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.Loaders.cs @@ -13,6 +13,7 @@ using Microsoft.UI.Xaml.Media.Animation; using PhotoSauce.MagicScaler; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -22,7 +23,6 @@ using Windows.Foundation; using Windows.UI; // ReSharper disable IdentifierTypo - // ReSharper disable CheckNamespace #nullable enable @@ -36,7 +36,7 @@ public partial class ImageBackgroundManager #endregion - private void LoadImageAtIndex(int index, CancellationToken token) + private void LoadImageAtIndex(int index, bool forceLoadToStatic, CancellationToken token) { if (ImageContextSources.Count <= index || index < 0 || @@ -46,14 +46,14 @@ private void LoadImageAtIndex(int index, CancellationToken token) } IsBackgroundLoading = true; - new Thread(() => LoadImageAtIndexCore(index, token).ConfigureAwait(false)) + new Thread(async () => await LoadImageAtIndexCore(index, forceLoadToStatic, token).ConfigureAwait(false)) { IsBackground = true, Priority = ThreadPriority.Lowest }.UnsafeStart(); } - private async Task LoadImageAtIndexCore(int index, CancellationToken token) + private async Task LoadImageAtIndexCore(int index, bool forceLoadToStatic, CancellationToken token) { Stopwatch? stopwatch = null; try @@ -126,10 +126,18 @@ private async Task LoadImageAtIndexCore(int index, CancellationToken token) } // -- Read Color Accent information from current background context. - new Thread(GetMediaAccentColor) + new Thread(async context => await GetMediaAccentColor(context).ConfigureAwait(false)) { IsBackground = true - }.Start((downloadedBackgroundUri, isUseFFmpeg)); + }.UnsafeStart((downloadedBackgroundUri, isUseFFmpeg)); + + // Try to force loading static image if requested. + if (forceLoadToStatic && downloadedBackgroundStaticUri != null) + { + isVideo = false; + downloadedBackgroundUri = downloadedBackgroundStaticUri; + downloadedBackgroundStaticUri = null; + } // -- Use UI thread and load image layer DispatcherQueueExtensions @@ -190,6 +198,11 @@ private void SpawnImageLayer(Uri? overlayFilePath, layerElement.IsVideoAutoplay = WindowUtility.CurrentWindowIsVisible; } + layerElement.BindProperty(LayeredBackgroundImage.FfmpegDecoderModeProperty, + this, + nameof(GlobalFFmpegDecodingMode), + bindingMode: BindingMode.OneWay); + layerElement.BindProperty(LayeredBackgroundImage.ParallaxHoverSourceProperty, this, nameof(GlobalParallaxHoverSource), @@ -256,19 +269,17 @@ private void LayerElementCanvasSizeChanged(LayeredBackgroundImage layerElement, private void LayerElementOnLoaded(LayeredBackgroundImage layerElement) { - layerElement.Transitions.Add(new PopupThemeTransition()); layerElement.ImageLoaded -= LayerElementOnLoaded; + layerElement.Transitions.Add(new PopupThemeTransition()); - if (PresenterGrid?.Children.Count > 1) + UIElement? lastElement = PresenterGrid?.Children.LastOrDefault(); + List elementToRemove = PresenterGrid?.Children.Where(element => element != lastElement).ToList() ?? []; + foreach (UIElement element in elementToRemove) { - UIElement? lastElement = PresenterGrid?.Children.LastOrDefault(); - foreach (UIElement element in PresenterGrid?.Children.Where(element => element != lastElement) ?? []) + PresenterGrid?.Children.Remove(element); + if (element is LayeredBackgroundImage asLayeredImage) { - PresenterGrid?.Children.Remove(element); - if (element is LayeredBackgroundImage asLayeredImage) - { - asLayeredImage.CanvasSizeChanged -= LayerElementCanvasSizeChanged; - } + asLayeredImage.CanvasSizeChanged -= LayerElementCanvasSizeChanged; } } @@ -350,7 +361,7 @@ void Impl() } } - private async void GetMediaAccentColor(object? context) + private async Task GetMediaAccentColor(object? context) { try { @@ -359,7 +370,8 @@ private async void GetMediaAccentColor(object? context) return; } - Color color = await ColorPaletteUtility.GetMediaAccentColorFromAsync(asUri, useFfmpegForVideo); + Color color = await ColorPaletteUtility.GetMediaAccentColorFromAsync(asUri, useFfmpegForVideo) + .ConfigureAwait(false); ColorAccentChanged?.Invoke(color); } catch (Exception ex) diff --git a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs index 9c95d13684..0213181534 100644 --- a/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs +++ b/CollapseLauncher/Classes/GameManagement/ImageBackground/ImageBackgroundManager.cs @@ -2,8 +2,10 @@ using CollapseLauncher.Helper.Image; using CollapseLauncher.Helper.LauncherApiLoader.HoYoPlay; using CollapseLauncher.Helper.Metadata; +using CollapseLauncher.Interfaces; using CollapseLauncher.Interfaces.Class; using CollapseLauncher.XAMLs.Theme.CustomControls; +using Google.Protobuf.WellKnownTypes; using Hi3Helper.SentryHelper; using Hi3Helper.Shared.Region; using Microsoft.UI.Xaml; @@ -27,7 +29,8 @@ namespace CollapseLauncher.GameManagement.ImageBackground; [GeneratedBindableCustomProperty] public partial class ImageBackgroundManager - : NotifyPropertyChanged + : NotifyPropertyChanged, + INotifyAllPropertyChanged { internal static ImageBackgroundManager Shared => field ??= new ImageBackgroundManager(); @@ -250,7 +253,7 @@ public int CurrentSelectedBackgroundIndex LauncherConfig.SetAndSaveConfigValue(CurrentSelectedBackgroundIndexKey, value); OnPropertyChanged(); - LoadImageAtIndex(value, CancellationToken.None); + LoadImageAtIndex(value, false, CancellationToken.None); } } @@ -586,7 +589,7 @@ ref ObservableCollectionExtension OnPropertyChanged(nameof(CurrentSelectedBackgroundIndex)); OnPropertyChanged(nameof(CurrentSelectedBackgroundContext)); OnPropertyChanged(nameof(CurrentBackgroundCount)); - LoadImageAtIndex(CurrentSelectedBackgroundIndex, token); + LoadImageAtIndex(CurrentSelectedBackgroundIndex, false, token); } #pragma warning restore CA1068 @@ -612,6 +615,12 @@ public void ResetContexts() Interlocked.Exchange(ref _previousContextHash, 0); ImageContextSources.Clear(); } + + void INotifyAllPropertyChanged.NotifyAllChanged() + { + OnPropertyChanged(nameof(GlobalFFmpegDecodingMode)); + OnPropertyChanged(nameof(AvailableFFmpegDecodingModes)); + } } [GeneratedBindableCustomProperty] diff --git a/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs b/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs index 7e135843d8..9f5c4c40ee 100644 --- a/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs +++ b/CollapseLauncher/Classes/Helper/FFmpegCodecInstaller.cs @@ -113,7 +113,7 @@ await extractDelegate(() => File.Open(packageFile, extractFolder, token); - string innerFfmpegDir = ImageBackgroundManager.FindFFmpegInstallFolder(extractFolder) ?? + string innerFfmpegDir = ImageBackgroundManager.FindFFmpegInstallFolder(extractFolder, ImageBackgroundManager.Shared.GlobalFFmpegLibraryNames) ?? throw new FileNotFoundException("Library files are not found!"); foreach (string dllFile in Directory.EnumerateFiles(innerFfmpegDir, "*.dll", SearchOption.TopDirectoryOnly)) diff --git a/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs b/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs index 2da51e509f..0ba637d9c1 100644 --- a/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs +++ b/CollapseLauncher/Classes/Helper/Image/Waifu2X.cs @@ -1,9 +1,7 @@ using Hi3Helper; using Hi3Helper.SentryHelper; -using Hi3Helper.Shared.Region; using System; using System.IO; -using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using static CollapseLauncher.Helper.Image.Waifu2X; @@ -21,41 +19,6 @@ internal static partial class Waifu2XPInvoke { private const string DllName = "Lib\\waifu2x-ncnn-vulkan.dll"; -#nullable enable - private static string? appDirPath; - private static string? waifu2xLibPath; -#nullable restore - - static Waifu2XPInvoke() - { - // Use custom Dll import resolver - NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver); - } - - private static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) - { - appDirPath ??= LauncherConfig.AppExecutableDir; - - if (DllImportSearchPath.AssemblyDirectory != searchPath - && DllImportSearchPath.ApplicationDirectory != searchPath) - { - return LoadInternal(libraryName, assembly, searchPath); - } - - waifu2xLibPath ??= Path.Combine(appDirPath, DllName); - return LoadInternal(waifu2xLibPath, assembly, null); - - } - - private static IntPtr LoadInternal(string path, Assembly assembly, DllImportSearchPath? searchPath) - { - bool isLoadSuccessful = NativeLibrary.TryLoad(path, assembly, null, out IntPtr pResult); - if (!isLoadSuccessful || pResult == IntPtr.Zero) - throw new FileLoadException($"Failed while loading library from this path: {path} with Search Path: {searchPath}\r\nMake sure that the library/.dll is a valid Win32 library and not corrupted!"); - - return pResult; - } - #region ncnn PInvokes [LibraryImport(DllName)] [DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/CollapsePInvoke.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/CollapsePInvoke.cs new file mode 100644 index 0000000000..9ae4bb817d --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/CollapsePInvoke.cs @@ -0,0 +1,85 @@ +using Hi3Helper; +using Hi3Helper.Shared.Region; +using System; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace CollapseLauncher.Helper.InternalPInvoke; + +internal static class CollapsePInvoke +{ + private const string LibraryExtension = ".dll"; + + internal static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + bool retryFirst = false; + string pathToLoad = libraryName; + LoadFirst: + if (LoadInternal(pathToLoad, assembly, searchPath) is var dllHandle && + dllHandle != nint.Zero) + { + return dllHandle; + } + + // Try append extension in case loading was failed due to it. Try to load it once again. + if (!pathToLoad.EndsWith(LibraryExtension, StringComparison.OrdinalIgnoreCase) && !retryFirst) + { + retryFirst = true; + pathToLoad = Path.Combine(Path.GetDirectoryName(libraryName), + Path.GetFileNameWithoutExtension(libraryName) + LibraryExtension); + goto LoadFirst; + } + + if (TryLoadFromDir(assembly, "Dir", libraryName, out dllHandle) || + TryLoadFromDir(assembly, LauncherConfig.AppExecutableDir, libraryName, out dllHandle)) + { + return dllHandle; + } + + throw new FileLoadException($"Cannot resolve library handle (.dll has failed to load) from this path: {libraryName} with Search Path: {searchPath}\r\nMake sure that the library/.dll is a valid Win32 library, has correct architecture and not corrupted!"); + } + + [SkipLocalsInit] + private static bool TryLoadFromDir(Assembly assembly, string dirPath, string libraryName, out nint handle) + { + Unsafe.SkipInit(out handle); + + bool retry = false; + string dllFileName = Path.GetFileName(libraryName); + string dllPathOnDir = Path.Combine(dirPath, dllFileName); + Load: + if (LoadInternal(dllPathOnDir, assembly, null) is var dllHandle && + dllHandle != nint.Zero) + { + handle = dllHandle; + return true; + } + + // If the second attempt by loading from specific dir also failed, + // Try append extension in case loading was failed due to it. Then try to load it once again :fingercrossed: + if (!dllPathOnDir.EndsWith(LibraryExtension, StringComparison.OrdinalIgnoreCase) && !retry) + { + retry = true; + dllPathOnDir = Path.Combine(Path.GetDirectoryName(dllPathOnDir), + Path.GetFileNameWithoutExtension(dllPathOnDir) + LibraryExtension); + goto Load; + } + + return false; + } + + private static nint LoadInternal(string path, Assembly assembly, DllImportSearchPath? searchPath = null) + { + bool isLoadSuccessful = NativeLibrary.TryLoad(path, assembly, searchPath, out nint pResult); + if (isLoadSuccessful) + { + Logger.LogWriteLine($"Successfully loaded dll: {path} using SearchPath: {searchPath} at handlePtr: {pResult}", + LogType.Debug, + true); + } + + return pResult; + } +} diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodec.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodec.cs new file mode 100644 index 0000000000..23e6d891ca --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodec.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +#nullable enable +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct AVCodec +{ + public byte* _Name; + public byte* _LongName; + public AVMediaType Type; + public AVCodecID Id; + public int Capabilities; + public byte MaxLowRes; + public AVRational* SupportedFramerates; + public AVPixelFormat* SupportedPixelFormats; + public int* SupportedSampleRates; + public AVSampleFormat* SupportedSampleFormats; + public void* PrivateClass; // AVClass + public void* Profiles; // AVProfiles + public byte* _WrapperName; + public void* ChannelLayouts; // AVChannelLayout + + public readonly string? Name => GetUtf8StringFrom(_Name); + public readonly string? LongName => GetUtf8StringFrom(_LongName); + public readonly string? WrapperName => GetUtf8StringFrom(_WrapperName); + + private static string? GetUtf8StringFrom(byte* ptr) + { + ReadOnlySpan bytes = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(ptr); + return Encoding.UTF8.GetString(bytes); + } +} diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodecID.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodecID.cs new file mode 100644 index 0000000000..daa582f78e --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVCodecID.cs @@ -0,0 +1,138 @@ +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; + +// https://ffmpeg.org/doxygen/7.0/group__lavc__core.html#gaadca229ad2c20e060a14fec08a5cc7ce +public enum AVCodecID +{ + AV_CODEC_ID_NONE, AV_CODEC_ID_MPEG1VIDEO, AV_CODEC_ID_MPEG2VIDEO, AV_CODEC_ID_H261, + AV_CODEC_ID_H263, AV_CODEC_ID_RV10, AV_CODEC_ID_RV20, AV_CODEC_ID_MJPEG, + AV_CODEC_ID_MJPEGB, AV_CODEC_ID_LJPEG, AV_CODEC_ID_SP5X, AV_CODEC_ID_JPEGLS, + AV_CODEC_ID_MPEG4, AV_CODEC_ID_RAWVIDEO, AV_CODEC_ID_MSMPEG4V1, AV_CODEC_ID_MSMPEG4V2, + AV_CODEC_ID_MSMPEG4V3, AV_CODEC_ID_WMV1, AV_CODEC_ID_WMV2, AV_CODEC_ID_H263P, + AV_CODEC_ID_H263I, AV_CODEC_ID_FLV1, AV_CODEC_ID_SVQ1, AV_CODEC_ID_SVQ3, + AV_CODEC_ID_DVVIDEO, AV_CODEC_ID_HUFFYUV, AV_CODEC_ID_CYUV, AV_CODEC_ID_H264, + AV_CODEC_ID_INDEO3, AV_CODEC_ID_VP3, AV_CODEC_ID_THEORA, AV_CODEC_ID_ASV1, + AV_CODEC_ID_ASV2, AV_CODEC_ID_FFV1, AV_CODEC_ID_4XM, AV_CODEC_ID_VCR1, + AV_CODEC_ID_CLJR, AV_CODEC_ID_MDEC, AV_CODEC_ID_ROQ, AV_CODEC_ID_INTERPLAY_VIDEO, + AV_CODEC_ID_XAN_WC3, AV_CODEC_ID_XAN_WC4, AV_CODEC_ID_RPZA, AV_CODEC_ID_CINEPAK, + AV_CODEC_ID_WS_VQA, AV_CODEC_ID_MSRLE, AV_CODEC_ID_MSVIDEO1, AV_CODEC_ID_IDCIN, + AV_CODEC_ID_8BPS, AV_CODEC_ID_SMC, AV_CODEC_ID_FLIC, AV_CODEC_ID_TRUEMOTION1, + AV_CODEC_ID_VMDVIDEO, AV_CODEC_ID_MSZH, AV_CODEC_ID_ZLIB, AV_CODEC_ID_QTRLE, + AV_CODEC_ID_TSCC, AV_CODEC_ID_ULTI, AV_CODEC_ID_QDRAW, AV_CODEC_ID_VIXL, + AV_CODEC_ID_QPEG, AV_CODEC_ID_PNG, AV_CODEC_ID_PPM, AV_CODEC_ID_PBM, + AV_CODEC_ID_PGM, AV_CODEC_ID_PGMYUV, AV_CODEC_ID_PAM, AV_CODEC_ID_FFVHUFF, + AV_CODEC_ID_RV30, AV_CODEC_ID_RV40, AV_CODEC_ID_VC1, AV_CODEC_ID_WMV3, + AV_CODEC_ID_LOCO, AV_CODEC_ID_WNV1, AV_CODEC_ID_AASC, AV_CODEC_ID_INDEO2, + AV_CODEC_ID_FRAPS, AV_CODEC_ID_TRUEMOTION2, AV_CODEC_ID_BMP, AV_CODEC_ID_CSCD, + AV_CODEC_ID_MMVIDEO, AV_CODEC_ID_ZMBV, AV_CODEC_ID_AVS, AV_CODEC_ID_SMACKVIDEO, + AV_CODEC_ID_NUV, AV_CODEC_ID_KMVC, AV_CODEC_ID_FLASHSV, AV_CODEC_ID_CAVS, + AV_CODEC_ID_JPEG2000, AV_CODEC_ID_VMNC, AV_CODEC_ID_VP5, AV_CODEC_ID_VP6, + AV_CODEC_ID_VP6F, AV_CODEC_ID_TARGA, AV_CODEC_ID_DSICINVIDEO, AV_CODEC_ID_TIERTEXSEQVIDEO, + AV_CODEC_ID_TIFF, AV_CODEC_ID_GIF, AV_CODEC_ID_DXA, AV_CODEC_ID_DNXHD, + AV_CODEC_ID_THP, AV_CODEC_ID_SGI, AV_CODEC_ID_C93, AV_CODEC_ID_BETHSOFTVID, + AV_CODEC_ID_PTX, AV_CODEC_ID_TXD, AV_CODEC_ID_VP6A, AV_CODEC_ID_AMV, + AV_CODEC_ID_VB, AV_CODEC_ID_PCX, AV_CODEC_ID_SUNRAST, AV_CODEC_ID_INDEO4, + AV_CODEC_ID_INDEO5, AV_CODEC_ID_MIMIC, AV_CODEC_ID_RL2, AV_CODEC_ID_ESCAPE124, + AV_CODEC_ID_DIRAC, AV_CODEC_ID_BFI, AV_CODEC_ID_CMV, AV_CODEC_ID_MOTIONPIXELS, + AV_CODEC_ID_TGV, AV_CODEC_ID_TGQ, AV_CODEC_ID_TQI, AV_CODEC_ID_AURA, + AV_CODEC_ID_AURA2, AV_CODEC_ID_V210X, AV_CODEC_ID_TMV, AV_CODEC_ID_V210, + AV_CODEC_ID_DPX, AV_CODEC_ID_MAD, AV_CODEC_ID_FRWU, AV_CODEC_ID_FLASHSV2, + AV_CODEC_ID_CDGRAPHICS, AV_CODEC_ID_R210, AV_CODEC_ID_ANM, AV_CODEC_ID_BINKVIDEO, + AV_CODEC_ID_IFF_ILBM, AV_CODEC_ID_KGV1, AV_CODEC_ID_YOP, AV_CODEC_ID_VP8, + AV_CODEC_ID_PICTOR, AV_CODEC_ID_ANSI, AV_CODEC_ID_A64_MULTI, AV_CODEC_ID_A64_MULTI5, + AV_CODEC_ID_R10K, AV_CODEC_ID_MXPEG, AV_CODEC_ID_LAGARITH, AV_CODEC_ID_PRORES, + AV_CODEC_ID_JV, AV_CODEC_ID_DFA, AV_CODEC_ID_WMV3IMAGE, AV_CODEC_ID_VC1IMAGE, + AV_CODEC_ID_UTVIDEO, AV_CODEC_ID_BMV_VIDEO, AV_CODEC_ID_VBLE, AV_CODEC_ID_DXTORY, + AV_CODEC_ID_V410, AV_CODEC_ID_XWD, AV_CODEC_ID_CDXL, AV_CODEC_ID_XBM, + AV_CODEC_ID_ZEROCODEC, AV_CODEC_ID_MSS1, AV_CODEC_ID_MSA1, AV_CODEC_ID_TSCC2, + AV_CODEC_ID_MTS2, AV_CODEC_ID_CLLC, AV_CODEC_ID_MSS2, AV_CODEC_ID_VP9, + AV_CODEC_ID_AIC, AV_CODEC_ID_ESCAPE130, AV_CODEC_ID_G2M, AV_CODEC_ID_WEBP, + AV_CODEC_ID_HNM4_VIDEO, AV_CODEC_ID_HEVC, AV_CODEC_ID_FIC, AV_CODEC_ID_ALIAS_PIX, + AV_CODEC_ID_BRENDER_PIX, AV_CODEC_ID_PAF_VIDEO, AV_CODEC_ID_EXR, AV_CODEC_ID_VP7, + AV_CODEC_ID_SANM, AV_CODEC_ID_SGIRLE, AV_CODEC_ID_MVC1, AV_CODEC_ID_MVC2, + AV_CODEC_ID_HQX, AV_CODEC_ID_TDSC, AV_CODEC_ID_HQ_HQA, AV_CODEC_ID_HAP, + AV_CODEC_ID_DDS, AV_CODEC_ID_DXV, AV_CODEC_ID_SCREENPRESSO, AV_CODEC_ID_RSCC, + AV_CODEC_ID_AVS2, AV_CODEC_ID_PGX, AV_CODEC_ID_AVS3, AV_CODEC_ID_MSP2, + AV_CODEC_ID_VVC, AV_CODEC_ID_Y41P, AV_CODEC_ID_AVRP, AV_CODEC_ID_012V, + AV_CODEC_ID_AVUI, AV_CODEC_ID_TARGA_Y216, AV_CODEC_ID_V308, AV_CODEC_ID_V408, + AV_CODEC_ID_YUV4, AV_CODEC_ID_AVRN, AV_CODEC_ID_CPIA, AV_CODEC_ID_XFACE, + AV_CODEC_ID_SNOW, AV_CODEC_ID_SMVJPEG, AV_CODEC_ID_APNG, AV_CODEC_ID_DAALA, + AV_CODEC_ID_CFHD, AV_CODEC_ID_TRUEMOTION2RT, AV_CODEC_ID_M101, AV_CODEC_ID_MAGICYUV, + AV_CODEC_ID_SHEERVIDEO, AV_CODEC_ID_YLC, AV_CODEC_ID_PSD, AV_CODEC_ID_PIXLET, + AV_CODEC_ID_SPEEDHQ, AV_CODEC_ID_FMVC, AV_CODEC_ID_SCPR, AV_CODEC_ID_CLEARVIDEO, + AV_CODEC_ID_XPM, AV_CODEC_ID_AV1, AV_CODEC_ID_BITPACKED, AV_CODEC_ID_MSCC, + AV_CODEC_ID_SRGC, AV_CODEC_ID_SVG, AV_CODEC_ID_GDV, AV_CODEC_ID_FITS, + AV_CODEC_ID_IMM4, AV_CODEC_ID_PROSUMER, AV_CODEC_ID_MWSC, AV_CODEC_ID_WCMV, + AV_CODEC_ID_RASC, AV_CODEC_ID_HYMT, AV_CODEC_ID_ARBC, AV_CODEC_ID_AGM, + AV_CODEC_ID_LSCR, AV_CODEC_ID_VP4, AV_CODEC_ID_IMM5, AV_CODEC_ID_MVDV, + AV_CODEC_ID_MVHA, AV_CODEC_ID_CDTOONS, AV_CODEC_ID_MV30, AV_CODEC_ID_NOTCHLC, + AV_CODEC_ID_PFM, AV_CODEC_ID_MOBICLIP, AV_CODEC_ID_PHOTOCD, AV_CODEC_ID_IPU, + AV_CODEC_ID_ARGO, AV_CODEC_ID_CRI, AV_CODEC_ID_SIMBIOSIS_IMX, AV_CODEC_ID_SGA_VIDEO, + AV_CODEC_ID_GEM, AV_CODEC_ID_VBN, AV_CODEC_ID_JPEGXL, AV_CODEC_ID_QOI, + AV_CODEC_ID_PHM, AV_CODEC_ID_RADIANCE_HDR, AV_CODEC_ID_WBMP, AV_CODEC_ID_MEDIA100, + AV_CODEC_ID_VQC, AV_CODEC_ID_PDV, AV_CODEC_ID_EVC, AV_CODEC_ID_RTV1, + AV_CODEC_ID_VMIX, AV_CODEC_ID_LEAD, AV_CODEC_ID_FIRST_AUDIO = 0x10000, AV_CODEC_ID_PCM_S16LE = 0x10000, + AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_U16LE, AV_CODEC_ID_PCM_U16BE, AV_CODEC_ID_PCM_S8, + AV_CODEC_ID_PCM_U8, AV_CODEC_ID_PCM_MULAW, AV_CODEC_ID_PCM_ALAW, AV_CODEC_ID_PCM_S32LE, + AV_CODEC_ID_PCM_S32BE, AV_CODEC_ID_PCM_U32LE, AV_CODEC_ID_PCM_U32BE, AV_CODEC_ID_PCM_S24LE, + AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_U24LE, AV_CODEC_ID_PCM_U24BE, AV_CODEC_ID_PCM_S24DAUD, + AV_CODEC_ID_PCM_ZORK, AV_CODEC_ID_PCM_S16LE_PLANAR, AV_CODEC_ID_PCM_DVD, AV_CODEC_ID_PCM_F32BE, + AV_CODEC_ID_PCM_F32LE, AV_CODEC_ID_PCM_F64BE, AV_CODEC_ID_PCM_F64LE, AV_CODEC_ID_PCM_BLURAY, + AV_CODEC_ID_PCM_LXF, AV_CODEC_ID_S302M, AV_CODEC_ID_PCM_S8_PLANAR, AV_CODEC_ID_PCM_S24LE_PLANAR, + AV_CODEC_ID_PCM_S32LE_PLANAR, AV_CODEC_ID_PCM_S16BE_PLANAR, AV_CODEC_ID_PCM_S64LE, AV_CODEC_ID_PCM_S64BE, + AV_CODEC_ID_PCM_F16LE, AV_CODEC_ID_PCM_F24LE, AV_CODEC_ID_PCM_VIDC, AV_CODEC_ID_PCM_SGA, + AV_CODEC_ID_ADPCM_IMA_QT = 0x11000, AV_CODEC_ID_ADPCM_IMA_WAV, AV_CODEC_ID_ADPCM_IMA_DK3, AV_CODEC_ID_ADPCM_IMA_DK4, + AV_CODEC_ID_ADPCM_IMA_WS, AV_CODEC_ID_ADPCM_IMA_SMJPEG, AV_CODEC_ID_ADPCM_MS, AV_CODEC_ID_ADPCM_4XM, + AV_CODEC_ID_ADPCM_XA, AV_CODEC_ID_ADPCM_ADX, AV_CODEC_ID_ADPCM_EA, AV_CODEC_ID_ADPCM_G726, + AV_CODEC_ID_ADPCM_CT, AV_CODEC_ID_ADPCM_SWF, AV_CODEC_ID_ADPCM_YAMAHA, AV_CODEC_ID_ADPCM_SBPRO_4, + AV_CODEC_ID_ADPCM_SBPRO_3, AV_CODEC_ID_ADPCM_SBPRO_2, AV_CODEC_ID_ADPCM_THP, AV_CODEC_ID_ADPCM_IMA_AMV, + AV_CODEC_ID_ADPCM_EA_R1, AV_CODEC_ID_ADPCM_EA_R3, AV_CODEC_ID_ADPCM_EA_R2, AV_CODEC_ID_ADPCM_IMA_EA_SEAD, + AV_CODEC_ID_ADPCM_IMA_EA_EACS, AV_CODEC_ID_ADPCM_EA_XAS, AV_CODEC_ID_ADPCM_EA_MAXIS_XA, AV_CODEC_ID_ADPCM_IMA_ISS, + AV_CODEC_ID_ADPCM_G722, AV_CODEC_ID_ADPCM_IMA_APC, AV_CODEC_ID_ADPCM_VIMA, AV_CODEC_ID_ADPCM_AFC, + AV_CODEC_ID_ADPCM_IMA_OKI, AV_CODEC_ID_ADPCM_DTK, AV_CODEC_ID_ADPCM_IMA_RAD, AV_CODEC_ID_ADPCM_G726LE, + AV_CODEC_ID_ADPCM_THP_LE, AV_CODEC_ID_ADPCM_PSX, AV_CODEC_ID_ADPCM_AICA, AV_CODEC_ID_ADPCM_IMA_DAT4, + AV_CODEC_ID_ADPCM_MTAF, AV_CODEC_ID_ADPCM_AGM, AV_CODEC_ID_ADPCM_ARGO, AV_CODEC_ID_ADPCM_IMA_SSI, + AV_CODEC_ID_ADPCM_ZORK, AV_CODEC_ID_ADPCM_IMA_APM, AV_CODEC_ID_ADPCM_IMA_ALP, AV_CODEC_ID_ADPCM_IMA_MTF, + AV_CODEC_ID_ADPCM_IMA_CUNNING, AV_CODEC_ID_ADPCM_IMA_MOFLEX, AV_CODEC_ID_ADPCM_IMA_ACORN, AV_CODEC_ID_ADPCM_XMD, + AV_CODEC_ID_AMR_NB = 0x12000, AV_CODEC_ID_AMR_WB, AV_CODEC_ID_RA_144 = 0x13000, AV_CODEC_ID_RA_288, + AV_CODEC_ID_ROQ_DPCM = 0x14000, AV_CODEC_ID_INTERPLAY_DPCM, AV_CODEC_ID_XAN_DPCM, AV_CODEC_ID_SOL_DPCM, + AV_CODEC_ID_SDX2_DPCM, AV_CODEC_ID_GREMLIN_DPCM, AV_CODEC_ID_DERF_DPCM, AV_CODEC_ID_WADY_DPCM, + AV_CODEC_ID_CBD2_DPCM, AV_CODEC_ID_MP2 = 0x15000, AV_CODEC_ID_MP3, AV_CODEC_ID_AAC, + AV_CODEC_ID_AC3, AV_CODEC_ID_DTS, AV_CODEC_ID_VORBIS, AV_CODEC_ID_DVAUDIO, + AV_CODEC_ID_WMAV1, AV_CODEC_ID_WMAV2, AV_CODEC_ID_MACE3, AV_CODEC_ID_MACE6, + AV_CODEC_ID_VMDAUDIO, AV_CODEC_ID_FLAC, AV_CODEC_ID_MP3ADU, AV_CODEC_ID_MP3ON4, + AV_CODEC_ID_SHORTEN, AV_CODEC_ID_ALAC, AV_CODEC_ID_WESTWOOD_SND1, AV_CODEC_ID_GSM, + AV_CODEC_ID_QDM2, AV_CODEC_ID_COOK, AV_CODEC_ID_TRUESPEECH, AV_CODEC_ID_TTA, + AV_CODEC_ID_SMACKAUDIO, AV_CODEC_ID_QCELP, AV_CODEC_ID_WAVPACK, AV_CODEC_ID_DSICINAUDIO, + AV_CODEC_ID_IMC, AV_CODEC_ID_MUSEPACK7, AV_CODEC_ID_MLP, AV_CODEC_ID_GSM_MS, + AV_CODEC_ID_ATRAC3, AV_CODEC_ID_APE, AV_CODEC_ID_NELLYMOSER, AV_CODEC_ID_MUSEPACK8, + AV_CODEC_ID_SPEEX, AV_CODEC_ID_WMAVOICE, AV_CODEC_ID_WMAPRO, AV_CODEC_ID_WMALOSSLESS, + AV_CODEC_ID_ATRAC3P, AV_CODEC_ID_EAC3, AV_CODEC_ID_SIPR, AV_CODEC_ID_MP1, + AV_CODEC_ID_TWINVQ, AV_CODEC_ID_TRUEHD, AV_CODEC_ID_MP4ALS, AV_CODEC_ID_ATRAC1, + AV_CODEC_ID_BINKAUDIO_RDFT, AV_CODEC_ID_BINKAUDIO_DCT, AV_CODEC_ID_AAC_LATM, AV_CODEC_ID_QDMC, + AV_CODEC_ID_CELT, AV_CODEC_ID_G723_1, AV_CODEC_ID_G729, AV_CODEC_ID_8SVX_EXP, + AV_CODEC_ID_8SVX_FIB, AV_CODEC_ID_BMV_AUDIO, AV_CODEC_ID_RALF, AV_CODEC_ID_IAC, + AV_CODEC_ID_ILBC, AV_CODEC_ID_OPUS, AV_CODEC_ID_COMFORT_NOISE, AV_CODEC_ID_TAK, + AV_CODEC_ID_METASOUND, AV_CODEC_ID_PAF_AUDIO, AV_CODEC_ID_ON2AVC, AV_CODEC_ID_DSS_SP, + AV_CODEC_ID_CODEC2, AV_CODEC_ID_FFWAVESYNTH, AV_CODEC_ID_SONIC, AV_CODEC_ID_SONIC_LS, + AV_CODEC_ID_EVRC, AV_CODEC_ID_SMV, AV_CODEC_ID_DSD_LSBF, AV_CODEC_ID_DSD_MSBF, + AV_CODEC_ID_DSD_LSBF_PLANAR, AV_CODEC_ID_DSD_MSBF_PLANAR, AV_CODEC_ID_4GV, AV_CODEC_ID_INTERPLAY_ACM, + AV_CODEC_ID_XMA1, AV_CODEC_ID_XMA2, AV_CODEC_ID_DST, AV_CODEC_ID_ATRAC3AL, + AV_CODEC_ID_ATRAC3PAL, AV_CODEC_ID_DOLBY_E, AV_CODEC_ID_APTX, AV_CODEC_ID_APTX_HD, + AV_CODEC_ID_SBC, AV_CODEC_ID_ATRAC9, AV_CODEC_ID_HCOM, AV_CODEC_ID_ACELP_KELVIN, + AV_CODEC_ID_MPEGH_3D_AUDIO, AV_CODEC_ID_SIREN, AV_CODEC_ID_HCA, AV_CODEC_ID_FASTAUDIO, + AV_CODEC_ID_MSNSIREN, AV_CODEC_ID_DFPWM, AV_CODEC_ID_BONK, AV_CODEC_ID_MISC4, + AV_CODEC_ID_APAC, AV_CODEC_ID_FTR, AV_CODEC_ID_WAVARC, AV_CODEC_ID_RKA, + AV_CODEC_ID_AC4, AV_CODEC_ID_OSQ, AV_CODEC_ID_QOA, AV_CODEC_ID_FIRST_SUBTITLE = 0x17000, + AV_CODEC_ID_DVD_SUBTITLE = 0x17000, AV_CODEC_ID_DVB_SUBTITLE, AV_CODEC_ID_TEXT, AV_CODEC_ID_XSUB, + AV_CODEC_ID_SSA, AV_CODEC_ID_MOV_TEXT, AV_CODEC_ID_HDMV_PGS_SUBTITLE, AV_CODEC_ID_DVB_TELETEXT, + AV_CODEC_ID_SRT, AV_CODEC_ID_MICRODVD, AV_CODEC_ID_EIA_608, AV_CODEC_ID_JACOSUB, + AV_CODEC_ID_SAMI, AV_CODEC_ID_REALTEXT, AV_CODEC_ID_STL, AV_CODEC_ID_SUBVIEWER1, + AV_CODEC_ID_SUBVIEWER, AV_CODEC_ID_SUBRIP, AV_CODEC_ID_WEBVTT, AV_CODEC_ID_MPL2, + AV_CODEC_ID_VPLAYER, AV_CODEC_ID_PJS, AV_CODEC_ID_ASS, AV_CODEC_ID_HDMV_TEXT_SUBTITLE, + AV_CODEC_ID_TTML, AV_CODEC_ID_ARIB_CAPTION, AV_CODEC_ID_FIRST_UNKNOWN = 0x18000, AV_CODEC_ID_TTF = 0x18000, + AV_CODEC_ID_SCTE_35, AV_CODEC_ID_EPG, AV_CODEC_ID_BINTEXT, AV_CODEC_ID_XBIN, + AV_CODEC_ID_IDF, AV_CODEC_ID_OTF, AV_CODEC_ID_SMPTE_KLV, AV_CODEC_ID_DVD_NAV, + AV_CODEC_ID_TIMED_ID3, AV_CODEC_ID_BIN_DATA, AV_CODEC_ID_SMPTE_2038, AV_CODEC_ID_PROBE = 0x19000, + AV_CODEC_ID_MPEG2TS = 0x20000, AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, AV_CODEC_ID_FFMETADATA = 0x21000, AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, + AV_CODEC_ID_VNULL, AV_CODEC_ID_ANULL +} diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVMediaType.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVMediaType.cs new file mode 100644 index 0000000000..56516236df --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVMediaType.cs @@ -0,0 +1,13 @@ +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; + +// https://ffmpeg.org/doxygen/7.0/group__lavu__misc.html#ga9a84bba4713dfced21a1a56163be1f48 +public enum AVMediaType +{ + AVMEDIA_TYPE_UNKNOWN = -1, + AVMEDIA_TYPE_VIDEO, + AVMEDIA_TYPE_AUDIO, + AVMEDIA_TYPE_DATA, + AVMEDIA_TYPE_SUBTITLE, + AVMEDIA_TYPE_ATTACHMENT, + AVMEDIA_TYPE_NB +} diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVPixelFormat.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVPixelFormat.cs new file mode 100644 index 0000000000..396bcac7fa --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVPixelFormat.cs @@ -0,0 +1,64 @@ +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; + +public enum AVPixelFormat +{ + AV_PIX_FMT_NONE = -1, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_RGB24, + AV_PIX_FMT_BGR24, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV410P, + AV_PIX_FMT_YUV411P, AV_PIX_FMT_GRAY8, AV_PIX_FMT_MONOWHITE, AV_PIX_FMT_MONOBLACK, + AV_PIX_FMT_PAL8, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, + AV_PIX_FMT_UYVY422, AV_PIX_FMT_UYYVYY411, AV_PIX_FMT_BGR8, AV_PIX_FMT_BGR4, + AV_PIX_FMT_BGR4_BYTE, AV_PIX_FMT_RGB8, AV_PIX_FMT_RGB4, AV_PIX_FMT_RGB4_BYTE, + AV_PIX_FMT_NV12, AV_PIX_FMT_NV21, AV_PIX_FMT_ARGB, AV_PIX_FMT_RGBA, + AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA, AV_PIX_FMT_GRAY16BE, AV_PIX_FMT_GRAY16LE, + AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_RGB48BE, + AV_PIX_FMT_RGB48LE, AV_PIX_FMT_RGB565BE, AV_PIX_FMT_RGB565LE, AV_PIX_FMT_RGB555BE, + AV_PIX_FMT_RGB555LE, AV_PIX_FMT_BGR565BE, AV_PIX_FMT_BGR565LE, AV_PIX_FMT_BGR555BE, + AV_PIX_FMT_BGR555LE, AV_PIX_FMT_VAAPI, AV_PIX_FMT_YUV420P16LE, AV_PIX_FMT_YUV420P16BE, + AV_PIX_FMT_YUV422P16LE, AV_PIX_FMT_YUV422P16BE, AV_PIX_FMT_YUV444P16LE, AV_PIX_FMT_YUV444P16BE, + AV_PIX_FMT_DXVA2_VLD, AV_PIX_FMT_RGB444LE, AV_PIX_FMT_RGB444BE, AV_PIX_FMT_BGR444LE, + AV_PIX_FMT_BGR444BE, AV_PIX_FMT_YA8, AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, AV_PIX_FMT_GRAY8A = AV_PIX_FMT_YA8, + AV_PIX_FMT_BGR48BE, AV_PIX_FMT_BGR48LE, AV_PIX_FMT_YUV420P9BE, AV_PIX_FMT_YUV420P9LE, + AV_PIX_FMT_YUV420P10BE, AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV422P10BE, AV_PIX_FMT_YUV422P10LE, + AV_PIX_FMT_YUV444P9BE, AV_PIX_FMT_YUV444P9LE, AV_PIX_FMT_YUV444P10BE, AV_PIX_FMT_YUV444P10LE, + AV_PIX_FMT_YUV422P9BE, AV_PIX_FMT_YUV422P9LE, AV_PIX_FMT_GBRP, AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, + AV_PIX_FMT_GBRP9BE, AV_PIX_FMT_GBRP9LE, AV_PIX_FMT_GBRP10BE, AV_PIX_FMT_GBRP10LE, + AV_PIX_FMT_GBRP16BE, AV_PIX_FMT_GBRP16LE, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P, + AV_PIX_FMT_YUVA420P9BE, AV_PIX_FMT_YUVA420P9LE, AV_PIX_FMT_YUVA422P9BE, AV_PIX_FMT_YUVA422P9LE, + AV_PIX_FMT_YUVA444P9BE, AV_PIX_FMT_YUVA444P9LE, AV_PIX_FMT_YUVA420P10BE, AV_PIX_FMT_YUVA420P10LE, + AV_PIX_FMT_YUVA422P10BE, AV_PIX_FMT_YUVA422P10LE, AV_PIX_FMT_YUVA444P10BE, AV_PIX_FMT_YUVA444P10LE, + AV_PIX_FMT_YUVA420P16BE, AV_PIX_FMT_YUVA420P16LE, AV_PIX_FMT_YUVA422P16BE, AV_PIX_FMT_YUVA422P16LE, + AV_PIX_FMT_YUVA444P16BE, AV_PIX_FMT_YUVA444P16LE, AV_PIX_FMT_VDPAU, AV_PIX_FMT_XYZ12LE, + AV_PIX_FMT_XYZ12BE, AV_PIX_FMT_NV16, AV_PIX_FMT_NV20LE, AV_PIX_FMT_NV20BE, + AV_PIX_FMT_RGBA64BE, AV_PIX_FMT_RGBA64LE, AV_PIX_FMT_BGRA64BE, AV_PIX_FMT_BGRA64LE, + AV_PIX_FMT_YVYU422, AV_PIX_FMT_YA16BE, AV_PIX_FMT_YA16LE, AV_PIX_FMT_GBRAP, + AV_PIX_FMT_GBRAP16BE, AV_PIX_FMT_GBRAP16LE, AV_PIX_FMT_QSV, AV_PIX_FMT_MMAL, + AV_PIX_FMT_D3D11VA_VLD, AV_PIX_FMT_CUDA, AV_PIX_FMT_0RGB, AV_PIX_FMT_RGB0, + AV_PIX_FMT_0BGR, AV_PIX_FMT_BGR0, AV_PIX_FMT_YUV420P12BE, AV_PIX_FMT_YUV420P12LE, + AV_PIX_FMT_YUV420P14BE, AV_PIX_FMT_YUV420P14LE, AV_PIX_FMT_YUV422P12BE, AV_PIX_FMT_YUV422P12LE, + AV_PIX_FMT_YUV422P14BE, AV_PIX_FMT_YUV422P14LE, AV_PIX_FMT_YUV444P12BE, AV_PIX_FMT_YUV444P12LE, + AV_PIX_FMT_YUV444P14BE, AV_PIX_FMT_YUV444P14LE, AV_PIX_FMT_GBRP12BE, AV_PIX_FMT_GBRP12LE, + AV_PIX_FMT_GBRP14BE, AV_PIX_FMT_GBRP14LE, AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_BAYER_BGGR8, + AV_PIX_FMT_BAYER_RGGB8, AV_PIX_FMT_BAYER_GBRG8, AV_PIX_FMT_BAYER_GRBG8, AV_PIX_FMT_BAYER_BGGR16LE, + AV_PIX_FMT_BAYER_BGGR16BE, AV_PIX_FMT_BAYER_RGGB16LE, AV_PIX_FMT_BAYER_RGGB16BE, AV_PIX_FMT_BAYER_GBRG16LE, + AV_PIX_FMT_BAYER_GBRG16BE, AV_PIX_FMT_BAYER_GRBG16LE, AV_PIX_FMT_BAYER_GRBG16BE, AV_PIX_FMT_YUV440P10LE, + AV_PIX_FMT_YUV440P10BE, AV_PIX_FMT_YUV440P12LE, AV_PIX_FMT_YUV440P12BE, AV_PIX_FMT_AYUV64LE, + AV_PIX_FMT_AYUV64BE, AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_P010LE, AV_PIX_FMT_P010BE, + AV_PIX_FMT_GBRAP12BE, AV_PIX_FMT_GBRAP12LE, AV_PIX_FMT_GBRAP10BE, AV_PIX_FMT_GBRAP10LE, + AV_PIX_FMT_MEDIACODEC, AV_PIX_FMT_GRAY12BE, AV_PIX_FMT_GRAY12LE, AV_PIX_FMT_GRAY10BE, + AV_PIX_FMT_GRAY10LE, AV_PIX_FMT_P016LE, AV_PIX_FMT_P016BE, AV_PIX_FMT_D3D11, + AV_PIX_FMT_GRAY9BE, AV_PIX_FMT_GRAY9LE, AV_PIX_FMT_GBRPF32BE, AV_PIX_FMT_GBRPF32LE, + AV_PIX_FMT_GBRAPF32BE, AV_PIX_FMT_GBRAPF32LE, AV_PIX_FMT_DRM_PRIME, AV_PIX_FMT_OPENCL, + AV_PIX_FMT_GRAY14BE, AV_PIX_FMT_GRAY14LE, AV_PIX_FMT_GRAYF32BE, AV_PIX_FMT_GRAYF32LE, + AV_PIX_FMT_YUVA422P12BE, AV_PIX_FMT_YUVA422P12LE, AV_PIX_FMT_YUVA444P12BE, AV_PIX_FMT_YUVA444P12LE, + AV_PIX_FMT_NV24, AV_PIX_FMT_NV42, AV_PIX_FMT_VULKAN, AV_PIX_FMT_Y210BE, + AV_PIX_FMT_Y210LE, AV_PIX_FMT_X2RGB10LE, AV_PIX_FMT_X2RGB10BE, AV_PIX_FMT_X2BGR10LE, + AV_PIX_FMT_X2BGR10BE, AV_PIX_FMT_P210BE, AV_PIX_FMT_P210LE, AV_PIX_FMT_P410BE, + AV_PIX_FMT_P410LE, AV_PIX_FMT_P216BE, AV_PIX_FMT_P216LE, AV_PIX_FMT_P416BE, + AV_PIX_FMT_P416LE, AV_PIX_FMT_VUYA, AV_PIX_FMT_RGBAF16BE, AV_PIX_FMT_RGBAF16LE, + AV_PIX_FMT_VUYX, AV_PIX_FMT_P012LE, AV_PIX_FMT_P012BE, AV_PIX_FMT_Y212BE, + AV_PIX_FMT_Y212LE, AV_PIX_FMT_XV30BE, AV_PIX_FMT_XV30LE, AV_PIX_FMT_XV36BE, + AV_PIX_FMT_XV36LE, AV_PIX_FMT_RGBF32BE, AV_PIX_FMT_RGBF32LE, AV_PIX_FMT_RGBAF32BE, + AV_PIX_FMT_RGBAF32LE, AV_PIX_FMT_P212BE, AV_PIX_FMT_P212LE, AV_PIX_FMT_P412BE, + AV_PIX_FMT_P412LE, AV_PIX_FMT_GBRAP14BE, AV_PIX_FMT_GBRAP14LE, AV_PIX_FMT_D3D12, + AV_PIX_FMT_NB +} diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVRational.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVRational.cs new file mode 100644 index 0000000000..e6548b292d --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVRational.cs @@ -0,0 +1,8 @@ +using System; +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; + +public struct AVRational +{ + public int Numerator; + public int Denominator; +} diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVSampleFormat.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVSampleFormat.cs new file mode 100644 index 0000000000..b6ac7dcf65 --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpeg/AVSampleFormat.cs @@ -0,0 +1,9 @@ +namespace CollapseLauncher.Helper.InternalPInvoke.FFmpeg; + +public enum AVSampleFormat +{ + AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S32, + AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_U8P, AV_SAMPLE_FMT_S16P, + AV_SAMPLE_FMT_S32P, AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_DBLP, AV_SAMPLE_FMT_S64, + AV_SAMPLE_FMT_S64P, AV_SAMPLE_FMT_NB +} diff --git a/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpegPInvoke.cs b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpegPInvoke.cs new file mode 100644 index 0000000000..53fb71e888 --- /dev/null +++ b/CollapseLauncher/Classes/Helper/InternalPInvoke/FFmpegPInvoke.cs @@ -0,0 +1,50 @@ +using CollapseLauncher.Helper.InternalPInvoke.FFmpeg; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#nullable enable +namespace CollapseLauncher.Helper.FFmpegPInvoke; + +public static unsafe partial class FFmpegPInvoke +{ + public static Dictionary FFmpegVersionLibNames = new Dictionary() + { + { 70, new FFmpegLibraryNames("avcodec-61.dll", "avdevice-61.dll", "avfilter-10.dll", "avformat-61.dll", "avutil-59.dll", "swresample-5.dll", "swscale-8.dll", "postproc-58.dll") }, + { 80, new FFmpegLibraryNames("avcodec-62.dll", "avdevice-62.dll", "avfilter-11.dll", "avformat-62.dll", "avutil-60.dll", "swresample-6.dll", "swscale-9.dll", "postproc-59.dll") } + }; + + [LibraryImport("avcodec")] + public static partial AVCodec* av_codec_iterate(ref nint opaque); + + public static IEnumerable EnumerateSupportedCodecs() + => EnumerateSupportedCodecs([]); + + public static IEnumerable EnumerateSupportedCodecs(params AVMediaType[] codecTypes) + { + nint opaque = nint.Zero; + scoped ref AVCodec codecPtr = ref Unsafe.NullRef(); + + Iterate: + codecPtr = ref IterateCodec(ref opaque); + if (Unsafe.IsNullRef(in codecPtr)) + { + yield break; + } + + if (codecTypes.Length != 0 && + !codecTypes.Contains(codecPtr.Type)) + { + goto Iterate; + } + + yield return codecPtr; + goto Iterate; + } + + private static ref AVCodec IterateCodec(ref nint opaque) + => ref Unsafe.AsRef(av_codec_iterate(ref opaque)); + + public record struct FFmpegLibraryNames(string Codec, string Device, string Filter, string Format, string Util, string Resample, string Scale, string PostProc); +} diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherGameResourcePackageApi.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherGameResourcePackageApi.cs index 93f4c79ba5..f256dc9552 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherGameResourcePackageApi.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HypLauncherGameResourcePackageApi.cs @@ -65,7 +65,7 @@ public class HypPackageData public long PackageDecompressSize { get; init; } [JsonPropertyName("md5")] - [JsonConverter(typeof(HexStringToArrayJsonConverter))] + [JsonConverter(typeof(BytesStringToArrayJsonConverter))] public byte[]? PackageMD5Hash { get; init; } [JsonIgnore] diff --git a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs index a4c4c0fb27..873039be1d 100644 --- a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs +++ b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.Sophon.cs @@ -541,6 +541,7 @@ private async Task ConfirmAdditionalInstallDataPackageFiles( List otherManifestIdentity = installManifestFirst.OtherSophonBuildData!.ManifestIdentityList .Where(x => !commonPackageMatchingFields.Contains(x.MatchingField, StringComparer.OrdinalIgnoreCase)) + .Where(x => x.MatchingField == null || !IsVoicePackMatchingField(x.MatchingField)) .ToList(); if (otherManifestIdentity.Count == 0) @@ -654,6 +655,7 @@ protected virtual async Task StartPackageUpdateSophon(GameInstallStateEnum gameS // Set the progress bar to indetermined IsSophonInUpdateMode = !isPreloadMode; + IsSophonInPreloadVerifyMode = isPreloadMode && _isSophonPreloadCompleted; Status.IsIncludePerFileIndicator = !isPreloadMode; Status.IsProgressPerFileIndetermined = true; Status.IsProgressAllIndetermined = true; @@ -868,7 +870,7 @@ await x.GetDownloadedPreloadSize( Status.IsProgressAllIndetermined = false; Status.ActivityStatus = $"{(IsSophonInUpdateMode && !isPreloadMode ? Locale.Current.Lang?._Misc?.UpdatingAndApplying - : Locale.Current.Lang?._Misc?.Downloading)}: {string.Format(Locale.Current.Lang?._Misc?.PerFromTo ?? "", ProgressAllCountCurrent, + : isSophonPreloadCompleted ? Locale.Current.Lang?._Misc?.Verifying : Locale.Current.Lang?._Misc?.Downloading)}: {string.Format(Locale.Current.Lang?._Misc?.PerFromTo ?? "", ProgressAllCountCurrent, ProgressAllCountTotal)}"; UpdateStatus(); @@ -1086,6 +1088,7 @@ private async Task TryGetAdditionalPackageForSophonDiff(HttpClient manifestPair.OtherSophonBuildData!.ManifestIdentityList .Where(x => !string.IsNullOrEmpty(x.MatchingField) && !CommonSophonPackageMatchingFields.Contains(x.MatchingField, StringComparer.OrdinalIgnoreCase)) .Select(x => x.MatchingField!) + .Where(x => !IsVoicePackMatchingField(x)) .WhereMatchPattern(x => x, true, excludeMatchingFieldsPattern) .ToList(); @@ -1266,6 +1269,23 @@ protected virtual int SophonGetHttpHandler() return Math.Clamp(n, 4, 128); } + private bool IsVoicePackMatchingField(string matchingField) + { + // Check regular locale code: zh-cn, en-us, etc. + if (IsValidLocaleCode(matchingField)) + return true; + + // Also check for "mini-xx-xx" format (e.g., mini-zh-cn, mini-en-us) + const string miniPrefix = "mini-"; + if (matchingField.Length > miniPrefix.Length && + matchingField.StartsWith(miniPrefix, StringComparison.OrdinalIgnoreCase)) + { + return IsValidLocaleCode(matchingField.AsSpan(miniPrefix.Length)); + } + + return false; + } + #endregion #region Sophon Audio/Voice-Packs Locale Methods diff --git a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs index aa69aa8972..1004d8f8c2 100644 --- a/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs +++ b/CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs @@ -160,6 +160,7 @@ protected virtual async Task ConfirmAdditionalPatchDataPackageFiles(SophonChunkM List otherManifestIdentity = patchManifest.OtherSophonPatchData!.ManifestIdentityList .Where(x => !CommonSophonPackageMatchingFields.Contains(x.MatchingField, StringComparer.OrdinalIgnoreCase)) + .Where(x => x.MatchingField == null || !IsVoicePackMatchingField(x.MatchingField)) .ToList(); if (otherManifestIdentity.Count == 0) @@ -672,7 +673,7 @@ void UpdateCurrentDownloadStatus() string perFromToLocale = string.Format(Locale.Current.Lang?._Misc?.PerFromTo ?? "", ProgressAllCountCurrent, ProgressAllCountTotal); - Status.ActivityStatus = $"{Locale.Current.Lang?._Misc?.Downloading}: {perFromToLocale}"; + Status.ActivityStatus = $"{(IsSophonInPreloadVerifyMode ? Locale.Current.Lang?._Misc?.Verifying : Locale.Current.Lang?._Misc?.Downloading)}: {perFromToLocale}"; UpdateStatus(); } diff --git a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs index cf37ec4c4f..2826425581 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs @@ -169,6 +169,7 @@ public ObservableCollection AssetEntry protected nint SpeedLimiterServiceContext { get; } = SpeedLimiterService.CreateServiceContext(); public bool IsSophonInUpdateMode { get; protected set; } + public bool IsSophonInPreloadVerifyMode { get; protected set; } protected bool IsAllowExtractCorruptZip { get; set; } ~ProgressBase() @@ -579,9 +580,8 @@ protected void UpdateSophonDownloadStatus(SophonAsset _) Interlocked.Add(ref ProgressAllCountCurrent, 1); Status.ActivityStatus = $"{(IsSophonInUpdateMode ? Locale.Current.Lang?._Misc?.Updating - : Locale.Current.Lang?._Misc?.Downloading)}: {string.Format(Locale.Current.Lang?._Misc?.PerFromTo ?? "", ProgressAllCountCurrent, - ProgressAllCountTotal)}"; - + : IsSophonInPreloadVerifyMode ? Locale.Current.Lang?._Misc?.Verifying : Locale.Current.Lang?._Misc?.Downloading)}: {string.Format(Locale.Current.Lang?._Misc?.PerFromTo ?? "", ProgressAllCountCurrent, + ProgressAllCountTotal)}"; UpdateStatus(); } diff --git a/CollapseLauncher/Classes/Interfaces/INotifyAllPropertyChanged.cs b/CollapseLauncher/Classes/Interfaces/INotifyAllPropertyChanged.cs new file mode 100644 index 0000000000..4715ce02e3 --- /dev/null +++ b/CollapseLauncher/Classes/Interfaces/INotifyAllPropertyChanged.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CollapseLauncher.Interfaces; + +public interface INotifyAllPropertyChanged +{ + void NotifyAllChanged(); +} diff --git a/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs b/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs index 3ddaded120..fb510838d8 100644 --- a/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs +++ b/CollapseLauncher/Classes/Plugins/PluginInfo.Update.cs @@ -325,7 +325,7 @@ async ValueTask Impl(PluginManifestAssetInfo asset, CancellationToken innerToken { string filePath = Path.Combine(outputDir, asset.FilePath); string? fileDir = Path.GetDirectoryName(filePath); - string fileUrl = cdnBaseUrl.CombineUrlFromString(asset.FilePath); + string fileUrl = cdnBaseUrl.CombineUrlFromString(asset.FilePath.Replace('\\', '/')); if (!string.IsNullOrEmpty(fileDir)) { @@ -350,6 +350,7 @@ async ValueTask Impl(PluginManifestAssetInfo asset, CancellationToken innerToken // ReSharper disable once AccessToDisposedClosure // Reason: The httpClient will never get disposed until the method is done being executed. await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, innerToken); + response.EnsureSuccessStatusCode(); await using Stream responseStream = await response.Content.ReadAsStreamAsync(innerToken); await using FileStream fileStream = File.Create(filePath); diff --git a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs index 7f456a9e71..f77e1bc073 100644 --- a/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs +++ b/CollapseLauncher/Classes/RegionManagement/RegionManagement.cs @@ -297,25 +297,25 @@ private static bool IsNotificationTimestampValid(NotificationProp Entry) return isBeginValid && isEndValid; } + private async void ChangeRegion(object sender, RoutedEventArgs e) + { + try + { + ToggleChangeRegionBtn(sender, true); + ChangeRegionInstant(); + } + finally + { + ToggleChangeRegionBtn(sender, false); + } + } + private async void ChangeRegionNoWarning(object sender, RoutedEventArgs e) { try { (sender as Button)?.IsEnabled = false; - if (!IsLoadRegionComplete) - { - return; - } - - _lockRegionChangeBtn = true; - _currentGameCategory = ComboBoxGameTitle.SelectedIndex; - _currentGameRegion = ComboBoxGameRegion.SelectedIndex; - await LoadRegionRootButton(); - InvokeLoadingRegionPopup(false); - - MainFrameChanger.ChangeMainFrame(m_appMode == AppMode.Hi3CacheUpdater - ? typeof(CachesPage) - : typeof(HomePage), true); + ChangeRegionInstant(); } catch (Exception ex) { @@ -324,7 +324,7 @@ private async void ChangeRegionNoWarning(object sender, RoutedEventArgs e) } finally { - _lockRegionChangeBtn = false; + (sender as Button)?.IsEnabled = true; } } @@ -341,7 +341,6 @@ private async void ChangeRegionInstant() _currentGameCategory = ComboBoxGameTitle.SelectedIndex; _currentGameRegion = ComboBoxGameRegion.SelectedIndex; await LoadRegionRootButton(); - InvokeLoadingRegionPopup(false); MainFrameChanger.ChangeMainFrame(m_appMode == AppMode.Hi3CacheUpdater ? typeof(CachesPage) @@ -355,39 +354,7 @@ private async void ChangeRegionInstant() finally { _lockRegionChangeBtn = false; - } - } - - private async void ChangeRegion(object sender, RoutedEventArgs e) - { - try - { - if (!IsLoadRegionComplete) - { - return; - } - - // Disable ChangeRegionBtn and hide flyout - _lockRegionChangeBtn = true; - ToggleChangeRegionBtn(sender, true); - if (!await LoadRegionRootButton()) - { - return; - } - - // Finalize loading - ToggleChangeRegionBtn(sender, false); - _currentGameCategory = ComboBoxGameTitle.SelectedIndex; - _currentGameRegion = ComboBoxGameRegion.SelectedIndex; - } - catch (Exception ex) - { - LogWriteLine($"Failed while changing region with normal method\r\n{ex}", LogType.Error, true); - await SentryHelper.ExceptionHandlerAsync(ex); - } - finally - { - _lockRegionChangeBtn = false; + InvokeLoadingRegionPopup(false); } } diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.FinalizeFetch.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.FinalizeFetch.cs index 85340378ca..763ec71c94 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.FinalizeFetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.FinalizeFetch.cs @@ -62,7 +62,7 @@ public async Task FinalizeCacheFetchAsync(StarRailRepairV2 instance, tempStream.Position = 0; // -- Parse manifest and get the first asset from stock metadata - StarRailAssetSignaturelessMetadata metadataLuaV = new(".bytes"); + StarRailAssetBytesSignaturelessMetadata metadataLuaV = new(); await metadataLuaV.ParseAsync(tempStream, true, token); // -- Get stock dictionary asset diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.GetPersistentFiles.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.GetPersistentFiles.cs index 0f1d1af0fc..4f624adc6f 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.GetPersistentFiles.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.GetPersistentFiles.cs @@ -96,7 +96,7 @@ public List GetPersistentFiles( BaseDirs.StreamingVideo, BaseDirs.PersistentVideo, BaseUrls.Video, - BaseUrls.Video, + BaseUrls.VideoPersistent, true, fileList, unusedAssets, @@ -110,7 +110,7 @@ public List GetPersistentFiles( BaseDirs.StreamingAudio, BaseDirs.PersistentAudio, BaseUrls.Audio, - BaseUrls.Audio, + BaseUrls.AudioPersistent, true, fileList, unusedAssets, @@ -140,6 +140,34 @@ public List GetPersistentFiles( Metadata.RawResV.DataList); } + if (Metadata.DesignV != null) + { + AddAdditionalAssets(gameDirPath, + BaseDirs.StreamingDesignData, + BaseDirs.PersistentDesignData, + BaseUrls.DesignData, + BaseUrls.DesignData, + false, + fileList, + unusedAssets, + oldDic, + Metadata.DesignV.DataList); + } + + if (Metadata.NativeDataV != null) + { + AddAdditionalAssets(gameDirPath, + BaseDirs.StreamingNativeData, + BaseDirs.PersistentNativeData, + BaseUrls.NativeData, + BaseUrls.NativeData, + false, + fileList, + unusedAssets, + oldDic, + Metadata.NativeDataV.DataList); + } + if (Metadata.CacheLua != null) { AddAdditionalAssets(gameDirPath, @@ -293,12 +321,13 @@ private static void AddAdditionalAssets( FilePropertiesRemote file = new() { - RN = url, - N = relPathInPersistent, - S = asset.FileSize, - CRCArray = asset.MD5Checksum, - FT = StarRailRepairV2.DetermineFileTypeFromExtension(asset.Filename ?? ""), - IsHasHashMark = isHashMarked + RN = url, + N = relPathInPersistent, + S = asset.FileSize, + CRCArray = asset.MD5Checksum, + FT = StarRailRepairV2.DetermineFileTypeFromExtension(asset.Filename ?? ""), + IsHasHashMark = isHashMarked, + AssociatedObject = asset, }; fileDic.TryAdd(relPathInPersistent, file); fileList.Add(file); diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs index f88543642e..39cf6d85c5 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/StarRailPersistentRefResult.cs @@ -12,6 +12,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text.Json.Serialization; @@ -54,9 +56,11 @@ public static async Task GetCacheReferenceAsync( AsbBlock = "", AsbBlockPersistent = "", Audio = "", + AudioPersistent = "", DesignData = "", NativeData = "", Video = "", + VideoPersistent = "", RawRes = "", CacheLua = mainUrlLua, CacheIFix = mainUrlIFix @@ -103,23 +107,16 @@ public static async Task GetCacheReferenceAsync( aDirIFix, token); - // -- Save local index files - // Notes to Dev: HoYo no longer provides a proper raw bytes data anymore and the client creates it based - // on data provided by "handleArchive", so we need to emulate how the game generates these data. - await SaveLocalIndexFiles(instance, handleLua, aDirLua, "LuaV", token); - await SaveLocalIndexFiles(instance, handleIFix, aDirIFix, "IFixV", token); - // -- Load metadata files // -- LuaV - StarRailAssetSignaturelessMetadata? metadataLuaV = new(".bytes"); - metadataLuaV = await LoadMetadataFile(instance, - handleLua, - client, - baseUrls.CacheLua, - "LuaV", - metadataLuaV, - aDirLua, - token); + StarRailAssetBytesSignaturelessMetadata? metadataLuaV = + await LoadMetadataFile(instance, + handleLua, + client, + baseUrls.CacheLua, + "LuaV", + aDirLua, + token); // -- IFixV StarRailAssetCsvMetadata? metadataIFixV = @@ -199,10 +196,12 @@ public static async Task GetRepairReferenceAsync( // We also made the second check for the actual block URLs below so HoYo wouldn't be able to fuck around with our code // anymore. string mainUrlAudio = mainUrlAsb.CombineURLFromString("AudioBlock"); + string mainUrlAudioAlt = mainUrlAsbAlt.CombineURLFromString("AudioBlock"); string mainUrlAsbBlock = mainUrlAsb.CombineURLFromString("Block"); string mainUrlAsbBlockAlt = mainUrlAsbAlt.CombineURLFromString("Block"); string mainUrlNativeData = mainUrlDesignData.CombineURLFromString("NativeData"); string mainUrlVideo = mainUrlAsb.CombineURLFromString("Video"); + string mainUrlVideoAlt = mainUrlAsbAlt.CombineURLFromString("Video"); string mainUrlRawRes = mainUrlAsb.CombineURLFromString("RawRes"); AssetBaseUrls baseUrl = new() @@ -211,10 +210,12 @@ public static async Task GetRepairReferenceAsync( DesignData = mainUrlDesignData, Archive = mainUrlArchive, Audio = mainUrlAudio, + AudioPersistent = mainUrlAudioAlt, AsbBlock = mainUrlAsbBlock, AsbBlockPersistent = mainUrlAsbBlockAlt, NativeData = mainUrlNativeData, Video = mainUrlVideo, + VideoPersistent = mainUrlVideoAlt, RawRes = mainUrlRawRes }; @@ -272,28 +273,16 @@ public static async Task GetRepairReferenceAsync( LogType.Debug, true); - // -- Save local index files - // Notes to Dev: HoYo no longer provides a proper raw bytes data anymore and the client creates it based - // on data provided by "handleArchive", so we need to emulate how the game generates these data. - await SaveLocalIndexFiles(instance, handleDesignArchive, aDirDesignData, "DesignV", token); - await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "AsbV", token); - await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "BlockV", token); - await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "Start_AsbV", token); - await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "Start_BlockV", token); - await SaveLocalIndexFiles(instance, handleArchive, aDirAudio, "AudioV", token); - await SaveLocalIndexFiles(instance, handleArchive, aDirVideo, "VideoV", token); - await SaveLocalIndexFiles(instance, handleArchive, aDirRawRes, "RawResV", token); - // -- Load metadata files // -- DesignV - StarRailAssetSignaturelessMetadata? metadataDesignV = - await LoadMetadataFile(instance, - handleDesignArchive, - client, - baseUrl.DesignData, - "DesignV", - aDirDesignData, - token); + StarRailAssetBytesSignaturelessMetadata? metadataDesignV = + await LoadMetadataFile(instance, + handleDesignArchive, + client, + baseUrl.DesignData, + "DesignV", + aDirDesignData, + token); // -- NativeDataV StarRailAssetNativeDataMetadata? metadataNativeDataV = @@ -332,7 +321,7 @@ await LoadMetadataFile(instance, client, baseUrl.AsbBlockPersistent, "AsbV", - null, + aDirAsbBlock, token); // -- BlockV @@ -342,7 +331,7 @@ await LoadMetadataFile(instance, client, baseUrl.AsbBlockPersistent, "BlockV", - null, + aDirAsbBlock, token); // -- AudioV @@ -375,6 +364,33 @@ await LoadMetadataFile(instance, aDirRawRes, token); + // -- Save local index files + // Notes to Dev: HoYo no longer provides a proper raw bytes data anymore and the client creates it based + // on data provided by "handleArchive", so we need to emulate how the game generates these data. + await SaveLocalIndexFiles(instance, handleDesignArchive, aDirDesignData, "DesignV", token); + await SaveLocalIndexFiles(instance, handleDesignArchive, aDirNativeData, "NativeDataV", token); + await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "AsbV", token); + await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "BlockV", token); + await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "Start_AsbV", token); + await SaveLocalIndexFiles(instance, handleArchive, aDirAsbBlock, "Start_BlockV", token); + await SaveLocalIndexFiles(instance, handleArchive, aDirAudio, "AudioV", token); + await SaveLocalIndexFiles(instance, handleArchive, aDirVideo, "VideoV", token); + await SaveLocalIndexFiles(instance, handleArchive, aDirRawRes, "RawResV", token); + + // Perform URL test & swap for Audio and Video + await baseUrl.TestAndSwapUrlAsync(client, + mainUrlAsbAlt.CombineURLFromString("AudioBlock"), + () => metadataAudioV?.DataList.FirstOrDefault(x => x.Filename?.EndsWith(".pck", StringComparison.OrdinalIgnoreCase) ?? false), + context => context.Audio, + (context, replace) => context.Audio = replace, + token); + await baseUrl.TestAndSwapUrlAsync(client, + mainUrlAsbAlt.CombineURLFromString("Video"), + () => metadataVideoV?.DataList.FirstOrDefault(x => x.Filename?.EndsWith(".usm", StringComparison.OrdinalIgnoreCase) ?? false), + context => context.Video, + (context, replace) => context.Video = replace, + token); + return new StarRailPersistentRefResult { BaseDirs = baseDirs, @@ -395,7 +411,7 @@ await LoadMetadataFile(instance, } private static async ValueTask SaveLocalIndexFiles( - StarRailRepairV2 instance, + StarRailRepairV2 instance, Dictionary handleArchiveSource, string outputDir, string indexKey, @@ -599,19 +615,69 @@ public AssetBaseDirs() : this("", "", "", "", "", "", "") public class AssetBaseUrls { public required Dictionary GatewayKvp { get; set; } - public required string DesignData { get; set; } + public required string DesignData { get; init; } public required string Archive { get; set; } public required string Audio { get; set; } + public required string AudioPersistent { get; set; } public required string AsbBlock { get; set; } public required string AsbBlockPersistent { get; set; } - public required string NativeData { get; set; } + public required string NativeData { get; init; } public required string Video { get; set; } - public required string RawRes { get; set; } + public required string VideoPersistent { get; set; } + public required string RawRes { get; init; } + + public string? CacheLua { get; init; } + public string? CacheIFix { get; init; } + + public void SwapAsbPersistentUrl() => + (AsbBlock, AsbBlockPersistent, Video, VideoPersistent, Audio, AudioPersistent) = + (AsbBlockPersistent, AsbBlock, VideoPersistent, Video, AudioPersistent, Audio); + + public async Task TestAndSwapUrlAsync( + HttpClient client, + string urlBaseAlt, + Func testAssetSelector, + Func propertySelector, + Action propertyModifier, + CancellationToken token) + { + if (testAssetSelector() is not {} firstAssetToTest || + firstAssetToTest.Filename == null) + { + return; + } - public string? CacheLua { get; set; } - public string? CacheIFix { get; set; } + bool isRetry = false; + string rootUrlToTest = propertySelector(this); + + Test: + string testUrl = rootUrlToTest.CombineURLFromString(firstAssetToTest.Filename); + UrlStatus testUrlStatus = await client.GetCachedUrlStatus(testUrl, token); - public void SwapAsbPersistentUrl() => (AsbBlock, AsbBlockPersistent) = (AsbBlockPersistent, AsbBlock); + switch (testUrlStatus.IsSuccessStatusCode) + { + // If status is success and retry is requested, then swap the Audio property + case true when isRetry: + propertyModifier(this, urlBaseAlt); + return; + // If status still fails and retry is requested, throw. + case false when isRetry: + throw new + HttpRequestException($"Both Base URLs: {Audio} or {urlBaseAlt} doesn't contain a correct location of the audio files.", + null, + testUrlStatus.StatusCode); + } + + // If status is 404 (Not Found), start swapping the URL + if (testUrlStatus is { IsSuccessStatusCode: false, StatusCode: HttpStatusCode.NotFound } && !isRetry) + { + isRetry = true; + rootUrlToTest = urlBaseAlt; + goto Test; + } + + // If status is already successful, do nothing. + } } public class AssetMetadata diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetBytesSignaturelessMetadata.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetBytesSignaturelessMetadata.cs new file mode 100644 index 0000000000..0b296a6e46 --- /dev/null +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetBytesSignaturelessMetadata.cs @@ -0,0 +1,13 @@ +#pragma warning disable IDE0290 // Shut the fuck up +#pragma warning disable IDE0130 +#nullable enable + +namespace CollapseLauncher.RepairManagement.StarRail.Struct.Assets; + +/// +/// Star Rail .Bytes Signatureless Metadata parser for DesignDataV and LuaV. This parser is read-only and cannot be written back.
+///
+public sealed class StarRailAssetBytesSignaturelessMetadata : StarRailAssetSignaturelessMetadata +{ + public StarRailAssetBytesSignaturelessMetadata() : base(".bytes") { } +} diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetGenericFileInfo.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetGenericFileInfo.cs index a98b5d2333..1e369e8cbe 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetGenericFileInfo.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetGenericFileInfo.cs @@ -48,7 +48,7 @@ public class StarRailAssetGenericFileInfo /// The MD5 hash checksum of the file. /// [JsonPropertyName("Md5")] - [JsonConverter(typeof(HexStringToArrayJsonConverter))] + [JsonConverter(typeof(BytesStringToArrayJsonConverter))] public required byte[] MD5Checksum { get; init; } public override string ToString() => diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetNativeDataMetadata.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetNativeDataMetadata.cs index 180472160c..31b9d372cd 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetNativeDataMetadata.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetNativeDataMetadata.cs @@ -157,7 +157,7 @@ public ReadOnlySpan Shifted4BytesMD5Checksum } } - public class Metadata : StarRailAssetGenericFileInfo + public class Metadata : StarRailAssetFlaggable { public static Span Parse(Span filenameBuffer, ref FileInfoStruct assetInfo, diff --git a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs index bb829a3c27..9853059985 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRailV2/Struct/Assets/StarRailAssetSignaturelessMetadata.cs @@ -16,9 +16,9 @@ namespace CollapseLauncher.RepairManagement.StarRail.Struct.Assets; /// -/// Star Rail Signatureless Metadata parser for LuaV, DesignV. This parser is read-only and cannot be written back.
+/// Star Rail Signatureless Metadata parser. This parser is read-only and cannot be written back.
///
-public sealed class StarRailAssetSignaturelessMetadata : StarRailAssetBinaryMetadata +public abstract class StarRailAssetSignaturelessMetadata : StarRailAssetBinaryMetadata { public StarRailAssetSignaturelessMetadata() : this(null) { diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index 5d8495800c..0f762e8ac2 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -16,11 +16,12 @@ $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2026 $(Company) - 1.84.1 + 1.84.2 preview x64 net10.0-windows10.0.26100.0 + 10.0.26100.84 10.0.17763.0 win-x64 true @@ -48,6 +49,12 @@ true + + + runtime-async=on + true + + $(NoWarn);TA101;TA100 5 @@ -106,11 +113,6 @@ true - - true - true - - - x86-64-v2 + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -311,21 +313,20 @@ - + - - - - - + + + + - - + + @@ -387,16 +388,15 @@ - - - - - - - - + + + + + + --> - + - - + + --> - - - + --> + diff --git a/CollapseLauncher/Program.cs b/CollapseLauncher/Program.cs index 450d2897cf..76223fd98a 100644 --- a/CollapseLauncher/Program.cs +++ b/CollapseLauncher/Program.cs @@ -1,8 +1,10 @@ using CollapseLauncher.Extension; using CollapseLauncher.Helper; using CollapseLauncher.Helper.Database; +using CollapseLauncher.Helper.InternalPInvoke; using CollapseLauncher.Helper.Update; using Hi3Helper; +using Hi3Helper.Data; using Hi3Helper.EncTool; using Hi3Helper.EncTool.Hashes; using Hi3Helper.Http.Legacy; @@ -22,7 +24,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -58,7 +60,8 @@ public static void Main(params string[] args) { try { - AppCurrentArgument = args.ToList(); + args = KillParentPidIfRestartRequested(args); + AppCurrentArgument = [.. args]; #if PREVIEW IsPreview = true; #endif @@ -66,6 +69,9 @@ public static void Main(params string[] args) // Initialize Application Configs and apply default settings. InitAppPreset(); + // Use custom Dll import resolver + NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), CollapsePInvoke.DllImportResolver); + // Initialize Application Icons to Static Variables InitAppIcons(); @@ -116,7 +122,6 @@ public static void Main(params string[] args) // Reason: These are methods that either has its own error handling and/or not that important, // so the execution could continue without anything to worry about **technically** CheckRuntimeFeatures(); - AppDomain.CurrentDomain.ProcessExit += OnProcessExit; Console.WriteLine(Directory.GetCurrentDirectory()); Application.Start(_ => @@ -341,7 +346,7 @@ private static void InitMagicScalerExternalCodecs() { CodecManager.Configure(codecs => { - codecs.UseWicCodecs(WicCodecPolicy.All); + codecs.UseWicCodecs(WicCodecPolicy.BuiltIn); codecs.UseLibwebp(); codecs.UseLibheif(); codecs.UseLibjxl(); @@ -419,22 +424,7 @@ public static void SpawnFatalErrorConsole(Exception ex) if (ConsoleKey.R == Console.ReadKey().Key) { - ProcessStartInfo startInfo = new() - { - FileName = AppExecutablePath, - UseShellExecute = false - }; - - foreach (string arg in AppCurrentArgument) - { - startInfo.ArgumentList.Add(arg); - } - - Process process = new() - { - StartInfo = startInfo - }; - process.Start(); + ForceRestart(); } tokenSource.Cancel(); @@ -603,14 +593,6 @@ private static void HttpClientLogWatcher(object sender, DownloadLogEvent e) LogWriteLine(e.Message, severity, true); } - private static void OnProcessExit(object? sender, EventArgs e) - { - // TODO: #671 This App.IsAppKilled will be replaced with cancellable-awaitable event - // to ensure no hot-exit being called before all background tasks - // hasn't being cancelled. - // App.IsAppKilled = true; - } - private static void CheckRuntimeFeatures() { try @@ -664,19 +646,15 @@ private static void InitLocale() private static void RunElevateUpdate() { - Process elevatedProc = new Process + Process? elevatedProc = Process.Start(new ProcessStartInfo { - StartInfo = new ProcessStartInfo - { - FileName = UpdaterWindow.SourcePath, - WorkingDirectory = UpdaterWindow.WorkingDir, - Arguments = - $"update --input \"{m_arguments.Updater.AppPath}\" --channel {m_arguments.Updater.UpdateChannel}", - UseShellExecute = true, - Verb = "runas" - } - }; - elevatedProc.Start(); + FileName = UpdaterWindow.SourcePath, + WorkingDirectory = UpdaterWindow.WorkingDir, + Arguments = $"update --input \"{m_arguments.Updater.AppPath}\" --channel {m_arguments.Updater.UpdateChannel}", + UseShellExecute = true, + Verb = "runas" + }); + elevatedProc?.Start(); } public static string GetVersionString() @@ -693,24 +671,70 @@ public static string MD5Hash(string path) return ""; } - FileStream stream = File.OpenRead(path); - byte[] hash = CryptoHashUtility.Shared.GetHashFromStream(stream); - stream.Close(); + using FileStream stream = File.OpenRead(path); + byte[] hash = CryptoHashUtility.Shared.GetHashFromStream(stream); return Convert.ToHexStringLower(hash); } + private static string restartedFromPidArgKey = "restartedFromPid"; + public static void ForceRestart() { // Workaround to artificially start new process and wait for the current one to be killed. - var cmdProc = Process.Start(new ProcessStartInfo + using Process? cmdProc = Process.Start(new ProcessStartInfo { - FileName = "cmd.exe", - Arguments = $"/c timeout /T 1 && start \"\" \"{AppExecutablePath}\"", - UseShellExecute = true, + FileName = AppExecutablePath, + WorkingDirectory = Environment.CurrentDirectory, + Arguments = $"{restartedFromPidArgKey}:{Environment.ProcessId} {string.Join(' ', AppCurrentArgument)}", + UseShellExecute = true }); - cmdProc?.WaitForExit(); Application.Current.Exit(); } + + private static string[] KillParentPidIfRestartRequested(params string[] args) + { + if (args.Length == 0) + { + return args; + } + + int indexOfArg = -1; + string[] restArgs = args.Length == 1 ? [] : new string[args.Length - 1]; + + // Copy other args and find restart arg + for (int i = 0; i < args.Length; i++) + { + string currentArg = args[i]; + if (currentArg.StartsWith(restartedFromPidArgKey, StringComparison.OrdinalIgnoreCase)) + { + indexOfArg = i; + continue; + } + restArgs[i < indexOfArg ? i : i - 1] = currentArg; + } + + if (indexOfArg < 0) + { + return args; + } + + ReadOnlySpan restartParentPidArg = args[indexOfArg]; + ReadOnlySpan restartParentPid = ConverterTool.GetSplit(restartParentPidArg, 1, ":,#$;"); + + // Check for PID. If exist, then kill. + if (!int.TryParse(restartParentPid, out int parentPid) || + !ProcessChecker.IsProcessExist(parentPid)) + { + return restArgs; + } + + Console.WriteLine($"Waiting to kill parent process: {parentPid}"); + using Process parentProcess = Process.GetProcessById(parentPid); + parentProcess.Kill(); + parentProcess.WaitForExit(); + + return restArgs; + } } } \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml index 1fc7edb241..802b580be0 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml @@ -17,7 +17,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. win-x64 - true + true false diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewLocalDevAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewLocalDevAOT.pubxml index ef36d65894..d24e95606e 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewLocalDevAOT.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewLocalDevAOT.pubxml @@ -17,7 +17,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. win-x64 - true + true true diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml index 5e44d91828..ffb13a1bd6 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml @@ -17,7 +17,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. win-x64 - true + true true diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml index 0ac4e102b7..797b1034c6 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml @@ -18,10 +18,6 @@ https://go.microsoft.com/fwlink/?LinkID=208121. true - true - false - true - false true diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index 683fb91cfc..986a10d5aa 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -132,14 +132,13 @@ x:FieldModifier="internal" extension:UIElementExtensions.CursorType="Hand" PointerPressed="ClickImageEventSpriteLink" - DataContext="{x:Bind CurrentGameBackgroundData, Mode=OneWay}" HorizontalAlignment="{x:Bind CurrentPresetConfig.GameEventButtonPosition.HorizontalAlignment, Mode=OneWay}" VerticalAlignment="{x:Bind CurrentPresetConfig.GameEventButtonPosition.VerticalAlignment, Mode=OneWay}" Height="{x:Bind CurrentPresetConfig.GameEventButtonPosition.VSize, Mode=OneWay}" Margin="{x:Bind CurrentPresetConfig.GameEventButtonPosition.Position, Mode=OneWay}" Visibility="{x:Bind NeedShowEventIcon, Mode=OneWay, Converter={StaticResource BooleanVisibilityConverter}}" - Source="{Binding FeaturedEventIconUrl, Mode=OneWay, Converter={StaticResource UrlToCachedImagePathConverter}}" - Tag="{Binding FeaturedEventIconClickLink, Mode=OneWay}"/> + Source="{x:Bind CurrentGameBackgroundData.FeaturedEventIconUrl, Mode=OneWay, Converter={StaticResource UrlToCachedImagePathConverter}}" + Tag="{x:Bind CurrentGameBackgroundData.FeaturedEventIconClickLink, Mode=OneWay}"/> diff --git a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml index 0a566e6e22..8d49bd0089 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml @@ -251,6 +251,16 @@ + + + + + + diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs index eb088f14ad..3c3a104f63 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs @@ -1514,6 +1514,7 @@ await SpawnDialog(Locale.Current.Lang?._Dialogs?.Media_VideoFFmpegCodecPrepareTi return false; } + var ffmpegLibNames = ImageBackgroundManager.Shared.GlobalFFmpegLibraryNames; string? foundFfmpegDir = null; if (result == ContentDialogResult.Secondary) { @@ -1523,7 +1524,7 @@ await SpawnDialog(Locale.Current.Lang?._Dialogs?.Media_VideoFFmpegCodecPrepareTi goto StartOver; } - foundFfmpegDir = ImageBackgroundManager.FindFFmpegInstallFolder(ffmpegDir); + foundFfmpegDir = ImageBackgroundManager.FindFFmpegInstallFolder(ffmpegDir, ffmpegLibNames); if (string.IsNullOrEmpty(foundFfmpegDir)) { await SpawnDialog(Locale.Current.Lang?._Dialogs?.Media_VideoFFmpegCodecPrepareLocateFailedTitle, @@ -1561,6 +1562,7 @@ await SpawnDialog(Locale.Current.Lang?._Dialogs?.Media_VideoFFmpegCodecPrepareLo { if (!ImageBackgroundManager.TryLinkFFmpegLibrary(foundFfmpegDir, Directory.GetCurrentDirectory(), + ffmpegLibNames, out Exception? ex)) { ErrorSender.SendException(ex); diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index 8c186ecbe8..2b6d97ac88 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -572,7 +572,6 @@ Margin="4,4" Padding="8" extension:UIElementExtensions.CursorType="Hand" - BorderThickness="0" Click="OpenSocMedLink" Content="{Binding}" CornerRadius="12" diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml index be978f4a8e..249ce132e3 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/PluginManagerPage.xaml @@ -345,6 +345,14 @@ + + + + + diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml index 7cc2dcfdb8..751041965a 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml @@ -15,6 +15,7 @@ xmlns:innerConfig="using:Hi3Helper.Shared.Region" xmlns:localWindowSize="using:CollapseLauncher.WindowSize" xmlns:localeSourceGen="using:Hi3Helper.LocaleSourceGen" + xmlns:ffmpegInterop="using:FFmpegInteropX" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:plugins="using:CollapseLauncher.Plugins" Loaded="Page_Loaded" @@ -35,6 +36,8 @@ + + @@ -450,6 +453,37 @@ + + + + + + + + + + + + + diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs index 28f8b75600..0389b5ba7b 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/SettingsPage.xaml.cs @@ -19,6 +19,7 @@ using CollapseLauncher.XAMLs.Theme.ContentDialog; using CollapseLauncher.XAMLs.Theme.CustomControls; using CommunityToolkit.WinUI; +using FFmpegInteropX; using Hi3Helper; using Hi3Helper.EncTool; using Hi3Helper.Plugin.Core.Management; @@ -57,6 +58,7 @@ using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; using CollapseUIExt = CollapseLauncher.Extension.UIElementExtensions; +using CollapseLauncher.Interfaces; // ReSharper disable AsyncVoidMethod // ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault @@ -93,7 +95,10 @@ public sealed partial class SettingsPage : Page private List DialogMethodNames { get; } public string SelectedDialogMethodName { get; set; } - #nullable enable + private List FFmpegDecodingModeSelectionItems { get; } = BuildFFmpegDecodingModeSelectionItems(); + private List FFmpegDecodingModeHelpItems { get; } = BuildFFmpegDecodingModeHelpItems(); + +#nullable enable private string? _previousSearchQuery; #nullable restore @@ -799,6 +804,10 @@ private void LanguageSelector_SelectionChanged(object sender, SelectionChangedEv string selectedKey = Locale.Current.Lang.LanguageID; PluginManager.SetPluginLocaleId(selectedKey); + ((INotifyAllPropertyChanged)ImageBackgroundManager.Shared).NotifyAllChanged(); + CustomDnsConnectionTypeComboBox.UpdateLayout(); + CustomDnsProviderListComboBox.UpdateLayout(); + VideoCodecFfmpegDecodingMethod.UpdateLayout(); InitializeSettingsSearch(); } @@ -1350,7 +1359,78 @@ private async void ValidateAndApplyDnsSettings(object sender, RoutedEventArgs e) senderAsButton.IsEnabled = true; } } + + private static List BuildFFmpegDecodingModeSelectionItems() + { + List list = []; + + foreach (VideoDecoderMode mode in Enum.GetValues()) + { + TextBlock textBlock = new(); + textBlock.BindProperty(TextBlock.TextProperty, + Locale.Current, + "Lang._DictKvpFFmpegDecodingMode", + StaticConverter.Shared, + bindingMode: BindingMode.OneWay, + converterParameter: mode); + + textBlock.BindTooltipToLocale(Locale.Current, + "Lang._DictKvpFFmpegDecodingModeTooltip", + converter: StaticConverter.Shared, + converterParameter: mode); + + list.Add(textBlock); + } + + return list; + } + + private static List BuildFFmpegDecodingModeHelpItems() + { + List list = []; + + foreach (VideoDecoderMode mode in Enum.GetValues()) + { + StackPanel stackPanel = new() + { + Orientation = Orientation.Vertical, + Margin = new Thickness(0, 8, 0, 8), + Spacing = 4, + MaxWidth = 420 + }; + + TextBlock textBlockHeader = stackPanel.AddElementToStackPanel(new TextBlock() + { + Style = CollapseUIExt.GetApplicationResource