Skip to content

Commit 8feb148

Browse files
committed
Refactor Hi3 Repair (pt. 7) + Add matching field exclude mechanism
1 parent bd27002 commit 8feb148

File tree

7 files changed

+198
-29
lines changed

7 files changed

+198
-29
lines changed

CollapseLauncher/Classes/Helper/JsonConverter/ServeV3Converter.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,51 @@ public override void Write(
7070
throw new JsonException("Serializing is not supported!");
7171
}
7272
}
73+
74+
public class ServeV3StringArrayConverter : JsonConverter<string[]>
75+
{
76+
public override bool CanConvert(Type type) => true;
77+
78+
public override string[] Read(
79+
ref Utf8JsonReader reader,
80+
Type typeToConvert,
81+
JsonSerializerOptions options)
82+
{
83+
// Initialize and check if the token is a start of an array
84+
List<string> returnValue = [];
85+
if (reader.TokenType != JsonTokenType.StartArray) // Throw if it's not
86+
throw new JsonException("The start token of the JSON field is not a start of an array!");
87+
88+
// Read the next value or token
89+
reader.Read();
90+
91+
// If the token is a string, initialize the list
92+
if (reader.TokenType == JsonTokenType.String)
93+
returnValue = [];
94+
95+
// Loop and read the value if the token is currently a string
96+
while (reader.TokenType == JsonTokenType.String)
97+
{
98+
// Try retrieve the data if it's a raw string or a ServeV3 string
99+
string returnString = Extension.GetServeV3String(reader);
100+
returnValue.Add(returnString); // Add the string
101+
reader.Read(); // Read the next token
102+
}
103+
104+
// If the token is not an end of an array, then throw
105+
if (reader.TokenType != JsonTokenType.EndArray)
106+
throw new JsonException("The end token of the JSON field is not an end of an array!");
107+
108+
// Return the list
109+
return returnValue.ToArray();
110+
}
111+
112+
public override void Write(
113+
Utf8JsonWriter writer,
114+
string[]? baseType,
115+
JsonSerializerOptions options)
116+
{
117+
throw new JsonException("Serializing is not supported!");
118+
}
119+
}
73120
}

CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs

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

101+
[JsonConverter(typeof(ServeV3StringArrayConverter))]
102+
public string[] ExcludeMatchingFieldMain { get; set; } = [];
103+
104+
[JsonConverter(typeof(ServeV3StringArrayConverter))]
105+
public string[] ExcludeMatchingFieldPreload { get; set; } = [];
106+
107+
[JsonConverter(typeof(ServeV3StringArrayConverter))]
108+
public string[] ExcludeMatchingFieldPatch { get; set; } = [];
109+
110+
[JsonConverter(typeof(ServeV3StringArrayConverter))]
111+
public string[] ExcludeMatchingFieldUpdate { get; set; } = [];
112+
101113
public bool ResetAssociation() => IsReassociated = false;
102114

103115
public Task EnsureReassociated(HttpClient client, string? branchUrl, string bizName, bool isPreloadForPatch, CancellationToken token)

CollapseLauncher/Classes/Helper/PatternMatcher.cs

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
4+
using System.Text;
35
using System.Text.RegularExpressions;
46

57
namespace CollapseLauncher.Helper
@@ -16,9 +18,12 @@ public static class PatternMatcher
1618
public static bool MatchSimpleExpression(string input, string pattern)
1719
{
1820
// Escape special regex characters in the pattern except for the wildcard '*'
19-
var regexPattern = "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$";
20-
return Regex.IsMatch(input, regexPattern,
21-
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.NonBacktracking);
21+
string regexPattern = "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$";
22+
return Regex.IsMatch(input,
23+
regexPattern,
24+
RegexOptions.IgnoreCase |
25+
RegexOptions.Compiled |
26+
RegexOptions.NonBacktracking);
2227
}
2328

