From 1f57477ac76e5cdbe6611fb565b56cb4b735b507 Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 17:24:06 +0000 Subject: [PATCH] Add datatable, netvar, sdk, trace, entity, player, weapon classes --- src/datatable.hh | 103 +++++++++++ src/entity.cc | 49 ++++++ src/entity.hh | 36 ++++ src/hooks.hh | 4 +- src/interface.cc | 12 +- src/interface.hh | 6 +- src/math.hh | 7 +- src/netvar.cc | 87 +++++++++ src/netvar.hh | 58 ++++++ src/platform.hh | 4 +- src/player.cc | 259 +++++++++++++++++++++++++++ src/player.hh | 88 ++++++++++ src/sdk.hh | 445 +++++++++++++++++++++++++++++++++++++++++++++++ src/signature.cc | 14 +- src/signature.hh | 4 +- src/trace.hh | 158 +++++++++++++++++ src/vfunc.hh | 14 +- src/weapon.cc | 27 +++ src/weapon.hh | 15 ++ 19 files changed, 1359 insertions(+), 31 deletions(-) create mode 100644 src/datatable.hh create mode 100644 src/entity.cc create mode 100644 src/entity.hh create mode 100644 src/netvar.cc create mode 100644 src/netvar.hh create mode 100644 src/player.cc create mode 100644 src/player.hh create mode 100644 src/sdk.hh create mode 100644 src/trace.hh create mode 100644 src/weapon.cc create mode 100644 src/weapon.hh diff --git a/src/datatable.hh b/src/datatable.hh new file mode 100644 index 0000000..4080bf7 --- /dev/null +++ b/src/datatable.hh @@ -0,0 +1,103 @@ +#pragma once + +#include + +#include "types.hh" + +namespace sdk { +class RecvTable; +class RecvProp; +class RecvDecoder; + +struct RecvProxyData { +public: + const RecvProp *prop; // The property it's receiving. + u8 value[16]; // The value given to you to store. + int element; // Which array element you're getting. + int id; // The object being referred to. +}; + +class RecvProp { +public: + using RecvVarProxyFn = void(const RecvProxyData *data, void *this_ptr, void *out); + using ArrayLengthRecvProxyFn = void(void *this_ptr, int id, int cur_length); + using DataTableRecvVarProxyFn = void(const RecvProp *prop, void **out, void *data, int id); + + RecvProp() = delete; + + auto as_datatable() const { + return data_table; + } + + auto as_array() const { + return array_prop; + } + + auto is_inside_array() const { + return inside_array; + } + + // If it's one of the numbered "000", "001", etc properties in an array, then + // these can be used to get its array property name for debugging. + auto parent_name() { + return parent_array_name; + } + + // Member ordering must remain the same! + +public: + const char *network_name; + int recv_type; + int flags; + int string_buffer_size; + + bool inside_array; // Set to true by the engine if this property sits inside an array. + + // Extra data that certain special property types bind to the property here. + const void *extra_data; + + // If this is an array (DPT_Array). + RecvProp *array_prop; + + ArrayLengthRecvProxyFn *array_length_proxy; + + RecvVarProxyFn *proxy_fn; + + DataTableRecvVarProxyFn *datatable_proxy_fn; // For RDT_DataTable. + RecvTable * data_table; // For RDT_DataTable. + int offset; + + int element_stride; + int element_count; + +private: + // If it's one of the numbered "000", "001", etc properties in an array, then + // these can be used to get its array property name for debugging. + const char *parent_array_name; +}; + +class RecvTable { +public: + RecvTable() = delete; + + auto prop(int i) { + assert(i < prop_count); + return &props[i]; + } + + // C++11 iterator functions + auto begin() { return prop(0); } + auto end() { return props + prop_count; } + +public: + // Properties described in a table. + RecvProp *props; + int prop_count; + + // The decoder. NOTE: this covers each RecvTable AND all its children (ie: its children + // will have their own decoders that include props for all their children). + RecvDecoder *decoder; + + const char *name; // The name matched between client and server. +}; +} // namespace sdk diff --git a/src/entity.cc b/src/entity.cc new file mode 100644 index 0000000..9502e11 --- /dev/null +++ b/src/entity.cc @@ -0,0 +1,49 @@ +#include "stdafx.hh" + +#include "entity.hh" +#include "player.hh" +#include "weapon.hh" + +#include "netvar.hh" + +#include "sdk.hh" + +using namespace sdk; + +auto Entity::to_handle() -> EntityHandle & { + return_virtual_func(to_handle, 2, 0, 0, 0); +} + +auto Entity::is_valid() -> bool { + // get around clangs "correctly formed code never has a null thisptr" + auto thisptr = reinterpret_cast(this); + if (thisptr == 0) return false; + + return true; +} + +auto Entity::to_player() -> class Player * { + auto clientclass = client_class(); + + // TODO: do not hardcode this value + if (clientclass->class_id == 246) return static_cast(this); + + return nullptr; +} + +auto Entity::to_weapon() -> class Weapon * { + // TODO: checks... + return static_cast(this); +} + +auto Entity::client_class() -> struct ClientClass * { + return_virtual_func(client_class, 2, 0, 0, 8); +} + +auto Entity::dormant() -> bool { + return_virtual_func(dormant, 8, 0, 0, 8); +} + +auto Entity::index() -> u32 { + return_virtual_func(index, 9, 0, 0, 8); +} diff --git a/src/entity.hh b/src/entity.hh new file mode 100644 index 0000000..d1d30c7 --- /dev/null +++ b/src/entity.hh @@ -0,0 +1,36 @@ +#pragma once + +#include "types.hh" +#include "vfunc.hh" + +namespace sdk { +struct EntityHandle { + u32 serial_index; +}; + +class Entity { +public: + Entity() = delete; + + // helper functions + bool is_valid(); + + EntityHandle &to_handle(); + + template + auto set(T data) { *reinterpret_cast(reinterpret_cast(this) + offset) = data; } + + template + auto get() { return *reinterpret_cast(reinterpret_cast(this) + offset); } + + // upcasts + class Player *to_player(); + class Weapon *to_weapon(); + + // virtual functions + struct ClientClass *client_class(); + + bool dormant(); + u32 index(); +}; +} // namespace sdk diff --git a/src/hooks.hh b/src/hooks.hh index df9e61d..14afc78 100644 --- a/src/hooks.hh +++ b/src/hooks.hh @@ -7,7 +7,7 @@ #include #include -namespace Hooks { +namespace hooks { template class HookInstance { @@ -161,4 +161,4 @@ public: template std::vector *> HookFunction::hooks; -} // namespace Hooks +} // namespace hooks diff --git a/src/interface.cc b/src/interface.cc index 3c96382..5b14625 100644 --- a/src/interface.cc +++ b/src/interface.cc @@ -3,6 +3,8 @@ #include "interface.hh" #include "signature.hh" +#include "platform.hh" + using InstantiateInterfaceFn = void *(*)(); class InterfaceReg { @@ -16,14 +18,14 @@ public: InterfaceReg *next; }; -void *InterfaceHelpers::find_interface(const char *module_name, const char *interface_name) { +void *interface_helpers::find_interface(const char *module_name, const char *interface_name) { InterfaceReg *interface_reg_head; - if constexpr (DoghookPlatform::windows()) { - interface_reg_head = **Signature::find_pattern(module_name, "8B 35 ? ? ? ? 57 85 F6 74 38", 2); - } else if constexpr (DoghookPlatform::linux()) { - } else if constexpr (DoghookPlatform::osx()) { + if constexpr (doghook_platform::windows()) { + interface_reg_head = **signature::find_pattern(module_name, "8B 35 ? ? ? ? 57 85 F6 74 38", 2); + } else if constexpr (doghook_platform::linux()) { + } else if constexpr (doghook_platform::osx()) { } assert(interface_reg_head); diff --git a/src/interface.hh b/src/interface.hh index d35105d..a112524 100644 --- a/src/interface.hh +++ b/src/interface.hh @@ -1,10 +1,10 @@ #pragma once -namespace InterfaceHelpers { +namespace interface_helpers { // this auto-resolves the library using Signature:: void *find_interface(const char *module_name, const char *interface_name); -} // namespace InterfaceHelpers +} // namespace interface_helpers template class Interface { @@ -16,7 +16,7 @@ public: // e.g. "VClient017" -> "VClient" static auto set_from_interface(const char *module_name, const char *interface_name) { value = static_cast( - InterfaceHelpers::find_interface(module_name, interface_name)); + interface_helpers::find_interface(module_name, interface_name)); } // set from a pointer (you should only do this for non-exported diff --git a/src/math.hh b/src/math.hh index 0ef4e30..4e6e91c 100644 --- a/src/math.hh +++ b/src/math.hh @@ -9,7 +9,7 @@ #undef max #undef min -namespace Math { +namespace math { constexpr auto PI = 3.14159265358979323846f; inline auto to_radians(float x) { @@ -252,8 +252,7 @@ public: out.z = data[row][2]; } - // - auto rotate_vector(const Math::Vector &in) const { + auto rotate_vector(const Vector &in) const { Vector out; Vector row; @@ -350,4 +349,4 @@ inline void matrix_angles(const Matrix3x4 &matrix, Vector &angles, Vector &posit matrix_angles(matrix, &angles.x); } -} // namespace Math +} // namespace math diff --git a/src/netvar.cc b/src/netvar.cc new file mode 100644 index 0000000..f2fc83f --- /dev/null +++ b/src/netvar.cc @@ -0,0 +1,87 @@ +#include "stdafx.hh" + +#include "interface.hh" +#include "netvar.hh" +#include "sdk.hh" + +using namespace sdk; + +static Netvar *head; + +void Netvar::Tree::populate_recursive(RecvTable *t, netvar_tree *nodes) { + for (auto i = 0; i < t->prop_count; i++) { + auto * prop = t->prop(i); + const auto new_node = new node(); + new_node->p = prop; + + if (prop->recv_type == 6) populate_recursive(prop->as_datatable(), &new_node->children); + + nodes->emplace_back(std::make_pair(prop->network_name, new_node)); + } +} + +Netvar::Tree::Tree() { +} + +void Netvar::Tree::init() { + if (prop_tree.size() > 0) return; + + auto cc = IFace()->get_all_classes(); + while (cc != nullptr) { + const auto new_node = new node(); + new_node->p = nullptr; + + populate_recursive(cc->recv_table, &new_node->children); + prop_tree.emplace_back(cc->recv_table->name, new_node); + + cc = cc->next; + } +} + +uptr Netvar::Tree::find_offset(std::vector t) { + uptr total = 0; + auto nodes = &prop_tree; + + for (auto &name : t) { + + auto old_nodes = nodes; + + auto end = nodes->end(); + for (auto it = nodes->begin(); it != end; ++it) { + auto p = *it; + + if (strcmp(name, p.first) == 0) { + nodes = &p.second->children; + if (p.second->p != nullptr) + total += p.second->p->offset; + break; + } + } + + if (nodes == old_nodes) { + // TODO: + //Log::msg("[Netvar] Unable to find '%s'", name); + } + } + + return total; +} + +Netvar::Tree Netvar::netvar_tree; + +void Netvar::add_to_init_list() { + next = head; + head = this; +} + +void Netvar::init() { + offset = netvar_tree.find_offset(tables); +} + +void Netvar::init_all() { + netvar_tree.init(); + + for (auto &n = head; n != nullptr; n = n->next) { + n->init(); + } +} diff --git a/src/netvar.hh b/src/netvar.hh new file mode 100644 index 0000000..b9f10c1 --- /dev/null +++ b/src/netvar.hh @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include "types.hh" + +namespace sdk { +class Netvar { + + class Tree { + + struct node; + using netvar_tree = std::vector>; + + struct node { + netvar_tree children; + class RecvProp *p; + }; + + netvar_tree prop_tree; + + void populate_recursive(class RecvTable *t, netvar_tree *nodes); + + public: + Tree(); + + void init(); + + uptr find_offset(std::vector t); + }; + static Tree netvar_tree; + + // the offset of this netvar from its parent instance + uptr offset; + + // used internally for registering + Netvar *next; + + std::vector tables; + + void add_to_init_list(); + void init(); + +public: + template + Netvar(A... args) : tables({args...}) { + add_to_init_list(); + } + + template + auto &get(void *instance) { + return *reinterpret_cast(static_cast(instance) + offset); + } + + static void init_all(); +}; +} // namespace sdk diff --git a/src/platform.hh b/src/platform.hh index 28a3b1b..3e1d0e3 100644 --- a/src/platform.hh +++ b/src/platform.hh @@ -40,7 +40,7 @@ #endif // clang-format on -namespace DoghookPlatform { +namespace doghook_platform { inline constexpr bool windows() { return doghook_platform_windows(); } inline constexpr bool linux() { return doghook_platform_linux(); } inline constexpr bool osx() { return doghook_platform_osx(); } @@ -48,7 +48,7 @@ inline constexpr bool osx() { return doghook_platform_osx(); } inline constexpr bool msvc() { return doghook_platform_msvc(); } inline constexpr bool clang() { return doghook_platform_clang(); } inline constexpr bool gcc() { return doghook_platform_gcc(); } -} // namespace DoghookPlatform +} // namespace doghook_platform #include "types.hh" diff --git a/src/player.cc b/src/player.cc new file mode 100644 index 0000000..db4d1f1 --- /dev/null +++ b/src/player.cc @@ -0,0 +1,259 @@ +#include "stdafx.hh" + +#include "player.hh" +#include "weapon.hh" + +#include "netvar.hh" +#include "vfunc.hh" + +#include "sdk.hh" + +using namespace sdk; + +auto Player::local() -> Player * { + return static_cast(IFace()->entity(IFace()->local_player_index())); +} + +static auto health = Netvar("DT_BasePlayer", "m_iHealth"); +auto Player::health() -> int & { + return ::health.get(this); +} + +static auto lifestate = Netvar("DT_BasePlayer", "m_lifeState"); +auto Player::alive() -> bool { + return ::lifestate.get(this) == 0; +} + +auto Player::origin() -> math::Vector & { + return_virtual_func(origin, 9, 0, 0, 0); +} + +auto Player::set_origin(const math::Vector &v) -> void { + if constexpr (doghook_platform::windows()) { + static auto original = signature::find_pattern("client", "55 8B EC 56 57 8B F1 E8 ? ? ? ? 8B 7D 08 F3 0F 10 07", 0); + assert(original); + return original(this, v); + } else if constexpr (doghook_platform::linux()) { + static_assert(doghook_platform::linux() == false); + } else if constexpr (doghook_platform::osx()) { + static_assert(doghook_platform::osx() == false); + } +} + +auto Player::angles() -> math::Vector & { + return_virtual_func(angles, 10, 0, 0, 0); +} + +auto Player::set_angles(const math::Vector &v) -> void { + if constexpr (doghook_platform::windows()) { + static auto original = signature::find_pattern("client", "55 8B EC 83 EC 60 56 57 8B F1", 0); + assert(original); + return original(this, v); + } else if constexpr (doghook_platform::linux()) { + static_assert(doghook_platform::linux() == false); + } else if constexpr (doghook_platform::osx()) { + static_assert(doghook_platform::osx() == false); + } +} + +static auto team = Netvar("DT_BaseEntity", "m_iTeamNum"); +auto Player::team() -> int { + return ::team.get(this); +} + +static auto cond = Netvar("DT_TFPlayer", "m_Shared", "m_nPlayerCond"); +auto Player::cond() -> u32 & { + return ::cond.get(this); +} + +auto Player::render_bounds() -> std::pair { + auto func = vfunc::Func(this, 20, 0, 0, 4); + + std::pair ret; + + func.invoke(ret.first, ret.second); + + auto origin = this->origin(); + + ret.first += origin; + ret.second += origin; + + return ret; +} + +static auto collideable_min = Netvar("DT_BaseEntity", "m_Collision", "m_vecMinsPreScaled"); +static auto collideable_max = Netvar("DT_BaseEntity", "m_Collision", "m_vecMaxsPreScaled"); +auto Player::collision_bounds() -> std::pair { + + auto &min = ::collideable_min.get(this); + auto &max = ::collideable_max.get(this); + + return std::make_pair(std::ref(min), std::ref(max)); +} + +static auto view_offset = Netvar("DT_BasePlayer", "localdata", "m_vecViewOffset[0]"); +auto Player::view_offset() -> math::Vector & { + return ::view_offset.get(this); +} + +static auto tf_class = Netvar("DT_TFPlayer", "m_PlayerClass", "m_iClass"); +auto Player::tf_class() -> int { + return ::tf_class.get(this); +} + +static auto tick_base = Netvar("DT_BasePlayer", "localdata", "m_nTickBase"); +auto Player::tick_base() -> int { + return ::tick_base.get(this); +} + +static auto active_weapon_handle = Netvar("DT_BaseCombatCharacter", "m_hActiveWeapon"); +auto Player::active_weapon() -> Weapon * { + return static_cast(IFace()->from_handle(::active_weapon_handle.get(this))); +} + +static auto sim_time = Netvar("DT_BaseEntity", "m_flSimulationTime"); +auto Player::sim_time() -> float & { + return ::sim_time.get(this); +} + +static auto anim_time = Netvar("DT_BaseEntity", "AnimTimeMustBeFirst", "m_flAnimTime"); +auto Player::anim_time() -> float & { + return ::anim_time.get(this); +} + +static auto cycle = Netvar("DT_BaseAnimating", "serveranimdata", "m_flCycle"); +auto Player::cycle() -> float & { + return ::cycle.get(this); +} + +static auto fov_time = Netvar("DT_BasePlayer", "m_flFOVTime"); +auto Player::fov_time() -> float { + return ::fov_time.get(this); +} + +static auto render_origin = Netvar("DT_BaseEntity", "m_vecOrigin"); +auto Player::render_origin() -> math::Vector & { + return ::render_origin.get(this); +} + +template +struct UtlVector { + T * mem; + int alloc_count; + int grow_size; + int size; + T * dbg_elements; +}; + +static auto player_anim_layer_vector(Player *p) -> UtlVector & { + // TODO: we need a better, cross platform method for doing this. + // check for "%8.4f : %30s : %5.3f : %4.2f : %1d\n" + + if constexpr (doghook_platform::windows()) { + UtlVector *anim_overlays = reinterpret_cast *>(p + 2216); + return *anim_overlays; + } else if constexpr (doghook_platform::linux()) { + } else if constexpr (doghook_platform::osx()) { + } +} + +auto Player::anim_layer(u32 index) -> AnimationLayer & { + return player_anim_layer_vector(this).mem[index]; +} + +auto Player::anim_layer_count() -> u32 { + return player_anim_layer_vector(this).size; +} + +auto Player::update_client_side_animation() -> void { + // Look for the string "UpdateClientSideAnimations + return_virtual_func(update_client_side_animation, 191, 0, 0, 0); +} + +auto Player::invalidate_physics_recursive(u32 flags) -> void { + typedef void(__thiscall * InvalidatePhysicsRecursiveFn)(Player *, u32 flags); + + if constexpr (doghook_platform::windows()) { + static auto fn = signature::find_pattern("client", "55 8B EC 51 53 8B 5D 08 56 8B F3 83 E6 04", 0); + fn(this, flags); + } else if constexpr (doghook_platform::linux()) { + } else if constexpr (doghook_platform::osx()) { + } +} + +static auto sequence = Netvar("DT_BaseAnimating", "m_nSequence"); +auto Player::sequence() -> int & { + return ::sequence.get(this); +} + +auto Player::view_position() -> math::Vector { + return origin() + view_offset(); +} + +auto Player::bone_transforms(math::Matrix3x4 *hitboxes_out, u32 max_bones, u32 bone_mask, float current_time) -> bool { + return_virtual_func(bone_transforms, 16, 0, 0, 4, hitboxes_out, max_bones, bone_mask, current_time); +} + +auto Player::model_handle() -> const ModelHandle * { + return_virtual_func(model_handle, 9, 0, 0, 4); +} + +auto Player::studio_model() -> const StudioModel * { + return IFace()->studio_model(this->model_handle()); +} + +static auto get_hitboxes_internal(Player *player, const StudioModel *model, PlayerHitboxes *hitboxes, bool create_pose) { + math::Matrix3x4 bone_to_world[128]; + + // #define BONE_USED_BY_ANYTHING 0x0007FF00 + bool success = player->bone_transforms(bone_to_world, 128, 0x0007FF00, IFace()->last_timestamp()); + assert(success); + + auto hitbox_set_ptr = model->hitbox_set(0); + assert(hitbox_set_ptr); + + auto &hitbox_set = *hitbox_set_ptr; + + auto hitboxes_count = std::min(128u, hitbox_set.hitboxes_count); + + math::Vector origin; + math::Vector centre; + + for (u32 i = 0; i < hitboxes_count; ++i) { + auto box = hitbox_set[i]; + assert(box); + + math::Vector rotation; + math::matrix_angles(bone_to_world[box->bone], rotation, origin); + + math::Matrix3x4 rotate_matrix; + rotate_matrix.from_angle(rotation); + + math::Vector rotated_min, rotated_max; + + rotated_min = rotate_matrix.rotate_vector(box->min); + rotated_max = rotate_matrix.rotate_vector(box->max); + + centre = rotated_min.lerp(rotated_max, 0.5); + + hitboxes->centre[i] = origin + centre; + hitboxes->min[i] = origin + rotated_min; + hitboxes->max[i] = origin + rotated_max; + +#ifdef _DEBUG + hitboxes->rotation[i] = rotation; + hitboxes->origin[i] = origin; + hitboxes->raw_min[i] = box->min; + hitboxes->raw_max[i] = box->max; +#endif + } + + return hitboxes_count; +} + +auto Player::hitboxes(PlayerHitboxes *hitboxes_out, bool create_pose) -> u32 { + auto model = studio_model(); + assert(model); + + return get_hitboxes_internal(this, model, hitboxes_out, create_pose); +} diff --git a/src/player.hh b/src/player.hh new file mode 100644 index 0000000..6c40612 --- /dev/null +++ b/src/player.hh @@ -0,0 +1,88 @@ +#pragma once + +#include "types.hh" + +#include "entity.hh" + +#undef min +#undef max + +namespace sdk { +struct PlayerHitboxes { + math::Vector centre[128]; + + math::Vector min[128]; + math::Vector max[128]; + +#ifdef _DEBUG + // These are used for debugging + math::Vector origin[128]; + math::Vector rotation[128]; + + math::Vector raw_min[128]; + math::Vector raw_max[128]; +#endif +}; + +class Player : public Entity { +public: + Player() = delete; + + // helper functions + static auto local() -> Player *; + + auto world_space_centre() -> math::Vector &; + + auto model_handle() -> const class ModelHandle *; + auto studio_model() -> const class StudioModel *; + + auto view_position() -> math::Vector; + + auto hitboxes(PlayerHitboxes *hitboxes_out, bool create_pose) -> u32; + + // netvars + auto health() -> int &; + + auto alive() -> bool; + auto team() -> int; + + auto cond() -> u32 &; + + auto view_offset() -> math::Vector &; + + auto tf_class() -> int; + + auto tick_base() -> int; + auto sim_time() -> float &; + auto anim_time() -> float &; + auto cycle() -> float &; + auto sequence() -> int &; + + auto fov_time() -> float; + + auto render_origin() -> math::Vector &; + + auto active_weapon() -> Weapon *; + + // virtual functions + auto origin() -> math::Vector &; + auto set_origin(const math::Vector &v) -> void; + + auto render_bounds() -> std::pair; + + auto angles() -> math::Vector &; + auto set_angles(const math::Vector &v) -> void; + + auto anim_layer(u32 index) -> class AnimationLayer &; + auto anim_layer_count() -> u32; + + auto update_client_side_animation() -> void; + + auto invalidate_physics_recursive(u32 flags) -> void; + + // These are relative to the origin + auto collision_bounds() -> std::pair; + + auto bone_transforms(math::Matrix3x4 *hitboxes_out, u32 max_bones, u32 bone_mask, float current_time) -> bool; +}; +} // namespace sdk diff --git a/src/sdk.hh b/src/sdk.hh new file mode 100644 index 0000000..8ef7841 --- /dev/null +++ b/src/sdk.hh @@ -0,0 +1,445 @@ +#pragma once + +#include "types.hh" +#include "vfunc.hh" + +#include "datatable.hh" +#include "entity.hh" +#include "interface.hh" +#include "signature.hh" + +#include "trace.hh" + +namespace sdk { +class UserCmd { +public: + virtual ~UserCmd(){}; + + int command_number; + int tick_count; + math::Vector viewangles; + float forwardmove; + float sidemove; + float upmove; + int buttons; + u8 impulse; + int weapon_select; + int weapon_subtype; + int random_seed; + short mouse_dx; + short mouse_dy; + bool has_been_predicted; +}; + +struct ClientClass { + void * create_fn; + void * create_event_fn; // Only called for event objects. + const char * network_name; + RecvTable * recv_table; + ClientClass *next; + int class_id; // Managed by the engine. +}; + +class Client { +public: + Client() = delete; + + auto get_all_classes() -> ClientClass * { + return_virtual_func(get_all_classes, 8, 8, 8, 0); + } +}; + +class ClientMode { +public: + ClientMode() = delete; +}; + +class NetChannel { +public: + enum class Flow { + outgoing, + incoming + }; + + NetChannel() = delete; + + auto get_sequence_number(sdk::NetChannel::Flow f) -> i32 { + return_virtual_func(get_sequence_number, 16, 16, 16, 0, f); + } + + auto queued_packets() -> i32 & { + static auto queued_packets_offset = []() { + if constexpr (doghook_platform::windows()) { + return *signature::find_pattern("engine", "83 BE ? ? ? ? ? 0F 9F C0 84 C0", 2); + } else if constexpr (doghook_platform::linux()) { + } else if constexpr (doghook_platform::osx()) { + } + }(); + + assert(queued_packets_offset); + + auto &queued_packets = *reinterpret_cast(reinterpret_cast(this) + queued_packets_offset); + + return queued_packets; + } +}; + +class Globals { +public: + float realtime; + int framecount; + + float absolute_frametime; + float curtime; + + float frametime; + + int max_clients; + + int tickcount; + float interval_per_tick; + + float interpolation_amount; + int sim_ticks_this_frame; + + int network_protocol; + + class CSaveRestoreData *save_data; + + bool is_client; +}; + +class Engine { +public: + Engine() = delete; + + auto last_timestamp() -> float { + return_virtual_func(last_timestamp, 15, 15, 15, 0); + } + + auto time() -> float { + return_virtual_func(time, 14, 14, 14, 0); + } + + auto in_game() -> bool { + return_virtual_func(in_game, 26, 26, 26, 0); + } + + auto local_player_index() -> u32 { + return_virtual_func(local_player_index, 12, 12, 12, 0); + } + + auto net_channel_info() -> NetChannel * { + return_virtual_func(net_channel_info, 72, 72, 72, 0); + } + + auto is_box_visible(const math::Vector &min, const math::Vector &max) -> bool { + return_virtual_func(is_box_visible, 31, 31, 31, 0, min, max); + } + + auto max_clients() -> u32 { + return_virtual_func(max_clients, 21, 21, 21, 0); + } + + auto set_view_angles(const math::Vector &v) -> void { + return_virtual_func(set_view_angles, 20, 20, 20, 0, v); + } +}; + +class EntList { +public: + EntList() = delete; + + auto entity(u32 index) -> Entity * { + return_virtual_func(entity, 3, 0, 0, 0, index); + } + + auto from_handle(EntityHandle h) -> Entity * { + return_virtual_func(from_handle, 4, 4, 4, 0, h); + } + + auto max_entity_index() -> u32 { + return_virtual_func(max_entity_index, 6, 0, 0, 0); + } + + class EntityRange { + EntList *parent; + + u32 max_entity; + + public: + class Iterator { + u32 index; + EntList *parent; + + public: + // TODO: should we use 1 here or should we use 0 and force people + // that loop to deal with that themselves... + Iterator(EntList *parent) : index(1), parent(parent) {} + explicit Iterator(u32 index, EntList *parent) + : index(index), parent(parent) {} + + auto &operator++() { + ++index; + return *this; + } + + auto operator*() { + return parent->entity(index); + } + + auto operator==(const Iterator &b) { + return index == b.index; + } + + auto operator!=(const Iterator &b) { + return !(*this == b); + } + }; + + EntityRange(EntList *parent) : parent(parent), max_entity(parent->max_entity_index()) {} + + explicit EntityRange(EntList *parent, u32 max_entity) : parent(parent), max_entity(max_entity) {} + + auto begin() { return Iterator(parent); } + + auto end() { return Iterator(max_entity, parent); } + }; + + auto get_range() { return EntityRange(this); } + auto get_range(u32 max_entity) { return EntityRange(this, max_entity + 1); } + +}; // namespace sdk + +class Input { +public: + Input() = delete; + + auto get_user_cmd(u32 sequence_number) -> UserCmd * { + static auto array_offset = []() { + if constexpr (doghook_platform::windows()) { + return *signature::find_pattern("client", "8B 87 ? ? ? ? 8B CA", 2); + } else if constexpr (doghook_platform::linux()) { + } else if constexpr (doghook_platform::osx()) { + } + }(); + // this should not be 0 + assert(array_offset != 0); + + auto cmd_array = *reinterpret_cast(reinterpret_cast(this) + array_offset); + + return &cmd_array[sequence_number % 90]; + } + + auto get_verified_user_cmd(u32 sequence_number) -> class VerifiedCmd * { + // 03 B7 ? ? ? ? 8D 04 88 + } +}; + +// defined in convar.cc +class ConCommandBase; + +class Cvar { +public: + Cvar() = delete; + + auto allocate_dll_identifier() -> u32 { + return_virtual_func(allocate_dll_identifier, 5, 5, 5, 0); + } + + auto register_command(ConCommandBase *command) -> void { + return_virtual_func(register_command, 6, 6, 6, 0, command); + } + + auto unregister_command(ConCommandBase *command) -> void { + return_virtual_func(unregister_command, 7, 7, 7, 0, command); + } +}; + +class Trace { +public: + Trace() = delete; + + auto trace_ray(const trace::Ray &ray, u32 mask, trace::Filter *filter, trace::TraceResult *results) -> void { + return_virtual_func(trace_ray, 4, 4, 4, 0, ray, mask, filter, results); + } +}; + +// These are defined to give us distinct types for these +// very different objects, to help us differentiate between them + +class ModelHandle; // model_t + +// mstudiobbox +class StudioHitbox { +public: + StudioHitbox() = delete; + + int bone; + int group; + + math::Vector min; + math::Vector max; + + int name_index; + +private: + int unused[8]; +}; + +// mstudiobboxset +class StudioHitboxSet { +public: + StudioHitboxSet() = delete; + + int name_index; + auto name() const -> const char * { + assert(0); + return ""; + } + + u32 hitboxes_count; + u32 hitbox_index; + + const auto operator[](u32 index) const { + assert(index < hitboxes_count); + + return reinterpret_cast( + reinterpret_cast(this) + hitbox_index) + + index; + } +}; + +// studiohdr_t +class StudioModel { +public: + StudioModel() = delete; + + int id; + int version; + int checksum; + char name[64]; + int length; + + math::Vector eye_position; // ideal eye position + + math::Vector illumination_position; // illumination center + + math::Vector hull_min; // ideal movement hull size + math::Vector hull_max; + + math::Vector view_bbmin; // clipping bounding box + math::Vector view_bbmax; + + int flags; + + int bones_count; + int bone_index; + + int bone_controllers_count; + int bone_controller_index; + + u32 hitbox_sets_count; + u32 hitbox_set_index; + + const auto hitbox_set(u32 index) const { + assert(index < hitbox_sets_count); + + return reinterpret_cast( + reinterpret_cast(this) + hitbox_set_index) + + index; + } + + //... TODO: +}; + +class ModelInfo { +public: + ModelInfo() = delete; + + auto model_name(const ModelHandle *m) -> const char * { + return_virtual_func(model_name, 3, 0, 0, 0, m); + } + + auto studio_model(const ModelHandle *m) -> const StudioModel * { + return_virtual_func(studio_model, 28, 0, 0, 0, m); + } +}; + +// This should only be used for debugging. +// For real output use the overlay +class DebugOverlay { +public: + using OverlayText_t = void; + + virtual void add_entity_text_overlay(int ent_index, int line_offset, float duration, int r, int g, int b, int a, const char *format, ...) = 0; + virtual void add_box_overlay(const math::Vector &origin, const math::Vector &mins, const math::Vector &max, const math ::Vector &orientation, int r, int g, int b, int a, float duration) = 0; + virtual void add_triangle_overlay(const math::Vector &p1, const math::Vector &p2, const math::Vector &p3, int r, int g, int b, int a, bool no_depth_test, float duration) = 0; + virtual void add_line_overlay(const math::Vector &origin, const math::Vector &dest, int r, int g, int b, bool no_depth_test, float duration) = 0; + virtual void add_text_overlay(const math::Vector &origin, float duration, const char *format, ...) = 0; + virtual void add_text_overlay(const math::Vector &origin, int line_offset, float duration, const char *format, ...) = 0; + virtual void add_screen_text_overlay(float x_pos, float y_pos, float duration, int r, int g, int b, int a, const char *text) = 0; + virtual void add_swept_box_overlay(const math::Vector &start, const math::Vector &end, const math::Vector &mins, const math::Vector &max, const math::Vector &angles, int r, int g, int b, int a, float duration) = 0; + virtual void add_grid_overlay(const math::Vector &origin) = 0; + virtual int screen_position(const math::Vector &point, math::Vector &screen) = 0; + virtual int screen_position(float x_pos, float y_pos, math::Vector &screen) = 0; + + virtual OverlayText_t *get_first(void) = 0; + virtual OverlayText_t *get_next(OverlayText_t *current) = 0; + virtual void clear_dead_overlays() = 0; + virtual void clear_all_overlays() = 0; + + virtual void add_text_overlay_rgb(const math::Vector &origin, int line_offset, float duration, float r, float g, float b, float alpha, const char *format, ...) = 0; + virtual void add_text_overlay_rgb(const math::Vector &origin, int line_offset, float duration, int r, int g, int b, int a, const char *format, ...) = 0; + + virtual void add_line_overlay_alpha(const math::Vector &origin, const math::Vector &dest, int r, int g, int b, int a, bool noDepthTest, float duration) = 0; + //virtual void add_box_overlay2(const math::Vector &origin, const math::Vector &mins, const math::Vector &max, const math::Vector &orientation, const Color &faceColor, const Color &edgeColor, float duration) = 0; +}; + +// This is from server.dll +class PlayerInfoManager { +public: + auto globals() -> Globals * { + return_virtual_func(globals, 1, 1, 1, 0); + } +}; + +class MoveHelper; + +class Prediction { +public: + auto setup_move(Player *player, UserCmd *ucmd, MoveHelper *helper, void *move) -> void { + return_virtual_func(setup_move, 18, 18, 18, 0, player, ucmd, helper, move); + } + void finish_move(Player *player, UserCmd *ucmd, void *move) { + return_virtual_func(finish_move, 19, 19, 19, 0, player, ucmd, move); + } +}; + +class GameMovement { +public: + void process_movement(Player *player, void *move) { + return_virtual_func(process_movement, 1, 1, 1, 0, player, move); + } +}; + +class AnimationLayer { +public: + int sequence; + float prev_cycle; + float weight; + int order; + + float playback_rate; + float cycle; + + float layer_anim_time; + float layer_fade_time; + + float blend_in; + float blend_out; + + bool client_blend; +}; + +} // namespace sdk diff --git a/src/signature.cc b/src/signature.cc index 00e7d62..02f1487 100644 --- a/src/signature.cc +++ b/src/signature.cc @@ -11,6 +11,8 @@ #include #endif +using namespace signature; + static constexpr auto in_range(char x, char a, char b) { return (x >= a && x <= b); } @@ -53,7 +55,7 @@ static u8 *find_pattern_internal(uptr start, uptr end, const char *pattern) { } static std::pair find_module_code_section(const char *module_name) { - auto module = Signature::resolve_library(module_name); + auto module = resolve_library(module_name); #if doghook_platform_windows() auto module_addr = reinterpret_cast(module); @@ -113,7 +115,7 @@ static std::pair find_module_code_section(const char *module_name) { assert(0); } -auto Signature::resolve_library(const char *name) -> void * { +auto signature::resolve_library(const char *name) -> void * { #if doghook_platform_windows() // TODO: actually check directories for this dll instead of // letting the loader do the work @@ -162,7 +164,7 @@ auto Signature::resolve_library(const char *name) -> void * { return nullptr; } -void *Signature::resolve_import(void *handle, const char *name) { +void *signature::resolve_import(void *handle, const char *name) { #if doghook_platform_windows() return reinterpret_cast(GetProcAddress(static_cast(handle), name)); #elif doghook_platform_linux() @@ -172,17 +174,17 @@ void *Signature::resolve_import(void *handle, const char *name) { #endif } -u8 *Signature::find_pattern(const char *module_name, const char *pattern) { +u8 *signature::find_pattern(const char *module_name, const char *pattern) { auto code_section = find_module_code_section(module_name); return find_pattern_internal(code_section.first, code_section.second, pattern); } -u8 *Signature::find_pattern(const char *pattern, uptr start, uptr length) { +u8 *signature::find_pattern(const char *pattern, uptr start, uptr length) { return find_pattern_internal(start, start + length, pattern); } -void *Signature::resolve_callgate(void *address) { +void *signature::resolve_callgate(void *address) { // TODO: are these the only instructions here? assert(reinterpret_cast(address)[0] == '\xE8' || reinterpret_cast(address)[0] == '\xE9'); diff --git a/src/signature.hh b/src/signature.hh index fde846d..685bba4 100644 --- a/src/signature.hh +++ b/src/signature.hh @@ -4,7 +4,7 @@ #include "platform.hh" -namespace Signature { +namespace signature { void *resolve_library(const char *name); void *resolve_import(void *handle, const char *name); @@ -16,4 +16,4 @@ auto find_pattern(const char *module, const char *pattern, u32 offset) { return reinterpret_cast(find_pattern(module, pattern) + offset); } void *resolve_callgate(void *address); -} // namespace Signature +} // namespace signature diff --git a/src/trace.hh b/src/trace.hh new file mode 100644 index 0000000..c9113bc --- /dev/null +++ b/src/trace.hh @@ -0,0 +1,158 @@ +#pragma once + +#include "platform.hh" + +namespace trace { +class VectorAligned : public math::Vector { + float pad; + +public: + VectorAligned() {} + + explicit VectorAligned(const math::Vector &other) { + x = other.x; + y = other.y; + z = other.z; + } + + explicit VectorAligned(const math::Vector &&other) { + x = other.x; + y = other.y; + z = other.z; + } + + auto operator=(const Vector &other) { + x = other.x; + y = other.y; + z = other.z; + + return *this; + } + + auto operator=(const Vector &&other) { + x = other.x; + y = other.y; + z = other.z; + + return *this; + } + + operator Vector() { + return *this; + } +}; + +struct Plane { + math::Vector normal; + float dist; + u8 type; // for fast side tests + u8 signbits; // signx + (signy<<1) + (signz<<1) + u8 pad[2]; +}; + +struct Model { + math::Vector min, max; + math::Vector origin; // for sounds or lights + int head_node; + + struct VCollide { + unsigned short solidCount : 15; + unsigned short isPacked : 1; + unsigned short descSize; + // VPhysicsSolids + void **solids; + char * pKeyValues; + }; + + VCollide vcollision_data; +}; + +struct Surface { + const char * name; + short surface_props; + unsigned short flags; +}; + +struct TraceResult { + math::Vector start_pos; // start position + math::Vector end_pos; // final position + Plane plane; // surface normal at impact + + float fraction; // time completed, 1.0 = didn't hit anything + + int contents; // contents on other side of surface hit + unsigned short disp_flags; // displacement flags for marking surfaces with data + + bool all_solid; // if true, plane is not valid + bool start_solid; // if true, the initial point was in a solid area + + float fraction_left_solid; // time we left a solid, only valid if we started in solid + Surface surface; // surface hit (impact surface) + + int hitgroup; // 0 == generic, non-zero is specific body part + short physicsbone; // physics bone hit by trace in studio + + sdk::Entity *entity; + + // NOTE: this member is overloaded. + // If hEnt points at the world entity, then this is the static prop index. + // Otherwise, this is the hitbox index. + int hitbox; // box hit by trace in studio +}; + +class Ray { +public: + VectorAligned start; + VectorAligned delta; + VectorAligned start_offset; + VectorAligned extents; + bool is_ray; + bool is_swept; + + // init for point ray + auto init(const math::Vector &start, const math::Vector &end) { + delta = end - start; + is_swept = (delta.length_sqr() != 0); + is_ray = true; + + extents = math::Vector::zero(); + start_offset = math::Vector::zero(); + + this->start = start; + } + + // init for box ray + auto init(const math::Vector &start, const math::Vector &end, const math::Vector &min, const math::Vector &max) { + delta = end - start; + + is_swept = (delta.length_sqr() != 0); + + extents = max - min; + extents *= 0.5; + is_ray = false; + + start_offset = min + max; + start_offset *= 0.5f; + + this->start = start_offset + start; + start_offset *= -1.0f; + } +}; + +class Filter { + sdk::Entity *ignore_self; + sdk::Entity *ignore_entity; + +public: + virtual auto should_hit_entity(sdk::Entity *handle_entity, int contents_mask) -> bool; + virtual auto GetTraceType() const -> u32 { + return 0; // hit everything + } + + Filter() : ignore_self(nullptr), ignore_entity(nullptr) {} + + Filter(sdk::Entity *ignore_self) : ignore_self(ignore_self) {} + Filter(sdk::Entity *ignore_self, sdk::Entity *ignore_entity) : ignore_self(ignore_self), ignore_entity(ignore_entity) {} +}; + +} // namespace trace diff --git a/src/vfunc.hh b/src/vfunc.hh index 0b2e757..f08da1a 100644 --- a/src/vfunc.hh +++ b/src/vfunc.hh @@ -7,7 +7,7 @@ #include "types.hh" // helpers for calling virtual functions -namespace VFunc { +namespace vfunc { inline auto get_table(void *inst, u32 offset) -> void ** { return *reinterpret_cast(reinterpret_cast(inst) + offset); } @@ -54,15 +54,15 @@ public: //assert(instance != nullptr); auto index = 0u; - if constexpr (BluePlatform::windows()) + if constexpr (doghook_platform::windows()) index = index_windows; - else if constexpr (BluePlatform::linux()) + else if constexpr (doghook_platform::linux()) index = index_linux; - else if constexpr (BluePlatform::osx()) + else if constexpr (doghook_platform::osx()) index = index_osx; auto offset = 0u; - if constexpr (BluePlatform::windows()) { + if constexpr (doghook_platform::windows()) { offset = offset_windows; this->instance = reinterpret_cast( @@ -79,7 +79,7 @@ public: } }; -} // namespace VFunc +} // namespace vfunc // macro for easier definitions of wrapper calls // name is the name of the function @@ -88,4 +88,4 @@ public: #define return_virtual_func(name, windows, linux, osx, off, ...) \ using c = std::remove_reference::type; \ using t = decltype(&c::name); \ - return VFunc::Func(this, windows, linux, osx, off).invoke(__VA_ARGS__) + return vfunc::Func(this, windows, linux, osx, off).invoke(__VA_ARGS__) diff --git a/src/weapon.cc b/src/weapon.cc new file mode 100644 index 0000000..a0a2a36 --- /dev/null +++ b/src/weapon.cc @@ -0,0 +1,27 @@ +#include "stdafx.hh" + +#include "interface.hh" +#include "netvar.hh" +#include "sdk.hh" +#include "weapon.hh" + +using namespace sdk; + +auto next_primary_attack = Netvar("DT_BaseCombatWeapon", "LocalActiveWeaponData", "m_flNextPrimaryAttack"); +auto Weapon::next_primary_attack() -> float { + return ::next_primary_attack.get(this); +} + +auto next_secondary_attack = Netvar("DT_BaseCombatWeapon", "LocalActiveWeaponData", "m_flNextSecondaryAttack"); +auto Weapon::next_secondary_attack() -> float { + return ::next_secondary_attack.get(this); +} + +auto Weapon::can_shoot(u32 tickbase) -> bool { + return tickbase * IFace()->interval_per_tick > next_primary_attack(); +} + +auto Weapon::owner() -> Entity * { + // TODO: + return nullptr; +} diff --git a/src/weapon.hh b/src/weapon.hh new file mode 100644 index 0000000..ed4dbd4 --- /dev/null +++ b/src/weapon.hh @@ -0,0 +1,15 @@ +#pragma once + +#include "entity.hh" + +namespace sdk { +class Weapon : public Entity { +public: + auto can_shoot(u32 tickbase) -> bool; + + auto next_primary_attack() -> float; + auto next_secondary_attack() -> float; + + auto owner() -> Entity *; +}; +} // namespace sdk