Skip to content

Implement async preload download with per-file progress callbacks#865

Merged
neon-nyan merged 16 commits intomainfrom
feat/plugin-preload
Apr 5, 2026
Merged

Implement async preload download with per-file progress callbacks#865
neon-nyan merged 16 commits intomainfrom
feat/plugin-preload

Conversation

@Cryotechnic
Copy link
Copy Markdown
Member

@Cryotechnic Cryotechnic commented Mar 30, 2026

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 IGameInstallManager compliance.

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 :

  • Overall Status : Done
  • Commits : Done
  • Synced to base (Collapse:main) : Yes
  • Build status : OK
  • Crashing : No
  • Bug found caused by PR : 0

Templates

Changelog Prefixes
  **[New]**
  **[Imp]**
  **[Fix]**
  **[Loc]**
  **[Doc]**

Key changes include:

Per-file Progress Tracking and Plugin Integration

  • [New] Added support for per-file progress callbacks in 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]])
  • [New] Implemented new methods in PluginInfo to 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])
  • [Imp] Added guard in TryRegisterPerFileProgressCallback to prevent leaking a GCHandle if the method is called more than once without a matching unregister.
  • [Imp] Status.IsIncludePerFileIndicator is now set to _hasPerFileProgress after callback registration, so the UI correctly shows or hides the per-file progress indicator based on plugin capability.

Exception Handling and Stability

  • [Imp] Wrapped all reverse P/Invoke callbacks (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]])
  • [Fix] StartPackageDownload now rethrows general exceptions after logging and Sentry reporting, preventing silent failures that would cause the verify→install pipeline to proceed with incomplete files.
  • [Fix] Added general catch (Exception) block to StartPackageInstallation with Sentry reporting and rethrow, matching StartPackageDownload's error-handling pattern.
  • [Fix] IsPreloadCompleted now propagates OperationCanceledException instead of swallowing it as false, making cancellation distinguishable from "not completed".
  • [Fix] Status.IsCompleted is now only set to true on successful completion in both StartPackageDownload and StartPackageInstallation, tracked via a bool isSuccess flag. Previously it was set unconditionally in finally, making failures appear as completions.

Download and Installation Workflow Improvements

  • [Imp] Refactored StartPackageDownload and StartPackageInstallation to integrate per-file progress registration/unregistration and to provide detailed logging and status updates throughout the process. [1]], [2]])
  • [Imp] Improved fallback logic for progress reporting when per-file progress is not available, ensuring backward compatibility with older plugins. ([CollapseLauncher/Classes/Plugins/PluginGameInstallWrapper.csL298-R452])

Functional Enhancements

  • [New] Implemented the IsPreloadCompleted method 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])
  • [New] Implemented UpdateCompletenessStatus to manage IsRunning/IsCompleted/IsCanceled state, progress indeterminate flags, and Discord presence updates, matching the InstallManagerBase convention.
  • [New] Implemented CleanUpGameFiles to scan and remove residual temp/staging files under {GameDirPath}\TempPath\, with support for the FileCleanupPage dialog UI or direct deletion.
  • [Fix] Fixed Locale.LangLocale.Current.Lang for all localization string accesses in UpdateStatusCallback, since Lang is an instance property on the Locale singleton.

Dependency Updates

Miscellaneous

Cryotechnic and others added 6 commits March 18, 2026 06:05
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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = true is set in finally even when OperationCanceledException is caught/rethrown (so IsCanceled and IsCompleted can both become true). Align completion/cancel status updates with the ProgressBase pattern (completed only on success; cancelled sets IsCanceled and keeps IsCompleted false).
        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.

Copy link
Copy Markdown
Member

@neon-nyan neon-nyan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the changes should be looking good. Only few minor improvements required before this can be merged.

Comment on lines +604 to +612
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.

100% reviewed source file: 'en_US.json'
on 'ja_JP'.
Comment on lines +477 to +481
_updateProgressProperty.LastDownloaded = downloadedBytes;

if (!CheckIfNeedRefreshStopwatch())
{
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

@neon-nyan neon-nyan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@neon-nyan neon-nyan merged commit e02ab8b into main Apr 5, 2026
3 of 4 checks passed
@neon-nyan neon-nyan mentioned this pull request Apr 5, 2026
neon-nyan added a commit that referenced this pull request Apr 11, 2026
## Superseeding from #862 


![UpdateIsAvailable-Campaign-Columbina](https://github.com/user-attachments/assets/e7043018-4d49-48f2-adf1-3c846fd3c3e4)
# 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants