Skip to content

Commit d8db1ea

Browse files
authored
[Urgent] Preview 1.82.7 Hotfix Release (#624)
# What's new? - 1.82.7 Hotfix - **[Fix]** Fix redundant Ini save-load mechanism and FileNotFoundException upon loading, by @neon-nyan This caused the launcher to throw ``FileNotFoundException`` error in the background and causing the region fail to load. - **[Imp]** Improvement on ``IniValue``, by @neon-nyan - Always cache ``IsEmpty`` property and update it only if the ``Value`` property is being set. - Add missing casting for ``Size`` struct - Add ``Create()`` static method - Add ``ToGuid()`` method and cast support - Add support for creating ``IniValue`` from ``Enum`` - **[Fix]** Avoid double desktop shortcut by using user's Desktop path, by @neon-nyan - **[Fix]** Fix wrong casting on implicit IniValue operator for Int32, by @neon-nyan ---------------- # What's new? - 1.82.6 - **[Fix]** Download corruption due to download chunk size being too small, by @neon-nyan - **[Fix]** Double taskbar entry if console is enabled, by @bagusnl - **[Fix]** Repair function for GI/SR/HI3 detected updated plugin as corrupted, by @bagusnl - **[Imp]** Update 7z dll to 24.09, by @neon-nyan - **[Imp]** Http downloader module improvements, by @neon-nyan - Bypass drive write cache - Use multi threaded file writer - **[Imp]** ``IniFile`` parser improvements, by @neon-nyan + Improving implicit casting on IniValue to numbers This to allow maintainers to directly assign the IniValue to variable types. + Reduce memory allocation on loading and saving IniFile + Improving saving performance to file or a stream. + More safety bound check to IniSection + Reduce overhead on checking Section Keys and Value Keys + Add more checks on loading values + Splitting class and structs into their own files ## ```IniFile``` benchmark (Old vs. New) ### Note There might be some minor performance degradation on Loading mechanism due to additional check overhead. More work will be implemented later to fix this. | Method | Mean Time | Ratio | Time Imp (%) | Allocated Mem. | Ratio | Mem. Imp (%) | |----------------- |-----------:|-----------:|----------------------:|------------:|------------:|-----------------------:| | Load_Old | 77.85 µs | 1.000 | - | 217.3 KB | 1.000 | - | | Load_New | 80.80 µs | 1.038 | -3.8% | 138.3 KB | 0.637 | +36.4% | | Load_OrderedNew | 79.73 µs | 1.024 | -2.4% | 138.3 KB | 0.637 | +36.4% | | Save_Old | 0.10 µs | 1.000 | - | 2.2 KB | 1.000 | - | | Save_New | 0.06 µs | 0.630 | +37.0% | 0.4 KB | 0.181 | +81.8% | | Save_OrderedNew | 0.06 µs | 0.665 | +33.5% | 0.4 KB | 0.181 | +81.8% | ### Templates <details> <summary>Changelog Prefixes</summary> ``` **[New]** **[Imp]** **[Fix]** **[Loc]** **[Doc]** ``` </details>
2 parents a826640 + d3cceab commit d8db1ea

File tree

8 files changed

+156
-62
lines changed

8 files changed

+156
-62
lines changed

CollapseLauncher/Classes/GameManagement/GameVersion/BaseClass/GameVersionBase.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ protected GameVersion? GameVersionInstalled
242242
}
243243
set
244244
{
245-
UpdateGameVersion(value ?? GameVersionAPI);
246-
UpdateGameChannels();
245+
UpdateGameVersion(value ?? GameVersionAPI, false);
246+
UpdateGameChannels(false);
247247
}
248248
}
249249

@@ -1071,7 +1071,7 @@ protected virtual void TryReinitializeGameVersion()
10711071
{
10721072
// Check if the GameVersionInstalled == null (version config doesn't exist),
10731073
// Reinitialize the version config and save the version config by assigning GameVersionInstalled.
1074-
if (GameVersionInstalled == null && GameAPIProp.data?.game?.latest?.version != null)
1074+
if (!GameVersionInstalled.HasValue && !string.IsNullOrEmpty(GameAPIProp.data?.game?.latest?.version))
10751075
{
10761076
GameVersionInstalled = GameVersionAPI;
10771077
}
@@ -1122,6 +1122,12 @@ private void InitializeIniProp()
11221122

11231123
private bool IsTryParseIniVersionExist(string iniPath)
11241124
{
1125+
// If the file doesn't exist, return false by default
1126+
if (!File.Exists(iniPath))
1127+
{
1128+
return false;
1129+
}
1130+
11251131
// Load version config file.
11261132
IniFile iniFile = new IniFile();
11271133
iniFile.Load(iniPath);
@@ -1156,7 +1162,7 @@ private bool IsDiskPartitionExist(string path)
11561162
}
11571163
}
11581164

