Overall cleanup

This commit is contained in:
ineedbots 2021-06-28 13:56:51 -06:00
parent 9564dcec13
commit b35949ddaf
22 changed files with 1034 additions and 19 deletions

141
src/Components/Loader.cpp Normal file
View File

@ -0,0 +1,141 @@
#include "STDInclude.hpp"
#include "Modules/Flags.hpp"
#include "Modules/HelloWorld.hpp"
#include "Modules/Logger.hpp"
namespace Components
{
bool Loader::Pregame = true;
bool Loader::Postgame = false;
bool Loader::Uninitializing = false;
std::vector<Component*> Loader::Components;
bool Loader::IsPregame()
{
return Loader::Pregame;
}
bool Loader::IsPostgame()
{
return Loader::Postgame;
}
bool Loader::IsUninitializing()
{
return Loader::Uninitializing;
}
void Loader::Initialize(GAMEEXE GameType)
{
Loader::Pregame = true;
Loader::Postgame = false;
Loader::Uninitializing = false;
Utils::Memory::GetAllocator()->clear();
Game::Init(GameType);
Loader::Register(new Flags());
Loader::Register(new HelloWorld());
Loader::Register(new Logger());
Loader::Pregame = false;
}
void Loader::Uninitialize()
{
Loader::Uninitializing = true;
Loader::PreDestroyNoPostGame();
std::reverse(Loader::Components.begin(), Loader::Components.end());
for (auto component : Loader::Components)
{
#ifdef DEBUG
if (!Loader::IsPerformingUnitTests())
{
Logger::Print("Unregistering component: %s\n", component->getName().data());
}
#endif
delete component;
}
Loader::Components.clear();
Utils::Memory::GetAllocator()->clear();
Loader::Uninitializing = false;
}
void Loader::PreDestroy()
{
if (!Loader::Postgame)
{
Loader::Postgame = true;
auto components = Loader::Components;
std::reverse(components.begin(), components.end());
for (auto component : components)
{
component->preDestroy();
}
}
}
void Loader::PreDestroyNoPostGame()
{
if (!Loader::Postgame)
{
auto components = Loader::Components;
std::reverse(components.begin(), components.end());
for (auto component : components)
{
component->preDestroy();
}
Loader::Postgame = true;
}
}
bool Loader::PerformUnitTests()
{
bool result = true;
Logger::Print("Performing unit tests for components:\n");
for (auto component : Loader::Components)
{
#if defined(DEBUG) || defined(FORCE_UNIT_TESTS)
Logger::Print("Testing '%s'...\n", component->getName().data());
#endif
auto startTime = std::chrono::high_resolution_clock::now();
bool testRes = component->unitTest();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
Logger::Print("Test done (%llims): %s\n\n", duration, (testRes ? "Success" : "Error"));
result &= testRes;
}
return result;
}
bool Loader::IsPerformingUnitTests()
{
#if defined(DEBUG) || defined(FORCE_UNIT_TESTS)
return Flags::HasFlag("tests");
#else
return false;
#endif
}
void Loader::Register(Component* component)
{
if (component)
{
#if defined(DEBUG) || defined(FORCE_UNIT_TESTS)
if (!Loader::IsPerformingUnitTests())
{
Logger::Print("Component registered: %s\n", component->getName().data());
}
#endif
Loader::Components.push_back(component);
}
}
}

62
src/Components/Loader.hpp Normal file
View File

