@@ -196,15 +196,73 @@ public async ValueTask<int> GetInstallationPath(bool isHasOnlyMigrateOption = fa
196196 return folder ;
197197 }
198198
199- public Task StartPackageDownload ( bool skipDialog = false )
199+ [ MethodImpl ( MethodImplOptions . NoOptimization | MethodImplOptions . NoInlining ) ]
200+ public async Task StartPackageDownload ( bool skipDialog = false )
200201 {
201- // NOP
202- return Task . CompletedTask ;
202+ ResetStatusAndProgressProperty ( ) ;
203+
204+ try
205+ {
206+ IsRunning = true ;
207+
208+ Status . IsProgressAllIndetermined = true ;
209+ Status . IsProgressPerFileIndetermined = true ;
210+ Status . IsRunning = true ;
211+ Status . IsIncludePerFileIndicator = false ;
212+ UpdateStatus ( ) ;
213+
214+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Step 1/5: InitPluginComAsync..." , LogType . Debug , true ) ;
215+ await _gameInstaller . InitPluginComAsync ( _plugin , Token ! . Token ) ;
216+
217+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Step 2/5: GetGameSizeAsync..." , LogType . Debug , true ) ;
218+ Guid cancelGuid = _plugin . RegisterCancelToken ( Token . Token ) ;
219+
220+ _gameInstaller . GetGameSizeAsync ( GameInstallerKind . Preload , in cancelGuid , out nint asyncSize ) ;
221+ long sizeToDownload = await asyncSize . AsTask < long > ( ) ;
222+ Logger . LogWriteLine ( $ "[PluginGameInstallWrapper::StartPackageDownload] Size to download: { sizeToDownload } ", LogType . Debug , true ) ;
223+
224+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Step 3/5: GetGameDownloadedSizeAsync..." , LogType . Debug , true ) ;
225+ _gameInstaller . GetGameDownloadedSizeAsync ( GameInstallerKind . Preload , in cancelGuid , out nint asyncDownloaded ) ;
226+ long sizeAlreadyDownloaded = await asyncDownloaded . AsTask < long > ( ) ;
227+ Logger . LogWriteLine ( $ "[PluginGameInstallWrapper::StartPackageDownload] Already downloaded: { sizeAlreadyDownloaded } ", LogType . Debug , true ) ;
228+
229+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Step 4/5: EnsureDiskSpaceAvailability..." , LogType . Debug , true ) ;
230+ await EnsureDiskSpaceAvailability ( GameManager . GameDirPath , sizeToDownload , sizeAlreadyDownloaded ) ;
231+
232+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Step 5/5: StartPreloadAsync..." , LogType . Debug , true ) ;
233+ _gameInstaller . StartPreloadAsync (
234+ _updateProgressDelegate ,
235+ _updateProgressStatusDelegate ,
236+ _plugin . RegisterCancelToken ( Token . Token ) ,
237+ out nint asyncPreload ) ;
238+
239+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Awaiting preload task..." , LogType . Debug , true ) ;
240+ await asyncPreload . AsTask ( ) . ConfigureAwait ( false ) ;
241+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Preload task completed." , LogType . Debug , true ) ;
242+ }
243+ catch ( OperationCanceledException )
244+ {
245+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Cancelled by user." , LogType . Warning , true ) ;
246+ Status . IsCanceled = true ;
247+ throw ;
248+ }
249+ catch ( Exception ex )
250+ {
251+ Logger . LogWriteLine ( $ "[PluginGameInstallWrapper::StartPackageDownload] Preload failed:\r \n { ex } ", LogType . Error , true ) ;
252+ SentryHelper . ExceptionHandler ( ex , SentryHelper . ExceptionType . UnhandledOther ) ;
253+ }
254+ finally
255+ {
256+ Logger . LogWriteLine ( "[PluginGameInstallWrapper::StartPackageDownload] Entering finally block." , LogType . Debug , true ) ;
257+ Status . IsCompleted = true ;
258+ IsRunning = false ;
259+ UpdateStatus ( ) ;
260+ }
203261 }
204262
205263 public ValueTask < int > StartPackageVerification ( List < GameInstallPackage > ? gamePackage = null )
206264 {
207- // NOP
265+ // NOP — preload download includes verification internally
208266 return new ValueTask < int > ( 1 ) ;
209267 }
210268
@@ -262,7 +320,7 @@ public async Task StartPackageInstallation()
262320
263321 await routineTask . ConfigureAwait ( false ) ;
264322 }
265- catch ( OperationCanceledException ) when ( Token ! . IsCancellationRequested )
323+ catch ( OperationCanceledException )
266324 {
267325 Status . IsCanceled = true ;
268326 throw ;
@@ -277,64 +335,93 @@ public async Task StartPackageInstallation()
277335
278336 private void UpdateProgressCallback ( in InstallProgress delegateProgress )
279337 {
280- using ( _updateStatusLock . EnterScope ( ) )
338+ // IMPORTANT: This callback is invoked via a function pointer from the NativeAOT plugin.
339+ // Any unhandled exception here crosses the reverse P/Invoke boundary and causes a
340+ // FailFast (STATUS_FAIL_FAST_EXCEPTION / exit code -1073741189). Must never throw.
341+ try
281342 {
282- _updateProgressProperty . StateCount = delegateProgress . StateCount ;
283- _updateProgressProperty . StateCountTotal = delegateProgress . TotalStateToComplete ;
343+ using ( _updateStatusLock . EnterScope ( ) )
344+ {
345+ _updateProgressProperty . StateCount = delegateProgress . StateCount ;
346+ _updateProgressProperty . StateCountTotal = delegateProgress . TotalStateToComplete ;
284347
285- _updateProgressProperty . AssetCount = delegateProgress . DownloadedCount ;
286- _updateProgressProperty . AssetCountTotal = delegateProgress . TotalCountToDownload ;
348+ _updateProgressProperty . AssetCount = delegateProgress . DownloadedCount ;
349+ _updateProgressProperty . AssetCountTotal = delegateProgress . TotalCountToDownload ;
287350
288- long downloadedBytes = delegateProgress . DownloadedBytes ;
289- long downloadedBytesTotal = delegateProgress . TotalBytesToDownload ;
351+ long downloadedBytes = delegateProgress . DownloadedBytes ;
352+ long downloadedBytesTotal = delegateProgress . TotalBytesToDownload ;
290353
291- long readDownload = delegateProgress . DownloadedBytes - _updateProgressProperty . LastDownloaded ;
292- double currentSpeed = CalculateSpeed ( readDownload ) ;
354+ long readDownload = delegateProgress . DownloadedBytes - _updateProgressProperty . LastDownloaded ;
355+ double currentSpeed = CalculateSpeed ( readDownload ) ;
293356
294- Progress . ProgressAllSizeCurrent = downloadedBytes ;
295- Progress . ProgressAllSizeTotal = downloadedBytesTotal ;
296- Progress . ProgressAllSpeed = currentSpeed ;
357+ Progress . ProgressAllSizeCurrent = downloadedBytes ;
358+ Progress . ProgressAllSizeTotal = downloadedBytesTotal ;
359+ Progress . ProgressAllSpeed = currentSpeed ;
297360
298- Progress . ProgressAllTimeLeft = ConverterTool
299- . ToTimeSpanRemain ( downloadedBytesTotal , downloadedBytes , currentSpeed ) ;
361+ // Mirror into per-file fields — plugin reports aggregate progress only,
362+ // but the preload UI has a left-side panel that reads PerFile values.
363+ Progress . ProgressPerFileSizeCurrent = downloadedBytes ;
364+ Progress . ProgressPerFileSizeTotal = downloadedBytesTotal ;
365+ Progress . ProgressPerFileSpeed = currentSpeed ;
300366
301- Progress . ProgressAllPercentage = ConverterTool . ToPercentage ( downloadedBytesTotal , downloadedBytes ) ;
367+ Progress . ProgressAllTimeLeft = downloadedBytesTotal > 0 && currentSpeed > 0
368+ ? ConverterTool . ToTimeSpanRemain ( downloadedBytesTotal , downloadedBytes , currentSpeed )
369+ : TimeSpan . Zero ;
302370
303- _updateProgressProperty . LastDownloaded = downloadedBytes ;
371+ Progress . ProgressAllPercentage = downloadedBytesTotal > 0
372+ ? ConverterTool . ToPercentage ( downloadedBytesTotal , downloadedBytes )
373+ : 0 ;
304374
305- if ( CheckIfNeedRefreshStopwatch ( ) )
306- {
307- return ;
308- }
375+ Progress . ProgressPerFilePercentage = Progress . ProgressAllPercentage ;
309376
310- if ( Status . IsProgressAllIndetermined )
311- {
312- Status . IsProgressAllIndetermined = false ;
313- Status . IsProgressPerFileIndetermined = false ;
314- UpdateStatus ( ) ;
315- }
377+ _updateProgressProperty . LastDownloaded = downloadedBytes ;
378+
379+ if ( ! CheckIfNeedRefreshStopwatch ( ) )
380+ {
381+ return ;
382+ }
383+
384+ if ( Status . IsProgressAllIndetermined )
385+ {
386+ Status . IsProgressAllIndetermined = false ;
387+ Status . IsProgressPerFileIndetermined = false ;
388+ UpdateStatus ( ) ;
389+ }
316390
317- UpdateProgress ( ) ;
391+ UpdateProgress ( ) ;
392+ }
393+ }
394+ catch ( Exception ex )
395+ {
396+ Logger . LogWriteLine ( $ "[PluginGameInstallWrapper::UpdateProgressCallback] Exception (swallowed to prevent FailFast):\r \n { ex } ", LogType . Error , true ) ;
318397 }
319398 }
320399
321400 private void UpdateStatusCallback ( InstallProgressState delegateState )
322401 {
323- using ( _updateStatusLock . EnterScope ( ) )
402+ // IMPORTANT: Same reverse P/Invoke boundary guard as UpdateProgressCallback above.
403+ try
324404 {
325- string stateString = delegateState switch
405+ using ( _updateStatusLock . EnterScope ( ) )
326406 {
327- InstallProgressState . Removing => string . Format ( "Deleting" + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal ) ,
328- InstallProgressState . Idle => Locale . Lang . _Misc . Idle ,
329- InstallProgressState . Install => string . Format ( Locale . Lang . _Misc . Extracting + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal ) ,
330- InstallProgressState . Verify or InstallProgressState . Preparing => string . Format ( Locale . Lang . _Misc . Verifying + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal ) ,
331- _ => string . Format ( ( ! _updateProgressProperty . IsUpdateMode ? Locale . Lang . _Misc . Downloading : Locale . Lang . _Misc . Updating ) + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal )
332- } ;
407+ string stateString = delegateState switch
408+ {
409+ InstallProgressState . Removing => string . Format ( "Deleting" + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal ) ,
410+ InstallProgressState . Idle => Locale . Lang . _Misc . Idle ,
411+ InstallProgressState . Install => string . Format ( Locale . Lang . _Misc . Extracting + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal ) ,
412+ InstallProgressState . Verify or InstallProgressState . Preparing => string . Format ( Locale . Lang . _Misc . Verifying + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal ) ,
413+ _ => string . Format ( ( ! _updateProgressProperty . IsUpdateMode ? Locale . Lang . _Misc . Downloading : Locale . Lang . _Misc . Updating ) + ": " + Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . StateCount , _updateProgressProperty . StateCountTotal )
414+ } ;
415+
416+ Status . ActivityStatus = stateString ;
417+ Status . ActivityAll = string . Format ( Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . AssetCount , _updateProgressProperty . AssetCountTotal ) ;
333418
334- Status . ActivityStatus = stateString ;
335- Status . ActivityAll = string . Format ( Locale . Lang . _Misc . PerFromTo , _updateProgressProperty . AssetCount , _updateProgressProperty . AssetCountTotal ) ;
336-
337- UpdateStatus ( ) ;
419+ UpdateStatus ( ) ;
420+ }
421+ }
422+ catch ( Exception ex )
423+ {
424+ Logger . LogWriteLine ( $ "[PluginGameInstallWrapper::UpdateStatusCallback] Exception (swallowed to prevent FailFast):\r \n { ex } ", LogType . Error , true ) ;
338425 }
339426 }
340427
@@ -412,12 +499,30 @@ public async ValueTask<bool> UninstallGame()
412499
413500 public void Flush ( ) => FlushingTrigger ? . Invoke ( this , EventArgs . Empty ) ;
414501
415- // TODO:
416- // Implement this after WuWa Plugin implementation is completed
417- public ValueTask < bool > IsPreloadCompleted ( CancellationToken token = default )
502+ public async ValueTask < bool > IsPreloadCompleted ( CancellationToken token = default )
418503 {
419- // NOP
420- return new ValueTask < bool > ( true ) ;
504+ try
505+ {
506+ await _gameInstaller . InitPluginComAsync ( _plugin , token ) ;
507+
508+ Guid cancelGuid = _plugin . RegisterCancelToken ( token ) ;
509+
510+ _gameInstaller . GetGameSizeAsync ( GameInstallerKind . Preload , in cancelGuid , out nint asyncTotal ) ;
511+ long totalSize = await asyncTotal . AsTask < long > ( ) ;
512+
513+ if ( totalSize <= 0 )
514+ return false ;
515+
516+ _gameInstaller . GetGameDownloadedSizeAsync ( GameInstallerKind . Preload , in cancelGuid , out nint asyncDownloaded ) ;
517+ long downloadedSize = await asyncDownloaded . AsTask < long > ( ) ;
518+
519+ return downloadedSize >= totalSize ;
520+ }
521+ catch ( Exception ex )
522+ {
523+ Logger . LogWriteLine ( $ "[PluginGameInstallWrapper::IsPreloadCompleted] Error checking preload status:\r \n { ex } ", LogType . Error , true ) ;
524+ return false ;
525+ }
421526 }
422527
423528 // TODO:
0 commit comments