Merge pull request #8 from josh33901/f1ssi0n/gamesystem

Add classid convar, gamesystem, log and doghook singleton
This commit is contained in:
Marc 2018-03-13 21:36:29 +01:00 committed by GitHub
commit 8d2e2fe838
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1091 additions and 2 deletions

52
src/class_id.cc Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
#pragma once
namespace Log {
void msg(const char *format, ...);
}

View File

@ -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() {}

View File

@ -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

View File

@ -239,6 +239,7 @@ public:
auto get_verified_user_cmd(u32 sequence_number) -> class VerifiedCmd * {
// 03 B7 ? ? ? ? 8D 04 88
return nullptr;
}
};