@@ -68,7 +68,8 @@ private class XmfSenadinaFileIdentifierProperty(SenadinaFileIdentifier? blocksPl
6868 public CancellationToken CancelToken { get ; } = token ;
6969 }
7070
71- private string ? _mainMetaRepoUrl ;
71+ private string ? _primaryMainMetaRepoUrl ;
72+ private string ? _secondaryMainMetaRepoUrl ;
7273 private readonly byte [ ] _collapseHeader = "Collapse"u8 . ToArray ( ) ;
7374
7475 private async Task Fetch ( List < FilePropertiesRemote > assetIndex , CancellationToken token )
@@ -122,7 +123,8 @@ private async Task Fetch(List<FilePropertiesRemote> assetIndex, CancellationToke
122123 /* 2025-05-01: This is disabled for now as we now fully use MhyMurmurHash2_64B for the hash
123124 SenadinaFileIdentifier? asbReferenceSenadinaFileIdentifier = null;
124125 */
125- _mainMetaRepoUrl = null ;
126+ _primaryMainMetaRepoUrl = null ;
127+ _secondaryMainMetaRepoUrl = null ;
126128
127129 // Get the status if the current game is Senadina version.
128130 GameTypeHonkaiVersion gameVersionKind = GameVersionManager . CastAs < GameTypeHonkaiVersion > ( ) ;
@@ -132,20 +134,55 @@ private async Task Fetch(List<FilePropertiesRemote> assetIndex, CancellationToke
132134 // TODO: Use FallbackCDNUtil to fetch the stream.
133135 if ( IsSenadinaVersion && ! IsOnlyRecoverMain )
134136 {
135- _mainMetaRepoUrl = $ "https://r2.bagelnl.my.id/cl-meta/pustaka/{ GameVersionManager ! . GamePreset . ProfileName } /{ string . Join ( '.' , versionArray ) } ";
137+ _primaryMainMetaRepoUrl = $ "https://r2.bagelnl.my.id/cl-meta/pustaka/{ GameVersionManager ! . GamePreset . ProfileName } /{ string . Join ( '.' , versionArray ) } ";
138+ _secondaryMainMetaRepoUrl = $ "https://cdn.collapselauncher.com/cl-meta/pustaka/{ GameVersionManager ! . GamePreset . ProfileName } /{ string . Join ( '.' , versionArray ) } ";
136139
137140 // Get the Senadina File Identifier Dictionary and its file references
138- senadinaFileIdentifier = await GetSenadinaIdentifierDictionary ( client , _mainMetaRepoUrl , token ) ;
139- audioManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client , senadinaFileIdentifier ,
140- SenadinaKind . chiptunesCurrent , versionArray , _mainMetaRepoUrl , false , token ) ;
141- blocksPlatformManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client , senadinaFileIdentifier ,
142- SenadinaKind . platformBase , versionArray , _mainMetaRepoUrl , false , token ) ;
143- blocksBaseManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client , senadinaFileIdentifier ,
144- SenadinaKind . bricksBase , versionArray , _mainMetaRepoUrl , false , token ) ;
145- blocksCurrentManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client , senadinaFileIdentifier ,
146- SenadinaKind . bricksCurrent , versionArray , _mainMetaRepoUrl , false , token ) ;
147- patchConfigManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client , senadinaFileIdentifier ,
148- SenadinaKind . wandCurrent , versionArray , _mainMetaRepoUrl , true , token ) ;
141+ senadinaFileIdentifier = await GetSenadinaIdentifierDictionary ( client ,
142+ _primaryMainMetaRepoUrl ,
143+ _secondaryMainMetaRepoUrl ,
144+ false ,
145+ token ) ;
146+ audioManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client ,
147+ senadinaFileIdentifier ,
148+ SenadinaKind . chiptunesCurrent ,
149+ versionArray ,
150+ _primaryMainMetaRepoUrl ,
151+ _secondaryMainMetaRepoUrl ,
152+ false ,
153+ token ) ;
154+ blocksPlatformManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client ,
155+ senadinaFileIdentifier ,
156+ SenadinaKind . platformBase ,
157+ versionArray ,
158+ _primaryMainMetaRepoUrl ,
159+ _secondaryMainMetaRepoUrl ,
160+ false ,
161+ token ) ;
162+ blocksBaseManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client ,
163+ senadinaFileIdentifier ,
164+ SenadinaKind . bricksBase ,
165+ versionArray ,
166+ _primaryMainMetaRepoUrl ,
167+ _secondaryMainMetaRepoUrl ,
168+ false ,
169+ token ) ;
170+ blocksCurrentManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client ,
171+ senadinaFileIdentifier ,
172+ SenadinaKind . bricksCurrent ,
173+ versionArray ,
174+ _primaryMainMetaRepoUrl ,
175+ _secondaryMainMetaRepoUrl ,
176+ false ,
177+ token ) ;
178+ patchConfigManifestSenadinaFileIdentifier = await GetSenadinaIdentifierKind ( client ,
179+ senadinaFileIdentifier ,
180+ SenadinaKind . wandCurrent ,
181+ versionArray ,
182+ _primaryMainMetaRepoUrl ,
183+ _secondaryMainMetaRepoUrl ,
184+ true ,
185+ token ) ;
149186 /* 2025-05-01: This is disabled for now as we now fully use MhyMurmurHash2_64B for the hash
150187 asbReferenceSenadinaFileIdentifier = await GetSenadinaIdentifierKind(client, senadinaFileIdentifier,
151188 SenadinaKind.wonderland, versionArray, _mainMetaRepoUrl, true, token);
@@ -430,47 +467,88 @@ private static void ReorderAssetIndex(List<FilePropertiesRemote> assetIndex)
430467 assetIndex . AddRange ( assetIndexFiltered ) ;
431468 }
432469
433- private async Task < Dictionary < string , SenadinaFileIdentifier > ? > GetSenadinaIdentifierDictionary ( HttpClient client , string mainUrl , CancellationToken token )
470+ private async Task < Dictionary < string , SenadinaFileIdentifier > ? >
471+ GetSenadinaIdentifierDictionary ( HttpClient client ,
472+ string ? mainUrl ,
473+ string ? secondaryUrl ,
474+ bool throwIfFail = false ,
475+ CancellationToken token = default )
434476 {
435- string identifierUrl = CombineURLFromString ( mainUrl , "daftar-pustaka" ) ;
436- await using Stream fileIdentifierStream = ( await HttpResponseInputStream . CreateStreamAsync ( client , identifierUrl , null , null , null , null , null , token ) ) ! ;
437- await using Stream fileIdentifierStreamDecoder = new BrotliStream ( fileIdentifierStream , CompressionMode . Decompress , true ) ;
438-
439- await ThrowIfFileIsNotSenadina ( fileIdentifierStream , token ) ;
440- #if DEBUG
441- using StreamReader rd = new StreamReader ( fileIdentifierStreamDecoder ) ;
442- string response = await rd . ReadToEndAsync ( token ) ;
443- LogWriteLine ( $ "[HonkaiRepair::GetSenadinaIdentifierDictionary() Dictionary Response:\r \n { response } ", LogType . Debug , true ) ;
444- return response . Deserialize ( SenadinaJsonContext . Default . DictionaryStringSenadinaFileIdentifier ) ;
445- #else
446- return await fileIdentifierStreamDecoder . DeserializeAsync ( SenadinaJsonContext . Default . DictionaryStringSenadinaFileIdentifier , token : token ) ;
447- #endif
477+ mainUrl ??= secondaryUrl ;
478+ try
479+ {
480+ string identifierUrl = CombineURLFromString ( mainUrl , "daftar-pustaka" ) ;
481+ await using Stream fileIdentifierStream = ( await HttpResponseInputStream . CreateStreamAsync ( client , identifierUrl , null , null , null , null , null , token ) ) ! ;
482+ await using Stream fileIdentifierStreamDecoder = new BrotliStream ( fileIdentifierStream , CompressionMode . Decompress , true ) ;
483+
484+ await ThrowIfFileIsNotSenadina ( fileIdentifierStream , token ) ;
485+ #if DEBUG
486+ using StreamReader rd = new StreamReader ( fileIdentifierStreamDecoder ) ;
487+ string response = await rd . ReadToEndAsync ( token ) ;
488+ LogWriteLine ( $ "[HonkaiRepair::GetSenadinaIdentifierDictionary() Dictionary Response:\r \n { response } ", LogType . Debug , true ) ;
489+ return response . Deserialize ( SenadinaJsonContext . Default . DictionaryStringSenadinaFileIdentifier ) ;
490+ #else
491+ return await fileIdentifierStreamDecoder . DeserializeAsync ( SenadinaJsonContext . Default . DictionaryStringSenadinaFileIdentifier , token : token ) ;
492+ #endif
493+ }
494+ catch ( Exception ex )
495+ {
496+ LogWriteLine ( $ "[Senadina::DaftarPustaka] Failed while fetching Senadina's daftar-pustaka from URL: { mainUrl } \r \n { ex } ", LogType . Error , true ) ;
497+ if ( throwIfFail )
498+ {
499+ throw ;
500+ }
501+ LogWriteLine ( $ "[Senadina::DaftarPustaka] Trying to get Senadina's daftar-pustaka from secondary URL: { secondaryUrl } ", LogType . Warning , true ) ;
502+ return await GetSenadinaIdentifierDictionary ( client , null , secondaryUrl , true , token ) ;
503+ }
448504 }
449505
450- private async Task < SenadinaFileIdentifier ? > GetSenadinaIdentifierKind ( HttpClient client , Dictionary < string , SenadinaFileIdentifier > ? dict , SenadinaKind kind , int [ ] gameVersion , string mainUrl , bool skipThrow , CancellationToken token )
506+ private async Task < SenadinaFileIdentifier ? >
507+ GetSenadinaIdentifierKind ( HttpClient client ,
508+ Dictionary < string , SenadinaFileIdentifier > ? dict ,
509+ SenadinaKind kind ,
510+ int [ ] gameVersion ,
511+ string ? mainUrl ,
512+ string ? secondaryUrl ,
513+ bool skipThrow ,
514+ CancellationToken token )
451515 {
516+ mainUrl ??= secondaryUrl ;
452517 ArgumentNullException . ThrowIfNull ( dict ) ;
453-
454- string origFileRelativePath = $ "{ gameVersion [ 0 ] } _{ gameVersion [ 1 ] } _{ kind . ToString ( ) . ToLower ( ) } ";
455- string hashedRelativePath = SenadinaFileIdentifier . GetHashedString ( origFileRelativePath ) ;
456518
457- string fileUrl = CombineURLFromString ( mainUrl , hashedRelativePath ) ;
458- if ( ! dict . TryGetValue ( origFileRelativePath , out var identifier ) )
519+ try
459520 {
460- LogWriteLine ( $ "Key reference to the pustaka file: { hashedRelativePath } is not found for game version: { string . Join ( '.' , gameVersion ) } . Please contact us on our Discord Server to report this issue.", LogType . Error , true ) ;
461- if ( skipThrow ) return null ;
462- throw new
463- FileNotFoundException ( "Assets reference for repair is not found. " +
464- "Please contact us in GitHub issues or Discord to let us know about this issue." ) ;
465- }
521+ string origFileRelativePath = $ "{ gameVersion [ 0 ] } _{ gameVersion [ 1 ] } _{ kind . ToString ( ) . ToLower ( ) } ";
522+ string hashedRelativePath = SenadinaFileIdentifier . GetHashedString ( origFileRelativePath ) ;
523+
524+ string fileUrl = CombineURLFromString ( mainUrl , hashedRelativePath ) ;
525+ if ( ! dict . TryGetValue ( origFileRelativePath , out var identifier ) )
526+ {
527+ LogWriteLine ( $ "Key reference to the pustaka file: { hashedRelativePath } is not found for game version: { string . Join ( '.' , gameVersion ) } . Please contact us on our Discord Server to report this issue.", LogType . Error , true ) ;
528+ if ( skipThrow ) return null ;
529+ throw new
530+ FileNotFoundException ( "Assets reference for repair is not found. " +
531+ "Please contact us in GitHub issues or Discord to let us know about this issue." ) ;
532+ }
466533
467- Stream networkStream = ( await HttpResponseInputStream . CreateStreamAsync ( client , fileUrl , null , null , null , null , null , token ) ) ! ;
534+ Stream networkStream = ( await HttpResponseInputStream . CreateStreamAsync ( client , fileUrl , null , null , null , null , null , token ) ) ! ;
468535
469- await ThrowIfFileIsNotSenadina ( networkStream , token ) ;
470- identifier . fileStream = SenadinaFileIdentifier . CreateKangBakso ( networkStream , identifier . lastIdentifier ! , origFileRelativePath , ( int ) identifier . fileTime ) ;
471- identifier . relativePath = origFileRelativePath ;
536+ await ThrowIfFileIsNotSenadina ( networkStream , token ) ;
537+ identifier . fileStream = SenadinaFileIdentifier . CreateKangBakso ( networkStream , identifier . lastIdentifier ! , origFileRelativePath , ( int ) identifier . fileTime ) ;
538+ identifier . relativePath = origFileRelativePath ;
472539
473- return identifier ;
540+ return identifier ;
541+ }
542+ catch ( Exception ex )
543+ {
544+ LogWriteLine ( $ "[Senadina::Identifier] Failed while fetching Senadina's identifier kind: { kind } from URL: { mainUrl } \r \n { ex } ", LogType . Error , true ) ;
545+ if ( skipThrow )
546+ {
547+ throw ;
548+ }
549+ LogWriteLine ( $ "[Senadina::Identifier] Trying to get Senadina's identifier kind: { kind } from secondary URL: { secondaryUrl } ", LogType . Warning , true ) ;
550+ return await GetSenadinaIdentifierKind ( client , dict , kind , gameVersion , null , secondaryUrl , true , token ) ;
551+ }
474552 }
475553
476554 private async Task ThrowIfFileIsNotSenadina ( Stream stream , CancellationToken token )
0 commit comments