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