Skip to content

Commit 9e8e74b

Browse files
committed
[Genshin] Use Sophon for Repair and Cleanup ref
1 parent e33da83 commit 9e8e74b

File tree

7 files changed

+368
-86
lines changed

7 files changed

+368
-86
lines changed

CollapseLauncher/Classes/InstallManagement/Base/InstallManagerBase.cs

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,19 +2145,24 @@ protected virtual string GetLanguageStringByLocaleCode([NotNull] string? localeC
21452145
#else
21462146
false
21472147
#endif
2148-
)
2149-
{
2150-
return localeCode switch
2151-
{
2152-
"zh-cn" => "Chinese",
2153-
"en-us" => "English(US)",
2154-
"ja-jp" => "Japanese",
2155-
"ko-kr" => "Korean",
2156-
_ => throwIfInvalid
2157-
? throw new NotSupportedException($"This locale code: {localeCode} is not supported")
2158-
: string.Empty
2159-
};
2160-
}
2148+
) => GetLanguageStringByLocaleCodeStatic(localeCode, throwIfInvalid);
2149+
2150+
internal static string GetLanguageStringByLocaleCodeStatic([NotNull] string? localeCode, bool throwIfInvalid =
2151+
#if DEBUG
2152+
true
2153+
#else
2154+
false
2155+
#endif
2156+
) => localeCode switch
2157+
{
2158+
"zh-cn" => "Chinese",
2159+
"en-us" => "English(US)",
2160+
"ja-jp" => "Japanese",
2161+
"ko-kr" => "Korean",
2162+
_ => throwIfInvalid
2163+
? throw new NotSupportedException($"This locale code: {localeCode} is not supported")
2164+
: string.Empty
2165+
};
21612166

21622167
protected virtual string GetLanguageStringByID(int id)
21632168
{
@@ -2171,20 +2176,31 @@ protected virtual string GetLanguageStringByID(int id)
21712176
};
21722177
}
21732178

2174-
protected virtual string? GetLanguageLocaleCodeByLanguageString([NotNullIfNotNull(nameof(langString))] string? langString, bool throwIfInvalid = true)
2175-
{
2176-
return langString switch
2177-
{
2178-
"Chinese" => "zh-cn",
2179-
"English" => "en-us",
2180-
"English(US)" => "en-us",
2181-
"Korean" => "ko-kr",
2182-
"Japanese" => "ja-jp",
2183-
_ => throwIfInvalid
2184-
? throw new NotSupportedException($"This language string: {langString} is not supported")
2185-
: null
2186-
};
2187-
}
2179+
protected virtual string? GetLanguageLocaleCodeByLanguageString([NotNullIfNotNull(nameof(langString))] string? langString, bool throwIfInvalid =
2180+
#if DEBUG
2181+
true
2182+
#else
2183+
false
2184+
#endif
2185+
) => GetLanguageLocaleCodeByLanguageStringStatic(langString, throwIfInvalid);
2186+
2187+
internal static string? GetLanguageLocaleCodeByLanguageStringStatic([NotNullIfNotNull(nameof(langString))] string? langString, bool throwIfInvalid =
2188+
#if DEBUG
2189+
true
2190+
#else
2191+
false
2192+
#endif
2193+
) => langString switch
2194+
{
2195+
"Chinese" => "zh-cn",
2196+
"English" => "en-us",
2197+
"English(US)" => "en-us",
2198+
"Korean" => "ko-kr",
2199+
"Japanese" => "ja-jp",
2200+
_ => throwIfInvalid
2201+
? throw new NotSupportedException($"This language string: {langString} is not supported")
2202+
: null
2203+
};
21882204

