Skip to content

Commit c17b01b

Browse files
committed
2024.14 fixes
- Fixes function array lookup and YYObjectBase::Add lookup on 2024.14 x64 - Fix error when attempting to check for a non-existent variable
1 parent 9b94d2f commit c17b01b

File tree

5 files changed

+247
-11
lines changed

5 files changed

+247
-11
lines changed

ExamplePlugin/include/YYToolkit/YYTK_Shared_Types.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,8 @@ bool YYTK::CInstance::ContainsValue(
389389
IN std::string_view MemberName
390390
) const
391391
{
392-
const RValue* member = this->GetRefMember(MemberName.data());
393-
394-
return member != nullptr;
392+
RValue self = this;
393+
return GetPrivateInterface()->RV_ContainsNestedValue(&self, MemberName);
395394
}
396395

397396
CInstance* YYTK::CInstance::FromInstanceID(

ExamplePlugin/source/ModuleMain.cpp

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
using namespace Aurie;
66
using namespace YYTK;
77

8+
static TRoutine g_OriginalFunction = nullptr;
9+
10+
void Hook(
11+
RValue* Result,
12+
CInstance* Self,
13+
CInstance* Other,
14+
int ArgumentCount,
15+
RValue* Arguments
16+
)
17+
{
18+
DbgPrintEx(LOG_SEVERITY_DEBUG, "instance_exists occurred.");
19+
return g_OriginalFunction(*Result, Self, Other, ArgumentCount, Arguments);
20+
}
21+
822
EXPORTED AurieStatus ModuleInitialize(
923
IN AurieModule* Module,
1024
IN const fs::path& ModulePath
@@ -13,9 +27,26 @@ EXPORTED AurieStatus ModuleInitialize(
1327
UNREFERENCED_PARAMETER(Module);
1428
UNREFERENCED_PARAMETER(ModulePath);
1529

16-
// We do a bit of trolling.
17-
YYTK::GetPrivateInterface()->YkSetRuntimeFlags(1);
18-
DbgPrint("[ExamplePlugin] Runtime flag bit 1 set.");
30+
TRoutine game_function = nullptr;
31+
32+
// Get a pointer to the target function using YYToolkit's interface
33+
GetInterface()->GetNamedRoutinePointer(
34+
"instance_exists",
35+
reinterpret_cast<PVOID*>(&game_function)
36+
);
37+
38+
DbgPrintEx(LOG_SEVERITY_DEBUG, "Got instance_exists at %p", game_function);
39+
40+
// Create the hook
41+
AurieStatus hook_status = MmCreateHook(
42+
g_ArSelfModule,
43+
"My Hook",
44+
game_function,
45+
Hook,
46+
reinterpret_cast<PVOID*>(&g_OriginalFunction)
47+
);
48+
49+
DbgPrintEx(LOG_SEVERITY_DEBUG, "We got status %s for the hook", AurieStatusToString(hook_status));
1950

2051
return AURIE_SUCCESS;
2152
}

YYToolkit/YYToolkit.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
104104
<MultiProcessorCompilation>true</MultiProcessorCompilation>
105105
<AdditionalOptions>/d1trimfile:"$(SolutionDir)\" %(AdditionalOptions)</AdditionalOptions>
106+
<Optimization>MaxSpeed</Optimization>
106107
</ClCompile>
107108
<Link>
108109
<SubSystem>Console</SubSystem>
@@ -141,6 +142,7 @@
141142
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
142143
<MultiProcessorCompilation>true</MultiProcessorCompilation>
143144
<AdditionalOptions>/d1trimfile:"$(SolutionDir)\" %(AdditionalOptions)</AdditionalOptions>
145+
<Optimization>MaxSpeed</Optimization>
144146
</ClCompile>
145147
<Link>
146148
<SubSystem>Console</SubSystem>

YYToolkit/source/YYTK/Module Internals/Hooks/Hooks.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ namespace YYTK
231231
}
232232

233233
description = std::format(
234-
"{}!{:06X}",
235-
filename,
234+
"{}+0x{:06X}",
235+
fs::path(filename).filename().string().c_str(),
236236
reinterpret_cast<uintptr_t>(function) - reinterpret_cast<uintptr_t>(ip_module)
237237
);
238238
}

YYToolkit/source/YYTK/Module Internals/Zeus/Zeus-x64.cpp

