From b35949ddaf7ed2ba09de8c3778b22996d8a4f153 Mon Sep 17 00:00:00 2001 From: ineedbots Date: Mon, 28 Jun 2021 13:56:51 -0600 Subject: [PATCH] Overall cleanup --- src/Components/Loader.cpp | 141 +++++++++++++++++ src/Components/Loader.hpp | 62 ++++++++ src/Components/Modules/Flags.cpp | 56 +++++++ src/Components/Modules/Flags.hpp | 18 +++ src/Components/Modules/HelloWorld.cpp | 13 ++ src/Components/Modules/HelloWorld.hpp | 11 ++ src/Components/Modules/Logger.cpp | 96 ++++++++++++ src/Components/Modules/Logger.hpp | 26 ++++ src/Game/Game.cpp | 10 ++ src/Game/Game.hpp | 6 + src/Game/MP.cpp | 9 -- src/Game/MP.hpp | 6 - src/Game/MP/Game.cpp | 1 - src/Game/MP/Game.hpp | 0 src/Game/MP/Structs.hpp | 0 src/Game/Structs.hpp | 5 + src/Main.cpp | 2 +- src/STDInclude.hpp | 15 +- src/Utils/Memory.cpp | 105 +++++++++++++ src/Utils/Memory.hpp | 163 ++++++++++++++++++++ src/Utils/String.cpp | 208 ++++++++++++++++++++++++++ src/Utils/String.hpp | 100 +++++++++++++ 22 files changed, 1034 insertions(+), 19 deletions(-) create mode 100644 src/Components/Loader.cpp create mode 100644 src/Components/Loader.hpp create mode 100644 src/Components/Modules/Flags.cpp create mode 100644 src/Components/Modules/Flags.hpp create mode 100644 src/Components/Modules/HelloWorld.cpp create mode 100644 src/Components/Modules/HelloWorld.hpp create mode 100644 src/Components/Modules/Logger.cpp create mode 100644 src/Components/Modules/Logger.hpp create mode 100644 src/Game/Game.cpp create mode 100644 src/Game/Game.hpp delete mode 100644 src/Game/MP.cpp delete mode 100644 src/Game/MP.hpp delete mode 100644 src/Game/MP/Game.cpp delete mode 100644 src/Game/MP/Game.hpp delete mode 100644 src/Game/MP/Structs.hpp create mode 100644 src/Game/Structs.hpp create mode 100644 src/Utils/Memory.cpp create mode 100644 src/Utils/Memory.hpp create mode 100644 src/Utils/String.cpp create mode 100644 src/Utils/String.hpp diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp new file mode 100644 index 0000000..0414014 --- /dev/null +++ b/src/Components/Loader.cpp @@ -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 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::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); + } + } +} diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp new file mode 100644 index 0000000..0b38360 --- /dev/null +++ b/src/Components/Loader.hpp @@ -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 + static T* GetInstance() + { + for (auto& component : Loader::Components) + { + if (typeid(*component) == typeid(T)) + { + return reinterpret_cast(component); + } + } + + return nullptr; + } + + private: + static bool Pregame; + static bool Postgame; + static bool Uninitializing; + static std::vector Components; + }; +} diff --git a/src/Components/Modules/Flags.cpp b/src/Components/Modules/Flags.cpp new file mode 100644 index 0000000..45c98cc --- /dev/null +++ b/src/Components/Modules/Flags.cpp @@ -0,0 +1,56 @@ +#include "STDInclude.hpp" +#include "Flags.hpp" + +namespace Components +{ + std::vector 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(); + } +} diff --git a/src/Components/Modules/Flags.hpp b/src/Components/Modules/Flags.hpp new file mode 100644 index 0000000..823a5ee --- /dev/null +++ b/src/Components/Modules/Flags.hpp @@ -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 EnabledFlags; + + static void ParseFlags(); + }; +} diff --git a/src/Components/Modules/HelloWorld.cpp b/src/Components/Modules/HelloWorld.cpp new file mode 100644 index 0000000..d9fc498 --- /dev/null +++ b/src/Components/Modules/HelloWorld.cpp @@ -0,0 +1,13 @@ +#include "STDInclude.hpp" +#include "HelloWorld.hpp" + +namespace Components +{ + HelloWorld::HelloWorld() + { + } + + HelloWorld::~HelloWorld() + { + } +} diff --git a/src/Components/Modules/HelloWorld.hpp b/src/Components/Modules/HelloWorld.hpp new file mode 100644 index 0000000..d8cae95 --- /dev/null +++ b/src/Components/Modules/HelloWorld.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace Components +{ + class HelloWorld : public Component + { + public: + HelloWorld(); + ~HelloWorld(); + }; +} diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp new file mode 100644 index 0000000..7f65620 --- /dev/null +++ b/src/Components/Modules/Logger.cpp @@ -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(bufferSize); + + va_list ap = reinterpret_cast(const_cast(&message[1])); + //va_start(ap, *message); + _vsnprintf_s(buffer, bufferSize, bufferSize, *message, ap); + va_end(ap); + + return buffer; + } + + Logger::Logger() + { + } + + Logger::~Logger() + { + } +} diff --git a/src/Components/Modules/Logger.hpp b/src/Components/Modules/Logger.hpp new file mode 100644 index 0000000..8b3da15 --- /dev/null +++ b/src/Components/Modules/Logger.hpp @@ -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); + }; +} diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp new file mode 100644 index 0000000..856ac1d --- /dev/null +++ b/src/Game/Game.cpp @@ -0,0 +1,10 @@ +#include "STDInclude.hpp" + +namespace Game +{ + void Init(GAMEEXE) + { + + } +} + diff --git a/src/Game/Game.hpp b/src/Game/Game.hpp new file mode 100644 index 0000000..7a725be --- /dev/null +++ b/src/Game/Game.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace Game +{ + void Init(GAMEEXE); +} diff --git a/src/Game/MP.cpp b/src/Game/MP.cpp deleted file mode 100644 index e4ee6ef..0000000 --- a/src/Game/MP.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "STDInclude.hpp" - -namespace MP -{ - void PatchT4() - { - MessageBoxA(nullptr, "MP", "DEBUG", 0); - } -} diff --git a/src/Game/MP.hpp b/src/Game/MP.hpp deleted file mode 100644 index 2f3ddc8..0000000 --- a/src/Game/MP.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -namespace MP -{ - void PatchT4(); -} diff --git a/src/Game/MP/Game.cpp b/src/Game/MP/Game.cpp deleted file mode 100644 index 1196d52..0000000 --- a/src/Game/MP/Game.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "STDInclude.hpp" diff --git a/src/Game/MP/Game.hpp b/src/Game/MP/Game.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/Game/MP/Structs.hpp b/src/Game/MP/Structs.hpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp new file mode 100644 index 0000000..b7090a5 --- /dev/null +++ b/src/Game/Structs.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace Game +{ +} diff --git a/src/Main.cpp b/src/Main.cpp index e886cea..e2a3422 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -31,7 +31,7 @@ namespace Main // detect which executable's patches to apply DWORD dataStrData = Utils::Hook::Get(0x59B69C); if (dataStrData == 0x6C6C6143) - MP::PatchT4(); + Components::Loader::Initialize(GAMEEXE::MP); hModule = GetModuleHandle(NULL); PIMAGE_DOS_HEADER header = (PIMAGE_DOS_HEADER)hModule; diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index b72257f..d06bcd6 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #pragma warning(push) #pragma warning(disable: 4091) @@ -81,9 +82,19 @@ template 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") diff --git a/src/Utils/Memory.cpp b/src/Utils/Memory.cpp new file mode 100644 index 0000000..a69c117 --- /dev/null +++ b/src/Utils/Memory.cpp @@ -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(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(data)); + } + + void Memory::FreeAlign(void* data) + { + if (data) + { + _aligned_free(data); + } + } + + void Memory::FreeAlign(const void* data) + { + Memory::FreeAlign(const_cast(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(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; + } +} diff --git a/src/Utils/Memory.hpp b/src/Utils/Memory.hpp new file mode 100644 index 0000000..015e7f8 --- /dev/null +++ b/src/Utils/Memory.hpp @@ -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 _(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 _(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(data)); + } + + void reference(void* memory, FreeCallback callback) + { + std::lock_guard _(this->mutex); + + this->refMemory[memory] = callback; + } + + void* allocate(size_t length) + { + std::lock_guard _(this->mutex); + + void* data = Memory::Allocate(length); + this->pool.push_back(data); + return data; + } + template inline T* allocate() + { + return this->allocateArray(1); + } + template inline T* allocateArray(size_t count = 1) + { + return static_cast(this->allocate(count * sizeof(T))); + } + + bool empty() + { + return (this->pool.empty() && this->refMemory.empty()); + } + + char* duplicateString(const std::string& string) + { + std::lock_guard _(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 T* getPointer(void* oldPtr) + { + if (this->isPointerMapped(oldPtr)) + { + return reinterpret_cast(this->ptrMap[oldPtr]); + } + + return nullptr; + } + + void mapPointer(void* oldPtr, void* newPtr) + { + this->ptrMap[oldPtr] = newPtr; + } + + private: + std::mutex mutex; + std::vector pool; + std::unordered_map ptrMap; + std::unordered_map refMemory; + }; + + static void* AllocateAlign(size_t length, size_t alignment); + static void* Allocate(size_t length); + template static inline T* Allocate() + { + return AllocateArray(1); + } + template static inline T* AllocateArray(size_t count = 1) + { + return static_cast(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; + }; +} diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp new file mode 100644 index 0000000..ed3eb47 --- /dev/null +++ b/src/Utils/String.cpp @@ -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 Explode(const std::string& str, char delim) + { + std::vector 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 <rim(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(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(const_cast(input)), inputSize, outbuf, &outlen); + std::string ret(reinterpret_cast(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(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 + } +} diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp new file mode 100644 index 0000000..2b0ed6d --- /dev/null +++ b/src/Utils/String.hpp @@ -0,0 +1,100 @@ +#pragma once + +namespace Utils +{ + namespace String + { + template + 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(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 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 <rim(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); + } +}