1 second backtrack, reorganise aimbot (#38)

* 1 second backtrack, reorganise aimbot again, remove lots of flags from cvars

* Fix linux codebug

* Fix linux code bugs
This commit is contained in:
F1ssi0N 2018-04-08 00:34:56 +01:00 committed by Marc
parent c5c038157a
commit 8f6602ed3f
15 changed files with 402 additions and 105 deletions

View File

@ -17,6 +17,7 @@
#include "hooks/createmove.hh"
#include "hooks/engine_vgui.hh"
#include "hooks/send_datagram.hh"
#include "modules/esp.hh"
#include "modules/misc.hh"
@ -137,6 +138,8 @@ public:
misc::init_all();
logging::msg("DOGHOOK:tm: :joy: :joy: :jo3: :nice:\nBuild: " __DATE__ " " __TIME__);
// at this point we are now inited and ready to go!
inited = true;
}
@ -155,6 +158,7 @@ public:
// Do level init here
create_move::level_init();
send_datagram::level_init();
}
void level_shutdown_pre_clear_steam_api_context() override { logging::msg("level_shutdown_pre_clear_steam_api_context"); }
void level_shutdown_pre_entity() override {
@ -162,6 +166,7 @@ public:
// Do level_shutdown here
create_move::level_shutdown();
send_datagram::level_shutdown();
}
void level_shutdown_post_entity() override { logging::msg("level_shutdown_post_entity"); }

View File

@ -0,0 +1,47 @@
#include <precompiled.hh>
#include <sdk/convar.hh>
#include <sdk/hooks.hh>
#include <sdk/sdk.hh>
#include <modules/backtrack.hh>
using namespace sdk;
class bf_write;
namespace send_datagram {
hooks::HookFunction<NetChannel, 0> *send_datagram_hook;
#if doghook_platform_windows()
u32 __fastcall hooked_send_datagram(NetChannel *channel, void *, bf_write *datagram)
#else
u32 hooked_send_datagram(NetChannel *channel, bf_write *datagram)
#endif
{
auto in_state = channel->in_reliable_state();
auto in_sequence = channel->in_sequence();
// TODO: Call out to backtrack
backtrack::add_latency_to_netchannel(channel);
auto ret = send_datagram_hook->call_original<u32>(datagram);
channel->in_reliable_state() = in_state;
channel->in_sequence() = in_sequence;
return ret;
}
void level_init() {
assert(send_datagram_hook == nullptr);
send_datagram_hook = new hooks::HookFunction<NetChannel, 0>(IFace<Engine>()->net_channel_info(), 46, 47, 47, reinterpret_cast<void *>(&hooked_send_datagram));
}
void level_shutdown() {
delete send_datagram_hook;
send_datagram_hook = nullptr;
}
} // namespace send_datagram

View File

@ -0,0 +1,7 @@
#pragma once
namespace send_datagram {
void level_init();
void level_shutdown();
} // namespace send_datagram

View File

