Skip to content
Merged
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
5633507
[skip ci] Update README.md
neon-nyan Apr 11, 2026
f228508
[skip ci] Update README.md
neon-nyan Apr 11, 2026
dd4b030
[skip ci] Update README.md
neon-nyan Apr 11, 2026
bb758ac
[skip ci] Sync translation Translate en_US.json in ja_JP
transifex-integration[bot] Apr 12, 2026
d35a4fc
Fix ImageCropper crash for small image
shatyuka Apr 12, 2026
fdc8215
[skip ci] Update tooling version requirements
Cryotechnic Apr 14, 2026
db91391
[skip ci] Fix typo
Cryotechnic Apr 14, 2026
e8b0f58
Update submodules
neon-nyan Apr 19, 2026
9e6905c
Background image improvement attempt (pt. 1)
neon-nyan Apr 19, 2026
46e4340
Update NuGet
neon-nyan Apr 19, 2026
785e942
[skip ci] Update global.json
neon-nyan Apr 19, 2026
21b570e
Decouple Hi3Helper.SourceGen as NuGet
neon-nyan Apr 19, 2026
207d295
Restore property PublishAotSelf -> PublishAot
neon-nyan Apr 19, 2026
906e31d
Fix TrimmerRootDescriptor not applied on .NET 11 AOT builds
neon-nyan Apr 19, 2026
3b86cdd
Add some new parent locale files for deletion on AOT builds
neon-nyan Apr 19, 2026
15671a7
Update app.manifest
neon-nyan Apr 19, 2026
381dbf6
Fix restarting process won't actually restarting
neon-nyan Apr 20, 2026
036c7f2
Update Hi3Helper.Plugin.Core
neon-nyan Apr 20, 2026
6f83ff1
Switch to BytesStringToArrayJsonConverter for JSON Contexes
neon-nyan Apr 20, 2026
081400f
[SourceGen] Recognize property as Dictionary with "_DictKvp" prefix
neon-nyan Apr 22, 2026
fad9ca6
Add option to select FFmpeg decoding mode
neon-nyan Apr 23, 2026
4fe65fc
Enable runtime-async feature for .NET 11 builds
neon-nyan Apr 23, 2026
374cdba
[HSR Game Repair] Fix missing URL on Audio and Video assets
neon-nyan Apr 23, 2026
b580ec6
[HSR Game Repair] Fix persistent URLs, missing M_ files, and missing …
neon-nyan Apr 23, 2026
7b3f885
Update NuGet + Switch to preview WASDK
neon-nyan Apr 26, 2026
af44646
Fix binding on FFmpeg decoding mode settings
neon-nyan Apr 26, 2026
b8a5915
Fix binding on Image Event icon
neon-nyan Apr 26, 2026
dd6f624
Fix seeking on pause causing background position to reset
neon-nyan Apr 26, 2026
beed30b
Fix 0x8001010E Exception on ImageBackgroundManager.CanOpenCropOverlay
neon-nyan Apr 26, 2026
a04d41f
Fix Manual Game Switching doesn't reload the page
neon-nyan Apr 26, 2026
9a60396
Bump version to 1.84.2 + Remove x86-64-v2 target
neon-nyan Apr 26, 2026
72b4ec9
PLJ
Cryotechnic Apr 29, 2026
74ba6ba
Merge PInvoke resolver + Small prep for FFmpeg 8.x support
neon-nyan May 1, 2026
f997f32
Fix bind conversion warning on SettingsPage
neon-nyan May 1, 2026
cd70f6b
Update NuGet
neon-nyan May 1, 2026
1b3f5dc
Disable system media transport controls
shatyuka May 4, 2026
3917e0f
[skip ci] Sync translation Translate en_US.json in zh_CN
transifex-integration[bot] May 4, 2026
6c046f7
Merge branch 'main' of github.com:CollapseLauncher/Collapse
Cryotechnic May 5, 2026
69491a6
Fix file URL formatting and ensure successful HTTP response in plugin…
Cryotechnic May 5, 2026
41479dc
Add HighContrast theme resources
Cryotechnic May 5, 2026
4825662
Exclude voice-pack matching fields
Cryotechnic May 5, 2026
e0f89cc
Fix incorrect state display based on preload status
Cryotechnic May 5, 2026
9f0c763
[skip ci] Sync translation Translate en_US.json in ja_JP
transifex-integration[bot] May 5, 2026
88dd306
Fix WicCodec init on external image decoder
neon-nyan May 5, 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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 0 additions & 3 deletions CollapseLauncher.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@
<BuildType Solution="Publish|*" Project="Release" />
<Platform Project="x64" />
</Project>
<Project Path="Hi3Helper.SourceGen/Hi3Helper.SourceGen.csproj">
<BuildType Solution="Publish|*" Project="Release" />
</Project>
<Project Path="Hi3Helper.TaskScheduler/Hi3Helper.TaskScheduler.csproj">
<BuildType Solution="Publish|*" Project="Release" />
<Platform Project="x64" />
Expand Down
6 changes: 4 additions & 2 deletions CollapseLauncher/Classes/Extension/UIElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,16 @@ internal static T BindNavigationViewItemText<T>(this T element, object? localeOb
element.BindTooltipToLocale(localeObjBinding, localePropertyName);
}

internal static T BindTooltipToLocale<T>(this T element, object? localeObjBinding, string localePropertyName)
internal static T BindTooltipToLocale<T>(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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<VideoDecoderMode>();

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
{
Expand All @@ -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
{
Expand All @@ -68,6 +96,31 @@ public bool GlobalIsFFmpegCurrentlyUsed
}
}

public VideoDecoderMode GlobalFFmpegDecodingMode
{
get
{
string? value = LauncherConfig.GetAppConfigValue(GlobalFFmpegDecodingModeConfigKey);
if (Enum.TryParse<VideoDecoderMode>(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()
Expand All @@ -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)
Expand All @@ -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
{
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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)
{
Expand All @@ -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) &&
Expand All @@ -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;
}
Expand All @@ -232,6 +291,7 @@ internal static string[] GetFFmpegRequiredDllFilenames() =>
public static bool TryLinkFFmpegLibrary(
string? sourceDir,
string? targetDir,
FFmpegPInvoke.FFmpegLibraryNames libraries,
[NotNullWhen(false)]
out Exception? exception)
{
Expand All @@ -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) &&
Expand Down
Loading
Loading