Instance, class decriptors, property descriptors + cleanup

This commit is contained in:
2026-08-05 14:20:44 +02:00
parent 2f48b90cf8
commit 5c2fac932c
9 changed files with 274 additions and 31 deletions
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "xrbx/engine/common.h"
#include "xrbx/process.h"
#include "offsets.h"
#include <stdexcept>
#include <vector>
namespace RBX {
class PropertyDescriptor : public InternalObjectWrapper {
public:
PropertyDescriptor(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {}
inline std::string getName() const {
return process().readCPPString(process().read<BYTE *>(VA() + OFF_PD_NAME));
}
};
class ClassDescriptor : public InternalObjectWrapper {
public:
ClassDescriptor(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {}
inline std::string getName() const {
try {
return process().readCPPString(process().read<BYTE *>(VA() + OFF_CD_NAME));
} catch (...) {
throw std::runtime_error(std::format("Failed to read class name for class @ %llx", uintptr_t(VA())));
}
}
inline std::vector<PropertyDescriptor> getPropertyDescriptors() const {
std::vector<PropertyDescriptor> propdescs;
BYTE *start = process().read<BYTE *>(VA() + OFF_CD_PROP_DESCRIPTORS);
uint64_t length = process().read<uint64_t>(VA() + OFF_CD_PROP_DESCRIPTORS + 8);
if (start == 0) return propdescs;
for (BYTE *current = start; (current - start) / 2 < length; current += 16) {
PropertyDescriptor pd(process().read<BYTE *>(current), process());
propdescs.push_back(pd);
}
return propdescs;
}
};
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <windows.h>
#include "../process.h"
namespace RBX {
class InternalObjectWrapper {
private:
BYTE *addr;
const Process &proc;
public:
InternalObjectWrapper(BYTE *va, const Process &process) : addr(va), proc(process) {}
inline BYTE *VA(void) const { return addr; }
inline const Process &process(void) const { return proc; }
};
}
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include "xrbx/process.h"
#include "offsets.h"
#include "common.h"
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include <windows.h>
#include "classdescriptor.h"
namespace RBX {
class Instance : public InternalObjectWrapper {
public:
Instance(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {}
inline std::string getName() const {
return process().readCPPString(process().read<BYTE *>(VA() + OFF_INSTANCE_NAME));
}
inline std::optional<Instance> getParent() const {
BYTE *parent_va = process().read<BYTE *>(VA() + OFF_INSTANCE_PARENT);
if (parent_va == 0) return std::nullopt;
return Instance(parent_va, process());
}
inline std::vector<Instance> getChildren() const {
try {
return process().readVector<Instance>(process().read<BYTE *>(VA() + OFF_INSTANCE_CHILDREN));
} catch (...) {
throw std::runtime_error(std::format("Failed to read children of instance '{}' @ {:x}", getName(), uintptr_t(VA())));
}
}
inline std::optional<Instance> findFirstChild(const std::string &name) {
return process().readVectorElement<Instance>(process().read<BYTE *>(VA() + OFF_INSTANCE_CHILDREN), [&name](const Instance &inst) -> bool {
return inst.getName() == name;
});
}
inline std::optional<Instance> findFirstChildOfClass(const std::string &name) {
return process().readVectorElement<Instance>(process().read<BYTE *>(VA() + OFF_INSTANCE_CHILDREN), [&name](const Instance &inst) -> bool {
return inst.getClassName() == name;
});
}
inline ClassDescriptor getClassDescriptor() const {
BYTE *va = process().read<BYTE *>(VA() + OFF_INSTANCE_CLASS_DESCRIPTOR);
if (va == 0) throw std::runtime_error("invalid class");
return ClassDescriptor(va, process());
}
inline std::string getClassName() const {
return getClassDescriptor().getName();
}
};
}
+11
View File
@@ -6,3 +6,14 @@
#define RVA_TASK_SCHEDULER RVA(0x1484A58E0) // "AllTaskSchedulerJobs" #define RVA_TASK_SCHEDULER RVA(0x1484A58E0) // "AllTaskSchedulerJobs"
#define OFF_SCHEDULER_JOBS 0xC8 // "TaskScheduler::Job:" #define OFF_SCHEDULER_JOBS 0xC8 // "TaskScheduler::Job:"
#define OFF_JOB_NAME 0xF0 // "{}({};{})" #define OFF_JOB_NAME 0xF0 // "{}({};{})"
#define OFF_JOB_RUNSERVICE 0x1A0
#define OFF_INSTANCE_NAME 0x98
#define OFF_INSTANCE_PARENT 0x68
#define OFF_INSTANCE_CHILDREN 0x70
#define OFF_INSTANCE_CLASS_DESCRIPTOR 0x18
#define OFF_CD_NAME 0x8
#define OFF_CD_PROP_DESCRIPTORS 0x40
#define OFF_PD_NAME 0x8
+22 -24
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
#include <windows.h> #include <windows.h>
@@ -8,45 +9,42 @@
#include "offsets.h" #include "offsets.h"
#include "../process.h" #include "../process.h"
#include "instance.h"
#include "xrbx/engine/common.h"
namespace RBX { namespace RBX {
class Scheduler { class Scheduler : public InternalObjectWrapper {
private:
BYTE *addr;
const Process &proc;
public: public:
class Job { class Job : public InternalObjectWrapper {
private:
BYTE *addr;
const Process &proc;
public: public:
Job(BYTE *va, const Process &process) : addr(va), proc(process) {} Job(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {}
const BYTE * const VA() const { return addr; }
inline std::string getName() const { inline std::string getName() const {
BYTE *name_ptr = addr + OFF_JOB_NAME; BYTE *name_ptr = VA() + OFF_JOB_NAME;
uint64_t length = proc.Read<uint64_t>(name_ptr + 8); uint64_t length = process().read<uint64_t>(name_ptr + 8);
std::string name(length, '\0'); std::string name(length, '\0');
proc.ReadBytes(proc.Read<BYTE *>(name_ptr), (BYTE *)name.data(), length); process().readBytes(process().read<BYTE *>(name_ptr), (BYTE *)name.data(), length);
name[length] = 0; name[length] = 0;
return name; return name;
} }
inline Instance getRunService() {
return Instance(process().read<BYTE *>(VA() + OFF_JOB_RUNSERVICE), process());
}
}; };
Scheduler(BYTE *va, const Process &process) : addr(va), proc(process) {} Scheduler(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {}
inline std::vector<Job> getJobs() const { inline std::vector<Job> getJobs() const {
std::vector<Job> jobs; return process().readVector<Job>(VA() + OFF_SCHEDULER_JOBS);
BYTE *jobsStart = proc.Read<BYTE *>(addr + OFF_SCHEDULER_JOBS);
BYTE *jobsEnd = proc.Read<BYTE *>(addr + OFF_SCHEDULER_JOBS + 8);
for (BYTE *job = jobsStart; job != jobsEnd; job += 16) {
Job j(proc.Read<BYTE *>(job), proc);
jobs.push_back(j);
}
return jobs;
} }
Job getJob(const std::string &name); Job getJob(const std::string &name) const {
auto j = process().readVectorElement<Job>(VA() + OFF_SCHEDULER_JOBS, [&name](const Job &job) -> bool {
return job.getName() == name;
});
if (!j.has_value()) throw std::runtime_error(std::format("Job '{}' not found", name));
return j.value();
}
}; };
} }
+66
View File
@@ -63,4 +63,70 @@ namespace Nt {
PVOID Mutant; PVOID Mutant;
PVOID ImageBaseAddress; PVOID ImageBaseAddress;
} PEB_PARTIAL; } PEB_PARTIAL;
typedef struct _SYSTEM_THREAD_INFORMATION
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientId;
LONG Priority;
LONG BasePriority;
ULONG ContextSwitches;
ULONG ThreadState;
ULONG WaitReason;
} SYSTEM_THREAD_INFORMATION;
typedef struct _SYSTEM_PROCESS_INFORMATION
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
HANDLE InheritedFromUniqueProcessId;
ULONG HandleCount;
ULONG SessionId;
SIZE_T UniqueProcessKey;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER ReadOperationCount;
LARGE_INTEGER WriteOperationCount;
LARGE_INTEGER OtherOperationCount;
LARGE_INTEGER ReadTransferCount;
LARGE_INTEGER WriteTransferCount;
LARGE_INTEGER OtherTransferCount;
SYSTEM_THREAD_INFORMATION Threads[1];
} SYSTEM_PROCESS_INFORMATION;
} }
+40 -2
View File
@@ -1,6 +1,10 @@
#pragma once #pragma once
#include <cstdint>
#include <format> #include <format>
#include <functional>
#include <iostream>
#include <optional>
#include <stdexcept> #include <stdexcept>
#include <windows.h> #include <windows.h>
#include <vector> #include <vector>
@@ -20,13 +24,14 @@ class Process {
BYTE *getImageBase(void); BYTE *getImageBase(void);
public: public:
static DWORD findProcessByName(const std::wstring &name); static DWORD findProcessByName(const std::wstring &name);
std::vector<DWORD> getThreads();
inline BYTE *imageBase(void) const { inline BYTE *imageBase(void) const {
return base; return base;
} }
template <typename T> template <typename T>
inline T Read(BYTE *addr) const { inline T read(BYTE *addr) const {
T buf; T buf;
SIZE_T bytesRead; SIZE_T bytesRead;
NTSTATUS status = Nt::resolve<Nt::ReadVirtualMemory>("NtReadVirtualMemory")(proc, addr, &buf, sizeof(buf), &bytesRead); NTSTATUS status = Nt::resolve<Nt::ReadVirtualMemory>("NtReadVirtualMemory")(proc, addr, &buf, sizeof(buf), &bytesRead);
@@ -36,7 +41,7 @@ class Process {
return buf; return buf;
} }
inline void ReadBytes(BYTE *addr, BYTE *buf, size_t bytes) const { inline void readBytes(BYTE *addr, BYTE *buf, size_t bytes) const {
SIZE_T bytesRead; SIZE_T bytesRead;
NTSTATUS status = Nt::resolve<Nt::ReadVirtualMemory>("NtReadVirtualMemory")(proc, addr, buf, bytes, &bytesRead); NTSTATUS status = Nt::resolve<Nt::ReadVirtualMemory>("NtReadVirtualMemory")(proc, addr, buf, bytes, &bytesRead);
if (status < 0) { if (status < 0) {
@@ -44,6 +49,39 @@ class Process {
} }
} }
inline std::string readCPPString(BYTE *addr) const {
uint64_t length = read<uint64_t>(addr + 16);
uint64_t capacity = read<uint64_t>(addr + 24);
std::string str(length, '\0');
readBytes(capacity < 16 ? addr : read<BYTE *>(addr), (BYTE *)str.data(), length);
return str;
}
template <typename T>
inline std::vector<T> readVector(BYTE *addr, uint8_t esz = 16) const {
std::vector<T> vec;
if (addr == 0) return vec;
BYTE *start = read<BYTE *>(addr);
BYTE *end = read<BYTE *>(addr + 8);
for (BYTE *el = start; el != end; el += esz) {
T o(read<BYTE *>(el), *this);
vec.push_back(o);
}
return vec;
}
template <typename T>
inline std::optional<T> readVectorElement(BYTE *addr, std::function<bool(const T &o)> filter, uint8_t esz = 16) const {
if (addr == 0) return std::nullopt;
BYTE *start = read<BYTE *>(addr);
BYTE *end = read<BYTE *>(addr + 8);
for (BYTE *el = start; el != end; el += esz) {
T o(read<BYTE *>(el), *this);
if (filter(o)) return o;
}
return std::nullopt;
}
inline BYTE *VA(uintptr_t rva) const { inline BYTE *VA(uintptr_t rva) const {
return base + rva; return base + rva;
} }
+1 -1
View File
@@ -35,7 +35,7 @@ BYTE *Process::getImageBase(void) {
throw std::runtime_error("Failed to get process information"); throw std::runtime_error("Failed to get process information");
} }
return (BYTE *)Read<Nt::PEB_PARTIAL>((BYTE *)pbi.PebBaseAddress).ImageBaseAddress; return (BYTE *)read<Nt::PEB_PARTIAL>((BYTE *)pbi.PebBaseAddress).ImageBaseAddress;
} }
DWORD Process::findProcessByName(const std::wstring &name) { DWORD Process::findProcessByName(const std::wstring &name) {
+16 -4
View File
@@ -1,16 +1,28 @@
#include "xrbx/engine/instance.h"
#include "xrbx/xrbx.h" #include "xrbx/xrbx.h"
#include "xrbx/engine/scheduler.h" #include "xrbx/engine/scheduler.h"
#include <cstdio>
#include <format> #include <format>
#include <iostream> #include <iostream>
void depth_first_print(RBX::Instance inst, int depth = 0) {
printf("%*s-> %s @ %llx (class %s @ %llx)\n", depth, "", inst.getName().c_str(), uintptr_t(inst.VA()), inst.getClassName().c_str(), uintptr_t(inst.getClassDescriptor().VA()));
for (const auto &prop : inst.getClassDescriptor().getPropertyDescriptors()) {
printf("%*s-> %s @ %llx\n", depth + 3, "", prop.getName().c_str(), uintptr_t(prop.VA()));
}
for (const auto &child : inst.getChildren()) {
depth_first_print(child, depth + 1);
}
}
int main() { int main() {
Process rbx(L"RobloxPlayerBeta.exe"); Process rbx(L"RobloxPlayerBeta.exe");
std::cout << std::format("Image Base: {:X}", uintptr_t(rbx.imageBase())) << std::endl; std::cout << std::format("Image Base: {:X}", uintptr_t(rbx.imageBase())) << std::endl;
RBX::Scheduler sched(rbx.Read<BYTE *>(rbx.VA(RVA_TASK_SCHEDULER)), rbx); RBX::Scheduler sched(rbx.read<BYTE *>(rbx.VA(RVA_TASK_SCHEDULER)), rbx);
try { try {
for (const auto &job : sched.getJobs()) { RBX::Scheduler::Job heartbeat = sched.getJob("Heartbeat(Heartbeat;LuaApp)");
std::cout << std::format("Job {} @ {:x}", job.getName(), uintptr_t(job.VA())) << std::endl; RBX::Instance runservice = heartbeat.getRunService();
} depth_first_print(runservice.getParent().value());
} catch (std::exception &e) { } catch (std::exception &e) {
std::cout << "err: " << e.what() << std::endl; std::cout << "err: " << e.what() << std::endl;
} }