1159-
private void SaveGameIni(string filePath, in IniFile INI)
1165+
private void SaveGameIni(string filePath, IniFile INI)
11601166
{
11611167
// Check if the disk partition exist. If it's exist, then save the INI.
11621168
if (IsDiskPartitionExist(filePath))
@@ -1165,7 +1171,7 @@ private void SaveGameIni(string filePath, in IniFile INI)
11651171
}
11661172
}
11671173

1168-
private void InitializeIniProp(string iniFilePath, in IniFile ini, IniSection defaults, string section, bool allowOverwriteUnmatchValues = false)
1174+
private void InitializeIniProp(string iniFilePath, IniFile ini, IniSection defaults, string section, bool allowOverwriteUnmatchValues = false)
11691175
{
11701176
// Get the file path of the INI file and normalize it
11711177
iniFilePath = ConverterTool.NormalizePath(iniFilePath);
@@ -1174,26 +1180,17 @@ private void InitializeIniProp(string iniFilePath, in IniFile ini, IniSection de
11741180
// Check if the disk partition is ready (exist)
11751181
bool IsDiskReady = IsDiskPartitionExist(iniDirPath);
11761182

1177-
// Create the directory of the file if it doesn't exist
1178-
if (iniDirPath != null && !Directory.Exists(iniDirPath) && IsDiskReady)
1179-
{
1180-
Directory.CreateDirectory(iniDirPath);
1181-
}
1182-
1183-
// Load the INI file.
1184-
if (IsDiskReady)
1183+
// Load the existing INI file if only the file exist.
1184+
if (IsDiskReady && File.Exists(iniFilePath))
11851185
{
11861186
ini.Load(iniFilePath);
11871187
}
11881188

11891189
// Initialize and ensure the non-existed values to their defaults.
11901190
InitializeIniDefaults(ini, defaults, section, allowOverwriteUnmatchValues);
1191-
1192-
// Always save the file to ensure file existency
1193-
SaveGameIni(iniFilePath, ini);
11941191
}
11951192

1196-
private void InitializeIniDefaults(in IniFile ini, IniSection defaults, string section, bool allowOverwriteUnmatchValues)
1193+
private void InitializeIniDefaults(IniFile ini, IniSection defaults, string section, bool allowOverwriteUnmatchValues)
11971194
{
11981195
// If the section doesn't exist, then add the section.
11991196
if (!ini.ContainsKey(section))

CollapseLauncher/Classes/Helper/Database/DBConfig.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static class DbConfig
1717
private const string _configFileName = "dbConfig.ini";
1818
private const string DbSectionName = "database";
1919

20-
private static readonly string _configPath = Path.Combine(_configFolder, _configFileName);
20+
private static readonly string _configPath = Path.Combine(_configFolder, _configFileName);
2121

2222
// ReSharper disable once FieldCanBeMadeReadOnly.Local
2323
private static IniFile _config = new();
@@ -32,7 +32,7 @@ public static void Init()
3232

3333
DefaultChecker();
3434
}
35-
35+
3636
private static void EnsureConfigExist()
3737
{
3838
if (File.Exists(_configPath)) return;
@@ -53,7 +53,14 @@ private static void DefaultChecker()
5353
}
5454
}
5555

56-
private static void Load(CancellationToken token = default) => _config.Load(_configPath);
56+
private static void Load(CancellationToken token = default)
57+
{
58+
if (File.Exists(_configPath))
59+
{
60+
_config.Load(_configPath);
61+
}
62+
}
63+
5764
private static void Save(CancellationToken token = default) => _config.Save(_configPath);
5865

5966
public static IniValue GetConfig(string key) => _config[DbSectionName][key];

CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ internal static (string IconStartMenu, string IconDesktop) GetIconLocationPaths(
215215
appProductName = currentExecVersionInfo.ProductName;
216216
string shortcutFilename = appProductName + ".lnk";
217217
string startMenuLocation = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu);
218-
string desktopLocation = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
218+
string desktopLocation = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
219219
string iconLocationStartMenu = Path.Combine(
220220
startMenuLocation,
221221
"Programs",

CollapseLauncher/CollapseLauncher.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<Authors>$(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm.</Authors>
1717
<Copyright>Copyright 2022-2024 $(Company)</Copyright>
1818
<!-- Versioning -->
19-
<Version>1.82.6</Version>
19+
<Version>1.82.7</Version>
2020
<LangVersion>preview</LangVersion>
2121
<!-- Target Settings -->
2222
<Platforms>x64</Platforms>

CollapseLauncher/XAMLs/Invoker/Classes/Migrate.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,11 @@ public void DoMigration(string source, string target)
9898

9999
IniFile iniFile = new IniFile();
100100

101-
if (File.Exists(Path.Combine(source, "config.ini")))
101+
string configFilePath = Path.Combine(source, "config.ini");
102+
103+
if (File.Exists(configFilePath))
102104
{
103-
iniFile.Load(Path.Combine(source, "config.ini"));
105+
iniFile.Load(configFilePath, true);
104106
sourceGame = ConverterTool.NormalizePath(iniFile["launcher"]["game_install_path"].ToString());
105107
targetGame = Path.Combine(target, Path.GetFileName(sourceGame));
106108
Console.WriteLine($"Moving From:\r\n\t{source}\r\nTo Destination:\r\n\t{target}");

Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,14 @@ public static void SetAndSaveConfigValue(string key, IniValue value, bool doNotL
9595
}
9696
public static void SetAppConfigValue(string key, IniValue value) => appIni.Profile![SectionName]![key!] = value;
9797

98-
public static void LoadAppConfig() => appIni.Profile!.Load(appIni.ProfilePath);
98+
public static void LoadAppConfig() => appIni.Profile!.Load(appIni.ProfilePath, true);
9999
public static void SaveAppConfig() => appIni.Profile!.Save(appIni.ProfilePath);
100100

101101
public static void CheckAndSetDefaultConfigValue()
102102
{
103103
foreach (KeyValuePair<string, IniValue> Entry in AppSettingsTemplate!)
104104
{
105-
if (!appIni.Profile![SectionName]!.ContainsKey(Entry.Key!) || string.IsNullOrEmpty(appIni.Profile[SectionName][Entry.Key]))
105+
if (!appIni.Profile![SectionName]!.ContainsKey(Entry.Key!) || appIni.Profile[SectionName][Entry.Key].IsEmpty)
106106
{
107107
SetAppConfigValue(Entry.Key, Entry.Value);
108108
}
@@ -111,7 +111,7 @@ public static void CheckAndSetDefaultConfigValue()
111111
#endregion
112112

113113
#region Misc Methods
114-
public static void LoadGamePreset() => AppGameFolder = Path.Combine(GetAppConfigValue("GameFolder").ToString()!);
114+
public static void LoadGamePreset() => AppGameFolder = Path.Combine(GetAppConfigValue("GameFolder")!);
115115

116116
public static void GetScreenResolutionString(ScreenProp screenProp)
117117
{
@@ -161,7 +161,7 @@ private static void InitScreenResSettings(ScreenProp screenProp)
161161
},
162162
};
163163

164-
public static CDNURLProperty GetCurrentCDN() => CDNList![GetAppConfigValue("CurrentCDN").ToInt()];
164+
public static CDNURLProperty GetCurrentCDN() => CDNList![GetAppConfigValue("CurrentCDN")];
165165
#endregion
166166

167167
#region Misc Fields
@@ -202,7 +202,7 @@ private static void InitScreenResSettings(ScreenProp screenProp)
202202
public static string AppImagesFolder = Path.Combine(AppFolder, "Assets", "Images");
203203
public static string AppGameFolder
204204
{
205-
get => GetAppConfigValue("GameFolder").ToString();
205+
get => GetAppConfigValue("GameFolder");
206206
set => SetAppConfigValue("GameFolder", value);
207207
}
208208
public static string[] AppCurrentArgument;
@@ -262,11 +262,11 @@ public static int AppCurrentThread
262262
{
263263
get
264264
{
265-
int val = GetAppConfigValue("ExtractionThread").ToInt();
265+
int val = GetAppConfigValue("ExtractionThread");
266266
return val <= 0 ? Environment.ProcessorCount : val;
267267
}
268268
}
269-
public static int AppCurrentDownloadThread => GetAppConfigValue("DownloadThread").ToInt();
269+
public static int AppCurrentDownloadThread => GetAppConfigValue("DownloadThread");
270270
public static string AppGameConfigMetadataFolder { get => Path.Combine(AppGameFolder!, "_metadatav3"); }
271271

272272
public static readonly bool IsAppLangNeedRestart = false;
@@ -278,55 +278,55 @@ public static int AppCurrentThread
278278
public static bool IsFirstInstall = false;
279279
public static bool IsConsoleEnabled
280280
{
281-
get => GetAppConfigValue("EnableConsole").ToBoolNullable() ?? false;
281+
get => GetAppConfigValue("EnableConsole");
282282
set => SetAppConfigValue("EnableConsole", value);
283283
}
284284

285285
public static bool IsMultipleInstanceEnabled
286286
{
287-
get => GetAppConfigValue("EnableMultipleInstance").ToBoolNullable() ?? false;
287+
get => GetAppConfigValue("EnableMultipleInstance");
288288
set => SetAndSaveConfigValue("EnableMultipleInstance", value);
289289
}
290290

291291
public static bool IsShowRegionChangeWarning
292292
{
293-
get => GetAppConfigValue("ShowRegionChangeWarning").ToBool();
293+
get => GetAppConfigValue("ShowRegionChangeWarning");
294294
set => SetAndSaveConfigValue("ShowRegionChangeWarning", value);
295295
}
296296

297297
public static bool EnableAcrylicEffect
298298
{
299-
get => GetAppConfigValue("EnableAcrylicEffect").ToBoolNullable() ?? false;
299+
get => GetAppConfigValue("EnableAcrylicEffect");
300300
set => SetAndSaveConfigValue("EnableAcrylicEffect", value);
301301
}
302302

303303
public static bool IsUseVideoBGDynamicColorUpdate
304304
{
305-
get => GetAppConfigValue("IsUseVideoBGDynamicColorUpdate").ToBoolNullable() ?? false;
305+
get => GetAppConfigValue("IsUseVideoBGDynamicColorUpdate");
306306
set => SetAndSaveConfigValue("IsUseVideoBGDynamicColorUpdate", value);
307307
}
308308

309309
public static bool IsIntroEnabled
310310
{
311-
get => GetAppConfigValue("IsIntroEnabled").ToBoolNullable() ?? true;
311+
get => GetAppConfigValue("IsIntroEnabled");
312312
set => SetAndSaveConfigValue("IsIntroEnabled", value);
313313
}
314314

315315
public static bool IsBurstDownloadModeEnabled
316316
{
317-
get => GetAppConfigValue("IsBurstDownloadModeEnabled").ToBoolNullable() ?? true;
317+
get => GetAppConfigValue("IsBurstDownloadModeEnabled");
318318
set => SetAndSaveConfigValue("IsBurstDownloadModeEnabled", value);
319319
}
320320

321321
public static bool IsUsePreallocatedDownloader
322322
{
323-
get => GetAppConfigValue("IsUsePreallocatedDownloader").ToBoolNullable() ?? true;
323+
get => GetAppConfigValue("IsUsePreallocatedDownloader");
324324
set => SetAndSaveConfigValue("IsUsePreallocatedDownloader", value);
325325
}
326326

327327
public static bool IsUseDownloadSpeedLimiter
328328
{
329-
get => GetAppConfigValue("IsUseDownloadSpeedLimiter").ToBoolNullable() ?? true;
329+
get => GetAppConfigValue("IsUseDownloadSpeedLimiter");
330330
set
331331
{
332332
SetAndSaveConfigValue("IsUseDownloadSpeedLimiter", value);
@@ -337,7 +337,7 @@ public static bool IsUseDownloadSpeedLimiter
337337

338338
public static long DownloadSpeedLimit
339339
{
340-
get => DownloadSpeedLimitCached = GetAppConfigValue("DownloadSpeedLimit").ToLong();
340+
get => DownloadSpeedLimitCached = GetAppConfigValue("DownloadSpeedLimit");
341341
set => SetAndSaveConfigValue("DownloadSpeedLimit", DownloadSpeedLimitCached = value);
342342
}
343343

@@ -346,7 +346,7 @@ public static int DownloadChunkSize
346346
get
347347
{
348348
// Clamp value, Min: 32 MiB, Max: 512 MiB
349-
int configValue = GetAppConfigValue("DownloadChunkSize").ToInt();
349+
int configValue = GetAppConfigValue("DownloadChunkSize");
350350
configValue = Math.Clamp(configValue, 32 << 20, 512 << 20);
351351
return configValue;
352352
}
@@ -360,7 +360,7 @@ public static int DownloadChunkSize
360360

361361
public static bool IsEnforceToUse7zipOnExtract
362362
{
363-
get => GetAppConfigValue("EnforceToUse7zipOnExtract").ToBool();
363+
get => GetAppConfigValue("EnforceToUse7zipOnExtract");
364364
set => SetAndSaveConfigValue("EnforceToUse7zipOnExtract", value);
365365
}
366366

@@ -384,7 +384,7 @@ public static bool IsInstantRegionChange
384384
{
385385
get
386386
{
387-
_cachedIsInstantRegionChange ??= GetAppConfigValue("UseInstantRegionChange").ToBool();
387+
_cachedIsInstantRegionChange ??= GetAppConfigValue("UseInstantRegionChange");
388388
return (bool)_cachedIsInstantRegionChange;
389389
}
390390
set => SetAndSaveConfigValue("UseInstantRegionChange", value);
@@ -395,14 +395,14 @@ public static bool IsInstantRegionChange
395395

396396
public static Guid GetGuid(int sessionNum)
397397
{
398-
var guidString = GetAppConfigValue($"sessionGuid{sessionNum}").ToString();
399-
if (string.IsNullOrEmpty(guidString))
398+
Guid guidString = GetAppConfigValue($"sessionGuid{sessionNum}");
399+
if (guidString == Guid.Empty)
400400
{
401401
var g = Guid.NewGuid();
402-
SetAndSaveConfigValue($"sessionGuid{sessionNum}", g.ToString());
402+
SetAndSaveConfigValue($"sessionGuid{sessionNum}", g);
403403
return g;
404404
}
405-
return Guid.Parse(guidString);
405+
return guidString;
406406
}
407407
#endregion
408408

@@ -422,7 +422,7 @@ public static Guid GetGuid(int sessionNum)
422422
{ "SendRemoteCrashData", true },
423423
{ "EnableMultipleInstance", false },
424424
{ "DontAskUpdate", false },
425-
{ "ThemeMode", new IniValue(AppThemeMode.Default) },
425+
{ "ThemeMode", IniValue.Create(AppThemeMode.Default) },
426426
{ "AppLanguage", "en-us" },
427427
{ "UseCustomBG", false },
428428
{ "IsUseVideoBGDynamicColorUpdate", false },

Hi3Helper.Core/Data/IniFile.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ public IniFile(IEqualityComparer<string>? stringComparer, bool isSaveOrdered)
104104
#region Load and Save Methods
105105
public void Save(string path)
106106
{
107+
EnsureFolderExist(path);
108+
107109
using FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, CreateStreamBufferSize, FileOptions.None);
108110
using StreamWriter writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: false);
109111
SaveInner(writer);
@@ -152,9 +154,16 @@ private void SaveInner(StreamWriter writer)
152154
}
153155
}
154156

155-
public void Load(string path)
157+
public void Load(string path, bool createIfNotExist = false)
156158
{
157-
using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, OpenStreamBufferSize, FileOptions.None);
159+
FileMode fileMode = createIfNotExist ? FileMode.OpenOrCreate : FileMode.Open;
160+
if (createIfNotExist)
161+
{
162+
// Always ensure folder existence if createIfNotExist was toggled
163+
EnsureFolderExist(path);
164+
}
165+
166+
using FileStream stream = new FileStream(path, fileMode, FileAccess.Read, FileShare.ReadWrite, OpenStreamBufferSize, FileOptions.None);
158167
using StreamReader reader = new StreamReader(stream, leaveOpen: false);
159168
LoadInner(reader);
160169
}
@@ -227,6 +236,14 @@ private void LoadInner(TextReader reader)
227236
}
228237
}
229238

239+
private void EnsureFolderExist(string filePath)
240+
{
241+
// Always create the directory if none of it exist
242+
string? pathDirectory = Path.GetDirectoryName(filePath);
243+
if (!string.IsNullOrEmpty(pathDirectory))
244+
_ = Directory.CreateDirectory(pathDirectory);
245+
}
246+
230247
private void GetOrCreateSection(string sectionName, [NotNull] out IniSection? iniSection)
231248
{
232249
// Try get the section and if it's not exist yet, then add it

0 commit comments

Comments
 (0)