Eventcallbacks replace hookedfunctions

This commit is contained in:
TotallyNotElite 2018-12-15 18:12:18 +01:00
parent 1a64809630
commit 7157d57da1
23 changed files with 241 additions and 209 deletions

View File

@ -40,8 +40,7 @@ constexpr bool IsTF()
return IsTF2() || IsTF2C();
}
// This one is supposed to be `if constexpr` but I have to upgrade to gcc7
#define IF_GAME(x) if (x)
#define IF_GAME(x) if constexpr (x)
#else

View File

@ -1,9 +0,0 @@
#include "common.hpp"
#include <hacks/Aimbot.hpp>
namespace hacks::shared::lightesp
{
#if ENABLE_VISUALS
void draw();
rgba_t LightESPColor(CachedEntity *ent);
#endif
} // namespace hacks::shared::lightesp

View File

@ -12,7 +12,6 @@
#if ENABLE_VISUALS
#include "ESP.hpp"
#include "LightESP.hpp"
#include "SkinChanger.hpp"
#include "Radar.hpp"
#include "SpyAlert.hpp"

View File

@ -1,111 +1,93 @@
#pragma once
#include <vector>
#include <functional>
#include <string>
#include "core/profiler.hpp"
#include <string>
#include "config.h"
#include "memory"
#include "functional"
#include <set>
class HookedFunction;
namespace HookTools
namespace EC
{
std::vector<HookedFunction *> &GetHookedFunctions();
void CM();
void DRAW();
void PAINT();
} // namespace HookTools
enum HookedFunctions_types
enum ec_types : int8_t
{
// Use CreateMove to run functions that need to run while ingame.
HF_CreateMove = 0,
// Use Draw to draw on screen
HF_Draw,
// Use Paint to run functions everywhere (including main menu)
HF_Paint
CreateMove = 0,
#if ENABLE_VISUALS
Draw,
#endif
Paint,
LevelInit
};
class HookedFunction
enum priority : int8_t
{
std::function<void()> m_func;
int m_priority;
std::string m_name;
#if ENABLE_PROFILER
ProfilerSection section = ProfilerSection("UNNAMED_FUNCTIONS");
very_early = -2,
early,
average,
late,
very_late
};
template <ec_types T> struct EventCallbackData
{
explicit EventCallbackData(std::function<void()> function, std::string name, int8_t priority) : function{ function }, priority{ priority }, section{ name }
{
}
std::function<void()> function;
int8_t priority;
mutable ProfilerSection section;
bool operator<(const EventCallbackData &other) const
{
return priority < other.priority;
}
};
extern std::multiset<EventCallbackData<CreateMove>> createmoves;
#if ENABLE_VISUALS
extern std::multiset<EventCallbackData<Draw>> draws;
#endif
void init(HookedFunctions_types type, std::string name, int priority, std::function<void()> func)
extern std::multiset<EventCallbackData<Paint>> paints;
extern std::multiset<EventCallbackData<LevelInit>> levelinits;
template <ec_types T> void Register(std::function<void()> function, std::string name, int8_t priority)
{
switch (T)
{
switch (type)
case CreateMove:
{
case HF_CreateMove:
m_name = "CM_";
EventCallbackData<CreateMove> data(function, name, priority);
createmoves.insert(data);
break;
case HF_Draw:
m_name = "DRAW_";
}
#if ENABLE_VISUALS
case Draw:
{
EventCallbackData<Draw> data(function, name, priority);
draws.insert(data);
break;
case HF_Paint:
m_name = "PAINT_";
}
#endif
case Paint:
{
EventCallbackData<Paint> data(function, name, priority);
paints.insert(data);
break;
}
case LevelInit:
{
EventCallbackData<LevelInit> data(function, name, priority);
levelinits.insert(data);
break;
}
default:
m_name = "UNDEFINED_";
throw(std::invalid_argument("Unknown event"));
break;
}
m_name.append(name);
m_priority = priority;
m_func = func;
m_type = type;
#if ENABLE_PROFILER
section.m_name = m_name;
#endif
HookTools::GetHookedFunctions().push_back(this);
}
}
public:
HookedFunctions_types m_type;
bool run(HookedFunctions_types type)
{
if (m_type == type)
{
#if ENABLE_PROFILER
ProfilerNode node(section);
void RunCreateMove();
#if ENABLE_VISUALS
void RunDraw();
#endif
m_func();
return true;
}
return false;
}
bool operator>(HookedFunction const &other)
{
if (this->m_type < other.m_type)
return true;
return this->m_priority > other.m_priority;
}
HookedFunction(HookedFunctions_types type, std::string name, int priority, std::function<void()> func)
{
init(type, name, priority, func);
}
HookedFunction(HookedFunctions_types type, int priority, std::function<void()> func)
{
static const std::string name("UNNAMED_FUNCTIONS");
init(type, name, priority, func);
}
HookedFunction(HookedFunctions_types type, std::string name, std::function<void()> func)
{
int priority = 5;
init(type, name, priority, func);
}
HookedFunction(HookedFunctions_types type, std::function<void()> func)
{
static const std::string name("UNNAMED_FUNCTIONS");
int priority = 5;
init(type, name, priority, func);
}
};
void RunPaint();
// struct CreateMove
//{
// int priority = 0;
// CreateMove(int priority, std::function<void()> func);
// CreateMove(std::function<void()> func);
//};
void RunLevelInit();
} // namespace EC

