mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-06 14:42:52 +08:00
update ci & remove old native lib
This commit is contained in:
9
.github/workflows/dotnet.yml
vendored
9
.github/workflows/dotnet.yml
vendored
@@ -8,9 +8,10 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./YaeAchievement
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
@@ -27,11 +28,11 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Artifacts-AOT
|
||||
path: YaeAchievement\publish\publish
|
||||
path: 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: YaeAchievement\naot-publish\publish
|
||||
path: naot-publish\publish
|
||||
|
||||
4
lib/.gitignore
vendored
4
lib/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
.vs
|
||||
build
|
||||
YaeAchievementLib.vcxproj.user
|
||||
YaeAchievementLib.vcxproj.filters
|
||||
@@ -1,17 +0,0 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Yae.Lib</id>
|
||||
<version>5.3.4</version>
|
||||
<authors>HolographicHat</authors>
|
||||
<developmentDependency>true</developmentDependency>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">GPL-3.0-only</license>
|
||||
<licenseUrl>https://licenses.nuget.org/GPL-3.0-only</licenseUrl>
|
||||
<projectUrl>https://github.com/HolographicHat/Yae</projectUrl>
|
||||
<description>Yae Lib</description>
|
||||
<repository type="git" url="https://github.com/HolographicHat/Yae" commit="$commit$" />
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="build\x64\Release\YaeLib.dll" target="runtimes\win-x64\native" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32407.343
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "YaeAchievementLib", "YaeAchievementLib.vcxproj", "{83C3DF1A-6219-408E-98A3-C7040CCC96FD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{83C3DF1A-6219-408E-98A3-C7040CCC96FD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{83C3DF1A-6219-408E-98A3-C7040CCC96FD}.Debug|x64.Build.0 = Debug|x64
|
||||
{83C3DF1A-6219-408E-98A3-C7040CCC96FD}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{83C3DF1A-6219-408E-98A3-C7040CCC96FD}.Release|x64.ActiveCfg = Release|x64
|
||||
{83C3DF1A-6219-408E-98A3-C7040CCC96FD}.Release|x64.Build.0 = Release|x64
|
||||
{83C3DF1A-6219-408E-98A3-C7040CCC96FD}.Release|x86.ActiveCfg = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {470905A4-E6C4-4363-B44D-BAE9A50755A3}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,114 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{83c3df1a-6219-408e-98a3-c7040ccc96fd}</ProjectGuid>
|
||||
<RootNamespace>YaeAchievementLib</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>build\$(Platform)\$(Configuration)\</IntDir>
|
||||
<TargetName>YaeLib</TargetName>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>build\$(Platform)\$(Configuration)\</IntDir>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<TargetName>YaeLib</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>NotSet</SubSystem>
|
||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PostBuildEvent />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<LanguageStandard_C>stdc11</LanguageStandard_C>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>NotSet</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PostBuildEvent />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\globals.h" />
|
||||
<ClInclude Include="src\il2cpp-types.h" />
|
||||
<ClInclude Include="src\il2cpp-init.h" />
|
||||
<ClInclude Include="src\NamedPipe.h" />
|
||||
<ClInclude Include="src\ntprivate.h" />
|
||||
<ClInclude Include="src\util.h" />
|
||||
<ClInclude Include="src\Zydis.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\dllmain.cpp" />
|
||||
<ClCompile Include="src\il2cpp-init.cpp" />
|
||||
<ClCompile Include="src\util.cpp" />
|
||||
<ClCompile Include="src\Zydis.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <span>
|
||||
|
||||
template <typename T>
|
||||
concept IsSpan = requires(T t) {
|
||||
{ t.data() } -> std::convertible_to<const void*>;
|
||||
{ t.size() } -> std::convertible_to<std::size_t>;
|
||||
{ t.size_bytes() } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
class NamedPipe
|
||||
{
|
||||
HANDLE m_hPipe = INVALID_HANDLE_VALUE;
|
||||
public:
|
||||
NamedPipe(HANDLE hPipe) : m_hPipe(hPipe) {}
|
||||
~NamedPipe() { if (m_hPipe != INVALID_HANDLE_VALUE) CloseHandle(m_hPipe); }
|
||||
|
||||
operator HANDLE() const { return m_hPipe; }
|
||||
operator bool() const { return m_hPipe != INVALID_HANDLE_VALUE && m_hPipe != nullptr; }
|
||||
NamedPipe& operator= (HANDLE hPipe) {
|
||||
m_hPipe = hPipe;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Write(const void* data, size_t size) const
|
||||
{
|
||||
DWORD bytesWritten;
|
||||
if (!WriteFile(m_hPipe, data, static_cast<DWORD>(size), &bytesWritten, nullptr) || bytesWritten != size)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <IsSpan T>
|
||||
bool Write(const T& data) const
|
||||
{
|
||||
return Write(data.data(), data.size_bytes());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Write(const T& data) const
|
||||
{
|
||||
return Write(&data, sizeof(T));
|
||||
}
|
||||
|
||||
};
|
||||
54990
lib/src/Zydis.c
54990
lib/src/Zydis.c
File diff suppressed because one or more lines are too long
12113
lib/src/Zydis.h
12113
lib/src/Zydis.h
File diff suppressed because it is too large
Load Diff
@@ -1,275 +0,0 @@
|
||||
// ReSharper disable CppClangTidyCertErr33C
|
||||
#include <Windows.h>
|
||||
#include <print>
|
||||
#include <string>
|
||||
#include <future>
|
||||
#include <TlHelp32.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "util.h"
|
||||
#include "il2cpp-init.h"
|
||||
#include "il2cpp-types.h"
|
||||
#include "ntprivate.h"
|
||||
|
||||
CRITICAL_SECTION CriticalSection;
|
||||
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index);
|
||||
|
||||
namespace
|
||||
{
|
||||
PacketType GetPacketType(const PacketMeta* packet)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto cmdid = packet->CmdId;
|
||||
|
||||
if (AchievementId && cmdid == AchievementId)
|
||||
return PacketType::Achivement;
|
||||
|
||||
if (AchievementIdSet.contains(cmdid) && packet->DataLength > 500)
|
||||
return PacketType::Achivement;
|
||||
|
||||
if (PlayerStoreId && cmdid == PlayerStoreId)
|
||||
return PacketType::Inventory;
|
||||
|
||||
return PacketType::None;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Hook {
|
||||
|
||||
|
||||
uint16_t __fastcall BitConverter_ToUInt16(Array<uint8_t>* val, const int startIndex)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto ToUInt16 = reinterpret_cast<decltype(&BitConverter_ToUInt16)>(Offset.BitConverter_ToUInt16);
|
||||
|
||||
EnterCriticalSection(&CriticalSection);
|
||||
SetBreakpoint((HANDLE)-2, 0, false, 0);
|
||||
const auto ret = ToUInt16(val, startIndex);
|
||||
SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true, 0);
|
||||
LeaveCriticalSection(&CriticalSection);
|
||||
|
||||
if (ret != 0xAB89)
|
||||
return ret;
|
||||
|
||||
const auto packet = val->As<PacketMeta*>();
|
||||
const auto packetType = GetPacketType(packet);
|
||||
if (packetType == PacketType::None)
|
||||
return ret;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::println("PacketType: {}", static_cast<uint8_t>(packetType));
|
||||
std::println("CmdId: {}", packet->CmdId);
|
||||
std::println("DataLength: {}", packet->DataLength);
|
||||
//std::println("Data: {}", Util::Base64Encode(packet->AsSpan()));
|
||||
#endif
|
||||
|
||||
if (!MessagePipe.Write(packetType))
|
||||
Util::Win32ErrorDialog(1002, GetLastError());
|
||||
|
||||
if (!MessagePipe.Write(packet->DataLength))
|
||||
Util::Win32ErrorDialog(1003, GetLastError());
|
||||
|
||||
if (!MessagePipe.Write(packet->AsSpan()))
|
||||
Util::Win32ErrorDialog(1004, GetLastError());
|
||||
|
||||
if (!AchievementsWritten)
|
||||
AchievementsWritten = packetType == PacketType::Achivement;
|
||||
|
||||
if (!PlayerStoreWritten)
|
||||
PlayerStoreWritten = packetType == PacketType::Inventory;
|
||||
|
||||
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
|
||||
{
|
||||
if (!MessagePipe.Write(PacketType::End))
|
||||
Util::Win32ErrorDialog(9001, GetLastError());
|
||||
#ifdef _DEBUG
|
||||
system("pause");
|
||||
#endif
|
||||
ExitProcess(0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void __fastcall AccountDataItem_UpdateNormalProp(const void* __this, const int type, const double value, const double lastValue, const int state)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto UpdateNormalProp = reinterpret_cast<decltype(&AccountDataItem_UpdateNormalProp)>(Offset.AccountDataItem_UpdateNormalProp);
|
||||
|
||||
EnterCriticalSection(&CriticalSection);
|
||||
SetBreakpoint((HANDLE)-2, 0, false, 1);
|
||||
UpdateNormalProp(__this, type, value, lastValue, state);
|
||||
SetBreakpoint((HANDLE)-2, Offset.AccountDataItem_UpdateNormalProp, true, 1);
|
||||
LeaveCriticalSection(&CriticalSection);
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::println("PropType: {}", type);
|
||||
std::println("PropState: {}", state);
|
||||
std::println("PropValue: {}", value);
|
||||
std::println("PropLastValue: {}", lastValue);
|
||||
#endif
|
||||
if (RequiredPlayerProperties.erase(type) != 0)
|
||||
{
|
||||
if (!MessagePipe.Write(PacketType::PropData))
|
||||
Util::Win32ErrorDialog(2002, GetLastError());
|
||||
if (!MessagePipe.Write(type))
|
||||
Util::Win32ErrorDialog(2003, GetLastError());
|
||||
if (!MessagePipe.Write(value))
|
||||
Util::Win32ErrorDialog(2004, GetLastError());
|
||||
}
|
||||
|
||||
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
|
||||
{
|
||||
if (!MessagePipe.Write(PacketType::End))
|
||||
Util::Win32ErrorDialog(9001, GetLastError());
|
||||
#ifdef _DEBUG
|
||||
system("pause");
|
||||
#endif
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto exceptionRecord = ep->ExceptionRecord;
|
||||
const auto contextRecord = ep->ContextRecord;
|
||||
|
||||
if (exceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
|
||||
{
|
||||
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.BitConverter_ToUInt16)) {
|
||||
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::BitConverter_ToUInt16);
|
||||
contextRecord->EFlags &= ~0x100; // clear the trap flag
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.AccountDataItem_UpdateNormalProp)) {
|
||||
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::AccountDataItem_UpdateNormalProp);
|
||||
contextRecord->EFlags &= ~0x100; // clear the trap flag
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index)
|
||||
{
|
||||
using namespace Globals;
|
||||
|
||||
if (index > 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
CONTEXT ctx{};
|
||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
GetThreadContext(thread, &ctx);
|
||||
|
||||
DWORD64* dr = &ctx.Dr0;
|
||||
dr[index] = enable ? address : 0;
|
||||
|
||||
const auto mask = 1ull << (index * 2);
|
||||
ctx.Dr7 |= mask;
|
||||
|
||||
SetThreadContext(thread, &ctx);
|
||||
}
|
||||
|
||||
DWORD __stdcall ThreadProc(LPVOID hInstance)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
AllocConsole();
|
||||
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
|
||||
system("pause");
|
||||
#endif
|
||||
InitializeCriticalSection(&CriticalSection);
|
||||
|
||||
auto initFuture = std::async(std::launch::async, InitIL2CPP);
|
||||
|
||||
using namespace Globals;
|
||||
const auto pid = GetCurrentProcessId();
|
||||
|
||||
while ((GameWindow = Util::FindMainWindowByPID(pid)) == nullptr) {
|
||||
SwitchToThread();
|
||||
}
|
||||
|
||||
if (!initFuture.get())
|
||||
ExitProcess(0);
|
||||
|
||||
MessagePipe = CreateFileA(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (!MessagePipe)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
std::println("CreateFile failed: {}", GetLastError());
|
||||
#else
|
||||
Util::Win32ErrorDialog(1001, GetLastError());
|
||||
ExitProcess(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
|
||||
while (true)
|
||||
{
|
||||
THREADENTRY32 te32{};
|
||||
te32.dwSize = sizeof(THREADENTRY32);
|
||||
const auto hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
for (Thread32First(hSnapshot, &te32); Thread32Next(hSnapshot, &te32);)
|
||||
{
|
||||
if (te32.th32OwnerProcessID != pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID))
|
||||
{
|
||||
EnterCriticalSection(&CriticalSection);
|
||||
SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true, 0);
|
||||
SetBreakpoint(hThread, Offset.AccountDataItem_UpdateNormalProp, true, 1);
|
||||
CloseHandle(hThread);
|
||||
LeaveCriticalSection(&CriticalSection);
|
||||
}
|
||||
}
|
||||
CloseHandle(hSnapshot);
|
||||
Sleep(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// DLL entry point
|
||||
BOOL __stdcall DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved)
|
||||
{
|
||||
// Check injectee
|
||||
WCHAR szFileName[MAX_PATH]{};
|
||||
DWORD length = 0;
|
||||
GetModuleFileNameW(NULL, szFileName, MAX_PATH);
|
||||
CharUpperBuffW(szFileName, wcslen(szFileName));
|
||||
if (!(wcsstr(szFileName, L"YUANSHEN.EXE") || wcsstr(szFileName, L"GENSHINIMPACT.EXE")))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (fdwReason == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
if (hInstance)
|
||||
{
|
||||
LdrAddRefDll(LDR_ADDREF_DLL_PIN, hInstance);
|
||||
}
|
||||
|
||||
if (const auto hThread = CreateThread(nullptr, 0, ThreadProc, hInstance, 0, nullptr)) {
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static LRESULT WINAPI YaeGetWindowHookImpl(int code, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
return CallNextHookEx(NULL, code, wParam, lParam);
|
||||
}
|
||||
|
||||
EXTERN_C __declspec(dllexport) HRESULT WINAPI YaeGetWindowHook(_Out_ HOOKPROC* pHookProc)
|
||||
{
|
||||
*pHookProc = YaeGetWindowHookImpl;
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <unordered_set>
|
||||
#include "NamedPipe.h"
|
||||
|
||||
#define PROPERTY2(type, name, cn, os) \
|
||||
type name##_cn = cn; \
|
||||
type name##_os = os; \
|
||||
type get_##name() { return Globals::IsCNREL ? name##_cn : name##_os; } \
|
||||
void set_##name(type value) { if (Globals::IsCNREL) name##_cn = value; else name##_os = value; } \
|
||||
__declspec(property(get = get_##name, put = set_##name)) type name;
|
||||
|
||||
namespace Globals
|
||||
{
|
||||
inline HWND GameWindow = nullptr;
|
||||
inline NamedPipe MessagePipe = nullptr;
|
||||
inline bool IsCNREL = true;
|
||||
inline uintptr_t BaseAddress = 0;
|
||||
|
||||
// 5.1.0 - 24082
|
||||
inline uint16_t AchievementId = 0; // use non-zero to override dynamic search
|
||||
inline std::unordered_set<uint16_t> AchievementIdSet;
|
||||
|
||||
// 5.3.0 - 23233
|
||||
inline uint16_t PlayerStoreId = 0; // use non-zero to override dynamic search
|
||||
|
||||
inline bool AchievementsWritten = false;
|
||||
inline bool PlayerStoreWritten = false;
|
||||
|
||||
/*
|
||||
* PROP_PLAYER_HCOIN = 10015,
|
||||
* PROP_PLAYER_WAIT_SUB_HCOIN = 10022,
|
||||
* PROP_PLAYER_SCOIN = 10016,
|
||||
* PROP_PLAYER_WAIT_SUB_SCOIN = 10023,
|
||||
* PROP_PLAYER_MCOIN = 10025,
|
||||
* PROP_PLAYER_WAIT_SUB_MCOIN = 10026,
|
||||
* PROP_PLAYER_HOME_COIN = 10042,
|
||||
* PROP_PLAYER_WAIT_SUB_HOME_COIN = 10043,
|
||||
* PROP_PLAYER_ROLE_COMBAT_COIN = 10053,
|
||||
* PROP_PLAYER_MUSIC_GAME_BOOK_COIN = 10058,
|
||||
*/
|
||||
inline std::unordered_set<int> RequiredPlayerProperties = { 10015, 10022, 10016, 10023, 10025, 10026, 10042, 10043, 10053, 10058 };
|
||||
|
||||
class Offsets
|
||||
{
|
||||
public:
|
||||
PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0, 0);
|
||||
//PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0x0F826CF0, 0x0F825F10); // use non-zero to override dynamic search
|
||||
PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0, 0);
|
||||
//PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0x0D9FE060, 0x0D94D910); // use non-zero to override dynamic search
|
||||
};
|
||||
|
||||
inline Offsets Offset;
|
||||
}
|
||||
@@ -1,588 +0,0 @@
|
||||
#include <Windows.h>
|
||||
#include <print>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <immintrin.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "Zydis.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
class DecodedInstruction
|
||||
{
|
||||
public:
|
||||
DecodedInstruction() = default;
|
||||
~DecodedInstruction() = default;
|
||||
DecodedInstruction(const ZydisDecodedInstruction& instruction) : Instruction(instruction) {}
|
||||
DecodedInstruction(const ZydisDecodedInstruction& instruction, ZydisDecodedOperand* operands, uint8_t operandCount) : Instruction(instruction) {
|
||||
Operands = { operands, operands + operandCount };
|
||||
}
|
||||
DecodedInstruction(const uint32_t rva, const ZydisDecodedInstruction& instruction, ZydisDecodedOperand* operands, uint8_t operandCount) : RVA(rva), Instruction(instruction) {
|
||||
Operands = { operands, operands + operandCount };
|
||||
}
|
||||
|
||||
// copy constructor
|
||||
DecodedInstruction(const DecodedInstruction& other) = default;
|
||||
|
||||
// move constructor
|
||||
DecodedInstruction(DecodedInstruction&& other) noexcept : RVA(other.RVA), Instruction(other.Instruction), Operands(std::move(other.Operands)) {}
|
||||
|
||||
uint32_t RVA = 0;
|
||||
ZydisDecodedInstruction Instruction;
|
||||
std::vector<ZydisDecodedOperand> Operands;
|
||||
};
|
||||
|
||||
std::span<uint8_t> GetSection(LPCSTR name)
|
||||
{
|
||||
using namespace Globals;
|
||||
if (BaseAddress == 0)
|
||||
return {};
|
||||
|
||||
const auto dosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
|
||||
const auto ntHeader = (PIMAGE_NT_HEADERS)((uintptr_t)dosHeader + dosHeader->e_lfanew);
|
||||
const auto sectionHeader = IMAGE_FIRST_SECTION(ntHeader);
|
||||
|
||||
for (auto i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
|
||||
{
|
||||
if (strcmp((char*)sectionHeader[i].Name, name) == 0)
|
||||
{
|
||||
const auto sectionSize = sectionHeader[i].Misc.VirtualSize;
|
||||
const auto virtualAddress = BaseAddress + sectionHeader[i].VirtualAddress;
|
||||
return std::span(reinterpret_cast<uint8_t*>(virtualAddress), sectionSize);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// decodes all instruction until next push, ignores branching
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="maxInstructions"></param>
|
||||
/// <returns>std::vector DecodedInstruction</returns>
|
||||
std::vector<DecodedInstruction> DecodeFunction(uintptr_t address, int32_t maxInstructions = -1)
|
||||
{
|
||||
using namespace Globals;
|
||||
|
||||
std::vector<DecodedInstruction> instructions;
|
||||
|
||||
ZydisDecoder decoder{};
|
||||
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
||||
|
||||
ZydisDecodedInstruction instruction{};
|
||||
ZydisDecoderContext context{};
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE]{};
|
||||
|
||||
while (true)
|
||||
{
|
||||
const auto data = reinterpret_cast<uint8_t*>(address);
|
||||
auto status = ZydisDecoderDecodeInstruction(&decoder, &context, data, ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction);
|
||||
if (!ZYAN_SUCCESS(status))
|
||||
{
|
||||
// for skipping jump tables
|
||||
address += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
status = ZydisDecoderDecodeOperands(&decoder, &context, &instruction, operands, instruction.operand_count_visible);
|
||||
if (!ZYAN_SUCCESS(status))
|
||||
{
|
||||
// for skipping jump tables
|
||||
address += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instruction.mnemonic == ZYDIS_MNEMONIC_PUSH && !instructions.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto rva = static_cast<uint32_t>(address - BaseAddress);
|
||||
instructions.emplace_back(rva, instruction, operands, instruction.operand_count_visible);
|
||||
|
||||
address += instruction.length;
|
||||
|
||||
if (maxInstructions != -1 && instructions.size() >= maxInstructions)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get the count of data references in the instructions (only second oprand of mov)
|
||||
/// </summary>
|
||||
/// <param name="instructions"></param>
|
||||
/// <returns></returns>
|
||||
int32_t GetDataReferenceCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
|
||||
if (instr.Instruction.mnemonic != ZYDIS_MNEMONIC_MOV)
|
||||
return false;
|
||||
|
||||
if (instr.Operands.size() != 2)
|
||||
return false;
|
||||
|
||||
const auto& op = instr.Operands[1];
|
||||
|
||||
// access to memory, based off of rip, 32-bit displacement
|
||||
return op.type == ZYDIS_OPERAND_TYPE_MEMORY && op.mem.base == ZYDIS_REGISTER_RIP && op.mem.disp.has_displacement;
|
||||
}));
|
||||
}
|
||||
|
||||
int32_t GetCallCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
|
||||
return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL;
|
||||
}));
|
||||
}
|
||||
|
||||
int32_t GetUniqueCallCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
std::unordered_set<uint32_t> calls;
|
||||
for (const auto& instr : instructions)
|
||||
{
|
||||
if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) {
|
||||
uint32_t destination = instr.Operands[0].imm.value.s + instr.RVA + instr.Instruction.length;
|
||||
calls.insert(destination);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(calls.size());
|
||||
}
|
||||
|
||||
int32_t GetCmpImmCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
|
||||
return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP && instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && instr.Operands[1].imm.value.u;
|
||||
}));
|
||||
}
|
||||
|
||||
void ResolveAchivementCmdId()
|
||||
{
|
||||
if (Globals::AchievementId != 0)
|
||||
return;
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
|
||||
std::println("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
|
||||
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
|
||||
|
||||
if (il2cppSection.empty())
|
||||
return; // message box?
|
||||
|
||||
const auto candidates = Util::PatternScanAll(il2cppSection, "56 48 83 EC 20 48 89 D0 48 89 CE 80 3D ? ? ? ? 00");
|
||||
std::println("Candidates: {}", candidates.size());
|
||||
|
||||
std::vector<std::vector<DecodedInstruction>> filteredInstructions;
|
||||
std::ranges::copy_if(
|
||||
candidates | std::views::transform([](auto va) { return DecodeFunction(va); }),
|
||||
std::back_inserter(filteredInstructions),
|
||||
[](const std::vector<DecodedInstruction>& instr) {
|
||||
return GetDataReferenceCount(instr) == 5 && GetCallCount(instr) == 10 &&
|
||||
GetUniqueCallCount(instr) == 6 && GetCmpImmCount(instr) == 5;
|
||||
});
|
||||
|
||||
// should have only one result
|
||||
if (filteredInstructions.size() != 1)
|
||||
{
|
||||
std::println("Filtered Instructions: {}", filteredInstructions.size());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& instructions = filteredInstructions[0];
|
||||
std::println("RVA: 0x{:08X}", instructions.front().RVA);
|
||||
|
||||
// extract all the non-zero immediate values from the cmp instructions
|
||||
std::vector<uint32_t> cmdIds;
|
||||
std::ranges::for_each(instructions, [&cmdIds](const DecodedInstruction& instr) {
|
||||
if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP &&
|
||||
instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||
instr.Operands[1].imm.value.u != 0) {
|
||||
cmdIds.push_back(static_cast<uint32_t>(instr.Operands[1].imm.value.u));
|
||||
}
|
||||
});
|
||||
|
||||
for (const auto& cmdId : cmdIds)
|
||||
{
|
||||
std::println("AchievementId: {}", cmdId);
|
||||
Globals::AchievementIdSet.insert(static_cast<uint16_t>(cmdId));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
std::vector<uintptr_t> GetCalls(uint8_t* target)
|
||||
{
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
const auto sectionAddress = reinterpret_cast<uintptr_t>(il2cppSection.data());
|
||||
const auto sectionSize = il2cppSection.size();
|
||||
|
||||
std::vector<uintptr_t> callSites;
|
||||
const __m128i callOpcode = _mm_set1_epi8(0xE8);
|
||||
const size_t simdEnd = sectionSize / 16 * 16;
|
||||
|
||||
for (size_t i = 0; i < simdEnd; i += 16) {
|
||||
// load 16 bytes from the current address
|
||||
const __m128i chunk = _mm_loadu_si128((__m128i*)(sectionAddress + i));
|
||||
|
||||
// compare the loaded chunk with 0xE8 in all 16 bytes
|
||||
const __m128i result = _mm_cmpeq_epi8(chunk, callOpcode);
|
||||
|
||||
// move the comparison results into a mask
|
||||
int mask = _mm_movemask_epi8(result);
|
||||
|
||||
while (mask != 0) {
|
||||
DWORD first_match_idx = 0;
|
||||
_BitScanForward(&first_match_idx, mask); // index of the first set bit (match)
|
||||
|
||||
// index of the instruction
|
||||
const size_t instruction_index = i + first_match_idx;
|
||||
|
||||
const int32_t delta = *(int32_t*)(sectionAddress + instruction_index + 1);
|
||||
const uintptr_t dest = sectionAddress + instruction_index + 5 + delta;
|
||||
|
||||
if (dest == (uintptr_t)target) {
|
||||
callSites.push_back(sectionAddress + instruction_index);
|
||||
}
|
||||
|
||||
// clear the bit we just processed and continue with the next match
|
||||
mask &= ~(1 << first_match_idx);
|
||||
}
|
||||
}
|
||||
|
||||
return callSites;
|
||||
}
|
||||
|
||||
uintptr_t FindFunctionEntry(uintptr_t address) // not a correct way to find function entry
|
||||
{
|
||||
__try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// go back to 'sub rsp' instruction
|
||||
uint32_t code = *(uint32_t*)address;
|
||||
code &= ~0xFF000000;
|
||||
|
||||
if (_byteswap_ulong(code) == 0x4883EC00) { // sub rsp, ??
|
||||
return address;
|
||||
}
|
||||
|
||||
address--;
|
||||
}
|
||||
|
||||
}
|
||||
__except (1) {}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
void Resolve_BitConverter_ToUInt16()
|
||||
{
|
||||
if (Globals::Offset.BitConverter_ToUInt16 != 0) {
|
||||
Globals::Offset.BitConverter_ToUInt16 += Globals::BaseAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
|
||||
std::print("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
|
||||
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
|
||||
|
||||
/*
|
||||
mov ecx, 0Fh
|
||||
call ThrowHelper.ThrowArgumentNullException
|
||||
mov ecx, 0Eh
|
||||
mov edx, 16h
|
||||
call ThrowHelper.ThrowArgumentOutOfRangeException
|
||||
mov ecx, 5
|
||||
call ThrowHelper.ThrowArgumentException
|
||||
*/
|
||||
auto candidates = Util::PatternScanAll(il2cppSection, "B9 0F 00 00 00 E8 ? ? ? ? B9 0E 00 00 00 BA 16 00 00 00 E8 ? ? ? ? B9 05 00 00 00 E8 ? ? ? ?");
|
||||
std::println("Candidates: {}", candidates.size());
|
||||
|
||||
std::vector<uintptr_t> filteredEntries;
|
||||
std::ranges::copy_if(candidates, std::back_inserter(filteredEntries), [](uintptr_t& entry) {
|
||||
entry = FindFunctionEntry(entry);
|
||||
return entry % 16 == 0;
|
||||
});
|
||||
|
||||
for (const auto& entry : filteredEntries)
|
||||
{
|
||||
std::println("Entry: 0x{:X}", entry);
|
||||
}
|
||||
|
||||
std::println("Looking for call counts...");
|
||||
std::mutex mutex;
|
||||
std::unordered_map<uintptr_t, int32_t> callCounts;
|
||||
// find the call counts to candidate functions
|
||||
std::vector<std::future<void>> futures;
|
||||
std::ranges::transform(filteredEntries, std::back_inserter(futures), [&](uintptr_t entry) {
|
||||
return std::async(std::launch::async, [&](uintptr_t e) {
|
||||
const auto callSites = GetCalls((uint8_t*)e);
|
||||
std::lock_guard lock(mutex);
|
||||
callCounts[e] = callSites.size();
|
||||
}, entry);
|
||||
});
|
||||
|
||||
for (auto& future : futures) {
|
||||
future.get();
|
||||
}
|
||||
|
||||
uintptr_t targetEntry = 0;
|
||||
for (const auto& [entry, count] : callCounts)
|
||||
{
|
||||
std::println("Entry: 0x{:X}, RVA: 0x{:08X}, Count: {}", entry, entry - Globals::BaseAddress, count);
|
||||
if (count == 3) {
|
||||
targetEntry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
Globals::Offset.BitConverter_ToUInt16 = targetEntry;
|
||||
}
|
||||
|
||||
void ResolveInventoryCmdId()
|
||||
{
|
||||
if (Globals::PlayerStoreId != 0)
|
||||
return;
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
std::println("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
|
||||
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
|
||||
|
||||
/*
|
||||
cmp r8d, 2
|
||||
jz 0x3B
|
||||
cmd r8d, 1
|
||||
mov rax
|
||||
*/
|
||||
|
||||
// look for ItemModule.GetBagManagerByStoreType <- mf got inlined in 5.5
|
||||
// we just gon to look for OnPlayerStoreNotify
|
||||
const auto candidates = Util::PatternScanAll(il2cppSection, "41 83 F8 02 B8 ? ? ? ? B9 ? ? ? ? 48 0F 45 C1");
|
||||
std::println("Candidates: {}", candidates.size());
|
||||
if (candidates.empty())
|
||||
return;
|
||||
|
||||
uintptr_t pOnPlayerStoreNotify = 0;
|
||||
{
|
||||
// one of the candidates is OnPlayerStoreNotify
|
||||
// search after the pattern to find an arbirary branch
|
||||
auto decodedInstructions = candidates | std::views::transform([](auto va) { return DecodeFunction(va, 20); });
|
||||
|
||||
// find the call site with an arbitrary branch (JMP or CALL) after the call
|
||||
auto targetInstructions = std::ranges::find_if(decodedInstructions, [](const auto& instr) {
|
||||
return std::ranges::any_of(instr, [](const DecodedInstruction& i) {
|
||||
return (i.Instruction.mnemonic == ZYDIS_MNEMONIC_JMP || i.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) &&
|
||||
i.Operands.size() == 1 && i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER;
|
||||
});
|
||||
});
|
||||
|
||||
if (targetInstructions == decodedInstructions.end()) {
|
||||
std::println("Failed to find target instruction");
|
||||
return;
|
||||
}
|
||||
|
||||
// ItemModule.OnPlayerStoreNotify
|
||||
const auto& instructions = *targetInstructions;
|
||||
pOnPlayerStoreNotify = Globals::BaseAddress + instructions.front().RVA;
|
||||
|
||||
const auto isFunctionEntry = [](uintptr_t va) -> bool {
|
||||
auto* code = reinterpret_cast<uint8_t*>(va);
|
||||
return (va % 16 == 0 &&
|
||||
code[0] == 0x56 && // push rsi
|
||||
(*reinterpret_cast<uint32_t*>(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
|
||||
};
|
||||
|
||||
auto range = std::views::iota(0, 126);
|
||||
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPlayerStoreNotify - i); });
|
||||
it != range.end())
|
||||
{
|
||||
pOnPlayerStoreNotify -= *it;
|
||||
}
|
||||
else {
|
||||
std::println("Failed to find function entry");
|
||||
return;
|
||||
}
|
||||
|
||||
std::println("OnPlayerStoreNotify: 0x{:X}", pOnPlayerStoreNotify);
|
||||
}
|
||||
|
||||
uintptr_t pOnPacket = 0;
|
||||
{
|
||||
// get all calls to OnPlayerStoreNotify
|
||||
const auto calls = GetCalls(reinterpret_cast<uint8_t*>(pOnPlayerStoreNotify));
|
||||
if (calls.size() != 1) {
|
||||
std::println("Failed to find call site");
|
||||
return;
|
||||
}
|
||||
|
||||
// ItemModule.OnPacket - search backwards for function entry
|
||||
pOnPacket = calls.front();
|
||||
const auto isFunctionEntry = [](uintptr_t va) -> bool {
|
||||
auto* code = reinterpret_cast<uint8_t*>(va);
|
||||
return (va % 16 == 0 &&
|
||||
code[0] == 0x56 && // push rsi
|
||||
(*reinterpret_cast<uint32_t*>(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
|
||||
};
|
||||
|
||||
auto range = std::views::iota(0, 3044);
|
||||
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPacket - i); });
|
||||
it != range.end())
|
||||
{
|
||||
pOnPacket -= *it;
|
||||
}
|
||||
else {
|
||||
std::println("Failed to find function entry");
|
||||
return;
|
||||
}
|
||||
|
||||
std::println("OnPacket: 0x{:X}", pOnPacket);
|
||||
}
|
||||
|
||||
const auto decodedInstructions = DecodeFunction(pOnPacket);
|
||||
uint32_t cmdid = 0;
|
||||
std::ranges::for_each(decodedInstructions, [&cmdid, pOnPlayerStoreNotify](const DecodedInstruction& i) {
|
||||
static uint32_t immValue = 0; // keep track of the last immediate value
|
||||
|
||||
if (i.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP &&
|
||||
i.Operands.size() == 2 &&
|
||||
i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||
i.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE)
|
||||
{
|
||||
immValue = static_cast<uint32_t>(i.Operands[1].imm.value.u);
|
||||
}
|
||||
|
||||
if (i.Instruction.meta.branch_type == ZYDIS_BRANCH_TYPE_NEAR && i.Operands.size() == 1 &&
|
||||
(i.Instruction.mnemonic == ZYDIS_MNEMONIC_JZ || i.Instruction.mnemonic == ZYDIS_MNEMONIC_JNZ)) // jz for true branch, jnz for false branch
|
||||
{
|
||||
// assume the branching is jz
|
||||
uintptr_t branchAddr = Globals::BaseAddress + i.RVA + i.Instruction.length + i.Operands[0].imm.value.s;
|
||||
|
||||
// check if the branch is jnz and adjust the branch address
|
||||
if (i.Instruction.mnemonic == ZYDIS_MNEMONIC_JNZ) {
|
||||
branchAddr = Globals::BaseAddress + i.RVA + i.Instruction.length;
|
||||
}
|
||||
|
||||
// decode the branch address immediately
|
||||
const auto instructions = DecodeFunction(branchAddr, 10);
|
||||
const auto isMatch = std::ranges::any_of(instructions, [pOnPlayerStoreNotify](const DecodedInstruction& instr) {
|
||||
if (instr.Instruction.mnemonic != ZYDIS_MNEMONIC_CALL)
|
||||
return false;
|
||||
|
||||
uintptr_t destination = 0;
|
||||
ZydisCalcAbsoluteAddress(&instr.Instruction, instr.Operands.data(), Globals::BaseAddress + instr.RVA, &destination);
|
||||
return destination == pOnPlayerStoreNotify;
|
||||
});
|
||||
|
||||
if (isMatch) {
|
||||
cmdid = immValue;
|
||||
}
|
||||
|
||||
}
|
||||
return cmdid == 0; // stop processing if cmdid is found
|
||||
});
|
||||
|
||||
Globals::PlayerStoreId = static_cast<uint16_t>(cmdid);
|
||||
std::println("PlayerStoreId: {}", Globals::PlayerStoreId);
|
||||
}
|
||||
|
||||
void Resolve_AccountDataItem_UpdateNormalProp()
|
||||
{
|
||||
if (Globals::Offset.AccountDataItem_UpdateNormalProp != 0) {
|
||||
Globals::Offset.AccountDataItem_UpdateNormalProp += Globals::BaseAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
|
||||
/*
|
||||
add ??, 0FFFFD8EEh
|
||||
cmp ??, 30h
|
||||
*/
|
||||
auto candidates = Util::PatternScanAll(il2cppSection, "81 ? EE D8 FF FF ? 83 ? 30");
|
||||
// should have only one result
|
||||
if (candidates.size() != 1)
|
||||
{
|
||||
std::println("Filtered Instructions: {}", candidates.size());
|
||||
return;
|
||||
}
|
||||
auto fp = candidates[0];
|
||||
|
||||
const auto isFunctionEntry = [](uintptr_t va) -> bool {
|
||||
auto* code = reinterpret_cast<uint8_t*>(va);
|
||||
/* push rsi */
|
||||
/* push rdi */
|
||||
return (va % 16 == 0 && code[0] == 0x56 && code[1] == 0x57);
|
||||
};
|
||||
|
||||
auto range = std::views::iota(0, 213);
|
||||
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(fp - i); }); it != range.end()) {
|
||||
fp -= *it;
|
||||
} else {
|
||||
std::println("Failed to find function entry");
|
||||
return;
|
||||
}
|
||||
|
||||
Globals::Offset.AccountDataItem_UpdateNormalProp = fp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool InitIL2CPP()
|
||||
{
|
||||
std::string buffer;
|
||||
buffer.resize(MAX_PATH);
|
||||
ZeroMemory(buffer.data(), MAX_PATH);
|
||||
const auto pathLength = GetModuleFileNameA(nullptr, buffer.data(), MAX_PATH);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
buffer.resize(pathLength);
|
||||
ZeroMemory(buffer.data(), pathLength);
|
||||
GetModuleFileNameA(nullptr, buffer.data(), pathLength);
|
||||
}
|
||||
buffer.shrink_to_fit();
|
||||
|
||||
using namespace Globals;
|
||||
IsCNREL = buffer.find("YuanShen.exe") != std::string::npos;
|
||||
BaseAddress = (uintptr_t)GetModuleHandleA(nullptr);
|
||||
|
||||
std::future<void> resolveFuncFuture = std::async(std::launch::async, Resolve_BitConverter_ToUInt16);
|
||||
std::future<void> resolveCmdIdFuture = std::async(std::launch::async, ResolveAchivementCmdId);
|
||||
std::future<void> resolveInventoryFuture = std::async(std::launch::async, ResolveInventoryCmdId);
|
||||
std::future<void> resolveUpdatePropFuture = std::async(std::launch::async, Resolve_AccountDataItem_UpdateNormalProp);
|
||||
|
||||
resolveFuncFuture.get();
|
||||
resolveCmdIdFuture.get();
|
||||
resolveInventoryFuture.get();
|
||||
resolveUpdatePropFuture.get();
|
||||
|
||||
std::println("BaseAddress: 0x{:X}", BaseAddress);
|
||||
std::println("IsCNREL: {:d}", IsCNREL);
|
||||
std::println("BitConverter_ToUInt16: 0x{:X}", Offset.BitConverter_ToUInt16);
|
||||
std::println("AccountDataItem_UpdateNormalProp: 0x{:X}", Offset.AccountDataItem_UpdateNormalProp);
|
||||
|
||||
if (!AchievementId && AchievementIdSet.empty())
|
||||
{
|
||||
Util::ErrorDialog("Failed to resolve achievement data");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PlayerStoreId)
|
||||
{
|
||||
Util::ErrorDialog("Failed to resolve inventory data");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// IL2CPP application initializer
|
||||
bool InitIL2CPP();
|
||||
@@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
#define PROPERTY_GET_CONST(type, name, funcBody) \
|
||||
type get_##name() const funcBody \
|
||||
__declspec(property(get = get_##name)) type name;
|
||||
|
||||
enum class PacketType : uint8_t
|
||||
{
|
||||
None = 0,
|
||||
Achivement = 1,
|
||||
Inventory = 2,
|
||||
PropData = 100,
|
||||
End = 255,
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Array
|
||||
{
|
||||
public:
|
||||
void* klass;
|
||||
void* monitor;
|
||||
void* bounds;
|
||||
size_t max_length;
|
||||
T vector[1];
|
||||
|
||||
Array() = delete;
|
||||
|
||||
T* data() {
|
||||
return vector;
|
||||
}
|
||||
|
||||
std::span<T> AsSpan() {
|
||||
return { vector, max_length };
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
U As() {
|
||||
return reinterpret_cast<U>(vector);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(alignof(Array<uint8_t>) == 8, "Array alignment is incorrect");
|
||||
static_assert(offsetof(Array<uint8_t>, vector) == 32, "vector offset is incorrect");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class PacketMeta
|
||||
{
|
||||
uint16_t m_HeadMagic;
|
||||
uint16_t m_CmdId;
|
||||
uint16_t m_HeaderLength;
|
||||
uint32_t m_DataLength;
|
||||
uint8_t m_Data[1];
|
||||
public:
|
||||
|
||||
PacketMeta() = delete;
|
||||
|
||||
PROPERTY_GET_CONST(uint16_t, HeadMagic, { return _byteswap_ushort(m_HeadMagic); });
|
||||
PROPERTY_GET_CONST(uint16_t, CmdId, { return _byteswap_ushort(m_CmdId); });
|
||||
PROPERTY_GET_CONST(uint16_t, HeaderLength, { return _byteswap_ushort(m_HeaderLength); });
|
||||
PROPERTY_GET_CONST(uint32_t, DataLength, { return _byteswap_ulong(m_DataLength); });
|
||||
|
||||
std::span<uint8_t> AsSpan() {
|
||||
return { m_Data + HeaderLength, DataLength };
|
||||
}
|
||||
|
||||
friend struct PacketMetaStaticAssertHelper;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct PacketMetaStaticAssertHelper
|
||||
{
|
||||
static_assert(offsetof(PacketMeta, m_CmdId) == 2, "CmdId offset is incorrect");
|
||||
static_assert(offsetof(PacketMeta, m_HeaderLength) == 4, "HeadLength offset is incorrect");
|
||||
static_assert(offsetof(PacketMeta, m_DataLength) == 6, "DataLength offset is incorrect");
|
||||
static_assert(offsetof(PacketMeta, m_Data) == 10, "Data offset is incorrect");
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <bcrypt.h>
|
||||
|
||||
#pragma comment(lib, "ntdll.lib")
|
||||
|
||||
#define LDR_ADDREF_DLL_PIN 0x00000001
|
||||
|
||||
EXTERN_C NTSYSAPI NTSTATUS NTAPI LdrAddRefDll(_In_ ULONG Flags, _In_ PVOID DllHandle);
|
||||
210
lib/src/util.cpp
210
lib/src/util.cpp
@@ -1,210 +0,0 @@
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <ranges>
|
||||
#include <intrin.h>
|
||||
#include "util.h"
|
||||
|
||||
#include "globals.h"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#pragma runtime_checks("", off)
|
||||
#endif
|
||||
|
||||
#pragma region FindMainWindowByPID
|
||||
|
||||
namespace
|
||||
{
|
||||
struct HandleData {
|
||||
DWORD Pid;
|
||||
HWND Hwnd;
|
||||
};
|
||||
|
||||
bool IsMainWindow(HWND handle) {
|
||||
return GetWindow(handle, GW_OWNER) == nullptr && IsWindowVisible(handle) == TRUE;
|
||||
}
|
||||
|
||||
bool IsUnityWindow(HWND handle) {
|
||||
char szName[256]{};
|
||||
GetClassNameA(handle, szName, 256);
|
||||
return _stricmp(szName, "UnityWndClass") == 0;
|
||||
}
|
||||
|
||||
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) {
|
||||
HandleData& data = *(HandleData*)lParam;
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(handle, &pid);
|
||||
if (data.Pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
|
||||
return TRUE;
|
||||
data.Hwnd = handle;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::tuple<std::vector<uint8_t>, std::string> PatternToBytes(const char* pattern)
|
||||
{
|
||||
std::vector<uint8_t> bytes;
|
||||
std::string mask;
|
||||
|
||||
const auto start = const_cast<char*>(pattern);
|
||||
const auto end = const_cast<char*>(pattern) + strlen(pattern);
|
||||
|
||||
for (auto current = start; current < end; ++current) {
|
||||
if (*current == '?') {
|
||||
++current;
|
||||
if (*current == '?')
|
||||
++current;
|
||||
bytes.push_back(-1);
|
||||
mask.push_back('?');
|
||||
}
|
||||
else {
|
||||
bytes.push_back(strtoul(current, ¤t, 16));
|
||||
mask.push_back('x');
|
||||
}
|
||||
}
|
||||
return { bytes, mask };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
static constexpr LPCSTR base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
namespace Util
|
||||
{
|
||||
HWND FindMainWindowByPID(DWORD pid)
|
||||
{
|
||||
HandleData data = {
|
||||
.Pid = pid,
|
||||
.Hwnd = nullptr
|
||||
};
|
||||
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
|
||||
return data.Hwnd;
|
||||
}
|
||||
|
||||
std::string Base64Encode(std::span<uint8_t> data)
|
||||
{
|
||||
return Base64Encode(data.data(), data.size());
|
||||
}
|
||||
|
||||
std::string Base64Encode(uint8_t const* buf, size_t bufLen)
|
||||
{
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
uint8_t char_array_3[3];
|
||||
uint8_t char_array_4[4];
|
||||
while (bufLen--) {
|
||||
char_array_3[i++] = *buf++;
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
for (i = 0; (i < 4); i++)
|
||||
ret += base64_chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (i) {
|
||||
int j;
|
||||
for (j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
for (j = 0; j < i + 1; j++)
|
||||
ret += base64_chars[char_array_4[j]];
|
||||
while (i++ < 3)
|
||||
ret += '=';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ErrorDialog(LPCSTR title, LPCSTR msg)
|
||||
{
|
||||
MessageBoxA(Globals::GameWindow, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
|
||||
}
|
||||
|
||||
void ErrorDialog(LPCSTR msg)
|
||||
{
|
||||
ErrorDialog("YaeAchievement", msg);
|
||||
}
|
||||
|
||||
void Win32ErrorDialog(DWORD code, DWORD winerrcode)
|
||||
{
|
||||
const std::string msg = "CRITICAL ERROR!\nError code: " + std::to_string(winerrcode) + "-" + std::to_string(code) +
|
||||
"\n\nPlease take the screenshot and contact developer by GitHub Issue to solve this problem\nNOT MIHOYO/COGNOSPHERE CUSTOMER SERVICE!";
|
||||
|
||||
ErrorDialog("YaeAchievement", msg.c_str());
|
||||
}
|
||||
|
||||
std::vector<uintptr_t> PatternScanAll(std::span<uint8_t> bytes, const char* pattern)
|
||||
{
|
||||
std::vector<uintptr_t> results;
|
||||
const auto [patternBytes, patternMask] = PatternToBytes(pattern);
|
||||
constexpr std::size_t chunkSize = 16;
|
||||
|
||||
const auto maskCount = static_cast<std::size_t>(std::ceil(patternMask.size() / chunkSize));
|
||||
std::array<int32_t, 32> masks{};
|
||||
|
||||
auto chunks = patternMask | std::views::chunk(chunkSize);
|
||||
for (std::size_t i = 0; auto chunk : chunks) {
|
||||
int32_t mask = 0;
|
||||
for (std::size_t j = 0; j < chunk.size(); ++j) {
|
||||
if (chunk[j] == 'x') {
|
||||
mask |= 1 << j;
|
||||
}
|
||||
}
|
||||
masks[i++] = mask;
|
||||
}
|
||||
|
||||
__m128i xmm1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(patternBytes.data()));
|
||||
__m128i xmm2, xmm3, mask;
|
||||
|
||||
auto pData = bytes.data();
|
||||
const auto end = pData + bytes.size() - patternMask.size();
|
||||
|
||||
while (pData < end)
|
||||
{
|
||||
_mm_prefetch(reinterpret_cast<const char*>(pData + 64), _MM_HINT_NTA);
|
||||
|
||||
if (patternBytes[0] == pData[0])
|
||||
{
|
||||
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData));
|
||||
mask = _mm_cmpeq_epi8(xmm1, xmm2);
|
||||
|
||||
if ((_mm_movemask_epi8(mask) & masks[0]) == masks[0])
|
||||
{
|
||||
bool found = true;
|
||||
for (int i = 1; i < maskCount; ++i)
|
||||
{
|
||||
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData + i * chunkSize));
|
||||
xmm3 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(patternBytes.data() + i * chunkSize));
|
||||
mask = _mm_cmpeq_epi8(xmm2, xmm3);
|
||||
if ((_mm_movemask_epi8(mask) & masks[i]) != masks[i])
|
||||
{
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (found) {
|
||||
results.push_back(reinterpret_cast<uintptr_t>(pData));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
++pData;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
#pragma runtime_checks("", restore)
|
||||
#endif
|
||||
@@ -1,19 +0,0 @@
|
||||
// ReSharper disable CppClangTidyClangDiagnosticLanguageExtensionToken
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
namespace Util
|
||||
{
|
||||
HWND FindMainWindowByPID(DWORD pid);
|
||||
std::string Base64Encode(std::span<uint8_t> data);
|
||||
std::string Base64Encode(uint8_t const* buf, size_t bufLen);
|
||||
|
||||
void ErrorDialog(LPCSTR title, LPCSTR msg);
|
||||
void ErrorDialog(LPCSTR msg);
|
||||
void Win32ErrorDialog(DWORD code, DWORD winerrcode);
|
||||
|
||||
std::vector<uintptr_t> PatternScanAll(std::span<uint8_t> bytes, const char* pattern);
|
||||
}
|
||||
Reference in New Issue
Block a user