Skip to content

Commit 24af3a5

Browse files
authored
Stable 1.82.11 Hotfix (#651)
# What's new? - 1.82.11 - **[New]** Toggle "Playing" Discord RPC status per-region, by @bagusnl - You can disable "Playing" status per-region now by going to Quick Settings (bottom right on Homepage) and toggling it off. - This is enabled by default (as it always is) - **[Fix]** Bloated Sophon pre-download/update size due to duplicate entries, by @neon-nyan - **[Imp]** Match API requests headers, by @neon-nyan - **[Fix]** Cannot copy error message caused by ``HGlobal`` buffer misallocation for some users, by @neon-nyan - **[Fix]** IOSharingViolation due to file/directory handling in some methods, by @neon-nyan - **[Fix]** Repair causing application crashes due to value overflow, by @bagusnl - **[Fix]** Game installation error due to invalid VO language selection, by @neon-nyan - **[Imp]** Sentry improvements, by @bagusnl - Filter exceptions to be sent + Exceptions caused by users' internet situation are filtered - Add loaded modules info to breadcrumb - Detach inner exception handler method - **[Imp]** Sophon Download Improvements, by @neon-nyan - Adding disk-space check on Game Installation/Update and Pre-load Download. - Ignore if VO's LanguageString is not valid on non-debug build - Separate Sophon methods to its own code files - **[Imp]** UI/UX Improvements, by @shatyuka @neon-nyan and @bagusnl - New fallback background when the background sprite is not yet downloaded > The official HoYoPlay's default background is used for the fallback background - Disable some tab focus within the PostPanel - Resize Pivot header in PostPanel - Make ScrollViewer receive focus as a whole - Enable navigation via arrow keys in ScrollViewer - Adjust Audio-related setting's Grid layout on Genshin Game Settings Page - **[Fix]** DB sync buttons on App Settings page doesn't get disabled when the toggle is off - **[Loc]** Localization Sync from Transifex, by Localizers <3 ### Templates <details> <summary>Changelog Prefixes</summary> ``` **[New]** **[Imp]** **[Fix]** **[Loc]** **[Doc]** ``` </details>
2 parents b6e1c29 + fe380cc commit 24af3a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2085
-1399
lines changed

.github/workflows/qodana-scan-pr.yml

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ on:
33
pull_request:
44
branches:
55
- main
6+
- preview
7+
- stable
68

79
jobs:
810
qodana:

CollapseLauncher/App.xaml

+150-27
Large diffs are not rendered by default.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

CollapseLauncher/Classes/CachesManagement/Honkai/Check.cs

+22-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using CollapseLauncher.Interfaces;
1+
using CollapseLauncher.Helper;
2+
using CollapseLauncher.Interfaces;
23
using Hi3Helper;
34
using System;
45
using System.Collections.Generic;
@@ -57,19 +58,31 @@ private async Task<List<CacheAsset>> Check(List<CacheAsset> assetIndex, Cancella
5758

5859
private void CheckUnusedAssets(List<CacheAsset> assetIndex, List<CacheAsset> returnAsset)
5960
{
61+
// Directory info and if the directory doesn't exist, return
62+
DirectoryInfo directoryInfo = new DirectoryInfo(_gamePath);
63+
if (!directoryInfo.Exists)
64+
{
65+
return;
66+
}
67+
6068
// Iterate the file contained in the _gamePath
61-
foreach (string filePath in Directory.EnumerateFiles(_gamePath!, "*", SearchOption.AllDirectories))
69+
foreach (FileInfo fileInfo in directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories)
70+
.EnumerateNoReadOnly())
6271
{
63-
if (!filePath.Contains("output_log") && !filePath.Contains("Crashes")
64-
&& !filePath.Contains("Verify.txt") && !filePath.Contains("APM")
65-
&& !filePath.Contains("FBData") && !filePath.Contains("asb.dat")
66-
&& !assetIndex!.Exists(x => x!.ConcatPath == filePath))
72+
string filePath = fileInfo.FullName;
73+
74+
if (!filePath.Contains("output_log", StringComparison.OrdinalIgnoreCase)
75+
&& !filePath.Contains("Crashes", StringComparison.OrdinalIgnoreCase)
76+
&& !filePath.Contains("Verify.txt", StringComparison.OrdinalIgnoreCase)
77+
&& !filePath.Contains("APM", StringComparison.OrdinalIgnoreCase)
78+
&& !filePath.Contains("FBData", StringComparison.OrdinalIgnoreCase)
79+
&& !filePath.Contains("asb.dat", StringComparison.OrdinalIgnoreCase)
80+
&& !assetIndex.Exists(x => x.ConcatPath == fileInfo.FullName))
6781
{
6882
// Increment the total found count
6983
_progressAllCountFound++;
7084

7185
// Add asset to the returnAsset
72-
FileInfo fileInfo = new FileInfo(filePath);
7386
CacheAsset asset = new CacheAsset()
7487
{
7588
BasePath = Path.GetDirectoryName(filePath),
@@ -106,10 +119,10 @@ private async ValueTask CheckAsset(CacheAsset asset, List<CacheAsset> returnAsse
106119
_status.ActivityAll = string.Format(Lang._CachesPage.CachesTotalStatusChecking!, _progressAllCountCurrent, _progressAllCountTotal);
107120

108121
// Assign the file info.
109-
FileInfo fileInfo = new FileInfo(asset.ConcatPath!);
122+
FileInfo fileInfo = new FileInfo(asset.ConcatPath).EnsureNoReadOnly(out bool isExist);
110123

111124
// Check if the file exist. If not, then add it to asset index.
112-
if (!fileInfo.Exists)
125+
if (!isExist)
113126
{
114127
AddGenericCheckAsset(asset, CacheAssetStatus.New, returnAsset, null, asset.CRCArray);
115128
return;

CollapseLauncher/Classes/CachesManagement/Honkai/Update.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,12 @@ private async Task UpdateCacheAsset((CacheAsset AssetIndex, IAssetProperty Asset
126126

127127
FileInfo fileInfo = new FileInfo(asset.AssetIndex.ConcatPath!)
128128
.EnsureCreationOfDirectory()
129-
.EnsureNoReadOnly();
129+
.EnsureNoReadOnly(out bool isExist);
130130

131131
// This is a action for Unused asset.
132132
if (asset.AssetIndex.DataType == CacheAssetType.Unused)
133133
{
134-
if (fileInfo.Exists)
134+
if (isExist)
135135
fileInfo.Delete();
136136

137137
LogWriteLine($"Deleted unused file: {fileInfo.FullName}", LogType.Default, true);

CollapseLauncher/Classes/CachesManagement/StarRail/Check.cs

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Hi3Helper;
1+
using CollapseLauncher.Helper;
2+
using Hi3Helper;
23
using Hi3Helper.EncTool.Parser.AssetMetadata.SRMetadataAsset;
34
using System;
45
using System.Collections.Generic;
@@ -79,12 +80,12 @@ private async ValueTask CheckAsset(SRAsset asset, List<SRAsset> returnAsset, str
7980
}
8081

8182
// Get persistent and streaming paths
82-
FileInfo fileInfoPersistent = new FileInfo(Path.Combine(basePersistent!, asset.LocalName!));
83-
FileInfo fileInfoStreaming = new FileInfo(Path.Combine(baseStreaming!, asset.LocalName!));
83+
FileInfo fileInfoPersistent = new FileInfo(Path.Combine(basePersistent!, asset.LocalName!)).EnsureNoReadOnly(out bool isPersistentExist);
84+
FileInfo fileInfoStreaming = new FileInfo(Path.Combine(baseStreaming!, asset.LocalName!)).EnsureNoReadOnly(out bool isStreamingExist);
8485

85-
bool UsePersistent = !fileInfoStreaming.Exists;
86-
bool IsPersistentExist = fileInfoPersistent.Exists && fileInfoPersistent.Length == asset.Size;
87-
bool IsStreamingExist = fileInfoStreaming.Exists && fileInfoStreaming.Length == asset.Size;
86+
bool UsePersistent = !isStreamingExist;
87+
bool IsPersistentExist = isPersistentExist && fileInfoPersistent.Length == asset.Size;
88+
bool IsStreamingExist = isStreamingExist && fileInfoStreaming.Length == asset.Size;
8889
asset.LocalName = UsePersistent ? fileInfoPersistent.FullName : fileInfoStreaming.FullName;
8990

9091
// Check if the file exist. If not, then add it to asset index.

CollapseLauncher/Classes/Extension/TaskExtensions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
#nullable enable
77
namespace CollapseLauncher.Extension
88
{
9-
internal delegate Task<TResult?> ActionTimeoutValueTaskCallback<TResult>(CancellationToken token);
10-
internal delegate void ActionOnTimeOutRetry(int retryAttemptCount, int retryAttemptTotal, int timeOutSecond, int timeOutStep);
9+
public delegate Task<TResult?> ActionTimeoutValueTaskCallback<TResult>(CancellationToken token);
10+
public delegate void ActionOnTimeOutRetry(int retryAttemptCount, int retryAttemptTotal, int timeOutSecond, int timeOutStep);
1111
internal static class TaskExtensions
1212
{
1313
internal const int DefaultTimeoutSec = 10;

CollapseLauncher/Classes/FileDialog/FileDialogHelper.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ private static bool IsCollapseProgramPath(ReadOnlySpan<char> path)
162162
/// </summary>
163163
/// <param name="path">Path to check</param>
164164
/// <returns>True if path is root of the drive</returns>
165-
internal static bool IsRootPath(ReadOnlySpan<char> path)
165+
public static bool IsRootPath(ReadOnlySpan<char> path)
166166
{
167167
ReadOnlySpan<char> rootPath = Path.GetPathRoot(path);
168168
return rootPath.SequenceEqual(path);

CollapseLauncher/Classes/FileMigrationProcess/FileMigrationProcess.cs

+26-19
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,25 @@ private async Task<string> StartRoutineInner(FileMigrationProcessUIRef uiRef)
9090

9191
private async Task<string> MoveFile(FileMigrationProcessUIRef uiRef)
9292
{
93-
FileInfo inputPathInfo = new FileInfo(this.inputPath!);
94-
FileInfo outputPathInfo = new FileInfo(this.outputPath!);
93+
FileInfo inputPathInfo = new FileInfo(inputPath);
94+
FileInfo outputPathInfo = new FileInfo(outputPath);
9595

96-
string inputPathDir = Path.GetDirectoryName(inputPathInfo.FullName);
97-
string outputPathDir = Path.GetDirectoryName(outputPathInfo.FullName);
96+
var inputPathDir = FileDialogHelper.IsRootPath(inputPath)
97+
? Path.GetPathRoot(inputPath)
98+
: Path.GetDirectoryName(inputPathInfo.FullName);
9899

99-
if (!Directory.Exists(outputPathDir))
100-
Directory.CreateDirectory(outputPathDir!);
100+
if (string.IsNullOrEmpty(inputPathDir))
101+
throw new InvalidOperationException(string.Format(Locale.Lang._Dialogs.InvalidGameDirNewTitleFormat,
102+
inputPath));
103+
104+
DirectoryInfo outputPathDirInfo = new DirectoryInfo(inputPathDir);
105+
outputPathDirInfo.Create();
101106

102107
// Update path display
103108
string inputFileBasePath = inputPathInfo.FullName.Substring(inputPathDir!.Length + 1);
104109
UpdateCountProcessed(uiRef, inputFileBasePath);
105110

106-
if (this.IsSameOutputDrive)
111+
if (IsSameOutputDrive)
107112
{
108113
Logger.LogWriteLine($"[FileMigrationProcess::MoveFile()] Moving file in the same drive from: {inputPathInfo.FullName} to {outputPathInfo.FullName}", LogType.Default, true);
109114
inputPathInfo.MoveTo(outputPathInfo.FullName, true);
@@ -112,29 +117,27 @@ private async Task<string> MoveFile(FileMigrationProcessUIRef uiRef)
112117
else
113118
{
114119
Logger.LogWriteLine($"[FileMigrationProcess::MoveFile()] Moving file across different drives from: {inputPathInfo.FullName} to {outputPathInfo.FullName}", LogType.Default, true);
115-
await MoveWriteFile(uiRef, inputPathInfo, outputPathInfo, this.tokenSource == null ? default : this.tokenSource.Token);
120+
await MoveWriteFile(uiRef, inputPathInfo, outputPathInfo, tokenSource == null ? default : tokenSource.Token);
116121
}
117122

118123
return outputPathInfo.FullName;
119124
}
120125

121126
private async Task<string> MoveDirectory(FileMigrationProcessUIRef uiRef)
122127
{
123-
DirectoryInfo inputPathInfo = new DirectoryInfo(this.inputPath!);
124-
if (!Directory.Exists(this.outputPath))
125-
Directory.CreateDirectory(this.outputPath!);
126-
127-
DirectoryInfo outputPathInfo = new DirectoryInfo(this.outputPath);
128+
DirectoryInfo inputPathInfo = new DirectoryInfo(inputPath);
129+
DirectoryInfo outputPathInfo = new DirectoryInfo(outputPath);
130+
outputPathInfo.Create();
128131

129132
int parentInputPathLength = inputPathInfo.Parent!.FullName.Length + 1;
130133
string outputDirBaseNamePath = inputPathInfo.FullName.Substring(parentInputPathLength);
131-
string outputDirPath = Path.Combine(this.outputPath, outputDirBaseNamePath);
134+
string outputDirPath = Path.Combine(outputPath, outputDirBaseNamePath);
132135

133136
await Parallel.ForEachAsync(
134137
inputPathInfo.EnumerateFiles("*", SearchOption.AllDirectories),
135138
new ParallelOptions
136139
{
137-
CancellationToken = this.tokenSource?.Token ?? default,
140+
CancellationToken = tokenSource?.Token ?? default,
138141
MaxDegreeOfParallelism = LauncherConfig.AppCurrentThread
139142
},
140143
async (inputFileInfo, cancellationToken) =>
@@ -146,10 +149,14 @@ await Parallel.ForEachAsync(
146149
UpdateCountProcessed(uiRef, inputFileBasePath);
147150

148151
string outputTargetPath = Path.Combine(outputPathInfo.FullName, inputFileBasePath);
149-
string outputTargetDirPath = Path.GetDirectoryName(outputTargetPath);
150-
151-
if (!Directory.Exists(outputTargetDirPath))
152-
Directory.CreateDirectory(outputTargetDirPath!);
152+
string outputTargetDirPath = Path.GetDirectoryName(outputTargetPath) ?? Path.GetPathRoot(outputTargetPath);
153+
154+
if (string.IsNullOrEmpty(outputTargetDirPath))
155+
throw new InvalidOperationException(string.Format(Locale.Lang._Dialogs.InvalidGameDirNewTitleFormat,
156+
inputPath));
157+
158+
DirectoryInfo outputTargetDirInfo = new DirectoryInfo(outputTargetDirPath);
159+
outputTargetDirInfo.Create();
153160

154161
if (this.IsSameOutputDrive)
155162
{

CollapseLauncher/Classes/FileMigrationProcess/IO.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ private async Task MoveWriteFile(FileMigrationProcessUIRef uiRef, FileInfo input
1717
{
1818
int bufferSize = 1 << 18; // 256 kB Buffer
1919

20-
if (inputFile!.Length < bufferSize)
20+
if (inputFile.Length < bufferSize)
2121
bufferSize = (int)inputFile.Length;
2222

2323
byte[] buffer = new byte[bufferSize];
2424

2525
await using (FileStream inputStream = inputFile.OpenRead())
26-
await using (FileStream outputStream = outputFile!.Exists && outputFile.Length <= inputFile.Length ?
26+
await using (FileStream outputStream = outputFile.Exists && outputFile.Length <= inputFile.Length ?
2727
outputFile.Open(FileMode.Open) : outputFile.Create())
2828
{
2929
// Set the output file size to inputStream's if the length is more than inputStream

CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseMiscSetting.cs

+5
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ public bool UseCustomRegionBG
116116
/// Determines if the game playtime should be synced to the database
117117
/// </summary>
118118
public bool IsSyncPlaytimeToDatabase { get; set; } = true;
119+
120+
/// <summary>
121+
/// Set per-region Discord Rich Presence setting for playing status
122+
/// </summary>
123+
public bool IsPlayingRpc { get; set; } = true;
119124

120125
#endregion
121126

CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ internal enum MediaType
6666
{
6767
try
6868
{
69-
await action.ConfigureAwait(false);
69+
await action;
7070
}
7171
catch (Exception ex)
7272
{

CollapseLauncher/Classes/Helper/HttpClientBuilder.cs

+50-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using CollapseLauncher.Helper.Update;
22
using Hi3Helper.Shared.Region;
33
using System;
4+
using System.Collections.Generic;
45
using System.Diagnostics;
56
using System.Net;
67
using System.Net.Http;
@@ -17,21 +18,22 @@ public class HttpClientBuilder : HttpClientBuilder<SocketsHttpHandler>;
1718
private const int _maxConnectionsDefault = 32;
1819
private const double _httpTimeoutDefault = 90; // in Seconds
1920

20-
private bool IsUseProxy { get; set; } = true;
21-
private bool IsUseSystemProxy { get; set; } = true;
21+
private bool IsUseProxy { get; set; } = true;
22+
private bool IsUseSystemProxy { get; set; } = true;
2223
private bool IsAllowHttpRedirections { get; set; }
23-
private bool IsAllowHttpCookies { get; set; }
24-
private bool IsAllowUntrustedCert { get; set; }
25-
26-
private int MaxConnections { get; set; } = _maxConnectionsDefault;
27-
private DecompressionMethods DecompressionMethod { get; set; } = DecompressionMethods.All;
28-
private WebProxy? ExternalProxy { get; set; }
29-
private Version HttpProtocolVersion { get; set; } = HttpVersion.Version30;
30-
private string? HttpUserAgent { get; set; } = GetDefaultUserAgent();
31-
private string? HttpAuthHeader { get; set; }
32-
private HttpVersionPolicy HttpProtocolVersionPolicy { get; set; } = HttpVersionPolicy.RequestVersionOrLower;
33-
private TimeSpan HttpTimeout { get; set; } = TimeSpan.FromSeconds(_httpTimeoutDefault);
34-
private Uri? HttpBaseUri { get; set; }
24+
private bool IsAllowHttpCookies { get; set; }
25+
private bool IsAllowUntrustedCert { get; set; }
26+
27+
private int MaxConnections { get; set; } = _maxConnectionsDefault;
28+
private DecompressionMethods DecompressionMethod { get; set; } = DecompressionMethods.All;
29+
private WebProxy? ExternalProxy { get; set; }
30+
private Version HttpProtocolVersion { get; set; } = HttpVersion.Version30;
31+
private string? HttpUserAgent { get; set; } = GetDefaultUserAgent();
32+
private string? HttpAuthHeader { get; set; }
33+
private HttpVersionPolicy HttpProtocolVersionPolicy { get; set; } = HttpVersionPolicy.RequestVersionOrLower;
34+
private TimeSpan HttpTimeout { get; set; } = TimeSpan.FromSeconds(_httpTimeoutDefault);
35+
private Uri? HttpBaseUri { get; set; }
36+
private Dictionary<string, string?> HttpHeaders { get; set; } = new(StringComparer.OrdinalIgnoreCase);
3537

3638
public HttpClientBuilder<THandler> UseProxy(bool isUseSystemProxy = true)
3739
{
@@ -191,6 +193,34 @@ public HttpClientBuilder<THandler> SetBaseUrl(Uri baseUrl)
191193
return this;
192194
}
193195

196+
public HttpClientBuilder<THandler> AddHeader(string key, string? value)
197+
{
198+
// Throw if the key is null or empty
199+
ArgumentException.ThrowIfNullOrEmpty(key, nameof(key));
200+
201+
// Try check if the key is user-agent. If the user-agent has already
202+
// been set, then override the value from HttpUserAgent property
203+
if (key.Equals("User-Agent", StringComparison.OrdinalIgnoreCase))
204+
{
205+
HttpUserAgent = null;
206+
}
207+
208+
// If the key already exist, then override the previous one.
209+
// Otherwise, add the new key-value pair
210+
// ReSharper disable once RedundantDictionaryContainsKeyBeforeAdding
211+
if (HttpHeaders.ContainsKey(key))
212+
{
213+
HttpHeaders[key] = value;
214+
}
215+
else
216+
{
217+
HttpHeaders.Add(key, value);
218+
}
219+
220+
// Return the instance of the builder
221+
return this;
222+
}
223+
194224
public HttpClient Create()
195225
{
196226
// Create the instance of the handler
@@ -273,6 +303,12 @@ public HttpClient Create()
273303
if (!string.IsNullOrEmpty(HttpAuthHeader))
274304
client.DefaultRequestHeaders.Add("Authorization", HttpAuthHeader);
275305

306+
// Add other headers
307+
foreach (KeyValuePair<string, string?> header in HttpHeaders)
308+
{
309+
_ = client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
310+
}
311+
276312
return client;
277313
}
278314
}

0 commit comments

Comments
 (0)