22 Commits
5.7.0 ... 5.7.3

Author SHA1 Message Date
HolographicHat
ccff7a1702 [skip ci] bump lib version 2025-10-23 08:05:27 +08:00
HolographicHat
79122d6ba7 bump version 2025-10-23 08:02:06 +08:00
HolographicHat
1218d1cbc4 fix #146, #147 2025-10-23 08:01:43 +08:00
HolographicHat
19f84bdb86 bump version 2025-10-22 06:23:04 +08:00
HolographicHat
9d22fdf6f9 implement WhenFirstSuccessful method for improved task handling 2025-10-22 06:21:22 +08:00
HolographicHat
27fa0d9c84 move srcgen project 2025-10-22 01:46:58 +08:00
HolographicHat
e9baf8f211 [skip ci] update ci 2025-10-18 20:40:18 +08:00
HolographicHat
b960165b7e Merge pull request #144 from Lightczx/master 2025-10-14 16:17:21 +08:00
DismissedLight
a4c2027ada code style 2025-10-14 13:44:51 +08:00
DismissedLight
f49477c49a Generated MinHook.Attach methods 2025-10-14 11:30:58 +08:00
HolographicHat
67c2fb3bda bump lib version 2025-10-10 18:20:13 +08:00
HolographicHat
be3440695d auto enter gate (close #143) 2025-09-16 11:42:49 +08:00
HolographicHat
f8b8a5a9e1 #142 2025-08-30 11:39:41 +08:00
HolographicHat
49f8679996 bump version 2025-08-05 16:46:56 +08:00
HolographicHat
222a26233e mirror 2025-08-05 01:45:02 +08:00
HolographicHat
1130f442fc update native lib 2025-08-05 00:10:13 +08:00
HolographicHat
b80987f574 [skip ci] update ci 2025-08-04 22:38:29 +08:00
HolographicHat
c96395e1a2 call awake in wndhook 2025-08-04 22:27:48 +08:00
HolographicHat
3f42156b20 ReadAtLeast 2025-08-04 16:19:33 +08:00
HolographicHat
45638b7327 update ci 2025-08-03 14:13:08 +08:00
HolographicHat
d514f3b5e7 [skip ci] update ci 2025-08-03 13:51:55 +08:00
HolographicHat
3fe54d908e add wndhook 2025-08-03 13:46:09 +08:00
20 changed files with 526 additions and 169 deletions

View File

@@ -1,6 +1,7 @@
name: .NET Build name: .NET Build
on: on:
workflow_dispatch:
push: push:
branches: [ "master" ] branches: [ "master" ]
pull_request: pull_request:
@@ -13,7 +14,8 @@ jobs:
run: run:
working-directory: ./YaeAchievement working-directory: ./YaeAchievement
steps: steps:
- uses: actions/checkout@v4 - name: Checkout Repo
uses: actions/checkout@v4
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v4 uses: actions/setup-dotnet@v4
with: with:
@@ -27,12 +29,12 @@ jobs:
- name: Upload-AOT - name: Upload-AOT
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Artifacts-AOT name: aot
path: publish\publish path: YaeAchievement\publish\publish
- name: Publish-NoAOT - name: Publish-NoAOT
run: dotnet publish --property:OutputPath=.\naot-publish\ --property:PublishAot=false --property:PublishSingleFile=true --property:PublishTrimmed=true run: dotnet publish --property:OutputPath=.\naot-publish\ --property:PublishAot=false --property:PublishSingleFile=true --property:PublishTrimmed=true
- name: Upload-NoAOT - name: Upload-NoAOT
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Artifacts-NoAOT name: normal
path: naot-publish\publish path: YaeAchievement\naot-publish\publish

View File

@@ -2,29 +2,35 @@ name: YaeLib NuGet Publish
on: on:
workflow_dispatch: workflow_dispatch:
release: inputs:
types: [released] confirm_version:
description: 'Version already increased?'
required: true
type: boolean
perform_publish:
description: 'Publish to nuget?'
required: true
default: true
type: boolean
jobs: jobs:
publish: publish:
runs-on: windows-latest runs-on: windows-latest
defaults:
run:
working-directory: YaeAchievementLib
steps: steps:
- name: Checkout Repo - uses: actions/checkout@v4
uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4
with:
- name: Setup MSBuild dotnet-version: 9.0.x
uses: microsoft/setup-msbuild@v2 - name: Build native library
run: dotnet publish
- name: Restore NuGet Packages
run: nuget restore lib\YaeAchievementLib.sln
- name: Build
continue-on-error: true
run: msbuild lib\YaeAchievementLib.sln /p:Configuration=Release
- name: Pack
run: nuget pack lib\YaeAchievementLib.nuspec
- name: Publish to NuGet - name: Publish to NuGet
run: nuget push *.nupkg ${{ secrets.NUGET_API_KEY }} -src https://api.nuget.org/v3/index.json if: ${{ inputs.perform_publish }}
run: nuget push bin\Release\*.nupkg ${{ secrets.NUGET_API_KEY }} -src https://api.nuget.org/v3/index.json
- name: Upload nuget package
uses: actions/upload-artifact@v4
with:
name: nupkg
path: YaeAchievementLib\bin\Release\*.nupkg

View File

@@ -0,0 +1,104 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace YaeAchievement.SourceGeneration;
[Generator(LanguageNames.CSharp)]
public sealed class MinHookAttachGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
IncrementalValueProvider<ImmutableArray<AttachInfo>> provider = context.SyntaxProvider.CreateSyntaxProvider(Filter, Transform).Collect();
context.RegisterSourceOutput(provider, Generate);
}
private static bool Filter(SyntaxNode node, CancellationToken token)
{
return node is InvocationExpressionSyntax
{
Expression: MemberAccessExpressionSyntax
{
Expression: IdentifierNameSyntax { Identifier.Text: "MinHook" },
Name.Identifier.Text: "Attach"
}
};
}
private static AttachInfo Transform(GeneratorSyntaxContext context, CancellationToken token)
{
InvocationExpressionSyntax invocation = (InvocationExpressionSyntax)context.Node;
SeparatedSyntaxList<ArgumentSyntax> args = invocation.ArgumentList.Arguments;
if (args.Count is not 3)
{
return null;
}
string type = context.SemanticModel.GetTypeInfo(args[0].Expression).Type?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
if (string.IsNullOrEmpty(type))
{
return null;
}
return new()
{
MinimallyQualifiedType = type,
};
}
private static void Generate(SourceProductionContext context, ImmutableArray<AttachInfo> infoArray)
{
CompilationUnitSyntax unit = CompilationUnit()
.WithMembers(List<MemberDeclarationSyntax>(
[
FileScopedNamespaceDeclaration(ParseName("Yae.Utilities")),
ClassDeclaration("MinHook")
.WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.PartialKeyword)))
.WithMembers(List(GenerateMethods(infoArray)))
]));
context.AddSource("MinHook.Attach.g.cs", unit.NormalizeWhitespace().ToFullString());
}
private static IEnumerable<MemberDeclarationSyntax> GenerateMethods(ImmutableArray<AttachInfo> infoArray)
{
foreach (AttachInfo info in infoArray)
{
TypeSyntax type = ParseTypeName(info.MinimallyQualifiedType);
yield return MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier("Attach"))
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.UnsafeKeyword)))
.WithParameterList(ParameterList(SeparatedList(
[
Parameter(Identifier("origin")).WithType(type),
Parameter(Identifier("handler")).WithType(type),
Parameter(Identifier("trampoline")).WithType(type).WithModifiers(TokenList(Token(SyntaxKind.OutKeyword)))
])))
.WithBody(Block(List<StatementSyntax>(
[
ExpressionStatement(InvocationExpression(IdentifierName("Attach"))
.WithArgumentList(ArgumentList(SeparatedList(
[
Argument(CastExpression(IdentifierName("nint"), IdentifierName("origin"))),
Argument(CastExpression(IdentifierName("nint"), IdentifierName("handler"))),
Argument(DeclarationExpression(IdentifierName("nint"), SingleVariableDesignation(Identifier("trampoline1"))))
.WithRefKindKeyword(Token(SyntaxKind.OutKeyword))
])))),
ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName("trampoline"),
CastExpression(type, IdentifierName("trampoline1"))))
])));
}
}
private record AttachInfo
{
public required string MinimallyQualifiedType { get; init; }
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageReference Include="PolySharp" Version="1.15.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

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

View File

@@ -2,5 +2,5 @@
"$schema": "https://aka.ms/CsWin32.schema.json", "$schema": "https://aka.ms/CsWin32.schema.json",
"className": "Native", "className": "Native",
"allowMarshaling": false, "allowMarshaling": false,
"public": true "public": false
} }