View File

@ -148,7 +148,8 @@ void updateSearch()
}
#endif
}
static HookedFunction update(HookedFunctions_types::HF_CreateMove, "Autojoin", 1, []() {
static void update()
{
if (autoteam_timer.test_and_set(500))
{
if (autojoin_team and UnassignedTeam())
@ -161,11 +162,13 @@ static HookedFunction update(HookedFunctions_types::HF_CreateMove, "Autojoin", 1
g_IEngine->ExecuteClientCmd(format("join_class ", classnames[int(autojoin_class) - 1]).c_str());
}
}
});
}
void onShutdown()
{
if (auto_queue)
tfmm::startQueue();
}
static InitRoutine init([]() { EC::Register<EC::CreateMove>(update, "cm_autojoin", EC::average); });
} // namespace hacks::shared::autojoin

View File

@ -270,7 +270,8 @@ static std::string ammo = "Ammo: 0/0";
static int max_ammo;
static CachedEntity *local_w;
// TODO: add more stuffs
static HookedFunction cm(HF_CreateMove, "catbot", 5, []() {
static void cm()
{
if (!*catbotmode)
return;
@ -309,7 +310,7 @@ static HookedFunction cm(HF_CreateMove, "catbot", 5, []() {
}
if (*autoReport && report_timer.test_and_set(60000))
reportall();
});
}
static Timer autojointeam{};
void update()
@ -427,13 +428,21 @@ void level_init()
}
#if ENABLE_VISUALS
static HookedFunction Paint(HookedFunctions_types::HF_Draw, "anti_motd_info", 3, []() {
static void draw()
{
if (!catbotmode || !anti_motd)
return;
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer())
return;
AddCenterString(health, colors::green);
AddCenterString(ammo, colors::yellow);
});
}
#endif
static InitRoutine runinit([]() {
EC::Register<EC::CreateMove>(cm, "cm_catbot", EC::average);
#if ENABLE_VISUALS
EC::Register<EC::Draw>(cm, "draw_catbot", EC::average);
#endif
});
} // namespace hacks::shared::catbot

View File

@ -234,7 +234,7 @@ void Draw()
}
// Function called on create move
static HookedFunction CreateMove(HookedFunctions_types::HF_CreateMove, "ESP", 18, []() {
static void cm() {
// Check usersettings if enabled
if (!*enable)
return;
@ -288,7 +288,7 @@ static HookedFunction CreateMove(HookedFunctions_types::HF_CreateMove, "ESP", 18
}
}
}
});
}
static glez::texture atlas{ DATA_PATH "/textures/atlas.png" };
static glez::texture idspec{ DATA_PATH "/textures/idspec.png" };
@ -1443,4 +1443,10 @@ void SetEntityColor(CachedEntity *entity, const rgba_t &color)
return;
data[entity->m_IDX].color = color;
}
static InitRoutine init([](){
EC::Register<EC::CreateMove>(cm, "cm_walkbot", EC::average);
});
} // namespace hacks::shared::esp

