99using Hi3Helper . EncTool . Parser . KianaDispatch ;
1010using Hi3Helper . EncTool . Parser . Senadina ;
1111using Hi3Helper . Shared . ClassStruct ;
12+ using Hi3Helper . Sophon ;
1213using Microsoft . Win32 ;
1314using System ;
1415using System . Buffers . Binary ;
1516using System . Collections . Generic ;
1617using System . IO ;
18+ using System . IO . Hashing ;
1719using System . Linq ;
20+ using System . Net . Http ;
1821using System . Runtime . CompilerServices ;
1922using System . Runtime . InteropServices ;
23+ using System . Security . Cryptography ;
2024using System . Threading ;
2125using System . Threading . Tasks ;
2226// ReSharper disable StringLiteralTypo
@@ -113,7 +117,8 @@ private async Task FetchAssetFromGameAssetBundle(List<FilePropertiesRemote> asse
113117 . GetResultFromAction ( async result =>
114118 {
115119 assetListFromAudio . AddRange ( result ) ;
116- await FinalizeAudioAssetsPath ( assetListFromAudio ,
120+ await FinalizeAudioAssetsPath ( assetIndex ,
121+ assetListFromAudio ,
117122 senadinaResult . Audio ,
118123 token ) ;
119124 } ) ;
@@ -296,7 +301,8 @@ private void FinalizeVideoAssetsPath(List<FilePropertiesRemote> assetList)
296301 }
297302 }
298303
299- private async Task FinalizeAudioAssetsPath ( List < FilePropertiesRemote > assetList ,
304+ private async Task FinalizeAudioAssetsPath ( List < FilePropertiesRemote > originAssetList ,
305+ List < FilePropertiesRemote > assetList ,
300306 SenadinaFileIdentifier ? audioManifestIdentifier ,
301307 CancellationToken token )
302308 {
@@ -310,14 +316,18 @@ private async Task FinalizeAudioAssetsPath(List<FilePropertiesRemote> assetList,
310316 . FirstOrDefault ( x => x . Name . StartsWith ( "AUDIO_Default" ) ) ;
311317
312318 string audioBasePath = Path . Combine ( GamePath , AssetBundleExtension . RelativePathAudio ) ;
313-
314- if ( audioDefaultAsset != null )
319+ FilePropertiesRemote ? audioDefaultManifestAsset = originAssetList . FirstOrDefault ( x => x . N . EndsWith ( "AUDIO_Default_manifest.m" ) ) ;
320+
321+ // Edit: 2026-05-07
322+ // Starting from 8.8, AUDIO_Default_manifest.m must expect to be existed. So, if the file is missing or hash doesn't match,
323+ // perform download in the background. Then write AUDIO_Default_Version.txt after.
324+ string audioDefaultVersionPath = Path . Combine ( audioBasePath , "AUDIO_Default_Version.txt" ) ;
325+ string audioDefaultManifestPath = Path . Combine ( audioBasePath , "AUDIO_Default_manifest.m" ) ;
326+ if ( await EnsureAudioDefaultManifestExisted ( HttpClientGeneric , audioDefaultManifestAsset , audioDefaultManifestPath , token ) )
315327 {
316- ulong audioDefaultAssetHash = GetLongFromHashStr ( audioDefaultAsset . HashString ) ;
317- byte [ ] audioDefaultManifestBuffer = new byte [ 16 ] ;
318- audioDefaultAsset . Hash . CopyTo ( audioDefaultManifestBuffer ) ;
319- await File . WriteAllTextAsync ( Path . Combine ( audioBasePath , "AUDIO_Default_Version.txt" ) , $ "{ gameVersion } \t { audioDefaultAssetHash } ", token ) ;
320- await File . WriteAllBytesAsync ( Path . Combine ( audioBasePath , "AUDIO_Default_manifest.m" ) , audioDefaultManifestBuffer , token ) ;
328+ byte [ ] manifestContent = File . ReadAllBytes ( audioDefaultManifestPath ) ;
329+ ulong audioDefaultAssetHash = BinaryPrimitives . ReadUInt64BigEndian ( manifestContent . AsSpan ( 0 , 8 ) ) ;
330+ await File . WriteAllTextAsync ( audioDefaultVersionPath , $ "{ gameVersion } \t { audioDefaultAssetHash } ", token ) ;
321331 }
322332
323333 // Create Versioning file.
@@ -380,6 +390,46 @@ await audioManifestIdentifier
380390 }
381391 }
382392
393+ private async Task < bool > EnsureAudioDefaultManifestExisted (
394+ HttpClient client ,
395+ FilePropertiesRemote ? audioDefaultManifestAsset ,
396+ string audioDefaultManifestPath ,
397+ CancellationToken token )
398+ {
399+ if ( audioDefaultManifestAsset == null )
400+ {
401+ return false ;
402+ }
403+
404+ // Check current hash first. If not match, delete and redownload.
405+ if ( File . Exists ( audioDefaultManifestPath ) )
406+ {
407+ byte [ ] hashRemote = audioDefaultManifestAsset . CRCArray ;
408+ byte [ ] hashLocal = hashRemote . Length == 8 ?
409+ await GetHashAsync < XxHash64 > ( audioDefaultManifestPath , false , false , token ) :
410+ await GetCryptoHashAsync < MD5 > ( audioDefaultManifestPath , null , false , false , token ) ;
411+
412+ if ( ! IsBytesEqualReversible ( hashRemote , hashLocal ) )
413+ {
414+ new FileInfo ( audioDefaultManifestPath ) . TryDeleteFile ( ) ;
415+ }
416+ }
417+
418+ if ( ! File . Exists ( audioDefaultManifestPath ) )
419+ {
420+ if ( audioDefaultManifestAsset . AssociatedObject is SophonAsset )
421+ {
422+ await RepairAssetGenericSophonType ( audioDefaultManifestAsset , token ) ;
423+ }
424+ else
425+ {
426+ await RepairAssetGenericType ( client , audioDefaultManifestAsset , token ) ;
427+ }
428+ }
429+
430+ return true ;
431+ }
432+
383433 private async Task FinalizeBlockAssetsPath (
384434 List < FilePropertiesRemote > sourceAssetList ,
385435 List < FilePropertiesRemote > targetAssetList ,
0 commit comments