diff --git a/include/xrbx/engine/offsets.h b/include/xrbx/engine/offsets.h new file mode 100644 index 0000000..47c178e --- /dev/null +++ b/include/xrbx/engine/offsets.h @@ -0,0 +1,8 @@ +#pragma once + +#define IDA_BASE 0x140000000 +#define RVA(ADDR) (ADDR - IDA_BASE) + +#define RVA_TASK_SCHEDULER RVA(0x1484A58E0) // "AllTaskSchedulerJobs" +#define OFF_SCHEDULER_JOBS 0xC8 // "TaskScheduler::Job:" +#define OFF_JOB_NAME 0xF0 // "{}({};{})" diff --git a/include/xrbx/engine/scheduler.h b/include/xrbx/engine/scheduler.h new file mode 100644 index 0000000..7ce634e --- /dev/null +++ b/include/xrbx/engine/scheduler.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "offsets.h" +#include "../process.h" + +namespace RBX { + class Scheduler { + private: + BYTE *addr; + const Process &proc; + public: + class Job { + private: + BYTE *addr; + const Process &proc; + public: + Job(BYTE *va, const Process &process) : addr(va), proc(process) {} + + const BYTE * const VA() const { return addr; } + + inline std::string getName() const { + BYTE *name_ptr = addr + OFF_JOB_NAME; + uint64_t length = proc.Read(name_ptr + 8); + std::string name(length, '\0'); + proc.ReadBytes(proc.Read(name_ptr), (BYTE *)name.data(), length); + name[length] = 0; + return name; + } + }; + + Scheduler(BYTE *va, const Process &process) : addr(va), proc(process) {} + + inline std::vector getJobs() const { + std::vector jobs; + BYTE *jobsStart = proc.Read(addr + OFF_SCHEDULER_JOBS); + BYTE *jobsEnd = proc.Read(addr + OFF_SCHEDULER_JOBS + 8); + for (BYTE *job = jobsStart; job != jobsEnd; job += 16) { + Job j(proc.Read(job), proc); + jobs.push_back(j); + } + return jobs; + } + + Job getJob(const std::string &name); + }; +} diff --git a/include/xrbx/process.h b/include/xrbx/process.h index 0d19e1a..1225555 100644 --- a/include/xrbx/process.h +++ b/include/xrbx/process.h @@ -21,12 +21,12 @@ class Process { public: static DWORD findProcessByName(const std::wstring &name); - inline BYTE *imageBase(void) { + inline BYTE *imageBase(void) const { return base; } template - inline T Read(BYTE *addr) { + inline T Read(BYTE *addr) const { T buf; SIZE_T bytesRead; NTSTATUS status = Nt::resolve("NtReadVirtualMemory")(proc, addr, &buf, sizeof(buf), &bytesRead); @@ -36,6 +36,18 @@ class Process { return buf; } + inline void ReadBytes(BYTE *addr, BYTE *buf, size_t bytes) const { + SIZE_T bytesRead; + NTSTATUS status = Nt::resolve("NtReadVirtualMemory")(proc, addr, buf, bytes, &bytesRead); + if (status < 0) { + throw std::runtime_error(std::format("Failed to read {} bytes from {:X}", bytes, uintptr_t(addr))); + } + } + + inline BYTE *VA(uintptr_t rva) const { + return base + rva; + } + inline Process(const std::wstring &name) { pid = findProcessByName(name); if (pid == 0) { diff --git a/tests/test.cpp b/tests/test.cpp index 21c718f..51bbe40 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1,9 +1,18 @@ #include "xrbx/xrbx.h" +#include "xrbx/engine/scheduler.h" #include #include int main() { - Process rbx(L"notepad.exe"); + Process rbx(L"RobloxPlayerBeta.exe"); std::cout << std::format("Image Base: {:X}", uintptr_t(rbx.imageBase())) << std::endl; + RBX::Scheduler sched(rbx.Read(rbx.VA(RVA_TASK_SCHEDULER)), rbx); + try { + for (const auto &job : sched.getJobs()) { + std::cout << std::format("Job {} @ {:x}", job.getName(), uintptr_t(job.VA())) << std::endl; + } + } catch (std::exception &e) { + std::cout << "err: " << e.what() << std::endl; + } return 0; }