View File

@ -188,7 +188,7 @@ Timer waittime{};
int lastent = 0;
#if ENABLE_IPC
static HookedFunction WorldTick(HookedFunctions_types::HF_CreateMove, "followbot", 20, []() {
static void cm() {
if (!enable)
{
follow_target = 0;
@ -555,11 +555,11 @@ static HookedFunction WorldTick(HookedFunctions_types::HF_CreateMove, "followbot
}
else
idle_time.update();
});
}
#endif
#if ENABLE_VISUALS
static HookedFunction func(HF_Draw, "followbot", 10, []() {
static void draw() {
if (!enable || !draw_crumb)
return;
if (breadcrumbs.size() < 2)
@ -577,9 +577,19 @@ static HookedFunction func(HF_Draw, "followbot", 10, []() {
return;
glez::draw::rect(wts.x - 4, wts.y - 4, 8, 8, colors::white);
glez::draw::rect_outline(wts.x - 4, wts.y - 4, 7, 7, colors::white, 1.0f);
});
}
#endif
static InitRoutine runinit([](){
#if ENABLE_IPC
EC::Register<EC::CreateMove>(cm, "cm_followbot", EC::average);
#endif
#if ENABLE_VISUALS
EC::Register<EC::Draw>(cm, "draw_followbot", EC::average);
#endif
});
int getTarget()
{
return follow_target;

View File

@ -104,7 +104,7 @@ class KillSayEventListener : public IGameEventListener2
}
};
static HookedFunction ProcessKillsay(HookedFunctions_types::HF_Paint, "KillSay_send", 1, []() {
static void ProcessKillsay() {
if (killsay_storage.empty())
return;
for (auto &i : killsay_storage)
@ -117,6 +117,10 @@ static HookedFunction ProcessKillsay(HookedFunctions_types::HF_Paint, "KillSay_s
i.second = {};
}
}
}
static InitRoutine runinit([](){
EC::Register<EC::Paint>(ProcessKillsay, "paint_killsay", EC::average);
});
static KillSayEventListener listener{};

View File

@ -1,21 +1,28 @@
#include "hacks/LightESP.hpp"
#include "config.h"
#if ENABLE_VISUALS
#include <glez/draw.hpp>
#endif
#include <settings/Bool.hpp>
#include "common.hpp"
#include "glez/draw.hpp"
static settings::Bool enable{ "lightesp.enable", "false" };
namespace hacks::shared::lightesp
{
Vector hitp[32];
Vector minp[32];
Vector maxp[32];
bool drawEsp[32];
static Vector hitp[32];
static Vector minp[32];
static Vector maxp[32];
static bool drawEsp[32];
#if ENABLE_VISUALS
static HookedFunction cm(HF_CreateMove, "lightesp", 5, []() {
rgba_t LightESPColor(CachedEntity *ent)
{
if (!playerlist::IsDefault(ent))
{
return playerlist::Color(ent);
}
return colors::green;
}
static void cm() {
if (!*enable)
return;
for (int i = 1; i < g_IEngine->GetMaxClients(); i++)
@ -40,12 +47,10 @@ static HookedFunction cm(HF_CreateMove, "lightesp", 5, []() {
maxp[i] = pEntity->hitboxes.GetHitbox(0)->max;
drawEsp[i] = true;
}
});
#endif
}
void draw()
{
#if ENABLE_VISUALS
if (!enable)
return;
for (int i = 1; i < g_IEngine->GetMaxClients(); i++)
@ -72,16 +77,12 @@ void draw()
glez::draw::rect(out.x - size / 8, out.y - size / 8, size / 4, size / 4, colors::red);
}
}
#endif
}
#if ENABLE_VISUALS
rgba_t LightESPColor(CachedEntity *ent)
{
if (!playerlist::IsDefault(ent))
{
return playerlist::Color(ent);
}
return colors::green;
}
#endif
static InitRoutine init([](){
EC::Register<EC::CreateMove>(cm, "cm_lightesp", EC::average);
EC::Register<EC::Draw>(draw, "draw_lightesp", EC::average);
});
} // namespace hacks::shared::lightesp
#endif

View File

@ -146,7 +146,7 @@ void DoSlowAim(Vector &input_angle)
fClampAngle(input_angle);
}
static HookedFunction SandwichAim(HookedFunctions_types::HF_CreateMove, "SandwichAim", 5, []() {
static void SandwichAim() {
if (!*sandwichaim_enabled)
return;
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer() || CE_BAD(LOCAL_W))
@ -186,11 +186,11 @@ static HookedFunction SandwichAim(HookedFunctions_types::HF_CreateMove, "Sandwic
current_user_cmd->buttons |= IN_ATTACK2;
g_pLocalPlayer->bUseSilentAngles = true;
}
});
}
static bool charge_aimbotted = false;
static settings::Bool charge_aim{ "chargeaim.enable", "false" };
static settings::Button charge_key{ "chargeaim.key", "<null>" };
static HookedFunction ChargeAimbot(HookedFunctions_types::HF_CreateMove, "ChargeAim", 2, []() {
static void ChargeAimbot() {
charge_aimbotted = false;
if (!*charge_aim)
return;
@ -214,11 +214,11 @@ static HookedFunction ChargeAimbot(HookedFunctions_types::HF_CreateMove, "Charge
current_user_cmd->viewangles = angles;
charge_aimbotted = true;
}
});
}
static settings::Bool charge_control{ "chargecontrol.enable", "false" };
static settings::Float charge_float{ "chargecontrol.strength", "3.0f" };
static HookedFunction ChargeControl(HookedFunctions_types::HF_CreateMove, "chargecontrol", 5, []() {
static void ChargeControl() {
if (!*charge_control || charge_aimbotted)
return;
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer() || CE_BAD(LOCAL_W))
@ -231,12 +231,12 @@ static HookedFunction ChargeControl(HookedFunctions_types::HF_CreateMove, "charg
if (current_user_cmd->buttons & IN_MOVERIGHT)
offset = -*charge_float;
current_user_cmd->viewangles.y += offset;
});
}
static settings::Bool autosapper_enabled("autosapper.enabled", "false");
static settings::Bool autosapper_silent("autosapper.silent", "true");
static HookedFunction SapperAimbot(HF_CreateMove, "sapperaimbot", 5, []() {
static void SapperAimbot() {
if (!autosapper_enabled)
return;
if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W) || !g_pLocalPlayer->holding_sapper)
@ -272,4 +272,13 @@ static HookedFunction SapperAimbot(HF_CreateMove, "sapperaimbot", 5, []() {
g_pLocalPlayer->bUseSilentAngles = true;
current_user_cmd->buttons |= IN_ATTACK;
}
});
}
static void CreateMove(){
SandwichAim();
ChargeAimbot();
ChargeControl();
SapperAimbot();
}
static InitRoutine init([](){EC::Register<EC::CreateMove>(CreateMove, "cm_miscaimbot", EC::late);});