Lines changed: 207 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -663,10 +663,10 @@ AurieStatus YYTK::Zeus::FindSlotAllocationFunction(
663663
if (!YYObjectBase_Add)
664664
return AURIE_UNAVAILABLE;
665665

666-
// Disassemble 48 bytes at the function
666+
// Disassemble 96 bytes at the function
667667
auto instructions = Memory::DmDisassembleInstructionByRange(
668668
YYObjectBase_Add,
669-
0x30
669+
0x60
670670
);
671671

672672
// Find the first call, and trace the target
@@ -694,6 +694,180 @@ AurieStatus YYTK::Zeus::FindSlotAllocationFunction(
694694
return AURIE_SUCCESS;
695695
}
696696

697+
// These functions are only present on x64. Having them on x86 would just clutter up the headers.
698+
static AurieStatus FindCurrentFunction(
699+
IN const YYTK::YYRunnerInterface& Interface,
700+
OUT YYTK::RFunction*** g_pFunction
701+
)
702+
{
703+
using namespace YYTK;
704+
705+
// Disassemble YYGetPtr.
706+
auto instructions = Memory::DmDisassembleInstructionByRange(
707+
Interface.YYGetPtr,
708+
0x50
709+
);
710+
711+
// It just so happens the first mov that has a
712+
// memory operand references the the_functions array.
713+
// It usually looks like mov <64bit register>, [the_functions]
714+
for (auto& instruction : instructions)
715+
{
716+
// The instruction has to be a mov
717+
if (instruction.info.mnemonic != ZYDIS_MNEMONIC_MOV)
718+
continue;
719+
720+
// The instruction has to have 2 operands
721+
// The first one (operands[0]) is the register being moved into
722+
// The second one (operands[1]) is the address
723+
if (instruction.info.operand_count != 2)
724+
continue;
725+
726+
ZydisDecodedOperand& first_operand = instruction.operands[0];
727+
ZydisDecodedOperand& second_operand = instruction.operands[1];
728+
729+
// We have to be moving INTO a register, not FROM a register
730+
if (first_operand.type != ZYDIS_OPERAND_TYPE_REGISTER)
731+
continue;
732+
733+
// Get the register we're moving into, and get the largest variant of that register
734+
ZydisRegister largest_enclosing = ZydisRegisterGetLargestEnclosing(
735+
ZYDIS_MACHINE_MODE_LONG_64,
736+
first_operand.reg.value
737+
);
738+
739+
// If the register is already in its largest variant, we can continue
740+
// This is to filter out the_numb (the number of elements in the functions array),
741+
// which is being moved in the same way, just into a 32-bit register.
742+
743+
if (largest_enclosing != first_operand.reg.value)
744+
continue;
745+
746+
// We have to be moving from a memory location, not from a register
747+
if (second_operand.type != ZYDIS_OPERAND_TYPE_MEMORY)
748+
continue;
749+
750+
// There has to be an offset... duh
751+
if (!second_operand.mem.disp.has_displacement)
752+
continue;
753+
754+
// Calculate the absolute address
755+
ZyanU64 call_address = 0;
756+
ZydisCalcAbsoluteAddress(
757+
&instruction.info,
758+
&second_operand,
759+
instruction.runtime_address,
760+
&call_address
761+
);
762+
763+
// It's a pointer to a pointer, we dereference it once to
764+
// get the actual pointer to the first element in the array
765+
*g_pFunction = reinterpret_cast<RFunction**>(call_address);
766+
return AURIE_SUCCESS;
767+
}
768+
769+
return AURIE_OBJECT_NOT_FOUND;
770+
}
771+
772+
static AurieStatus FindFunctionsArrayWithScriptPerform(
773+
IN const YYTK::YYRunnerInterface& Interface,
774+
IN YYTK::RFunction** g_pFunctions,
775+
OUT YYTK::RFunction*** FunctionsArray
776+
)
777+
{
778+
using namespace YYTK;
779+
780+
// Disassemble instructions at Script_Perform
781+
auto instructions = Memory::DmDisassembleInstructionByRange(
782+
Interface.Script_Perform,
783+
0x100
784+
);
785+
786+
// Get the first MOV instruction that references g_pFunctions.
787+
auto current_function_iterator = std::find_if(
788+
instructions.begin(),
789+
instructions.end(),
790+
[g_pFunctions](IN const ZydisDisassembledInstruction& Instruction) -> bool
791+
{
792+
// Looking for a mov.
793+
if (Instruction.info.mnemonic != ZYDIS_MNEMONIC_MOV)
794+
return false;
795+
796+
const ZydisDecodedOperand& first_operand = Instruction.operands[0];
797+
const ZydisDecodedOperand& second_operand = Instruction.operands[1];
798+
799+
// Our mov should be moving to a register from a memory location.
800+
if (first_operand.type != ZYDIS_OPERAND_TYPE_REGISTER)
801+
return false;
802+
803+
if (second_operand.type != ZYDIS_OPERAND_TYPE_MEMORY)
804+
return false;
805+
806+
// Calculate the absolute address
807+
ZyanU64 call_address = 0;
808+
ZydisCalcAbsoluteAddress(
809+
&Instruction.info,
810+
&second_operand,
811+
Instruction.runtime_address,
812+
&call_address
813+
);
814+
815+
// Return true if it's referencing g_pFunctions.
816+
return call_address == reinterpret_cast<ZyanU64>(g_pFunctions);
817+
}
818+
);
819+
820+
if (current_function_iterator == instructions.end())
821+
{
822+
DbgPrintEx(LOG_SEVERITY_ERROR, "Failed to find g_pFunction reference in Script_Perform!");
823+
return AURIE_OBJECT_NOT_FOUND;
824+
}
825+
826+
// Get the index that current_function_iterator belongs to.
827+
ptrdiff_t current_function_index = std::distance(instructions.begin(), current_function_iterator);
828+
829+
// Go back in the instructions list from the g_pFunctions-referencing instruction until
830+
// we find another MOV instruction referencing a memory address.
831+
//
832+
// This will be our the_functions reference.
833+
for (auto i = (current_function_index - 1); i > 0; i--)
834+
{
835+
// Looking for a mov.
836+
if (instructions[i].info.mnemonic != ZYDIS_MNEMONIC_MOV)
837+
continue;
838+
839+
const ZydisDecodedOperand& first_operand = instructions[i].operands[0];
840+
const ZydisDecodedOperand& second_operand = instructions[i].operands[1];
841+
842+
// Our mov should be moving to a register from a memory location.
843+
if (first_operand.type != ZYDIS_OPERAND_TYPE_REGISTER)
844+
continue;
845+
846+
if (second_operand.type != ZYDIS_OPERAND_TYPE_MEMORY)
847+
continue;
848+
849+
// Calculate the absolute address
850+
ZyanU64 call_address = 0;
851+
ZydisCalcAbsoluteAddress(
852+
&instructions[i].info,
853+
&second_operand,
854+
instructions[i].runtime_address,
855+
&call_address
856+
);
857+
858+
if (call_address)
859+
{
860+
DbgPrintEx(LOG_SEVERITY_TRACE, "Found the_functions reference in Script_Perform (%s)", instructions[i].text);
861+
862+
*FunctionsArray = reinterpret_cast<RFunction**>(call_address);
863+
return AURIE_SUCCESS;
864+
}
865+
}
866+
867+
DbgPrintEx(LOG_SEVERITY_ERROR, "Failed to find the_functions reference in Script_Perform!");
868+
return AURIE_OBJECT_NOT_FOUND;
869+
}
870+
697871
AurieStatus YYTK::Zeus::YYC::FindFunctionsArray(
698872
IN const YYRunnerInterface& Interface,
699873
OUT RFunction*** FunctionsArray
@@ -702,7 +876,37 @@ AurieStatus YYTK::Zeus::YYC::FindFunctionsArray(
702876
if (!Interface.Code_Function_Find)
703877
return AURIE_MODULE_INTERNAL_ERROR;
704878

705-
// Disassemble this function
879+
// If we have Script_Perform (and YYGetPtr, but we always seem to have that),
880+
// chances are we're on a newer runner. We can use the v5-exclusive method of finding g_pFunction,
881+
// and then scanning Script_Perform for references to the true functions array.
882+
//
883+
// This bypasses the 2024.14 changes that make referencing the functions array from Code_Function_Find impossible.
884+
if (Interface.Script_Perform && Interface.YYGetPtr)
885+
{
886+
// Get the g_pFunction pointer. It will contain nullptr at this point, but we don't care about it's actual value.
887+
RFunction** g_pFunction = nullptr;
888+
AurieStatus last_status = FindCurrentFunction(
889+
Interface,
890+
&g_pFunction
891+
);
892+
893+
// Make sure we got it.
894+
if (!AurieSuccess(last_status))
895+
return last_status;
896+
897+
last_status = FindFunctionsArrayWithScriptPerform(
898+
Interface,
899+
g_pFunction,
900+
FunctionsArray
901+
);
902+
903+
return last_status;
904+
}
905+
906+
// If the required functions are unavailable, we resort to the old method of scanning Code_Function_Find, and
907+
// either have it work or crash the runner.
908+
909+
// Disassemble the Code_Function_Find function.
706910
auto instructions = Memory::DmDisassembleInstructionByRange(
707911
Interface.Code_Function_Find,
708912
0x200

0 commit comments

Comments
 (0)