Skip to content

Commit 9310b63

Browse files
authored
Preview 1.84.3 - Emergency Hotfix (#877)
![UpdateIsAvailable-Campaign-Columbina](https://github.com/user-attachments/assets/e7043018-4d49-48f2-adf1-3c846fd3c3e4) # Preview 1.84.3 - Emergency Hotfix (Codename: Columbina) ## What's changed? - **[Fix]** [Fix crash when started with other arguments](87be59a), by @neon-nyan - **[Fix][Background Image]** [Crash if WIC Codec is unavailable](f1491e1), by @neon-nyan - Removing the use of WIC Codec on MagicScaler (External image decoder) - **[Fix][Hi3 Game Repair]** [Fix Audio_Default_Manifest.m always get detected as broken asset](5d407b0), by @neon-nyan - **[Imp][Hi3 Game Repair]** [Verbose error report on game repair for `HttpRequestException`](f804cb1), by @neon-nyan - **[Fix][Hi3 Game Repair]** [Game Asset URL reporting invalid `streamingasb:80` URL while downloading in some cases](9113085), by @neon-nyan - **[Imp]** Localization updates, by localizers 🥳 - de-DE - German (Progress: 99%) - es-419 - Spanish (Latin America)(Progress: 99%) - fr-FR - French (Progress: 98%) - id-ID - Bahasa Indonesia (Progress: 100%) - it-IT - Italian (Progress: 44%) - ja-JP - Japanese (Progress: 100%) - ko-KR - Korean (Progress: 90%) - nl-NL - Dutch (Progress: 99%) - pl-PL - Polish (Progress: 55%) - pt-BR - Portuguese (Brazil)(Progress: 72%) - pt-PT - Portuguese (Portugal)(Progress: 65%) - ru-RU - Russian (Progress: 75%) - th-TH - Thai (Progress: 94%) - uk-UA - Ukrainian (Progress: 84%) - zh-CN - Chinese Simplified (Progress: 100%) - zh-TW - Chinese Traditional (Progress: 60%) ## Known Issues - [Zenless Zone Zero] Attempts to download all voice packs on update in some occasion. - [Honkai: Star Rail] Some users may reported `InvalidOperationException: An invalid request URI was provided. Either the request URI must be an absolute URI or BaseAddress must be set.` error while performing Game Repair **Full Changelog**: CL-v1.84.2-pre...CL-v1.84.3-pre # Code Signing Policy > Free code signing provided by [SignPath.io], certificate by [SignPath Foundation] - This program will not transfer any information to other networked systems. - Read our full [**Privacy Policy**](https://github.com/CollapseLauncher/Collapse/blob/main/PRIVACY.md) - Also read our [**Third Party Notices**](https://github.com/CollapseLauncher/Collapse/blob/main/THIRD_PARTY_NOTICES.md) for license used by third party libraries that we use. [SignPath Foundation]:https://signpath.org [SignPath.io]:https://signpath.io
2 parents 7bdcefe + cb56655 commit 9310b63

19 files changed

Lines changed: 347 additions & 348 deletions

CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using CollapseLauncher.Helper;
22
using CollapseLauncher.Helper.Metadata;
33
using CollapseLauncher.Interfaces;
4+
using CollapseLauncher.RepairManagement;
45
using Hi3Helper;
56
using Hi3Helper.EncTool;
67
using Hi3Helper.EncTool.Parser.KianaDispatch;
@@ -19,6 +20,7 @@
1920
// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault
2021
// ReSharper disable CommentTypo
2122
// ReSharper disable StringLiteralTypo
23+
#pragma warning disable IDE0130
2224

2325
namespace CollapseLauncher
2426
{
@@ -36,6 +38,11 @@ private async Task<List<CacheAsset>> Fetch(CancellationToken token)
3638
.SetAllowedDecompression(DecompressionMethods.None)
3739
.Create();
3840

41+
#if DEBUG
42+
// Assign Dispatcher Logger for Debug
43+
KianaDispatch.DebugLogger = ILoggerHelper.GetILogger("KianaDispatch");
44+
#endif
45+
3946
// Build _gameRepoURL from loading Dispatcher and Gateway
4047
await BuildGameRepoURL(httpClientNew, token);
4148

@@ -96,13 +103,14 @@ private async Task BuildGameRepoURL(HttpClient client, CancellationToken token)
96103
string key = GameVersionManager.GamePreset.DispatcherKey;
97104

98105
// Try assign dispatcher
99-
dispatch = await KianaDispatch.GetDispatch(client,
100-
baseURL,
101-
GameVersionManager.GamePreset.GameDispatchURLTemplate,
102-
GameVersionManager.GamePreset.GameDispatchChannelName,
103-
key,
104-
GameVersion.VersionArray,
105-
token);
106+
if (GameVersionManager.GamePreset is { GameDispatchURLTemplate: not null, GameDispatchChannelName: not null })
107+
dispatch = await KianaDispatch.GetDispatch(client,
108+
baseURL,
109+
GameVersionManager.GamePreset.GameDispatchURLTemplate,
110+
GameVersionManager.GamePreset.GameDispatchChannelName,
111+
key,
112+
GameVersion.VersionArray,
113+
token);
106114
lastException = null;
107115
break;
108116
}
@@ -116,12 +124,11 @@ private async Task BuildGameRepoURL(HttpClient client, CancellationToken token)
116124
if (lastException != null) throw lastException;
117125

118126
// Get gatewayURl and fetch the gateway
119-
GameGateway =
120-
await KianaDispatch.GetGameserver(client, dispatch!, GameVersionManager.GamePreset.GameGatewayDefault!, token);
127+
GameGateway = await KianaDispatch.GetGameserver(client, dispatch!, GameVersionManager.GamePreset.GameGatewayDefault!, token);
121128
GameRepoURL = BuildAssetBundleURL(GameGateway);
122129
}
123130

124-
private static string BuildAssetBundleURL(KianaDispatch gateway) => CombineURLFromString(gateway!.AssetBundleUrls![0], "/{0}/editor_compressed/");
131+
private string BuildAssetBundleURL(KianaDispatch gateway) => CombineURLFromString(this.GetRandomCacheBaseUrl(gateway), "/{0}/editor_compressed/");
125132

126133
private async Task<(int, long)> FetchByType(CacheAssetType type, HttpClient client, List<CacheAsset> assetIndex, CancellationToken token)
127134
{
@@ -132,7 +139,7 @@ private async Task BuildGameRepoURL(HttpClient client, CancellationToken token)
132139
UpdateStatus();
133140

134141
// Get the asset index properties
135-
string baseURL = string.Format(GameRepoURL!, type.ToString().ToLowerInvariant());
142+
string baseURL = string.Format(BuildAssetBundleURL(GameGateway), type.ToString().ToLowerInvariant());
136143
string assetIndexURL = string.Format(CombineURLFromString(baseURL, "{0}Version.unity3d"),
137144
type == CacheAssetType.Data ? "Data" : "Resource");
138145

@@ -150,25 +157,6 @@ private async Task BuildGameRepoURL(HttpClient client, CancellationToken token)
150157
return returnValue;
151158
}
152159

153-
/*
154-
private void BuildDataPatchConfig(MemoryStream stream, List<CacheAsset> assetIndex)
155-
{
156-
// Reset position
157-
stream.Position = 0;
158-
159-
// Initialize manifest file
160-
CachePatchManifest manifest = new CachePatchManifest(stream, true);
161-
162-
for (int i = 0; i < assetIndex.Count; i++)
163-
{
164-
if (assetIndex[i].DataType == CacheAssetType.Data)
165-
{
166-
CachePatchInfo info = manifest.PatchAsset.Where(x => IsArrayMatch(x.NewHashSHA1, assetIndex[i].CRCArray)).FirstOrDefault();
167-
}
168-
}
169-
}
170-
*/
171-
172160
private IEnumerable<CacheAsset> EnumerateCacheTextAsset(CacheAssetType type, IEnumerable<string> enumerator, string baseUrl)
173161
{
174162
// Set isFirst flag as true if type is Data and
@@ -287,7 +275,7 @@ await Parallel.ForEachAsync(EnumerateCacheTextAsset(type, dataTextAsset.GetStrin
287275
UpdateStatus();
288276

289277
// Check for the URL availability and is not available, then skip.
290-
var urlStatus = await client.GetURLStatusCode(content.ConcatURL, cancellationToken);
278+
UrlStatus urlStatus = await client.GetURLStatusCode(content.ConcatURL, cancellationToken);
291279
LogWriteLine($"The Cache {type} asset: {content.N} " + (urlStatus.IsSuccessStatusCode ? "is" : "is not") + $" available (Status code: {urlStatus.StatusCode})", LogType.Default, true);
292280

293281
if (!urlStatus.IsSuccessStatusCode) return;
@@ -342,21 +330,5 @@ private static bool IsValidRegionFile(string input, string lang)
342330
}
343331

344332
public KianaDispatch GetCurrentGateway() => GameGateway;
345-
346-
public async Task<(List<CacheAsset>, string, string, int)> GetCacheAssetList(HttpClient client, CacheAssetType type, CancellationToken token)
347-
{
348-
// Initialize asset index for the return
349-
List<CacheAsset> returnAsset = [];
350-
351-
// Build _gameRepoURL from loading Dispatcher and Gateway
352-
await BuildGameRepoURL(client, token);
353-
354-
// Fetch the progress
355-
_ = await FetchByType(type, client, returnAsset, token);
356-
357-
// Return the list and base asset bundle repo URL
358-
return (returnAsset, GameGateway!.ExternalAssetUrls!.FirstOrDefault(), BuildAssetBundleURL(GameGateway),
359-
LuckyNumber);
360-
}
361333
}
362334
}

CollapseLauncher/Classes/Interfaces/Class/ProgressBase.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using System.Collections;
2525
using System.Collections.Generic;
2626
using System.Collections.ObjectModel;
27+
using System.Diagnostics.CodeAnalysis;
2728
using System.IO;
2829
using System.IO.Hashing;
2930
using System.Linq;
@@ -1359,6 +1360,31 @@ await downloadClient.DownloadAsync(assetURL,
13591360
goto StartOver;
13601361
}
13611362
}
1363+
1364+
[return: NotNullIfNotNull(nameof(url))]
1365+
internal string? GetHttpsOrHttpOverrideUrl(string? url)
1366+
{
1367+
const string schemeStart = "://";
1368+
1369+
if (string.IsNullOrEmpty(url))
1370+
{
1371+
return url;
1372+
}
1373+
1374+
string scheme = IsForceHttpOverride ? "http://" : "https://";
1375+
if (url.StartsWith(scheme, StringComparison.OrdinalIgnoreCase))
1376+
{
1377+
return url;
1378+
}
1379+
1380+
string urlNoScheme = url;
1381+
int indexOfSchemeMark = url.IndexOf(schemeStart, StringComparison.OrdinalIgnoreCase);
1382+
if (indexOfSchemeMark < 0) return scheme + urlNoScheme;
1383+
1384+
indexOfSchemeMark += schemeStart.Length;
1385+
urlNoScheme = url[indexOfSchemeMark..];
1386+
return scheme + urlNoScheme;
1387+
}
13621388
#endregion
13631389

