diff --git a/YaeAchievement/res/App.Designer.cs b/YaeAchievement/res/App.Designer.cs index eab56c2..42da7df 100644 --- a/YaeAchievement/res/App.Designer.cs +++ b/YaeAchievement/res/App.Designer.cs @@ -446,6 +446,15 @@ namespace YaeAchievement.res { } } + /// + /// Looks up a localized string similar to An error occurred while reading the data. Please try again.. + /// + internal static string StreamReadDataFail { + get { + return ResourceManager.GetString("StreamReadDataFail", resourceCulture); + } + } + /// /// Looks up a localized string similar to Checking update.... /// @@ -474,6 +483,15 @@ namespace YaeAchievement.res { } } + /// + /// Looks up a localized string similar to The process cannot access the file '{0}' because it is being used by another process. Please restart your computer and try again.. + /// + internal static string UpdateFileShareViolation { + get { + return ResourceManager.GetString("UpdateFileShareViolation", resourceCulture); + } + } + /// /// Looks up a localized string similar to Has update: {0} => {1}. /// diff --git a/YaeAchievement/res/App.resx b/YaeAchievement/res/App.resx index 9f04f70..7e2291c 100644 --- a/YaeAchievement/res/App.resx +++ b/YaeAchievement/res/App.resx @@ -175,4 +175,10 @@ Please enter a number between 0 and {0} + + An error occurred while reading the data. Please try again. + + + The process cannot access the file '{0}' because it is being used by another process. Please restart your computer and try again. + \ No newline at end of file diff --git a/YaeAchievement/res/App.zh.resx b/YaeAchievement/res/App.zh.resx index 58e25e8..f384dbc 100644 --- a/YaeAchievement/res/App.zh.resx +++ b/YaeAchievement/res/App.zh.resx @@ -168,4 +168,10 @@ 请输入 0 到 {0} 之间的数字 + + 读取数据时发生错误,请重试 + + + 文件 {0} 被其它程序占用,请 重启电脑 或 解除文件占用 后重试。 + \ No newline at end of file diff --git a/YaeAchievement/src/AppConfig.cs b/YaeAchievement/src/AppConfig.cs index fd1f29f..3a7d938 100644 --- a/YaeAchievement/src/AppConfig.cs +++ b/YaeAchievement/src/AppConfig.cs @@ -1,5 +1,5 @@ using System.Text.RegularExpressions; -using YaeAchievement.res; +using Spectre.Console; using YaeAchievement.Utilities; namespace YaeAchievement; @@ -28,11 +28,15 @@ public static partial class AppConfig { .Where(File.Exists) .MaxBy(File.GetLastWriteTime); if (logPath == null) { - throw new ApplicationException(App.ConfigNeedStartGenshin); + AnsiConsole.WriteLine(App.ConfigNeedStartGenshin); + Environment.Exit(-1); } - GamePath = GetGamePathFromLogFile(logPath) - ?? GetGamePathFromLogFile($"{logPath}.last") - ?? throw new ApplicationException(App.ConfigNeedStartGenshin); + var gamePath = GetGamePathFromLogFile(logPath) ?? GetGamePathFromLogFile($"{logPath}.last"); + if (gamePath == null) { + AnsiConsole.WriteLine(App.ConfigNeedStartGenshin); + Environment.Exit(-1); + } + GamePath = gamePath; SentrySdk.AddBreadcrumb(GamePath.EndsWith("YuanShen.exe") ? "CN" : "OS", "GamePath"); } diff --git a/YaeAchievement/src/Export.cs b/YaeAchievement/src/Export.cs index 38ad678..f24f6d8 100644 --- a/YaeAchievement/src/Export.cs +++ b/YaeAchievement/src/Export.cs @@ -1,5 +1,4 @@ using System.ComponentModel; -using System.Diagnostics; using System.Net; using System.Text; using System.Text.Json; @@ -8,7 +7,6 @@ using Microsoft.Win32; using Spectre.Console; using YaeAchievement.Outputs; using YaeAchievement.Parsers; -using YaeAchievement.res; using YaeAchievement.Utilities; // ReSharper disable UnusedMember.Local diff --git a/YaeAchievement/src/GlobalVars.cs b/YaeAchievement/src/GlobalVars.cs index 0d9750b..88a130f 100644 --- a/YaeAchievement/src/GlobalVars.cs +++ b/YaeAchievement/src/GlobalVars.cs @@ -1,4 +1,7 @@ -using System.Diagnostics.CodeAnalysis; +global using System.Diagnostics; +global using YaeAchievement.res; + +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Proto; diff --git a/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs b/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs index 4c174b7..e45562e 100644 --- a/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs +++ b/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs @@ -2,7 +2,6 @@ using System.Text.Json.Serialization; using Google.Protobuf; using Spectre.Console; -using YaeAchievement.res; using YaeAchievement.Utilities; namespace YaeAchievement.Parsers; @@ -31,7 +30,7 @@ public sealed class AchievementAllDataNotify { private static AchievementAllDataNotify? Instance { get; set; } public static bool OnReceive(BinaryReader reader) { - var bytes = reader.ReadBytes(reader.ReadInt32()); + var bytes = reader.ReadBytes(); CacheFile.Write("achievement_data", bytes); Instance = ParseFrom(bytes); return true; diff --git a/YaeAchievement/src/Parsers/PlayerStoreNotify.cs b/YaeAchievement/src/Parsers/PlayerStoreNotify.cs index 08138b1..da93dad 100644 --- a/YaeAchievement/src/Parsers/PlayerStoreNotify.cs +++ b/YaeAchievement/src/Parsers/PlayerStoreNotify.cs @@ -20,7 +20,7 @@ public sealed class PlayerStoreNotify { public static PlayerStoreNotify Instance { get; } = new (); public static bool OnReceive(BinaryReader reader) { - var bytes = reader.ReadBytes(reader.ReadInt32()); + var bytes = reader.ReadBytes(); Instance.ParseFrom(bytes); return true; } diff --git a/YaeAchievement/src/Program.cs b/YaeAchievement/src/Program.cs index df9ba3f..8362c75 100644 --- a/YaeAchievement/src/Program.cs +++ b/YaeAchievement/src/Program.cs @@ -2,7 +2,6 @@ using System.Text; using Spectre.Console; using YaeAchievement.Parsers; -using YaeAchievement.res; using YaeAchievement.Utilities; using static YaeAchievement.Utils; diff --git a/YaeAchievement/src/Utilities/Extensions/Stream.cs b/YaeAchievement/src/Utilities/Extensions/Stream.cs index b515cf1..b1faa23 100644 --- a/YaeAchievement/src/Utilities/Extensions/Stream.cs +++ b/YaeAchievement/src/Utilities/Extensions/Stream.cs @@ -1,10 +1,29 @@ using System.Runtime.CompilerServices; +using Spectre.Console; // ReSharper disable CheckNamespace namespace Google.Protobuf; -public static class CodedInputStreamExtensions { +internal static class BinaryReaderExtensions { + + public static byte[] ReadBytes(this BinaryReader reader) { + try { + var length = reader.ReadInt32(); + if (length is < 0 or > 114514 * 2) { + throw new ArgumentException(nameof(length)); + } + return reader.ReadBytes(length); + } catch (Exception e) when (e is IOException or ArgumentException) { + AnsiConsole.WriteLine(App.StreamReadDataFail); + Environment.Exit(-1); + throw new UnreachableException(); + } + } + +} + +internal static class CodedInputStreamExtensions { [UnsafeAccessor(UnsafeAccessorKind.Method)] private static extern byte[] ReadRawBytes(CodedInputStream stream, int size); diff --git a/YaeAchievement/src/Utilities/GameProcess.cs b/YaeAchievement/src/Utilities/GameProcess.cs index daf72c9..d925586 100644 --- a/YaeAchievement/src/Utilities/GameProcess.cs +++ b/YaeAchievement/src/Utilities/GameProcess.cs @@ -29,7 +29,16 @@ public sealed unsafe class GameProcess { }; var wd = Path.GetDirectoryName(path)!; if (!Native.CreateProcess(path, ref cmdLines, null, null, false, flags, null, wd, si, out var pi)) { - throw new ApplicationException($"CreateProcess fail: {Marshal.GetLastPInvokeErrorMessage()}"); + var argumentData = new Dictionary { + { "path", path }, + { "workdir", wd }, + { "file_exists", File.Exists(path) }, + }; + throw new ApplicationException($"CreateProcess fail: {Marshal.GetLastPInvokeErrorMessage()}") { + Data = { + { "sentry:context:Arguments", argumentData } + } + }; } Id = pi.dwProcessId; Handle = pi.hProcess; diff --git a/YaeAchievement/src/Utilities/SelectionPromptCompat.cs b/YaeAchievement/src/Utilities/SelectionPromptCompat.cs index c34d773..74a7571 100644 --- a/YaeAchievement/src/Utilities/SelectionPromptCompat.cs +++ b/YaeAchievement/src/Utilities/SelectionPromptCompat.cs @@ -1,5 +1,4 @@ using Spectre.Console; -using YaeAchievement.res; namespace YaeAchievement.Utilities; diff --git a/YaeAchievement/src/Utils.cs b/YaeAchievement/src/Utils.cs index b563caa..1735ae7 100644 --- a/YaeAchievement/src/Utils.cs +++ b/YaeAchievement/src/Utils.cs @@ -1,5 +1,4 @@ using System.ComponentModel; -using System.Diagnostics; using System.Globalization; using System.IO.Pipes; using System.Net; @@ -11,7 +10,6 @@ using Windows.Win32.Foundation; using Windows.Win32.System.Console; using Proto; using Spectre.Console; -using YaeAchievement.res; using YaeAchievement.Utilities; namespace YaeAchievement; @@ -109,35 +107,42 @@ public static class Utils { } public static async Task CheckUpdate(bool useLocalLib) { - var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version")); - var versionInfo = UpdateInfo.Parser.ParseFrom(versionData)!; - if (GlobalVars.AppVersionCode < versionInfo.VersionCode) { - AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName); - AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description); - if (versionInfo.EnableAutoUpdate) { - var newBin = await StartSpinnerAsync(App.UpdateDownloading, _ => GetBucketFile(versionInfo.PackageLink)); - var tmpPath = Path.GetTempFileName(); - var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe"); - await using (var dstStream = File.Open($"{GlobalVars.DataPath}/update.exe", FileMode.Create)) { - await using var srcStream = typeof(Program).Assembly.GetManifestResourceStream("updater")!; - await srcStream.CopyToAsync(dstStream); + try { + var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version")); + var versionInfo = UpdateInfo.Parser.ParseFrom(versionData)!; + if (GlobalVars.AppVersionCode < versionInfo.VersionCode) { + AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName); + AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description); + if (versionInfo.EnableAutoUpdate) { + var newBin = await StartSpinnerAsync(App.UpdateDownloading, _ => GetBucketFile(versionInfo.PackageLink)); + var tmpPath = Path.GetTempFileName(); + var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe"); + await using (var dstStream = File.Open($"{GlobalVars.DataPath}/update.exe", FileMode.Create)) { + await using var srcStream = typeof(Program).Assembly.GetManifestResourceStream("updater")!; + await srcStream.CopyToAsync(dstStream); + } + await File.WriteAllBytesAsync(tmpPath, newBin); + ShellOpen(updaterPath, $"{Environment.ProcessId} \"{tmpPath}\""); + await StartSpinnerAsync(App.UpdateChecking, _ => Task.Delay(1919810)); + GlobalVars.PauseOnExit = false; + Environment.Exit(0); + } + AnsiConsole.MarkupLine($"[link]{App.DownloadLink}[/]", versionInfo.PackageLink); + if (versionInfo.ForceUpdate) { + Environment.Exit(0); } - await File.WriteAllBytesAsync(tmpPath, newBin); - ShellOpen(updaterPath, $"{Environment.ProcessId} \"{tmpPath}\""); - await StartSpinnerAsync(App.UpdateChecking, _ => Task.Delay(1919810)); - GlobalVars.PauseOnExit = false; - Environment.Exit(0); } - AnsiConsole.MarkupLine($"[link]{App.DownloadLink}[/]", versionInfo.PackageLink); - if (versionInfo.ForceUpdate) { - Environment.Exit(0); + if (versionInfo.EnableLibDownload && !useLocalLib) { + var data = await GetBucketFile("schicksal/lic.dll"); + await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); // 要求重启电脑 } + _updateInfo = versionInfo; + } catch (IOException e) when ((uint) e.HResult == 0x80070020) { // ERROR_SHARING_VIOLATION + // IO_SharingViolation_File + // The process cannot access the file '{0}' because it is being used by another process. + AnsiConsole.WriteLine("文件 {0} 被其它程序占用,请 重启电脑 或 解除文件占用 后重试。", e.Message[36..^46]); + Environment.Exit(-1); } - if (versionInfo.EnableLibDownload && !useLocalLib) { - var data = await GetBucketFile("schicksal/lic.dll"); - await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); - } - _updateInfo = versionInfo; } // ReSharper disable once UnusedMethodReturnValue.Global