Merge pull request #8 from josh33901/f1ssi0n/gamesystem
Add classid convar, gamesystem, log and doghook singleton
This commit is contained in:
commit
8d2e2fe838
52
src/class_id.cc
Normal file
52
src/class_id.cc
Normal file
@ -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<Client>()->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();
|
||||
}
|
||||
}
|
50
src/class_id.hh
Normal file
50
src/class_id.hh
Normal file
@ -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
|
334
src/convar.cc
Normal file
334
src/convar.cc
Normal file
@ -0,0 +1,334 @@
|
||||
#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<Cvar>()->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<Cvar>()->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<Cvar>()) {
|
||||
IFace<Cvar>()->register_command(this);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool init() {
|
||||
IFace<Cvar>()->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<int>(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<int>(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<float>(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<ConCommandBase *>(reinterpret_cast<u8 *>(v) - 24); }
|
||||
static auto to_iconvar(ConCommandBase *b) { return reinterpret_cast<IConVar *>(reinterpret_cast<u8 *>(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<ConvarBase *>(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, ConvarType type, const ConvarBase *parent) : parent(parent), t(type), next(head), init_complete(false) {
|
||||
head = this;
|
||||
|
||||
#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;
|
||||
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<ConvarBase *>(c);
|
||||
if (c->next == this) modifiable->next = this->next;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvarBase::init_all() {
|
||||
assert(IFace<sdk::Cvar>());
|
||||
|
||||
// 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<sdk::Cvar>()->register_command(c);
|
||||
|
||||
c = next;
|
||||
}
|
||||
}
|
316
src/convar.hh
Normal file
316
src/convar.hh
Normal file
@ -0,0 +1,316 @@
|
||||
#include "platform.hh"
|
||||
|
||||
#include <cfloat>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
// 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 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 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; }
|
||||
|
||||
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 <typename T>
|
||||
class Convar;
|
||||
|
||||
template <>
|
||||
class Convar<bool> : 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;
|
||||
}
|
||||
|
||||
bool from_string(const char *str) 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;
|
||||
}
|
||||
|
||||
const char *to_string() const override final {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
operator bool() const { return value; }
|
||||
|
||||
auto operator=(bool v) {
|
||||
value = v;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class Convar<int> : 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;
|
||||
}
|
||||
|
||||
bool from_string(const char *str) 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;
|
||||
}
|
||||
|
||||
const char *to_string() const 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
|
||||
sprintf(temp[cur_index], "%d", value);
|
||||
#endif
|
||||
return temp[cur_index];
|
||||
}
|
||||
|
||||
operator int() const { return value; }
|
||||
|
||||
auto operator=(int v) {
|
||||
value = v;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class Convar<float> : 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;
|
||||
}
|
||||
|
||||
bool from_string(const char *str) override {
|
||||
assert(str);
|
||||
|
||||
auto new_value = static_cast<float>(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;
|
||||
}
|
||||
|
||||
const char *to_string() const 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<char *> : 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];
|
||||
#if doghook_platform_windows()
|
||||
strcpy_s(this->value, size, value);
|
||||
#elif doghook_platform_linux()
|
||||
strncpy(this->value, value, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
~Convar() {
|
||||
if (value != nullptr) {
|
||||
delete[] value;
|
||||
value = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool from_string(const char *str) override {
|
||||
if (value != nullptr) delete[] value;
|
||||
|
||||
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, str, size);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *to_string() const override {
|
||||
return value;
|
||||
}
|
||||
};
|
125
src/doghook.cc
Normal file
125
src/doghook.cc
Normal file
@ -0,0 +1,125 @@
|
||||
#include "stdafx.hh"
|
||||
|
||||
#include "gamesystem.hh"
|
||||
#include "log.hh"
|
||||
|
||||
#include "class_id.hh"
|
||||
#include "convar.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<sdk::Client>().set_from_interface("client", "VClient");
|
||||
IFace<sdk::Engine>().set_from_interface("engine", "VEngineClient");
|
||||
IFace<sdk::EntList>().set_from_interface("client", "VClientEntityList");
|
||||
IFace<sdk::Input>().set_from_pointer(**reinterpret_cast<sdk::Input ***>(
|
||||
vfunc::get_func<u8 *>(IFace<sdk::Client>().get(), 15, 0) + 0x2));
|
||||
IFace<sdk::Cvar>().set_from_interface("vstdlib", "VEngineCvar");
|
||||
|
||||
// TODO linux signatures
|
||||
IFace<sdk::ClientMode>().set_from_pointer(
|
||||
*signature::find_pattern<sdk::ClientMode **>(
|
||||
"client", "B9 ? ? ? ? A3 ? ? ? ? E8 ? ? ? ? 68 ? ? ? ? E8 ? ? ? ? 83 C4 04 C7 05", 1));
|
||||
IFace<sdk::ModelInfo>().set_from_interface("engine", "VModelInfoClient");
|
||||
IFace<sdk::Trace>().set_from_interface("engine", "EngineTraceClient");
|
||||
IFace<sdk::DebugOverlay>().set_from_interface("engine", "VDebugOverlay");
|
||||
IFace<sdk::PlayerInfoManager>().set_from_interface("server", "PlayerInfoManager");
|
||||
|
||||
IFace<sdk::Globals>().set_from_pointer(IFace<sdk::PlayerInfoManager>()->globals());
|
||||
|
||||
auto globals_server_address = (u32)IFace<sdk::Globals>().get();
|
||||
|
||||
// TODO: this globals_real_address is windows only!
|
||||
if constexpr (doghook_platform::windows()) {
|
||||
auto globals_real_address = (u32)*signature::find_pattern<sdk::Globals **>("engine", "A1 ? ? ? ? 8B 11 68", 8);
|
||||
IFace<sdk::Globals>().set_from_pointer((sdk::Globals *)globals_real_address);
|
||||
}
|
||||
|
||||
IFace<sdk::GameMovement>().set_from_interface("client", "GameMovement");
|
||||
IFace<sdk::Prediction>().set_from_interface("client", "VClientPrediction");
|
||||
IFace<sdk::MoveHelper>().set_from_pointer(
|
||||
*signature::find_pattern<sdk::MoveHelper **>(
|
||||
"client", "8B 0D ? ? ? ? 8B 01 FF 50 28 56", 2));
|
||||
}
|
||||
|
||||
void process_attach() {
|
||||
Log::msg("process_attach()");
|
||||
|
||||
// make sure that the netvars are initialised
|
||||
// becuase their dynamic initialiser could be after the
|
||||
// gamesystems one
|
||||
sdk::Netvar::init_all();
|
||||
|
||||
// register all convars now that we have the interfaces we need
|
||||
ConvarBase::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.
|
||||
|
||||
// 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<sdk::Engine>()->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
|
48
src/gamesystem.cc
Normal file
48
src/gamesystem.cc
Normal file
@ -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<AddFn>(
|
||||
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<AddFn>(
|
||||
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<AddFn>(
|
||||
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;
|
||||
}
|
133
src/gamesystem.hh
Normal file
133
src/gamesystem.hh
Normal file
@ -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();
|
||||
};
|
21
src/log.cc
Normal file
21
src/log.cc
Normal file
@ -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);
|
||||
}
|
5
src/log.hh
Normal file
5
src/log.hh
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace Log {
|
||||
void msg(const char *format, ...);
|
||||
}
|
@ -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() {}
|
||||
|
@ -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
|
||||
|
@ -239,6 +239,7 @@ public:
|
||||
|
||||
auto get_verified_user_cmd(u32 sequence_number) -> class VerifiedCmd * {
|
||||
// 03 B7 ? ? ? ? 8D 04 88
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user