Implement async preload download with per-file progress callbacks#865
Implement async preload download with per-file progress callbacks#865
Conversation
Add full async preload download flow and harden progress/status callbacks. - Implement StartPackageDownload as an async method (with NoOptimization/NoInlining) that initializes the plugin COM, queries total and already-downloaded sizes, ensures disk space, starts the preload task and awaits it. Handles cancellation, exceptions, and updates Status/IsRunning and logging. - Implement IsPreloadCompleted to check preload completion by comparing downloaded vs total size via plugin calls (with error handling). - Add defensive try/catch around UpdateProgressCallback and UpdateStatusCallback to prevent unhandled exceptions from crossing reverse P/Invoke boundaries (which would cause FailFast). Mirror aggregate progress into per-file fields, compute speeds/time left safely, and log swallowed exceptions. - Update StartPackageVerification comment to note verification is included in preload, and relax an OperationCanceledException catch condition to always handle cancellations. - Add additional debug/error logging throughout to aid troubleshooting.
Register a native per-file progress callback for plugins and use it to populate per-file progress fields. - Introduces an unmanaged Cdecl delegate and GCHandle to hold a reverse P/Invoke callback (OnPerFileProgressCallback) and safe registration/unregistration helpers (TryRegisterPerFileProgressCallback, UnregisterPerFileProgressCallback). - Calls registration before starting preload/download and ensures unregistration in finally blocks. - OnPerFileProgressCallback updates per-file bytes/percentage inside a try/catch (must not throw when invoked from native AOT). - Adjusts progress reporting: when the plugin provides per-file updates, use that data (and only mirror speed from aggregate); otherwise fall back to mirroring aggregate values into per-file fields. - Adds PluginInfo.EnablePerFileProgressCallback and DisablePerFileProgressCallback which call the plugin export SetPerFileProgressCallback (Cdecl) and log failures.
There was a problem hiding this comment.
Pull request overview
This PR enhances the plugin-based install/preload workflow by adding per-file progress reporting support (via a new reverse P/Invoke callback), improving callback exception safety across the native boundary, and updating a few dependencies/localized strings.
Changes:
- Add per-file progress callback registration/unregistration to the plugin wrapper and wire it into download/install progress reporting.
- Guard reverse P/Invoke callbacks with try/catch to prevent FailFast crashes from unhandled exceptions.
- Update dependencies / lockfiles and adjust an English UI string.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| Hi3Helper.TaskScheduler/packages.lock.json | Adds Microsoft.NETFramework.ReferenceAssemblies to lockfile for build/reference resolution. |
| Hi3Helper.Core/Lang/en_US.json | Tweaks FFmpeg lock message wording. |
| CollapseLauncher/Classes/Plugins/PluginInfo.cs | Adds plugin export calls to set/unset the per-file progress callback. |
| CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs | Implements async preload download, registers per-file progress callback, and hardens reverse P/Invoke callbacks. |
Comments suppressed due to low confidence (1)
CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs:399
- Same issue here:
Status.IsCompleted = trueis set infinallyeven whenOperationCanceledExceptionis caught/rethrown (soIsCanceledandIsCompletedcan both become true). Align completion/cancel status updates with theProgressBasepattern (completed only on success; cancelled setsIsCanceledand keepsIsCompletedfalse).
catch (OperationCanceledException)
{
Status.IsCanceled = true;
throw;
}
finally
{
UnregisterPerFileProgressCallback();
Status.IsCompleted = true;
IsRunning = false;
UpdateStatus();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
neon-nyan
left a comment
There was a problem hiding this comment.
All the changes should be looking good. Only few minor improvements required before this can be merged.
| try | ||
| { | ||
| await _gameInstaller.InitPluginComAsync(_plugin, token); | ||
|
|
||
| Guid cancelGuid = _plugin.RegisterCancelToken(token); | ||
|
|
||
| _gameInstaller.GetGameSizeAsync(GameInstallerKind.Preload, in cancelGuid, out nint asyncTotal); | ||
| long totalSize = await asyncTotal.AsTask<long>(); | ||
|
|
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
100% reviewed source file: 'en_US.json' on 'ja_JP'.
| _updateProgressProperty.LastDownloaded = downloadedBytes; | ||
|
|
||
| if (!CheckIfNeedRefreshStopwatch()) | ||
| { | ||
| return; |
There was a problem hiding this comment.
Bug: The UpdateProgressCallback method lacks a null check for the delegateProgress parameter, which could cause a NullReferenceException if a null value is passed from the native P/Invoke boundary.
Severity: HIGH
Suggested Fix
Add a null check at the beginning of the UpdateProgressCallback method. If the delegateProgress parameter is null, the method should return immediately to prevent a NullReferenceException. This would align its implementation with the safer pattern used in OnPerFileProgressCallback.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.cs#L477-L481
Potential issue: In the `UpdateProgressCallback` method, the `delegateProgress`
parameter, which is passed by reference from a native P/Invoke boundary, is accessed
without a preceding null check. If the calling native plugin provides a null or invalid
pointer, attempting to access members of `delegateProgress` (e.g., `DownloadedBytes`)
will result in a `NullReferenceException`. This violates the method's documented
contract that it "Must never throw," which can lead to an unhandled exception and cause
the application to crash.
## Superseeding from #862  # Preview 1.84.1 (Codename: Columbina) ### Hewwo, it's neon-nyan here~ It's beeeeeen a while since the last 1.83.x update. For now, this release is focusing on more quality improvements, bug fixes and internal code reworks rather than new features as we are preparing on reworking Collapse for new codebase. That being said, this 1.84 update will be the marked as the last V1 release after roughly 4 years since the start of this project as we are going to move into V2 codebase starting this year (at Q3 or Q4 2026). Thank you so much for your continous support and interest in this project💖 Without further ado, let's dig into what's new in this release so far. # What's new? ## Reworked Background System Since months, HoYoverse has updated HoYoPlay to support multiple background to display, including static image and dynamic background ones. This has been our backlog since this release as due to "spaghetti-code" nature of our entire codebase, this made us harder to adapt the changes and thus making Collapse still only support one static background image. Thanks to [this massive rework](#862), we are now able to pull-off this feature by splitting the parts of the code into its own element, making it more easier and more manageable for the change and for incoming improvements. We are also moving to [**FFmpeg**](https://www.ffmpeg.org/) as our secondary library for background video decoder if no built-in codec is present. You will be prompted to install the FFmpeg library if none of the required built-in Windows Media Foundation codec for VP9 or any codec is present. > [**FFmpeg**](https://www.ffmpeg.org/legal.html) is licensed and distributed under [GNU Lesser General Public License, version 2.1 **(LGPLv2.1)**](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) [2026-04-11 16-22-41.webm](https://github.com/user-attachments/assets/2587251d-5132-4c83-a331-a7d75816cd5c) The experience might still be sluggish due to rushed implementation. But this will be improved in future updates🤞 ### Update: 2026/03/27 Due to corrupted video background situation in any regions for Zenless Zone Zero game, FFmpeg has been set as a default decoder for Collapse Launcher now (You can opt-in for using built-in Windows Media Foundation decoder, though). You might be asked to install a new one if you don't have any defined in your system's Environment Variable. [2026-03-27 11-55-13.webm](https://github.com/user-attachments/assets/670dd6d4-a3d3-44ba-9ee5-48866a0362da) ## Reworked Localization System #861 This is more like development-experience improvement rather than user focused ones. Previously in order to implement the localization for new elements, we have to manually map each class properties to represented JSON entries. Thanks to newly source-generated class mapper, every JSON entries will be mapped automatically. Each class properties can now be bind into UI element directly, making the UI able to update the visual itself rather than being told manually, one-by-one (which is expensive). ## Reworked Download Speed Limiter #859 The feature has been long broken since last 1.83.x release due to inconsistency and changes to other download-related libraries. Even though if you're enabling this feature, you might experience that the download speed "isn't actually being limited" and noticing that your bandwidth is still being fully utilized. This feature should now be fully fixed by decentralizing the code of the feature into its own library and making it easier to maintain. This feature could also be applied for any game plugins whose have v1-update4 API standard fully implemented. ## Minor UI Adjustments Not so noticeable UI changes at all. But it's worth to mention here. #### 1. News Carousel Design <img width="1282" height="457" alt="image" src="https://github.com/user-attachments/assets/3f3df571-63b5-42d5-9d54-6b6e005a8b83" /> #### 2. About Card <img width="810" height="535" alt="image" src="https://github.com/user-attachments/assets/66b826b0-219d-4733-ba49-8a600705ede1" /> ## Other New Changes - **[New]** Adding Files Clean-up Button to the Menu Bar, by @bagusnl <img width="334" height="139" alt="image" src="https://github.com/user-attachments/assets/50869299-c0dc-4888-9a5b-1819f05bed99" /> - **[Imp]** Switching Static Libraries Compiler from MSVC 19 to Clang 20.1, by @neon-nyan - **[Imp]** Caching Page Loading, by @neon-nyan - **[Imp]** Detach SDK Loading (for Sentry, Libsql.Client), by @neon-nyan - **[Imp]** Add Plugin Details (Breadcrumbs) to Sentry Reports, by @bagusnl - **[Imp]** Update overlay mask according to UI changes, by @shatyuka - **[New]** Implement async preload download with per-file progress callbacks #865, by @Cryotechnic - **[Fix]** Remove ZZZ Game Settings Resolution Limit, by @shatyuka - **[Fix]** Invalid Encoding while saving ZZZ Game Settings Profile (GENERAL_DATA.bin), by @shatyuka - **[Fix]** Deleted assets still getting downloaded while updating Honkai: Star Rail and Zenless Zone Zero, by @neon-nyan This caused by the filtering of the asset is still being redirected to FilterSophonPatchAssetList, which is unused rather than to FilterAssetList. This causes the supposedly deleted assets to get included, causing files to be redownloaded if not exist. - **[Fix]** Fix Honkai Impact 3rd cutscene detection on Game Repair, by @neon-nyan - **[Imp]** Localization updates, by localizers 🥳 - de-DE - German (Progress: 100%) - es-419 - Spanish (Latin America)(Progress: 100%) - fr-FR - French (Progress: 98%) - id-ID - Bahasa Indonesia (Progress: 100%) - it-IT - Italian (Progress: 45%) - ja-JP - Japanese (Progress: 100%) - ko-KR - Korean (Progress: 87%) - nl-NL - Dutch (Progress: 100%) - pl-PL - Polish (Progress: 55%) - pt-BR - Portuguese (Brazil)(Progress: 72%) - pt-PT - Portuguese (Portugal)(Progress: 65%) - ru-RU - Russian (Progress: 76%) - th-TH - Thai (Progress: 95%) - uk-UA - Ukranian (Progress: 84%) - zh-CN - Chinese Simplified (Progress: 100%) - zh-TW - Chinese Traditional (Progress: 61%) ## PR Status : - Overall Status : Done - Commits : Done - Synced to base (Collapse:main) : Yes - Build status : OK - Crashing : No - Bug found caused by PR : - - Known Issues: - ~**[Regression][High Priority]** Game Config's API is not changed while changing the games (Assignee: @neon-nyan)~ Fixed as per 052cb94 - ~**[High Priority]** Fix deleted assets still getting downloaded while updating Honkai: Star Rail and Zenless Zone Zero (Assignee: @neon-nyan)~ Fixed as per f87ef76 - Backport the changes to Stable branch - ~**[High Priority]** Adjust Video Assets metadata parser for Honkai Impact 3rd v8.8 (Assignee: @neon-nyan)~ Fixed as per bd26158 - Adjust struct field position for video availability flags - Fix LINQ selector on Collapse side to ignore unavailable flags - Reduce dependency on HTTP Status Code checks and use flags from video asset metadata instead. - Backport the changes to Stable branch - ~**[Medium Priority]** Fix gateway URL for Game Repair and Cache Update for Honkai: Star Rail (Assingee: @neon-nyan)~ Require further investigation - Backport the changes to Stable branch ### Templates <details> <summary>Changelog Prefixes</summary> ``` **[New]** **[Imp]** **[Fix]** **[Loc]** **[Doc]** ``` </details>

Main Goal
Implement robust plugin-based preload, install, and update workflows with per-file progress tracking, proper exception safety across P/Invoke boundaries, and full
IGameInstallManagercompliance.This pull request introduces significant improvements to the plugin-based game installation system, with a focus on adding robust per-file progress tracking and enhancing exception safety across reverse P/Invoke boundaries.
PR Status :
Templates
Changelog Prefixes
Key changes include:
Per-file Progress Tracking and Plugin Integration
PluginGameInstallWrapper, enabling more granular progress reporting during game installation and download. This includes defining a new delegate, registering/unregistering the callback with the plugin, and updating progress fields accordingly. [1]], [2]], [3]], [4]], [5]])PluginInfoto register and unregister the per-file progress callback with the plugin via exported functions, with error handling and logging. ([CollapseLauncher/Classes/Plugins/PluginInfo.csR332-R368])TryRegisterPerFileProgressCallbackto prevent leaking a GCHandle if the method is called more than once without a matching unregister.Status.IsIncludePerFileIndicatoris now set to_hasPerFileProgressafter callback registration, so the UI correctly shows or hides the per-file progress indicator based on plugin capability.Exception Handling and Stability
UpdateProgressCallback,UpdateStatusCallback, and the new per-file progress callback) in try/catch blocks to prevent unhandled exceptions from causing FailFast crashes, and added logging for swallowed exceptions. [1]], [2]], [3]], [4]])StartPackageDownloadnow rethrows general exceptions after logging and Sentry reporting, preventing silent failures that would cause the verify→install pipeline to proceed with incomplete files.catch (Exception)block toStartPackageInstallationwith Sentry reporting and rethrow, matchingStartPackageDownload's error-handling pattern.IsPreloadCompletednow propagatesOperationCanceledExceptioninstead of swallowing it asfalse, making cancellation distinguishable from "not completed".Status.IsCompletedis now only set totrueon successful completion in bothStartPackageDownloadandStartPackageInstallation, tracked via abool isSuccessflag. Previously it was set unconditionally infinally, making failures appear as completions.Download and Installation Workflow Improvements
StartPackageDownloadandStartPackageInstallationto integrate per-file progress registration/unregistration and to provide detailed logging and status updates throughout the process. [1]], [2]])Functional Enhancements
IsPreloadCompletedmethod to accurately check if a preload download is complete by querying the plugin for total and downloaded sizes, with error handling. ([CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.csL415-R598])UpdateCompletenessStatusto manageIsRunning/IsCompleted/IsCanceledstate, progress indeterminate flags, and Discord presence updates, matching theInstallManagerBaseconvention.CleanUpGameFilesto scan and remove residual temp/staging files under{GameDirPath}\TempPath\, with support for theFileCleanupPagedialog UI or direct deletion.Locale.Lang→Locale.Current.Langfor all localization string accesses inUpdateStatusCallback, sinceLangis an instance property on theLocalesingleton.Dependency Updates
Microsoft.NET.ILLink.TasksNuGet package from version 10.0.4 to 10.0.5 across multiple projects. [1]], [2]], [3]], [4]], [5]])Microsoft.NETFramework.ReferenceAssembliesversion 1.0.3 as a new dependency inHi3Helper.TaskScheduler. ([Hi3Helper.TaskScheduler/packages.lock.jsonR20-R28])Miscellaneous
en_US.json. ([Hi3Helper.Core/Lang/en_US.jsonL563-R563])