Skip to content

Commit 4b21ccf

Browse files
committed
Improve Hi3 Cache Update and Game Repair file checks
+ Fix broken cutscene detection due to unmatching file size reported by metadata vs. the actual size reported by the URL (good job, HoYo. As always :]) + Dynamically loads Cache Update's unused file ignore list from preset config.
1 parent 60e2530 commit 4b21ccf

File tree

6 files changed

+120
-44
lines changed

6 files changed

+120
-44
lines changed

CollapseLauncher/Classes/CachesManagement/Honkai/Check.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,6 @@ private async Task<List<CacheAsset>> Check(List<CacheAsset> assetIndex, Cancella
5858
return returnAsset;
5959
}
6060

61-
private readonly SearchValues<string> _unusedSearchValues = SearchValues.Create([
62-
"output_log",
63-
"Crashes",
64-
"Verify.txt",
65-
"APM",
66-
"FBData",
67-
"asb.dat"
68-
], StringComparison.OrdinalIgnoreCase);
69-
7061
private void CheckUnusedAssets(List<CacheAsset> assetIndex, List<CacheAsset> returnAsset)
7162
{
7263
// Directory info and if the directory doesn't exist, return
@@ -76,13 +67,25 @@ private void CheckUnusedAssets(List<CacheAsset> assetIndex, List<CacheAsset> ret
7667
return;
7768
}
7869

70+
SearchValues<string> unusedSearchValues = SearchValues
71+
.Create(GameVersionManager.GamePreset.GameInstallFileInfo?.CacheUpdateUnusedFilesIgnoreList
72+
?? [
73+
"output_log",
74+
"Crashes",
75+
"Verify.txt",
76+
"APM",
77+
"FBData",
78+
"asb.dat",
79+
"MiHoYoSDK.log"
80+
], StringComparison.OrdinalIgnoreCase);
81+
7982
// Iterate the file contained in the _gamePath
8083
foreach (FileInfo fileInfo in directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories)
8184
.EnumerateNoReadOnly())
8285
{
8386
ReadOnlySpan<char> filePath = fileInfo.FullName;
8487

85-
if (filePath.ContainsAny(_unusedSearchValues)
88+
if (filePath.ContainsAny(unusedSearchValues)
8689
|| assetIndex.Exists(x => x.ConcatPath == fileInfo.FullName))
8790
{
8891
continue;

CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,12 @@ public enum LauncherType
8181

8282
public class GameInstallFileInfo
8383
{
84-
public string GameDataFolderName { get; init; } = string.Empty;
85-
public string[] FilesToDelete { get; init; } = [];
86-
public string[] FoldersToDelete { get; init; } = [];
87-
public string[] FoldersToKeepInData { get; init; } = [];
88-
public string[] FilesCleanupIgnoreList { get; init; } = [];
84+
public string GameDataFolderName { get; init; } = string.Empty;
85+
public string[] FilesToDelete { get; init; } = [];
86+
public string[] FoldersToDelete { get; init; } = [];
87+
public string[] FoldersToKeepInData { get; init; } = [];
88+
public string[] FilesCleanupIgnoreList { get; init; } = [];
89+
public string[] CacheUpdateUnusedFilesIgnoreList { get; init; } = [];
8990
}
9091

9192
public class SophonChunkUrls

CollapseLauncher/Classes/Helper/StreamUtility/StreamExtension.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -309,28 +309,42 @@ public static FileInfo ResolveSymlink(this FileInfo fileInfo)
309309
/// </summary>
310310
/// <param name="dir">The directory to remove.</param>
311311
/// <param name="recursive">Whether to remove all possibly empty directories recursively.</param>
312-
public static void DeleteEmptyDirectory(this string dir, bool recursive = false)
312+
public static bool DeleteEmptyDirectory(this string dir, bool recursive = false)
313313
=> new DirectoryInfo(dir).DeleteEmptyDirectory(recursive);
314314

315315
/// <summary>
316316
/// Deletes the directory if it is empty.
317317
/// </summary>
318318
/// <param name="dir">The directory to remove.</param>
319319
/// <param name="recursive">Whether to remove all possibly empty directories recursively.</param>
320-
public static void DeleteEmptyDirectory(this DirectoryInfo dir, bool recursive = false)
320+
public static bool DeleteEmptyDirectory(this DirectoryInfo dir, bool recursive = false)
321321
{
322-
if (recursive)
322+
try
323323
{
324-
foreach (DirectoryInfo childDir in dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
324+
if (!dir.Exists)
325325
{
326-
childDir.DeleteEmptyDirectory();
326+
return true;
327+
}
328+
329+
if (recursive)
330+
{
331+
foreach (DirectoryInfo childDir in dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
332+
{
333+
childDir.DeleteEmptyDirectory();
334+
}
327335
}
328-
}
329336

330-
FindFiles.TryIsDirectoryEmpty(dir.FullName, out bool isEmpty);
331-
if (isEmpty)
337+
FindFiles.TryIsDirectoryEmpty(dir.FullName, out bool isEmpty);
338+
if (isEmpty)
339+
{
340+
dir.Delete(true);
341+
}
342+
343+
return true;
344+
}
345+
catch
332346
{
333-
dir.Delete(true);
347+
return false;
334348
}
335349
}
336350

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

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ internal static partial class AssetBundleExtension
3131
internal const string RelativePathVideo = @"BH3_Data\StreamingAssets\Video\";
3232
internal const string MetadataFilename = "107438912";
3333

34+
internal static void RemoveUnlistedVideoAssetFromList(this List<FilePropertiesRemote> originList,
35+
List<FilePropertiesRemote> assetListFromVideo)
36+
{
37+
List<FilePropertiesRemote> originOthersListOnly = originList.Where(x => x.FT != FileType.Video).ToList();
38+
List<FilePropertiesRemote> originVideoListOnly = originList.Where(x => x.FT == FileType.Video).ToList();
39+
originList.Clear();
40+
originList.AddRange(originOthersListOnly);
41+
42+
HashSet<string> assetListVideoDict =
43+
assetListFromVideo.Select(x => x.N).ToHashSet(StringComparer.OrdinalIgnoreCase);
44+
45+
originList.AddRange(originVideoListOnly.Where(originVideoAsset => assetListVideoDict.Contains(originVideoAsset.N)));
46+
}
47+
3448
internal static async Task<List<FilePropertiesRemote>>
3549
GetVideoAssetListAsync<T>(
3650
this HttpClient assetBundleHttpClient,
@@ -47,12 +61,12 @@ internal static async Task<List<FilePropertiesRemote>>
4761

4862
HashSet<int> ignoredCgHashset = new(ignoredCgIds ?? []);
4963
List<CacheAssetInfo> assetInfoList =
50-
await GetCacheAssetBundleListAsync(assetBundleHttpClient,
51-
presetConfig,
52-
gameServerInfo,
53-
CacheAssetType.Data,
54-
progressibleInstance,
55-
token);
64+
await assetBundleHttpClient
65+
.GetCacheAssetBundleListAsync(presetConfig,
66+
gameServerInfo,
67+
CacheAssetType.Data,
68+
progressibleInstance,
69+
token);
5670

5771
CacheAssetInfo? cgMetadataFile = assetInfoList
5872
.FirstOrDefault(x => x.Asset.N.EndsWith(MetadataFilename));
@@ -97,8 +111,7 @@ await Parallel
97111

98112
async ValueTask ImplCheckAndAdd(KeyValuePair<int, KianaCgMetadata> entry, CancellationToken innerToken)
99113
{
100-
if (entry.Value.DownloadMode == CGDownloadMode.DownloadTipAlways ||
101-
ignoredCgHashset.Contains(entry.Value.SubCategoryId))
114+
if (ignoredCgHashset.Contains(entry.Value.SubCategoryId))
102115
{
103116
return;
104117
}
@@ -117,14 +130,6 @@ async ValueTask ImplCheckAndAdd(KeyValuePair<int, KianaCgMetadata> entry, Cancel
117130
string assetUrl = (isUseHttpRepairOverride ? "http://" : "https://") + baseUrl;
118131
assetUrl = assetUrl.CombineURLFromString("Video", assetName);
119132

120-
// If the file has no appoinment schedule (like non-birthday CG), then return true
121-
/*
122-
if (entry.AppointmentDownloadScheduleID == 0)
123-
{
124-
goto AddCgEntry; // I love goto. Dun ask me why :>
125-
}
126-
*/
127-
128133
// Update status
129134
if (progressibleInstance != null)
130135
{
@@ -158,7 +163,7 @@ async ValueTask ImplCheckAndAdd(KeyValuePair<int, KianaCgMetadata> entry, Cancel
158163
N = assetName,
159164
RN = assetUrl,
160165
S = assetFilesize,
161-
AssociatedObject = entry
166+
AssociatedObject = entry.Value
162167
});
163168
}
164169
return;

CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Check.Generic.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
using CollapseLauncher.RepairManagement;
44
using Hi3Helper;
55
using Hi3Helper.Data;
6+
using Hi3Helper.EncTool;
67
using Hi3Helper.EncTool.Hashes;
8+
using Hi3Helper.EncTool.Parser.CacheParser;
79
using Hi3Helper.EncTool.Parser.Senadina;
810
using Hi3Helper.Shared.ClassStruct;
911
using System;
@@ -49,6 +51,39 @@ private async Task<bool> IsHashMatchedAuto(
4951
token)).IsHashMatched;
5052
}
5153

54+
/// <summary>
55+
/// Check actual remote size asset from the actual URL.<br/>
56+
/// <br/>
57+
/// Note: This method only works on <see cref="FileType.Video"/> asset type with URL defined.
58+
/// Otherwise, the method will immediately returns <see langword="true"/>.
59+
/// </summary>
60+
private async ValueTask<bool> TryIsAssetRemoteSizeEquals(
61+
FilePropertiesRemote asset,
62+
FileInfo fileInfo,
63+
bool useFastCheck,
64+
CancellationToken token = default)
65+
{
66+
if (!fileInfo.Exists)
67+
{
68+
return false;
69+
}
70+
71+
if (asset.FT != FileType.Video ||
72+
string.IsNullOrEmpty(asset.RN) ||
73+
useFastCheck)
74+
{
75+
return true;
76+
}
77+
78+
UrlStatus status = await HttpClientAssetBundle.GetCachedUrlStatus(asset.RN, token);
79+
if (!status.IsSuccessStatusCode || status.FileSize == 0) // Returns true if status is not successful or size is 0 anyways
80+
{
81+
return true;
82+
}
83+
84+
return fileInfo.Exists && status.FileSize == fileInfo.Length;
85+
}
86+
5287
private async Task<(bool IsHashMatched, int HashSize)> IsHashMatchedAuto(
5388
FilePropertiesRemote asset,
5489
byte[] hashBuffer,
@@ -78,6 +113,12 @@ private async Task<bool> IsHashMatchedAuto(
78113

79114
if (!isAssetExist || (assetFileInfo.Length != asset.S && !isSkipSizeCheck))
80115
{
116+
// Try alternate check for video
117+
if (await TryIsAssetRemoteSizeEquals(asset, assetFileInfo, useFastCheck, token))
118+
{
119+
return (true, hashSize);
120+
}
121+
81122
Interlocked.Add(ref ProgressAllSizeCurrent, asset.S);
82123
if (addAssetIfBroken)
83124
{

CollapseLauncher/Classes/RepairManagement/HonkaiV2/HonkaiRepairV2.Fetch.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ private async Task FetchAssetFromGameAssetBundle(List<FilePropertiesRemote> asse
8383

8484
#region Fetch Video Assets from AssetBundle
8585
List<FilePropertiesRemote> assetListFromVideo = [];
86+
List<FilePropertiesRemote> assetListFromVideoOnlyDownloadable = [];
8687
Task assetListFromVideoTask =
8788
HttpClientAssetBundle
8889
.GetVideoAssetListAsync(gamePresetConfig,
@@ -93,6 +94,7 @@ private async Task FetchAssetFromGameAssetBundle(List<FilePropertiesRemote> asse
9394
.GetResultFromAction(result =>
9495
{
9596
assetListFromVideo.AddRange(result);
97+
assetListFromVideoOnlyDownloadable.AddRange(result.Where(x => ((KianaCgMetadata)x.AssociatedObject).DownloadMode == CGDownloadMode.DownloadTipOnce));
9698
FinalizeVideoAssetsPath(assetListFromVideo);
9799
});
98100
#endregion
@@ -144,9 +146,18 @@ await Task.WhenAll(assetListFromVideoTask,
144146
assetListFromBlockTask);
145147
#endregion
146148

149+
#region Remove Video Assets from base
150+
151+
if (!IsMainAssetOnlyMode && !IsCacheMode)
152+
{
153+
assetIndex.RemoveUnlistedVideoAssetFromList(assetListFromVideo);
154+
}
155+
156+
#endregion
157+
147158
// Finalize the asset index list by overriding it from above additional sources.
148159
FinalizeBaseAssetIndex(assetIndex,
149-
assetListFromVideo,
160+
assetListFromVideoOnlyDownloadable,
150161
assetListFromAudio,
151162
assetListFromBlock);
152163
}
@@ -155,6 +166,7 @@ await Task.WhenAll(assetListFromVideoTask,
155166
#region Fetch by Game Cache Files
156167
private static Task FetchAssetFromGameCacheFiles(List<FilePropertiesRemote> assetIndex, CancellationToken token)
157168
{
169+
// TODO: Use it for altering assets for Cache Update mode
158170
return Task.CompletedTask;
159171
}
160172
#endregion
@@ -276,7 +288,7 @@ private void FinalizeVideoAssetsPath(List<FilePropertiesRemote> assetList)
276288
{
277289
string relativePath = Path.Combine(AssetBundleExtension.RelativePathVideo, asset.N);
278290
ConverterTool.NormalizePathInplaceNoTrim(relativePath);
279-
if (asset.AssociatedObject is KianaCgMetadata { DownloadMode: CGDownloadMode.DownloadTipAlways })
291+
if (asset.AssociatedObject is KianaCgMetadata { DownloadMode: CGDownloadMode.DownloadTipOnce })
280292
{
281293
versionStreamWriter.WriteLine($"Video/{asset.N}\t1");
282294
}
@@ -375,7 +387,7 @@ private async Task FinalizeBlockAssetsPath(
375387
CancellationToken token)
376388
{
377389
// Block assets replacement and add
378-
HashSet<string> oldBlockNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
390+
HashSet<string> oldBlockNames = new(StringComparer.OrdinalIgnoreCase);
379391
foreach (FilePropertiesRemote asset in targetAssetList)
380392
{
381393
string relativePath = Path.Combine(AssetBundleExtension.RelativePathBlock, asset.N);

0 commit comments

Comments
 (0)