From fd8c1c8c0bea6343ad59abcd3750e560dc6707ea Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 19:36:48 +0000 Subject: [PATCH 1/7] Add classid convar, gamesystem, log and doghook singleton --- src/class_id.cc | 52 ++++++++ src/class_id.hh | 50 +++++++ src/convar.cc | 330 ++++++++++++++++++++++++++++++++++++++++++++++ src/convar.hh | 305 ++++++++++++++++++++++++++++++++++++++++++ src/doghook.cc | 130 ++++++++++++++++++ src/gamesystem.cc | 48 +++++++ src/gamesystem.hh | 133 +++++++++++++++++++ src/log.cc | 21 +++ src/log.hh | 5 + src/main.cc | 4 +- 10 files changed, 1076 insertions(+), 2 deletions(-) create mode 100644 src/class_id.cc create mode 100644 src/class_id.hh create mode 100644 src/convar.cc create mode 100644 src/convar.hh create mode 100644 src/doghook.cc create mode 100644 src/gamesystem.cc create mode 100644 src/gamesystem.hh create mode 100644 src/log.cc create mode 100644 src/log.hh diff --git a/src/class_id.cc b/src/class_id.cc new file mode 100644 index 0000000..c49f127 --- /dev/null +++ b/src/class_id.cc @@ -0,0 +1,52 @@ +#include "stdafx.hh" + +#define PLACE_CHECKER + +#include "class_id.hh" + +#include "interface.hh" +#include "log.hh" +#include "sdk.hh" + +using namespace sdk; +using namespace class_id; + +internal_checker::ClassIDChecker *internal_checker::ClassIDChecker::head = nullptr; + +internal_checker::ClassIDChecker::ClassIDChecker(const char *name, const u32 value) : name(name), intended_value(value) { + this->name = name; + this->intended_value = value; + next = head; + head = this; +} + +static auto find_class_id(const char *name) { + for (auto client_class = IFace()->get_all_classes(); + client_class != nullptr; + client_class = client_class->next) + if (strcmp(client_class->network_name, name) == 0) return client_class->class_id; + + return -1; +} + +bool internal_checker::ClassIDChecker::check_correct() { + auto found_value = find_class_id(name); + + if (found_value == -1) { + Log::msg("[ClassID] Unable to find correct value for '%s'", name); + return false; + } + + if (found_value != intended_value) { + Log::msg("[ClassID] value for %s is wrong (wanted %d, got %d)", name, intended_value, found_value); + return false; + } + + return true; +} + +void internal_checker::ClassIDChecker::check_all_correct() { + for (auto checker = internal_checker::ClassIDChecker::head; checker != nullptr; checker = checker->next) { + checker->check_correct(); + } +} diff --git a/src/class_id.hh b/src/class_id.hh new file mode 100644 index 0000000..059120b --- /dev/null +++ b/src/class_id.hh @@ -0,0 +1,50 @@ +#pragma once + +#include "platform.hh" + +// This file defines helpers and functionality for using and updating classids + +namespace sdk { +namespace class_id { + +// See comment blow for #define ID +namespace internal_checker { +class ClassIDChecker { + static ClassIDChecker *head; + +public: + const char * name; + u32 intended_value; + ClassIDChecker *next = nullptr; + + ClassIDChecker(const char *name, const u32 value); + + bool check_correct(); + static void check_all_correct(); +}; +} // namespace internal_checker + +// For debug builds we want to be able to check our classids are correct and issue warnings if they are not correct +// So that we can update the value for next time. +#if defined(_DEBUG) && defined(PLACE_CHECKER) +#define ID(name, value) \ + enum { name = value }; \ + namespace internal_checker { \ + inline auto checker_##name = ClassIDChecker( \ + #name, \ + value); \ + } +#else +#define ID(name, value) \ + enum { name = value }; +#endif + +// Put ids here +ID(CTFPlayer, 246); +ID(CTFRevolver, 284); +ID(CTFSniperRifle, 305); + +#undef ID + +} // namespace class_id +} // namespace sdk diff --git a/src/convar.cc b/src/convar.cc new file mode 100644 index 0000000..54f8869 --- /dev/null +++ b/src/convar.cc @@ -0,0 +1,330 @@ +#include "stdafx.hh" + +#include "convar.hh" + +#include "interface.hh" +#include "log.hh" +#include "sdk.hh" + +// implementation of a source convar +namespace sdk { + +class IConVar; + +using ChangeCallbackFn = void (*)(IConVar *, const char *, float); + +static auto dll_identifier = -1; + +class ConCommandBase { +public: + ConCommandBase() : name(nullptr), value_string(nullptr), help_string(nullptr) {} + ConCommandBase(const char *name, const char *help_string, u32 flags = 0) { + create_base(name, help_string, flags); + } + virtual ~ConCommandBase() { + IFace()->unregister_command(this); + } + + virtual bool is_command() const { return false; } + + virtual bool has_flag(int flag) const { return (flags & flag); } + virtual void add_flag(int new_flags) { flags |= new_flags; } + + virtual const char *get_name() const { return name; } + virtual const char *get_help_text() const { return help_string; } + + virtual bool is_registered() const { return registered; } + + virtual int get_dll_identifier() const { + if (dll_identifier == -1) dll_identifier = IFace()->allocate_dll_identifier(); + return dll_identifier; + } + + virtual void create_base(const char *name, const char *help_string, int flags = 0) { + assert(name); + assert(help_string); + + registered = false; + + this->name = name; + this->help_string = help_string; + this->flags = flags; + + next = head; + head = this; + + // We might not have Cvar here (global variables) + + if (auto cvar = IFace()) { + IFace()->register_command(this); + } + } + + virtual bool init() { + IFace()->register_command(this); + return true; + } + + // Convar vtable items + virtual void set_value(const char *value) { + assert(parent == this); // Only valid for root convars. + + float old_value = value_float; + + float new_value; + if (value == nullptr) + new_value = 0.0f; + else + new_value = (float)atof(value); + + // if we need to clamp this value then swap the original string + // out with a temp one + auto new_string_value = value; + char temp_value[32]; + if (clamp_value(new_value) == true) { + snprintf(temp_value, sizeof(temp_value), "%f", new_value); + new_string_value = temp_value; + } + + // Redetermine value + value_float = new_value; + value_int = static_cast(value_float); + + // TODO: do we need to handle never as string convars?? + change_string_value(new_string_value, old_value); + } + virtual void set_value(float new_value) { + assert(parent == this); + + clamp_value(new_value); + + auto old_value = value_float; + value_float = new_value; + value_int = static_cast(new_value); + + char temp_value[32]; + snprintf(temp_value, sizeof(temp_value), "%f", new_value); + change_string_value(temp_value, old_value); + } + virtual void set_value(int new_value) { + return set_value(static_cast(new_value)); + } + + virtual void internal_set_value(const char *new_value) { + return set_value(new_value); + } + virtual void internal_set_value(float new_value) { + return set_value(new_value); + } + virtual void internal_set_value(int new_value) { + return set_value(new_value); + } + + virtual bool clamp_value(float &value) { + if (has_min && (value < value_min)) { + value = value_min; + return true; + } + + if (has_max && (value > value_max)) { + value = value_max; + return true; + } + + return false; + } + + virtual void change_string_value(const char *new_value, float old_value) { + if (value_string != nullptr) { + delete[] value_string; + value_string = nullptr; + } + + auto new_len = strlen(new_value) + 1; + value_string = new char[new_len]; + +#if doghook_platform_windows() + strcpy_s(value_string, new_len, new_value); +#else + strncpy(value_string, new_value, new_len); +#endif + value_string_length = new_len; + + if (change_callback != nullptr) change_callback(to_iconvar(), new_value, old_value); + } + + // helper functions for converting from IConVar and to IConVar + static auto from_iconvar(IConVar *v) { return reinterpret_cast(reinterpret_cast(v) - 24); } + static auto to_iconvar(ConCommandBase *b) { return reinterpret_cast(reinterpret_cast(b) + 24); } + auto to_iconvar() -> IConVar * { return ConCommandBase::to_iconvar(this); } + +#define DEFINE_THUNK(type, name, real_name) \ + static void __fastcall name(IConVar *ecx, void *edx, type v) { \ + auto *real = ConCommandBase::from_iconvar(ecx); \ + real->real_name(v); \ + } + + DEFINE_THUNK(const char *, set_value_string_thunk, set_value); + DEFINE_THUNK(float, set_value_float_thunk, set_value); + DEFINE_THUNK(int, set_value_int_thunk, set_value); +#undef DEFINE_THUNK + + // It doesnt look like IConVar::GetName and IConVar::IsFlagSet are called + static void __fastcall undefined_thunk(u8 *ecx, void *edx, int arg1) { assert(0); } + + virtual void create_convar(char const *name, char const *default_value, u32 flags, char const *help_string, + bool has_min, float min, bool has_max, float max, ChangeCallbackFn change_callback) { + create_base(name, help_string, flags); + +#if doghook_platform_windows() + { + // Set up the MI vtables properly + // IConvar.h + /* + virtual void SetValue( const char *pValue ) = 0; + virtual void SetValue( float flValue ) = 0; + virtual void SetValue( int nValue ) = 0; + virtual const char *GetName( void ) const = 0; + virtual bool IsFlagSet( int nFlag ) const = 0; + */ + + // These all need to be properly thunked + static auto iconvar_vtable = []() { + auto ret = new void *[5]; + ret[0] = &set_value_int_thunk; + ret[1] = &set_value_float_thunk; + ret[2] = &set_value_string_thunk; + + ret[3] = &undefined_thunk; + ret[4] = &undefined_thunk; + + return ret; + }(); + + this->convar_vtable = iconvar_vtable; + } +#endif + parent = this; + + this->default_value = (default_value == nullptr) || (default_value[0] == '\0') ? "0.0" : default_value; + + this->has_min = has_min; + this->value_min = min; + + this->has_max = has_max; + this->value_max = max; + + this->change_callback = change_callback; + + set_value(this->default_value); + } + +public: + ConCommandBase *next; + + bool registered; + + const char *name; + const char *help_string; + + u32 flags; + + static ConCommandBase *head; + + // Convar is an mi class and therefore needs this + // TODO: is this windows only - what is the linux equivilent?? + void *convar_vtable; + + // Convar members + ConCommandBase *parent; + + // Static data + const char *default_value; + + // Value + // Dynamically allocated + char *value_string; + int value_string_length; + + // Values + float value_float; + int value_int; + + // Min/Max values + bool has_min; + float value_min; + bool has_max; + float value_max; + + ChangeCallbackFn change_callback; +}; + +ConCommandBase *ConCommandBase::head; +} // namespace sdk + +const ConvarBase *ConvarBase::head = nullptr; + +void ConvarBase::tf_convar_changed(sdk::IConVar *iconvar, const char *old_string, float old_float) { + auto convar = sdk::ConCommandBase::from_iconvar(iconvar); + assert(convar); + + for (auto c : ConvarBase::get_range()) { + if (c->tf_convar == convar) { + if (convar->registered == false) return; + if (c->init_complete == false) return; + + auto modifiable = const_cast(c); + auto was_clamped = modifiable->from_string(convar->value_string); + + // Remove the callback when we call set_value to prevent recursion + // TODO: there is probably a better way to do this... + auto callback_backup = convar->change_callback; + convar->change_callback = nullptr; + if (was_clamped) convar->set_value(modifiable->to_string()); + convar->change_callback = callback_backup; + + Log::msg("Updated convar %s to '%s' (%s)", convar->get_name(), convar->value_string, was_clamped ? "clamped" : "not clamped"); + } + } +} + +ConvarBase::ConvarBase(const char *name, Convar_Type type, const ConvarBase *parent) : parent(parent), t(type), next(head), init_complete(false) { + head = this; + + strcpy_s(internal_name, name); + + // Create a tf convar based on this one + tf_convar = new sdk::ConCommandBase; + tf_convar->create_convar(name, "", 0, name, false, 0, false, 0, &ConvarBase::tf_convar_changed); + + init_complete = true; +} + +ConvarBase::~ConvarBase() { + + if (this == head) head = this->next; + + for (auto c : ConvarBase::get_range()) { + auto modifiable = const_cast(c); + if (c->next == this) modifiable->next = this->next; + } +} + +void ConvarBase::init_all() { + assert(IFace()); + + // We have to do this goofy loop here as register_command() will + // change the `next` pointer to the next in its chain + // which will cause all kinds of problems for us + + // TODO: this could also be fixed by using the ConvarBase linked list... + + auto c = sdk::ConCommandBase::head; + while (c != nullptr) { + auto next = c->next; + + IFace()->register_command(c); + + c = next; + } +} diff --git a/src/convar.hh b/src/convar.hh new file mode 100644 index 0000000..ae47b66 --- /dev/null +++ b/src/convar.hh @@ -0,0 +1,305 @@ +#include "platform.hh" + +#include +#include + +// Using a tagged class system allows us to avoid the overhead +// of trying to dynamic_cast to each type and seeing if it works +// and then discarding that when we try the next + +// Whilst it is more clumsy - it is faster. + +// This will differ from the source version as we will split different +// Convar types into their own templated class so that you can static_cast +// to it once you know what you are dealing with. + +namespace sdk { +class ConCommandBase; +class IConVar; +} // namespace sdk + +enum class ConvarType { + Bool, + Int, + Float, + String, + Enum, + + // Impossible... + None = -1, +}; + +class ConvarBase { + // Convar list + static const ConvarBase *head; + + ConvarBase const *next; + ConvarBase const *parent; + + char internal_name[128]; + ConvarType t; + + sdk::ConCommandBase *tf_convar; + + bool init_complete; + + static auto tf_convar_changed(sdk::IConVar *convar, const char *old_string, float old_float) -> void; + +public: + ConvarBase(const char *name, ConvarType type, const ConvarBase *parent); + virtual ~ConvarBase(); + + // virtual functions - so you dont have to check type, cast and then call + virtual auto from_string(const char *str) -> bool = 0; + virtual auto to_string() const -> const char * = 0; + + auto name() const { return internal_name; } + auto type() const { return t; } + + class Convar_Range { + public: + class Iterator { + const ConvarBase *current; + + public: + Iterator() : current(nullptr) {} + explicit Iterator(const ConvarBase *b) : current(b) {} + + auto &operator++() { + current = current->next; + return *this; + } + + auto operator*() const { + assert(current); + return current; + } + + auto operator==(const Iterator &b) const { return current == b.current; } + auto operator!=(const Iterator &b) const { return !(*this == b); } + }; + + auto begin() const { return Iterator(head); } + + auto end() const { return Iterator(nullptr); } + }; + + static auto get_range() { return Convar_Range(); } + + static auto init_all() -> void; +}; + +template +class Convar; + +template <> +class Convar : public ConvarBase { + bool value; + +public: + Convar(const char *name, const ConvarBase *parent) : ConvarBase(name, ConvarType::Bool, parent), value(false) {} + + Convar(const char *name, bool value, const ConvarBase *parent) : Convar(name, parent) { + this->value = value; + } + + auto from_string(const char *str) -> bool override final { + assert(str); + + if (_stricmp(str, "false") == 0) { + value = false; + return false; + } else if (_stricmp(str, "true") == 0) { + value = true; + return false; + } + value = atoi(str); + + return false; + } + + auto to_string() const -> const char * override final { + return value ? "true" : "false"; + } + + operator bool() const { return value; } + + auto operator=(bool v) { + value = v; + return *this; + } +}; + +template <> +class Convar : public ConvarBase { + + int value; + + // min + max values (INT_MAX == no value) + int min_value; + int max_value; + +public: + static constexpr int no_value = INT_MAX; + + Convar(const char *name, const ConvarBase *parent) : ConvarBase(name, ConvarType::Int, parent), + value(0), min_value(no_value), max_value(no_value) {} + + // use no_value if you want their to be no min/max + Convar(const char *name, int value, int min_value, int max_value, const ConvarBase *parent) : Convar(name, parent) { + this->value = value; + this->min_value = min_value; + this->max_value = max_value; + } + + auto from_string(const char *str) -> bool override { + assert(str); + + auto new_value = atoi(str); + + if (min_value != no_value) { + if (new_value < min_value) { + value = min_value; + return true; + } + } + if (max_value != no_value) { + if (new_value > max_value) { + value = max_value; + return true; + } + } + + value = new_value; + + return false; + } + + auto to_string() const -> const char * override { + static u32 cur_index = 0; + static char temp[20][8]; + + cur_index += 1; + +#ifdef _DEBUG + memset(temp[cur_index], 0, sizeof(temp)); +#endif + +#if doghook_platform_windows() + _itoa_s(value, temp[cur_index], 10); +#else + itoa(value, temp[cur_index], 10); +#endif + return temp[cur_index]; + } + + operator int() const { return value; } + + auto operator=(int v) { + value = v; + return *this; + } +}; + +template <> +class Convar : public ConvarBase { + + float value; + + // min + max values (INT_MAX == no value) + float min_value; + float max_value; + +public: + static constexpr float no_value = FLT_MAX; + + Convar(const char *name, const ConvarBase *parent) : ConvarBase(name, ConvarType::Float, parent), + value(0), min_value(no_value), max_value(no_value) {} + + // use no_value if you want their to be no min/max + Convar(const char *name, float value, float min_value, float max_value, const ConvarBase *parent) : Convar(name, parent) { + this->value = value; + this->min_value = min_value; + this->max_value = max_value; + } + + auto from_string(const char *str) -> bool override { + assert(str); + + auto new_value = static_cast(atof(str)); + + if (min_value != no_value) { + if (value < min_value) { + value = min_value; + return true; + } + } + if (max_value != no_value) { + if (value > max_value) { + value = min_value; + return true; + } + } + + value = new_value; + + return false; + } + + auto to_string() const -> const char * override { + static u32 cur_index = 0; + static char temp[20][8]; + + cur_index += 1; + +#ifdef _DEBUG + memset(temp[cur_index], 0, sizeof(temp)); +#endif + + // TODO: this is clumsy + return std::to_string(value).c_str(); + } + + operator float() const { return value; } + + auto operator=(float v) { + value = v; + return *this; + } +}; + +template <> +class Convar : public ConvarBase { + char *value; + +public: + Convar(const char *name, const ConvarBase *parent) : ConvarBase(name, ConvarType::String, parent), + value(nullptr) {} + + Convar(const char *name, const char *value, const ConvarBase *parent) : Convar(name, parent) { + auto size = strlen(value) + 1; + this->value = new char[size]; + strcpy_s(this->value, size, value); + } + + ~Convar() { + if (value != nullptr) { + delete[] value; + value = nullptr; + } + } + + auto from_string(const char *str) -> bool override { + if (value != nullptr) delete[] value; + + auto size = strlen(str) + 1; + value = new char[size]; + strcpy_s(this->value, size, str); + + return false; + } + + auto to_string() const -> const char * override { + return value; + } +}; diff --git a/src/doghook.cc b/src/doghook.cc new file mode 100644 index 0000000..8aec8cf --- /dev/null +++ b/src/doghook.cc @@ -0,0 +1,130 @@ +#include "stdafx.hh" + +#include "gamesystem.hh" +#include "log.hh" + +#include "class_id.hh" +#include "hooks.hh" +#include "interface.hh" +#include "netvar.hh" +#include "player.hh" +#include "sdk.hh" +#include "vfunc.hh" + +// Singleton for doing init / deinit of doghook +// and dealing with hooks from gamesystem + +class Doghook : public GameSystem { + bool inited = false; + +public: + bool init() override { + // Guard against having init() called by the game and our constructor + static bool init_happened = false; + if (init_happened) return true; + + Log::msg("init()"); + init_happened = true; + return true; + } + + void post_init() override { + Log::msg("post_init()"); + + // Get interfaces here before init_all has a chance to do anything + + IFace().set_from_interface("client", "VClient"); + IFace().set_from_interface("engine", "VEngineClient"); + IFace().set_from_interface("client", "VClientEntityList"); + IFace().set_from_pointer(**reinterpret_cast( + vfunc::get_func(IFace().get(), 15, 0) + 0x2)); + IFace().set_from_interface("vstdlib", "VEngineCvar"); + + // TODO linux signatures + IFace().set_from_pointer( + *signature::find_pattern( + "client", "B9 ? ? ? ? A3 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? E8 ? ? ? ? 83 C4 04 C7 05", 1)); + IFace().set_from_interface("engine", "VModelInfoClient"); + IFace().set_from_interface("engine", "EngineTraceClient"); + IFace().set_from_interface("engine", "VDebugOverlay"); + IFace().set_from_interface("server", "PlayerInfoManager"); + + IFace().set_from_pointer(IFace()->globals()); + + auto globals_server_address = (u32)IFace().get(); + + // TODO: this globals_real_address is windows only! + if constexpr (doghook_platform::windows()) { + auto globals_real_address = (u32)*signature::find_pattern("engine", "A1 ? ? ? ? 8B 11 68", 8); + IFace().set_from_pointer((sdk::Globals *)globals_real_address); + } + + IFace().set_from_interface("client", "GameMovement"); + IFace().set_from_interface("client", "VClientPrediction"); + IFace().set_from_pointer( + *signature::find_pattern( + "client", "8B 0D ? ? ? ? 8B 01 FF 50 28 56", 2)); + } + + void process_attach() { + Log::msg("process_attach()"); + + // TODO: should these two be modules?? + + // make sure that the netvars are initialised + // becuase their dynamic initialiser could be after the + // gamesystems one + sdk::Netvar::init_all(); + + // TODO: + + // register all convars now that we have the interfaces we need + //Convar_Base::init_all(); + + // at this point we are now inited and ready to go! + inited = true; + } + + void shutdown() override {} + + void level_init_pre_entity() override { Log::msg("init_pre_entity()"); } + void level_init_post_entity() override { + Log::msg("level_init_post_entity"); + + // Make sure that all our class_ids are correct + // This will only do anything on debug builds and not on release builds. + + // TODO: + + // This needs to be done here becuase classids arent initialised before we are in game + sdk::class_id::internal_checker::ClassIDChecker::check_all_correct(); + } + auto level_shutdown_pre_clear_steam_api_context() -> void override { Log::msg("level_shutdown_pre_clear_steam_api_context"); } + auto level_shutdown_pre_entity() -> void override { + Log::msg("level_shutdown_pre_entity"); + } + auto level_shutdown_post_entity() -> void override { Log::msg("level_shutdown_post_entity"); } + + // update is called from CHLClient_HudUpdate + // in theory we should be able to render here + // and be perfectly ok + // HOWEVER: it might be better to do this at frame_end() + void update([[maybe_unused]] float frametime) override { + if (inited != true || IFace()->in_game() != true) return; + } + + Doghook() { + GameSystem::add_all(); + } +}; + +Doghook doghook; + +#if doghook_platform_windows() +auto __stdcall doghook_process_attach([[maybe_unused]] void *hmodule) -> u32 { + // TODO: pass module over to the gamesystem + doghook.process_attach(); + + return 0; +} +#endif diff --git a/src/gamesystem.cc b/src/gamesystem.cc new file mode 100644 index 0000000..8a0c231 --- /dev/null +++ b/src/gamesystem.cc @@ -0,0 +1,48 @@ +#include "stdafx.hh" + +#include "gamesystem.hh" +#include "signature.hh" + +static GameSystem *head = nullptr; +GameSystem::GameSystem() { + next = head; + head = this; +} + +GameSystem::~GameSystem() { + // TODO: remove the gamesystem! +} + +auto GameSystem::add_all() -> void { + using AddFn = void (*)(IGameSystem *); + AddFn add_fn; + + if constexpr (doghook_platform::windows()) { + add_fn = reinterpret_cast( + signature::resolve_callgate( + signature::find_pattern("client", "E8 ? ? ? ? 83 C4 04 8B 76 04 85 F6 75 D0"))); + } else if constexpr (doghook_platform::linux()) { + add_fn = reinterpret_cast( + signature::resolve_callgate( + signature::find_pattern("client", "E8 ? ? ? ? 8B 5B 04 85 DB 75 C1"))); + } else if constexpr (doghook_platform::osx()) { + add_fn = reinterpret_cast( + signature::resolve_callgate( + signature::find_pattern("client", "E8 ? ? ? ? 8B 7F 04 85 FF 75 A1"))); + } + + assert(add_fn); + + for (auto system = head; system != nullptr; system = system->next) { + // Call original Add() function + ((void (*)(IGameSystem *))add_fn)(system); + system->init(); + } + + for (auto system = head; system != nullptr; system = system->next) { + system->post_init(); + } + + // reset the list + head = nullptr; +} diff --git a/src/gamesystem.hh b/src/gamesystem.hh new file mode 100644 index 0000000..acbe1b4 --- /dev/null +++ b/src/gamesystem.hh @@ -0,0 +1,133 @@ +#pragma once + +// These classes must remain by these names in order to fufill some rtti hacks when adding gamesystems +// DO NOT CHANGE THEM + +class IGameSystem { +public: + // GameSystems are expected to implement these methods. + virtual char const *name() = 0; + + // Init, shutdown + // return true on success. false to abort DLL init! + virtual bool init() = 0; + virtual void post_init() = 0; + virtual void shutdown() = 0; + + // Level init, shutdown + virtual void level_init_pre_entity() = 0; + // entities are created / spawned / precached here + virtual void level_init_post_entity() = 0; + + virtual void level_shutdown_pre_clear_steam_api_context() = 0; + virtual void level_shutdown_pre_entity() = 0; + // Entities are deleted / released here... + virtual void level_shutdown_post_entity() = 0; + // end of level shutdown + + // Called during game save + virtual void on_save() = 0; + + // Called during game restore, after the local player has connected and entities have been fully restored + virtual void on_restore() = 0; + + // Called every frame. It's safe to remove an igamesystem from within this callback. + virtual void safe_remove_if_desired() = 0; + + virtual bool is_per_frame() = 0; + + // destructor, cleans up automagically.... + virtual ~IGameSystem(){}; + + // Client systems can use this to get at the map name + static const char *map_name(); + + // These methods are used to add and remove server systems from the + // main server loop. The systems are invoked in the order in which + // they are added. +private: + static void add(IGameSystem *pSys); + static void remove(IGameSystem *pSys); + static void remove_all(); + + // These methods are used to initialize, shutdown, etc all systems + static bool init_all_systems(); + static void post_init_all_systems(); + static void shutdown_all_systems(); + static void level_init_pre_entity_all_systems(char const *pMapName); + static void level_init_post_all_systems(); + static void level_shutdown_pre_clear_steam_api_context_all_systems(); // Called prior to steamgameserverapicontext->Clear() + static void level_shutdown_pre_entity_all_systems(); + static void level_shutdown_post_entity_all_systems(); + + static void on_save_all_systems(); + static void on_restore_all_systems(); + + static void safe_remove_if_desired_all_systems(); + + static void pre_render_all_systems(); + static void update_all_systems(float frametime); + static void post_render_all_systems(); +}; + +class IGameSystemPerFrame : public IGameSystem { +public: + // destructor, cleans up automagically.... + virtual ~IGameSystemPerFrame(){}; + + // Called before rendering + virtual void pre_render() = 0; + + // Gets called each frame + virtual void update(float frametime) = 0; + + // Called after rendering + virtual void post_render() = 0; +}; + +// Quick and dirty server system for users who don't care about precise ordering +// and usually only want to implement a few of the callbacks +class CBaseGameSystemPerFrame : public IGameSystemPerFrame { + +public: + virtual const char *name() override { return "unnamed"; } + + // Init, shutdown + // return true on success. false to abort DLL init! + virtual bool init() override { return true; } + virtual void post_init() override {} + virtual void shutdown() override {} + + // Level init, shutdown + virtual void level_init_pre_entity() override {} + virtual void level_init_post_entity() override {} + virtual void level_shutdown_pre_clear_steam_api_context() override {} + virtual void level_shutdown_pre_entity() override {} + virtual void level_shutdown_post_entity() override {} + + virtual void on_save() override {} + virtual void on_restore() override {} + virtual void safe_remove_if_desired() override {} + + virtual bool is_per_frame() override { return true; } + + // Called before rendering + virtual void pre_render() override {} + + // Gets called each frame + virtual void update(float) override {} + + // Called after rendering + virtual void post_render() override {} +}; + +class GameSystem : public CBaseGameSystemPerFrame { + GameSystem *next; + +public: + GameSystem(); + virtual ~GameSystem(); + + // add all functions that are in the linked list + static void add_all(); +}; diff --git a/src/log.cc b/src/log.cc new file mode 100644 index 0000000..f227259 --- /dev/null +++ b/src/log.cc @@ -0,0 +1,21 @@ +#include "stdafx.hh" + +#include "log.hh" +#include "signature.hh" + +auto Log::msg(const char *format, ...) -> void { + + char buffer[1024]; + va_list vlist; + va_start(vlist, format); + vsnprintf(buffer, 1024, format, vlist); + va_end(vlist); + + using MessageFn = void (*)(const char *format, ...); + + static auto msg_fn = (MessageFn)signature::resolve_import(signature::resolve_library("tier0"), "Msg"); + + msg_fn("[Woof] %s\n", buffer); + + printf("[Woof] %s\n", buffer); +} diff --git a/src/log.hh b/src/log.hh new file mode 100644 index 0000000..893ec75 --- /dev/null +++ b/src/log.hh @@ -0,0 +1,5 @@ +#pragma once + +namespace Log { +void msg(const char *format, ...); +} diff --git a/src/main.cc b/src/main.cc index be1fd0d..34c2653 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,8 +4,6 @@ extern u32 __stdcall doghook_process_attach(void *a); -u32 __stdcall doghook_process_attach(void *a) { return 0; } - __declspec(dllexport) BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { @@ -18,6 +16,8 @@ __declspec(dllexport) BOOL APIENTRY DllMain(HMODULE hModule, return true; } #else +// TODO: send process attach over to gamesystem + void __attribute__((constructor)) startup() {} void __attribute__((destructor)) shutdown() {} From 0147ecc4cb83154848d54918d75388cb27bb6bdb Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 19:38:05 +0000 Subject: [PATCH 2/7] Update init routines for new code --- src/doghook.cc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/doghook.cc b/src/doghook.cc index 8aec8cf..4986164 100644 --- a/src/doghook.cc +++ b/src/doghook.cc @@ -4,6 +4,7 @@ #include "log.hh" #include "class_id.hh" +#include "convar.hh" #include "hooks.hh" #include "interface.hh" #include "netvar.hh" @@ -69,17 +70,13 @@ public: void process_attach() { Log::msg("process_attach()"); - // TODO: should these two be modules?? - // make sure that the netvars are initialised // becuase their dynamic initialiser could be after the // gamesystems one sdk::Netvar::init_all(); - // TODO: - // register all convars now that we have the interfaces we need - //Convar_Base::init_all(); + ConvarBase::init_all(); // at this point we are now inited and ready to go! inited = true; @@ -94,8 +91,6 @@ public: // Make sure that all our class_ids are correct // This will only do anything on debug builds and not on release builds. - // TODO: - // This needs to be done here becuase classids arent initialised before we are in game sdk::class_id::internal_checker::ClassIDChecker::check_all_correct(); } From 238ffc43ddb068df49bb85f27943c0a5482f22e0 Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 19:45:49 +0000 Subject: [PATCH 3/7] Fix compilation issues --- src/convar.cc | 7 +++++-- src/convar.hh | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/convar.cc b/src/convar.cc index 54f8869..bf561c7 100644 --- a/src/convar.cc +++ b/src/convar.cc @@ -288,10 +288,13 @@ void ConvarBase::tf_convar_changed(sdk::IConVar *iconvar, const char *old_string } } -ConvarBase::ConvarBase(const char *name, Convar_Type type, const ConvarBase *parent) : parent(parent), t(type), next(head), init_complete(false) { +ConvarBase::ConvarBase(const char *name, ConvarType type, const ConvarBase *parent) : parent(parent), t(type), next(head), init_complete(false) { head = this; - strcpy_s(internal_name, name); + if constexpr (doghook_platform_windows()) + strcpy_s(internal_name, name); + else if constexpr (doghook_platform_linux()) + strcpy(internal_name, name); // Create a tf convar based on this one tf_convar = new sdk::ConCommandBase; diff --git a/src/convar.hh b/src/convar.hh index ae47b66..b4a9871 100644 --- a/src/convar.hh +++ b/src/convar.hh @@ -106,10 +106,10 @@ public: auto from_string(const char *str) -> bool override final { assert(str); - if (_stricmp(str, "false") == 0) { + if (stricmp(str, "false") == 0) { value = false; return false; - } else if (_stricmp(str, "true") == 0) { + } else if (stricmp(str, "true") == 0) { value = true; return false; } @@ -279,7 +279,10 @@ public: Convar(const char *name, const char *value, const ConvarBase *parent) : Convar(name, parent) { auto size = strlen(value) + 1; this->value = new char[size]; - strcpy_s(this->value, size, value); + if constexpr (doghook_platform_windows()) + strcpy_s(this->value, size, value); + else if constexpr (doghook_platform_linux()) + strncpy(this->value, value, size); } ~Convar() { From 29f293655092206f23bd3ac5908a9a101ca3069f Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 19:55:36 +0000 Subject: [PATCH 4/7] Update convar and fix strcmp issues --- src/convar.hh | 26 +++++++++++++------------- src/platform.hh | 4 ++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/convar.hh b/src/convar.hh index b4a9871..01cefda 100644 --- a/src/convar.hh +++ b/src/convar.hh @@ -43,15 +43,15 @@ class ConvarBase { bool init_complete; - static auto tf_convar_changed(sdk::IConVar *convar, const char *old_string, float old_float) -> void; + static void tf_convar_changed(sdk::IConVar *convar, const char *old_string, float old_float); public: ConvarBase(const char *name, ConvarType type, const ConvarBase *parent); virtual ~ConvarBase(); // virtual functions - so you dont have to check type, cast and then call - virtual auto from_string(const char *str) -> bool = 0; - virtual auto to_string() const -> const char * = 0; + virtual bool from_string(const char *str) = 0; + virtual const char *to_string() const = 0; auto name() const { return internal_name; } auto type() const { return t; } @@ -103,13 +103,13 @@ public: this->value = value; } - auto from_string(const char *str) -> bool override final { + bool from_string(const char *str) override final { assert(str); - if (stricmp(str, "false") == 0) { + if (_stricmp(str, "false") == 0) { value = false; return false; - } else if (stricmp(str, "true") == 0) { + } else if (_stricmp(str, "true") == 0) { value = true; return false; } @@ -118,7 +118,7 @@ public: return false; } - auto to_string() const -> const char * override final { + const char *to_string() const override final { return value ? "true" : "false"; } @@ -152,7 +152,7 @@ public: this->max_value = max_value; } - auto from_string(const char *str) -> bool override { + bool from_string(const char *str) override { assert(str); auto new_value = atoi(str); @@ -175,7 +175,7 @@ public: return false; } - auto to_string() const -> const char * override { + const char *to_string() const override { static u32 cur_index = 0; static char temp[20][8]; @@ -223,7 +223,7 @@ public: this->max_value = max_value; } - auto from_string(const char *str) -> bool override { + bool from_string(const char *str) override { assert(str); auto new_value = static_cast(atof(str)); @@ -246,7 +246,7 @@ public: return false; } - auto to_string() const -> const char * override { + const char *to_string() const override { static u32 cur_index = 0; static char temp[20][8]; @@ -292,7 +292,7 @@ public: } } - auto from_string(const char *str) -> bool override { + bool from_string(const char *str) override { if (value != nullptr) delete[] value; auto size = strlen(str) + 1; @@ -302,7 +302,7 @@ public: return false; } - auto to_string() const -> const char * override { + const char *to_string() const override { return value; } }; diff --git a/src/platform.hh b/src/platform.hh index 3e1d0e3..870f80f 100644 --- a/src/platform.hh +++ b/src/platform.hh @@ -63,4 +63,8 @@ inline constexpr bool gcc() { return doghook_platform_gcc(); } #define __fastcall #define __stdcall #define __cdecl + +// TODO: make a function wrapper please +#define _stricmp strcasecmp + #endif From b542f59bd1a930d64fca447bd6e05eec3efce312 Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 20:02:08 +0000 Subject: [PATCH 5/7] Fix more crossplatform issues --- src/convar.hh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/convar.hh b/src/convar.hh index 01cefda..f1f9d4c 100644 --- a/src/convar.hh +++ b/src/convar.hh @@ -1,6 +1,8 @@ #include "platform.hh" #include +#include +#include #include // Using a tagged class system allows us to avoid the overhead @@ -188,7 +190,7 @@ public: #if doghook_platform_windows() _itoa_s(value, temp[cur_index], 10); #else - itoa(value, temp[cur_index], 10); + sprintf(temp[cur_index], "%d", value); #endif return temp[cur_index]; } @@ -279,10 +281,11 @@ public: Convar(const char *name, const char *value, const ConvarBase *parent) : Convar(name, parent) { auto size = strlen(value) + 1; this->value = new char[size]; - if constexpr (doghook_platform_windows()) - strcpy_s(this->value, size, value); - else if constexpr (doghook_platform_linux()) - strncpy(this->value, value, size); +#if doghook_platform_windows() + strcpy_s(this->value, size, value); +#elif doghook_platform_linux() + strncpy(this->value, value, size); +#endif } ~Convar() { From 4096976ea93fb0ab064962a7a8ffbddf59761582 Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 20:24:41 +0000 Subject: [PATCH 6/7] Fix more crossplat issues --- src/convar.cc | 9 +++++---- src/convar.hh | 5 +++++ src/sdk.hh | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/convar.cc b/src/convar.cc index bf561c7..ea74836 100644 --- a/src/convar.cc +++ b/src/convar.cc @@ -291,10 +291,11 @@ void ConvarBase::tf_convar_changed(sdk::IConVar *iconvar, const char *old_string ConvarBase::ConvarBase(const char *name, ConvarType type, const ConvarBase *parent) : parent(parent), t(type), next(head), init_complete(false) { head = this; - if constexpr (doghook_platform_windows()) - strcpy_s(internal_name, name); - else if constexpr (doghook_platform_linux()) - strcpy(internal_name, name); +#if doghook_platform_windows() + strcpy_s(internal_name, name); +#elif doghook_platform_linux() + strcpy(internal_name, name); +#endif // Create a tf convar based on this one tf_convar = new sdk::ConCommandBase; diff --git a/src/convar.hh b/src/convar.hh index f1f9d4c..c9be2b4 100644 --- a/src/convar.hh +++ b/src/convar.hh @@ -300,7 +300,12 @@ public: auto size = strlen(str) + 1; value = new char[size]; + +#if doghook_platform_windows() strcpy_s(this->value, size, str); +#elif doghook_platform_linux() + strncpy(this->value, size, str); +#endif return false; } diff --git a/src/sdk.hh b/src/sdk.hh index eb0e50d..6fe726b 100644 --- a/src/sdk.hh +++ b/src/sdk.hh @@ -239,6 +239,7 @@ public: auto get_verified_user_cmd(u32 sequence_number) -> class VerifiedCmd * { // 03 B7 ? ? ? ? 8D 04 88 + return nullptr; } }; From e61e43964299228dc7c196771acaaa04d5fe1172 Mon Sep 17 00:00:00 2001 From: F1ssi0N Date: Tue, 13 Mar 2018 20:31:18 +0000 Subject: [PATCH 7/7] Fix cross platform issues --- src/convar.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/convar.hh b/src/convar.hh index c9be2b4..b9105c9 100644 --- a/src/convar.hh +++ b/src/convar.hh @@ -304,7 +304,7 @@ public: #if doghook_platform_windows() strcpy_s(this->value, size, str); #elif doghook_platform_linux() - strncpy(this->value, size, str); + strncpy(this->value, str, size); #endif return false;