- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 297
- Reaction score
- 7
Anyone currently poking at APB?
Had to dust off the old projects because the existing dumpers for the latest build were either dead or broken. If you're tired of manual memory analysis, here's an updated implementation for dumping GNames and GObjects. It uses a clean external approach, though you'll need to plug in your own memory driver headers.
Technical Breakdown:
The Implementation:
Notes & Troubleshooting:
Anyone else successfully dumping the latest build, or are you running into structural changes?
Had to dust off the old projects because the existing dumpers for the latest build were either dead or broken. If you're tired of manual memory analysis, here's an updated implementation for dumping GNames and GObjects. It uses a clean external approach, though you'll need to plug in your own memory driver headers.
Technical Breakdown:
- GNames: Iterates through the name pool, handles both standard and flag-based string retrieval (0x4000 flag check included).
- GObjects: Parses the object array to generate logs for your SDK research.
- Object Traversal: Logic for recursive Outer pointer tracing to resolve full object paths.
The Implementation:
Code:
#pragma once
#include <windows.h>
#include <string>
#include <fstream>
#include <sstream>
#include <unordered_set>
#include <stdexcept>
#include <driver/Memory.hpp>
#include <iostream>
#include <filesystem>
#include "utils/offsets.h"
class ApbTools {
private:
public:
uint64_t GetClass(uint64_t objectPtr) {
return driver->read<uint64_t>(objectPtr + 0x18);
}
uint64_t GetOuter(uint64_t objectPtr) {
return driver->read<uint64_t>(objectPtr + 0x2C);
}
std::string GetObjectName(uint64_t objectPtr) {
if (objectPtr == 0) {
return "invalid object";
}
int32_t fNameIndex = driver->read<int32_t>(objectPtr + 0x24);
return GetNameByIdx(fNameIndex);
}
uint64_t GetPackageObject(uint64_t objectPtr) {
uint64_t outerPtr = GetOuter(objectPtr);
uint64_t lastValidPtr = outerPtr;
while (outerPtr != 0) {
outerPtr = GetOuter(outerPtr);
if (outerPtr != 0) {
lastValidPtr = outerPtr;
}
}
return lastValidPtr;
}
std::string GetObjectFullName(uint64_t objectPtr) {
uint64_t classPtr = GetClass(objectPtr);
uint64_t outerPtr = GetOuter(objectPtr);
if (classPtr == 0 || outerPtr == 0) {
return "(null)";
}
std::stringstream sb;
sb << GetObjectName(classPtr) << " ";
std::string name = GetObjectName(objectPtr);
sb << name;
while (outerPtr != 0) {
name = GetObjectName(outerPtr);
sb.seekp(0);
sb << name << "." << sb.str();
outerPtr = GetOuter(outerPtr);
}
return sb.str();
}
ApbTools() {}
~ApbTools() {
// Destructor handles cleanup (equivalent to Dispose)
}
void DumpGNames() {
int32_t num = driver->read<int32_t>(driver->image_base + cached.Offset_GNames.load() + 8);
int32_t max = driver->read<int32_t>(driver->image_base + cached.Offset_GNames.load() + 12);
std::cout << "num " << num << " max " << max << std::endl;
std::ofstream sw("D:\\APB_Names.log");
if (!sw.is_open()) {
printf("%s\n", "Failed to open APB_Names.log");
return;
}
for (uint64_t i = 0; i < static_cast<uint64_t>(num); ++i) {
uint64_t namePtr = driver->read<uint64_t>(driver->read<uint64_t>(driver->image_base + cached.Offset_GNames.load()) + i * 8);
if (namePtr == 0) {
continue;
}
int32_t flag = driver->read<int32_t>(namePtr);
uint64_t strPtr = (flag == 0x4000) ? driver->read<uint64_t>(namePtr + 0x30) : namePtr + 0x18;
std::vector<char> buffer(1024);
if (!driver->read(strPtr, buffer.data(), 1024)) {
printf("%s\n", ("Failed to read string at address: " + std::to_string(strPtr)).c_str());
continue;
}
// Find null terminator
size_t len = 0;
while (len < 1024 && buffer[len] != '\0') {
++len;
}
std::string str = std::string(buffer.data(), len);
sw << "idx: " << std::setw(5) << std::setfill('0') << i << " name: " << str << "\n";
}
sw.close();
}
std::string GetNameByIdx(int32_t idx) {
int32_t num = driver->read<int32_t>(driver->image_base + cached.Offset_GNames.load() + 8);
if (idx < 0 || idx >= num) {
return "invalid index";
}
uint64_t namePtr = driver->read<uint64_t>(driver->read<uint64_t>(driver->image_base + cached.Offset_GNames.load()) + static_cast<uint64_t>(idx) * 8);
if (namePtr == 0) {
return "invalid name";
}
int32_t flag = driver->read<int32_t>(namePtr);
uint64_t strPtr = (flag == 0x4000) ? driver->read<uint64_t>(namePtr + 0x30) : namePtr + 0x18;
std::vector<char> buffer(1024);
if (!driver->read(strPtr, buffer.data(), 1024)) {
printf("%s\n", ("Failed to read string at address: " + std::to_string(strPtr)).c_str());
return "error:GetNameByIdx";
}
// Find null terminator
size_t len = 0;
while (len < 1024 && buffer[len] != '\0') {
++len;
}
std::string str = std::string(buffer.data(), len);
return str;
}
void DumpSDK(){
std::ofstream sw("D:\\APB_sdk.h");
//apb_sdk::UObject::Class
int32_t num = driver->read<int32_t>(driver->image_base + cached.Offset_GObjects.load() + 8);
int32_t max = driver->read<int32_t>(driver->image_base + cached.Offset_GObjects.load() + 12);
std::cout << "num " << num << " max " << max << std::endl;
for (uint64_t i = 0; i < static_cast<uint64_t>(num); ++i) {
uint64_t objectPtr = driver->read<uint64_t>(driver->read<uint64_t>(driver->image_base + cached.Offset_GObjects.load()) + i * 8);
if (objectPtr == 0) {
continue;
}
//uintptr_t _class = driver->read<uintptr_t>(objectPtr + apb_sdk::UObject::Class);
//while (_class) {
// int id = driver->read<int>(objectPtr + apb_sdk::UObject::Name);
// std::string name = GetNameByIdx(id);
// if (id == 0 || name.empty()) {
// break;
// }
// std::string fullName = GetObjectFullName(objectPtr);
// sw << "Class " << std::left << std::setw(25) << fullName << "\n";
// sw << std::dec; // Reset to decimal
//}
}
return;
}
void DumpGObjects() {
int32_t num = driver->read<int32_t>(driver->image_base + cached.Offset_GObjects.load() + 8);
int32_t max = driver->read<int32_t>(driver->image_base + cached.Offset_GObjects.load() + 12);
std::cout << "num " << num << " max " << max << std::endl;
std::ofstream sw("D:\\APB_Objects.log");
if (!sw.is_open()) {
printf("%s\n", "Failed to open APB_Objects.log");
return;
}
for (uint64_t i = 0; i < static_cast<uint64_t>(num); ++i) {
uint64_t objectPtr = driver->read<uint64_t>(driver->read<uint64_t>(driver->image_base + cached.Offset_GObjects.load()) + i * 8);
if (objectPtr == 0) {
continue;
}
std::string name = GetObjectName(objectPtr);
std::string fullName = GetObjectFullName(objectPtr);
sw << "idx " << std::setw(10) << std::setfill('0') << i
<< " ptr: " << std::hex << std::setw(16) << std::setfill('0') << objectPtr
<< " name: " << std::left << std::setw(40) << std::setfill(' ') << name
<< " fullName: " << std::setw(40) << fullName << "\n";
sw << std::dec; // Reset to decimal
}
sw.close();
}
void DumpPackages() {
std::unordered_set<std::string> packagesSet;
int32_t num = driver->read<int32_t>(driver->image_base + cached.Offset_GObjects.load() + 8);
std::ofstream sw("D:\\APB_Packages.log");
if (!sw.is_open()) {
printf("%s\n", "Failed to open APB_Packages.log");
return;
}
for (uint64_t i = 0; i < static_cast<uint64_t>(num); ++i) {
uint64_t objectPtr = driver->read<uint64_t>(driver->read<uint64_t>(driver->image_base + cached.Offset_GObjects.load()) + i * 8);
if (objectPtr == 0) {
continue;
}
uint64_t package = GetPackageObject(objectPtr);
if (package == 0) {
continue;
}
std::string packageName = GetObjectName(package);
if (packagesSet.insert(packageName).second) {
sw << packageName << "\n";
}
}
sw.close();
}
};
Notes & Troubleshooting:
- Offsets: Ensure your Offset_GNames and Offset_GObjects are correctly updated in your local config; otherwise, you'll just be dumping garbage memory.
- Driver: This assumes you have a standard read function exported via your driver wrapper. If you're getting access violations, verify your process handle/EPROCESS status.
- Stability: This will work until the devs decide to shift the UObject structure, which happens more often than we'd like.
Q: Why is my log file empty?
A: Check your base address and verify the offsets. If your driver isn't reading the process memory correctly, you'll never find the count/max variables.
Q: Does this work for main?
A: Never run debug tools on your main account. This is strictly for RE and SDK generation. Use a burner if you're testing live.
A: Check your base address and verify the offsets. If your driver isn't reading the process memory correctly, you'll never find the count/max variables.
Q: Does this work for main?
A: Never run debug tools on your main account. This is strictly for RE and SDK generation. Use a burner if you're testing live.
Anyone else successfully dumping the latest build, or are you running into structural changes?