View File

@ -442,7 +442,9 @@ static void updateSlot()
}
}
static HookedFunction cm(HookedFunctions_types::HF_CreateMove, "NavBot", 16, &CreateMove);
static InitRoutine runinit([](){
EC::Register<EC::Paint>(CreateMove, "paint_killsay", EC::average);
});
void change(settings::VariableBase<bool> &, bool)
{

View File

@ -1149,7 +1149,7 @@ void OnLevelInit()
Timer quit_timer{};
Timer map_check{};
int erasedelay = 0;
static HookedFunction Move(HookedFunctions_types::HF_CreateMove, "Walkbot", 16, []() {
static void cm() {
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer() || CE_BAD(LOCAL_W))
return;
if (state::state == WB_DISABLED)
@ -1219,5 +1219,10 @@ static HookedFunction Move(HookedFunctions_types::HF_CreateMove, "Walkbot", 16,
}
break;
}
}
static InitRoutine init([](){
EC::Register<EC::CreateMove>(cm, "cm_walkbot", EC::average);
});
} // namespace hacks::shared::walkbot

View File

@ -93,11 +93,6 @@ static int attackticks = 0;
namespace hooked_methods
{
static HookedFunction viewangs(HookedFunctions_types::HF_CreateMove, "set_ang", 21, []() {
if (CE_BAD(LOCAL_E))
return;
g_pLocalPlayer->v_OrigViewangles = current_user_cmd->viewangles;
});
DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, CUserCmd *cmd)
{
#define TICK_INTERVAL (g_GlobalVars->interval_per_tick)
@ -281,7 +276,7 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, CUs
{
PROF_SECTION(CM_WRAPPER);
HookTools::CM();
EC::RunCreateMove();
}
if (CE_GOOD(g_pLocalPlayer->entity))
{

View File

@ -171,7 +171,8 @@ static InitRoutine init([]() {
});
});
static Timer set_name{};
static HookedFunction CM(HookedFunctions_types::HF_CreateMove, "namesteal", 2, []() {
static void cm()
{
if (!namesteal)
return;
if (!set_name.test_and_set(500000))
@ -187,5 +188,10 @@ static HookedFunction CM(HookedFunctions_types::HF_CreateMove, "namesteal", 2, [
setname.SetReliable(false);
ch->SendNetMsg(setname, false);
}
}
static InitRoutine runinit([]() {
EC::Register<EC::CreateMove>(cm, "cm_namesteal", EC::late);
});
} // namespace hooked_methods

View File

@ -1,43 +1,45 @@
#include "HookTools.hpp"
#include "common.hpp"
#include "HookTools.hpp"
std::vector<HookedFunction *> &HookTools::GetHookedFunctions()
namespace EC
{
static std::vector<HookedFunction *> CreateMoves{};
return CreateMoves;
// Ordered set to always keep priorities correct
std::multiset<EventCallbackData<CreateMove>> createmoves;
#if ENABLE_VISUALS
std::multiset<EventCallbackData<Draw>> draws;
#endif
std::multiset<EventCallbackData<Paint>> paints;
std::multiset<EventCallbackData<LevelInit>> levelinits;
template <typename t> inline void run(t &set)
{
for (auto &i : set)
{
#if ENABLE_PROFILER
ProfilerNode node(i.section);
#endif
i.function();
}
}
// -----------------------------------------------------------
void RunHookedFunctions(HookedFunctions_types type)
void RunCreateMove()
{
auto &HookedFunctions = HookTools::GetHookedFunctions();
for (auto &i : HookedFunctions)
i->run(type);
run(createmoves);
}
#if ENABLE_VISUALS
void RunDraw()
{
run(draws);
}
#endif
void RunPaint()
{
run(paints);
}
void HookTools::CM()
void RunLevelInit()
{
RunHookedFunctions(HF_CreateMove);
run(levelinits);
}
void HookTools::DRAW()
{
RunHookedFunctions(HF_Draw);
}
void HookTools::PAINT()
{
RunHookedFunctions(HF_Paint);
}
static InitRoutine init([]() {
auto &HookedFunctions = HookTools::GetHookedFunctions();
logging::Info("Hooked Functions amount: %i", HookedFunctions.size());
std::sort(HookedFunctions.begin(), HookedFunctions.end(), [](HookedFunction *a, HookedFunction *b) { return *a > *b; });
logging::Info("Sorted Hooked Functions: %i", HookedFunctions.size());
});
static CatCommand print("debug_print_hookedfunctions", "Print hooked functions (CreateMove, Draw, Paint)", []() {
});
} // namespace EC

View File

@ -57,6 +57,7 @@ DEFINE_HOOKED_METHOD(LevelInit, void, void *this_, const char *name)
hacks::shared::anticheat::ResetEverything();
original::LevelInit(this_, name);
hacks::shared::walkbot::OnLevelInit();
EC::RunLevelInit();
#if ENABLE_IPC
if (ipc::peer)
{

View File

@ -89,7 +89,7 @@ DEFINE_HOOKED_METHOD(Paint, void, IEngineVGui *this_, PaintMode_t mode)
render_cheat_visuals();
#endif
// Call all paint functions
HookTools::PAINT();
EC::RunPaint();
}
return original::Paint(this_, mode);

View File

@ -37,7 +37,7 @@ bool IsHoovyHelper(CachedEntity *entity)
return false;
}
static HookedFunction UpdateHoovyList(HookedFunctions_types::HF_CreateMove, "HoovyList", 19, []() {
void UpdateHoovyList() {
if (CE_BAD(LOCAL_E))
return;
@ -59,7 +59,7 @@ static HookedFunction UpdateHoovyList(HookedFunctions_types::HF_CreateMove, "Hoo
}
}
}
});
}
bool IsHoovy(CachedEntity *entity)
{
@ -67,3 +67,5 @@ bool IsHoovy(CachedEntity *entity)
return false;
return hoovy_list[entity->m_IDX - 1];
}
static InitRoutine init([](){EC::Register<EC::CreateMove>(UpdateHoovyList, "cm_hoovylist", EC::average);});

