Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
41500b5
[Plugin] Implement preload download and safe callbacks
Cryotechnic Mar 18, 2026
f5995e9
Bump ILLink.Tasks to 10.0.5; add .NET ref packs
Cryotechnic Mar 18, 2026
6a71b12
Add per-file progress callback support
Cryotechnic Mar 18, 2026
b62a37e
Make TryRegisterPerFileProgressCallback unsafe for improved performance
Cryotechnic Mar 19, 2026
6bdf973
Update FFmpeg installation note for clarity and conciseness
Cryotechnic Mar 19, 2026
f74ab60
Merge branch 'main' into feat/plugin-preload
Cryotechnic Mar 30, 2026
763ff55
Update localization references in plugin installation progress messages
Cryotechnic Mar 30, 2026
5ded46e
Prevent multiple allocations of per-file progress callback delegate
Cryotechnic Mar 30, 2026
0023f0c
Add per-file progress indicator status updates during preload
Cryotechnic Mar 30, 2026
7f2b766
Update download status handling to reflect success or failure
Cryotechnic Mar 30, 2026
085bd65
Handle OperationCanceledException in preload status check
Cryotechnic Mar 30, 2026
2d10717
Re-throw exception after logging preload failure in StartPackageDownload
Cryotechnic Mar 30, 2026
ddf66a8
Enhance file cleanup process and error handling in PluginGameInstallW…
Cryotechnic Mar 30, 2026
52d3ae7
Require scroll-to-bottom before accepting license agreement dialog
Cryotechnic Mar 31, 2026
a7a1b9b
[skip ci] Sync translation Translate en_US.json in de_DE
transifex-integration[bot] Apr 1, 2026
14d74bf
docs: update README.md [skip ci]
allcontributors[bot] Apr 1, 2026
0e8e498
docs: update .all-contributorsrc [skip ci]
allcontributors[bot] Apr 1, 2026
1dab2ad
docs: add perfectdelusions as a contributor for translation (#867)
bagusnl Apr 1, 2026
a30d984
Simplify BitmapIcon Create
neon-nyan Apr 3, 2026
cdec12d
Update MainPage.Navigation.cs
neon-nyan Apr 3, 2026
277a83c
Log error when unregistering per-file progress callback in PluginInfo
Cryotechnic Apr 4, 2026
9ad64fd
Add null check for progress in PluginGameInstallWrapper to prevent ex…
Cryotechnic Apr 4, 2026
df8717f
[skip ci] Sync translation Translate en_US.json in ja_JP
transifex-integration[bot] Apr 5, 2026
ff64a98
[skip ci] Sync translation Translate en_US.json in ja_JP
transifex-integration[bot] Apr 5, 2026
e02ab8b
Implement async preload download with per-file progress callbacks (#865)
neon-nyan Apr 5, 2026
ff1e82b
Simplify method to add navigation items
neon-nyan Apr 5, 2026
ba0472d
Update EncTool repo
neon-nyan Apr 5, 2026
c44c7f4
Update submodule
neon-nyan Apr 5, 2026
edbd6ae
Update NuGet
neon-nyan Apr 5, 2026
517ae11
Update ImageEx
neon-nyan Apr 5, 2026
55f4403
Fix ReturnToHomePage not refreshing after returning to its game
neon-nyan Apr 5, 2026
f87ef76
Fix deleted files re-downloaded on ZZZ and HSR update
neon-nyan Apr 5, 2026
bd26158
Refactor Hi3 CG metadata parser
neon-nyan Apr 5, 2026
5c0e414
Remove inclusion of postproc on FFmpeg
neon-nyan Apr 5, 2026
0875136
Revert "Remove inclusion of postproc on FFmpeg"
neon-nyan Apr 5, 2026
5ba9bd2
Improve Hi3 Cache Update and Game Repair file checks
neon-nyan Apr 6, 2026
00ce752
[skip ci] Sync translation Translate en_US.json in id_ID
transifex-integration[bot] Apr 6, 2026
ecc5919
[skip ci] Sync translation Translate en_US.json in zh_CN
transifex-integration[bot] Apr 6, 2026
73fc79d
Fix detection on LGPLv2.1 compliance FFmpeg builds
neon-nyan Apr 6, 2026
1b19c1a
Custom media element improvements
neon-nyan Apr 8, 2026
45d5548
Allow Genshin Repair if no dispatch token is available
neon-nyan Apr 8, 2026
6c1d75e
[skip ci] Adding German translation for Readme, by @puyomi2k
neon-nyan Apr 8, 2026
23e027f
[skip ci] Fix localized Readme links
neon-nyan Apr 8, 2026
ee13b0b
Use fade-in/fade-out transition on background image
neon-nyan Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@
"contributions": [
"translation"
]
},
{
"login": "perfectdelusions",
"name": "eden",
"avatar_url": "https://avatars.githubusercontent.com/u/272893080?v=4",
"profile": "https://github.com/perfectdelusions",
"contributions": [
"translation"
]
}
],
"repoType": "github"
Expand Down
23 changes: 13 additions & 10 deletions CollapseLauncher/Classes/CachesManagement/Honkai/Check.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,6 @@ private async Task<List<CacheAsset>> Check(List<CacheAsset> assetIndex, Cancella
return returnAsset;
}

