Skip to content

Commit e33da83

Browse files
committed
[Genshin] Use Sophon as reference for File Cleanup
1 parent 3224b04 commit e33da83

File tree

5 files changed

+203
-22
lines changed

5 files changed

+203
-22
lines changed

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

Lines changed: 1 addition & 1 deletion
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

CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
6666
using ZipArchiveEntry = SharpCompress.Archives.Zip.ZipArchiveEntry;
6767
// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault
68+
#pragma warning disable CS8777 // Parameter must have a non-null value when exiting.
6869
#endif
6970

7071
// ReSharper disable ForCanBeConvertedToForeach
@@ -2138,15 +2139,23 @@ protected virtual int GetIDByLanguageLocaleCode([NotNull] string? localeCode)
21382139
};
21392140
}
21402141

2141-
protected virtual string GetLanguageStringByLocaleCode([NotNull] string? localeCode)
2142+
protected virtual string GetLanguageStringByLocaleCode([NotNull] string? localeCode, bool throwIfInvalid =
2143+
#if DEBUG
2144+
true
2145+
#else
2146+
false
2147+
#endif
2148+
)
21422149
{
21432150
return localeCode switch
21442151
{
21452152
"zh-cn" => "Chinese",
21462153
"en-us" => "English(US)",
21472154
"ja-jp" => "Japanese",
21482155
"ko-kr" => "Korean",
2149-
_ => throw new KeyNotFoundException($"Locale code: {localeCode} is not supported!")
2156+
_ => throwIfInvalid
2157+
? throw new NotSupportedException($"This locale code: {localeCode} is not supported")
2158+
: string.Empty
21502159
};
21512160
}
21522161

@@ -2365,7 +2374,7 @@ protected virtual void WriteAudioLangList(List<GameInstallPackage> gamePackage)
23652374
sw.WriteLine(langString);
23662375
}
23672376
}
2368-
#endregion
2377+
#endregion
23692378

23702379
#region Private Methods - GetInstallationPath
23712380
private async ValueTask<int> CheckExistingBHI3LInstallation(bool isHasOnlyMigrateOption = false)
@@ -2696,7 +2705,7 @@ private bool TryGetExistingSteamPath(ref string OutputPath)
26962705
}
26972706

26982707
List<AppInfo> steamAppList = SteamTool.GetSteamApps(steamLibsList);
2699-
#nullable enable
2708+
#nullable enable
27002709
AppInfo? steamAppInfo = steamAppList.FirstOrDefault(x => x.Id == steamID);
27012710

27022711
// If the app info is not null, then assign OutputPath to the game path
@@ -2707,14 +2716,14 @@ private bool TryGetExistingSteamPath(ref string OutputPath)
27072716

27082717
OutputPath = steamAppInfo.GameRoot;
27092718
return true;
2710-
#nullable disable
2719+
#nullable disable
27112720

27122721
// If none of them has it, then return false
27132722
}
27142723

27152724
private bool TryGetExistingBHI3LPath(ref string OutputPath)
27162725
{
2717-
#nullable enable
2726+
#nullable enable
27182727
// If the preset doesn't have BetterHi3Launcher registry ver info, then return false
27192728
if (GameVersionManager.GamePreset.BetterHi3LauncherVerInfoReg == null)
27202729
{
@@ -2840,7 +2849,7 @@ private async Task GetLatestPackageList(List<GameInstallPackage> packageList, Ga
28402849
}
28412850
}
28422851

2843-
#nullable enable
2852+
#nullable enable
28442853
protected virtual void TryAddPluginPackage(List<GameInstallPackage> assetList)
28452854
{
28462855
const string pluginKeyStart = "plugin_";
@@ -2910,7 +2919,7 @@ protected virtual void TryAddPluginPackage(List<GameInstallPackage> assetList)
29102919
assetList.Add(new GameInstallPackage(pluginResource.Value, GamePath));
29112920
}
29122921
}
2913-
#nullable restore
2922+
#nullable restore
29142923