@ -0,0 +1,62 @@
#pragma once
namespace Components
{
class Component
{
public:
Component() {};
virtual ~Component() {};
#if defined(DEBUG) || defined(FORCE_UNIT_TESTS)
virtual std::string getName()
{
std::string name = typeid(*this).name();
Utils::String::Replace(name, "class Components::", "");
return name;
};
#endif
// It's illegal to spawn threads in DLLMain, and apparently it causes problems if they are destroyed there as well.
// This method is called before DLLMain (if possible) and should to destroy threads.
// It's not 100% guaranteed that it's called outside DLLMain, as it depends on the game, but it's 100% guaranteed, that it is called at all.
virtual void preDestroy() {};
virtual bool unitTest() { return true; }; // Unit testing entry
};
class Loader
{
public:
static void Initialize(GAMEEXE);
static void Uninitialize();
static void PreDestroy();
static void PreDestroyNoPostGame();
static bool PerformUnitTests();
static bool IsPerformingUnitTests();
static void Register(Component* component);
static bool IsPregame();
static bool IsPostgame();
static bool IsUninitializing();
template <typename T>
static T* GetInstance()
{
for (auto& component : Loader::Components)
{
if (typeid(*component) == typeid(T))
{
return reinterpret_cast<T*>(component);
}
}
return nullptr;
}
private:
static bool Pregame;
static bool Postgame;
static bool Uninitializing;
static std::vector<Component*> Components;
};
}

View File

@ -0,0 +1,56 @@
#include "STDInclude.hpp"
#include "Flags.hpp"
namespace Components
{
std::vector<std::string> Flags::EnabledFlags;
bool Flags::HasFlag(const std::string& flag)
{
Flags::ParseFlags();
for (auto entry : Flags::EnabledFlags)
{
if (Utils::String::ToLower(entry) == Utils::String::ToLower(flag))
{
return true;
}
}
return false;
}
void Flags::ParseFlags()
{
static bool flagsParsed = false;
if (flagsParsed) return;
flagsParsed = true;
int numArgs;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &numArgs);
if (argv)
{
for (int i = 0; i < numArgs; ++i)
{
std::wstring wFlag(argv[i]);
if (wFlag[0] == L'-')
{
Flags::EnabledFlags.push_back(std::string(++wFlag.begin(), wFlag.end()));
}
}
LocalFree(argv);
}
}
Flags::Flags()
{
Flags::ParseFlags();
}
Flags::~Flags()
{
Flags::EnabledFlags.clear();
}
}

View File

@ -0,0 +1,18 @@
#pragma once
namespace Components
{
class Flags : public Component
{
public:
Flags();
~Flags();
static bool HasFlag(const std::string& flag);
private:
static std::vector<std::string> EnabledFlags;
static void ParseFlags();
};
}

View File

@ -0,0 +1,13 @@
#include "STDInclude.hpp"
#include "HelloWorld.hpp"
namespace Components
{
HelloWorld::HelloWorld()
{
}
HelloWorld::~HelloWorld()
{
}
}

View File

@ -0,0 +1,11 @@
#pragma once
namespace Components
{
class HelloWorld : public Component
{
public:
HelloWorld();
~HelloWorld();
};
}

View File

@ -0,0 +1,96 @@
#include "STDInclude.hpp"
#include "Logger.hpp"
#include "Flags.hpp"
namespace Components
{
bool Logger::IsConsoleReady()
{
return true;
}
void Logger::PrintStub(int channel, const char* message, ...)
{
return Logger::MessagePrint(channel, Logger::Format(&message));
}
void Logger::Print(const char* message, ...)
{
return Logger::MessagePrint(0, Logger::Format(&message));
}
void Logger::Print(int channel, const char* message, ...)
{
return Logger::MessagePrint(channel, Logger::Format(&message));
}
void Logger::MessagePrint([[maybe_unused]] int channel, [[maybe_unused]] const std::string& message)
{
if (Flags::HasFlag("stdout") || Loader::IsPerformingUnitTests())
{
printf("%s", message.data());
fflush(stdout);
return;
}
/*if (!Logger::IsConsoleReady())
{
OutputDebugStringA(message.data());
}
if (!Game::Sys_IsMainThread())
{
Logger::EnqueueMessage(message);
}
else
{
Game::Com_PrintMessage(channel, message.data(), 0);
}*/
}
void Logger::ErrorPrint([[maybe_unused]] int error, [[maybe_unused]] const std::string& message)
{
#ifdef DEBUG
if (IsDebuggerPresent()) __debugbreak();
#endif
//Game::Com_Error(error, "%s", message.data());
}
void Logger::Error(int error, const char* message, ...)
{
return Logger::ErrorPrint(error, Logger::Format(&message));
}
void Logger::Error(const char* message, ...)
{
return Logger::ErrorPrint(0, Logger::Format(&message));
}
void Logger::SoftError(const char* message, ...)
{
return Logger::ErrorPrint(2, Logger::Format(&message));
}
std::string Logger::Format(const char** message)
{
const size_t bufferSize = 0x10000;
Utils::Memory::Allocator allocator;
char* buffer = allocator.allocateArray<char>(bufferSize);
va_list ap = reinterpret_cast<char*>(const_cast<char**>(&message[1]));
//va_start(ap, *message);
_vsnprintf_s(buffer, bufferSize, bufferSize, *message, ap);
va_end(ap);
return buffer;
}
Logger::Logger()
{
}
Logger::~Logger()
{
}
}