View File

@@ -1,28 +1,30 @@
CloseClipboard // kernel32
CreateProcess
CreateRemoteThread
EmptyClipboard
GetConsoleMode
GetDC
GetDeviceCaps
GetModuleHandle
GetProcAddress
GetStdHandle
GlobalLock GlobalLock
OpenProcess
GetStdHandle
GlobalUnlock GlobalUnlock
OpenClipboard
ResumeThread ResumeThread
SetClipboardData Module32Next
Module32First
CreateProcess
LoadLibraryEx
VirtualFreeEx
VirtualAllocEx
GetProcAddress
GetConsoleMode
SetConsoleMode SetConsoleMode
TerminateProcess TerminateProcess
VirtualAllocEx CreateRemoteThread
VirtualFreeEx
WaitForSingleObject
WriteProcessMemory WriteProcessMemory
WaitForSingleObject
GetCurrentConsoleFontEx GetCurrentConsoleFontEx
CreateToolhelp32Snapshot
OpenProcess // psapi
GetModuleFileNameEx GetModuleFileNameEx
LoadLibraryEx // user32
OpenClipboard
CloseClipboard
EmptyClipboard
SetClipboardData

View File

@@ -7,8 +7,8 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net9.0-windows</TargetFramework> <TargetFramework>net9.0-windows</TargetFramework>
<FileVersion>5.7.0</FileVersion> <FileVersion>5.7.2</FileVersion>
<AssemblyVersion>5.7.0</AssemblyVersion> <AssemblyVersion>5.7.2</AssemblyVersion>
<ApplicationIcon>res\icon.ico</ApplicationIcon> <ApplicationIcon>res\icon.ico</ApplicationIcon>
<ApplicationManifest>res\app.manifest</ApplicationManifest> <ApplicationManifest>res\app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
@@ -24,7 +24,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<!-- [Update to 3.31.0 breaks AOT build](https://github.com/protocolbuffers/protobuf/issues/21824) --> <!-- [Update to 3.31.0 breaks AOT build](https://github.com/protocolbuffers/protobuf/issues/21824) -->
<PackageReference Include="Google.Protobuf" Version="3.30.2" /> <PackageReference Include="Google.Protobuf" Version="3.30.2"/>
<PackageReference Include="Grpc.Tools" Version="2.72.0"> <PackageReference Include="Grpc.Tools" Version="2.72.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -19,8 +19,15 @@ message AchievementItem {
message MethodRvaConfig { message MethodRvaConfig {
uint32 do_cmd = 1; uint32 do_cmd = 1;
uint32 to_uint16 = 2;
uint32 update_normal_prop = 3; uint32 update_normal_prop = 3;
uint32 new_string = 4;
uint32 find_game_object = 5;
uint32 event_system_update = 6;
uint32 simulate_pointer_click = 7;
uint32 to_int32 = 8;
uint32 tcp_state_ptr = 9;
uint32 shared_info_ptr = 10;
uint32 decompress = 11;
} }
message NativeLibConfig { message NativeLibConfig {

View File

@@ -2,6 +2,12 @@ syntax = "proto3";
option csharp_namespace = "Proto"; option csharp_namespace = "Proto";
message CdnFileInfo {
uint32 size = 1;
uint32 hash = 2;
repeated string urls = 4;
}
message UpdateInfo { message UpdateInfo {
uint32 version_code = 1; uint32 version_code = 1;
string version_name = 2; string version_name = 2;
@@ -10,4 +16,5 @@ message UpdateInfo {
bool force_update = 5; bool force_update = 5;
bool enable_lib_download = 6; bool enable_lib_download = 6;
bool enable_auto_update = 7; bool enable_auto_update = 7;
map<string, CdnFileInfo> cdn_files = 8;
} }

View File

@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Spectre.Console; using Spectre.Console;
@@ -23,12 +22,7 @@ public static partial class AppConfig {
} else { } else {
GamePath = ReadGamePathFromProcess(); GamePath = ReadGamePathFromProcess();
} }
Span<byte> buffer = stackalloc byte[0x10000]; CacheFile.Write("genshin_impact_game_path_v2", Encoding.UTF8.GetBytes($"{GamePath}\u1145{Utils.GetGameHash(GamePath)}"));
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"); SentrySdk.AddBreadcrumb(GamePath.EndsWith("YuanShen.exe") ? "CN" : "OS", "GamePath");
return; return;
static bool TryReadGamePathFromCache([NotNullWhen(true)] out string? path) { static bool TryReadGamePathFromCache([NotNullWhen(true)] out string? path) {
@@ -38,9 +32,7 @@ public static partial class AppConfig {
return false; return false;
} }
var cacheData = cacheFile.Content.ToStringUtf8().Split("\u1145"); var cacheData = cacheFile.Content.ToStringUtf8().Split("\u1145");
Span<byte> buffer = stackalloc byte[0x10000]; if (Utils.GetGameHash(cacheData[0]) != uint.Parse(cacheData[1])) {
using var stream = File.OpenRead(cacheData[0]);
if (stream.Read(buffer) != buffer.Length || Convert.ToHexString(MD5.HashData(buffer)) != cacheData[1]) {
return false; return false;
} }
path = cacheData[0]; path = cacheData[0];

View File

@@ -18,8 +18,8 @@ public static class GlobalVars {
public static readonly string CachePath = Path.Combine(DataPath, "cache"); public static readonly string CachePath = Path.Combine(DataPath, "cache");
public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll"); public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll");
public const uint AppVersionCode = 240; public const uint AppVersionCode = 243;
public const string AppVersionName = "5.7"; public const string AppVersionName = "5.7.3";
public const string PipeName = "YaeAchievementPipe"; public const string PipeName = "YaeAchievementPipe";

View File

@@ -7,8 +7,6 @@ using static YaeAchievement.Utils;
namespace YaeAchievement; namespace YaeAchievement;
// TODO: WndHook
internal static class Program { internal static class Program {
public static async Task Main(string[] args) { public static async Task Main(string[] args) {
@@ -47,10 +45,10 @@ internal static class Program {
Environment.Exit(-1); Environment.Exit(-1);
} }
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2))); await CheckUpdate(ToBooleanOrDefault(args.ElementAtOrDefault(2)));
AppConfig.Load(args.GetOrNull(0) ?? "auto"); AppConfig.Load(args.ElementAtOrDefault(0) ?? "auto");
Export.ExportTo = ToIntOrDefault(args.GetOrNull(1), 114514); Export.ExportTo = ToIntOrDefault(args.ElementAtOrDefault(1), 114514);
AchievementAllDataNotify? data = null; AchievementAllDataNotify? data = null;
try { try {

View File

@@ -2,6 +2,7 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Windows.Win32; using Windows.Win32;
using Windows.Win32.Foundation; using Windows.Win32.Foundation;
using Windows.Win32.System.Diagnostics.ToolHelp;
using Windows.Win32.System.LibraryLoader; using Windows.Win32.System.LibraryLoader;
using Windows.Win32.System.Threading; using Windows.Win32.System.Threading;
using Spectre.Console; using Spectre.Console;
@@ -13,7 +14,7 @@ using static Windows.Win32.System.Memory.VIRTUAL_FREE_TYPE;
namespace YaeAchievement.Utilities; namespace YaeAchievement.Utilities;
public sealed unsafe class GameProcess { internal sealed unsafe class GameProcess {
public uint Id { get; } public uint Id { get; }
@@ -73,7 +74,29 @@ public sealed unsafe class GameProcess {
if (Native.WaitForSingleObject(hThread, 2000) == 0) { if (Native.WaitForSingleObject(hThread, 2000) == 0) {
Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE); Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE);
} }
var libHandle = Native.LoadLibraryEx(libPath, LOAD_LIBRARY_FLAGS.DONT_RESOLVE_DLL_REFERENCES); // Get lib base address in target process
byte* baseAddress = null;
using (var hSnap = Native.CreateToolhelp32Snapshot_SafeHandle(CREATE_TOOLHELP_SNAPSHOT_FLAGS.TH32CS_SNAPMODULE, Id)) {
if (hSnap.IsInvalid) {
throw new Win32Exception { Data = { { "api", "CreateToolhelp32Snapshot" } } };
}
var moduleEntry = new MODULEENTRY32 {
dwSize = (uint) sizeof(MODULEENTRY32)
};
if (Native.Module32First(hSnap, ref moduleEntry)) {
do {
if (new string((sbyte*) &moduleEntry.szExePath._0) == libPath) {
baseAddress = moduleEntry.modBaseAddr;
break;
}
} while (Native.Module32Next(hSnap, ref moduleEntry));
}
}
if (baseAddress == null) {
throw new InvalidOperationException("No matching module found in target process.");
}
//
using var libHandle = Native.LoadLibraryEx(libPath, LOAD_LIBRARY_FLAGS.DONT_RESOLVE_DLL_REFERENCES);
if (libHandle.IsInvalid) { if (libHandle.IsInvalid) {
throw new Win32Exception { Data = { { "api", "LoadLibraryEx" } } }; throw new Win32Exception { Data = { { "api", "LoadLibraryEx" } } };
} }
@@ -81,7 +104,9 @@ public sealed unsafe class GameProcess {
if (libMainProc.IsNull) { if (libMainProc.IsNull) {
throw new Win32Exception { Data = { { "api", "GetProcAddress" } } }; throw new Win32Exception { Data = { { "api", "GetProcAddress" } } };
} }
var lpStartAddress2 = (delegate*unmanaged[Stdcall]<void*, uint>) libMainProc.Value; // THREAD_START_ROUTINE var libMainProcRVA = libMainProc.Value - libHandle.DangerousGetHandle();
var lpStartAddress2 = (delegate*unmanaged[Stdcall]<void*, uint>) (baseAddress + libMainProcRVA); // THREAD_START_ROUTINE
//
var hThread2 = Native.CreateRemoteThread(Handle, null, 0, lpStartAddress2, null, 0); var hThread2 = Native.CreateRemoteThread(Handle, null, 0, lpStartAddress2, null, 0);
if (hThread2.IsNull) { if (hThread2.IsNull) {
throw new Win32Exception { Data = { { "api", "CreateRemoteThread2" } } }; throw new Win32Exception { Data = { { "api", "CreateRemoteThread2" } } };

View File

@@ -1,9 +1,11 @@
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.IO.Compression;
using System.IO.Pipes; using System.IO.Pipes;
using System.Net; using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Windows.Win32; using Windows.Win32;
using Windows.Win32.Foundation; using Windows.Win32.Foundation;
@@ -28,30 +30,68 @@ public static class Utils {
public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) { public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) {
var transaction = SentrySdk.StartTransaction(path, "bucket.get"); var transaction = SentrySdk.StartTransaction(path, "bucket.get");
SentrySdk.ConfigureScope(scope => scope.Transaction = transaction); SentrySdk.ConfigureScope(static (scope, transaction) => scope.Transaction = transaction, transaction);
var cacheKey = useCache ? path : null;
//
if (_updateInfo?.CdnFiles.TryGetValue(path, out var cdnFile) == true) {
try {
var data = await cdnFile.Urls
.Select(url => GetFileFromCdn(url, cacheKey, cdnFile.Hash, cdnFile.Size))
.WhenFirstSuccessful()
.Unwrap();
transaction.Finish();
return data;
} catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException or InvalidDataException) {}
}
//
try { try {
var data = await GetFile("https://api.qhy04.com/hutaocdn/download?filename={0}", path, useCache); var data = await WhenFirstSuccessful([
GetFileReal($"https://rin.holohat.work/{path}", cacheKey),
GetFileReal($"https://ena-rin.holohat.work//{path}", cacheKey),
GetFileReal($"https://cn-cd-1259389942.file.myqcloud.com/{path}", cacheKey)
]).Unwrap();
transaction.Finish(); transaction.Finish();
return data; return data;
} catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException) { } catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException) {
AnsiConsole.WriteLine(App.NetworkError, e.Message);
} }
try { transaction.Finish();
var data = await Task.WhenAny( Environment.Exit(-1);
GetFile("https://rin.holohat.work/{0}", path, useCache),
GetFile("https://cn-cd-1259389942.file.myqcloud.com/{0}", path, useCache)
).Unwrap();
transaction.Finish();
return data;
} catch (Exception ex) when (ex is HttpRequestException or SocketException or TaskCanceledException) {
transaction.Finish();
AnsiConsole.WriteLine(App.NetworkError, ex.Message);
Environment.Exit(-1);
}
throw new UnreachableException(); throw new UnreachableException();
static async Task<byte[]> GetFile(string baseUrl, string objectKey, bool useCache) { static async Task<byte[]> GetFileFromCdn(string url, string? cacheKey, uint hash, uint size) {
using var reqwest = new HttpRequestMessage(HttpMethod.Get, string.Format(baseUrl, objectKey)); var data = await GetFileReal(url, cacheKey);
if (data.Length != size || Crc32.Compute(data) != hash) {
throw new InvalidDataException();
}
if (data.Length > 44 && Unsafe.As<byte, uint>(ref data[0]) == 0x38464947) { // GIF8
var seed = Unsafe.As<byte, uint>(ref data[44]) ^ 0x01919810;
var hush = Unsafe.As<byte, uint>(ref data[48]) - 0x32123432; //          
var span = data.AsSpan()[52..];
Span<byte> xorTable = stackalloc byte[4096];
new Random((int) seed).NextBytes(xorTable);
for (var i = 0; i < span.Length; i++) {
span[i] ^= xorTable[i % 4096];
}
using var dataStream = new MemoryStream();
unsafe {
fixed (byte* p = span) {
var cmpStream = new UnmanagedMemoryStream(p, span.Length);
using var decompressor = new BrotliStream(cmpStream, CompressionMode.Decompress);
// ReSharper disable once MethodHasAsyncOverload
decompressor.CopyTo(dataStream);
}
}
data = dataStream.ToArray();
if (Crc32.Compute(data) != hush) {
throw new InvalidDataException();
}
}
return data;
}
static async Task<byte[]> GetFileReal(string url, string? cacheKey) {
using var reqwest = new HttpRequestMessage(HttpMethod.Get, url);
CacheItem? cache = null; CacheItem? cache = null;
if (useCache && CacheFile.TryRead(objectKey, out cache)) { if (cacheKey != null && CacheFile.TryRead(cacheKey, out cache)) {
reqwest.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}"); reqwest.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
} }
using var response = await CHttpClient.SendAsync(reqwest); using var response = await CHttpClient.SendAsync(reqwest);
@@ -60,18 +100,14 @@ public static class Utils {
} }
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync(); var bytes = await response.Content.ReadAsByteArrayAsync();
if (useCache) { if (cacheKey != null) {
var etag = response.Headers.ETag!.Tag; var etag = response.Headers.ETag!.Tag;
CacheFile.Write(objectKey, bytes, etag); CacheFile.Write(cacheKey, bytes, etag);
} }
return bytes; return bytes;
} }
} }
public static T? GetOrNull<T>(this T[] array, uint index) where T : class {
return array.Length > index ? array[index] : null;
}
public static int ToIntOrDefault(string? value, int defaultValue = 0) { public static int ToIntOrDefault(string? value, int defaultValue = 0) {
return value != null && int.TryParse(value, out var result) ? result : defaultValue; return value != null && int.TryParse(value, out var result) ? result : defaultValue;
} }
@@ -96,7 +132,7 @@ public static class Utils {
} }
// ReSharper disable once NotAccessedField.Local // ReSharper disable once NotAccessedField.Local
private static UpdateInfo _updateInfo = null!; private static UpdateInfo? _updateInfo;
public static Task StartSpinnerAsync(string status, Func<StatusContext, Task> func) { public static Task StartSpinnerAsync(string status, Func<StatusContext, Task> func) {
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func); return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
@@ -109,7 +145,7 @@ public static class Utils {
public static async Task CheckUpdate(bool useLocalLib) { public static async Task CheckUpdate(bool useLocalLib) {
try { try {
var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version")); var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version"));
var versionInfo = UpdateInfo.Parser.ParseFrom(versionData)!; var versionInfo = _updateInfo = UpdateInfo.Parser.ParseFrom(versionData)!;
if (GlobalVars.AppVersionCode < versionInfo.VersionCode) { if (GlobalVars.AppVersionCode < versionInfo.VersionCode) {
AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName); AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName);
AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description); AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description);
@@ -136,7 +172,6 @@ public static class Utils {
var data = await GetBucketFile("schicksal/lic.dll"); var data = await GetBucketFile("schicksal/lic.dll");
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); // 要求重启电脑 await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); // 要求重启电脑
} }
_updateInfo = versionInfo;
} catch (IOException e) when ((uint) e.HResult == 0x80070020) { // ERROR_SHARING_VIOLATION } catch (IOException e) when ((uint) e.HResult == 0x80070020) { // ERROR_SHARING_VIOLATION
// IO_SharingViolation_File // IO_SharingViolation_File
// The process cannot access the file '{0}' because it is being used by another process. // The process cannot access the file '{0}' because it is being used by another process.
@@ -146,17 +181,14 @@ public static class Utils {
} }
// ReSharper disable once UnusedMethodReturnValue.Global // ReSharper disable once UnusedMethodReturnValue.Global
public static bool ShellOpen(string path, string? args = null) { public static bool ShellOpen(string path, string args = "") {
try { try {
var startInfo = new ProcessStartInfo {
FileName = path,
UseShellExecute = true
};
if (args != null) {
startInfo.Arguments = args;
}
return new Process { return new Process {
StartInfo = startInfo StartInfo = new ProcessStartInfo {
FileName = path,
UseShellExecute = true,
Arguments = args
}
}.Start(); }.Start();
} catch (Exception) { } catch (Exception) {
return false; return false;
@@ -228,13 +260,21 @@ public static class Utils {
break; break;
case 0xFD: case 0xFD:
writer.Write(methodRva.DoCmd); writer.Write(methodRva.DoCmd);
writer.Write(methodRva.ToUint16);
writer.Write(methodRva.UpdateNormalProp); writer.Write(methodRva.UpdateNormalProp);
writer.Write(methodRva.NewString);
writer.Write(methodRva.FindGameObject);
writer.Write(methodRva.EventSystemUpdate);
writer.Write(methodRva.SimulatePointerClick);
writer.Write(methodRva.ToInt32);
writer.Write(methodRva.TcpStatePtr);
writer.Write(methodRva.SharedInfoPtr);
writer.Write(methodRva.Decompress);
break; break;
case 0xFE: case 0xFE:
_proc!.ResumeMainThread(); _proc!.ResumeMainThread();
break; break;
case 0xFF: case 0xFF:
writer.Write(true);
_isUnexpectedExit = false; _isUnexpectedExit = false;
onFinish(); onFinish();
return; return;
@@ -259,11 +299,15 @@ public static class Utils {
AnsiConsole.WriteLine(App.GameLoading, _proc.Id); AnsiConsole.WriteLine(App.GameLoading, _proc.Id);
} }
private static uint GetGameHash(string exePath) { public static uint GetGameHash(string exePath) {
Span<byte> buffer = stackalloc byte[0x10000]; try {
using var stream = File.OpenRead(exePath); Span<byte> buffer = stackalloc byte[0x10000];
_ = stream.Read(buffer); using var stream = File.OpenRead(exePath);
return Crc32.Compute(buffer); _ = stream.ReadAtLeast(buffer, 0x10000, false);
return Crc32.Compute(buffer);
} catch (IOException) {
return 0xFFFFFFFF;
}
} }
internal static unsafe void SetQuickEditMode(bool enable) { internal static unsafe void SetQuickEditMode(bool enable) {
@@ -289,4 +333,26 @@ public static class Utils {
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US"); // todo: use better way like auto set console font etc. CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US"); // todo: use better way like auto set console font etc.
} }
} }
// https://stackoverflow.com/a/76953892
private static async Task<Task<TResult>> WhenFirstSuccessful<TResult>(this IEnumerable<Task<TResult>> tasks) {
var cts = new CancellationTokenSource();
Task<TResult>? selectedTask = null;
var continuations = tasks
.TakeWhile(_ => !cts.IsCancellationRequested)
.Select(task => {
return task.ContinueWith(t => {
if (t.IsCompletedSuccessfully) {
if (Interlocked.CompareExchange(ref selectedTask, t, null) is null) {
cts.Cancel();
}
}
}, cts.Token, TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
});
var whenAll = Task.WhenAll(continuations);
try {
await whenAll.ConfigureAwait(false);
} catch when (whenAll.IsCanceled) { /* ignore */ }
return selectedTask!;
}
} }

View File

@@ -25,20 +25,47 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<DirectPInvoke Include="NTDLL"/> <DirectPInvoke Include="NTDLL" />
<DirectPInvoke Include="USER32"/> <DirectPInvoke Include="USER32" />
<DirectPInvoke Include="KERNEL32"/> <DirectPInvoke Include="KERNEL32" />
<DirectPInvoke Include="libMinHook.x64"/> <DirectPInvoke Include="libMinHook.x64" />
<NativeLibrary Include="lib\libMinHook.x64.lib"/> <NativeLibrary Include="lib\libMinHook.x64.lib" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute"/> <AssemblyAttribute Include="System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Using Include="System.Diagnostics"/> <Using Include="System.Diagnostics" />
<Using Include="System.Diagnostics.CodeAnalysis"/> <Using Include="System.Diagnostics.CodeAnalysis" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<PackageId>Yae.Lib</PackageId>
<Version>5.4.4</Version>
<Authors>HoloHat</Authors>
<DevelopmentDependency>true</DevelopmentDependency>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<RepositoryUrl>https://github.com/HolographicHat/Yae</RepositoryUrl>
<Description>Yae Lib</Description>
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
</PropertyGroup>
<ItemGroup>
<None Include="$(PublishDir)\$(TargetName)$(NativeBinaryExt)" Pack="true" PackagePath="runtimes\win-x64\native" Visible="false" />
</ItemGroup>
<ItemGroup>
<ProjectReference OutputItemType="Analyzer" Include="..\YaeAchievement.SourceGeneration\YaeAchievement.SourceGeneration.csproj">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</ProjectReference>
</ItemGroup>
<Target Name="GenerateNuGetPackage" AfterTargets="CopyNativeBinary">
<Exec Command="dotnet pack --no-build --nologo" UseUtf8Encoding="Always" EchoOff="true" />
</Target>
</Project> </Project>

View File

@@ -2,13 +2,19 @@ using System.Buffers.Binary;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Yae.Utilities; using Yae.Utilities;
using static Yae.GameMethod;
namespace Yae; namespace Yae;
internal static unsafe class Application { internal static unsafe class Application {
private static bool _initialized;
[UnmanagedCallersOnly(EntryPoint = "YaeMain")] [UnmanagedCallersOnly(EntryPoint = "YaeMain")]
private static uint Awake(nint hModule) { private static uint Awake(nint hModule) {
if (Interlocked.Exchange(ref _initialized, true)) {
return 1;
}
Native.RegisterUnhandledExceptionHandler(); Native.RegisterUnhandledExceptionHandler();
Log.UseConsoleOutput(); Log.UseConsoleOutput();
Log.Trace("~"); Log.Trace("~");
@@ -21,20 +27,27 @@ internal static unsafe class Application {
Log.ResetConsole(); Log.ResetConsole();
// //
RecordChecksum(); RecordChecksum();
MinHook.Attach(GameMethod.DoCmd, &OnDoCmd, out _doCmd); MinHook.Attach(DoCmd, &OnDoCmd, out _doCmd);
MinHook.Attach(GameMethod.ToUInt16, &OnToUInt16, out _toUInt16); MinHook.Attach(ToUInt32, &OnToInt32, out _toInt32);
MinHook.Attach(GameMethod.UpdateNormalProp, &OnUpdateNormalProp, out _updateNormalProp); MinHook.Attach(UpdateNormalProp, &OnUpdateNormalProp, out _updateNormalProp);
MinHook.Attach(EventSystemUpdate, &OnEventSystemUpdate, out _eventSystemUpdate);
return 0; return 0;
} }
[UnmanagedCallersOnly(EntryPoint = "YaeWndHook")]
private static nint WndHook(int nCode, nint wParam, nint lParam) {
((delegate*unmanaged<nint, uint>) &Awake)(0);
return User32.CallNextHookEx(0, nCode, wParam, lParam);
}
#region RecvPacket #region RecvPacket
private static delegate*unmanaged<byte*, int, ushort> _toUInt16; private static delegate*unmanaged<byte*, int, int> _toInt32;
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
private static ushort OnToUInt16(byte* val, int startIndex) { private static int OnToInt32(byte* val, int startIndex) {
var ret = _toUInt16(val, startIndex); var ret = _toInt32(val, startIndex);
if (ret != 0xAB89 || *(ushort*) (val += 0x20) != 0x6745) { if (startIndex != 6 || *(ushort*) (val += 0x20) != 0x6745) {
return ret; return ret;
} }
var cmdId = BinaryPrimitives.ReverseEndianness(*(ushort*) (val + 2)); var cmdId = BinaryPrimitives.ReverseEndianness(*(ushort*) (val + 2));
@@ -46,11 +59,61 @@ internal static unsafe class Application {
return ret; return ret;
static Span<byte> GetData(byte* val) { static Span<byte> GetData(byte* val) {
var headLen = BinaryPrimitives.ReverseEndianness(*(ushort*) (val + 4)); var headLen = BinaryPrimitives.ReverseEndianness(*(ushort*) (val + 4));
var headPtr = val + 10;
var dataLen = BinaryPrimitives.ReverseEndianness(*(uint*) (val + 6)); var dataLen = BinaryPrimitives.ReverseEndianness(*(uint*) (val + 6));
return new Span<byte>(val + 10 + headLen, (int) dataLen); var dataPtr = val + 10 + headLen;
var unzipLen = GetDecompressedSize(new Span<byte>(headPtr, headLen));
if (unzipLen == 0) {
return new Span<byte>(dataPtr, (int) dataLen);
}
var unzipBuf = NativeMemory.Alloc(unzipLen);
if (!Decompress(*TcpStatePtr, *SharedInfoPtr, dataPtr, dataLen, unzipBuf, unzipLen)) {
throw new InvalidDataException("Decompress failed.");
}
return new Span<byte>(unzipBuf, (int) unzipLen);
} }
} }
private static uint GetDecompressedSize(Span<byte> header) {
var offset = 0;
ulong tag;
while (offset != header.Length && (tag = ReadRawVarInt64(header, ref offset)) != 0) {
if (tag == 64) {
return (uint) ReadRawVarInt64(header, ref offset);
}
switch (tag & 7) {
case 0:
ReadRawVarInt64(header, ref offset);
break;
case 1:
offset += 8;
break;
case 2:
offset += (int) ReadRawVarInt64(header, ref offset);
break;
case 3:
case 4:
throw new NotSupportedException();
case 5:
offset += 4;
break;
}
}
return 0;
}
private static ulong ReadRawVarInt64(this Span<byte> span, ref int offset) {
ulong result = 0;
for (var i = 0; i < 8; i++) {
var b = span[offset++];
result |= (ulong) (b & 0x7F) << (i * 7);
if (b < 0x80) {
return result;
}
}
throw new InvalidDataException("CodedInputStream encountered a malformed varint.");
}
#endregion #endregion
#region Prop #region Prop
@@ -106,7 +169,7 @@ internal static unsafe class Application {
Buffer = buffer, Buffer = buffer,
Length = 256 Length = 256
}; };
_ = GameMethod.DoCmd(23, Unsafe.AsPointer(ref data), sizeof(RecordChecksumCmdData)); _ = DoCmd(23, Unsafe.AsPointer(ref data), sizeof(RecordChecksumCmdData));
RecordedChecksum[i] = data; RecordedChecksum[i] = data;
//REPL//Log.Trace($"nType={i}, value={new string((sbyte*) buffer, 0, data.Length)}"); //REPL//Log.Trace($"nType={i}, value={new string((sbyte*) buffer, 0, data.Length)}");
} }
@@ -131,4 +194,24 @@ internal static unsafe class Application {
#endregion #endregion
#region EnterGate
private static long _lastTryEnterTime;
private static delegate*unmanaged<nint, void> _eventSystemUpdate;
[UnmanagedCallersOnly]
public static void OnEventSystemUpdate(nint @this) {
_eventSystemUpdate(@this);
if (Environment.TickCount64 - _lastTryEnterTime > 200) {
var obj = FindGameObject(NewString("BtnStart"u8.AsPointer()));
if (obj != 0 && SimulatePointerClick(@this, obj)) {
MinHook.Detach((nint) EventSystemUpdate);
}
_lastTryEnterTime = Environment.TickCount64;
}
}
#endregion
} }

View File

@@ -15,10 +15,24 @@ internal static unsafe class GameMethod {
public static delegate*unmanaged<int, void*, int, int> DoCmd { get; set; } public static delegate*unmanaged<int, void*, int, int> DoCmd { get; set; }
public static delegate*unmanaged<byte*, int, ushort> ToUInt16 { get; set; }
public static delegate*unmanaged<nint, int, double, double, int, void> UpdateNormalProp { get; set; } public static delegate*unmanaged<nint, int, double, double, int, void> UpdateNormalProp { get; set; }
public static delegate*unmanaged<nint, nint> NewString { get; set; }
public static delegate*unmanaged<nint, nint> FindGameObject { get; set; }
public static delegate*unmanaged<nint, void> EventSystemUpdate { get; set; }
public static delegate*unmanaged<nint, nint, bool> SimulatePointerClick { get; set; }
public static delegate*unmanaged<byte*, int, int> ToUInt32 { get; set; }
public static void** TcpStatePtr { get; set; }
public static void** SharedInfoPtr { get; set; }
public static delegate*unmanaged<void*, void*, void*, uint, void*, uint, bool> Decompress { get; set; }
} }
internal static class Goshujin { internal static class Goshujin {
@@ -26,8 +40,10 @@ internal static class Goshujin {
private static NamedPipeClientStream _pipeStream = null!; private static NamedPipeClientStream _pipeStream = null!;
private static BinaryReader _pipeReader = null!; private static BinaryReader _pipeReader = null!;
private static BinaryWriter _pipeWriter = null!; private static BinaryWriter _pipeWriter = null!;
private static Lock _lock = null!;
public static void Init(string pipeName = "YaeAchievementPipe") { public static void Init(string pipeName = "YaeAchievementPipe") {
_lock = new Lock();
_pipeStream = new NamedPipeClientStream(pipeName); _pipeStream = new NamedPipeClientStream(pipeName);
_pipeReader = new BinaryReader(_pipeStream); _pipeReader = new BinaryReader(_pipeStream);
_pipeWriter = new BinaryWriter(_pipeStream); _pipeWriter = new BinaryWriter(_pipeStream);
@@ -36,26 +52,32 @@ internal static class Goshujin {
} }
public static void PushAchievementData(Span<byte> data) { public static void PushAchievementData(Span<byte> data) {
_pipeWriter.Write((byte) 1); using (_lock.EnterScope()) {
_pipeWriter.Write(data.Length); _pipeWriter.Write((byte) 1);
_pipeWriter.Write(data); _pipeWriter.Write(data.Length);
_achievementDataPushed = true; _pipeWriter.Write(data);
ExitIfFinished(); _achievementDataPushed = true;
ExitIfFinished();
}
} }
public static void PushStoreData(Span<byte> data) { public static void PushStoreData(Span<byte> data) {
_pipeWriter.Write((byte) 2); using (_lock.EnterScope()) {
_pipeWriter.Write(data.Length); _pipeWriter.Write((byte) 2);
_pipeWriter.Write(data); _pipeWriter.Write(data.Length);
_storeDataPushed = true; _pipeWriter.Write(data);
ExitIfFinished(); _storeDataPushed = true;
ExitIfFinished();
}
} }
public static void PushPlayerProp(int type, double value) { public static void PushPlayerProp(int type, double value) {
_pipeWriter.Write((byte) 3); using (_lock.EnterScope()) {
_pipeWriter.Write(type); _pipeWriter.Write((byte) 3);
_pipeWriter.Write(value); _pipeWriter.Write(type);
ExitIfFinished(); _pipeWriter.Write(value);
ExitIfFinished();
}
} }
public static void LoadCmdTable() { public static void LoadCmdTable() {
@@ -67,8 +89,15 @@ internal static class Goshujin {
public static unsafe void LoadMethodTable() { public static unsafe void LoadMethodTable() {
_pipeWriter.Write((byte) 0xFD); _pipeWriter.Write((byte) 0xFD);
GameMethod.DoCmd = (delegate*unmanaged<int, void*, int, int>) Native.RVAToVA(_pipeReader.ReadUInt32()); GameMethod.DoCmd = (delegate*unmanaged<int, void*, int, int>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.ToUInt16 = (delegate*unmanaged<byte*, int, ushort>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.UpdateNormalProp = (delegate*unmanaged<nint, int, double, double, int, void>) Native.RVAToVA(_pipeReader.ReadUInt32()); GameMethod.UpdateNormalProp = (delegate*unmanaged<nint, int, double, double, int, void>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.NewString = (delegate*unmanaged<nint, nint>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.FindGameObject = (delegate*unmanaged<nint, nint>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.EventSystemUpdate = (delegate*unmanaged<nint, void>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.SimulatePointerClick = (delegate*unmanaged<nint, nint, bool>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.ToUInt32 = (delegate*unmanaged<byte*, int, int>) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.TcpStatePtr = (void**) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.SharedInfoPtr = (void**) Native.RVAToVA(_pipeReader.ReadUInt32());
GameMethod.Decompress = (delegate*unmanaged<void*, void*, void*, uint, void*, uint, bool>) Native.RVAToVA(_pipeReader.ReadUInt32());
} }
public static void ResumeMainThread() { public static void ResumeMainThread() {
@@ -82,6 +111,7 @@ internal static class Goshujin {
private static void ExitIfFinished() { private static void ExitIfFinished() {
if (_storeDataPushed && _achievementDataPushed && Application.RequiredPlayerProperties.Count == 0) { if (_storeDataPushed && _achievementDataPushed && Application.RequiredPlayerProperties.Count == 0) {
_pipeWriter.Write((byte) 0xFF); _pipeWriter.Write((byte) 0xFF);
_pipeReader.ReadBoolean();
Environment.Exit(0); Environment.Exit(0);
} }
} }

View File

@@ -93,6 +93,9 @@ internal static unsafe class Native {
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint AsPointer(this ReadOnlySpan<byte> span) => *(nint*) Unsafe.AsPointer(ref span);
} }
internal static partial class MinHook { internal static partial class MinHook {
@@ -146,6 +149,7 @@ internal static partial class MinHook {
/// Uninitialize the MinHook library. You must call this function EXACTLY ONCE at the end of your program. /// Uninitialize the MinHook library. You must call this function EXACTLY ONCE at the end of your program.
/// </summary> /// </summary>
[LibraryImport("libMinHook.x64", EntryPoint = "MH_Uninitialize")] [LibraryImport("libMinHook.x64", EntryPoint = "MH_Uninitialize")]
// ReSharper disable once UnusedMember.Local
private static partial uint MinHookUninitialize(); private static partial uint MinHookUninitialize();
static MinHook() { static MinHook() {
@@ -155,30 +159,6 @@ internal static partial class MinHook {
} }
} }
// todo: auto gen
public static unsafe void Attach(delegate*unmanaged<byte*, int, ushort> origin, delegate*unmanaged<byte*, int, ushort> handler, out delegate*unmanaged<byte*, int, ushort> trampoline) {
Attach((nint) origin, (nint) handler, out var trampoline1);
trampoline = (delegate*unmanaged<byte*, int, ushort>) trampoline1;
}
// todo: auto gen
public static unsafe void Attach(delegate*unmanaged<nint, int, double, double, int, void> origin, delegate*unmanaged<nint, int, double, double, int, void> handler, out delegate*unmanaged<nint, int, double, double, int, void> trampoline) {
Attach((nint) origin, (nint) handler, out var trampoline1);
trampoline = (delegate*unmanaged<nint, int, double, double, int, void>) trampoline1;
}
// todo: auto gen
public static unsafe void Attach(delegate*unmanaged<nint, nint, uint, void> origin, delegate*unmanaged<nint, nint, uint, void> handler, out delegate*unmanaged<nint, nint, uint, void> trampoline) {
Attach((nint) origin, (nint) handler, out var trampoline1);
trampoline = (delegate*unmanaged<nint, nint, uint, void>) trampoline1;
}
// todo: auto gen
public static unsafe void Attach(delegate*unmanaged<int, void*, int, int> origin, delegate*unmanaged<int, void*, int, int> handler, out delegate*unmanaged<int, void*, int, int> trampoline) {
Attach((nint) origin, (nint) handler, out var trampoline1);
trampoline = (delegate*unmanaged<int, void*, int, int>) trampoline1;
}
public static void Attach(nint origin, nint handler, out nint trampoline) { public static void Attach(nint origin, nint handler, out nint trampoline) {
uint result; uint result;
if ((result = MinHookCreate(origin, handler, out trampoline)) != 0) { if ((result = MinHookCreate(origin, handler, out trampoline)) != 0) {

View File

@@ -43,6 +43,9 @@ internal static unsafe partial class Kernel32 {
[return:MarshalAs(UnmanagedType.I4)] [return:MarshalAs(UnmanagedType.I4)]
[LibraryImport("KERNEL32.dll", SetLastError = true)] [LibraryImport("KERNEL32.dll", SetLastError = true)]
internal static partial bool SetConsoleMode(nint hConsoleHandle, uint dwMode); internal static partial bool SetConsoleMode(nint hConsoleHandle, uint dwMode);
[LibraryImport("KERNEL32.dll", SetLastError = true)]
internal static partial nint CreateThread(nint lpThreadAttributes, nint dwStackSize, delegate*unmanaged<nint, uint> lpStartAddress, nint lpParameter, uint dwCreationFlags, uint* lpThreadId);
} }
@@ -65,4 +68,7 @@ internal static unsafe partial class User32 {
[LibraryImport("USER32.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] [LibraryImport("USER32.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)]
internal static partial int MessageBoxW(nint hWnd, string text, string caption, uint uType); internal static partial int MessageBoxW(nint hWnd, string text, string caption, uint uType);
[LibraryImport("USER32.dll")]
internal static partial nint CallNextHookEx(nint hhk, int nCode, nint wParam, nint lParam);
} }