Skip to content

Commit 5cb49f0

Browse files
committed
[GenshinRepair] Fix attempt for missing files
+ This ended up refactoring the whole fetch and check mechanism
1 parent c1ff034 commit 5cb49f0

File tree

3 files changed

+285
-231
lines changed

3 files changed

+285
-231
lines changed

CollapseLauncher/Classes/RepairManagement/Genshin/Check.cs

Lines changed: 108 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using Hi3Helper.Win32.Native.ManagedTools;
88
using System;
99
using System.Buffers;
10-
using System.Collections.Concurrent;
1110
using System.Collections.Generic;
1211
using System.IO;
1312
using System.IO.Hashing;
@@ -57,18 +56,11 @@ private async Task Check(List<PkgVersionProperties> assetIndex, CancellationToke
5756
$"Thread count set to {threadCount}.", LogType.Warning, true);
5857
}
5958

60-
ConcurrentDictionary<PkgVersionProperties, byte> runningTask = new();
6159
// Await the task for parallel processing
6260
// and iterate assetIndex and check it using different method for each type and run it in parallel
6361
await Parallel.ForEachAsync(assetIndex, new ParallelOptions { MaxDegreeOfParallelism = threadCount, CancellationToken = token }, async (asset, threadToken) =>
6462
{
65-
if (!runningTask.TryAdd(asset, 0))
66-
{
67-
LogWriteLine($"Found duplicated task for {asset.remoteURL}! Skipping...", LogType.Warning, true);
68-
return;
69-
}
7063
await CheckAssetAllType(asset, brokenAssetIndex, threadToken);
71-
runningTask.Remove(asset, out _);
7264
});
7365
}
7466
catch (AggregateException ex)
@@ -103,7 +95,7 @@ private void TryMoveAudioPersistent(IEnumerable<PkgVersionProperties> assetIndex
10395
{
10496
// Try to get the exclusion list of the audio (language specific) files
10597
string[] exclusionList = assetIndex
106-
.Where(x => x.isForceStoreInPersistent && x.remoteName
98+
.Where(x => x.isPatch && x.remoteNamePersistent != null && x.remoteName
10799
.AsSpan()
108100
.EndsWith(".pck"))
109101
.Select(x => x.remoteNamePersistent
@@ -114,7 +106,9 @@ private void TryMoveAudioPersistent(IEnumerable<PkgVersionProperties> assetIndex
114106
string audioAsbPath = Path.Combine(GameStreamingAssetsPath, "AudioAssets");
115107
string audioPersistentPath = Path.Combine(GamePersistentPath, "AudioAssets");
116108
if (!Directory.Exists(audioPersistentPath)) return;
117-
if (!Directory.Exists(audioAsbPath)) Directory.CreateDirectory(audioAsbPath);
109+
110+
// Create audio streaming asset directory anyway
111+
Directory.CreateDirectory(audioAsbPath);
118112

119113
// Get the list of audio language names from _gameVersionManager
120114
List<string> audioLangList = ((GameTypeGenshinVersion)GameVersionManager).AudioVoiceLanguageList;
@@ -137,7 +131,7 @@ private void TryMoveAudioPersistent(IEnumerable<PkgVersionProperties> assetIndex
137131
.Where(x => !exclusionList.Any(y => x.AsSpan().EndsWith(y.AsSpan()))))
138132
{
139133
// Trim the path name to get the generic languageName/filename form
140-
string pathName = filePath.AsSpan().Slice(audioPersistentPath.Length + 1).ToString();
134+
string pathName = filePath.AsSpan()[(audioPersistentPath.Length + 1)..].ToString();
141135
// Combine the generic name with audioAsbPath
142136
string newPath = EnsureCreationOfDirectory(Path.Combine(audioAsbPath, pathName));
143137

@@ -178,25 +172,9 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List<PkgVe
178172
FileInfo fileInfoPersistent = new FileInfo(filePathPersistent).EnsureNoReadOnly();
179173

180174
// Decide file state
181-
bool isUsePersistent = asset.isForceStoreInPersistent;
182-
bool isPatch = asset.isPatch;
183175
bool isPersistentExist = fileInfoPersistent.Exists;
184176
bool isStreamingExist = fileInfoStreaming.Exists;
185177

186-
// Try to get hash buffer
187-
bool isUseXxh64Hash = !string.IsNullOrEmpty(asset.xxh64hash);
188-
int hashBufferLen = (isUseXxh64Hash ? asset.xxh64hash.Length : asset.md5.Length) / 2;
189-
byte[] hashBuffer = ArrayPool<byte>.Shared.Rent(hashBufferLen);
190-
191-
try
192-
{
193-
if (!HexTool.TryHexToBytesUnsafe(asset.md5, hashBuffer))
194-
{
195-
#if DEBUG
196-
throw new InvalidOperationException();
197-
#endif
198-
}
199-
200178
// Get the file info to use
201179
bool isUnmatchedFileFound = !await IsFileMatched(
202180
a =>
@@ -222,8 +200,7 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List<PkgVe
222200

223201
PkgVersionProperties clonedAsset = asset.Clone();
224202

225-
Dispatch(() => AssetEntry.Add(
226-
new AssetProperty<RepairAssetType>(
203+
Dispatch(() => AssetEntry.Add(new AssetProperty<RepairAssetType>(
227204
Path.GetFileName(c),
228205
RepairAssetType.Unused,
229206
Path.GetDirectoryName(c),
@@ -241,105 +218,157 @@ private async ValueTask CheckAssetAllType(PkgVersionProperties asset, List<PkgVe
241218
}
242219
);
243220

244-
FileInfo fileInfoToUse = asset.isForceStoreInPersistent ? fileInfoPersistent : fileInfoStreaming;
245-
246221
if (isUnmatchedFileFound)
247222
{
248-
AddNotFoundOrMismatchAsset(asset, fileInfoToUse);
223+
AddNotFoundOrMismatchAsset(asset);
249224
}
250-
}
251-
finally
252-
{
253-
ArrayPool<byte>.Shared.Return(hashBuffer);
254-
}
255225

256226
return;
257227

258228
async ValueTask<bool> IsFileMatched(Action<bool> storeAsStreaming, Action<bool> storeAsPersistent, Action<string, string> storeAsUnused)
259229
{
260-
if (!isUsePersistent)
230+
bool isUsePersistent = asset.isPatch ||
231+
(!asset.xxh64hashPersistent?.Equals(asset.xxh64hash, StringComparison.OrdinalIgnoreCase) ?? false);
232+
233+
// Try to get hash buffer
234+
byte[] hashBuffer = ArrayPool<byte>.Shared.Rent(16);
235+
try
261236
{
262-
if (isStreamingExist && await IsFileHashMatch(fileInfoStreaming, hashBuffer, token))
237+
// Create memory and compare the hash
238+
string streamingHash = asset.xxh64hash ?? asset.md5;
239+
Memory<byte> streamingHashMemory = new(hashBuffer, 0, streamingHash.Length / 2);
240+
_ = HexTool.TryHexToBytesUnsafe(streamingHash, streamingHashMemory.Span);
241+
242+
if (!isUsePersistent)
263243
{
244+
if (isPersistentExist)
245+
{
246+
storeAsUnused(asset.remoteNamePersistent, fileInfoPersistent.FullName);
247+
}
248+
249+
// ReSharper disable once InvertIf
250+
if (!isStreamingExist ||
251+
!await IsFileHashMatch(fileInfoStreaming, asset.fileSize, streamingHashMemory, true, token))
252+
{
253+
storeAsStreaming(true);
254+
storeAsPersistent(false);
255+
asset.isStreamingNeedDownload = true;
256+
return false;
257+
}
258+
264259
return true;
265260
}
266261

267-
if (isPersistentExist)
262+
bool isBrokenFoundOnPersistent = false;
263+
bool isHasStreamingInvalid = false;
264+
265+
if (!asset.isPatch &&
266+
isStreamingExist &&
267+
!await IsFileHashMatch(fileInfoStreaming, asset.fileSize, streamingHashMemory, true, token))
268268
{
269-
storeAsUnused(asset.remoteNamePersistent, fileInfoPersistent.FullName);
269+
storeAsStreaming(true);
270+
storeAsPersistent(false);
271+
isBrokenFoundOnPersistent = true;
272+
isHasStreamingInvalid = true;
273+
asset.isStreamingNeedDownload = true;
270274
}
271275

272-
storeAsStreaming(true);
273-
storeAsPersistent(false);
274-
return false;
276+
// Create memory and compare the hash
277+
string persistentHash = asset.xxh64hashPersistent ?? asset.md5Persistent;
278+
Memory<byte> persistentHashMemory = new(hashBuffer, 0, persistentHash.Length / 2);
279+
_ = HexTool.TryHexToBytesUnsafe(persistentHash, persistentHashMemory.Span);
275280

276-
}
277-
278-
bool isStreamingMatch = isStreamingExist && await IsFileHashMatch(fileInfoStreaming, hashBuffer, token);
279-
bool isPersistentMatch = isPersistentExist && await IsFileHashMatch(fileInfoPersistent, hashBuffer, token);
281+
if (isHasStreamingInvalid)
282+
{
283+
Interlocked.Add(ref ProgressAllSizeCurrent, -fileInfoStreaming.Length);
284+
}
280285

281-
switch (isStreamingMatch)
282-
{
283-
case true when isPersistentMatch && isPatch:
284-
storeAsUnused(asset.remoteName, fileInfoStreaming.FullName);
285-
break;
286-
case false when !isPersistentMatch:
286+
// ReSharper disable once InvertIf
287+
if (!isPersistentExist || !await IsFileHashMatch(fileInfoPersistent, asset.fileSizePersistent, persistentHashMemory, true, token))
288+
{
287289
storeAsStreaming(false);
288290
storeAsPersistent(true);
289-
return false;
290-
}
291+
isBrokenFoundOnPersistent = true;
292+
asset.isPersistentNeedDownload = true;
293+
}
291294

292-
return true;
295+
return !isBrokenFoundOnPersistent;
296+
}
297+
finally
298+
{
299+
ArrayPool<byte>.Shared.Return(hashBuffer);
300+
}
293301
}
294302

295-
async ValueTask<bool> IsFileHashMatch(FileInfo fileInfo, ReadOnlyMemory<byte> hashToCompare, CancellationToken cancelToken)
303+
async ValueTask<bool> IsFileHashMatch(FileInfo fileInfo, long fileSizeToCheck, ReadOnlyMemory<byte> hashToCompare, bool isUpdateProgress, CancellationToken cancelToken)
296304
{
297305
// Refresh the fileInfo
298306
fileInfo.Refresh();
299307

300-
if (fileInfo.Length != asset.fileSize) return false; // Skip the hash calculation if the file size is different
301-
308+
if (fileInfo.Length != fileSizeToCheck)
309+
return false; // Skip the hash calculation if the file size is different
310+
302311
if (UseFastMethod) return true; // Skip the hash calculation if the fast method is enabled
303-
312+
304313
// Try to get filestream
305314
await using FileStream fileStream = await fileInfo.NaivelyOpenFileStreamAsync(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
306315

307316
// If pass the check above, then do hash calculation
308317
// Additional: the total file size progress is disabled and will be incremented after this
309318
byte[] localHash = hashToCompare.Length == 8 ?
310-
await GetHashAsync<XxHash64>(fileStream, true, true, cancelToken) :
311-
await GetCryptoHashAsync<MD5>(fileStream, null, true, true, cancelToken);
319+
await GetHashAsync<XxHash64>(fileStream, isUpdateProgress, isUpdateProgress, cancelToken) :
320+
await GetCryptoHashAsync<MD5>(fileStream, null, isUpdateProgress, isUpdateProgress, cancelToken);
312321

313322
// Check if the hash is equal
314323
bool isMatch = IsArrayMatch(hashToCompare.Span, localHash);
315324
return isMatch;
316325
}
317326

318-
void AddNotFoundOrMismatchAsset(PkgVersionProperties assetInner, FileInfo repairFile)
327+
void AddNotFoundOrMismatchAsset(PkgVersionProperties assetInner)
319328
{
320329
// Update the total progress and found counter
321-
ProgressAllSizeFound += assetInner.fileSize;
322-
ProgressAllCountFound++;
330+
if (assetInner.isStreamingNeedDownload)
331+
{
332+
Interlocked.Increment(ref ProgressAllCountFound);
333+
Interlocked.Add(ref ProgressAllSizeFound, assetInner.fileSize);
334+
335+
Dispatch(() => AssetEntry.Add(new AssetProperty<RepairAssetType>(
336+
Path.GetFileName(assetInner.remoteName),
337+
RepairAssetType.Generic,
338+
Path.GetDirectoryName(assetInner.remoteName),
339+
assetInner.fileSize,
340+
null,
341+
HexTool.HexToBytesUnsafe(string.IsNullOrEmpty(assetInner.xxh64hash) ? assetInner.md5 : assetInner.xxh64hash)
342+
)
343+
));
323344

324-
// Set the per size progress
325-
ProgressPerFileSizeCurrent = assetInner.fileSize;
345+
targetAssetIndex.Add(assetInner);
326346

327-
// Increment the total current progress
328-
ProgressAllSizeCurrent += assetInner.fileSize;
347+
LogWriteLine($"File [T: {RepairAssetType.Generic}-Streaming]: {assetInner.localName} is not found or has unmatched size",
348+
LogType.Warning, true);
349+
}
329350

330-
Dispatch(() => AssetEntry.Add(
331-
new AssetProperty<RepairAssetType>(
332-
repairFile.Name,
351+
if (!assetInner.isPersistentNeedDownload)
352+
{
353+
return;
354+
}
355+
356+
Interlocked.Increment(ref ProgressAllCountFound);
357+
Interlocked.Add(ref ProgressAllSizeFound, assetInner.fileSizePersistent);
358+
359+
Dispatch(() => AssetEntry.Add(new AssetProperty<RepairAssetType>(
360+
Path.GetFileName(assetInner.remoteNamePersistent),
333361
RepairAssetType.Generic,
334-
Path.GetDirectoryName(assetInner.isForceStoreInPersistent ? assetInner.remoteNamePersistent : assetInner.remoteName),
335-
assetInner.fileSize,
336-
repairFile.Exists ? hashBuffer : null,
337-
HexTool.HexToBytesUnsafe(string.IsNullOrEmpty(assetInner.xxh64hash) ? assetInner.md5 : assetInner.xxh64hash)
362+
Path.GetDirectoryName(assetInner.remoteNamePersistent),
363+
assetInner.fileSizePersistent,
364+
null,
365+
HexTool.HexToBytesUnsafe(string.IsNullOrEmpty(assetInner.xxh64hashPersistent) ? assetInner.md5Persistent : assetInner.xxh64hashPersistent)
338366
)
339367
));
340-
targetAssetIndex.Add(assetInner);
341368

342-
LogWriteLine($"File [T: {RepairAssetType.Generic}]: {assetInner.localName} is not found or has unmatched size",
369+
targetAssetIndex.Add(assetInner.CloneAsPersistent());
370+
371+
LogWriteLine($"File [T: {RepairAssetType.Generic}-Persistent]: {assetInner.localName} is not found or has unmatched size",
343372
LogType.Warning, true);
344373
}
345374
}

0 commit comments

Comments
 (0)