- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 598
- Reaction score
- 7
Digging back into APB? If you are building an external or just need to map out the current object tree, you will need a flexible dumper. This is a straightforward C# implementation for dumping Global Names and Global Objects, which is essential for any serious reversing effort in this game.
The logic relies on standard external memory reading via kernel32. It maps out the UE3 structure to extract names, object indexes, and full package paths. This is particularly useful for finding GObjects offsets and generating a clean SDK base.
Core Offsets & Logic
The following offsets are targeted for the current build. The dumper iterates through the GNames and GObjects arrays, handling the string pointer flags (like the 0x4000 flag for names) to ensure you don't get garbage data.
Features Included:
Usage is simple: just instantiate ApbTools and call the dump methods. This is an external approach, so it stays fairly isolated from the game's internal checks compared to a logic-heavy DLL injection.
Technical Note: If you are hitting nullptrs, check if the base addresses for GNames and GObjects have shifted after a patch. You can find them by searching for the classic Unreal Engine string signatures or looking at the ViewMatrix initialization.
Have fun reversing. Drop your crash logs below if you run into issues with the memory reader handles.
The logic relies on standard external memory reading via kernel32. It maps out the UE3 structure to extract names, object indexes, and full package paths. This is particularly useful for finding GObjects offsets and generating a clean SDK base.
Core Offsets & Logic
The following offsets are targeted for the current build. The dumper iterates through the GNames and GObjects arrays, handling the string pointer flags (like the 0x4000 flag for names) to ensure you don't get garbage data.
Code:
private const ulong GNames = 0x14390DFE0;
private const ulong GObjects = 0x14398E150;
Features Included:
- Full GNames dump to log file.
- GObjects dump including hex pointers and full names.
- Package extraction to map out the library dependencies.
- External memory reader using OpenProcess/ReadProcessMemory.
This class handles the iteration logic for the Unreal arrays. Make sure to adjust your OutDirectory before running.
Code:
public class ApbTools : IDisposable
{
private const string ProcessName = "APB";
private const ulong GNames = 0x14390DFE0;
private const ulong GObjects = 0x14398E150;
private const string OutDirectory = "C:\\APBDump";
private readonly MemoryReader _reader = new(ProcessName);
public void DumpGNames()
{
var num = _reader.ReadInt(GNames + 8);
using var sw = new StreamWriter($"{OutDirectory}\\APB_Names.log");
for (ulong i = 0; i < (ulong)num; i++)
{
var namePtr = _reader.ReadLong(_reader.ReadLong(GNames) + i * 8);
if (namePtr == 0) continue;
var flag = _reader.ReadInt(namePtr);
var strPtr = flag == 0x4000 ? _reader.ReadLong(namePtr + 0x30) : namePtr + 0x18;
var str = _reader.ReadString(strPtr, 1024);
sw.WriteLine($"idx: {i:D5} name: {str}");
}
}
public void DumpGObjects()
{
var num = _reader.ReadInt(GObjects + 8);
using var sw = new StreamWriter($"{OutDirectory}\\APB_Objects.log");
for (ulong i = 0; i < (ulong)num; i++)
{
var objectPtr = _reader.ReadLong(_reader.ReadLong(GObjects) + i * 8);
if (objectPtr == 0) continue;
var name = GetObjectName(objectPtr);
var fullName = GetObjectFullName(objectPtr);
sw.WriteLine($"idx {i:D10} ptr: {objectPtr,16:X} name: {name,-40} fullName: {fullName,-40}");
}
}
private ulong GetClass(ulong objectPtr) => _reader.ReadLong(objectPtr + 0x18);
private ulong GetOuter(ulong objectPtr) => _reader.ReadLong(objectPtr + 0x2C);
private string GetObjectName(ulong objectPtr)
{
if (objectPtr == 0) return "invalid object";
var fNameIndex = _reader.ReadInt(objectPtr + 0x24);
return GetNameByIdx(fNameIndex);
}
public string GetNameByIdx(int idx)
{
var num = _reader.ReadInt(GNames + 8);
if (idx < 0 || idx >= num) return "invalid index";
var namePtr = _reader.ReadLong(_reader.ReadLong(GNames) + (ulong)(idx * 8));
if (namePtr == 0) return "invalid name";
var flag = _reader.ReadInt(namePtr);
var strPtr = flag == 0x4000 ? _reader.ReadLong(namePtr + 0x30) : namePtr + 0x18;
return _reader.ReadString(strPtr, 1024);
}
private string GetObjectFullName(ulong objectPtr)
{
var classPtr = GetClass(objectPtr);
var outerPtr = GetOuter(objectPtr);
if (classPtr == 0 || outerPtr == 0) return "(null)";
var sb = new StringBuilder();
sb.Append(GetObjectName(objectPtr));
while (outerPtr != 0)
{
sb.Insert(0, GetObjectName(outerPtr) + ".");
outerPtr = GetOuter(outerPtr);
}
sb.Insert(0, GetObjectName(classPtr) + " ");
return sb.ToString();
}
public void Dispose() => _reader.Dispose();
}
Standard P/Invoke wrapper for RPM. Ensure you are running as Administrator to get a proper handle on the APB process.
Code:
public class MemoryReader : IDisposable
{
private IntPtr _processHandle;
private const int PROCESS_VM_READ = 0x0010;
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
public MemoryReader(string processName)
{
var process = Process.GetProcessesByName(processName)[0];
_processHandle = OpenProcess(PROCESS_VM_READ, false, process.Id);
}
public int ReadInt(ulong address)
{
var buffer = new byte[4];
return ReadProcessMemory(_processHandle, (IntPtr)address, buffer, buffer.Length, out _) ? BitConverter.ToInt32(buffer, 0) : 0;
}
public ulong ReadLong(ulong address)
{
var buffer = new byte[8];
return ReadProcessMemory(_processHandle, (IntPtr)address, buffer, buffer.Length, out _) ? BitConverter.ToUInt64(buffer, 0) : 0;
}
public string ReadString(ulong address, int length)
{
var buffer = new byte[length];
ReadProcessMemory(_processHandle, (IntPtr)address, buffer, buffer.Length, out _);
var nullByteIndex = Array.IndexOf(buffer, (byte)0);
return nullByteIndex == -1 ? "" : Encoding.ASCII.GetString(buffer, 0, nullByteIndex);
}
public void Dispose() { if (_processHandle != IntPtr.Zero) CloseHandle(_processHandle); }
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
}
Usage is simple: just instantiate ApbTools and call the dump methods. This is an external approach, so it stays fairly isolated from the game's internal checks compared to a logic-heavy DLL injection.
Technical Note: If you are hitting nullptrs, check if the base addresses for GNames and GObjects have shifted after a patch. You can find them by searching for the classic Unreal Engine string signatures or looking at the ViewMatrix initialization.
Have fun reversing. Drop your crash logs below if you run into issues with the memory reader handles.