Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
82e9188
Update submodules
Cryotechnic Aug 10, 2026
7c1a411
feat(settings): render plugin game settings pages
Cryotechnic Aug 10, 2026
847a75f
Hydrate background activity notification state
Cryotechnic Aug 18, 2026
6ddfd39
Reset install speed baseline on state changes
Cryotechnic Aug 19, 2026
3945b9d
Bump submodules
Cryotechnic Aug 19, 2026
90c9c1a
Fix opacity causing elements not to render correctly
Cryotechnic Aug 19, 2026
e46deea
Improve plugin settings layout
Cryotechnic Sep 7, 2026
88598cf
Merge remote-tracking branch 'https/main' into plugin
bagusnl Oct 1, 2026
db9f9b1
Use FFmpeg for YUV420 10-bit video streams
Cryotechnic Sep 29, 2026
bebdbc8
Harden video frame init and native cleanup
Cryotechnic Sep 29, 2026
cc870b7
Harden layered video frame initialization
Cryotechnic Sep 29, 2026
bdaaf91
bump submodules
Cryotechnic Sep 29, 2026
961f6d2
Queue `UseFfmpegDecoder` property via `DispatcherQueue`
Cryotechnic Sep 30, 2026
07fc91b
Fix HRESULT handling in unsafe video frame copying
Cryotechnic Sep 30, 2026
e6d253a
Guard game settings navigation init state
Cryotechnic Sep 30, 2026
7950349
Fix layered video render locking
Cryotechnic Sep 30, 2026
a6828bb
Unify plugin game settings design
neon-nyan Sep 30, 2026
b550fb4
Add custom launch args section to plugin settings
Cryotechnic Sep 30, 2026
ff37d4d
[BG] Revert changes (Fix UI freeze due to lock and other changes)
neon-nyan Oct 1, 2026
f988e48
[BG] Revert using CanvasVirtualImageSource
neon-nyan Oct 1, 2026
feb26ce
[BG] Off-load CanvasDrawingSession outside of UI Thread
neon-nyan Oct 1, 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
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ private static void AttachEventToNotification(PresetConfig presetConfig, IBackgr
activity!.ProgressChanged += progressChangedEventHandler;
activity!.StatusChanged += statusChangedEventHandler;

// The notification can be attached after the operation has already
// started. Hydrate it immediately instead of waiting for another
// progress/status event and leaving the placeholder text visible.
progressChangedEventHandler(activity, activity.Progress);
statusChangedEventHandler(activity, activity.Status);

activity.FlushingTrigger += (_, _) =>
{
activity.ProgressChanged -= progressChangedEventHandler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ public partial class ImageBackgroundManager

// -- Check using FFmpeg if available
MediaSupport codecInfo = FFmpegPInvoke.GetMediaSupport(filePath);
PixelColorModel pixelFormat = codecInfo.VideoPixelFormatInfo.ColorModel;
if ((GlobalIsUseFFmpeg && GlobalIsFFmpegAvailable) ||
(codecInfo.Video.DecoderAvailable && pixelFormat.HasFlag(PixelColorModel.Rgb)))
RequiresFFmpegDecoder(codecInfo))
{
return (true, true, true);
}
Expand All @@ -77,6 +76,14 @@ public partial class ImageBackgroundManager

#endregion

private static bool RequiresFFmpegDecoder(MediaSupport codecInfo)
{
// Windows' H.264 decoder does not support the High 10 profile used by some backgrounds.
return codecInfo.Video.DecoderAvailable &&
(codecInfo.VideoPixelFormatInfo.ColorModel.HasFlag(PixelColorModel.Rgb) ||
codecInfo.VideoPixelFormatInfo.Format is AVPixelFormat.AV_PIX_FMT_YUV420P10LE or AVPixelFormat.AV_PIX_FMT_YUV420P10BE);
}

private static bool IsMediaFileExtensionSupported(ReadOnlySpan<char> filePath)
{
return IsImageMediaFileExtensionSupported(filePath) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CollapseLauncher.Extension;
using CollapseLauncher.Extension;
using CollapseLauncher.Helper;
using CollapseLauncher.Helper.Background;
using CollapseLauncher.Helper.Image;
Expand Down Expand Up @@ -154,8 +154,7 @@ public void PreloadBackground(PresetConfig? preset, Grid? presenterGrid)
bool isVideo = IsVideoMediaFileExtensionSupported(primaryBg ?? staticBgUrl!);

MediaSupport codecInfo = isVideo ? FFmpegPInvoke.GetMediaSupport(localBackground) : default;
PixelColorModel pixelFormat = codecInfo.VideoPixelFormatInfo.ColorModel;
if (isUseFFmpeg || (codecInfo.Video.DecoderAvailable && pixelFormat.HasFlag(PixelColorModel.Rgb)))
if (isUseFFmpeg || RequiresFFmpegDecoder(codecInfo))
{
isUseFFmpeg = true;
}
Expand Down Expand Up @@ -367,6 +366,7 @@ private void SpawnImageLayer(Uri? overlayFilePath,

if (!forceReload &&
CurrentBackgroundElement != null &&
CurrentBackgroundElement.UseFfmpegDecoder == (forceFFmpeg || (GlobalIsUseFFmpeg && GlobalIsFFmpegAvailable)) &&
_displayedContext != null)
{
if (context.Equals(_displayedContext))
Expand Down Expand Up @@ -448,9 +448,10 @@ private LayeredBackgroundImage CreateLayerElement(Uri?
{
LayeredBackgroundImage layerElement = new()
{
Opacity = 0,
BackgroundSource = backgroundFilePath,
BackgroundStaticSource = backgroundStaticFilePath,
ForegroundSource = overlayFilePath,
ForegroundSource = isVideo ? overlayFilePath : null,
UseFfmpegDecoder = forceFFmpeg || (GlobalIsUseFFmpeg && GlobalIsFFmpegAvailable),
Tag = context,
ParallaxResetOnUnfocused = false,
Expand Down Expand Up @@ -568,6 +569,8 @@ private void LayerElementOnLoaded(LayeredBackgroundImage layerElement)
PresenterGrid.Background = null;
}

layerElement.Opacity = 1;

if (CurrentIsEnableBackgroundAutoPlay && WindowUtility.CurrentWindowIsVisible)
{
Play(false);
Expand Down Expand Up @@ -676,4 +679,4 @@ private async Task GetMediaAccentColor(object? context)
SentryHelper.ExceptionHandler(ex);
}
}
}
}
7 changes: 7 additions & 0 deletions CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,13 @@ protected virtual void UpdateProgressCopyStream(long currentPosition, int read,

protected double CalculateSpeed(long receivedBytes) => CalculateSpeed(receivedBytes, ref _scLastSpeed, ref _scLastReceivedBytes, ref _scLastTick);

protected void ResetSpeedCalculator()
{
Interlocked.Exchange(ref _scLastReceivedBytes, 0);
Interlocked.Exchange(ref _scLastTick, Environment.TickCount64);
_scLastSpeed = 0;
}

protected static double CalculateSpeed(long receivedBytes, ref double lastSpeedToUse, ref long lastReceivedBytesToUse, ref long lastTickToUse)
{
long currentTick = Environment.TickCount64 - lastTickToUse + 1;
Expand Down
2 changes: 2 additions & 0 deletions CollapseLauncher/Classes/Interfaces/IBackgroundActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ internal interface IBackgroundActivity
event EventHandler<TotalPerFileStatus> StatusChanged;
event EventHandler FlushingTrigger;

TotalPerFileProgress Progress { get; }
TotalPerFileStatus Status { get; }
bool IsRunning { get; }
UIElement ParentUI { get; }
void CancelRoutine();
Expand Down
37 changes: 33 additions & 4 deletions CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
private InstallProgressState _currentInstallState = InstallProgressState.Idle;
private string _lastActivityStatus = string.Empty;
private float _lastLoggedPercentage = -1f;
private bool _resetSpeedBaseline = true;

private PerFileProgressCallbackNative? _perFileProgressDelegate;
private GCHandle _perFileProgressGcHandle;
Expand Down Expand Up @@ -212,13 +213,13 @@
return _currentInstallState switch
{
InstallProgressState.Reconciling => "Reconciling sources",
InstallProgressState.Verify or InstallProgressState.Preparing => Locale.Current.Lang._Misc.Verifying,

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 216 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.
InstallProgressState.Idle => Locale.Current.Lang._Misc.Idle,

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 217 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.
InstallProgressState.Removing => "Deleting",
InstallProgressState.Install => Locale.Current.Lang._Misc.Extracting,

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 219 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.
InstallProgressState.Download => !_updateProgressProperty.IsUpdateMode
? Locale.Current.Lang._Misc.Downloading

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 221 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.
: Locale.Current.Lang._Misc.Updating,

Check warning on line 222 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 222 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 222 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 222 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.

Check warning on line 222 in CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs

View workflow job for this annotation

GitHub Actions / build-nativeaot (Debug, x64, net10.0-windows10.0.26100.0)

Dereference of a possibly null reference.
InstallProgressState.Updating => Locale.Current.Lang._Misc.Updating,
_ => !_updateProgressProperty.IsUpdateMode
? Locale.Current.Lang._Misc.Downloading
Expand Down Expand Up @@ -558,7 +559,18 @@
long downloadedBytes = delegateProgress.DownloadedBytes;
long downloadedBytesTotal = delegateProgress.TotalBytesToDownload;

long readDownload = delegateProgress.DownloadedBytes - _updateProgressProperty.LastDownloaded;
long readDownload = 0;
if (_resetSpeedBaseline)
{
_resetSpeedBaseline = false;
_updateProgressProperty.LastDownloaded = downloadedBytes;
}
else if (downloadedBytes >= _updateProgressProperty.LastDownloaded)
{
readDownload = downloadedBytes - _updateProgressProperty.LastDownloaded;
_updateProgressProperty.LastDownloaded = downloadedBytes;
}

double currentSpeed = CalculateSpeed(readDownload);

Progress.ProgressAllSizeCurrent = downloadedBytes;
Expand Down Expand Up @@ -605,8 +617,6 @@
: 0;
}

_updateProgressProperty.LastDownloaded = downloadedBytes;

PublishProgressUi(updateProgressBar: true);
}
}
Expand All @@ -623,7 +633,26 @@
{
using (_updateStatusLock.EnterScope())
{
_currentInstallState = delegateState;
if (_currentInstallState != delegateState)
{
_currentInstallState = delegateState;
_resetSpeedBaseline = true;
ResetSpeedCalculator();
Progress.ProgressAllSpeed = 0;
}

if (delegateState == InstallProgressState.Completed)
{
Progress.ProgressAllTimeLeft = TimeSpan.Zero;
if (Progress.ProgressPerFileSizeTotal > 0)
{
Progress.ProgressPerFilePercentage = Math.Min(100d,
ConverterTool.ToPercentage(
Progress.ProgressPerFileSizeTotal,
Progress.ProgressPerFileSizeCurrent));
}
}

ApplyActivityStatusFromProperty();
PublishProgressUi(updateProgressBar: true);
}
Expand Down
2 changes: 2 additions & 0 deletions CollapseLauncher/Classes/Plugins/PluginPresetConfigWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public partial class PluginPresetConfigWrapper : PresetConfig, IDisposable
{
public DiscordPresenceExtension.DiscordPresenceContext DiscordPresenceContext { get; }
public GameManagerExtension.RunGameFromGameManagerContext RunGameContext { get; }
public GameSettingsExtension.GameSettingsContext GameSettingsContext { get; }
public readonly PluginInfo PluginInfo;
public readonly IPlugin Plugin;
private readonly IPluginPresetConfig _config;
Expand All @@ -50,6 +51,7 @@ private unsafe PluginPresetConfigWrapper(PluginInfo pluginInfo, IPluginPresetCon
};

DiscordPresenceContext = new DiscordPresenceExtension.DiscordPresenceContext(pluginInfo.Handle, config);
GameSettingsContext = new GameSettingsExtension.GameSettingsContext(pluginInfo.Handle, config);
}

public unsafe GameManagerExtension.RunGameFromGameManagerContext UseToggledGameLaunchContext()
Expand Down
17 changes: 13 additions & 4 deletions CollapseLauncher/XAMLs/MainApp/MainPage.Navigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,13 @@ void Impl()
GameNameType.StarRail => typeof(StarRailGameSettingsPage),
GameNameType.Genshin => typeof(GenshinGameSettingsPage),
GameNameType.Zenless => typeof(ZenlessGameSettingsPage),
GameNameType.Plugin when presetConfig is PluginPresetConfigWrapper
{ GameSettingsContext.HasPage: true } => typeof(PluginGameSettingsPage),
_ => null
};

NavigationViewItemsContext.GameSettingsPage.Item.Tag = gspPageType;
NavigationViewItemsContext.GameSettingsPage.Item.Visibility = isPluginGame ? Visibility.Collapsed : Visibility.Visible;
NavigationViewItemsContext.GameSettingsPage.Item.Visibility = gspPageType == null ? Visibility.Collapsed : Visibility.Visible;
NavigationViewItemsContext.FileCleanupPage.Item.Visibility = isPluginGame ? Visibility.Collapsed : Visibility.Visible;
}
}
Expand Down Expand Up @@ -378,10 +380,17 @@ typeOfPageObj is Type currentPageType &&
{
case Type pageType:
{
if (pageType.Name.EndsWith("GameSettingsPage") &&
!IsGameInstalled())
if (pageType.Name.EndsWith("GameSettingsPage"))
{
pageType = typeof(NotInstalledPage);
if (!IsLoadRegionComplete)
{
return false;
}

if (!IsGameInstalled())
{
pageType = typeof(NotInstalledPage);
}
}

LauncherFrame.Navigate(pageType, null, transitionInfo ?? new DrillInNavigationTransitionInfo());
Expand Down
Loading
Loading