Files
Yae/src/Utils.cs
2023-03-30 08:54:50 +08:00

319 lines
12 KiB
C#

using Microsoft.Win32;
using System.ComponentModel;
using System.Diagnostics;
using System.IO.Pipes;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using Proto;
using YaeAchievement.AppCenterSDK;
using YaeAchievement.res;
namespace YaeAchievement;
public static class Utils {
public static readonly HttpClient CHttpClient = new (new HttpClientHandler {
Proxy = GlobalVars.DebugProxy ? new WebProxy("http://127.0.0.1:8888") : null,
AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip
}) {
DefaultRequestHeaders = {
UserAgent = {
new ProductInfoHeaderValue("YaeAchievement", GlobalVars.AppVersion.ToString(2))
}
}
};
public static byte[] GetBucketFileAsByteArray(string path, bool cache = true) {
try {
using var msg = new HttpRequestMessage {
Method = HttpMethod.Get,
RequestUri = new Uri($"{GlobalVars.BucketHost}/{path}")
};
var cacheFile = new CacheFile(path);
if (cache && cacheFile.Exists()) {
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cacheFile.Read().Etag}");
}
using var response = CHttpClient.Send(msg);
if (cache && response.StatusCode == HttpStatusCode.NotModified) {
return cacheFile.Read().Content.ToByteArray();
}
response.EnsureSuccessStatusCode();
var responseBytes = response.Content.ReadAsByteArrayAsync().Result;
if (cache) {
var etag = response.Headers.ETag!.Tag;
cacheFile.Write(responseBytes, etag);
}
return responseBytes;
} catch (Exception e) {
Console.WriteLine(App.NetworkError, e.Message);
Environment.Exit(-1);
return null!;
}
}
public static T? GetOrNull<T>(this T[] array, uint index) where T : class {
return array.Length > index ? array[index] : null;
}
public static uint? ToUIntOrNull(string? value) {
return value != null ? uint.TryParse(value, out var result) ? result : null : null;
}
public static bool ToBooleanOrFalse(string? value) {
return value != null && bool.TryParse(value, out var result) && result;
}
public static unsafe void CopyToClipboard(string text) {
if (Native.OpenClipboard(HWND.Null))
{
Native.EmptyClipboard();
HANDLE hGlobal = (HANDLE)Marshal.AllocHGlobal((text.Length + 1) * 2);
IntPtr hPtr = (IntPtr)Native.GlobalLock(hGlobal);
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
Native.GlobalUnlock(hPtr);
Native.SetClipboardData(13, hGlobal);
Marshal.FreeHGlobal(hGlobal);
Native.CloseClipboard();
}
else
{
throw new Win32Exception();
}
}
// ReSharper disable once NotAccessedField.Local
private static UpdateInfo _updateInfo = null!;
public static void CheckUpdate(bool useLocalLib) {
var info = UpdateInfo.Parser.ParseFrom(GetBucketFileAsByteArray("schicksal/version"))!;
if (GlobalVars.AppVersionCode != info.VersionCode) {
Console.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, info.VersionName);
Console.WriteLine(App.UpdateDescription, info.Description);
if (info.EnableAutoDownload) {
Console.WriteLine(App.UpdateDownloading);
var tmpPath = Path.GetTempFileName();
File.WriteAllBytes(tmpPath, GetBucketFileAsByteArray(info.PackageLink));
var updaterArgs = $"{Environment.ProcessId}|{Environment.ProcessPath}|{tmpPath}";
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
File.WriteAllBytes(updaterPath, App.Updater);
ShellOpen(updaterPath, updaterArgs.ToBytes().ToBase64());
GlobalVars.PauseOnExit = false;
Environment.Exit(0);
}
Console.WriteLine(App.DownloadLink, info.PackageLink);
if (info.ForceUpdate) {
Environment.Exit(0);
}
}
if (useLocalLib) {
Console.WriteLine(@"[DEBUG] Use local native lib.");
File.Copy(Path.Combine(GlobalVars.AppPath, "YaeAchievementLib.dll"), GlobalVars.LibFilePath, true);
} else if (info.EnableLibDownload) {
File.WriteAllBytes(GlobalVars.LibFilePath, GetBucketFileAsByteArray("schicksal/lib.dll"));
}
_updateInfo = info;
}
public static void CheckSelfIsRunning() {
Process.EnterDebugMode();
var cur = Process.GetCurrentProcess();
foreach (var process in Process.GetProcesses().Where(process => process.Id != cur.Id)) {
if (process.ProcessName == cur.ProcessName) {
Console.WriteLine(App.AnotherInstance);
Environment.Exit(302);
}
}
Process.LeaveDebugMode();
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static bool ShellOpen(string path, string? args = null) {
try {
var startInfo = new ProcessStartInfo {
FileName = path,
UseShellExecute = true
};
if (args != null) {
startInfo.Arguments = args;
}
return new Process {
StartInfo = startInfo
}.Start();
} catch (Exception) {
return false;
}
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static unsafe bool TryDisableQuickEdit() {
var handle = Native.GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
CONSOLE_MODE mode = default;
return Native.GetConsoleMode(handle, &mode) && Native.SetConsoleMode(handle, mode & ~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE);
}
public static void CheckGenshinIsRunning() {
Process.EnterDebugMode();
foreach (var process in Process.GetProcesses()) {
if (process.ProcessName is "GenshinImpact" or "YuanShen"
&& !process.HasExited
&& process.MainWindowHandle != IntPtr.Zero
) {
Console.WriteLine(App.GenshinIsRunning, process.Id);
Environment.Exit(301);
}
}
Process.LeaveDebugMode();
}
// ReSharper disable once InconsistentNaming
private static Process? proc;
public static void InstallExitHook() {
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
proc?.Kill();
if (GlobalVars.PauseOnExit) {
Console.WriteLine(App.PressKeyToExit);
Console.ReadKey();
}
};
}
public static void InstallExceptionHook() {
AppDomain.CurrentDomain.UnhandledException += (_, e) => {
var ex = e.ExceptionObject;
switch (ex) {
case ApplicationException ex1:
Console.WriteLine(ex1.Message);
break;
case SocketException ex2:
Console.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
break;
case HttpRequestException ex3:
Console.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
break;
default:
Console.WriteLine(ex.ToString());
Console.WriteLine(App.UploadError);
AppCenter.TrackCrash((Exception) e.ExceptionObject);
AppCenter.Upload();
break;
}
Environment.Exit(-1);
};
}
private static bool CheckGenshinIsLatestVersion(string path) {
#if DEBUG
return true;
#else
var hash = File.ReadAllBytes(path).MD5Hash();
return File.Exists(path) && (hash == _updateInfo.CurrentCNGameHash || hash == _updateInfo.CurrentOSGameHash);
#endif
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static Thread StartAndWaitResult(string exePath, Func<string, bool> onReceive) {
if (!CheckGenshinIsLatestVersion(exePath)) {
Console.WriteLine(App.GenshinHashError);
Environment.Exit(0);
}
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
try {
File.Delete(GlobalVars.LibFilePath);
} catch (Exception) { /* ignored */ }
};
if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) {
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("ICreateProcess fail"));
}
if (Injector.LoadLibraryAndInject(hProcess,Encoding.UTF8.GetBytes(GlobalVars.LibFilePath)) != 0)
{
if (!Native.TerminateProcess(hProcess, 0))
{
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail"));
}
}
Console.WriteLine(App.GameLoading, pid);
proc = Process.GetProcessById(Convert.ToInt32(pid));
proc.EnableRaisingEvents = true;
proc.Exited += (_, _) => {
if (GlobalVars.UnexpectedExit)
{
proc = null;
Console.WriteLine(App.GameProcessExit);
Environment.Exit(114514);
}
};
if (Native.ResumeThread(hThread) == 0xFFFFFFFF)
{
var e = new Win32Exception();
if (!Native.TerminateProcess(hProcess, 0))
{
new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail");
}
Environment.Exit(e.PrintMsgAndReturnErrCode("ResumeThread fail"));
}
if (!Native.CloseHandle(hProcess))
{
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail"));
}
var ts = new ThreadStart(() => {
var server = new NamedPipeServerStream(GlobalVars.PipeName);
server.WaitForConnection();
using var reader = new StreamReader(server);
while (!proc.HasExited) {
var line = reader.ReadLine();
if (line?.Length > 0) {
if (onReceive(line)) {
break;
}
server.Disconnect();
server.WaitForConnection();
}
}
});
var th = new Thread(ts);
th.Start();
return th;
}
#pragma warning disable CA1416
public static async Task CheckVcRuntime() {
using var root = Registry.LocalMachine;
using var sub = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")!;
var installed = sub.GetSubKeyNames()
.Select(subKeyName => {
try {
return sub.OpenSubKey(subKeyName);
} catch (Exception) {
return null;
}
})
.Select(item => item?.GetValue("DisplayName") as string ?? string.Empty)
.Any(name => name.Contains("Microsoft Visual C++ 2022 X64 "));
if (!installed) {
Console.WriteLine(App.VcRuntimeDownload);
var pkgPath = Path.Combine(GlobalVars.DataPath, "vc_redist.x64.exe");
var bytes = await CHttpClient.GetByteArrayAsync("https://aka.ms/vs/17/release/vc_redist.x64.exe");
await File.WriteAllBytesAsync(pkgPath, bytes);
Console.WriteLine(App.VcRuntimeInstalling);
using var process = new Process {
StartInfo = {
FileName = pkgPath,
Arguments = "/install /passive /norestart"
}
};
process.Start();
await process.WaitForExitAsync();
File.Delete(pkgPath);
}
}
}