private readonly SearchValues<string> _unusedSearchValues = SearchValues.Create([
"output_log",
"Crashes",
"Verify.txt",
"APM",
"FBData",
"asb.dat"
], StringComparison.OrdinalIgnoreCase);

private void CheckUnusedAssets(List<CacheAsset> assetIndex, List<CacheAsset> returnAsset)
{
// Directory info and if the directory doesn't exist, return
Expand All @@ -76,13 +67,25 @@ private void CheckUnusedAssets(List<CacheAsset> assetIndex, List<CacheAsset> ret
return;
}

SearchValues<string> unusedSearchValues = SearchValues
.Create(GameVersionManager.GamePreset.GameInstallFileInfo?.CacheUpdateUnusedFilesIgnoreList
?? [
"output_log",
"Crashes",
"Verify.txt",
"APM",
"FBData",
"asb.dat",
"MiHoYoSDK.log"
], StringComparison.OrdinalIgnoreCase);

// Iterate the file contained in the _gamePath
foreach (FileInfo fileInfo in directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories)
.EnumerateNoReadOnly())
{
ReadOnlySpan<char> filePath = fileInfo.FullName;

if (filePath.ContainsAny(_unusedSearchValues)
if (filePath.ContainsAny(unusedSearchValues)
|| assetIndex.Exists(x => x.ConcatPath == fileInfo.FullName))
{
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
using CollapseLauncher.Plugins;
using Hi3Helper;
using Hi3Helper.Data;
using Hi3Helper.Shared.Region;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;

// ReSharper disable StringLiteralTypo
Expand Down Expand Up @@ -67,23 +69,24 @@ private static IconElement GetGamePresetIcon(PresetConfig presetConfig)

if (presetConfig is not PluginPresetConfigWrapper pluginPresetConfig)
{
return new BitmapIcon
{
UriSource = new Uri(uri)
};
return Create(uri);
}

PluginInfo pluginInfo = pluginPresetConfig.PluginInfo;
GamePluginIconConverter converter = StaticConverter<GamePluginIconConverter>.Shared;
if (converter.Convert(pluginInfo, null!, null!, "") is not IconElement iconElement)
{
return new BitmapIcon
{
UriSource = new Uri(uri)
};
return Create(uri);
}

return iconElement;

static BitmapIcon Create(string uri)
=> new()
{
UriSource = new Uri(uri),
ShowAsMonochrome = false
};
}

private static void AttachEventToNotification(PresetConfig presetConfig, IBackgroundActivity activity, string activityTitle, string activitySubtitle)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using WinRT;

Expand Down Expand Up @@ -34,7 +35,7 @@ internal static T WithCursor<T>(this T element, InputCursor inputCursor) where T
/// Check whether a WinRT object has been disposed.
/// </summary>
/// <returns><see langword="true"/> if object is already disposed. Otherwise, <see langword="false"/>.</returns>
internal static bool IsObjectDisposed(this IWinRTObject? winRtObject)
internal static bool IsObjectDisposed([NotNullWhen(false)] this IWinRTObject? winRtObject)
{
if (winRtObject == null)
{
Expand Down
8 changes: 7 additions & 1 deletion CollapseLauncher/Classes/Extension/UIElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ internal static T BindNavigationViewItemText<T>(this T element, object? localeOb
localePropertyName,
sourceTrigger: UpdateSourceTrigger.PropertyChanged);

if (element is not NavigationViewItem) return element;
return element is not NavigationViewItem ?
element :
element.BindTooltipToLocale(localeObjBinding, localePropertyName);
}

internal static T BindTooltipToLocale<T>(this T element, object? localeObjBinding, string localePropertyName)
where T : DependencyObject
{
TextBlock tooltipTextBlock = new();
tooltipTextBlock.BindProperty(TextBlock.TextProperty,
localeObjBinding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,18 @@ public bool TryRelinkFFmpegPath()
return false;
}

// -- If custom FFmpeg path is set but FFmpeg is not available,
// Try to resolve the symbolic link path again.
// -- Check for custom FFmpeg path availability first. If not available, skip.
result = (IsFFmpegAvailable(customFFmpegDirPath, out exception) &&
// -- Re-link FFmpeg symbolic link
TryLinkFFmpegLibrary(customFFmpegDirPath, curDir, out exception)) ||
// -- If custom FFmpeg path is not avail, then try to find one from EnvVar (this might be a bit expensive).
// If found, the GlobalCustomFFmpegPath will be updated to the found path.
(TryFindFFmpegInstallFromEnvVar(out string? envVarPath, out exception) && TryLinkFFmpegLibrary(envVarPath, curDir, out exception));

return result;
// -- 1. Check from custom path first. If it exists, then pass.
if (!string.IsNullOrEmpty(customFFmpegDirPath) &&
IsFFmpegAvailable(customFFmpegDirPath, out exception) &&
TryLinkFFmpegLibrary(customFFmpegDirPath, curDir, 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);
}
finally
{
Expand Down Expand Up @@ -180,7 +181,6 @@ internal static bool IsFFmpegAvailable(string? checkOnDirectory,
string dllPathAvfilter = Path.Combine(checkOnDirectory, Fields.DllNameAvfilter);
string dllPathAvformat = Path.Combine(checkOnDirectory, Fields.DllNameAvformat);
string dllPathAvutil = Path.Combine(checkOnDirectory, Fields.DllNameAvutil);
string dllPathPostproc = Path.Combine(checkOnDirectory, Fields.DllNamePostproc);
string dllPathSwresample = Path.Combine(checkOnDirectory, Fields.DllNameSwresample);
string dllPathSwscale = Path.Combine(checkOnDirectory, Fields.DllNameSwscale);

Expand All @@ -189,7 +189,6 @@ internal static bool IsFFmpegAvailable(string? checkOnDirectory,
FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathAvfilter, out _, out exception) &&
FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathAvformat, out _, out exception) &&
FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathAvutil, out _, out exception) &&
FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathPostproc, out _, out exception) &&
FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathSwresample, out _, out exception) &&
FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathSwscale, out _, out exception);
}
Expand All @@ -201,7 +200,6 @@ internal static string[] GetFFmpegRequiredDllFilenames() =>
Fields.DllNameAvfilter,
Fields.DllNameAvformat,
Fields.DllNameAvutil,
Fields.DllNamePostproc,
Fields.DllNameSwresample,
Fields.DllNameSwscale
];
Expand Down Expand Up @@ -262,14 +260,24 @@ public static bool TryLinkFFmpegLibrary(
string dllPathSwresample = Path.Combine(sourceDir, Fields.DllNameSwresample);
string dllPathSwscale = Path.Combine(sourceDir, Fields.DllNameSwscale);

return CreateSymbolLink(dllPathAvcodec, targetDir, out exception) &&
CreateSymbolLink(dllPathAvdevice, targetDir, out exception) &&
CreateSymbolLink(dllPathAvfilter, targetDir, out exception) &&
CreateSymbolLink(dllPathAvformat, targetDir, out exception) &&
CreateSymbolLink(dllPathAvutil, targetDir, out exception) &&
CreateSymbolLink(dllPathPostproc, targetDir, out exception) &&
CreateSymbolLink(dllPathSwresample, targetDir, out exception) &&
CreateSymbolLink(dllPathSwscale, targetDir, out exception);
bool result =
CreateSymbolLink(dllPathAvcodec, targetDir, out exception) &&
CreateSymbolLink(dllPathAvdevice, targetDir, out exception) &&
CreateSymbolLink(dllPathAvfilter, targetDir, out exception) &&
CreateSymbolLink(dllPathAvformat, targetDir, out exception) &&
CreateSymbolLink(dllPathAvutil, targetDir, out exception) &&
CreateSymbolLink(dllPathSwresample, targetDir, out exception) &&
CreateSymbolLink(dllPathSwscale, targetDir, out exception);

// Additionally, link postproc if it exists.
// Since some non-free/GPL custom build (if used by the user) still requires postproc library to exist if enabled on build.
// Without it, some build might fail to run.
if (result && FileUtility.IsFileExistOrSymbolicLinkResolved(dllPathPostproc, out string? resolvedOptDllPostproc, out exception))
{
return result && CreateSymbolLink(resolvedOptDllPostproc, targetDir, out exception);
}

return result;

static bool CreateSymbolLink(string filePath,
string targetDirectory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Microsoft.UI.Xaml.Media.Animation;
using PhotoSauce.MagicScaler;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
Expand All @@ -36,27 +35,30 @@ public partial class ImageBackgroundManager

#endregion

private void LoadImageAtIndex(int index, CancellationToken token) =>
new Thread(() => LoadImageAtIndexCore(index, token))
private void LoadImageAtIndex(int index, CancellationToken token)
{
if (ImageContextSources.Count <= index ||
index < 0 ||
IsBackgroundLoading)
{
IsBackground = true
}.Start();
return;
}

IsBackgroundLoading = true;
new Thread(() => LoadImageAtIndexCore(index, token).ConfigureAwait(false))
{
IsBackground = true,
Priority = ThreadPriority.Lowest
}.UnsafeStart();
}

private async void LoadImageAtIndexCore(int index, CancellationToken token)
private async Task LoadImageAtIndexCore(int index, CancellationToken token)
{
Stopwatch? stopwatch = null;
try
{
bool isUseFFmpeg = GlobalIsUseFFmpeg && GlobalIsFFmpegAvailable;

stopwatch = Stopwatch.StartNew();
if (ImageContextSources.Count <= index ||
index < 0)
{
return;
}

IsBackgroundLoading = true;

// -- Notify changes on context menu properties
DispatcherQueueExtensions
Expand All @@ -78,24 +80,24 @@ private async void LoadImageAtIndexCore(int index, CancellationToken token)
Unsafe.SkipInit(out Uri? downloadedOverlayUri);
if (Uri.TryCreate(context.OverlayImagePath, UriKind.Absolute, out Uri? overlayImageUri))
{
downloadedOverlayUri = await GetLocalOrDownloadedFilePath(overlayImageUri, token);
(downloadedOverlayUri, _) = await GetNativeOrDecodedImagePath(downloadedOverlayUri, token);
downloadedOverlayUri = await GetLocalOrDownloadedFilePath(overlayImageUri, token).ConfigureAwait(false);
(downloadedOverlayUri, _) = await GetNativeOrDecodedImagePath(downloadedOverlayUri, token).ConfigureAwait(false);
}

// -- Download background.
Unsafe.SkipInit(out Uri? downloadedBackgroundUri);
if (Uri.TryCreate(context.BackgroundImagePath, UriKind.Absolute, out Uri? backgroundImageUri))
{
downloadedBackgroundUri = await GetLocalOrDownloadedFilePath(backgroundImageUri, token);
(downloadedBackgroundUri, _) = await GetNativeOrDecodedImagePath(downloadedBackgroundUri, token);
downloadedBackgroundUri = await GetLocalOrDownloadedFilePath(backgroundImageUri, token).ConfigureAwait(false);
(downloadedBackgroundUri, _) = await GetNativeOrDecodedImagePath(downloadedBackgroundUri, token).ConfigureAwait(false);
}

// -- Download static background.
Unsafe.SkipInit(out Uri? downloadedBackgroundStaticUri);
if (Uri.TryCreate(context.BackgroundImageStaticPath, UriKind.Absolute, out Uri? backgroundStaticImageUri))
{
downloadedBackgroundStaticUri = await GetLocalOrDownloadedFilePath(backgroundStaticImageUri, token);
(downloadedBackgroundStaticUri, _) = await GetNativeOrDecodedImagePath(downloadedBackgroundStaticUri, token);
downloadedBackgroundStaticUri = await GetLocalOrDownloadedFilePath(backgroundStaticImageUri, token).ConfigureAwait(false);
(downloadedBackgroundStaticUri, _) = await GetNativeOrDecodedImagePath(downloadedBackgroundStaticUri, token).ConfigureAwait(false);
}

// Try to use static bg URL if normal bg is not available.
Expand All @@ -108,9 +110,9 @@ private async void LoadImageAtIndexCore(int index, CancellationToken token)
// -- Get upscaled image file if Waifu2X is enabled
if (GlobalIsWaifu2XEnabled)
{
downloadedOverlayUri = await TryGetScaledWaifu2XImagePath(downloadedOverlayUri, token);
downloadedBackgroundUri = await TryGetScaledWaifu2XImagePath(downloadedBackgroundUri, token);
downloadedBackgroundStaticUri = await TryGetScaledWaifu2XImagePath(downloadedBackgroundStaticUri, token);
downloadedOverlayUri = await TryGetScaledWaifu2XImagePath(downloadedOverlayUri, token).ConfigureAwait(false);
downloadedBackgroundUri = await TryGetScaledWaifu2XImagePath(downloadedBackgroundUri, token).ConfigureAwait(false);
downloadedBackgroundStaticUri = await TryGetScaledWaifu2XImagePath(downloadedBackgroundStaticUri, token).ConfigureAwait(false);
}

token.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -238,7 +240,6 @@ private void SpawnImageLayer(Uri? overlayFilePath,
bindingMode: BindingMode.OneWay,
converter: StaticConverter<InverseBooleanConverter>.Shared);

layerElement.Transitions.Add(new PopupThemeTransition());
layerElement.ImageLoaded += LayerElementOnLoaded;
PresenterGrid?.Children.Add(layerElement);

Expand All @@ -247,10 +248,16 @@ private void SpawnImageLayer(Uri? overlayFilePath,

private void LayerElementOnLoaded(LayeredBackgroundImage layerElement)
{
List<UIElement> lastElements = PresenterGrid?.Children.ToList() ?? [];
foreach (UIElement element in lastElements.Where(element => element != layerElement))
layerElement.Transitions.Add(new PopupThemeTransition());
layerElement.ImageLoaded -= LayerElementOnLoaded;

if (PresenterGrid?.Children.Count > 1)
{
PresenterGrid?.Children.Remove(element);
UIElement? lastElement = PresenterGrid?.Children.LastOrDefault();
foreach (UIElement element in PresenterGrid?.Children.Where(element => element != lastElement) ?? [])
{
PresenterGrid?.Children.Remove(element);
}
}

if (CurrentIsEnableBackgroundAutoPlay && WindowUtility.CurrentWindowIsVisible)
Expand All @@ -263,7 +270,6 @@ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement)
{
CurrentBackgroundIsSeekable = isDisplayControl;
}
layerElement.ImageLoaded -= LayerElementOnLoaded;
}

private static bool TryGetUpscaledFilePath(string inputFilePath,
Expand Down
11 changes: 6 additions & 5 deletions CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@ public enum LauncherType

public class GameInstallFileInfo
{
public string GameDataFolderName { get; init; } = string.Empty;
public string[] FilesToDelete { get; init; } = [];
public string[] FoldersToDelete { get; init; } = [];
public string[] FoldersToKeepInData { get; init; } = [];
public string[] FilesCleanupIgnoreList { get; init; } = [];
public string GameDataFolderName { get; init; } = string.Empty;
public string[] FilesToDelete { get; init; } = [];
public string[] FoldersToDelete { get; init; } = [];
public string[] FoldersToKeepInData { get; init; } = [];
public string[] FilesCleanupIgnoreList { get; init; } = [];
public string[] CacheUpdateUnusedFilesIgnoreList { get; init; } = [];
}

public class SophonChunkUrls
Expand Down
Loading
Loading