Skip to content

Commit 80e0551

Browse files
authored
[EMERGENCY HOTFIX] Forcefully Redirect to Sophon if Zip Method is Unavailable (#733)
# Main Goal As per Genshin Impact 5.6.0 preload today, miHoYo just removed Zip packages on HoYoPlay API. This caused our launcher unable to detect or determine update state due to the main dependencies to Zip packages only. This PR fixes the issue by checking if Zip is unavailable, then tell the GameVersionManager to fallback by forcefully redirect the install/update/preload methods to Sophon mode. This PR also avoid the same issue if HoYo might remove the Zip packages to other games in the future.
2 parents 36c67d3 + 1ddf162 commit 80e0551

File tree

9 files changed

+158
-39
lines changed

9 files changed

+158
-39
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,11 @@ public virtual async ValueTask<List<RegionResourcePlugin>> CheckPluginUpdate(str
528528
// If all not passed, then return null.
529529
return null;
530530
}
531+
532+
public virtual bool IsForceRedirectToSophon()
533+
{
534+
return GamePreset.GameLauncherApi?.IsForceRedirectToSophon ?? false;
535+
}
531536
#endregion
532537
}
533538
}

CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,6 @@ void AfterExecute(Task action)
156156
ConvertPackageResources(sophonResourceData, hypResourceResponse?.Data?.LauncherPackages);
157157

158158
LauncherGameResource = sophonResourcePropRoot;
159-
160-
PerformDebugRoutines();
161159
}
162160
}
163161

CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherGameInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public sealed class HoYoPlayGameInfoBranchField
4040
[JsonPropertyName("branch")] public string? Branch { get; init; }
4141
[JsonPropertyName("password")] public string? Password { get; init; }
4242
[JsonPropertyName("tag")] public string? Tag { get; init; }
43+
[JsonPropertyName("diff_tags")] public List<string>? DiffTags { get; init; }
4344
}
4445

4546
public sealed class HoYoPlayGameInfoField

CollapseLauncher/Classes/Helper/LauncherApiLoader/ILauncherApi.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,26 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
// ReSharper disable UnusedMemberInSuper.Global
9+
// ReSharper disable IdentifierTypo
910

