Skip to content

Commit c46c9ec

Browse files
authored
[EMERGENCY HOTFIX] Preview - 1.83.1 Sophon Incident (#740)
# Main Goal As per May 5th 2025 (on Genshin Impact 5.6 update), HoYo just removed the entire Zip packages from the launcher APIs. causing a major issue throughout Third-party Launcher Community (Collapse is no exception). This PR also fixed some essential features which has stopped working due its dependency on Zip's ScatteredFiles references. Now, all those features are moving its dependency entirely to Sophon as its game files references. # What's changed? - **[Fix - Genshin Impact]** Infinite Loop/Getting Stuck while Updating Game to 5.6.0 - **[Fix - Genshin Impact]** Crash on File Cleanup feature due to missing Zip's pkg_version ScatteredFiles reference. - **[Fix - Genshin Impact]** Crash on Game Repair feature due to missing Zip's pkg_version ScatteredFiles reference. - **[Fix]** Possible crash when user defines ``version`` field in ``config.ini`` with 2-numbers or less format. - **[Fix]** [#736](#736) GameIniVersion ignores mismatched value - **[Fix]** [#737](#737) Multi-instance doesn't work
2 parents 0983b7a + 21a9e79 commit c46c9ec

29 files changed

+856
-384
lines changed

CollapseLauncher/Classes/GameManagement/Versioning/GameVersionBase.IniConfig.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ internal partial class GameVersionBase
6363
protected virtual IniFile GameIniProfile { get; } = new();
6464
protected virtual IniFile GameIniVersion { get; } = new();
6565
public virtual IniSection GameIniVersionSection { get => GameIniVersion[DefaultIniVersionSection]; }
66-
public virtual IniSection GameIniProfileSection { get => GameIniVersion[DefaultIniProfileSection]; }
66+
public virtual IniSection GameIniProfileSection { get => GameIniProfile[DefaultIniProfileSection]; }
6767
#endregion
6868

6969
#region Game Config Path Properties
@@ -75,7 +75,7 @@ protected virtual string GameIniVersionPath
7575
{
7676
get
7777
{
78-
var configPath = GameIniProfile[DefaultIniProfileSection]["game_install_path"].ToString();
78+
var configPath = GameIniProfileSection["game_install_path"].ToString();
7979
var defaultPath = Path.Combine(GameConfigDirPath ?? "", GamePreset.GameDirectoryName ?? "Games", ConfigFileName);
8080

8181
if (string.IsNullOrEmpty(configPath)) return defaultPath;
@@ -173,9 +173,9 @@ protected virtual void FixInvalidGameConfigId()
173173

174174
string gameIniVersionPath = Path.Combine(GameDirPath, ConfigFileName);
175175

176-
GameIniVersion[DefaultIniVersionSection][channelIdKeyName] = GamePreset.ChannelID ?? 0;
177-
GameIniVersion[DefaultIniVersionSection][subChannelIdKeyName] = GamePreset.SubChannelID ?? 0;
178-
GameIniVersion[DefaultIniVersionSection][cpsKeyName] = GamePreset.LauncherCPSType;
176+
GameIniVersionSection[channelIdKeyName] = GamePreset.ChannelID ?? 0;
177+
GameIniVersionSection[subChannelIdKeyName] = GamePreset.SubChannelID ?? 0;
178+
GameIniVersionSection[cpsKeyName] = GamePreset.LauncherCPSType;
179179

180180
SaveGameIni(gameIniVersionPath, GameIniVersion);
181181
}
@@ -198,7 +198,7 @@ protected virtual void FixInvalidGameBilibiliStatus(string? executableName)
198198
#region Update Game Config Methods
199199
public void UpdateGamePath(string? path, bool saveValue = true)
200200
{
201-
GameIniProfile[DefaultIniProfileSection]["game_install_path"] = path?.Replace('\\', '/');
201+
GameIniProfileSection["game_install_path"] = path?.Replace('\\', '/');
202202
if (saveValue)
203203
{
204204
SaveGameIni(GameIniProfilePath, GameIniProfile);
@@ -220,7 +220,7 @@ public void UpdateGameVersionToLatest(bool saveValue = true)
220220

221221
public void UpdateGameVersion(GameVersion? version, bool saveValue = true)
222222
{
223-
GameIniVersion[DefaultIniVersionSection]["game_version"] = version?.VersionString;
223+
GameIniVersionSection["game_version"] = version?.VersionString;
224224
if (saveValue)
225225
{
226226
SaveGameIni(GameIniVersionPath, GameIniVersion);
@@ -229,9 +229,9 @@ public void UpdateGameVersion(GameVersion? version, bool saveValue = true)
229229

230230
public void UpdateGameChannels(bool saveValue = true)
231231
{
232-
GameIniVersion[DefaultIniVersionSection]["channel"] = DefaultGameChannelID;
233-
GameIniVersion[DefaultIniVersionSection]["sub_channel"] = DefaultGameSubChannelID;
234-
GameIniVersion[DefaultIniVersionSection]["cps"] = DefaultGameCps;
232+
GameIniVersionSection["channel"] = DefaultGameChannelID;
233+
GameIniVersionSection["sub_channel"] = DefaultGameSubChannelID;
234+
GameIniVersionSection["cps"] = DefaultGameCps;
235235

236236
if (saveValue)
237237
{
@@ -253,7 +253,7 @@ public void UpdatePluginVersions(Dictionary<string, GameVersion> versions, bool
253253
string keyName = $"plugin_{version.Key}_version";
254254

255255
// Set the value
256-
GameIniVersion[DefaultIniVersionSection][keyName] = version.Value.VersionString;
256+
GameIniVersionSection[keyName] = version.Value.VersionString;
257257
}
258258

259259
if (saveValue)
@@ -274,7 +274,7 @@ public void UpdateSdkVersion(GameVersion? version, bool saveValue = true)
274274

275275
// Set the value
276276
const string keyName = "plugin_sdk_version";
277-
GameIniVersion[DefaultIniVersionSection][keyName] = version.ToString();
277+
GameIniVersionSection[keyName] = version.ToString();
278278

279279
if (saveValue)
280280
{
@@ -339,7 +339,7 @@ public virtual void InitializeIniProp()
339339
InitializeIniProp(GameIniProfilePath, GameIniProfile, DefaultIniProfile,
340340
DefaultIniProfileSection);
341341
InitializeIniProp(GameIniVersionPath, GameIniVersion, DefaultIniVersion,
342-
DefaultIniVersionSection, true);
342+
DefaultIniVersionSection);
343343

344344
// Initialize the GameVendorType
345345
VendorTypeProp = new GameVendorProp(GameDirPath,
@@ -392,7 +392,7 @@ private void InitializeIniDefaults(IniFile ini, IniSection defaults, string sect
392392
}
393393
}
394394

395-
UpdateGameChannels(false);
395+
// UpdateGameChannels(false);
396396
}
397397

398398
private void SaveGameIni(string filePath, IniFile ini)

CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.PkgVersion.cs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,42 @@ static void ExitFromOverlay(object? sender, RoutedEventArgs args)
160160
}
161161
}
162162

163+
protected virtual async Task<string> DownloadPkgVersion(DownloadClient downloadClient, RegionResourceVersion? packageLatestBase)
164+
{
165+
string? packageExtractBasePath = packageLatestBase?.decompressed_path;
166+
167+
// Check Fail-safe: Download pkg_version files if not exist
168+
string pkgVersionPath = Path.Combine(GamePath, "pkg_version");
169+
if (string.IsNullOrEmpty(packageExtractBasePath))
170+
{
171+
return pkgVersionPath;
172+
}
173+
174+
// Check Fail-safe: Download main pkg_version file
175+
string mainPkgVersionUrl = packageExtractBasePath.CombineURLFromString("pkg_version");
176+
await downloadClient.DownloadAsync(mainPkgVersionUrl, pkgVersionPath, true);
177+
178+
// Check Fail-safe: Download audio pkg_version files
179+
if (string.IsNullOrEmpty(_gameAudioLangListPathStatic) ||
180+
string.IsNullOrEmpty(packageExtractBasePath))
181+
{
182+
return pkgVersionPath;
183+
}
184+
185+
if (!File.Exists(_gameAudioLangListPathStatic))
186+
{
187+
throw new
188+
FileNotFoundException("Game does have audio lang index file but does not exist!"
189+
+ $" Expecting location: {_gameAudioLangListPathStatic}");
190+
}
191+
192+
await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic,
193+
packageExtractBasePath,
194+
downloadClient);
195+
196+
return pkgVersionPath;
197+
}
198+
163199
protected virtual async Task<(List<LocalFileInfo>, long)> GetUnusedFileInfoList(bool includeZipCheck)
164200
{
165201
LoadingMessageHelper.ShowLoadingFrame();
@@ -195,32 +231,9 @@ static void ExitFromOverlay(object? sender, RoutedEventArgs args)
195231
DownloadClient downloadClient = DownloadClient.CreateInstance(httpClient);
196232
RegionResourceVersion? packageLatestBase = GameVersionManager
197233
.GetGameLatestZip(gameStateEnum).FirstOrDefault();
198-
string? packageExtractBasePath = packageLatestBase?.decompressed_path;
199234

200-
// Check Fail-safe: Download pkg_version files if not exist
201-
string pkgVersionPath = Path.Combine(GamePath, "pkg_version");
202-
if (!string.IsNullOrEmpty(packageExtractBasePath))
203-
{
204-
// Check Fail-safe: Download main pkg_version file
205-
string mainPkgVersionUrl = packageExtractBasePath.CombineURLFromString("pkg_version");
206-
await downloadClient.DownloadAsync(mainPkgVersionUrl, pkgVersionPath, true);
207-
208-
// Check Fail-safe: Download audio pkg_version files
209-
if (!string.IsNullOrEmpty(_gameAudioLangListPathStatic) &&
210-
!string.IsNullOrEmpty(packageExtractBasePath))
211-
{
212-
if (!File.Exists(_gameAudioLangListPathStatic))
213-
{
214-
throw new
215-
FileNotFoundException("Game does have audio lang index file but does not exist!"
216-
+ $" Expecting location: {_gameAudioLangListPathStatic}");
217-
}
218-
219-
await DownloadOtherAudioPkgVersion(_gameAudioLangListPathStatic,
220-
packageExtractBasePath,
221-
downloadClient);
222-
}
223-
}
235+
// Download pkg_version file (with additional audio ones)
236+
string pkgVersionPath = await DownloadPkgVersion(downloadClient, packageLatestBase);
224237

225238
// Check Fail-safe: If the main pkg_version still not exist, throw!
226239
bool isMainPkgVersionExist = File.Exists(pkgVersionPath);
@@ -341,7 +354,7 @@ protected virtual async ValueTask DownloadOtherAudioPkgVersion(string audioListF
341354
string pkgUrl = baseExtractUrl.CombineURLFromString(pkgFileName);
342355

343356
// Skip if URL is not found
344-
if ((await FallbackCDNUtil.GetURLStatusCode(pkgUrl, default)).StatusCode == HttpStatusCode.NotFound)
357+
if ((await FallbackCDNUtil.GetURLStatusCode(pkgUrl, CancellationToken.None)).StatusCode == HttpStatusCode.NotFound)
345358
{
346359
continue;
347360
}

CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.SophonPatch.cs

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ await branchResources.EnsureReassociated(httpClient,
7777
await SophonPatch.CreateSophonChunkManifestInfoPair(httpClient,
7878
url: branchResources.PatchUrl,
7979
versionUpdateFrom: requestedVersionFrom.Value.VersionString,
80-
matchingField: GameVersionManager.GamePreset.LauncherResourceChunksURL.MainBranchMatchingField,
80+
matchingField: branchResources.MainBranchMatchingField,
8181
token: Token.Token);
8282

8383
// If the patch metadata is not found, then return false and back to old compare method
@@ -101,7 +101,7 @@ await SophonPatch.CreateSophonChunkManifestInfoPair(httpClient,
101101
(List<SophonPatchAsset>, List<SophonChunkManifestInfoPair>) patchAssets =
102102
await GetAlterSophonPatchAssets(httpClient,
103103
branchResources.PatchUrl,
104-
GameRepoURL,
104+
(isPreloadMode ? branchResources.PreloadUrl : branchResources.MainUrl) ?? "",
105105
matchingFields,
106106
requestedVersionFrom.Value.VersionString,
107107
downloadSpeedLimiter,
@@ -128,23 +128,35 @@ await StartAlterSophonPatch(httpClient,
128128
SophonDownloadSpeedLimiter downloadLimiter,
129129
CancellationToken token)
130130
{
131-
SophonChunkManifestInfoPair? rootPatchManifest = null;
132-
List<SophonChunkManifestInfoPair> patchManifestList = [];
131+
SophonChunkManifestInfoPair? rootPatchManifest = null;
132+
SophonChunkManifestInfoPair? rootMainManifest = null;
133+
List<(SophonChunkManifestInfoPair Patch, SophonChunkManifestInfoPair Main)> patchManifestList = [];
133134

134135
// Iterate matching fields and get the patch metadata
135136
foreach (string matchingField in matchingFields)
136137
{
137138
// Initialize root manifest if it's null
138-
rootPatchManifest ??= await SophonPatch.CreateSophonChunkManifestInfoPair(httpClient,
139-
url: manifestUrl,
140-
versionUpdateFrom: updateVersionfrom,
141-
matchingField: matchingField,
142-
token: token);
139+
rootPatchManifest ??= await SophonPatch
140+
.CreateSophonChunkManifestInfoPair(httpClient,
141+
url: manifestUrl,
142+
versionUpdateFrom: updateVersionfrom,
143+
matchingField: matchingField,
144+
token: token);
145+
146+
rootMainManifest ??= await SophonManifest
147+
.CreateSophonChunkManifestInfoPair(httpClient,
148+
url: downloadOverUrl,
149+
matchingField: matchingField,
150+
token: token);
143151

144152
// Get the manifest pair based on the matching field
145153
SophonChunkManifestInfoPair patchManifest = rootPatchManifest
146154
.GetOtherPatchInfoPair(matchingField, updateVersionfrom);
147155

156+
// Get the main manifest pair based on the matching field
157+
SophonChunkManifestInfoPair mainManifest = rootMainManifest
158+
.GetOtherManifestInfoPair(matchingField);
159+
148160
// If the patch metadata is not found, continue to other manifest pair
149161
if (!patchManifest.IsFound)
150162
{
@@ -155,27 +167,27 @@ await StartAlterSophonPatch(httpClient,
155167
}
156168

157169
// Otherwise, add the manifest to the list
158-
patchManifestList.Add(patchManifest);
170+
patchManifestList.Add((patchManifest, mainManifest));
159171
}
160172

161173
// Initialize the return list and iterate the manifests
162174
List<SophonPatchAsset> patchAssets = [];
163-
foreach (SophonChunkManifestInfoPair manifestPair in patchManifestList)
175+
foreach (var manifestPair in patchManifestList)
164176
{
165177
// Get the asset and add it to the list
166178
await foreach (SophonPatchAsset patchAsset in SophonPatch
167179
.EnumerateUpdateAsync(httpClient,
168-
manifestPair,
180+
manifestPair.Patch,
181+
manifestPair.Main,
169182
updateVersionfrom,
170-
downloadOverUrl,
171183
downloadLimiter,
172184
token))
173185
{
174186
patchAssets.Add(patchAsset);
175187
}
176188
}
177189

178-
return (patchAssets, patchManifestList);
190+
return (patchAssets, patchManifestList.Select(x => x.Patch).ToList());
179191
}
180192

181193
protected virtual async Task<List<string>> GetAlterSophonPatchVOMatchingFields(CancellationToken token)
@@ -235,7 +247,7 @@ protected virtual async Task StartAlterSophonPatch(HttpClient httpClient,
235247
long downloadSizePatchOnlyRemote = patchManifestInfoPairs.Sum(x => x.ChunksInfo.TotalSize);
236248

237249
// Get download counts
238-
int downloadCountTotalAssetRemote = patchAssets.Count(x => x.PatchMethod != SophonPatchMethod.Remove);
250+
int downloadCountTotalAssetRemote = patchAssets.Count;
239251
int downloadCountPatchOnlyRemote = patchManifestInfoPairs.Sum(x => x.ChunksInfo.ChunksCount);
240252

241253
// Ensure disk space sufficiency

0 commit comments

Comments
 (0)