77using Hi3Helper . Win32 . Native . ManagedTools ;
88using System ;
99using System . Buffers ;
10- using System . Collections . Concurrent ;
1110using System . Collections . Generic ;
1211using System . IO ;
1312using System . IO . Hashing ;
@@ -57,18 +56,11 @@ private async Task Check(List<PkgVersionProperties> assetIndex, CancellationToke
5756 $ "Thread count set to { threadCount } .", LogType . Warning , true ) ;
5857 }
5958
60- ConcurrentDictionary < PkgVersionProperties , byte > runningTask = new ( ) ;
6159 // Await the task for parallel processing
6260 // and iterate assetIndex and check it using different method for each type and run it in parallel
6361 await Parallel . ForEachAsync ( assetIndex , new ParallelOptions { MaxDegreeOfParallelism = threadCount , CancellationToken = token } , async ( asset , threadToken ) =>
6462 {
65- if ( ! runningTask . TryAdd ( asset , 0 ) )
66- {
67- LogWriteLine ( $ "Found duplicated task for { asset . remoteURL } ! Skipping...", LogType . Warning , true ) ;
68- return ;
69- }
7063 await CheckAssetAllType ( asset , brokenAssetIndex , threadToken ) ;
71- runningTask . Remove ( asset , out _ ) ;
7264 } ) ;
7365 }
7466 catch ( AggregateException ex )
@@ -103,7 +95,7 @@ private void TryMoveAudioPersistent(IEnumerable<PkgVersionProperties> assetIndex
10395 {
10496 // Try to get the exclusion list of the audio (language specific) files
10597 string [ ] exclusionList = assetIndex
106- . Where ( x => x . isForceStoreInPersistent && x . remoteName
98+ . Where ( x => x . isPatch && x . remoteNamePersistent != null && x . remoteName
10799 . AsSpan ( )
108100 . EndsWith ( ".pck" ) )
109101 . Select ( x => x . remoteNamePersistent
@@ -114,7 +106,9 @@ private void TryMoveAudioPersistent(IEnumerable<PkgVersionProperties> assetIndex
114106 string audioAsbPath = Path . Combine ( GameStreamingAssetsPath , "AudioAssets" ) ;
115107 string audioPersistentPath = Path . Combine ( GamePersistentPath , "AudioAssets" ) ;
116108 if ( ! Directory . Exists ( audioPersistentPath ) ) return ;
117- if ( ! Directory . Exists ( audioAsbPath ) ) Directory . CreateDirectory ( audioAsbPath ) ;
109+
110+ // Create audio streaming asset directory anyway
111+ Directory . CreateDirectory ( audioAsbPath ) ;
118112
119113 // Get the list of audio language names from _gameVersionManager
120114 List < string > audioLangList = ( ( GameTypeGenshinVersion ) GameVersionManager ) . AudioVoiceLanguageList ;
@@ -137,7 +131,7 @@ private void TryMoveAudioPersistent(IEnumerable<PkgVersionProperties> assetIndex
137131 . Where ( x => ! exclusionList . Any ( y => x . AsSpan ( ) . EndsWith ( y . AsSpan ( ) ) ) ) )
138132 {
139133 // Trim the path name to get the generic languageName/filename form
140- string pathName = filePath . AsSpan ( ) . Slice ( audioPersistentPath . Length + 1 ) . ToString ( ) ;
134+ string pathName = filePath . AsSpan ( ) [ ( audioPersistentPath . Length + 1 ) .. ] . ToString ( ) ;
141135 // Combine the generic name with audioAsbPath
142136 string newPath = EnsureCreationOfDirectory ( Path . Combine ( audioAsbPath , pathName ) ) ;
143137
@@ -178,25 +172,9 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List<PkgVe
178172 FileInfo fileInfoPersistent = new FileInfo ( filePathPersistent ) . EnsureNoReadOnly ( ) ;
179173
180174 // Decide file state
181- bool isUsePersistent = asset . isForceStoreInPersistent ;
182- bool isPatch = asset . isPatch ;
183175 bool isPersistentExist = fileInfoPersistent . Exists ;
184176 bool isStreamingExist = fileInfoStreaming . Exists ;
185177
186- // Try to get hash buffer
187- bool isUseXxh64Hash = ! string . IsNullOrEmpty ( asset . xxh64hash ) ;
188- int hashBufferLen = ( isUseXxh64Hash ? asset . xxh64hash . Length : asset . md5 . Length ) / 2 ;
189- byte [ ] hashBuffer = ArrayPool < byte > . Shared . Rent ( hashBufferLen ) ;
190-
191- try
192- {
193- if ( ! HexTool . TryHexToBytesUnsafe ( asset . md5 , hashBuffer ) )
194- {
195- #if DEBUG
196- throw new InvalidOperationException ( ) ;
197- #endif
198- }
199-
200178 // Get the file info to use
201179 bool isUnmatchedFileFound = ! await IsFileMatched (
202180 a =>
@@ -222,8 +200,7 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List<PkgVe
222200
223201 PkgVersionProperties clonedAsset = asset . Clone ( ) ;
224202
225- Dispatch ( ( ) => AssetEntry . Add (
226- new AssetProperty < RepairAssetType > (
203+ Dispatch ( ( ) => AssetEntry . Add ( new AssetProperty < RepairAssetType > (
227204 Path . GetFileName ( c ) ,
228205 RepairAssetType . Unused ,
229206 Path . GetDirectoryName ( c ) ,
@@ -241,105 +218,157 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List<PkgVe
241218 }
242219 ) ;
243220
244- FileInfo fileInfoToUse = asset . isForceStoreInPersistent ? fileInfoPersistent : fileInfoStreaming ;
245-
246221 if ( isUnmatchedFileFound )
247222 {
248- AddNotFoundOrMismatchAsset ( asset , fileInfoToUse ) ;
223+ AddNotFoundOrMismatchAsset ( asset ) ;
249224 }
250- }
251- finally
252- {
253- ArrayPool < byte > . Shared . Return ( hashBuffer ) ;
254- }
255225
256226 return ;
257227
258228 async ValueTask < bool > IsFileMatched ( Action < bool > storeAsStreaming , Action < bool > storeAsPersistent , Action < string , string > storeAsUnused )
259229 {
260- if ( ! isUsePersistent )
230+ bool isUsePersistent = asset . isPatch ||
231+ ( ! asset . xxh64hashPersistent ? . Equals ( asset . xxh64hash , StringComparison . OrdinalIgnoreCase ) ?? false ) ;
232+
233+ // Try to get hash buffer
234+ byte [ ] hashBuffer = ArrayPool < byte > . Shared . Rent ( 16 ) ;
235+ try
261236 {
262- if ( isStreamingExist && await IsFileHashMatch ( fileInfoStreaming , hashBuffer , token ) )
237+ // Create memory and compare the hash
238+ string streamingHash = asset . xxh64hash ?? asset . md5 ;
239+ Memory < byte > streamingHashMemory = new ( hashBuffer , 0 , streamingHash . Length / 2 ) ;
240+ _ = HexTool . TryHexToBytesUnsafe ( streamingHash , streamingHashMemory . Span ) ;
241+
242+ if ( ! isUsePersistent )
263243 {
244+ if ( isPersistentExist )
245+ {
246+ storeAsUnused ( asset . remoteNamePersistent , fileInfoPersistent . FullName ) ;
247+ }
248+
249+ // ReSharper disable once InvertIf
250+ if ( ! isStreamingExist ||
251+ ! await IsFileHashMatch ( fileInfoStreaming , asset . fileSize , streamingHashMemory , true , token ) )
252+ {
253+ storeAsStreaming ( true ) ;
254+ storeAsPersistent ( false ) ;
255+ asset . isStreamingNeedDownload = true ;
256+ return false ;
257+ }
258+
264259 return true ;
265260 }
266261
267- if ( isPersistentExist )
262+ bool isBrokenFoundOnPersistent = false ;
263+ bool isHasStreamingInvalid = false ;
264+
265+ if ( ! asset . isPatch &&
266+ isStreamingExist &&
267+ ! await IsFileHashMatch ( fileInfoStreaming , asset . fileSize , streamingHashMemory , true , token ) )
268268 {
269- storeAsUnused ( asset . remoteNamePersistent , fileInfoPersistent . FullName ) ;
269+ storeAsStreaming ( true ) ;
270+ storeAsPersistent ( false ) ;
271+ isBrokenFoundOnPersistent = true ;
272+ isHasStreamingInvalid = true ;
273+ asset . isStreamingNeedDownload = true ;
270274 }
271275
272- storeAsStreaming ( true ) ;
273- storeAsPersistent ( false ) ;
274- return false ;
276+ // Create memory and compare the hash
277+ string persistentHash = asset . xxh64hashPersistent ?? asset . md5Persistent ;
278+ Memory < byte > persistentHashMemory = new ( hashBuffer , 0 , persistentHash . Length / 2 ) ;
279+ _ = HexTool . TryHexToBytesUnsafe ( persistentHash , persistentHashMemory . Span ) ;
275280
276- }
277-
278- bool isStreamingMatch = isStreamingExist && await IsFileHashMatch ( fileInfoStreaming , hashBuffer , token ) ;
279- bool isPersistentMatch = isPersistentExist && await IsFileHashMatch ( fileInfoPersistent , hashBuffer , token ) ;
281+ if ( isHasStreamingInvalid )
282+ {
283+ Interlocked . Add ( ref ProgressAllSizeCurrent , - fileInfoStreaming . Length ) ;
284+ }
280285
281- switch ( isStreamingMatch )
282- {
283- case true when isPersistentMatch && isPatch :
284- storeAsUnused ( asset . remoteName , fileInfoStreaming . FullName ) ;
285- break ;
286- case false when ! isPersistentMatch :
286+ // ReSharper disable once InvertIf
287+ if ( ! isPersistentExist || ! await IsFileHashMatch ( fileInfoPersistent , asset . fileSizePersistent , persistentHashMemory , true , token ) )
288+ {
287289 storeAsStreaming ( false ) ;
288290 storeAsPersistent ( true ) ;
289- return false ;
290- }
291+ isBrokenFoundOnPersistent = true ;
292+ asset . isPersistentNeedDownload = true ;
293+ }
291294
292- return true ;
295+ return ! isBrokenFoundOnPersistent ;
296+ }
297+ finally
298+ {
299+ ArrayPool < byte > . Shared . Return ( hashBuffer ) ;
300+ }
293301 }
294302
295- async ValueTask < bool > IsFileHashMatch ( FileInfo fileInfo , ReadOnlyMemory < byte > hashToCompare , CancellationToken cancelToken )
303+ async ValueTask < bool > IsFileHashMatch ( FileInfo fileInfo , long fileSizeToCheck , ReadOnlyMemory < byte > hashToCompare , bool isUpdateProgress , CancellationToken cancelToken )
296304 {
297305 // Refresh the fileInfo
298306 fileInfo . Refresh ( ) ;
299307
300- if ( fileInfo . Length != asset . fileSize ) return false ; // Skip the hash calculation if the file size is different
301-
308+ if ( fileInfo . Length != fileSizeToCheck )
309+ return false ; // Skip the hash calculation if the file size is different
310+
302311 if ( UseFastMethod ) return true ; // Skip the hash calculation if the fast method is enabled
303-
312+
304313 // Try to get filestream
305314 await using FileStream fileStream = await fileInfo . NaivelyOpenFileStreamAsync ( FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
306315
307316 // If pass the check above, then do hash calculation
308317 // Additional: the total file size progress is disabled and will be incremented after this
309318 byte [ ] localHash = hashToCompare . Length == 8 ?
310- await GetHashAsync < XxHash64 > ( fileStream , true , true , cancelToken ) :
311- await GetCryptoHashAsync < MD5 > ( fileStream , null , true , true , cancelToken ) ;
319+ await GetHashAsync < XxHash64 > ( fileStream , isUpdateProgress , isUpdateProgress , cancelToken ) :
320+ await GetCryptoHashAsync < MD5 > ( fileStream , null , isUpdateProgress , isUpdateProgress , cancelToken ) ;
312321
313322 // Check if the hash is equal
314323 bool isMatch = IsArrayMatch ( hashToCompare . Span , localHash ) ;
315324 return isMatch ;
316325 }
317326
318- void AddNotFoundOrMismatchAsset ( PkgVersionProperties assetInner , FileInfo repairFile )
327+ void AddNotFoundOrMismatchAsset ( PkgVersionProperties assetInner )
319328 {
320329 // Update the total progress and found counter
321- ProgressAllSizeFound += assetInner . fileSize ;
322- ProgressAllCountFound ++ ;
330+ if ( assetInner . isStreamingNeedDownload )
331+ {
332+ Interlocked . Increment ( ref ProgressAllCountFound ) ;
333+ Interlocked . Add ( ref ProgressAllSizeFound , assetInner . fileSize ) ;
334+
335+ Dispatch ( ( ) => AssetEntry . Add ( new AssetProperty < RepairAssetType > (
336+ Path . GetFileName ( assetInner . remoteName ) ,
337+ RepairAssetType . Generic ,
338+ Path . GetDirectoryName ( assetInner . remoteName ) ,
339+ assetInner . fileSize ,
340+ null ,
341+ HexTool . HexToBytesUnsafe ( string . IsNullOrEmpty ( assetInner . xxh64hash ) ? assetInner . md5 : assetInner . xxh64hash )
342+ )
343+ ) ) ;
323344
324- // Set the per size progress
325- ProgressPerFileSizeCurrent = assetInner . fileSize ;
345+ targetAssetIndex . Add ( assetInner ) ;
326346
327- // Increment the total current progress
328- ProgressAllSizeCurrent += assetInner . fileSize ;
347+ LogWriteLine ( $ "File [T: { RepairAssetType . Generic } -Streaming]: { assetInner . localName } is not found or has unmatched size",
348+ LogType . Warning , true ) ;
349+ }
329350
330- Dispatch ( ( ) => AssetEntry . Add (
331- new AssetProperty < RepairAssetType > (
332- repairFile . Name ,
351+ if ( ! assetInner . isPersistentNeedDownload )
352+ {
353+ return ;
354+ }
355+
356+ Interlocked . Increment ( ref ProgressAllCountFound ) ;
357+ Interlocked . Add ( ref ProgressAllSizeFound , assetInner . fileSizePersistent ) ;
358+
359+ Dispatch ( ( ) => AssetEntry . Add ( new AssetProperty < RepairAssetType > (
360+ Path . GetFileName ( assetInner . remoteNamePersistent ) ,
333361 RepairAssetType . Generic ,
334- Path . GetDirectoryName ( assetInner . isForceStoreInPersistent ? assetInner . remoteNamePersistent : assetInner . remoteName ) ,
335- assetInner . fileSize ,
336- repairFile . Exists ? hashBuffer : null ,
337- HexTool . HexToBytesUnsafe ( string . IsNullOrEmpty ( assetInner . xxh64hash ) ? assetInner . md5 : assetInner . xxh64hash )
362+ Path . GetDirectoryName ( assetInner . remoteNamePersistent ) ,
363+ assetInner . fileSizePersistent ,
364+ null ,
365+ HexTool . HexToBytesUnsafe ( string . IsNullOrEmpty ( assetInner . xxh64hashPersistent ) ? assetInner . md5Persistent : assetInner . xxh64hashPersistent )
338366 )
339367 ) ) ;
340- targetAssetIndex . Add ( assetInner ) ;
341368
342- LogWriteLine ( $ "File [T: { RepairAssetType . Generic } ]: { assetInner . localName } is not found or has unmatched size",
369+ targetAssetIndex . Add ( assetInner . CloneAsPersistent ( ) ) ;
370+
371+ LogWriteLine ( $ "File [T: { RepairAssetType . Generic } -Persistent]: { assetInner . localName } is not found or has unmatched size",
343372 LogType . Warning , true ) ;
344373 }
345374 }
0 commit comments