1011
#nullable enable
1112
namespace CollapseLauncher.Helper.LauncherApiLoader
1213
{
1314
public interface ILauncherApi : IDisposable
1415
{
15-
bool IsLoadingCompleted { get; }
16-
string? GameBackgroundImg { get; }
17-
string? GameBackgroundImgLocal { get; set; }
18-
string? GameName { get; }
19-
string? GameRegion { get; }
20-
string? GameNameTranslation { get; }
21-
string? GameRegionTranslation { get; }
22-
HoYoPlayGameInfoField? LauncherGameInfoField { get; }
23-
RegionResourceProp? LauncherGameResource { get; }
24-
LauncherGameNews? LauncherGameNews { get; }
25-
HttpClient? ApiGeneralHttpClient { get; }
26-
HttpClient? ApiResourceHttpClient { get; }
16+
bool IsLoadingCompleted { get; }
17+
bool IsForceRedirectToSophon { get; }
18+
string? GameBackgroundImg { get; }
19+
string? GameBackgroundImgLocal { get; set; }
20+
string? GameName { get; }
21+
string? GameRegion { get; }
22+
string? GameNameTranslation { get; }
23+
string? GameRegionTranslation { get; }
24+
HoYoPlayGameInfoField? LauncherGameInfoField { get; }
25+
RegionResourceProp? LauncherGameResource { get; }
26+
LauncherGameNews? LauncherGameNews { get; }
27+
HttpClient? ApiGeneralHttpClient { get; }
28+
HttpClient? ApiResourceHttpClient { get; }
2729
Task<bool> LoadAsync(OnLoadTaskAction? beforeLoadRoutine = null, OnLoadTaskAction? afterLoadRoutine = null,
2830
ActionOnTimeOutRetry? onTimeoutRoutine = null, ErrorLoadRoutineDelegate? errorLoadRoutine = null,
2931
CancellationToken token = default);

CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs

Lines changed: 122 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
using System.Net;
1616
using System.Net.Http.Json;
1717
using System.Runtime.CompilerServices;
18+
using System.Linq;
19+
using System.Collections.Generic;
1820
// ReSharper disable PartialTypeWithSinglePart
1921
// ReSharper disable IdentifierTypo
2022
// ReSharper disable StringLiteralTypo
2123
// ReSharper disable VirtualMemberCallInConstructor
24+
// ReSharper disable CommentTypo
2225

2326
#nullable enable
2427
namespace CollapseLauncher.Helper.LauncherApiLoader
@@ -33,23 +36,25 @@ internal partial class LauncherApiBase : ILauncherApi
3336
public const int ExecutionTimeoutAttempt = 5;
3437
protected PresetConfig? PresetConfig { get; }
3538

36-
public bool IsLoadingCompleted { get; private set; }
37-
public string? GameBackgroundImg { get => LauncherGameNews?.Content?.Background?.BackgroundImg; }
38-
public string? GameBackgroundImgLocal { get; set; }
39-
public string? GameName { get; init; }
40-
public string? GameRegion { get; init; }
39+
public bool IsLoadingCompleted { get; private set; }
40+
public bool IsForceRedirectToSophon { get; private set; }
41+
public string? GameBackgroundImg { get => LauncherGameNews?.Content?.Background?.BackgroundImg; }
42+
public string? GameBackgroundImgLocal { get; set; }
43+
public string? GameName { get; init; }
44+
public string? GameRegion { get; init; }
4145

4246
public string? GameNameTranslation =>
4347
InnerLauncherConfig.GetGameTitleRegionTranslationString(GameName, Locale.Lang._GameClientTitles);
4448

4549
public string? GameRegionTranslation =>
4650
InnerLauncherConfig.GetGameTitleRegionTranslationString(GameRegion, Locale.Lang._GameClientRegions);
4751

48-
public virtual RegionResourceProp? LauncherGameResource { get; protected set; }
49-
public virtual LauncherGameNews? LauncherGameNews { get; protected set; }
50-
public virtual HoYoPlayGameInfoField? LauncherGameInfoField { get; protected set; }
51-
public virtual HttpClient ApiGeneralHttpClient { get; protected set; }
52-
public virtual HttpClient ApiResourceHttpClient { get; protected set; }
52+
public virtual RegionResourceProp? LauncherGameResource { get; protected set; }
53+
public virtual HoYoPlayLauncherGameInfo? LauncherGameResourceSophon { get; protected set; }
54+
public virtual LauncherGameNews? LauncherGameNews { get; protected set; }
55+
public virtual HoYoPlayGameInfoField? LauncherGameInfoField { get; protected set; }
56+
public virtual HttpClient ApiGeneralHttpClient { get; protected set; }
57+
public virtual HttpClient ApiResourceHttpClient { get; protected set; }
5358

5459
public void Dispose()
5560
{
@@ -114,9 +119,11 @@ protected LauncherApiBase(PresetConfig presetConfig, string gameName, string gam
114119
ApiResourceHttpClient = apiResourceHttpBuilder.Create();
115120
}
116121

117-
public async Task<bool> LoadAsync(OnLoadTaskAction? beforeLoadRoutine, OnLoadTaskAction? afterLoadRoutine,
118-
ActionOnTimeOutRetry? onTimeoutRoutine, ErrorLoadRoutineDelegate? errorLoadRoutine,
119-
CancellationToken token)
122+
public async Task<bool> LoadAsync(OnLoadTaskAction? beforeLoadRoutine,
123+
OnLoadTaskAction? afterLoadRoutine,
124+
ActionOnTimeOutRetry? onTimeoutRoutine,
125+
ErrorLoadRoutineDelegate? errorLoadRoutine,
126+
CancellationToken token)
120127
{
121128
_ = beforeLoadRoutine?.Invoke(token) ?? Task.CompletedTask;
122129

@@ -140,12 +147,109 @@ public async Task<bool> LoadAsync(OnLoadTaskAction? beforeLoadRoutine, OnLoa
140147
}
141148
}
142149

143-
protected virtual Task LoadAsyncInner(ActionOnTimeOutRetry? onTimeoutRoutine,
144-
CancellationToken token)
150+
protected virtual async Task LoadAsyncInner(ActionOnTimeOutRetry? onTimeoutRoutine,
151+
CancellationToken token)
145152
{
146-
return Task.WhenAll(LoadLauncherGameResource(onTimeoutRoutine, token),
147-
LoadLauncherNews(onTimeoutRoutine, token),
148-
LoadLauncherGameInfo(onTimeoutRoutine, token));
153+
// 2025-05-05: As per now, the Sophon resource information requires to be fetched first.
154+
// This is mandatory due to latest Genshin Impact changes which removes zip
155+
// packages and also version infos.
156+
await LoadLauncherGameResourceSophon(onTimeoutRoutine, token);
157+
await Task.WhenAll(LoadLauncherGameResource(onTimeoutRoutine, token),
158+
LoadLauncherNews(onTimeoutRoutine, token),
159+
LoadLauncherGameInfo(onTimeoutRoutine, token));
160+
161+
InitializeFakeVersionInfo();
162+
PerformDebugRoutines();
163+
}
164+
165+
protected virtual void InitializeFakeVersionInfo()
166+
{
167+
if (LauncherGameResource?.data == null)
168+
{
169+
return;
170+
}
171+
172+
HoYoPlayGameInfoBranch? gameBranch = LauncherGameResourceSophon?.GameInfoData?.GameBranchesInfo?
173+
.FirstOrDefault(x => x.GameInfo?.BizName?.Equals(PresetConfig?.LauncherBizName) ?? false);
174+
175+
if (gameBranch == null)
176+
{
177+
return;
178+
}
179+
180+
var branchPreloadField = gameBranch.GamePreloadField;
181+
var branchBaseField = gameBranch.GameMainField;
182+
183+
if (branchPreloadField != null)
184+
{
185+
LauncherGameResource.data.pre_download_game ??= new RegionResourceLatest();
186+
AddFakeVersionInfo(branchPreloadField, LauncherGameResource.data.pre_download_game);
187+
}
188+
189+
if (branchBaseField == null)
190+
{
191+
return;
192+
}
193+
194+
LauncherGameResource.data.game ??= new RegionResourceLatest();
195+
AddFakeVersionInfo(branchBaseField, LauncherGameResource.data.game);
196+
IsForceRedirectToSophon = true;
197+
}
198+
199+
protected virtual void AddFakeVersionInfo(HoYoPlayGameInfoBranchField branchField, RegionResourceLatest region)
200+
{
201+
region.latest ??= new RegionResourceVersion();
202+
region.latest.version = branchField.Tag;
203+
204+
region.diffs ??= [];
205+
206+
HashSet<string> existingDiffsVer = new(region.diffs.Select(x => x.version)!);
207+
foreach (var versionTags in (branchField.DiffTags ?? []).Where(x => !existingDiffsVer.Contains(x)))
208+
{
209+
region.diffs.Add(new RegionResourceVersion
210+
{
211+
version = versionTags
212+
});
213+
}
214+
}
215+
216+
protected virtual async Task LoadLauncherGameResourceSophon(ActionOnTimeOutRetry? onTimeoutRoutine,
217+
CancellationToken token)
218+
{
219+
EnsurePresetConfigNotNull();
220+
221+
SophonChunkUrls? sophonUrls = PresetConfig?.LauncherResourceChunksURL;
222+
if (sophonUrls == null)
223+
{
224+
return;
225+
}
226+
227+
string? sophonBranchUrl = sophonUrls.BranchUrl;
228+
if (string.IsNullOrEmpty(PresetConfig!.LauncherBizName) || string.IsNullOrEmpty(sophonBranchUrl))
229+
{
230+
Logger.LogWriteLine("This game/region doesn't have Sophon->BranchUrl or PresetConfig->LauncherBizName property defined! This might cause the launcher inaccurately check the version if Zip download is unavailable", LogType.Warning, true);
231+
}
232+
233+
await sophonUrls.EnsureReassociated(ApiGeneralHttpClient,
234+
sophonBranchUrl,
235+
PresetConfig.LauncherBizName!,
236+
false,
237+
token);
238+
sophonUrls.ResetAssociation(); // Reset association so it won't conflict with preload/update/install activity
239+
240+
ActionTimeoutTaskAwaitableCallback<HoYoPlayLauncherGameInfo?> launcherSophonBranchCallback =
241+
innerToken =>
242+
ApiGeneralHttpClient.GetFromJsonAsync(PresetConfig.LauncherResourceChunksURL?.BranchUrl,
243+
HoYoPlayLauncherGameInfoJsonContext.Default.HoYoPlayLauncherGameInfo,
244+
innerToken)
245+
.ConfigureAwait(false);
246+
247+
LauncherGameResourceSophon = await launcherSophonBranchCallback
248+
.WaitForRetryAsync(ExecutionTimeout,
249+
ExecutionTimeoutStep,
250+
ExecutionTimeoutAttempt,
251+
onTimeoutRoutine,
252+
token);
149253
}
150254

151255
protected virtual Task LoadLauncherGameResource(ActionOnTimeOutRetry? onTimeoutRoutine,
@@ -213,8 +317,6 @@ void AfterExecute(Task action)
213317
LogType.Debug, true);
214318
#endif
215319
}
216-
217-
PerformDebugRoutines();
218320
}
219321
}
220322

CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ public class SophonChunkUrls
9898
[JsonConverter(typeof(ServeV3StringConverter))]
9999
public string? SdkUrl { get; set; }
100100

101+
public bool ResetAssociation() => IsReassociated = false;
102+
101103
public Task EnsureReassociated(HttpClient client, string? branchUrl, string bizName, bool isPreloadForPatch, CancellationToken token)
102104
{
103105
if (IsReassociated)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,11 @@ protected bool _isSophonPreloadCompleted
8383

8484
#region Public Virtual Properties
8585
public virtual bool IsUseSophon =>
86-
GameVersionManager.GamePreset.LauncherResourceChunksURL != null
86+
GameVersionManager.IsForceRedirectToSophon() ||
87+
(GameVersionManager.GamePreset.LauncherResourceChunksURL != null
8788
&& !File.Exists(Path.Combine(GamePath, "@DisableSophon"))
8889
&& !_canDeltaPatch && !_forceIgnoreDeltaPatch
89-
&& LauncherConfig.GetAppConfigValue("IsEnableSophon").ToBool();
90+
&& LauncherConfig.GetAppConfigValue("IsEnableSophon").ToBool());
9091
#endregion
9192

9293
#region Sophon Verification Methods

CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,10 @@ namespace CollapseLauncher.InstallManager.Genshin
2727
internal sealed partial class GenshinInstall : InstallManagerBase
2828
{
2929
#region Override Properties
30-
3130
protected override int _gameVoiceLanguageID => GameVersionManager.GamePreset.GetVoiceLanguageID();
32-
3331
#endregion
3432

3533
#region Properties
36-
3734
protected override string _gameAudioLangListPath
3835
{
3936
get
@@ -74,6 +71,12 @@ public GenshinInstall(UIElement parentUI, IGameVersion GameVersionManager)
7471
#nullable enable
7572
public override async ValueTask<bool> IsPreloadCompleted(CancellationToken token)
7673
{
74+
// If it's forcely redirected to sophon, check the preload using sophon
75+
if (GameVersionManager.IsForceRedirectToSophon())
76+
{
77+
return await base.IsPreloadCompleted(token);
78+
}
79+
7780
// Get the primary file first check
7881
List<RegionResourceVersion>? resource = GameVersionManager.GetGamePreloadZip();
7982

CollapseLauncher/Classes/Interfaces/IGameVersionCheck.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ internal interface IGameVersion
130130
/// </summary>
131131
bool IsGameHasDeltaPatch();
132132

133+
/// <summary>
134+
/// Check if the game installation is forcefully redirected to Sophon.
135+
/// </summary>
136+
bool IsForceRedirectToSophon();
137+
133138
/// <summary>
134139
/// Returns the state of the game.
135140
/// </summary>

0 commit comments

Comments
 (0)