Article Entity list update

DREDD

Administrator
Administrator

DREDD

Administrator
Administrator
Status
Offline
Joined
Apr 18, 2019
Messages
191
Reaction score
323
Technique to efficiently scan the game's entity list. It is based on diffing the ent_info array to see what needs to be updated. Very useful code that increase optimization in cheats.

This is just a demo code required for detecting when an entity slot has changed, and only updating those entities you care about when necessary.


Code:
#include <string>
#include <cstdint>
#include <vector>
 
// put in here apex sdk
 
namespace sdk {
    const size_t NUM_ENT_ENTRIES = 0x10000;
    struct CEntInfo {
        uint64_t pEntity;
        int64_t SerialNumber;
        uint64_t pPrev;
        uint64_t pNext;
    };
}
 
// Your process class to access Apex Legends
class Process {
public:
    Process();
 
    bool heartbeat();
 
    // Reads an array of `T`.
    template<typename T>
    bool read_array(uint64_t address, T* ptr, size_t len);
 
    // Reads a value of `T`.
    template<typename T>
    bool read(uint64_t address, T& value);
 
public:
    uint64_t r5apex_exe;
};
 
// Your process local entity hierarchy
 
namespace entities {
 
    class BaseEntity {
    public:
        virtual ~BaseEntity() = 0;
        // Read from the process to update this entity's state
        virtual void update(Process& process) = 0;
    };
 
    class Player : public BaseEntity {
    public:
        Player(uint64_t entity_ptr) : entity_ptr(entity_ptr) {}
 
        virtual void update(Process& process);
 
    public:
        uint64_t entity_ptr;
        float origin[3];
        float angles[3];
        int team_index;
        int shield_health;
        int max_shield_health;
        int health;
        int max_health;
        float view_offset[3];
        // etc...
    };
 
    class PropSurvival : public BaseEntity {
    public:
        PropSurvival(uint64_t entity_ptr) : entity_ptr(entity_ptr) {}
 
        virtual void update(Process& process);
 
    public:
        uint64_t entity_ptr;
        float origin[3];
        int custom_script_int;
    };
 
    class WeaponX : public BaseEntity {
    public:
        WeaponX(uint64_t entity_ptr) : entity_ptr(entity_ptr) {}
 
        virtual void update(Process& process);
 
    public:
        uint64_t entity_ptr;
        int weapon_owner;
        int weapon_name_index;
        int ammo_in_clip;
        int ammo_in_stockpile;
    };
}
 
//----------------------------------------------------------------
// Your hack
 
std::string get_client_class_name(Process& process, uint64_t entity_ptr);
 
int main() {
    // Your process management (eg through driver)
    Process process;
 
    // Offset of entity list
    uint32_t cl_entitylist = 0x01f9ae68;
 
    // Cached state of process local entity data
    std::vector<entities::BaseEntity*> entities;
    std::vector<sdk::CEntInfo> ent_infos;
    std::vector<sdk::CEntInfo> prev_infos;
 
    // Initialize the cached state
    for (size_t i = 0; i < sdk::NUM_ENT_ENTRIES; ++i) {
        entities.push_back(nullptr);
        ent_infos.push_back(sdk::CEntInfo{});
        prev_infos.push_back(sdk::CEntInfo{});
    }
 
    // While the process is running
    while (process.heartbeat()) {
        // Keep the ent_infos around to diff for changes
        std::swap(ent_infos, prev_infos);
        // Read the game's current ent_infos array
        process.read_array(process.r5apex_exe + cl_entitylist, ent_infos.data(), sdk::NUM_ENT_ENTRIES);
        // Update the entities
        for (size_t i = 0; i < sdk::NUM_ENT_ENTRIES; ++i) {
            auto& entity_place = entities[i];
            auto& prev_info = prev_infos[i];
            auto& ent_info = ent_infos[i];
 
            // If the entity pointer remained the same
            if (prev_info.pEntity == ent_info.pEntity) {
                // Update its state
                if (entity_place) {
                    entity_place->update(process);
                }
            }
            // The entity pointer changed to something non-null
            else if (ent_info.pEntity != 0) {
                delete entity_place;
                entity_place = nullptr;
                auto class_name = get_client_class_name(process, ent_info.pEntity);
                if (class_name == "CPlayer") {
                    entity_place = new entities::Player(ent_info.pEntity);
                }
                else if (class_name == "CPropSurvival") {
                    entity_place = new entities::PropSurvival(ent_info.pEntity);
                }
                else if (class_name == "CWeaponX") {
                    entity_place = new entities::WeaponX(ent_info.pEntity);
                }
                if (entity_place) {
                    entity_place->update(process);
                }
            }
            // The entity pointer became null do cleanup
            else {
                delete entity_place;
                entity_place = nullptr;
            }
        }
    }
}
 
std::string get_client_class_name(Process& process, uint64_t entity_ptr) {
    uint64_t client_networkable_vtable;
    uint64_t get_client_entity;
    uint32_t offset;
    uint64_t network_name_ptr;
    char buffer[32];
    // Read the ClientClass's network name for to given entity
    bool success = process.read(entity_ptr + 3 * 8, client_networkable_vtable)
        && process.read(client_networkable_vtable + 3 * 8, get_client_entity)
        && process.read(get_client_entity + 3, offset)
        && process.read(get_client_entity + offset + 7 + 16, network_name_ptr)
        && process.read_array(network_name_ptr, buffer, 32);
    std::string result;
    if (success) {
        // Return up to 32 chars from the network name
        size_t len;
        for (len = 0; len < 32; ++len)
            if (buffer[len] == '\0')
                break;
        result.assign(buffer, len);
    }
    return result;
}

Credits: Casual_Hacker
 
Top