View File

@ -301,7 +301,7 @@ int GetMaxParty()
CatCommand debug_maxparty("debug_partysize", "Debug party size", []() { logging::Info("%d", GetMaxParty()); });
static Timer resize_party{};
static HookedFunction paint(HookedFunctions_types::HF_Paint, "IRC", 16, []() {
static void run() {
if (!restarting)
{
auto party_client = re::CTFPartyClient::GTFPartyClient();
@ -374,7 +374,7 @@ static HookedFunction paint(HookedFunctions_types::HF_Paint, "IRC", 16, []() {
irc.setState(state);
}
}
});
}
template <typename T> void rvarCallback(settings::VariableBase<T> &var, T after)
{
@ -394,6 +394,7 @@ template <typename T> void rvarCallback(settings::VariableBase<T> &var, T after)
}
static InitRoutine init([]() {
EC::Register<EC::Paint>(run, "PAINT_irc", EC::average);
updateData();
enabled.installChangeCallback(rvarCallback<bool>);
anon.installChangeCallback(rvarCallback<bool>);

View File

@ -91,6 +91,7 @@ void LocalPlayer::Update()
life_state = CE_BYTE(entity, netvar.iLifeState);
v_ViewOffset = CE_VECTOR(entity, netvar.vViewOffset);
v_Origin = entity->m_vecOrigin();
v_OrigViewangles = current_user_cmd->viewangles;
v_Eye = v_Origin + v_ViewOffset;
clazz = CE_INT(entity, netvar.iClass);
health = CE_INT(entity, netvar.iHealth);

View File

@ -563,7 +563,8 @@ void repath()
static Timer last_jump{};
// Main movement function, gets path from NavTo
static HookedFunction CreateMove(HookedFunctions_types::HF_CreateMove, "NavParser", 17, []() {
static void cm()
{
if (!enabled || status != on)
return;
if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W))
@ -616,10 +617,11 @@ static HookedFunction CreateMove(HookedFunctions_types::HF_CreateMove, "NavParse
}
// Walk to next crumb
WalkTo(crumbs.at(0));
});
}
#if ENABLE_VISUALS
static HookedFunction drawcrumbs(HF_Draw, "navparser", 10, []() {
static void drawcrumbs()
{
if (!enabled || !draw)
return;
if (!enabled)
@ -643,9 +645,16 @@ static HookedFunction drawcrumbs(HF_Draw, "navparser", 10, []() {
return;
glez::draw::rect(wts.x - 4, wts.y - 4, 8, 8, colors::white);
glez::draw::rect_outline(wts.x - 4, wts.y - 4, 7, 7, colors::white, 1.0f);
});
}
#endif
static InitRoutine runinit([]() {
EC::Register<EC::CreateMove>(cm, "cm_navparser", EC::late);
#if ENABLE_VISUALS
EC::Register<EC::Paint>(cm, "cm_navparser", EC::average);
#endif
});
void ResetPather()
{
Map.pather->Reset();

View File

@ -183,7 +183,7 @@ void DrawCheatVisuals()
}
{
PROF_SECTION(DRAW_WRAPPER);
HookTools::DRAW();
EC::RunDraw();
}
if (CE_GOOD(g_pLocalPlayer->entity) && !g_Settings.bInvalid)
{
@ -214,11 +214,6 @@ void DrawCheatVisuals()
PROF_SECTION(DRAW_backtracc);
hacks::shared::backtrack::Draw();
}
IF_GAME(IsTF2())
{
PROF_SECTION(DRAW_lightesp);
hacks::shared::lightesp::draw();
}
{
PROF_SECTION(DRAW_walkbot);
hacks::shared::walkbot::Draw();