2429
/// <summary>
@@ -31,5 +36,82 @@ public static bool MatchesAnyPattern(string input, List<string> patterns)
3136
{
3237
return patterns.Any(pattern => MatchSimpleExpression(input, pattern));
3338
}
39+
40+
/// <summary>
41+
/// Perform Regex Matching and enumerate element.
42+
/// </summary>
43+
/// <typeparam name="T">The type of element to enumerate.</typeparam>
44+
/// <param name="enumerable">The enumerable input instance.</param>
45+
/// <param name="selector">Determines which string element to match the pattern from.</param>
46+
/// <param name="isMatchNegate">Whether to select negated match.</param>
47+
/// <param name="regexPatterns">Single or multiple patterns to use.</param>
48+
/// <returns>Enumerated element matched by the pattern.</returns>
49+
public static IEnumerable<T> WhereMatchPattern<T>(
50+
this IEnumerable<T> enumerable,
51+
Func<T, string> selector,
52+
bool isMatchNegate,
53+
params string[] regexPatterns)
54+
{
55+
if (regexPatterns.Length == 0)
56+
{
57+
return enumerable;
58+
}
59+
60+
string mergedPattern = MergeRegexPattern(regexPatterns);
61+
return WhereMatchPattern(enumerable, selector, isMatchNegate, mergedPattern);
62+
}
63+
64+
/// <summary>
65+
/// Perform Regex Matching and enumerate element.
66+
/// </summary>
67+
/// <typeparam name="T">The type of element to enumerate.</typeparam>
68+
/// <param name="enumerable">The enumerable input instance.</param>
69+
/// <param name="selector">Determines which string element to match the pattern from.</param>
70+
/// <param name="isMatchNegate">Whether to select negated match.</param>
71+
/// <param name="regexPattern">Regex pattern to use.</param>
72+
/// <returns>Enumerated element matched by the pattern.</returns>
73+
public static IEnumerable<T> WhereMatchPattern<T>(
74+
this IEnumerable<T> enumerable,
75+
Func<T, string> selector,
76+
bool isMatchNegate,
77+
string regexPattern)
78+
{
79+
Regex regex = new(regexPattern,
80+
RegexOptions.IgnoreCase |
81+
RegexOptions.NonBacktracking |
82+
RegexOptions.Compiled);
83+
84+
if (string.IsNullOrEmpty(regexPattern))
85+
{
86+
return enumerable;
87+
}
88+
89+
return enumerable.Where(x => regex.IsMatch(selector(x)) != isMatchNegate);
90+
}
91+
92+
/// <summary>
93+
/// Merge multiple Regex pattern strings into a single Regex pattern string.
94+
/// </summary>
95+
/// <param name="regexPatterns">Single or multiple patterns to merge.</param>
96+
/// <returns>Merged multiple Regex pattern.</returns>
97+
public static string MergeRegexPattern(params ReadOnlySpan<string> regexPatterns)
98+
{
99+
if (regexPatterns.IsEmpty)
100+
{
101+
return string.Empty;
102+
}
103+
104+
StringBuilder builder = new();
105+
for (int i = 0; i < regexPatterns.Length; i++)
106+
{
107+
builder.Append($"(?:{regexPatterns[i]})");
108+
if (i < regexPatterns.Length - 1)
109+
{
110+
builder.Append('|');
111+
}
112+
}
113+
114+
return builder.ToString();
115+
}
34116
}
35117
}

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

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
using System.Linq;
2828
using System.Net.Http;
2929
using System.Numerics;
30+
using System.Text.RegularExpressions;
3031
using System.Threading;
3132
using System.Threading.Tasks;
3233
using SophonLogger = Hi3Helper.Sophon.Helper.Logger;
@@ -184,7 +185,7 @@ public virtual async Task StartPackageInstallSophon(GameInstallStateEnum gameSta
184185

185186
// Get the requested URL and version based on current state.
186187
if (GameVersionManager.GamePreset
187-
.LauncherResourceChunksURL != null)
188+
.LauncherResourceChunksURL != null)
188189
{
189190
// Reassociate the URL if branch url exist
190191
string? branchUrl = GameVersionManager.GamePreset
@@ -242,8 +243,7 @@ await GameVersionManager.GamePreset
242243

243244
// Get the info pair based on info provided above (for main game file)
244245
var sophonMainInfoPair = await
245-
SophonManifest.CreateSophonChunkManifestInfoPair(
246-
httpClient,
246+
SophonManifest.CreateSophonChunkManifestInfoPair(httpClient,
247247
requestedUrl,
248248
GameVersionManager.GamePreset.LauncherResourceChunksURL.MainBranchMatchingField,
249249
Token.Token);
@@ -360,6 +360,7 @@ await SimpleDialogs.Dialog_ChooseAudioLanguageChoice(
360360
httpClient,
361361
sophonInfoPairList,
362362
downloadSpeedLimiter,
363+
GameVersionManager.GamePreset.LauncherResourceChunksURL?.ExcludeMatchingFieldMain ?? [],
363364
Token.Token);
364365

365366
// Get the remote total size and current total size
@@ -498,6 +499,7 @@ private async Task<List<SophonAsset>> GetSophonAssetListFromPair(
498499
HttpClient client,
499500
List<SophonChunkManifestInfoPair> sophonInfoPairs,
500501
SophonDownloadSpeedLimiter downloadSpeedLimiter,
502+
string[] excludeMatchingFieldPatterns,
501503
CancellationToken token)
502504
{
503505
List<SophonAsset> sophonAssetList = [];
@@ -506,7 +508,10 @@ private async Task<List<SophonAsset>> GetSophonAssetListFromPair(
506508

507509
// Avoid duplicates by using HashSet of the url
508510
HashSet<string> currentlyProcessedPair = [];
509-
foreach (SophonChunkManifestInfoPair sophonDownloadInfoPair in sophonInfoPairs)
511+
foreach (SophonChunkManifestInfoPair sophonDownloadInfoPair in sophonInfoPairs
512+
.WhereMatchPattern(x => x.MatchingField,
513+
true,
514+
excludeMatchingFieldPatterns))
510515
{
511516
// Try add and if the hashset already contains the same Manifest ID registered, then skip
512517
if (!currentlyProcessedPair.Add(sophonDownloadInfoPair.ManifestInfo!.ManifestId))
@@ -780,6 +785,11 @@ await GameVersionManager.GamePreset
780785
return;
781786
}
782787

788+
string[] excludeMatchingFieldPatterns =
789+
isPreloadMode
790+
? GameVersionManager.GamePreset.LauncherResourceChunksURL.ExcludeMatchingFieldPreload
791+
: GameVersionManager.GamePreset.LauncherResourceChunksURL.ExcludeMatchingFieldUpdate;
792+
783793
// If the game has lang list path, then add it
784794
if (_gameAudioLangListPath != null)
785795
{
@@ -788,13 +798,15 @@ await AddSophonAdditionalVODiffAssetsToList(httpClient,
788798
requestedBaseUrlFrom,
789799
requestedBaseUrlTo,
790800
sophonUpdateAssetList,
801+
excludeMatchingFieldPatterns,
791802
downloadSpeedLimiter);
792803
}
793804

794805
await TryGetAdditionalPackageForSophonDiff(httpClient,
795806
requestedBaseUrlFrom,
796807
requestedBaseUrlTo,
797808
GameVersionManager.GamePreset.LauncherResourceChunksURL.MainBranchMatchingField,
809+
excludeMatchingFieldPatterns,
798810
sophonUpdateAssetList,
799811
downloadSpeedLimiter);
800812
}
@@ -1074,6 +1086,7 @@ private async Task TryGetAdditionalPackageForSophonDiff(HttpClient
10741086
string requestedUrlFrom,
10751087
string requestedUrlTo,
10761088
string mainMatchingField,
1089+
string[] excludeMatchingFieldsPattern,
10771090
List<SophonAsset> sophonPreloadAssetList,
10781091
SophonDownloadSpeedLimiter downloadSpeedLimiter)
10791092
{
@@ -1085,10 +1098,12 @@ private async Task TryGetAdditionalPackageForSophonDiff(HttpClient
10851098
return;
10861099
}
10871100

1088-
List<string> additionalPackageMatchingFields = manifestPair.OtherSophonBuildData!.ManifestIdentityList
1089-
.Where(x => !CommonSophonPackageMatchingFields.Contains(x.MatchingField, StringComparer.OrdinalIgnoreCase))
1090-
.Select(x => x.MatchingField)
1091-
.ToList();
1101+
List<string> additionalPackageMatchingFields =
1102+
manifestPair.OtherSophonBuildData!.ManifestIdentityList
1103+
.Where(x => !CommonSophonPackageMatchingFields.Contains(x.MatchingField, StringComparer.OrdinalIgnoreCase))
1104+
.Select(x => x.MatchingField)
1105+
.WhereMatchPattern(x => x, true, excludeMatchingFieldsPattern)
1106+
.ToList();
10921107

10931108
if (additionalPackageMatchingFields.Count == 0)
10941109
{
@@ -1176,22 +1191,28 @@ private async Task<bool> AddSophonDiffAssetsToList(HttpClient ht
11761191
return true;
11771192
}
11781193

1179-
private async Task AddSophonAdditionalVODiffAssetsToList(HttpClient httpClient,
1180-
string requestedUrlFrom,
1181-
string requestedUrlTo,
1182-
List<SophonAsset> sophonPreloadAssetList,
1183-
SophonDownloadSpeedLimiter downloadSpeedLimiter)
1194+
private async Task AddSophonAdditionalVODiffAssetsToList(
1195+
HttpClient httpClient,
1196+
string requestedUrlFrom,
1197+
string requestedUrlTo,
1198+
List<SophonAsset> sophonPreloadAssetList,
1199+
string[] excludeMatchingFieldsPattern,
1200+
SophonDownloadSpeedLimiter downloadSpeedLimiter)
11841201
{
11851202
// Get the main VO language name from Id
11861203
string mainLangId = GetLanguageLocaleCodeByID(_gameVoiceLanguageID);
1187-
// Get the manifest pair for both previous (from) and next (to) version for the main VO
1188-
await AddSophonDiffAssetsToList(httpClient,
1189-
requestedUrlFrom,
1190-
requestedUrlTo,
1191-
sophonPreloadAssetList,
1192-
mainLangId,
1193-
false,
1194-
downloadSpeedLimiter);
1204+
1205+
if (!excludeMatchingFieldsPattern.Any(x => Regex.IsMatch(mainLangId, x)))
1206+
{
1207+
// Get the manifest pair for both previous (from) and next (to) version for the main VO
1208+
await AddSophonDiffAssetsToList(httpClient,
1209+
requestedUrlFrom,
1210+
requestedUrlTo,
1211+
sophonPreloadAssetList,
1212+
mainLangId,
1213+
false,
1214+
downloadSpeedLimiter);
1215+
}
11951216

11961217
// Check if the audio lang list file is exist, then try add others
11971218
FileInfo fileInfo = new FileInfo(_gameAudioLangListPath).StripAlternateDataStream().EnsureNoReadOnly();
@@ -1211,7 +1232,8 @@ await AddSophonDiffAssetsToList(httpClient,
12111232

12121233
// Check if the voice pack is actually the same as default.
12131234
if (string.IsNullOrEmpty(otherLangId) ||
1214-
otherLangId.Equals(mainLangId, StringComparison.OrdinalIgnoreCase))
1235+
otherLangId.Equals(mainLangId, StringComparison.OrdinalIgnoreCase) ||
1236+
excludeMatchingFieldsPattern.Any(x => Regex.IsMatch(otherLangId, x)))
12151237
{
12161238
continue;
12171239
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
using CollapseLauncher.Dialogs;
1111
using CollapseLauncher.Extension;
12+
using CollapseLauncher.Helper;
1213
using CollapseLauncher.Helper.Metadata;
1314
using CollapseLauncher.Interfaces;
1415
using Hi3Helper;
@@ -100,6 +101,12 @@ await SophonPatch.CreateSophonChunkManifestInfoPair(httpClient,
100101
List<string> matchingFields = [GameVersionManager.GamePreset.LauncherResourceChunksURL.MainBranchMatchingField];
101102
matchingFields.AddRange(await GetAlterSophonPatchVOMatchingFields(Token.Token));
102103

104+
// Exclude matching fields from metadata filter
105+
string[] excludeMatchingFields =
106+
GameVersionManager.GamePreset.LauncherResourceChunksURL.ExcludeMatchingFieldPatch;
107+
matchingFields = matchingFields.WhereMatchPattern(x => x, true, excludeMatchingFields)
108+
.ToList();
109+
103110
// Perform check on additional data package (in this case: Zenless Zone Zero has some cutscene files registered)
104111
await ConfirmAdditionalPatchDataPackageFiles(patchManifest, matchingFields, Token.Token);
105112

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using CollapseLauncher.Interfaces;
22
using Hi3Helper;
33
using Hi3Helper.Shared.ClassStruct;
4-
using System.Collections.Generic;
54
using System.IO;
6-
using System.Linq;
75
using System.Threading;
86
// ReSharper disable CheckNamespace
97
// ReSharper disable IdentifierTypo

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using CollapseLauncher.Helper.StreamUtility;
1+
using CollapseLauncher.Helper;
2+
using CollapseLauncher.Helper.StreamUtility;
23
using CollapseLauncher.RepairManagement;
34
using Hi3Helper;
45
using Hi3Helper.Data;
@@ -51,7 +52,7 @@ private void CheckAssetUnusedType(
5152
try
5253
{
5354
string[] ignoredFiles = File.ReadAllLines(ignoredFilesPath);
54-
string mergedPattern = string.Join("|", ignoredFiles.Select(r => $"(?:{r})"));
55+
string mergedPattern = PatternMatcher.MergeRegexPattern(ignoredFiles);
5556

5657
matchIgnoredRegex = new Regex(mergedPattern, RegexOptions.IgnoreCase |
5758
RegexOptions.NonBacktracking |

0 commit comments

Comments
 (0)