View File

@ -0,0 +1,26 @@
#pragma once
namespace Components
{
class Logger : public Component
{
public:
Logger();
~Logger();
static void MessagePrint(int channel, const std::string& message);
static void Print(int channel, const char* message, ...);
static void Print(const char* message, ...);
static void ErrorPrint(int error, const std::string& message);
static void Error(const char* message, ...);
static void Error(int error, const char* message, ...);
static void SoftError(const char* message, ...);
static bool IsConsoleReady();
static void PrintStub(int channel, const char* message, ...);
private:
static std::string Format(const char** message);
};
}

10
src/Game/Game.cpp Normal file
View File

@ -0,0 +1,10 @@
#include "STDInclude.hpp"
namespace Game
{
void Init(GAMEEXE)
{
}
}

6
src/Game/Game.hpp Normal file
View File

@ -0,0 +1,6 @@
#pragma once
namespace Game
{
void Init(GAMEEXE);
}

View File

@ -1,9 +0,0 @@
#include "STDInclude.hpp"
namespace MP
{
void PatchT4()
{
MessageBoxA(nullptr, "MP", "DEBUG", 0);
}
}

View File

@ -1,6 +0,0 @@
#pragma once
namespace MP
{
void PatchT4();
}

View File

@ -1 +0,0 @@
#include "STDInclude.hpp"

View File

5
src/Game/Structs.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
namespace Game
{
}

View File

@ -31,7 +31,7 @@ namespace Main
// detect which executable's patches to apply
DWORD dataStrData = Utils::Hook::Get<DWORD>(0x59B69C);
if (dataStrData == 0x6C6C6143)
MP::PatchT4();
Components::Loader::Initialize(GAMEEXE::MP);
hModule = GetModuleHandle(NULL);
PIMAGE_DOS_HEADER header = (PIMAGE_DOS_HEADER)hModule;

View File

@ -24,6 +24,7 @@
#include <Psapi.h>
#include <tlhelp32.h>
#include <Shlwapi.h>
#include <assert.h>
#pragma warning(push)
#pragma warning(disable: 4091)
@ -81,9 +82,19 @@ template <size_t S> class Sizer { };
#pragma warning(pop)
#include "Utils/Hooking.hpp"
enum GAMEEXE
{
MP
};
#include "Game/MP.hpp"
#include "Utils/Hooking.hpp"
#include "Utils/Memory.hpp"
#include "Utils/String.hpp"
#include "Game/Structs.hpp"
#include "Game/Game.hpp"
#include "Components/Loader.hpp"
// Libraries
#pragma comment(lib, "Winmm.lib")

105
src/Utils/Memory.cpp Normal file
View File