21892205
protected virtual string? GetLanguageDisplayByLocaleCode([NotNullIfNotNull(nameof(localeCode))] string? localeCode, bool throwIfInvalid = true)
21902206
{

CollapseLauncher/Classes/InstallManagement/Genshin/GenshinInstall.PkgVersion.cs

Lines changed: 151 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
using CollapseLauncher.Helper.Metadata;
1+
using CollapseLauncher.GameVersioning;
2+
using CollapseLauncher.Helper.Metadata;
23
using CollapseLauncher.Helper.StreamUtility;
4+
using CollapseLauncher.InstallManager.Base;
35
using CollapseLauncher.Interfaces;
6+
using CollapseLauncher.Statics;
47
using Hi3Helper;
58
using Hi3Helper.EncTool.Parser.AssetIndex;
69
using Hi3Helper.Http;
@@ -21,7 +24,117 @@ namespace CollapseLauncher.InstallManager.Genshin;
2124
#nullable enable
2225
internal sealed partial class GenshinInstall
2326
{
24-
protected override async Task<string> DownloadPkgVersion(DownloadClient downloadClient, RegionResourceVersion? packageLatestBase)
27+
protected override async Task<string> DownloadPkgVersion(DownloadClient downloadClient,
28+
RegionResourceVersion? _)
29+
{
30+
// First, build the fake pkg_version from Sophon
31+
string pkgVersionPath = await DownloadPkgVersionStatic(downloadClient.GetHttpClient(),
32+
GameVersionManager,
33+
GamePath,
34+
_gameAudioLangListPathStatic,
35+
token: Token.Token);
36+
37+
// Second, build persistent manifest from game_res dispatcher.
38+
// If game repair is not available, then skip.
39+
GamePresetProperty presetProperty = GamePropertyVault.GetCurrentGameProperty();
40+
if (presetProperty.GameRepair is not GenshinRepair genshinRepairInstance)
41+
{
42+
return pkgVersionPath;
43+
}
44+
45+
// Call and borrow persistent manifest method from GenshinRepair instance
46+
await genshinRepairInstance.BuildPersistentManifest(downloadClient,
47+
null,
48+
[],
49+
[],
50+
Token.Token);
51+
52+
return pkgVersionPath;
53+
}
54+
55+
protected override async ValueTask ParsePkgVersions2FileInfo(List<LocalFileInfo> pkgFileInfo,
56+
HashSet<string> pkgFileInfoHashSet,
57+
CancellationToken token)
58+
{
59+
// Parse primary pkg_version as main manifest
60+
await base.ParsePkgVersions2FileInfo(pkgFileInfo,
61+
pkgFileInfoHashSet,
62+
token);
63+
64+
string? execPrefix = Path.GetFileNameWithoutExtension(GameVersionManager.GamePreset.GameExecutableName);
65+
if (string.IsNullOrEmpty(execPrefix))
66+
{
67+
return;
68+
}
69+
70+
string basePersistentPath = $"{execPrefix}_Data\\Persistent";
71+
string baseStreamingAssetsPath = $"{execPrefix}_Data\\StreamingAssets";
72+
string persistentFolder = Path.Combine(GamePath, basePersistentPath);
73+
74+
List<string>? audioLangList = (GameVersionManager as GameTypeGenshinVersion)?.AudioVoiceLanguageList;
75+
string audioLangListPath = Path.Combine(GamePath, basePersistentPath, "audio_lang_14");
76+
77+
// Then add additional parsing to persistent manifests (provided by dispatcher)
78+
string persistentResVersions = Path.Combine(persistentFolder, "res_versions_persist");
79+
if (File.Exists(persistentResVersions))
80+
{
81+
await ParsePersistentManifest2FileInfo(GamePath,
82+
asset => Path.Combine(baseStreamingAssetsPath,
83+
GenshinRepair.GetParentFromAssetRelativePath(asset.RelativePath,
84+
out _)),
85+
persistentResVersions,
86+
pkgFileInfo,
87+
pkgFileInfoHashSet,
88+
token);
89+
}
90+
91+
// Filter unnecessary files
92+
if (audioLangList != null)
93+
{
94+
GenshinRepair.EliminateUnnecessaryAssetIndex(audioLangListPath, audioLangList, pkgFileInfo, '\\', x => x.FullPath);
95+
}
96+
}
97+
98+
private static async Task ParsePersistentManifest2FileInfo(string baseGamePath,
99+
Func<LocalFileInfo, string?>? assetGetMiddlePath,
100+
string pkgFilePath,
101+
List<LocalFileInfo> pkgFileInfo,
102+
HashSet<string> pkgFileInfoHashSet,
103+
CancellationToken token)
104+
{
105+
using StreamReader reader = File.OpenText(pkgFilePath);
106+
while (await reader.ReadLineAsync(token) is { } line)
107+
{
108+
LocalFileInfo? localFileInfo = line.Deserialize(LocalFileInfoJsonContext.Default.LocalFileInfo);
109+
110+
// If null, then go to next line
111+
if (localFileInfo == null)
112+
continue;
113+
114+
string? middlePath = assetGetMiddlePath?.Invoke(localFileInfo);
115+
string fullBasePath = string.IsNullOrEmpty(middlePath) ? baseGamePath : Path.Combine(baseGamePath, middlePath);
116+
117+
localFileInfo.FullPath = Path.Combine(fullBasePath, localFileInfo.RelativePath);
118+
localFileInfo.FileName = Path.GetFileName(localFileInfo.RelativePath);
119+
localFileInfo.IsFileExist = File.Exists(localFileInfo.FullPath);
120+
121+
string relativePathNoBase = Path.Combine(middlePath ?? "", localFileInfo.RelativePath);
122+
123+
// Add it to the list and hashset (if it's not registered yet)
124+
if (pkgFileInfoHashSet.Add(relativePathNoBase))
125+
{
126+
pkgFileInfo.Add(localFileInfo);
127+
}
128+
}
129+
}
130+
131+
public static async Task<string> DownloadPkgVersionStatic(HttpClient client,
132+
IGameVersion gameVersionManager,
133+
string gameBasePath,
134+
string gameAudioListPath,
135+
SophonChunkManifestInfoPair? manifestInfoPair = null,
136+
List<SophonAsset>? outputAssetList = null,
137+
CancellationToken token = default)
25138
{
26139
const string errorRaiseMsg = "Please raise this issue to our Github Repo or Official Discord";
27140

@@ -31,8 +144,7 @@ protected override async Task<string> DownloadPkgVersion(DownloadClient download
31144
// by making a fake JSON response of pkg_version by fetching references from Sophon manifest.
32145

33146
// Get GameVersionManager and GamePreset
34-
IGameVersion gameVersion = GameVersionManager;
35-
PresetConfig gamePreset = gameVersion.GamePreset;
147+
PresetConfig gamePreset = gameVersionManager.GamePreset;
36148
SophonChunkUrls? branchResources = gamePreset.LauncherResourceChunksURL;
37149

38150
// If branchResources is null, throw
@@ -42,25 +154,34 @@ protected override async Task<string> DownloadPkgVersion(DownloadClient download
42154
}
43155

44156
// Try to get client and manifest info pair
45-
HttpClient client = downloadClient.GetHttpClient();
46-
SophonChunkManifestInfoPair manifestInfoPair = await SophonManifest
157+
manifestInfoPair ??= await SophonManifest
47158
.CreateSophonChunkManifestInfoPair(client,
48159
branchResources.MainUrl,
49160
branchResources.MainBranchMatchingField,
50-
Token.Token);
161+
token);
51162

52163
// Throw if main manifest pair is not found
53164
if (!manifestInfoPair.IsFound)
54165
{
55-
throw new InvalidOperationException($"Sophon main manifest info pair is not found! " + errorRaiseMsg);
166+
throw new InvalidOperationException("Sophon main manifest info pair is not found! " + errorRaiseMsg);
56167
}
57168

169+
// Create the main pkg_version
170+
await CreateFakePkgVersionFromSophon(client,
171+
gameBasePath,
172+
manifestInfoPair,
173+
outputAssetList,
174+
"pkg_version",
175+
token);
176+
58177
// Get the existing voice-over matching fields
59178
List<string> availableVaMatchingFields = [];
60-
await GetVoiceOverPkgVersionMatchingFields(availableVaMatchingFields);
179+
await GetVoiceOverPkgVersionMatchingFields(availableVaMatchingFields,
180+
gameAudioListPath,
181+
token);
61182

62-
// If any, then create them
63-
if (availableVaMatchingFields.Count != 0)
183+
// ReSharper disable once InvertIf
184+
if (availableVaMatchingFields.Count != 0) // If any, then create them
64185
{
65186
foreach (var field in availableVaMatchingFields)
66187
{
@@ -70,31 +191,29 @@ protected override async Task<string> DownloadPkgVersion(DownloadClient download
70191
throw new InvalidOperationException($"Sophon voice-over manifest info pair for field: {field} is not found! " + errorRaiseMsg);
71192
}
72193

73-
string languageString = GetLanguageStringByLocaleCode(field);
194+
string languageString = GetLanguageStringByLocaleCodeStatic(field);
74195
await CreateFakePkgVersionFromSophon(client,
196+
gameBasePath,
75197
voManifestInfoPair,
198+
outputAssetList,
76199
$"Audio_{languageString}_pkg_version",
77-
Token.Token);
200+
token);
78201
}
79202
}
80203

81-
// Create the main pkg_version
82-
await CreateFakePkgVersionFromSophon(client,
83-
manifestInfoPair,
84-
"pkg_version",
85-
Token.Token);
86-
87204
// Return the main pkg_version path
88-
return Path.Combine(GamePath, "pkg_version");
205+
return Path.Combine(gameBasePath, "pkg_version");
89206
}
90207

91-
private async Task CreateFakePkgVersionFromSophon(HttpClient client,
92-
SophonChunkManifestInfoPair manifestPair,
93-
string pkgVersionFilename,
94-
CancellationToken token)
208+
private static async Task CreateFakePkgVersionFromSophon(HttpClient client,
209+
string gamePath,
210+
SophonChunkManifestInfoPair manifestPair,
211+
List<SophonAsset>? outputAssetList,
212+
string pkgVersionFilename,
213+
CancellationToken token)
95214
{
96215
// Ensure and try to create examine FileInfo
97-
string filePath = Path.Combine(GamePath, pkgVersionFilename);
216+
string filePath = Path.Combine(gamePath, pkgVersionFilename);
98217
FileInfo fileInfo = new FileInfo(filePath)
99218
.EnsureCreationOfDirectory()
100219
.EnsureNoReadOnly();
@@ -126,13 +245,16 @@ private async Task CreateFakePkgVersionFromSophon(HttpClient cl
126245
// Serialize and write to stream
127246
await pkgVersionEntry.SerializeAsync(stream, CoreLibraryJsonContext.Default.PkgVersionProperties, token);
128247
await stream.WriteAsync(newLineBytes, token);
248+
249+
outputAssetList?.Add(assetInfo);
129250
}
130251
}
131252

132-
private async Task GetVoiceOverPkgVersionMatchingFields(List<string> matchingFields)
253+
private static async Task GetVoiceOverPkgVersionMatchingFields(List<string> matchingFields,
254+
string audioLangListPath,
255+
CancellationToken token)
133256
{
134257
// Try to get audio lang list file and if null, ignore
135-
string audioLangListPath = _gameAudioLangListPathStatic;
136258
if (string.IsNullOrEmpty(audioLangListPath))
137259
{
138260
return;
@@ -148,10 +270,10 @@ private async Task GetVoiceOverPkgVersionMatchingFields(List<string> matchingFie
148270
// Open the file and read the content
149271
await using FileStream audioLangFileStream = audioLangFile.OpenRead();
150272
using StreamReader reader = new StreamReader(audioLangFileStream);
151-
while (await reader.ReadLineAsync(Token.Token) is { } line)
273+
while (await reader.ReadLineAsync(token) is { } line)
152274
{
153275
// Get locale code to be used as Sophon Manifest field later
154-
string? currentLocaleId = GetLanguageLocaleCodeByLanguageString(line
276+
string? currentLocaleId = GetLanguageLocaleCodeByLanguageStringStatic(line
155277
#if !DEBUG
156278
, false
157279
#endif

CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List<PkgVe
228228
async ValueTask<bool> IsFileMatched(Action<bool> storeAsStreaming, Action<bool> storeAsPersistent, Action<string, string> storeAsUnused)
229229
{
230230
bool isUsePersistent = asset.isPatch ||
231-
(!asset.xxh64hashPersistent?.Equals(asset.xxh64hash, StringComparison.OrdinalIgnoreCase) ?? false);
231+
(!string.IsNullOrEmpty(asset.xxh64hash) &&
232+
!string.IsNullOrEmpty(asset.xxh64hashPersistent) &&
233+
!asset.xxh64hashPersistent.Equals(asset.xxh64hash, StringComparison.OrdinalIgnoreCase));
232234

233235
// Try to get hash buffer
234236
byte[] hashBuffer = ArrayPool<byte>.Shared.Rent(16);

0 commit comments

Comments
 (0)