refactor updater and injector

This commit is contained in:
HolographicHat
2025-04-08 23:15:53 +08:00
parent f718687b3f
commit 4ff2b454f3
11 changed files with 123 additions and 156 deletions

View File

@@ -1,3 +1,3 @@
<Solution>
<Project Path="YaeAchievement\YaeAchievement.csproj" Type="Classic C#"/>
<Project Path="YaeAchievement\YaeAchievement.csproj" Type="Classic C#" />
</Solution>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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.

View File

@@ -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;
}
}
}
}

View File

@@ -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]

View 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);
}

View File

@@ -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;
}
}