@ -0,0 +1,105 @@
#include "STDInclude.hpp"
namespace Utils
{
Utils::Memory::Allocator Memory::MemAllocator;
void* Memory::AllocateAlign(size_t length, size_t alignment)
{
void* data = _aligned_malloc(length, alignment);
assert(data != nullptr);
if (data) ZeroMemory(data, length);
return data;
}
void* Memory::Allocate(size_t length)
{
void* data = calloc(length, 1);
assert(data != nullptr);
return data;
}
char* Memory::DuplicateString(const std::string& string)
{
char* newString = Memory::AllocateArray<char>(string.size() + 1);
std::memcpy(newString, string.data(), string.size());
return newString;
}
void Memory::Free(void* data)
{
if (data)
{
free(data);
}
}
void Memory::Free(const void* data)
{
Memory::Free(const_cast<void*>(data));
}
void Memory::FreeAlign(void* data)
{
if (data)
{
_aligned_free(data);
}
}
void Memory::FreeAlign(const void* data)
{
Memory::FreeAlign(const_cast<void*>(data));
}
// Complementary function for memset, which checks if memory is filled with a char
bool Memory::IsSet(void* mem, char chr, size_t length)
{
char* memArr = reinterpret_cast<char*>(mem);
for (size_t i = 0; i < length; ++i)
{
if (memArr[i] != chr)
{
return false;
}
}
return true;
}
bool Memory::IsBadReadPtr(const void* ptr)
{
MEMORY_BASIC_INFORMATION mbi = { nullptr };
if (VirtualQuery(ptr, &mbi, sizeof(mbi)))
{
DWORD mask = (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY);
bool b = !(mbi.Protect & mask);
// check the page is not a guard page
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true;
return b;
}
return true;
}
bool Memory::IsBadCodePtr(const void* ptr)
{
MEMORY_BASIC_INFORMATION mbi = { nullptr };
if (VirtualQuery(ptr, &mbi, sizeof(mbi)))
{
DWORD mask = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY);
bool b = !(mbi.Protect & mask);
// check the page is not a guard page
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true;
return b;
}
return true;
}
Utils::Memory::Allocator* Memory::GetAllocator()
{
return &Memory::MemAllocator;
}
}

163
src/Utils/Memory.hpp Normal file
View File

@ -0,0 +1,163 @@
#pragma once
namespace Utils
{
class Memory
{
public:
class Allocator
{
public:
typedef void(*FreeCallback)(void*);
Allocator()
{
this->pool.clear();
this->refMemory.clear();
}
~Allocator()
{
this->clear();
}
void clear()
{
std::lock_guard<std::mutex> _(this->mutex);
for (auto i = this->refMemory.begin(); i != this->refMemory.end(); ++i)
{
if (i->first && i->second)
{
i->second(i->first);
}
}
this->refMemory.clear();
for (auto& data : this->pool)
{
Memory::Free(data);
}
this->pool.clear();
}
void free(void* data)
{
std::lock_guard<std::mutex> _(this->mutex);
auto i = this->refMemory.find(data);
if (i != this->refMemory.end())
{
i->second(i->first);
this->refMemory.erase(i);
}
auto j = std::find(this->pool.begin(), this->pool.end(), data);
if (j != this->pool.end())
{
Memory::Free(data);
this->pool.erase(j);
}
}
void free(const void* data)
{
this->free(const_cast<void*>(data));
}
void reference(void* memory, FreeCallback callback)
{
std::lock_guard<std::mutex> _(this->mutex);
this->refMemory[memory] = callback;
}
void* allocate(size_t length)
{
std::lock_guard<std::mutex> _(this->mutex);
void* data = Memory::Allocate(length);
this->pool.push_back(data);
return data;
}
template <typename T> inline T* allocate()
{
return this->allocateArray<T>(1);
}
template <typename T> inline T* allocateArray(size_t count = 1)
{
return static_cast<T*>(this->allocate(count * sizeof(T)));
}
bool empty()
{
return (this->pool.empty() && this->refMemory.empty());
}
char* duplicateString(const std::string& string)
{
std::lock_guard<std::mutex> _(this->mutex);
char* data = Memory::DuplicateString(string);
this->pool.push_back(data);
return data;
}
bool isPointerMapped(void* ptr)
{
return this->ptrMap.find(ptr) != this->ptrMap.end();
}
template <typename T> T* getPointer(void* oldPtr)
{
if (this->isPointerMapped(oldPtr))
{
return reinterpret_cast<T*>(this->ptrMap[oldPtr]);
}
return nullptr;
}
void mapPointer(void* oldPtr, void* newPtr)
{
this->ptrMap[oldPtr] = newPtr;
}
private:
std::mutex mutex;
std::vector<void*> pool;
std::unordered_map<void*, void*> ptrMap;
std::unordered_map<void*, FreeCallback> refMemory;
};
static void* AllocateAlign(size_t length, size_t alignment);
static void* Allocate(size_t length);
template <typename T> static inline T* Allocate()
{
return AllocateArray<T>(1);
}
template <typename T> static inline T* AllocateArray(size_t count = 1)
{
return static_cast<T*>(Allocate(count * sizeof(T)));
}
static char* DuplicateString(const std::string& string);
static void Free(void* data);
static void Free(const void* data);
static void FreeAlign(void* data);
static void FreeAlign(const void* data);
static bool IsSet(void* mem, char chr, size_t length);
static bool IsBadReadPtr(const void* ptr);
static bool IsBadCodePtr(const void* ptr);
static Utils::Memory::Allocator* GetAllocator();
private:
static Utils::Memory::Allocator MemAllocator;
};
}