13641390
#region Stream and Archive Tools

CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Audio.cs

Lines changed: 62 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -57,88 +57,74 @@ internal static async Task<List<FilePropertiesRemote>>
5757

5858
progressibleInstance.UpdateStatus();
5959

60-
bool isUseHttpRepairOverride = progressibleInstance.IsForceHttpOverride;
61-
AudioLanguageType gameLanguageType = GetCurrentGameAudioLanguage(presetConfig);
62-
63-
Exception? lastException = null;
64-
foreach (string baseAsbUrl in gameServerInfo.ExternalAssetUrls)
60+
AudioLanguageType gameLanguageType = GetCurrentGameAudioLanguage(presetConfig);
61+
string baseUrl = progressibleInstance.GetRandomAsbBaseUrl(gameServerInfo);
62+
string baseAudioUrl =
63+
baseUrl.CombineURLFromString($"Audio/Windows/{gameVersion.Major}_{gameVersion.Minor}/{gameServerInfo
64+
.Manifest
65+
.ManifestAudio
66+
.ManifestAudioRevision}");
67+
68+
await using Stream manifestStream = audioFileIdentifier.fileStream ?? throw new NullReferenceException("Senadina Audio Identifier Stream cannot be null!");
69+
KianaAudioManifest manifestData =
70+
new(manifestStream, gameVersion.VersionArrayManifest);
71+
72+
List<FilePropertiesRemote> assetList = [];
73+
await Parallel.ForEachAsync(manifestData.AudioAssets,
74+
new ParallelOptions
75+
{
76+
CancellationToken = token,
77+
MaxDegreeOfParallelism = parallelThread
78+
},
79+
ImplCheckAndAdd);
80+
81+
return assetList;
82+
83+
async ValueTask ImplCheckAndAdd(ManifestAssetInfo audioAsset, CancellationToken innerToken)
6584
{
66-
try
85+
// Eliminate removed audio assets or not matching language.
86+
if ((audioAsset.Language != gameLanguageType &&
87+
audioAsset.Language != AudioLanguageType.Common) ||
88+
ignoredAudioHashset.Contains(audioAsset.PckType))
6789
{
68-
string baseAudioAssetUrl = ((isUseHttpRepairOverride ? "http://" : "https://") + baseAsbUrl)
69-
.CombineURLFromString($"Audio/Windows/{gameVersion.Major}_{gameVersion.Minor}/{gameServerInfo
70-
.Manifest
71-
.ManifestAudio
72-
.ManifestAudioRevision}");
73-
74-
await using Stream manifestStream = audioFileIdentifier.fileStream ?? throw new NullReferenceException("Senadina Audio Identifier Stream cannot be null!");
75-
KianaAudioManifest manifestData =
76-
new(manifestStream, gameVersion.VersionArrayManifest);
77-
78-
List<FilePropertiesRemote> assetList = [];
79-
await Parallel.ForEachAsync(manifestData.AudioAssets,
80-
new ParallelOptions
81-
{
82-
CancellationToken = token,
83-
MaxDegreeOfParallelism = parallelThread
84-
},
85-
ImplCheckAndAdd);
86-
87-
return assetList;
88-
89-
async ValueTask ImplCheckAndAdd(ManifestAssetInfo audioAsset, CancellationToken innerToken)
90-
{
91-
// Eliminate removed audio assets or not matching language.
92-
if ((audioAsset.Language != gameLanguageType &&
93-
audioAsset.Language != AudioLanguageType.Common) ||
94-
ignoredAudioHashset.Contains(audioAsset.PckType))
95-
{
96-
return;
97-
}
98-
99-
if (audioAsset.NeedMap)
100-
{
101-
goto AddAsset; // I love goto. Dun ask me why :>
102-
}
103-
104-
progressibleInstance.Status.ActivityStatus = string.Format(Locale.Current.Lang?._GameRepairPage?.Status15 ?? "", audioAsset.Path);
105-
progressibleInstance.Status.IsProgressAllIndetermined = true;
106-
progressibleInstance.Status.IsProgressPerFileIndetermined = true;
107-
progressibleInstance.UpdateStatus();
108-
109-
string assetUrl = baseAudioAssetUrl.CombineURLFromString(audioAsset.Path);
110-
UrlStatus urlStatus = await assetBundleHttpClient.GetURLStatusCode(assetUrl, innerToken);
111-
Logger.LogWriteLine($"The audio asset: {audioAsset.Path} " + (urlStatus.IsSuccessStatusCode ? "is" : "is not") + $" available (Status code: {urlStatus.StatusCode})", LogType.Default, true);
112-
113-
if (!urlStatus.IsSuccessStatusCode)
114-
{
115-
return;
116-
}
117-
118-
AddAsset:
119-
lock (assetList)
120-
{
121-
assetList.Add(new FilePropertiesRemote
122-
{
123-
IsPatchApplicable = audioAsset.IsHasPatch,
124-
AssociatedObject = audioAsset,
125-
AudioPatchInfo = audioAsset.PatchInfo,
126-
CRC = audioAsset.HashString,
127-
FT = FileType.Audio,
128-
RN = baseAudioAssetUrl.CombineURLFromString(audioAsset.Path),
129-
N = audioAsset.Name + ".pck",
130-
S = audioAsset.Size
131-
});
132-
}
133-
}
90+
return;
13491
}
135-
catch (Exception e)
92+
93+
if (audioAsset.NeedMap)
13694
{
137-
lastException = e;
95+
goto AddAsset; // I love goto. Dun ask me why :>
13896
}
139-
}
14097

