mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-11 00:48:12 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccff7a1702 | ||
|
|
79122d6ba7 | ||
|
|
1218d1cbc4 | ||
|
|
19f84bdb86 | ||
|
|
9d22fdf6f9 | ||
|
|
27fa0d9c84 | ||
|
|
e9baf8f211 | ||
|
|
b960165b7e | ||
|
|
a4c2027ada | ||
|
|
f49477c49a | ||
|
|
67c2fb3bda | ||
|
|
be3440695d | ||
|
|
f8b8a5a9e1 | ||
|
|
49f8679996 | ||
|
|
222a26233e | ||
|
|
1130f442fc | ||
|
|
b80987f574 | ||
|
|
c96395e1a2 | ||
|
|
3f42156b20 | ||
|
|
45638b7327 | ||
|
|
d514f3b5e7 | ||
|
|
3fe54d908e |
12
.github/workflows/dotnet.yml
vendored
12
.github/workflows/dotnet.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: .NET Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
@@ -13,7 +14,8 @@ jobs:
|
||||
run:
|
||||
working-directory: ./YaeAchievement
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
@@ -27,12 +29,12 @@ jobs:
|
||||
- name: Upload-AOT
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Artifacts-AOT
|
||||
path: publish\publish
|
||||
name: aot
|
||||
path: YaeAchievement\publish\publish
|
||||
- name: Publish-NoAOT
|
||||
run: dotnet publish --property:OutputPath=.\naot-publish\ --property:PublishAot=false --property:PublishSingleFile=true --property:PublishTrimmed=true
|
||||
- name: Upload-NoAOT
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Artifacts-NoAOT
|
||||
path: naot-publish\publish
|
||||
name: normal
|
||||
path: YaeAchievement\naot-publish\publish
|
||||
|
||||
46
.github/workflows/lib-nuget.yml
vendored
46
.github/workflows/lib-nuget.yml
vendored
@@ -2,29 +2,35 @@ name: YaeLib NuGet Publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [released]
|
||||
inputs:
|
||||
confirm_version:
|
||||
description: 'Version already increased?'
|
||||
required: true
|
||||
type: boolean
|
||||
perform_publish:
|
||||
description: 'Publish to nuget?'
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: YaeAchievementLib
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- 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
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
- name: Build native library
|
||||
run: dotnet publish
|
||||
- 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
|
||||
|
||||
104
YaeAchievement.SourceGeneration/MinHookAttachGenerator.cs
Normal file
104
YaeAchievement.SourceGeneration/MinHookAttachGenerator.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,4 +1,5 @@
|
||||
<Solution>
|
||||
<Project Path="YaeAchievementLib\YaeAchievementLib.csproj" Type="Classic C#" />
|
||||
<Project Path="YaeAchievement\YaeAchievement.csproj" Type="Classic C#" />
|
||||
<Project Path="YaeAchievement.SourceGeneration/YaeAchievement.SourceGeneration.csproj" />
|
||||
<Project Path="YaeAchievementLib\YaeAchievementLib.csproj" />
|
||||
<Project Path="YaeAchievement\YaeAchievement.csproj" />
|
||||
</Solution>
|
||||
@@ -2,5 +2,5 @@
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"className": "Native",
|
||||
"allowMarshaling": false,
|
||||
"public": true
|
||||
"public": false
|
||||
}
|
||||
@@ -1,28 +1,30 @@
|
||||
CloseClipboard
|
||||
CreateProcess
|
||||
CreateRemoteThread
|
||||
EmptyClipboard
|
||||
GetConsoleMode
|
||||
GetDC
|
||||
GetDeviceCaps
|
||||
GetModuleHandle
|
||||
GetProcAddress
|
||||
GetStdHandle
|
||||
// kernel32
|
||||
GlobalLock
|
||||
OpenProcess
|
||||
GetStdHandle
|
||||
GlobalUnlock
|
||||
OpenClipboard
|
||||
ResumeThread
|
||||
SetClipboardData
|
||||
Module32Next
|
||||
Module32First
|
||||
CreateProcess
|
||||
LoadLibraryEx
|
||||
VirtualFreeEx
|
||||
VirtualAllocEx
|
||||
GetProcAddress
|
||||
GetConsoleMode
|
||||
SetConsoleMode
|
||||
TerminateProcess
|
||||
VirtualAllocEx
|
||||
VirtualFreeEx
|
||||
WaitForSingleObject
|
||||
CreateRemoteThread
|
||||
WriteProcessMemory
|
||||
|
||||
WaitForSingleObject
|
||||
GetCurrentConsoleFontEx
|
||||
CreateToolhelp32Snapshot
|
||||
|
||||
OpenProcess
|
||||
// psapi
|
||||
GetModuleFileNameEx
|
||||
|
||||
LoadLibraryEx
|
||||
// user32
|
||||
OpenClipboard
|
||||
CloseClipboard
|
||||
EmptyClipboard
|
||||
SetClipboardData
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<FileVersion>5.7.0</FileVersion>
|
||||
<AssemblyVersion>5.7.0</AssemblyVersion>
|
||||
<FileVersion>5.7.2</FileVersion>
|
||||
<AssemblyVersion>5.7.2</AssemblyVersion>
|
||||
<ApplicationIcon>res\icon.ico</ApplicationIcon>
|
||||
<ApplicationManifest>res\app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
@@ -24,7 +24,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<!-- [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">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -19,8 +19,15 @@ message AchievementItem {
|
||||
|
||||
message MethodRvaConfig {
|
||||
uint32 do_cmd = 1;
|
||||
uint32 to_uint16 = 2;
|
||||
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 {
|
||||
|
||||
@@ -2,6 +2,12 @@ syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Proto";
|
||||
|
||||
message CdnFileInfo {
|
||||
uint32 size = 1;
|
||||
uint32 hash = 2;
|
||||
repeated string urls = 4;
|
||||
}
|
||||
|
||||
message UpdateInfo {
|
||||
uint32 version_code = 1;
|
||||
string version_name = 2;
|
||||
@@ -10,4 +16,5 @@ message UpdateInfo {
|
||||
bool force_update = 5;
|
||||
bool enable_lib_download = 6;
|
||||
bool enable_auto_update = 7;
|
||||
map<string, CdnFileInfo> cdn_files = 8;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Spectre.Console;
|
||||
@@ -23,12 +22,7 @@ public static partial class AppConfig {
|
||||
} else {
|
||||
GamePath = ReadGamePathFromProcess();
|
||||
}
|
||||
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}"));
|
||||
}
|
||||
CacheFile.Write("genshin_impact_game_path_v2", Encoding.UTF8.GetBytes($"{GamePath}\u1145{Utils.GetGameHash(GamePath)}"));
|
||||
SentrySdk.AddBreadcrumb(GamePath.EndsWith("YuanShen.exe") ? "CN" : "OS", "GamePath");
|
||||
return;
|
||||
static bool TryReadGamePathFromCache([NotNullWhen(true)] out string? path) {
|
||||
@@ -38,9 +32,7 @@ public static partial class AppConfig {
|
||||
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]) {
|
||||
if (Utils.GetGameHash(cacheData[0]) != uint.Parse(cacheData[1])) {
|
||||
return false;
|
||||
}
|
||||
path = cacheData[0];
|
||||
|
||||
@@ -18,8 +18,8 @@ public static class GlobalVars {
|
||||
public static readonly string CachePath = Path.Combine(DataPath, "cache");
|
||||
public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll");
|
||||
|
||||
public const uint AppVersionCode = 240;
|
||||
public const string AppVersionName = "5.7";
|
||||
public const uint AppVersionCode = 243;
|
||||
public const string AppVersionName = "5.7.3";
|
||||
|
||||
public const string PipeName = "YaeAchievementPipe";
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ using static YaeAchievement.Utils;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
// TODO: WndHook
|
||||
|
||||
internal static class Program {
|
||||
|
||||
public static async Task Main(string[] args) {
|
||||
@@ -47,10 +45,10 @@ internal static class Program {
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2)));
|
||||
await CheckUpdate(ToBooleanOrDefault(args.ElementAtOrDefault(2)));
|
||||
|
||||
AppConfig.Load(args.GetOrNull(0) ?? "auto");
|
||||
Export.ExportTo = ToIntOrDefault(args.GetOrNull(1), 114514);
|
||||
AppConfig.Load(args.ElementAtOrDefault(0) ?? "auto");
|
||||
Export.ExportTo = ToIntOrDefault(args.ElementAtOrDefault(1), 114514);
|
||||
|
||||
AchievementAllDataNotify? data = null;
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Diagnostics.ToolHelp;
|
||||
using Windows.Win32.System.LibraryLoader;
|
||||
using Windows.Win32.System.Threading;
|
||||
using Spectre.Console;
|
||||
@@ -13,7 +14,7 @@ using static Windows.Win32.System.Memory.VIRTUAL_FREE_TYPE;
|
||||
|
||||
namespace YaeAchievement.Utilities;
|
||||
|
||||
public sealed unsafe class GameProcess {
|
||||
internal sealed unsafe class GameProcess {
|
||||
|
||||
public uint Id { get; }
|
||||
|
||||
@@ -73,7 +74,29 @@ public sealed unsafe class GameProcess {
|
||||
if (Native.WaitForSingleObject(hThread, 2000) == 0) {
|
||||
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) {
|
||||
throw new Win32Exception { Data = { { "api", "LoadLibraryEx" } } };
|
||||
}
|
||||
@@ -81,7 +104,9 @@ public sealed unsafe class GameProcess {
|
||||
if (libMainProc.IsNull) {
|
||||
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);
|
||||
if (hThread2.IsNull) {
|
||||
throw new Win32Exception { Data = { { "api", "CreateRemoteThread2" } } };
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Pipes;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
@@ -28,30 +30,68 @@ public static class Utils {
|
||||
|
||||
public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) {
|
||||
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 {
|
||||
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();
|
||||
return data;
|
||||
} catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException) {
|
||||
AnsiConsole.WriteLine(App.NetworkError, e.Message);
|
||||
}
|
||||
try {
|
||||
var data = await Task.WhenAny(
|
||||
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);
|
||||
}
|
||||
transaction.Finish();
|
||||
Environment.Exit(-1);
|
||||
throw new UnreachableException();
|
||||
static async Task<byte[]> GetFile(string baseUrl, string objectKey, bool useCache) {
|
||||
using var reqwest = new HttpRequestMessage(HttpMethod.Get, string.Format(baseUrl, objectKey));
|
||||
static async Task<byte[]> GetFileFromCdn(string url, string? cacheKey, uint hash, uint size) {
|
||||
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;
|
||||
if (useCache && CacheFile.TryRead(objectKey, out cache)) {
|
||||
if (cacheKey != null && CacheFile.TryRead(cacheKey, out cache)) {
|
||||
reqwest.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
|
||||
}
|
||||
using var response = await CHttpClient.SendAsync(reqwest);
|
||||
@@ -60,18 +100,14 @@ public static class Utils {
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync();
|
||||
if (useCache) {
|
||||
if (cacheKey != null) {
|
||||
var etag = response.Headers.ETag!.Tag;
|
||||
CacheFile.Write(objectKey, bytes, etag);
|
||||
CacheFile.Write(cacheKey, bytes, etag);
|
||||
}
|
||||
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) {
|
||||
return value != null && int.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
@@ -96,7 +132,7 @@ public static class Utils {
|
||||
}
|
||||
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private static UpdateInfo _updateInfo = null!;
|
||||
private static UpdateInfo? _updateInfo;
|
||||
|
||||
public static Task StartSpinnerAsync(string status, Func<StatusContext, Task> 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) {
|
||||
try {
|
||||
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) {
|
||||
AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName);
|
||||
AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description);
|
||||
@@ -136,7 +172,6 @@ public static class Utils {
|
||||
var data = await GetBucketFile("schicksal/lic.dll");
|
||||
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); // 要求重启电脑
|
||||
}
|
||||
_updateInfo = versionInfo;
|
||||
} catch (IOException e) when ((uint) e.HResult == 0x80070020) { // ERROR_SHARING_VIOLATION
|
||||
// IO_SharingViolation_File
|
||||
// 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
|
||||
public static bool ShellOpen(string path, string? args = null) {
|
||||
public static bool ShellOpen(string path, string args = "") {
|
||||
try {
|
||||
var startInfo = new ProcessStartInfo {
|
||||
FileName = path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
if (args != null) {
|
||||
startInfo.Arguments = args;
|
||||
}
|
||||
return new Process {
|
||||
StartInfo = startInfo
|
||||
StartInfo = new ProcessStartInfo {
|
||||
FileName = path,
|
||||
UseShellExecute = true,
|
||||
Arguments = args
|
||||
}
|
||||
}.Start();
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
@@ -228,13 +260,21 @@ public static class Utils {
|
||||
break;
|
||||
case 0xFD:
|
||||
writer.Write(methodRva.DoCmd);
|
||||
writer.Write(methodRva.ToUint16);
|
||||
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;
|
||||
case 0xFE:
|
||||
_proc!.ResumeMainThread();
|
||||
break;
|
||||
case 0xFF:
|
||||
writer.Write(true);
|
||||
_isUnexpectedExit = false;
|
||||
onFinish();
|
||||
return;
|
||||
@@ -259,11 +299,15 @@ public static class Utils {
|
||||
AnsiConsole.WriteLine(App.GameLoading, _proc.Id);
|
||||
}
|
||||
|
||||
private static uint GetGameHash(string exePath) {
|
||||
Span<byte> buffer = stackalloc byte[0x10000];
|
||||
using var stream = File.OpenRead(exePath);
|
||||
_ = stream.Read(buffer);
|
||||
return Crc32.Compute(buffer);
|
||||
public static uint GetGameHash(string exePath) {
|
||||
try {
|
||||
Span<byte> buffer = stackalloc byte[0x10000];
|
||||
using var stream = File.OpenRead(exePath);
|
||||
_ = stream.ReadAtLeast(buffer, 0x10000, false);
|
||||
return Crc32.Compute(buffer);
|
||||
} catch (IOException) {
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
// 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!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,20 +25,47 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<DirectPInvoke Include="NTDLL"/>
|
||||
<DirectPInvoke Include="USER32"/>
|
||||
<DirectPInvoke Include="KERNEL32"/>
|
||||
<DirectPInvoke Include="libMinHook.x64"/>
|
||||
<NativeLibrary Include="lib\libMinHook.x64.lib"/>
|
||||
<DirectPInvoke Include="NTDLL" />
|
||||
<DirectPInvoke Include="USER32" />
|
||||
<DirectPInvoke Include="KERNEL32" />
|
||||
<DirectPInvoke Include="libMinHook.x64" />
|
||||
<NativeLibrary Include="lib\libMinHook.x64.lib" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute"/>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="System.Diagnostics"/>
|
||||
<Using Include="System.Diagnostics.CodeAnalysis"/>
|
||||
<Using Include="System.Diagnostics" />
|
||||
<Using Include="System.Diagnostics.CodeAnalysis" />
|
||||
</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>
|
||||
|
||||
@@ -2,13 +2,19 @@ using System.Buffers.Binary;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Yae.Utilities;
|
||||
using static Yae.GameMethod;
|
||||
|
||||
namespace Yae;
|
||||
|
||||
internal static unsafe class Application {
|
||||
|
||||
private static bool _initialized;
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "YaeMain")]
|
||||
private static uint Awake(nint hModule) {
|
||||
if (Interlocked.Exchange(ref _initialized, true)) {
|
||||
return 1;
|
||||
}
|
||||
Native.RegisterUnhandledExceptionHandler();
|
||||
Log.UseConsoleOutput();
|
||||
Log.Trace("~");
|
||||
@@ -21,20 +27,27 @@ internal static unsafe class Application {
|
||||
Log.ResetConsole();
|
||||
//
|
||||
RecordChecksum();
|
||||
MinHook.Attach(GameMethod.DoCmd, &OnDoCmd, out _doCmd);
|
||||
MinHook.Attach(GameMethod.ToUInt16, &OnToUInt16, out _toUInt16);
|
||||
MinHook.Attach(GameMethod.UpdateNormalProp, &OnUpdateNormalProp, out _updateNormalProp);
|
||||
MinHook.Attach(DoCmd, &OnDoCmd, out _doCmd);
|
||||
MinHook.Attach(ToUInt32, &OnToInt32, out _toInt32);
|
||||
MinHook.Attach(UpdateNormalProp, &OnUpdateNormalProp, out _updateNormalProp);
|
||||
MinHook.Attach(EventSystemUpdate, &OnEventSystemUpdate, out _eventSystemUpdate);
|
||||
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
|
||||
|
||||
private static delegate*unmanaged<byte*, int, ushort> _toUInt16;
|
||||
private static delegate*unmanaged<byte*, int, int> _toInt32;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static ushort OnToUInt16(byte* val, int startIndex) {
|
||||
var ret = _toUInt16(val, startIndex);
|
||||
if (ret != 0xAB89 || *(ushort*) (val += 0x20) != 0x6745) {
|
||||
private static int OnToInt32(byte* val, int startIndex) {
|
||||
var ret = _toInt32(val, startIndex);
|
||||
if (startIndex != 6 || *(ushort*) (val += 0x20) != 0x6745) {
|
||||
return ret;
|
||||
}
|
||||
var cmdId = BinaryPrimitives.ReverseEndianness(*(ushort*) (val + 2));
|
||||
@@ -46,11 +59,61 @@ internal static unsafe class Application {
|
||||
return ret;
|
||||
static Span<byte> GetData(byte* val) {
|
||||
var headLen = BinaryPrimitives.ReverseEndianness(*(ushort*) (val + 4));
|
||||
var headPtr = val + 10;
|
||||
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
|
||||
|
||||
#region Prop
|
||||
@@ -106,7 +169,7 @@ internal static unsafe class Application {
|
||||
Buffer = buffer,
|
||||
Length = 256
|
||||
};
|
||||
_ = GameMethod.DoCmd(23, Unsafe.AsPointer(ref data), sizeof(RecordChecksumCmdData));
|
||||
_ = DoCmd(23, Unsafe.AsPointer(ref data), sizeof(RecordChecksumCmdData));
|
||||
RecordedChecksum[i] = data;
|
||||
//REPL//Log.Trace($"nType={i}, value={new string((sbyte*) buffer, 0, data.Length)}");
|
||||
}
|
||||
@@ -131,4 +194,24 @@ internal static unsafe class Application {
|
||||
|
||||
#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
|
||||
|
||||
}
|
||||
|
||||
@@ -15,10 +15,24 @@ internal static unsafe class GameMethod {
|
||||
|
||||
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, 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 {
|
||||
@@ -26,8 +40,10 @@ internal static class Goshujin {
|
||||
private static NamedPipeClientStream _pipeStream = null!;
|
||||
private static BinaryReader _pipeReader = null!;
|
||||
private static BinaryWriter _pipeWriter = null!;
|
||||
private static Lock _lock = null!;
|
||||
|
||||
public static void Init(string pipeName = "YaeAchievementPipe") {
|
||||
_lock = new Lock();
|
||||
_pipeStream = new NamedPipeClientStream(pipeName);
|
||||
_pipeReader = new BinaryReader(_pipeStream);
|
||||
_pipeWriter = new BinaryWriter(_pipeStream);
|
||||
@@ -36,26 +52,32 @@ internal static class Goshujin {
|
||||
}
|
||||
|
||||
public static void PushAchievementData(Span<byte> data) {
|
||||
_pipeWriter.Write((byte) 1);
|
||||
_pipeWriter.Write(data.Length);
|
||||
_pipeWriter.Write(data);
|
||||
_achievementDataPushed = true;
|
||||
ExitIfFinished();
|
||||
using (_lock.EnterScope()) {
|
||||
_pipeWriter.Write((byte) 1);
|
||||
_pipeWriter.Write(data.Length);
|
||||
_pipeWriter.Write(data);
|
||||
_achievementDataPushed = true;
|
||||
ExitIfFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public static void PushStoreData(Span<byte> data) {
|
||||
_pipeWriter.Write((byte) 2);
|
||||
_pipeWriter.Write(data.Length);
|
||||
_pipeWriter.Write(data);
|
||||
_storeDataPushed = true;
|
||||
ExitIfFinished();
|
||||
using (_lock.EnterScope()) {
|
||||
_pipeWriter.Write((byte) 2);
|
||||
_pipeWriter.Write(data.Length);
|
||||
_pipeWriter.Write(data);
|
||||
_storeDataPushed = true;
|
||||
ExitIfFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public static void PushPlayerProp(int type, double value) {
|
||||
_pipeWriter.Write((byte) 3);
|
||||
_pipeWriter.Write(type);
|
||||
_pipeWriter.Write(value);
|
||||
ExitIfFinished();
|
||||
using (_lock.EnterScope()) {
|
||||
_pipeWriter.Write((byte) 3);
|
||||
_pipeWriter.Write(type);
|
||||
_pipeWriter.Write(value);
|
||||
ExitIfFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadCmdTable() {
|
||||
@@ -67,8 +89,15 @@ internal static class Goshujin {
|
||||
public static unsafe void LoadMethodTable() {
|
||||
_pipeWriter.Write((byte) 0xFD);
|
||||
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.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() {
|
||||
@@ -82,6 +111,7 @@ internal static class Goshujin {
|
||||
private static void ExitIfFinished() {
|
||||
if (_storeDataPushed && _achievementDataPushed && Application.RequiredPlayerProperties.Count == 0) {
|
||||
_pipeWriter.Write((byte) 0xFF);
|
||||
_pipeReader.ReadBoolean();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
[LibraryImport("libMinHook.x64", EntryPoint = "MH_Uninitialize")]
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private static partial uint MinHookUninitialize();
|
||||
|
||||
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) {
|
||||
uint result;
|
||||
if ((result = MinHookCreate(origin, handler, out trampoline)) != 0) {
|
||||
|
||||
@@ -43,6 +43,9 @@ internal static unsafe partial class Kernel32 {
|
||||
[return:MarshalAs(UnmanagedType.I4)]
|
||||
[LibraryImport("KERNEL32.dll", SetLastError = true)]
|
||||
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)]
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user