208
src/Utils/String.cpp Normal file
View File

@ -0,0 +1,208 @@
#include "STDInclude.hpp"
namespace Utils
{
namespace String
{
const char *VA(const char *fmt, ...)
{
static VAProvider<4, 100> globalProvider;
static thread_local VAProvider<8, 256> provider;
va_list ap;
va_start(ap, fmt);
const char* result;
if (Components::Loader::IsUninitializing()) result = globalProvider.get(fmt, ap);
else result = provider.get(fmt, ap);
va_end(ap);
return result;
}
std::string ToLower(std::string input)
{
std::transform(input.begin(), input.end(), input.begin(), ::tolower);
return input;
}
std::string ToUpper(std::string input)
{
std::transform(input.begin(), input.end(), input.begin(), ::toupper);
return input;
}
std::string DumpHex(const std::string& data, const std::string& separator)
{
std::string result;
for (unsigned int i = 0; i < data.size(); ++i)
{
if (i > 0)
{
result.append(separator);
}
result.append(Utils::String::VA("%02X", data[i] & 0xFF));
}
return result;
}
std::string XOR(std::string str, char value)
{
for (unsigned int i = 0; i < str.size(); ++i)
{
str[i] ^= value;
}
return str;
}
std::vector<std::string> Explode(const std::string& str, char delim)
{
std::vector<std::string> result;
std::istringstream iss(str);
for (std::string token; std::getline(iss, token, delim);)
{
std::string _entry = std::move(token);
// Remove trailing 0x0 bytes
while (_entry.size() && !_entry.back())
{
_entry = _entry.substr(0, _entry.size() - 1);
}
result.push_back(_entry);
}
return result;
}
void Replace(std::string &string, const std::string& find, const std::string& replace)
{
size_t nPos = 0;
while ((nPos = string.find(find, nPos)) != std::string::npos)
{
string = string.replace(nPos, find.length(), replace);
nPos += replace.length();
}
}
bool StartsWith(const std::string& haystack, const std::string& needle)
{
return (haystack.size() >= needle.size() && haystack.substr(0, needle.size()) == needle);
}
bool EndsWith(const std::string& haystack, const std::string& needle)
{
return (haystack.size() >= needle.size() && haystack.substr(haystack.size() - needle.size()) == needle);
}
int IsSpace(int c)
{
if (c < -1) return 0;
return _isspace_l(c, nullptr);
}
// trim from start
std::string &LTrim(std::string &s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int val)
{
return !IsSpace(val);
}));
return s;
}
// trim from end
std::string &RTrim(std::string &s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](int val)
{
return !IsSpace(val);
}).base(), s.end());
return s;
}
// trim from both ends
std::string &Trim(std::string &s)
{
return LTrim(RTrim(s));
}
std::string FormatTimeSpan(int milliseconds)
{
int secondsTotal = milliseconds / 1000;
int seconds = secondsTotal % 60;
int minutesTotal = secondsTotal / 60;
int minutes = minutesTotal % 60;
int hoursTotal = minutesTotal / 60;
return Utils::String::VA("%02d:%02d:%02d", hoursTotal, minutes, seconds);
}
std::string FormatBandwidth(size_t bytes, int milliseconds)
{
static const char* sizes[] =
{
"B",
"KB",
"MB",
"GB",
"TB"
};
if (!milliseconds) return "0.00 B/s";
double bytesPerSecond = (1000.0 / milliseconds) * bytes;
int i;
for (i = 0; bytesPerSecond > 1000 && i < ARRAYSIZE(sizes); ++i) // 1024 or 1000?
{
bytesPerSecond /= 1000;
}
return Utils::String::VA("%.2f %s/s", static_cast<float>(bytesPerSecond), sizes[i]);
}
#ifdef ENABLE_BASE64
// Encodes a given string in Base64
std::string EncodeBase64(const char* input, const unsigned long inputSize)
{
unsigned long outlen = long(inputSize + (inputSize / 3.0) + 16);
unsigned char* outbuf = new unsigned char[outlen]; //Reserve output memory
base64_encode(reinterpret_cast<unsigned char*>(const_cast<char*>(input)), inputSize, outbuf, &outlen);
std::string ret(reinterpret_cast<char*>(outbuf), outlen);
delete[] outbuf;
return ret;
}
// Encodes a given string in Base64
std::string EncodeBase64(const std::string& input)
{
return EncodeBase64(input.data(), input.size());
}
#endif
#ifdef ENABLE_BASE128
// Encodes a given string in Base128
std::string EncodeBase128(const std::string& input)
{
base128 encoder;
void* inbuffer = const_cast<char*>(input.data());
char* buffer = encoder.encode(inbuffer, input.size());
/*
Interesting to see that the buffer returned by the encoder is not a standalone string copy
but instead is a pointer to the internal "encoded" field of the encoder. So if you deinitialize
the encoder that string will probably become garbage.
*/
std::string retval(buffer);
return retval;
}
#endif
}
}