141-
throw lastException ?? new HttpRequestException("No Asset bundle URLs were reachable");
98+
progressibleInstance.Status.ActivityStatus = string.Format(Locale.Current.Lang?._GameRepairPage?.Status15 ?? "", audioAsset.Path);
99+
progressibleInstance.Status.IsProgressAllIndetermined = true;
100+
progressibleInstance.Status.IsProgressPerFileIndetermined = true;
101+
progressibleInstance.UpdateStatus();
102+
103+
string assetUrl = baseAudioUrl.CombineURLFromString(audioAsset.Path);
104+
UrlStatus urlStatus = await assetBundleHttpClient.GetURLStatusCode(assetUrl, innerToken);
105+
Logger.LogWriteLine($"The audio asset: {audioAsset.Path} " + (urlStatus.IsSuccessStatusCode ? "is" : "is not") + $" available (Status code: {urlStatus.StatusCode})", LogType.Default, true);
106+
107+
if (!urlStatus.IsSuccessStatusCode)
108+
{
109+
throw new HttpRequestException("No Asset bundle URLs were reachable");
110+
}
111+
112+
AddAsset:
113+
lock (assetList)
114+
{
115+
assetList.Add(new FilePropertiesRemote
116+
{
117+
IsPatchApplicable = audioAsset.IsHasPatch,
118+
AssociatedObject = audioAsset,
119+
AudioPatchInfo = audioAsset.PatchInfo,
120+
CRC = audioAsset.HashString,
121+
FT = FileType.Audio,
122+
RN = baseAudioUrl.CombineURLFromString(audioAsset.Path),
123+
N = audioAsset.Name + ".pck",
124+
S = audioAsset.Size
125+
});
126+
}
127+
}
142128
}
143129

