Skip to content

Commit cd9b5a8

Browse files
authored
Preview 1.83.2 Hotfix (#746)
# What's Changed? - **[Fix]** SharingViolation errors while installing/updating games with Sophon mode (eg. Genshin Impact and Zenless Zone Zero), by @neon-nyan - **[Imp]** Streamline submodule branches to main for Hi3Helper.EncTool, Hi3Helper.Http and Hi3Helper.Sophon for easier maintainability, by @neon-nyan - **[Imp+Fix]** Rewriting the whole Genshin Impact's Game Repair pipelines to match game in-game download behavior, by @neon-nyan. This fixes most bugs, including some NotFound error, misplaced files and duplicate files due to the code still runs in how Genshin's in-game download behaves prior < 5.x - **[Fix]** Binding issues causing lags on SettingsPage's initial load, by @neon-nyan - **[Fix]** ``FileMigrationProcess`` pipelines - Fix where the migration might remove unwanted non-game files. - Fix where some old empty folders aren't getting removed. - Fix [#727](#727) where user can't move the game files into a subfolder inside of the current folder. - **[Imp]** Borrow some functions from ``GenshinRepair`` instance on File Clean-up on Genshin Impact, by @neon-nyan - **[Fix]** Some race condition due to ``Lock`` misuses, by @neon-nyan
2 parents c46c9ec + 43b3a17 commit cd9b5a8

33 files changed

+1251
-992
lines changed

CONTRIBUTING.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@ If you wish to add new language that isn't yet listed in the Crowdin project, pl
1515

1616
## Tools Needed
1717
Below is a list of tools needed to contribute to this project:
18-
1. **Visual Studio 2022 (Any Edition - 17.8 or later)**
18+
1. **Visual Studio 2022 (Any Edition - latest version)**
1919
- Select .NET desktop development component
2020
2. **Windows SDK (10.0.26100.0 ONLY)** via Visual Studio Installer
21-
3. .NET 8 SDK: [**(8.0.0 or later)**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
22-
4. WinUI 3: [**(WindowsAppSDK 1.4.0 Stable Runtime)**](https://aka.ms/windowsappsdk/1.4/latest/windowsappruntimeinstall-x64.exe)
21+
3. .NET 9 SDK: [**(9.0.4 or later)**](https://dotnet.microsoft.com/en-us/download/dotnet/9.0)
2322

2423
> **Note**:
25-
>
26-
> Starting from November 21<sup>st</sup> 2023, you <b>must</b> have Visual Studio 2022 version 17.8+ installed on your computer due to the updated minimum system requirement of `WindowsAppSDK` and `.NET 8`.
27-
>
28-
> Using a lower Visual Studio version (like VS2019) is not possible due to requirement from WindowsAppSDK used in this project.
24+
> Make sure to always use the latest version of Visual Studio in order to be able to open the project.
25+
2926
## Restrictions for New Feature(s)
3027
While this software is fully open source and not affiliated with HoYoverse, Cognosphere, or any of its related entities in any way, we are nonetheless bound to their Terms of Service and Code of Conduct when developing Collapse. This means that there are some features that we will **not** implement. We will close any issue or PRs that are made to add such functionality to Collapse. Such features include, but are not limited to:
3128
- Anything that, in any way, interacts with the miHoYo SDK and/or API, including their Authentication and Payment Processing endpoints.
@@ -48,8 +45,9 @@ While this software is fully open source and not affiliated with HoYoverse, Cogn
4845
- [Genshin Impact Terms of Service](https://genshin.hoyoverse.com/en/company/terms)
4946
- [Honkai Impact 3rd Terms of Service](https://honkaiimpact3.hoyoverse.com/global/en-us/terms)
5047
- [Honkai: Star Rail Terms of Service](https://hsr.hoyoverse.com/en-us/company/terms)
48+
- [Zenless Zone Zero Terms of Service](https://zenless.hoyoverse.com/en-us/company/terms)
5149
- [HoYoLAB Forum Terms of Service](https://www.hoyolab.com/agreement)
5250

5351
# A Humble Thank You
5452
As contributors, we always feel grateful for all your contributions to the project, whether it be through helping with localizing the app, coming up with new features, reporting bugs, and even using this launcher. Through everyone's effort, we can keep this project alive by bringing even more features and quality-of-life (QoL) upgrades over the existing launchers (including official) that are out there.
55-
Thank you ❤️
53+
Thank you ❤️

CollapseLauncher/Classes/Extension/UIElementExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,5 +1025,21 @@ internal static IEnumerable<object> EnumerateSelectableElementChildren(this Fram
10251025
yield return item;
10261026
}
10271027
}
1028+
1029+
internal static bool IsElementVisible(this FrameworkElement? element)
1030+
{
1031+
if (element == null)
1032+
{
1033+
return false;
1034+
}
1035+
1036+
if (element.Visibility == Visibility.Collapsed || element.Opacity < 1)
1037+
{
1038+
return false;
1039+
}
1040+
1041+
return VisualTreeHelper.GetParent(element) is not FrameworkElement parentElement ||
1042+
(parentElement.Visibility != Visibility.Collapsed && !(element.Opacity < 1));
1043+
}
10281044
}
10291045
}

CollapseLauncher/Classes/FileMigrationProcess/FileMigrationProcess.cs

Lines changed: 112 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
using CollapseLauncher.FileDialogCOM;
22
using CollapseLauncher.Helper;
3+
using CollapseLauncher.Helper.StreamUtility;
34
using Hi3Helper;
45
using Hi3Helper.Shared.Region;
6+
using Hi3Helper.Win32.ManagedTools;
7+
using Hi3Helper.Win32.Native.ManagedTools;
58
using Microsoft.UI.Dispatching;
69
using System;
10+
using System.Collections.Generic;
711
using System.Diagnostics;
812
using System.IO;
13+
using System.Linq;
914
using System.Threading;
1015
using System.Threading.Tasks;
1116

@@ -49,7 +54,7 @@ internal async Task<string> StartRoutine()
4954
_currentFileCountMoved = 0;
5055
_totalFileSize = 0;
5156
_totalFileCount = 0;
52-
FileMigrationProcessUIRef? uiRef = null;
57+
FileMigrationProcessUIRef uiRef = null;
5358

5459
try
5560
{
@@ -62,20 +67,20 @@ internal async Task<string> StartRoutine()
6267
}
6368

6469
uiRef = BuildMainMigrationUI();
65-
string outputPath = await StartRoutineInner(uiRef.Value);
66-
uiRef.Value.MainDialogWindow!.Hide();
70+
string outputPath = await StartRoutineInner(uiRef);
71+
uiRef.MainDialogWindow!.Hide();
6772
isSuccess = true;
6873

6974
return outputPath;
7075
}
7176
catch when (!isSuccess) // Throw if the isSuccess is not set to true
7277
{
73-
if (!uiRef.HasValue || uiRef.Value.MainDialogWindow == null)
78+
if (uiRef?.MainDialogWindow == null)
7479
{
7580
throw;
7681
}
7782

78-
uiRef.Value.MainDialogWindow.Hide();
83+
uiRef.MainDialogWindow.Hide();
7984
await Task.Delay(500); // Give artificial delay to give main dialog window thread to close first
8085
throw;
8186
}
@@ -122,7 +127,7 @@ private async Task<string> MoveFile(FileMigrationProcessUIRef uiRef)
122127
else
123128
{
124129
Logger.LogWriteLine($"[FileMigrationProcess::MoveFile()] Moving file across different drives from: {inputPathInfo.FullName} to {outputPathInfo.FullName}", LogType.Default, true);
125-
await MoveWriteFile(uiRef, inputPathInfo, outputPathInfo, TokenSource?.Token ?? default);
130+
await MoveWriteFile(uiRef, inputPathInfo, outputPathInfo, TokenSource?.Token ?? CancellationToken.None);
126131
}
127132

128133
return outputPathInfo.FullName;
@@ -134,51 +139,115 @@ private async Task<string> MoveDirectory(FileMigrationProcessUIRef uiRef)
134139
DirectoryInfo outputPathInfo = new DirectoryInfo(OutputPath);
135140
outputPathInfo.Create();
136141

137-
int parentInputPathLength = inputPathInfo.Parent!.FullName.Length + 1;
138-
string outputDirBaseNamePath = inputPathInfo.FullName.Substring(parentInputPathLength);
139-
string outputDirPath = Path.Combine(OutputPath, outputDirBaseNamePath);
142+
bool isMoveBackward = inputPathInfo.FullName.StartsWith(outputPathInfo.FullName, StringComparison.OrdinalIgnoreCase) &&
143+
!inputPathInfo.FullName.Equals(outputPathInfo.FullName, StringComparison.OrdinalIgnoreCase);
144+
145+
// Listing all the existed files first
146+
List <FileInfo> inputFileList = [];
147+
inputFileList.AddRange(inputPathInfo
148+
.EnumerateFiles("*", SearchOption.AllDirectories)
149+
.EnumerateNoReadOnly()
150+
.Where(x => isMoveBackward || IsNotInOutputDir(x)));
151+
152+
// Check if both destination and source are SSDs. If true, enable multi-threading.
153+
// Disabling multi-threading while either destination or source are HDDs could help
154+
// reduce massive seeking, hence improving speed.
155+
bool isBothSsd = DriveTypeChecker.IsDriveSsd(InputPath) &&
156+
DriveTypeChecker.IsDriveSsd(OutputPath);
157+
ParallelOptions parallelOptions = new ParallelOptions
158+
{
159+
CancellationToken = TokenSource?.Token ?? CancellationToken.None,
160+
MaxDegreeOfParallelism = isBothSsd ? LauncherConfig.AppCurrentThread : 1
161+
};
162+
163+
// Get old list of empty directories so it can be removed later.
164+
StringComparer comparer = StringComparer.OrdinalIgnoreCase;
165+
HashSet<string> oldDirectoryList = new(inputFileList
166+
.Select(x => x.DirectoryName)
167+
.Where(x => !string.IsNullOrEmpty(x) && !x.Equals(inputPathInfo.FullName, StringComparison.OrdinalIgnoreCase))
168+
.Distinct(comparer)
169+
.OrderDescending(), comparer);
170+
171+
// Perform file migration task
172+
await Parallel.ForEachAsync(inputFileList, parallelOptions, Impl);
173+
foreach (string dir in oldDirectoryList)
174+
{
175+
RemoveEmptyDirectory(dir);
176+
}
140177

141-
await Parallel.ForEachAsync(
142-
inputPathInfo.EnumerateFiles("*", SearchOption.AllDirectories),
143-
new ParallelOptions
144-
{
145-
CancellationToken = TokenSource?.Token ?? default,
146-
MaxDegreeOfParallelism = LauncherConfig.AppCurrentThread
147-
},
148-
async (inputFileInfo, cancellationToken) =>
149-
{
150-
int parentInputPathLengthLocal = inputPathInfo.Parent!.FullName.Length + 1;
151-
string inputFileBasePath = inputFileInfo!.FullName[parentInputPathLengthLocal..];
178+
return OutputPath;
152179

153-
// Update path display
154-
UpdateCountProcessed(uiRef, inputFileBasePath);
180+
bool IsNotInOutputDir(FileInfo fileInfo)
181+
{
182+
bool isEmpty = string.IsNullOrEmpty(fileInfo.DirectoryName);
183+
if (isEmpty)
184+
{
185+
return false;
186+
}
155187

156-
string outputTargetPath = Path.Combine(outputPathInfo.FullName, inputFileBasePath);
157-
string outputTargetDirPath = Path.GetDirectoryName(outputTargetPath) ?? Path.GetPathRoot(outputTargetPath);
188+
bool isStartsWith = fileInfo.DirectoryName.StartsWith(outputPathInfo.FullName);
189+
return !isStartsWith;
190+
}
158191

159-
if (string.IsNullOrEmpty(outputTargetDirPath))
160-
throw new InvalidOperationException(string.Format(Locale.Lang._Dialogs.InvalidGameDirNewTitleFormat,
161-
InputPath));
162-
163-
DirectoryInfo outputTargetDirInfo = new DirectoryInfo(outputTargetDirPath);
164-
outputTargetDirInfo.Create();
192+
void RemoveEmptyDirectory(string dir)
193+
{
194+
foreach (string innerDir in Directory.EnumerateDirectories(dir))
195+
{
196+
RemoveEmptyDirectory(innerDir);
197+
}
165198

166-
if (IsSameOutputDrive)
167-
{
168-
Logger.LogWriteLine($"[FileMigrationProcess::MoveDirectory()] Moving directory content in the same drive from: {inputFileInfo.FullName} to {outputTargetPath}", LogType.Default, true);
169-
inputFileInfo.MoveTo(outputTargetPath, true);
170-
UpdateSizeProcessed(uiRef, inputFileInfo.Length);
171-
}
172-
else
199+
try
200+
{
201+
_ = FindFiles.TryIsDirectoryEmpty(dir, out bool isEmpty);
202+
if (!isEmpty)
173203
{
174-
Logger.LogWriteLine($"[FileMigrationProcess::MoveDirectory()] Moving directory content across different drives from: {inputFileInfo.FullName} to {outputTargetPath}", LogType.Default, true);
175-
FileInfo outputFileInfo = new FileInfo(outputTargetPath);
176-
await MoveWriteFile(uiRef, inputFileInfo, outputFileInfo, cancellationToken);
204+
string parentDir = Path.GetDirectoryName(dir);
205+
if (!string.IsNullOrEmpty(parentDir))
206+
{
207+
RemoveEmptyDirectory(parentDir);
208+
}
177209
}
178-
});
179210

180-
inputPathInfo.Delete(true);
181-
return outputDirPath;
211+
Directory.Delete(dir);
212+
Logger.LogWriteLine($"[FileMigrationProcess::MoveDirectory()] Empty directory: {dir} has been deleted!", LogType.Default, true);
213+
}
214+
catch (IOException)
215+
{
216+
// ignored
217+
}
218+
}
219+
220+
async ValueTask Impl(FileInfo inputFileInfo, CancellationToken cancellationToken)
221+
{
222+
string inputFileRelativePath = inputFileInfo.FullName
223+
.AsSpan(InputPath.Length)
224+
.TrimStart("\\/")
225+
.ToString();
226+
227+
string outputNewFilePath = Path.Combine(OutputPath, inputFileRelativePath);
228+
string outputNewFileDir = Path.GetDirectoryName(outputNewFilePath) ?? Path.GetPathRoot(outputNewFilePath);
229+
if (!string.IsNullOrEmpty(outputNewFileDir))
230+
Directory.CreateDirectory(outputNewFileDir);
231+
232+
// Update path display
233+
UpdateCountProcessed(uiRef, inputFileRelativePath);
234+
if (string.IsNullOrEmpty(outputNewFileDir))
235+
throw new InvalidOperationException(string.Format(Locale.Lang._Dialogs.InvalidGameDirNewTitleFormat,
236+
InputPath));
237+
238+
if (IsSameOutputDrive)
239+
{
240+
Logger.LogWriteLine($"[FileMigrationProcess::MoveDirectory()] Moving directory content in the same drive from: {inputFileInfo.FullName} to {outputNewFilePath}", LogType.Default, true);
241+
inputFileInfo.MoveTo(outputNewFilePath, true);
242+
UpdateSizeProcessed(uiRef, inputFileInfo.Length);
243+
}
244+
else
245+
{
246+
Logger.LogWriteLine($"[FileMigrationProcess::MoveDirectory()] Moving directory content across different drives from: {inputFileInfo.FullName} to {outputNewFilePath}", LogType.Default, true);
247+
FileInfo outputFileInfo = new FileInfo(outputNewFilePath);
248+
await MoveWriteFile(uiRef, inputFileInfo, outputFileInfo, cancellationToken);
249+
}
250+
}
182251
}
183252
}
184253
}

CollapseLauncher/Classes/FileMigrationProcess/FileMigrationProcessRef.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
namespace CollapseLauncher
66
{
7-
internal struct FileMigrationProcessUIRef
7+
internal class FileMigrationProcessUIRef
88
{
9-
internal ContentDialogCollapse MainDialogWindow;
10-
internal TextBlock PathActivitySubtitle;
11-
internal Run SpeedIndicatorSubtitle;
12-
internal Run FileCountIndicatorSubtitle;
13-
internal Run FileSizeIndicatorSubtitle;
14-
internal ProgressBar ProgressBarIndicator;
9+
internal ContentDialogCollapse MainDialogWindow { get; set; }
10+
internal TextBlock PathActivitySubtitle { get; set; }
11+
internal Run SpeedIndicatorSubtitle { get; set; }
12+
internal Run FileCountIndicatorSubtitle { get; set; }
13+
internal Run FileSizeIndicatorSubtitle { get; set; }
14+
internal ProgressBar ProgressBarIndicator { get; set; }
1515
}
1616
}

CollapseLauncher/Classes/FileMigrationProcess/Statics.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ private static bool IsOutputPathSameAsInput(string inputPath, string outputPath,
5656
bool isStringEmpty = string.IsNullOrEmpty(outputPath);
5757

5858
if (!isFilePath) inputPath = Path.GetDirectoryName(inputPath);
59-
bool isPathEqual = inputPath.AsSpan().TrimEnd('\\').SequenceEqual(outputPath.AsSpan().TrimEnd('\\'));
59+
bool isPathEqual = inputPath.AsSpan().TrimEnd("\\/").SequenceEqual(outputPath.AsSpan().TrimEnd("\\/"));
6060

6161
return isStringEmpty || isPathEqual;
6262
}

0 commit comments

Comments
 (0)