@ -147,13 +147,12 @@ static auto find_best_box() {
static Convar<bool> doghook_aimbot_enable_backtrack{"doghook_aimbot_enable_backtrack", true, nullptr};
static Convar<bool> doghook_aimbot_reverse_backtrack_order{"doghook_aimbot_reverse_backtrack_order", true, nullptr};
auto visible_target_inner(Player *player, std::pair<int, bool> best_box, u32 current_tick, u32 delta, PlayerHitboxes &hitboxes, u32 hitboxes_count, math::Vector &pos) {
if (delta > 0) {
auto success = backtrack::backtrack_player_to_tick(player, current_tick - delta);
if (success == false) return false;
auto visible_target_inner(Player *player, std::pair<int, bool> best_box, u32 tick, math::Vector &pos) {
PlayerHitboxes hitboxes;
u32 hitboxes_count;
hitboxes_count = backtrack::hitboxes_for_player(player, tick, hitboxes);
backtrack::hitboxes_for_player(player, current_tick - delta, hitboxes);
}
// check best hitbox first
if (visible(player, hitboxes.centre[best_box.first], best_box.first)) {
pos = hitboxes.centre[best_box.first];
@ -191,52 +190,11 @@ auto visible_target_inner(Player *player, std::pair<int, bool> best_box, u32 cur
// To compliment this we can check whether any player is visible first, then check all players in tickcount -1
// and then -2, so on so forth.
auto visible_target(Entity *e, math::Vector &pos, u32 &cmd_delta) {
auto visible_player(Player *p, std::pair<int, bool> &best_box, u32 tick, math::Vector &pos) {
profiler_profile_function();
// TODO: should entity have a to_player_nocheck() method
// as we already know at this point that this is a player...
auto player = e->to_player();
PlayerHitboxes hitboxes;
auto hitboxes_count = player->hitboxes(&hitboxes, false);
// Tell backtrack about these hitboxes
backtrack::update_player_hitboxes(player, hitboxes, hitboxes_count);
auto current_tick = IFace<Globals>()->tickcount;
auto best_box = find_best_box();
bool reverse_order = doghook_aimbot_reverse_backtrack_order;
if (!reverse_order) {
// Do no backtrack first
auto visible = visible_target_inner(player, best_box, current_tick, 0, hitboxes, hitboxes_count, pos);
if (visible) return true;
}
if (!doghook_aimbot_enable_backtrack) return false;
// If we are going in reverse order then make sure that happens
const auto delta_delta = reverse_order ? -1 : 1;
auto delta = reverse_order ? backtrack::max_ticks : 0;
u32 new_tick;
do {
// Go onto the next tick and see what
delta += delta_delta;
new_tick = current_tick - delta;
if (!backtrack::tick_valid(new_tick)) continue;
if (backtrack::backtrack_player_to_tick(player, current_tick - delta)) {
auto visible = visible_target_inner(player, best_box, current_tick, delta, hitboxes, hitboxes_count, pos);
if (visible) {
cmd_delta = delta;
return true;
}
}
} while (delta > 0 && delta < backtrack::max_ticks);
auto visible = visible_target_inner(p, best_box, tick, pos);
if (visible) return true;
return false;
}
@ -253,7 +211,8 @@ auto valid_target(Entity *e) {
}
void finished_target(Target t) {
IFace<DebugOverlay>()->add_entity_text_overlay(t.e->index(), 2, 0, 255, 255, 255, 255, "finished");
IFace<DebugOverlay>()->add_entity_text_overlay(t.e->index(), 1, 0, 255, 255, 255, 255, "finished");
IFace<DebugOverlay>()->add_entity_text_overlay(t.e->index(), 2, 0, 255, 255, 255, 255, "%d", t.cmd_delta);
targets.push_back(t);
}
@ -270,27 +229,67 @@ auto sort_targets() {
auto find_targets() {
profiler_profile_function();
if (can_find_targets == false) return;
auto best_box = find_best_box();
// find targets
// TODO: clean up this mess
for (auto e : IFace<EntList>()->get_range()) {
if (!e->is_valid()) continue;
if (e->dormant()) continue;
auto find_target_inner = [&best_box](u32 tick, u32 delta) {
for (auto e : IFace<EntList>()->get_range()) {
if (!e->is_valid()) continue;
if (valid_target(e)) {
auto pos = math::Vector::invalid();
auto delta = 0u;
if (visible_target(e, pos, delta)) {
finished_target(Target{e, pos, delta});
if (valid_target(e)) {
auto pos = math::Vector::invalid();
// Now that we have a target break!
// TODO: only do this when we want to do speedy targets!
//break;
if (auto p = e->to_player()) {
if (visible_player(p, best_box, tick, pos)) {
finished_target(Target{e, pos, delta});
// Now that we have a target break!
// TODO: only do this when we want to do speedy targets!
//break;
}
}
}
}
};
auto current_tick = IFace<Globals>()->tickcount;
bool reverse_order = doghook_aimbot_reverse_backtrack_order;
// Easy out
if (!reverse_order || !doghook_aimbot_enable_backtrack) {
find_target_inner(current_tick, 0);
if (targets.size() > 0) {
sort_targets();
return;
}
}
if (!doghook_aimbot_enable_backtrack) return;
const auto delta_delta = reverse_order ? -1 : 1;
auto delta = reverse_order ? backtrack::max_ticks : 1;
u32 new_tick;
backtrack::RewindState rewind;
do {
new_tick = current_tick - delta;
if (backtrack::tick_valid(new_tick)) {
rewind.to_tick(new_tick);
find_target_inner(new_tick, delta);
// we found some targets for this state... stop
if (targets.size() > 0) break;
}
// Move to the next tick
delta += delta_delta;
new_tick = current_tick - delta;
} while (delta > 0 && delta < backtrack::max_ticks);
sort_targets();
}

View File

@ -1,9 +1,11 @@
#include <precompiled.hh>
#include <algorithm>
#include <queue>
#include "backtrack.hh"
#include <sdk/convar.hh>
#include <sdk/log.hh>
#include <sdk/player.hh>
#include <sdk/sdk.hh>
@ -84,6 +86,7 @@ public:
u32 max_hitboxes;
// misc
u8 life_state;
float simulation_time;
float animation_time;
float choked_time;
@ -123,25 +126,91 @@ auto &current_record(u32 index) { return record(index, current_tick); }
// Players that have been moved this tick and need restoring
std::vector<sdk::Player *> players_to_restore;
struct sequence {
u32 in_state;
u32 out_state;
u32 in_sequence;
u32 out_sequence;
float cur_time;
};
std::deque<sequence> sequences;
u32 last_incoming_sequence;
Convar<float> doghook_backtrack_latency{"doghook_backtrack_latency", 0, 0, 1, nullptr};
void add_latency_to_netchannel(NetChannel *c) {
float current_time = IFace<Globals>()->realtime;
for (auto &s : sequences) {
if (current_time - s.cur_time > doghook_backtrack_latency) {
c->in_reliable_state() = s.in_state;
c->in_sequence() = s.in_sequence;
break;
}
}
}
void update_incoming_sequences() {
NetChannel *c = IFace<Engine>()->net_channel_info();
auto incoming_sequence = c->in_sequence();
if (incoming_sequence > last_incoming_sequence) {
last_incoming_sequence = incoming_sequence;
sequences.push_front({c->in_reliable_state(), c->out_reliable_state(), c->in_sequence(), c->out_sequence(), IFace<Globals>()->realtime});
}
if (sequences.size() > 2048)
sequences.pop_back();
}
auto latency_outgoing = 0.0f;
auto latency_incoming = 0.0f;
auto total_latency_time = 0.0f;
u32 total_latency_ticks = 0;
sdk::ConvarWrapper cl_interp{"cl_interp"};
sdk::ConvarWrapper cl_interp_ratio{"cl_interp_ratio"};
sdk::ConvarWrapper cl_update_rate{"cl_updaterate"};
sdk::ConvarWrapper sv_client_min_interp_ratio{"sv_client_min_interp_ratio"};
sdk::ConvarWrapper sv_client_max_interp_ratio{"sv_client_max_interp_ratio"};
sdk::ConvarWrapper sv_minupdaterate{"sv_minupdaterate"};
sdk::ConvarWrapper sv_maxupdaterate{"sv_maxupdaterate"};
sdk::ConvarWrapper sv_maxunlag{"sv_maxunlag"};
float lerp_time() {
auto interp_ratio = std::clamp(cl_interp_ratio.get_float(), sv_client_min_interp_ratio.get_float(), sv_client_max_interp_ratio.get_float());
auto update_rate = std::clamp(cl_update_rate.get_float(), sv_minupdaterate.get_float(), sv_maxupdaterate.get_float());
auto lerp_time = std::max(interp_ratio / update_rate, cl_interp.get_float());
return lerp_time;
}
bool tick_valid(u32 tick) {
auto net_channel = IFace<Engine>()->net_channel_info();
// TODO: do not hardcode
// Assuming lowest possible
auto lerp_time = 0.015f;
auto lerp_ticks = 1;
auto lerp = lerp_time();
auto lerp_ticks = IFace<Globals>()->time_to_ticks(lerp);
auto cmd_arrive_tick = IFace<Globals>()->tickcount + 1;
auto correct = std::clamp(lerp + doghook_backtrack_latency + latency_outgoing, 0.0f, sv_maxunlag.get_float());
auto correct = std::clamp(lerp_time + latency_outgoing, 0.0f, 0.1f) -
IFace<Globals>()->ticks_to_time(cmd_arrive_tick - tick);
auto delta_time = correct - IFace<Globals>()->ticks_to_time(IFace<Globals>()->tickcount + 1 + lerp_ticks - tick);
return std::abs(correct) <= 0.2;
bool valid = std::abs(delta_time) <= 0.2;
if (!valid) {
auto new_tick = IFace<Globals>()->tickcount - IFace<Globals>()->time_to_ticks(correct);
//logging::msg("[Backtracking] !valid (%d -> %d d: %d)", tick, new_tick, new_tick - tick);
}
return valid;
}
// Update player to this record
@ -219,16 +288,20 @@ static bool restore_player_to_record(sdk::Player *p, const Record &r) {
}
// Backtrack a player to this tick
bool backtrack_player_to_tick(sdk::Player *p, u32 tick, bool restoring) {
bool backtrack_player_to_tick(sdk::Player *p, u32 tick, bool set_alive, bool restoring) {
profiler_profile_function();
auto array_index = entity_index_to_array_index(p->index());
auto &r = record(array_index, tick);
if (!r.alive) return false;
if (set_alive) p->life_state() = r.life_state;
if (!restoring) players_to_restore.push_back(p);
return restore_player_to_record(p, r);
bool success = restore_player_to_record(p, r);
return success;
}
// Set the hitboxes for this player at this tick
@ -243,22 +316,26 @@ void update_player_hitboxes(sdk::Player *p, const sdk::PlayerHitboxes &hitboxes,
// Get the hitboxes for this player at this tick
// TODO: this can probably just return a const pointer to the hitboxes inside the record
void hitboxes_for_player(sdk::Player *p, u32 tick, sdk::PlayerHitboxes &hitboxes) {
u32 hitboxes_for_player(sdk::Player *p, u32 tick, sdk::PlayerHitboxes &hitboxes) {
profiler_profile_function();
auto array_index = entity_index_to_array_index(p->index());
auto &r = record(array_index, tick);
if (!r.alive) return;
if (!r.alive) return 0;
std::memcpy(&hitboxes, &r.hitboxes, sizeof(PlayerHitboxes));
return r.max_hitboxes;
}
// Get new information about each player
void create_move_pre_predict(sdk::UserCmd *cmd) {
profiler_profile_function();
update_incoming_sequences();
current_tick = IFace<Globals>()->tickcount;
auto local_player = Player::local();
@ -286,7 +363,8 @@ void create_move_pre_predict(sdk::UserCmd *cmd) {
// Clean out the record - might not be necessary but a memset is pretty cheap
new_record.reset();
new_record.alive = player->alive();
new_record.life_state = player->life_state();
new_record.alive = player->alive();
if (player->dormant()) new_record.alive = false;
// If this fails then it is clear to other algorithms whether this record is valid just by looking at this bool
@ -330,7 +408,7 @@ void create_move_pre_predict(sdk::UserCmd *cmd) {
new_record.animation_layers[i] = player->anim_layer(i);
}
// TODO: pose parameters (are these even important??)
new_record.max_hitboxes = player->hitboxes(&new_record.hitboxes, false);
}
}
@ -343,12 +421,16 @@ void create_move(sdk::UserCmd *cmd) {
auto player = entity->to_player();
if (auto player = entity->to_player()) {
// for (auto &r : record_track(player->index()))
{
auto &r = record(player->index(), IFace<Globals>()->tickcount + 1);
for (auto &r : record_track(player->index())) {
//auto &r = record(player->index(), IFace<Globals>()->tickcount + 1);
if (!r.alive) continue;
if (!tick_valid(r.this_tick)) continue;
IFace<DebugOverlay>()->add_box_overlay(r.origin, {-2, -2, -2}, {2, 2, 2}, {0, 0, 0}, 0, 255, 0, 100, 0);
#if 0
auto &hitboxes = r.hitboxes;
for (u32 i = 0; i < r.max_hitboxes; i++) {
@ -359,8 +441,6 @@ void create_move(sdk::UserCmd *cmd) {
IFace<DebugOverlay>()->add_box_overlay(hitboxes.origin[i], hitboxes.raw_min[i], hitboxes.raw_max[i], hitboxes.rotation[i], r, g, b, 100, 0);
auto bone_transform = hitboxes.bone_to_world[i];
//math::Vector origin;
//math::Vector angles;
//math::matrix_angles(bone_transform, angles, origin);
@ -371,19 +451,64 @@ void create_move(sdk::UserCmd *cmd) {
//IFace<DebugOverlay>()->add_box_overlay(origin, hitboxes.raw_min[i], hitboxes.raw_max[i], angles, r, g, b, 100, 0);
}
#endif
}
}
}
#endif
}
void restore_all_players() {
for (auto &p : players_to_restore)
backtrack_player_to_tick(p, current_tick, true, true);
players_to_restore.clear();
}
void create_move_finish(sdk::UserCmd *cmd) {
profiler_profile_function();
// Cleanup from whatever has been done
for (auto &p : players_to_restore)
backtrack_player_to_tick(p, current_tick, true);
restore_all_players();
}
players_to_restore.clear();
bool rewind_state_active = false;
RewindState::RewindState() {
assert(!rewind_state_active);
rewind_state_active = true;
}
RewindState::~RewindState() {
restore_all_players();
rewind_state_active = false;
}
// TODO: if a player isnt alive at a record then we need a way
// of effectively eliminating their bones from traceray
void RewindState::to_tick(u32 t) {
// TODO: could this be multithreaded effectively?
auto local_player = Player::local();
for (auto entity : IFace<EntList>()->get_range(IFace<Engine>()->max_clients() + 1)) {
if (!entity->is_valid()) continue;
if (auto p = entity->to_player()) {
if (p == local_player) continue;
if (p->team() == local_player->team()) continue;
// Setalive so that dead players at this tick are dead...
// This means that aimbots is_valid will work properly
auto success = backtrack_player_to_tick(p, t, true);
if (!success) {
// Player isnt valid this tick...
// TODO: Set hitboxes to invalid here...
}
}
}
}
} // namespace backtrack

View File

@ -5,20 +5,34 @@
namespace sdk {
class UserCmd;
class Player;
class NetChannel;
struct PlayerHitboxes;
} // namespace sdk
namespace backtrack {
// Helper class to rewind all players back to a specific tick
class RewindState {
public:
RewindState();
~RewindState();
void to_tick(u32 tick);
};
enum { max_ticks = 66 };
void create_move_pre_predict(sdk::UserCmd *cmd);
void create_move(sdk::UserCmd *cmd);
void create_move_finish(sdk::UserCmd *cmd);
// Returns true if this tick was valid
bool backtrack_player_to_tick(sdk::Player *p, u32 tick, bool restoring = false);
bool tick_valid(u32 tick);
float lerp_time();
bool tick_valid(u32 tick);
void update_player_hitboxes(sdk::Player *p, const sdk::PlayerHitboxes &hitboxes, u32 hitboxes_count);
void hitboxes_for_player(sdk::Player *p, u32 tick, sdk::PlayerHitboxes &hitboxes);
u32 hitboxes_for_player(sdk::Player *p, u32 tick, sdk::PlayerHitboxes &hitboxes);
// Returns true if this tick was valid
// You should be using RewindState instead of this...
bool backtrack_player_to_tick(sdk::Player *p, u32 tick, bool set_alive = false, bool restoring = false);
void add_latency_to_netchannel(sdk::NetChannel *channel);
} // namespace backtrack

View File

@ -92,8 +92,8 @@ void create_move(UserCmd *cmd) {
last_wep_id = current_wep_id;
if (IFace<InputSystem>()->is_button_down((ButtonCode)(int)doghook_lagexploit_key)) // SDK is gay // look in inputcodes.hh for possible solution and nicer shit
if (IFace<InputSystem>()->is_button_down((ButtonCode)(int)doghook_lagexploit_key))
numsequnce = doghook_lagexploit_ticks * (doghook_lagexploit_backup ? 90 : 66);
IFace<Engine>()->net_channel_info()->m_nOutSequenceNr() += numsequnce;
IFace<Engine>()->net_channel_info()->out_sequence() += numsequnce;
};
}; // namespace lagexploit

View File

@ -10,9 +10,62 @@ using namespace sdk;
namespace misc {
static sdk::ConvarWrapper sv_cheats{"sv_cheats"};
// TODO: cleanup and move out of here
enum cvar_flags {
FCVAR_UNREGISTERED = (1 << 0), // If this is set, don't add to linked list, etc.
FCVAR_DEVELOPMENTONLY = (1 << 1), // Hidden in released products. Flag is removed automatically if ALLOW_DEVELOPMENT_CVARS is defined.
FCVAR_GAMEDLL = (1 << 2), // defined by the game DLL
FCVAR_CLIENTDLL = (1 << 3), // defined by the client DLL
FCVAR_HIDDEN = (1 << 4), // Hidden. Doesn't appear in find or autocomplete. Like DEVELOPMENTONLY, but can't be compiled out.
// ConVar only
FCVAR_PROTECTED = (1 << 5), // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value
FCVAR_SPONLY = (1 << 6), // This cvar cannot be changed by clients connected to a multiplayer server.
FCVAR_ARCHIVE = (1 << 7), // set to cause it to be saved to vars.rc
FCVAR_NOTIFY = (1 << 8), // notifies players when changed
FCVAR_USERINFO = (1 << 9), // changes the client's info string
FCVAR_CHEAT = (1 << 14), // Only useable in singleplayer / debug / multiplayer & sv_cheats
FCVAR_PRINTABLEONLY = (1 << 10), // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ).
FCVAR_UNLOGGED = (1 << 11), // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log
FCVAR_NEVER_AS_STRING = (1 << 12), // never try to print that cvar
// It's a ConVar that's shared between the client and the server.
// At signon, the values of all such ConVars are sent from the server to the client (skipped for local
// client, of course )
// If a change is requested it must come from the console (i.e., no remote client changes)
// If a value is changed while a server is active, it's replicated to all connected clients
FCVAR_REPLICATED = (1 << 13), // server setting enforced on clients, TODO rename to FCAR_SERVER at some time
FCVAR_DEMO = (1 << 16), // record this cvar when starting a demo file
FCVAR_DONTRECORD = (1 << 17), // don't record these command in demofiles
FCVAR_RELOAD_MATERIALS = (1 << 20), // If this cvar changes, it forces a material reload
FCVAR_RELOAD_TEXTURES = (1 << 21), // If this cvar changes, if forces a texture reload
FCVAR_NOT_CONNECTED = (1 << 22), // cvar cannot be changed by a client that is connected to a server
FCVAR_MATERIAL_SYSTEM_THREAD = (1 << 23), // Indicates this cvar is read from the material system thread
FCVAR_ARCHIVE_XBOX = (1 << 24), // cvar written to config.cfg on the Xbox
FCVAR_ACCESSIBLE_FROM_THREADS = (1 << 25), // used as a debugging tool necessary to check material system thread convars
FCVAR_SERVER_CAN_EXECUTE = (1 << 28), // the server is allowed to execute this command on clients via ClientCommand/NET_StringCmd/CBaseClientState::ProcessStringCmd.
FCVAR_SERVER_CANNOT_QUERY = (1 << 29), // If this is set, then the server is not allowed to query this cvar's value (via IServerPluginHelpers::StartQueryCvarValue).
FCVAR_CLIENTCMD_CAN_EXECUTE = (1 << 30), // IVEngineClient::ClientCmd is allowed to execute this command.
// Note: IVEngineClient::ClientCmd_Unrestricted can run any client command.
};
void init_all() {
sv_cheats.set_flags(0);
for (auto c : sdk::ConvarWrapper::get_range()) {
auto flags = c.flags();
flags &= ~FCVAR_CHEAT;
flags &= ~FCVAR_DEVELOPMENTONLY;
flags &= ~FCVAR_PROTECTED;
flags &= ~FCVAR_SPONLY;
flags &= ~FCVAR_CHEAT;
flags &= ~FCVAR_REPLICATED;
flags &= ~FCVAR_NOT_CONNECTED;
flags &= ~FCVAR_HIDDEN;
c.set_flags(flags);
}
}
} // namespace misc

View File

@ -412,4 +412,11 @@ void ConvarWrapper::set_value(const char *v) {
base->set_value(v);
}
ConvarWrapper ConvarWrapper::Range::Iterator::operator++() {
current = current->next;
return ConvarWrapper(const_cast<ConCommandBase *>(current));
}
ConvarWrapper::Range::Iterator ConvarWrapper::Range::begin() const { return Iterator(IFace<Cvar>()->root_node()); }
} // namespace sdk

View File

@ -309,6 +309,8 @@ public:
class ConvarWrapper {
ConCommandBase *base;
ConvarWrapper(ConCommandBase *b) : base(b) {}
public:
ConvarWrapper(const char *name);
@ -325,6 +327,33 @@ public:
void set_value(int v);
void set_value(float v);
void set_value(const char *v);
class Range {
public:
class Iterator {
const ConCommandBase *current;
public:
Iterator() : current(nullptr) {}
explicit Iterator(const ConCommandBase *b) : current(b) {}
ConvarWrapper operator++();
auto operator*() const {
assert(current);
return ConvarWrapper(const_cast<ConCommandBase *>(current));
}
auto operator==(const Iterator &b) const { return current == b.current; }
auto operator!=(const Iterator &b) const { return !(*this == b); }
};
Iterator begin() const;
Iterator end() const { return Iterator(nullptr); }
};
static auto get_range() { return Range(); }
};
} // namespace sdk

View File

@ -24,10 +24,10 @@ public:
auto set(u32 offset, T data) { *reinterpret_cast<T *>(reinterpret_cast<uptr>(this) + offset) = data; }
template <typename T, u32 offset>
auto get() { return *reinterpret_cast<T *>(reinterpret_cast<uptr>(this) + offset); }
auto &get() { return *reinterpret_cast<T *>(reinterpret_cast<uptr>(this) + offset); }
template <typename T>
auto get(u32 offset) { return *reinterpret_cast<T *>(reinterpret_cast<uptr>(this) + offset); }
auto &get(u32 offset) { return *reinterpret_cast<T *>(reinterpret_cast<uptr>(this) + offset); }
// upcasts
class Player *to_player();

View File

@ -181,7 +181,7 @@ public:
using F = ret(__thiscall *)(T *, Args...);
#endif
auto function = reinterpret_cast<F>(original_function);
function(instance, args...);
return function(instance, args...);
}
};

View File

@ -21,7 +21,11 @@ int & Player::health() {
}
static auto lifestate = Netvar("DT_BasePlayer", "m_lifeState");
bool Player::alive() {
u8 & Player::life_state() {
return ::lifestate.get<u8>(this);
}
bool Player::alive() {
return ::lifestate.get<u8>(this) == 0;
}

View File

@ -62,6 +62,7 @@ public:
// netvars
int &health();
u8 & life_state();
bool alive();
int team();

View File

@ -91,11 +91,17 @@ public:
return queued_packets;
}
i32 &m_nOutSequenceNr() {
static u32 m_nOutSequenceNr_offset = 8; // TODO: don't hardcode
// Below this point are functions that use offsets
// These offsets should be consistent accross multiple platforms
// As the netchannel structure is the same
return *(i32 *)((u32)this + m_nOutSequenceNr_offset);
}
template <typename T, u32 offset>
auto &get() { return *reinterpret_cast<T *>(reinterpret_cast<uptr>(this) + offset); }
auto &out_sequence() { return get<u32, 8>(); }
auto &in_sequence() { return get<u32, 12>(); }
auto &in_reliable_state() { return get<u32, 24>(); }
auto &out_reliable_state() { return get<u32, 20>(); }
};
class Globals {