Skip to content

Commit

Permalink
feat: improve logging performance, refactor code, and add console colors
Browse files Browse the repository at this point in the history
- perf: submit logs once per millisecond and in a separate thread
- refactor: overhaul the codebase with a major cleanup
- feat: add colors to the console output
  • Loading branch information
BenMcAvoy committed Jan 5, 2025
1 parent f239632 commit 058103f
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 233 deletions.
15 changes: 15 additions & 0 deletions CarbonLauncher/include/guimanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
#include <GLFW/glfw3.h>

namespace Carbon {
enum LogColour : WORD {
DARKGREEN = 2,
BLUE = 3,
PURPLE = 5,
GOLD = 6,
WHITE = 7,
DARKGRAY = 8,
DARKBLUE = 9,
GREEN = 10,
CYAN = 11,
RED = 12,
PINK = 13,
YELLOW = 14,
};

class GUIManager {
public:
// Initializes the GUI manager
Expand Down
1 change: 1 addition & 0 deletions CarbonLauncher/include/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Carbon {
struct LogMessage {
int colour;
std::string message;
std::string time;
};
Expand Down
42 changes: 41 additions & 1 deletion CarbonLauncher/src/guimanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,47 @@ void _GUI() {
auto& message = *it;
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), message.time.c_str());
ImGui::SameLine();
ImGui::TextWrapped(message.message.c_str());

switch (message.colour) {
case LogColour::DARKGREEN:
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), message.message.c_str());
break;
case LogColour::BLUE:
ImGui::TextColored(ImVec4(0.0f, 0.0f, 1.0f, 1.0f), message.message.c_str());
break;
case LogColour::PURPLE:
ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), message.message.c_str());
break;
case LogColour::GOLD:
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), message.message.c_str());
break;
case LogColour::WHITE:
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), message.message.c_str());
break;
case LogColour::DARKGRAY:
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), message.message.c_str());
break;
case LogColour::DARKBLUE:
ImGui::TextColored(ImVec4(0.0f, 0.0f, 0.5f, 1.0f), message.message.c_str());
break;
case LogColour::GREEN:
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), message.message.c_str());
break;
case LogColour::CYAN:
ImGui::TextColored(ImVec4(0.0f, 1.0f, 1.0f, 1.0f), message.message.c_str());
break;
case LogColour::RED:
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), message.message.c_str());
break;
case LogColour::PINK:
ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), message.message.c_str());
break;
case LogColour::YELLOW:
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), message.message.c_str());
break;
}

//ImGui::TextWrapped(message.message.c_str());
}

// If we were scrolled to the bottom, scroll down
Expand Down
6 changes: 6 additions & 0 deletions CarbonLauncher/src/pipemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ PipeManager::PipeManager() {

std::string time = fmt::format("{:02}:{:02}:{:02}", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);

// Split on -|- to get the log colour
auto colourDelimiter = data.find("-|-");
auto colour = data.substr(0, colourDelimiter);
data = data.substr(colourDelimiter + 3);

LogMessage logMessage;
logMessage.colour = std::stoi(colour);
logMessage.message = data;
logMessage.time = time;
C.logMessages.emplace_back(logMessage);
Expand Down
9 changes: 8 additions & 1 deletion CarbonSupervisor/CarbonSupervisor.vcxproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
Expand Down Expand Up @@ -158,7 +158,14 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\console.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\sender.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\globals.h" />
<ClInclude Include="include\sm.h" />
<ClInclude Include="include\utils.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
22 changes: 22 additions & 0 deletions CarbonSupervisor/include/globals.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <string>
#include <queue>

struct LogMessage {
WORD colour;
std::string message;
};

class Globals_t {
public:
std::queue<LogMessage> logMessages = {};

static Globals_t* GetInstance() {
static Globals_t instance;
return &instance;
}
};
77 changes: 77 additions & 0 deletions CarbonSupervisor/include/sm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#pragma once

#define WIN32_LEAN_AND_MEAN

#include <string>
#include <Windows.h>

#include <spdlog/spdlog.h>

enum ContraptionState {
LOADING = 1,
IN_GAME = 2,
MAIN_MENU = 3
};

namespace SM {
const uintptr_t ContraptionOffset = 0x1267538;

struct LogMessage {
int colour;
std::string message;
};

class Console {
virtual ~Console() {}
virtual void Log(const std::string&, WORD colour, WORD LogType) = 0;
virtual void LogNoRepeat(const std::string&, WORD colour, WORD LogType) = 0;

public:
void Hook();
};

class Contraption {
private:
/* 0x0000 */ char pad_0x0000[0x58];
public:
/* 0x0058 */ Console* console;
private:
/* 0x0060 */ char pad_0x0060[0x11C];
public:
/* 0x017C */ int state;

public:
static Contraption* GetInstance() {
// TODO sig scan for this (90 48 89 05 ? ? ? ? + 0x4 @ Contraption)
auto contraption = *reinterpret_cast<Contraption**>((uintptr_t)GetModuleHandle(nullptr) + ContraptionOffset);
while (contraption == nullptr || contraption->state < LOADING || contraption->state > 3) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
contraption = SM::Contraption::GetInstance();

static int i = 0;
if (i++ % 30 == 0) { // Every 3 seconds
spdlog::warn("Waiting for Contraption...");
}
}

return contraption;
}

void WaitForStateEgress(ContraptionState state) const {
while (this->state == (int)state) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}

void OnStateChange(std::function<void(ContraptionState)> callback) const {
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
static int lastState = 0;
if (lastState != this->state) {
lastState = this->state;
callback((ContraptionState)lastState);
}
}
}
};
}
41 changes: 41 additions & 0 deletions CarbonSupervisor/include/utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>