29152924
private async ValueTask<int> CheckExistingOrAskFolderDialog()
29162925
{
@@ -2939,7 +2948,7 @@ private async ValueTask<int> CheckExistingOrAskFolderDialog()
29392948

29402949
#region Virtual Methods - GetInstallationPath
29412950

2942-
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
2951+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
29432952
protected virtual async ValueTask TryAddResourceVersionList(RegionResourceVersion asset,
29442953
List<GameInstallPackage> packageList,
29452954
bool isSkipMainPackage = false)
@@ -3067,7 +3076,7 @@ protected virtual async ValueTask AddMainResourceVersionList(RegionResourceVersi
30673076
}
30683077
}
30693078
}
3070-
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
3079+
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
30713080

30723081
protected virtual async ValueTask TryAddOtherInstalledVoicePacks(
30733082
List<RegionResourceVersion> packs, List<GameInstallPackage> packageList, string assetVersion)
@@ -3486,7 +3495,7 @@ private async ValueTask CheckDriveFreeSpace(List<GameInstallPackage> packageList
34863495
LogWriteLine($"Total free space required: {ConverterTool.SummarizeSizeSimple(remainedDownloadUncompressed)} remained to be downloaded (Total: {ConverterTool.SummarizeSizeSimple(requiredSpaceUncompressed)}) with {driveInfo.Name} remaining free space: {ConverterTool.SummarizeSizeSimple(diskFreeSpace)}",
34873496
LogType.Default, true);
34883497

3489-
#if DEBUG
3498+
#if DEBUG
34903499
double diskSpaceGb = Math.Round(ConverterTool.SummarizeSizeDouble(Convert.ToDouble(diskFreeSpace), 3), 4);
34913500
double requiredSpaceGb = Convert.ToDouble(requiredSpaceUncompressed / (1L << 30));
34923501
double existingPackageSizeGb = Convert.ToDouble(sizeDownloaded / (1L << 30));
@@ -3505,7 +3514,7 @@ private async ValueTask CheckDriveFreeSpace(List<GameInstallPackage> packageList
35053514
LogType.Debug);
35063515
LogWriteLine($"Remaining Package Download Size (Compressed Size)(GB): {remainingDownloadSizeGb}",
35073516
LogType.Debug);
3508-
#endif
3517+
#endif
35093518

35103519
if (diskFreeSpace < remainedDownloadUncompressed)
35113520
{
@@ -3550,9 +3559,9 @@ public void UpdateCompletenessStatus(CompletenessStatus status)
35503559
Status.IsRunning = true;
35513560
Status.IsCompleted = false;
35523561
Status.IsCanceled = false;
3553-
#if !DISABLEDISCORD
3562+
#if !DISABLEDISCORD
35543563
InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Update);
3555-
#endif
3564+
#endif
35563565
break;
35573566
case CompletenessStatus.Completed:
35583567
IsRunning = false;
@@ -3561,9 +3570,9 @@ public void UpdateCompletenessStatus(CompletenessStatus status)
35613570
Status.IsCanceled = false;
35623571
Status.IsProgressAllIndetermined = false;
35633572
Status.IsProgressPerFileIndetermined = false;
3564-
#if !DISABLEDISCORD
3573+
#if !DISABLEDISCORD
35653574
InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle);
3566-
#endif
3575+
#endif
35673576
// HACK: Fix the progress not achieving 100% while completed
35683577
lock (Progress)
35693578
{
@@ -3578,9 +3587,9 @@ public void UpdateCompletenessStatus(CompletenessStatus status)
35783587
Status.IsCanceled = true;
35793588
Status.IsProgressAllIndetermined = false;
35803589
Status.IsProgressPerFileIndetermined = false;
3581-
#if !DISABLEDISCORD
3590+
#if !DISABLEDISCORD
35823591
InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle);
3583-
#endif
3592+
#endif
35843593
break;
35853594
case CompletenessStatus.Idle:
35863595
IsRunning = false;
@@ -3589,9 +3598,9 @@ public void UpdateCompletenessStatus(CompletenessStatus status)
35893598
Status.IsCanceled = false;
35903599
Status.IsProgressAllIndetermined = false;
35913600
Status.IsProgressPerFileIndetermined = false;
3592-
#if !DISABLEDISCORD
3601+
#if !DISABLEDISCORD
35933602
InnerLauncherConfig.AppDiscordPresence?.SetActivity(ActivityType.Idle);
3594-
#endif
3603+
#endif
35953604
break;
35963605
}
35973606

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using CollapseLauncher.Helper.Metadata;
2+
using CollapseLauncher.Helper.StreamUtility;
3+
using CollapseLauncher.Interfaces;
4+
using Hi3Helper;
5+
using Hi3Helper.EncTool.Parser.AssetIndex;
6+
using Hi3Helper.Http;
7+
using Hi3Helper.Sophon;
8+
using Hi3Helper.Sophon.Structs;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.IO;
12+
using System.Net.Http;
13+
using System.Threading;
14+
using System.Threading.Tasks;
15+
// ReSharper disable IdentifierTypo
16+
// ReSharper disable CommentTypo
17+
// ReSharper disable StringLiteralTypo
18+
19+
namespace CollapseLauncher.InstallManager.Genshin;
20+
21+
#nullable enable
22+
internal sealed partial class GenshinInstall
23+
{
24+
protected override async Task<string> DownloadPkgVersion(DownloadClient downloadClient, RegionResourceVersion? packageLatestBase)
25+
{
26+
const string errorRaiseMsg = "Please raise this issue to our Github Repo or Official Discord";
27+
28+
// Note:
29+
// Starting from Genshin 5.6 update, HoYo removed ScatteredFiles alongside Zip packages.
30+
// This cause the pkg_version to be unavailable. As for alternative, we are replacing it
31+
// by making a fake JSON response of pkg_version by fetching references from Sophon manifest.
32+
33+
// Get GameVersionManager and GamePreset
34+
IGameVersion gameVersion = GameVersionManager;
35+
PresetConfig gamePreset = gameVersion.GamePreset;
36+
SophonChunkUrls? branchResources = gamePreset.LauncherResourceChunksURL;
37+
38+
// If branchResources is null, throw
39+
if (branchResources == null)
40+
{
41+
throw new InvalidOperationException("Branch resources are unavailable! " + errorRaiseMsg);
42+
}
43+
44+
// Try to get client and manifest info pair
45+
HttpClient client = downloadClient.GetHttpClient();
46+
SophonChunkManifestInfoPair manifestInfoPair = await SophonManifest
47+
.CreateSophonChunkManifestInfoPair(client,
48+
branchResources.MainUrl,
49+
branchResources.MainBranchMatchingField,
50+
Token.Token);
51+
52+
// Throw if main manifest pair is not found
53+
if (!manifestInfoPair.IsFound)
54+
{
55+
throw new InvalidOperationException($"Sophon main manifest info pair is not found! " + errorRaiseMsg);
56+
}
57+
58+
// Get the existing voice-over matching fields
59+
List<string> availableVaMatchingFields = [];
60+
await GetVoiceOverPkgVersionMatchingFields(availableVaMatchingFields);
61+
62+
// If any, then create them
63+
if (availableVaMatchingFields.Count != 0)
64+
{
65+
foreach (var field in availableVaMatchingFields)
66+
{
67+
SophonChunkManifestInfoPair voManifestInfoPair = manifestInfoPair.GetOtherManifestInfoPair(field);
68+
if (!voManifestInfoPair.IsFound)
69+
{
70+
throw new InvalidOperationException($"Sophon voice-over manifest info pair for field: {field} is not found! " + errorRaiseMsg);
71+
}
72+
73+
string languageString = GetLanguageStringByLocaleCode(field);
74+
await CreateFakePkgVersionFromSophon(client,
75+
voManifestInfoPair,
76+
$"Audio_{languageString}_pkg_version",
77+
Token.Token);
78+
}
79+
}
80+
81+
// Create the main pkg_version
82+
await CreateFakePkgVersionFromSophon(client,
83+
manifestInfoPair,
84+
"pkg_version",
85+
Token.Token);
86+
87+
// Return the main pkg_version path
88+
return Path.Combine(GamePath, "pkg_version");
89+
}
90+
91+
private async Task CreateFakePkgVersionFromSophon(HttpClient client,
92+
SophonChunkManifestInfoPair manifestPair,
93+
string pkgVersionFilename,
94+
CancellationToken token)
95+
{
96+
// Ensure and try to create examine FileInfo
97+
string filePath = Path.Combine(GamePath, pkgVersionFilename);
98+
FileInfo fileInfo = new FileInfo(filePath)
99+
.EnsureCreationOfDirectory()
100+
.EnsureNoReadOnly();
101+
102+
// Create file stream
103+
await using FileStream stream = fileInfo.Create();
104+
105+
// Start enumerate the SophonAsset
106+
byte[] newLineBytes = "\r\n"u8.ToArray();
107+
await foreach (SophonAsset assetInfo in SophonManifest.EnumerateAsync(client,
108+
manifestPair,
109+
null,
110+
token))
111+
{
112+
// Ignore stock pkg_version
113+
if (assetInfo.AssetName.EndsWith("pkg_version", StringComparison.OrdinalIgnoreCase))
114+
{
115+
continue;
116+
}
117+
118+
// Create pkg_version entry
119+
PkgVersionProperties pkgVersionEntry = new PkgVersionProperties
120+
{
121+
remoteName = assetInfo.AssetName,
122+
md5 = assetInfo.AssetHash,
123+
fileSize = assetInfo.AssetSize
124+
};
125+
126+
// Serialize and write to stream
127+
await pkgVersionEntry.SerializeAsync(stream, CoreLibraryJsonContext.Default.PkgVersionProperties, token);
128+
await stream.WriteAsync(newLineBytes, token);
129+
}
130+
}
131+
132+
private async Task GetVoiceOverPkgVersionMatchingFields(List<string> matchingFields)
133+
{
134+
// Try to get audio lang list file and if null, ignore
135+
string audioLangListPath = _gameAudioLangListPathStatic;
136+
if (string.IsNullOrEmpty(audioLangListPath))
137+
{
138+
return;
139+
}
140+
141+
// Try get existing audio lang file. If it doesn't exist, ignore
142+
FileInfo audioLangFile = new FileInfo(audioLangListPath);
143+
if (!audioLangFile.Exists)
144+
{
145+
return;
146+
}
147+
148+
// Open the file and read the content
149+
await using FileStream audioLangFileStream = audioLangFile.OpenRead();
150+
using StreamReader reader = new StreamReader(audioLangFileStream);
151+
while (await reader.ReadLineAsync(Token.Token) is { } line)
152+
{
153+
// Get locale code to be used as Sophon Manifest field later
154+
string? currentLocaleId = GetLanguageLocaleCodeByLanguageString(line
155+
#if !DEBUG
156+
, false
157+
#endif
158+
);
159+
160+
// If it's empty or invalid, go to next line
161+
if (string.IsNullOrEmpty(currentLocaleId))
162+
{
163+
continue;
164+
}
165+
166+
// Otherwise, Add the field to the list
167+
matchingFields.Add(currentLocaleId);
168+
}
169+
}
170+
}

Hi3Helper.Core/Classes/Shared/ClassStruct/Class/GameDataStructure.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ public string CRC
3636
public string M { get; set; }
3737
public FileType FT { get; set; }
3838
public List<XMFBlockList> BlkC { get; set; }
39+
#nullable enable
3940
public ManifestAudioPatchInfo? AudioPatchInfo { get; set; }
4041
public BlockPatchInfo? BlockPatchInfo { get; set; }
42+
#nullable restore
4143
public long S { get; set; }
4244
public bool IsPatchApplicable { get; set; }
4345
public bool IsBlockNeedRepair { get; set; }

Hi3Helper.EncTool

0 commit comments

Comments
 (0)