100
src/Utils/String.hpp Normal file
View File

@ -0,0 +1,100 @@
#pragma once
namespace Utils
{
namespace String
{
template <size_t Buffers, size_t MinBufferSize>
class VAProvider
{
public:
static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0");
VAProvider() : currentBuffer(0) {}
~VAProvider() {}
const char* get(const char* format, va_list ap)
{
++this->currentBuffer %= ARRAYSIZE(this->stringPool);
auto entry = &this->stringPool[this->currentBuffer];
if (!entry->size || !entry->buffer)
{
throw std::runtime_error("String pool not initialized");
}
while (true)
{
int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap);
if (res > 0) break; // Success
if (res == 0) return ""; // Error
entry->doubleSize();
}
return entry->buffer;
}
private:
class Entry
{
public:
Entry(size_t _size = MinBufferSize) : size(_size), buffer(nullptr)
{
if (this->size < MinBufferSize) this->size = MinBufferSize;
this->allocate();
}
~Entry()
{
if (this->buffer) Utils::Memory::GetAllocator()->free(this->buffer);
this->size = 0;
this->buffer = nullptr;
}
void allocate()
{
if (this->buffer) Utils::Memory::GetAllocator()->free(this->buffer);
this->buffer = Utils::Memory::GetAllocator()->allocateArray<char>(this->size + 1);
}
void doubleSize()
{
this->size *= 2;
this->allocate();
}
size_t size;
char* buffer;
};
size_t currentBuffer;
Entry stringPool[Buffers];
};
const char *VA(const char *fmt, ...);
int IsSpace(int c);
std::string ToLower(std::string input);
std::string ToUpper(std::string input);
bool EndsWith(const std::string& haystack, const std::string& needle);
std::vector<std::string> Explode(const std::string& str, char delim);
void Replace(std::string &string, const std::string& find, const std::string& replace);
bool StartsWith(const std::string& haystack, const std::string& needle);
std::string &LTrim(std::string &s);
std::string &RTrim(std::string &s);
std::string &Trim(std::string &s);
std::string FormatTimeSpan(int milliseconds);
std::string FormatBandwidth(size_t bytes, int milliseconds);
std::string DumpHex(const std::string& data, const std::string& separator = " ");
std::string XOR(const std::string str, char value);
std::string EncodeBase64(const char* input, const unsigned long inputSize);
std::string EncodeBase64(const std::string& input);
std::string EncodeBase128(const std::string& input);
}
}