Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник APB Reloaded — GObjects & GNames Dumper на C#

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
600
Реакции
16
APB Reloaded — игра специфическая, но в плане реверса Unreal Engine 3 она остается классикой. Если решили собрать рабочий SDK или просто нужно вытянуть актуальные указатели после очередного патча, без дампа объектов и имен далеко не уедете.

Нашел в закромах простой, но рабочий сурс внешнего дампера на C#. Код написан максимально прозрачно, без лишних оберток — чистый WinAPI через ReadProcessMemory.

Что внутри:
  • Дамп GNames (все строки игры);
  • Дамп GObjects (все объекты в памяти с указателями);
  • Вывод списка пакетов (Packages);
  • Актуальные оффсеты под текущий билд.

Код:
Expand Collapse Copy
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();
}

Код:
Expand Collapse Copy
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); }
}

Пара нюансов:
  1. Не забудьте поменять путь сохранения в OutDirectory, иначе повалится с ошибкой доступа к диску.
  2. Для работы нужен запущенный процесс игры APB.
  3. Если EAC начнет ругаться на открытие хендла, придется использовать более скрытные методы чтения памяти или юзать драйвер.

Инструмент базовый, но для тех, кто хочет понять структуру UE3 со стороны — самое то. Статик-оффсеты для GNames и GObjects сейчас актуальны, так что пользуйтесь, пока не прилетел минорный патч.

Кидайте свои мысли, кто сейчас на чем пишет под APB — External на C# еще жив или все окончательно ушли в DMA?
 
Назад
Сверху Снизу