- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 600
- Реакции
- 16
APB Reloaded — игра специфическая, но в плане реверса Unreal Engine 3 она остается классикой. Если решили собрать рабочий SDK или просто нужно вытянуть актуальные указатели после очередного патча, без дампа объектов и имен далеко не уедете.
Нашел в закромах простой, но рабочий сурс внешнего дампера на C#. Код написан максимально прозрачно, без лишних оберток — чистый WinAPI через ReadProcessMemory.
Что внутри:
Пара нюансов:
Инструмент базовый, но для тех, кто хочет понять структуру UE3 со стороны — самое то. Статик-оффсеты для GNames и GObjects сейчас актуальны, так что пользуйтесь, пока не прилетел минорный патч.
Кидайте свои мысли, кто сейчас на чем пишет под APB — External на C# еще жив или все окончательно ушли в DMA?
Нашел в закромах простой, но рабочий сурс внешнего дампера на C#. Код написан максимально прозрачно, без лишних оберток — чистый WinAPI через ReadProcessMemory.
Что внутри:
- Дамп GNames (все строки игры);
- Дамп GObjects (все объекты в памяти с указателями);
- Вывод списка пакетов (Packages);
- Актуальные оффсеты под текущий билд.
Код:
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} fullName: {fullName}");
}
}
private string GetObjectName(ulong objectPtr)
{
var fNameIndex = _reader.ReadInt(objectPtr + 0x24);
return GetNameByIdx(fNameIndex);
}
public string GetNameByIdx(int idx)
{
var namePtr = _reader.ReadLong(_reader.ReadLong(GNames) + (ulong)(idx * 8));
var flag = _reader.ReadInt(namePtr);
var strPtr = flag == 0x4000 ? _reader.ReadLong(namePtr + 0x30) : namePtr + 0x18;
return _reader.ReadString(strPtr, 1024);
}
public void Dispose() => _reader.Dispose();
}
Код:
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];
ReadProcessMemory(_processHandle, (IntPtr)address, buffer, 4, out _);
return BitConverter.ToInt32(buffer, 0);
}
public ulong ReadLong(ulong address)
{
var buffer = new byte[8];
ReadProcessMemory(_processHandle, (IntPtr)address, buffer, 8, out _);
return BitConverter.ToUInt64(buffer, 0);
}
public string ReadString(ulong address, int length)
{
var buffer = new byte[length];
ReadProcessMemory(_processHandle, (IntPtr)address, buffer, length, out _);
var nullByteIndex = Array.IndexOf(buffer, (byte)0);
return Encoding.ASCII.GetString(buffer, 0, nullByteIndex);
}
public void Dispose() { if (_processHandle != IntPtr.Zero) CloseHandle(_processHandle); }
}
Пара нюансов:
- Не забудьте поменять путь сохранения в OutDirectory, иначе повалится с ошибкой доступа к диску.
- Для работы нужен запущенный процесс игры APB.
- Если EAC начнет ругаться на открытие хендла, придется использовать более скрытные методы чтения памяти или юзать драйвер.
Инструмент базовый, но для тех, кто хочет понять структуру UE3 со стороны — самое то. Статик-оффсеты для GNames и GObjects сейчас актуальны, так что пользуйтесь, пока не прилетел минорный патч.
Кидайте свои мысли, кто сейчас на чем пишет под APB — External на C# еще жив или все окончательно ушли в DMA?