optimize GetGamePath

This commit is contained in:
HolographicHat
2025-06-11 01:47:34 +08:00
parent 62c08f54ab
commit 9eb8955fda
11 changed files with 128 additions and 69 deletions

View File

@@ -21,3 +21,6 @@ WaitForSingleObject
WriteProcessMemory
GetCurrentConsoleFontEx
OpenProcess
GetModuleFileNameEx

View File

@@ -105,7 +105,7 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to You need to login genshin impact before exporting..
/// Looks up a localized string similar to Please launch GenshinImpact to continue..
/// </summary>
internal static string ConfigNeedStartGenshin {
get {

View File

@@ -61,7 +61,7 @@
<value>Reward not taken</value>
</data>
<data name="ConfigNeedStartGenshin" xml:space="preserve">
<value>You need to login genshin impact before exporting.</value>
<value>Please launch GenshinImpact to continue.</value>
</data>
<data name="DownloadLink" xml:space="preserve">
<value>Download: {0}</value>

View File

@@ -54,7 +54,7 @@
<value>已完成</value>
</data>
<data name="ConfigNeedStartGenshin" xml:space="preserve">
<value>在导出前你需要先完成一次登入流程.</value>
<value>请打开 原神 后继续操作</value>
</data>
<data name="DownloadLink" xml:space="preserve">
<value>下载地址: {0}</value>

View File

@@ -1,4 +1,7 @@
using System.Text.RegularExpressions;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Spectre.Console;
using YaeAchievement.Utilities;
@@ -13,31 +16,66 @@ public static partial class AppConfig {
internal static void Load(string argumentPath) {
if (argumentPath != "auto" && File.Exists(argumentPath)) {
GamePath = argumentPath;
return;
} else if (TryReadGamePathFromCache(out var cachedPath)) {
GamePath = cachedPath;
} else if (TryReadGamePathFromUnityLog(out var loggedPath)) {
GamePath = loggedPath;
} else {
GamePath = ReadGamePathFromProcess();
}
if (CacheFile.TryRead("genshin_impact_game_path", out var cache)) {
var path = cache.Content.ToStringUtf8();
if (path != null && File.Exists(path)) {
GamePath = path;
return;
Span<byte> buffer = stackalloc byte[0x10000];
using var stream = File.OpenRead(GamePath);
if (stream.Read(buffer) == buffer.Length) {
var hash = Convert.ToHexString(MD5.HashData(buffer));
CacheFile.Write("genshin_impact_game_path_v2", Encoding.UTF8.GetBytes($"{GamePath}\u1145{hash}"));
}
SentrySdk.AddBreadcrumb(GamePath.EndsWith("YuanShen.exe") ? "CN" : "OS", "GamePath");
return;
static bool TryReadGamePathFromCache([NotNullWhen(true)] out string? path) {
path = null;
try {
if (!CacheFile.TryRead("genshin_impact_game_path_v2", out var cacheFile)) {
return false;
}
var cacheData = cacheFile.Content.ToStringUtf8().Split("\u1145");
Span<byte> buffer = stackalloc byte[0x10000];
using var stream = File.OpenRead(cacheData[0]);
if (stream.Read(buffer) != buffer.Length || Convert.ToHexString(MD5.HashData(buffer)) != cacheData[1]) {
return false;
}
path = cacheData[0];
return true;
} catch (Exception) {
return false;
}
}
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var logPath = ProductNames
.Select(name => $"{appDataPath}/../LocalLow/miHoYo/{name}/output_log.txt")
.Where(File.Exists)
.MaxBy(File.GetLastWriteTime);
if (logPath == null) {
AnsiConsole.WriteLine(App.ConfigNeedStartGenshin);
Environment.Exit(-1);
static bool TryReadGamePathFromUnityLog([NotNullWhen(true)] out string? path) {
path = null;
try {
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var logPath = ProductNames
.Select(name => $"{appDataPath}/../LocalLow/miHoYo/{name}/output_log.txt")
.Where(File.Exists)
.MaxBy(File.GetLastWriteTime);
if (logPath == null) {
return false;
}
return (path = GetGamePathFromLogFile(logPath) ?? GetGamePathFromLogFile($"{logPath}.last")) != null;
} catch (Exception) {
return false;
}
}
var gamePath = GetGamePathFromLogFile(logPath) ?? GetGamePathFromLogFile($"{logPath}.last");
if (gamePath == null) {
AnsiConsole.WriteLine(App.ConfigNeedStartGenshin);
Environment.Exit(-1);
static string ReadGamePathFromProcess() {
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).Start(App.ConfigNeedStartGenshin, _ => {
Process? proc;
while ((proc = Utils.GetGameProcess()) == null) {
Thread.Sleep(250);
}
var fileName = proc.GetFileName()!;
proc.Kill();
return fileName;
});
}
GamePath = gamePath;
SentrySdk.AddBreadcrumb(GamePath.EndsWith("YuanShen.exe") ? "CN" : "OS", "GamePath");
}
private static string? GetGamePathFromLogFile(string path) {

View File

@@ -42,13 +42,16 @@ internal static class Program {
InstallExitHook();
InstallExceptionHook();
CheckGenshinIsRunning();
if (GetGameProcess() != null) {
AnsiConsole.WriteLine(App.GenshinIsRunning, 0);
Environment.Exit(-1);
}
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2)));
AppConfig.Load(args.GetOrNull(0) ?? "auto");
Export.ExportTo = ToIntOrDefault(args.GetOrNull(1), 114514);
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2)));
AchievementAllDataNotify? data = null;
try {
if (CacheFile.TryRead("achievement_data", out var cache)) {

View File

@@ -1,16 +0,0 @@
// ReSharper disable once CheckNamespace
namespace System.Collections.Generic;
public static class Collection {
public static IDictionary<TKey, TValue> RemoveValues<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary,
params TKey[] keys
) {
foreach (var key in keys) {
dictionary.Remove(key);
}
return dictionary;
}
}

View File

@@ -1,14 +1,31 @@
// ReSharper disable once CheckNamespace
using System.ComponentModel;
namespace System.Linq;
namespace System.Collections.Generic {
public static class Enumerable {
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class CollectionExtensions {
public static IEnumerable<IGrouping<TKey, TKey>> GroupKeys<TKey, TValue>(
this IEnumerable<Dictionary<TKey, TValue>> source,
Func<TValue, bool> condition
) where TKey : notnull {
return source.SelectMany(dict => dict.Where(pair => condition(pair.Value)).Select(pair => pair.Key)).GroupBy(x => x);
public static IDictionary<TKey, TValue> RemoveValues<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary, params TKey[] keys
) {
foreach (var key in keys) {
dictionary.Remove(key);
}
return dictionary;
}
}
}
namespace System.Linq {
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class EnumerableExtensions {
public static IEnumerable<IGrouping<TKey, TKey>> GroupKeys<TKey, TValue>(
this IEnumerable<Dictionary<TKey, TValue>> source,
Func<TValue, bool> condition
) where TKey : notnull => source
.SelectMany(dict => dict.Where(pair => condition(pair.Value)).Select(pair => pair.Key))
.GroupBy(x => x);
}
}

View File

@@ -0,0 +1,24 @@
using System.ComponentModel;
using Windows.Win32;
using Windows.Win32.Foundation;
using static Windows.Win32.System.Threading.PROCESS_ACCESS_RIGHTS;
// ReSharper disable CheckNamespace
namespace System.Diagnostics;
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class ProcessExtensions {
public static unsafe string? GetFileName(this Process process) {
using var hProc = Native.OpenProcess_SafeHandle(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (uint) process.Id);
if (hProc.IsInvalid) {
return null;
}
var sProcPath = stackalloc char[32767];
return Native.GetModuleFileNameEx((HANDLE) hProc.DangerousGetHandle(), HMODULE.Null, sProcPath, 32767) == 0
? null
: new string(sProcPath);
}
}

View File

@@ -1,10 +1,12 @@
using System.Runtime.CompilerServices;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Spectre.Console;
// ReSharper disable CheckNamespace
namespace Google.Protobuf;
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class BinaryReaderExtensions {
public static byte[] ReadBytes(this BinaryReader reader) {
@@ -23,6 +25,7 @@ internal static class BinaryReaderExtensions {
}
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class CodedInputStreamExtensions {
[UnsafeAccessor(UnsafeAccessorKind.Method)]

View File

@@ -163,22 +163,9 @@ public static class Utils {
}
}
internal static void CheckGenshinIsRunning() {
// QueryProcessEvent?
var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var dataPath = $"{appdata}/../LocalLow/miHoYo";
if (!Directory.Exists(dataPath)) {
return;
}
foreach (var path in Directory.EnumerateDirectories(dataPath).Where(p => File.Exists($"{p}/info.txt"))) {
try {
using var handle = File.OpenHandle($"{path}/output_log.txt", share: FileShare.None, mode: FileMode.OpenOrCreate);
} catch (IOException) {
AnsiConsole.WriteLine(App.GenshinIsRunning, 0);
Environment.Exit(301);
}
}
}
internal static Process? GetGameProcess() => Process.GetProcessesByName("YuanShen")
.Concat(Process.GetProcessesByName("GenshinImpact"))
.FirstOrDefault(p => File.Exists($"{p.GetFileName()}/../HoYoKProtect.sys"));
private static GameProcess? _proc;