mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-10 16:38:13 +08:00
refactor updater and injector
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="YaeAchievement\YaeAchievement.csproj" Type="Classic C#"/>
|
||||
<Project Path="YaeAchievement\YaeAchievement.csproj" Type="Classic C#" />
|
||||
</Solution>
|
||||
@@ -23,42 +23,36 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.0"/>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.67.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Spectre.Console" Version="0.49.1" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.49.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="res\App.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>App.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Remove="res\Updater.exe" />
|
||||
<None Remove="src\NativeMethods.json" />
|
||||
<None Remove="src\NativeMethods.txt" />
|
||||
<AdditionalFiles Include="src\NativeMethods.json" />
|
||||
<AdditionalFiles Include="src\NativeMethods.txt" />
|
||||
<EmbeddedResource Include="res\Updater.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="res\App.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>App.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Remove="res\updater.exe"/>
|
||||
<EmbeddedResource Include="res\updater.exe" LogicalName="updater"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="res\App.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>App.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="res\App.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>App.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="res/proto/*.proto" ProtoRoot="res/proto" GrpcServices="None" />
|
||||
<Protobuf Include="res/proto/*.proto" ProtoRoot="res/proto" GrpcServices="None"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
10
YaeAchievement/res/App.Designer.cs
generated
10
YaeAchievement/res/App.Designer.cs
generated
@@ -348,16 +348,6 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] Updater {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Updater", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Upload error to appcenter....
|
||||
/// </summary>
|
||||
|
||||
@@ -122,9 +122,6 @@ Input a number (0-8): </value>
|
||||
<value>Network error ({0}: {1})</value>
|
||||
</data>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="Updater" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>Updater.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="GenshinHashError" xml:space="preserve">
|
||||
<value>Please update genshin and retry.</value>
|
||||
</data>
|
||||
|
||||
Binary file not shown.
@@ -1,63 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Memory;
|
||||
using Windows.Win32.System.Threading;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class Injector {
|
||||
|
||||
public static unsafe bool CreateProcess(string path, out HANDLE hProc, out HANDLE hThread, out uint pid) {
|
||||
Span<char> cmdLines = stackalloc char[1]; // "\0"
|
||||
var si = new STARTUPINFOW {
|
||||
cb = unchecked((uint)sizeof(STARTUPINFOW))
|
||||
};
|
||||
var dir = Path.GetDirectoryName(path)!;
|
||||
var result = Native.CreateProcess(
|
||||
path, ref cmdLines, default, default, false,
|
||||
PROCESS_CREATION_FLAGS.CREATE_SUSPENDED, default, dir, in si, out var pi
|
||||
);
|
||||
pid = pi.dwProcessId;
|
||||
hProc = pi.hProcess;
|
||||
hThread = pi.hThread;
|
||||
return result;
|
||||
}
|
||||
|
||||
// todo: refactor
|
||||
public static unsafe int LoadLibraryAndInject(HANDLE hProc, ReadOnlySpan<char> libPath) {
|
||||
fixed (char* lpModelName = "kernel32.dll") {
|
||||
var hKernel = Native.GetModuleHandle(lpModelName);
|
||||
if (hKernel.IsNull) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail");
|
||||
}
|
||||
fixed(byte* lpProcName = "LoadLibraryW"u8) {
|
||||
var pLoadLibrary = Native.GetProcAddress(hKernel, (PCSTR)lpProcName);
|
||||
if (pLoadLibrary.IsNull) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("GetProcAddress fail");
|
||||
}
|
||||
var libPathByteLen = (uint) libPath.Length * 2;
|
||||
var pBase = Native.VirtualAllocEx(hProc, default, libPathByteLen + 2, VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE | VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT, PAGE_PROTECTION_FLAGS.PAGE_READWRITE);
|
||||
if ((nint)pBase == 0) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail");
|
||||
}
|
||||
fixed (void* lpBuffer = libPath) {
|
||||
if (!Native.WriteProcessMemory(hProc, pBase, lpBuffer, libPathByteLen)) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
|
||||
}
|
||||
}
|
||||
var lpStartAddress = (delegate* unmanaged[Stdcall]<void*, uint>)pLoadLibrary.Value; //THREAD_START_ROUTINE
|
||||
var hThread = Native.CreateRemoteThread(hProc, default, 0, lpStartAddress, pBase, 0);
|
||||
if (hThread.IsNull) {
|
||||
var e = new Win32Exception();
|
||||
Native.VirtualFreeEx(hProc, pBase, 0, VIRTUAL_FREE_TYPE.MEM_RELEASE);
|
||||
return e.PrintMsgAndReturnErrCode("CreateRemoteThread fail");
|
||||
}
|
||||
if (Native.WaitForSingleObject(hThread, 2000) == 0) {
|
||||
Native.VirtualFreeEx(hProc, pBase, 0, VIRTUAL_FREE_TYPE.MEM_RELEASE);
|
||||
}
|
||||
return !Native.CloseHandle(hThread) ? new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail") : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ internal static class Program {
|
||||
}
|
||||
}
|
||||
|
||||
StartAndWaitResult(AppConfig.GamePath, new Dictionary<byte, Func<BinaryReader, bool>> {
|
||||
StartAndWaitResult(AppConfig.GamePath, new Dictionary<int, Func<BinaryReader, bool>> {
|
||||
{ 1, AchievementAllDataNotify.OnReceive },
|
||||
{ 2, PlayerStoreNotify.OnReceive },
|
||||
{ 100, PlayerPropNotify.OnReceive },
|
||||
@@ -61,7 +61,9 @@ internal static class Program {
|
||||
}));
|
||||
#endif
|
||||
AchievementAllDataNotify.OnFinish();
|
||||
Environment.Exit(0);
|
||||
});
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
[ModuleInitializer]
|
||||
|
||||
73
YaeAchievement/src/Utilities/GameProcess.cs
Normal file
73
YaeAchievement/src/Utilities/GameProcess.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Threading;
|
||||
|
||||
using static Windows.Win32.System.Memory.VIRTUAL_ALLOCATION_TYPE;
|
||||
using static Windows.Win32.System.Memory.PAGE_PROTECTION_FLAGS;
|
||||
using static Windows.Win32.System.Memory.VIRTUAL_FREE_TYPE;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
namespace YaeAchievement.Utilities;
|
||||
|
||||
public unsafe class GameProcess {
|
||||
|
||||
public uint Id { get; }
|
||||
|
||||
public HANDLE Handle { get; }
|
||||
|
||||
public HANDLE MainThreadHandle { get; }
|
||||
|
||||
public event Action? OnExit;
|
||||
|
||||
public GameProcess(string path) {
|
||||
const PROCESS_CREATION_FLAGS flags = PROCESS_CREATION_FLAGS.CREATE_SUSPENDED;
|
||||
Span<char> cmdLines = stackalloc char[1]; // "\0"
|
||||
var si = new STARTUPINFOW {
|
||||
cb = (uint) sizeof(STARTUPINFOW)
|
||||
};
|
||||
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()}");
|
||||
}
|
||||
Id = pi.dwProcessId;
|
||||
Handle = pi.hProcess;
|
||||
MainThreadHandle = pi.hThread;
|
||||
Task.Run(() => {
|
||||
Native.WaitForSingleObject(Handle, 0xFFFFFFFF); // INFINITE
|
||||
OnExit?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadLibrary(string libPath) {
|
||||
var hKrnl32 = NativeLibrary.Load("kernel32");
|
||||
var mLoadLibraryW = NativeLibrary.GetExport(hKrnl32, "LoadLibraryW");
|
||||
var libPathLen = (uint) libPath.Length * sizeof(char);
|
||||
var lpLibPath = Native.VirtualAllocEx(Handle, default, libPathLen + 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
if (lpLibPath == null) {
|
||||
throw new ApplicationException($"VirtualAllocEx fail: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
fixed (void* lpBuffer = libPath) {
|
||||
if (!Native.WriteProcessMemory(Handle, lpLibPath, lpBuffer, libPathLen)) {
|
||||
throw new ApplicationException($"WriteProcessMemory fail: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
var lpStartAddress = (delegate*unmanaged[Stdcall]<void*, uint>) mLoadLibraryW; // THREAD_START_ROUTINE
|
||||
var hThread = Native.CreateRemoteThread(Handle, default, 0, lpStartAddress, lpLibPath, 0);
|
||||
if (hThread.IsNull) {
|
||||
var error = Marshal.GetLastPInvokeErrorMessage();
|
||||
Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE);
|
||||
throw new ApplicationException($"CreateRemoteThread fail: {error}");
|
||||
}
|
||||
if (Native.WaitForSingleObject(hThread, 2000) == 0) {
|
||||
Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE);
|
||||
}
|
||||
Native.CloseHandle(hThread);
|
||||
}
|
||||
|
||||
public bool ResumeMainThread() => Native.ResumeThread(MainThreadHandle) != 0xFFFFFFFF;
|
||||
|
||||
public bool Terminate(uint exitCode) => Native.TerminateProcess(Handle, exitCode);
|
||||
|
||||
}
|
||||
@@ -94,10 +94,13 @@ public static class Utils {
|
||||
Console.WriteLine(App.UpdateDownloading);
|
||||
var tmpPath = Path.GetTempFileName();
|
||||
await File.WriteAllBytesAsync(tmpPath, await GetBucketFile(info.PackageLink));
|
||||
var updaterArgs = $"{Environment.ProcessId}|{Environment.ProcessPath}|{tmpPath}";
|
||||
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
|
||||
await File.WriteAllBytesAsync(updaterPath, App.Updater);
|
||||
ShellOpen(updaterPath, updaterArgs.ToBytes().ToBase64());
|
||||
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);
|
||||
}
|
||||
ShellOpen(updaterPath, $"{Environment.ProcessId} \"{tmpPath}\"");
|
||||
await Task.Delay(1919810);
|
||||
GlobalVars.PauseOnExit = false;
|
||||
Environment.Exit(0);
|
||||
}
|
||||
@@ -131,26 +134,24 @@ public static class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void CheckGenshinIsRunning() {
|
||||
Process.EnterDebugMode();
|
||||
foreach (var process in Process.GetProcesses()) {
|
||||
if (process.ProcessName is "GenshinImpact" or "YuanShen"
|
||||
&& !process.HasExited
|
||||
&& process.MainWindowHandle != nint.Zero
|
||||
) {
|
||||
Console.WriteLine(App.GenshinIsRunning, process.Id);
|
||||
internal static void CheckGenshinIsRunning() {
|
||||
// QueryProcessEvent?
|
||||
var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
foreach (var path in Directory.EnumerateDirectories($"{appdata}/../LocalLow/miHoYo").Where(p => File.Exists($"{p}/info.txt"))) {
|
||||
try {
|
||||
using var handle = File.OpenHandle($"{path}/output_log.txt", share: FileShare.None);
|
||||
} catch (IOException) {
|
||||
Console.WriteLine(App.GenshinIsRunning, 0);
|
||||
Environment.Exit(301);
|
||||
}
|
||||
}
|
||||
Process.LeaveDebugMode();
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static Process? proc;
|
||||
private static GameProcess? _proc;
|
||||
|
||||
public static void InstallExitHook() {
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
||||
proc?.Kill();
|
||||
_proc?.Terminate(0);
|
||||
if (GlobalVars.PauseOnExit) {
|
||||
Console.WriteLine(App.PressKeyToExit);
|
||||
Console.ReadKey();
|
||||
@@ -179,51 +180,27 @@ public static class Utils {
|
||||
};
|
||||
}
|
||||
|
||||
private static bool _isUnexpectedExit;
|
||||
private static bool _isUnexpectedExit = true;
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
public static Thread StartAndWaitResult(string exePath, Dictionary<byte, Func<BinaryReader, bool>> handlers, Action onFinish) {
|
||||
if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) {
|
||||
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("ICreateProcess fail"));
|
||||
}
|
||||
if (Injector.LoadLibraryAndInject(hProcess,GlobalVars.LibFilePath.AsSpan()) != 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 (_isUnexpectedExit)
|
||||
{
|
||||
proc = null;
|
||||
public static void StartAndWaitResult(string exePath, Dictionary<int, Func<BinaryReader, bool>> handlers, Action onFinish) {
|
||||
_proc = new GameProcess(exePath);
|
||||
_proc.OnExit += () => {
|
||||
if (_isUnexpectedExit) {
|
||||
_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 BinaryReader(server);
|
||||
while (!proc.HasExited) {
|
||||
var type = reader.ReadByte();
|
||||
_proc.LoadLibrary(GlobalVars.LibFilePath);
|
||||
_proc.ResumeMainThread();
|
||||
Console.WriteLine(App.GameLoading, _proc.Id);
|
||||
Task.Run(() => {
|
||||
using var stream = new NamedPipeServerStream(GlobalVars.PipeName);
|
||||
using var reader = new BinaryReader(stream);
|
||||
stream.WaitForConnection();
|
||||
int type;
|
||||
while ((type = stream.ReadByte()) != -1) {
|
||||
if (type == 0xFF) {
|
||||
_isUnexpectedExit = false;
|
||||
onFinish();
|
||||
@@ -236,8 +213,5 @@ public static class Utils {
|
||||
}
|
||||
}
|
||||
});
|
||||
var th = new Thread(ts);
|
||||
th.Start();
|
||||
return th;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user