enum PacketType {
LOADED,
STATECHANGE,
LOG,
UNKNOWNTYPE
};

namespace Utils {
void InitLogging();

class Pipe {
public:
Pipe() = default;

static Pipe* GetInstance() {
static Pipe instance;
return &instance;
}

// Delete copy constructor and assignment operator
Pipe(Pipe const&) = delete;
void operator=(Pipe const&) = delete;

void ResetPipe(bool reInformLauncher = false);

void SendPacket(PacketType packetType, const std::string& data);
void SendPacket(PacketType packetType, int data);
void SendPacket(PacketType packetType);

void ValidatePipe();

private:
std::mutex logMutex{};
HANDLE pipe = INVALID_HANDLE_VALUE;
};
} // namespace Utils
62 changes: 62 additions & 0 deletions CarbonSupervisor/src/console.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "sm.h"
#include "utils.h"
#include "globals.h"

using LogFunction = void(SM::Console::*)(const std::string&, WORD, WORD);
LogFunction oLogFunction;
LogFunction oLogNoRepeatFunction;

static void LogPacket(const std::string& message, WORD colour, WORD type) {
static Utils::Pipe* pipe = Utils::Pipe::GetInstance();
pipe->SendPacket(LOG, fmt::format("{}-|-{}", colour, message));
}

static void HookedLog(SM::Console* console, const std::string& message, WORD colour, WORD type) {
static Globals_t* globals = Globals_t::GetInstance();
globals->logMessages.push({ colour, message });
(console->*oLogFunction)(message, colour, type);
}

static void HookVTableFunc(void** vtable, int index, void* newFunction, void** originalFunction) {
DWORD oldProtect;
VirtualProtect(&vtable[index], sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProtect);
*originalFunction = vtable[index];
vtable[index] = newFunction;
VirtualProtect(&vtable[index], sizeof(void*), oldProtect, &oldProtect);
}

void SM::Console::Hook() {
spdlog::info("Hooking console functions");

// Hook the consoles `Log` and `LogNoRepeat` functions (to the same thing)
// Make them send a packet to the pipe with LOG:-:<message>
void** vtable = *reinterpret_cast<void***>(this);
HookVTableFunc(vtable, 1, HookedLog, reinterpret_cast<void**>(&oLogFunction));
HookVTableFunc(vtable, 2, HookedLog, nullptr);

// Start a thread to send log messages to the launcher
std::thread([]() {
auto pipe = Utils::Pipe::GetInstance();
static Globals_t* globals = Globals_t::GetInstance();
auto& logMessages = globals->logMessages;

while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (!logMessages.empty()) {
auto& message = logMessages.front();
pipe->SendPacket(LOG, fmt::format("{}-|-{}", message.colour, message.message));
logMessages.pop();
}
}
}).detach();
}

void Utils::InitLogging() {
AllocConsole();
freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout);

auto console = spdlog::stdout_color_mt("carbon");
spdlog::set_default_logger(console);
spdlog::set_level(spdlog::level::trace);
spdlog::set_pattern("%^[ %H:%M:%S | %-8l] %n: %v%$");
}
Loading

0 comments on commit 058103f

Please sign in to comment.