144130
internal static bool GetAudioPatchUrlProperty(

CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.AsbExt.Block.cs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ internal static async Task<List<FilePropertiesRemote>>
5858
progressibleInstance.Status.IsIncludePerFileIndicator = false;
5959
progressibleInstance.UpdateStatus();
6060

61-
bool isUseHttpRepairOverride = progressibleInstance.IsForceHttpOverride;
62-
6361
await using Stream xmfMetaCurrentFileStream = senadinaResults.XmfMeta?.fileStream ?? throw new NullReferenceException("Senadina BlockMeta Identifier Stream cannot be null!");
6462
await using Stream xmfPatchCurrentFileStream = senadinaResults.XmfPatch?.fileStream ?? throw new NullReferenceException("Senadina BlockPatch Identifier Stream cannot be null!");
6563

@@ -106,15 +104,18 @@ internal static async Task<List<FilePropertiesRemote>>
106104
ref BlockPatchInfo patchInfoRef =
107105
ref CollectionsMarshal.GetValueRefOrNullRef(patchInfos, xmfBlock.BlockName);
108106

107+
string asbBaseUrl = progressibleInstance.GetRandomAsbBaseUrl(gameServerInfo);
108+
string assetUrl =
109+
asbBaseUrl.CombineURLFromString($"StreamingAsb/{baseUrlVersionPrefix}/pc/HD/asb", xmfBlock.BlockName);
110+
109111
FilePropertiesRemote asset = new()
110112
{
111113
AssociatedObject = xmfBlock,
112-
CRC = Path.GetFileNameWithoutExtension(xmfBlock.BlockName),
113-
FT = FileType.Block,
114-
RN = GetRandomBaseUrl(gameServerInfo)
115-
.CombineURLFromString($"StreamingAsb/{baseUrlVersionPrefix}/pc/HD/asb", xmfBlock.BlockName),
116-
N = xmfBlock.BlockName,
117-
S = xmfBlock.Size
114+
CRC = Path.GetFileNameWithoutExtension(xmfBlock.BlockName),
115+
FT = FileType.Block,
116+
RN = assetUrl,
117+
N = xmfBlock.BlockName,
118+
S = xmfBlock.Size
118119
};
119120
assetList.Add(asset);
120121

@@ -128,12 +129,20 @@ internal static async Task<List<FilePropertiesRemote>>
128129
}
129130

130131
return assetList;
132+
}
131133

132-
string GetRandomBaseUrl(KianaDispatch kianaDispatch)
133-
{
134-
string selectedUrl = kianaDispatch.ExternalAssetUrls.RandomSelectSingle();
135-
return isUseHttpRepairOverride ? "http://" : "https://" + selectedUrl;
136-
}
134+
internal static string GetRandomAsbBaseUrl<T>(this ProgressBase<T> instance, KianaDispatch kianaDispatch)
135+
where T : IAssetIndexSummary
136+
{
137+
string selectedUrl = kianaDispatch.ExternalAssetUrls.RandomSelectSingle();
138+
return instance.GetHttpsOrHttpOverrideUrl(selectedUrl);
139+
}
140+
141+
internal static string GetRandomCacheBaseUrl<T>(this ProgressBase<T> instance, KianaDispatch kianaDispatch)
142+
where T : IAssetIndexSummary
143+
{
144+
string selectedUrl = kianaDispatch.AssetBundleUrls.RandomSelectSingle();
145+
return instance.GetHttpsOrHttpOverrideUrl(selectedUrl);
137146
}
138147

139148
internal static bool GetBlockPatchUrlProperty(

0 commit comments

Comments
 (0)