From 66cca7eb8487e36bfea293ce05a7ca6f4dd4d319 Mon Sep 17 00:00:00 2001 From: Ben McAvoy Date: Wed, 11 Dec 2024 01:56:50 +0000 Subject: [PATCH] refactor(wip): rebuild project with structure --- CarbonLauncher.sln | 42 +- CarbonLauncher/CarbonLauncher.vcxproj | 40 +- CarbonLauncher/include/discordmanager.h | 24 ++ CarbonLauncher/include/gamemanager.h | 31 ++ CarbonLauncher/include/guimanager.h | 29 ++ CarbonLauncher/include/pipemanager.h | 45 ++ CarbonLauncher/include/state.h | 18 + CarbonLauncher/include/utils.h | 55 --- CarbonLauncher/src/discordmanager.cpp | 89 ++++ CarbonLauncher/src/gamemanager.cpp | 80 ++++ CarbonLauncher/src/guimanager.cpp | 157 +++++++ CarbonLauncher/src/main.cpp | 503 ++-------------------- CarbonLauncher/src/pipemanager.cpp | 83 ++++ CarbonLauncher/src/state.cpp | 3 + CarbonLauncher/src/utils.cpp | 248 ----------- CarbonLauncher/vcpkg.json | 2 +- CarbonSupervisor/CarbonSupervisor.vcxproj | 139 ------ CarbonSupervisor/src/main.cpp | 126 ------ CarbonSupervisor/vcpkg-configuration.json | 14 - CarbonSupervisor/vcpkg.json | 1 - DummyGame/DummyGame.vcxproj | 150 ------- DummyGame/src/main.cpp | 100 ----- DummyGame/vcpkg-configuration.json | 14 - DummyGame/vcpkg.json | 6 - 24 files changed, 620 insertions(+), 1379 deletions(-) create mode 100644 CarbonLauncher/include/discordmanager.h create mode 100644 CarbonLauncher/include/gamemanager.h create mode 100644 CarbonLauncher/include/guimanager.h create mode 100644 CarbonLauncher/include/pipemanager.h create mode 100644 CarbonLauncher/include/state.h delete mode 100644 CarbonLauncher/include/utils.h create mode 100644 CarbonLauncher/src/discordmanager.cpp create mode 100644 CarbonLauncher/src/gamemanager.cpp create mode 100644 CarbonLauncher/src/guimanager.cpp create mode 100644 CarbonLauncher/src/pipemanager.cpp create mode 100644 CarbonLauncher/src/state.cpp delete mode 100644 CarbonLauncher/src/utils.cpp delete mode 100644 CarbonSupervisor/CarbonSupervisor.vcxproj delete mode 100644 CarbonSupervisor/src/main.cpp delete mode 100644 CarbonSupervisor/vcpkg-configuration.json delete mode 100644 CarbonSupervisor/vcpkg.json delete mode 100644 DummyGame/DummyGame.vcxproj delete mode 100644 DummyGame/src/main.cpp delete mode 100644 DummyGame/vcpkg-configuration.json delete mode 100644 DummyGame/vcpkg.json diff --git a/CarbonLauncher.sln b/CarbonLauncher.sln index 1ce08f2..aec51f1 100644 --- a/CarbonLauncher.sln +++ b/CarbonLauncher.sln @@ -1,13 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.13.35507.96 +VisualStudioVersion = 17.13.35507.96 d17.13 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CarbonLauncher", "CarbonLauncher\CarbonLauncher.vcxproj", "{E5A80EF0-1C60-4D13-8569-03CF1794CDB6}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CarbonSupervisor", "CarbonSupervisor\CarbonSupervisor.vcxproj", "{1F14E774-5A60-4742-9534-712E254FAA60}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DummyGame", "DummyGame\DummyGame.vcxproj", "{A56AC389-85AD-4E6F-9E64-E859DD676099}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CarbonLauncher", "CarbonLauncher\CarbonLauncher.vcxproj", "{F55718C8-63D5-4CCA-8499-07500910C9BC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,35 +13,19 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Debug|x64.ActiveCfg = Debug|x64 - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Debug|x64.Build.0 = Debug|x64 - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Debug|x86.ActiveCfg = Debug|Win32 - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Debug|x86.Build.0 = Debug|Win32 - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Release|x64.ActiveCfg = Release|x64 - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Release|x64.Build.0 = Release|x64 - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Release|x86.ActiveCfg = Release|Win32 - {E5A80EF0-1C60-4D13-8569-03CF1794CDB6}.Release|x86.Build.0 = Release|Win32 - {1F14E774-5A60-4742-9534-712E254FAA60}.Debug|x64.ActiveCfg = Debug|x64 - {1F14E774-5A60-4742-9534-712E254FAA60}.Debug|x64.Build.0 = Debug|x64 - {1F14E774-5A60-4742-9534-712E254FAA60}.Debug|x86.ActiveCfg = Debug|Win32 - {1F14E774-5A60-4742-9534-712E254FAA60}.Debug|x86.Build.0 = Debug|Win32 - {1F14E774-5A60-4742-9534-712E254FAA60}.Release|x64.ActiveCfg = Release|x64 - {1F14E774-5A60-4742-9534-712E254FAA60}.Release|x64.Build.0 = Release|x64 - {1F14E774-5A60-4742-9534-712E254FAA60}.Release|x86.ActiveCfg = Release|Win32 - {1F14E774-5A60-4742-9534-712E254FAA60}.Release|x86.Build.0 = Release|Win32 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Debug|x64.ActiveCfg = Debug|x64 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Debug|x64.Build.0 = Debug|x64 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Debug|x86.ActiveCfg = Debug|Win32 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Debug|x86.Build.0 = Debug|Win32 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Release|x64.ActiveCfg = Release|x64 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Release|x64.Build.0 = Release|x64 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Release|x86.ActiveCfg = Release|Win32 - {A56AC389-85AD-4E6F-9E64-E859DD676099}.Release|x86.Build.0 = Release|Win32 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Debug|x64.ActiveCfg = Debug|x64 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Debug|x64.Build.0 = Debug|x64 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Debug|x86.ActiveCfg = Debug|Win32 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Debug|x86.Build.0 = Debug|Win32 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Release|x64.ActiveCfg = Release|x64 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Release|x64.Build.0 = Release|x64 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Release|x86.ActiveCfg = Release|Win32 + {F55718C8-63D5-4CCA-8499-07500910C9BC}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {0197DFC4-D5CF-4C67-AEF4-436F47231050} + SolutionGuid = {5C1163F1-0D28-415C-BAC3-80138FA2F668} EndGlobalSection EndGlobal diff --git a/CarbonLauncher/CarbonLauncher.vcxproj b/CarbonLauncher/CarbonLauncher.vcxproj index ae91bcc..dcb13db 100644 --- a/CarbonLauncher/CarbonLauncher.vcxproj +++ b/CarbonLauncher/CarbonLauncher.vcxproj @@ -18,17 +18,10 @@ x64 - - - - - - - 17.0 Win32Proj - {e5a80ef0-1c60-4d13-8569-03cf1794cdb6} + {f55718c8-63d5-4cca-8499-07500910c9bc} CarbonLauncher 10.0 @@ -37,27 +30,27 @@ Application true v143 - MultiByte + Unicode Application false v143 true - MultiByte + Unicode Application true v143 - MultiByte + Unicode Application false v143 true - MultiByte + Unicode @@ -91,7 +84,7 @@ /utf-8 %(AdditionalOptions) - Console + Windows true @@ -108,7 +101,7 @@ /utf-8 %(AdditionalOptions) - Console + Windows true true true @@ -125,7 +118,7 @@ /utf-8 %(AdditionalOptions) - Console + Windows true @@ -142,12 +135,27 @@ /utf-8 %(AdditionalOptions) - Console + Windows true true true + + + + + + + + + + + + + + + diff --git a/CarbonLauncher/include/discordmanager.h b/CarbonLauncher/include/discordmanager.h new file mode 100644 index 0000000..73d18b2 --- /dev/null +++ b/CarbonLauncher/include/discordmanager.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include + +namespace Carbon { + class DiscordManager { + public: + DiscordManager(); + ~DiscordManager(); + + void UpdateActivity() const; + discord::Activity& GetActivity(); + + void Update(); + + discord::User currentUser = discord::User{}; + discord::Activity currentActivity = discord::Activity{}; + + std::unique_ptr core = nullptr; + }; +}; // namespace Carbon diff --git a/CarbonLauncher/include/gamemanager.h b/CarbonLauncher/include/gamemanager.h new file mode 100644 index 0000000..ec58e5d --- /dev/null +++ b/CarbonLauncher/include/gamemanager.h @@ -0,0 +1,31 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define GLFW_INCLUDE_NONE + +#include +#include + +namespace Carbon { + class GameManager { + public: + GameManager(); + ~GameManager(); + + bool IsGameRunning(); + + void StartGame(); + void KillGame(); + + // TODO: Send a message to the + // supervisor to kill the game + // void KillGame(); + + private: + // Checks every 1s if the game is running + std::thread gameStatusThread; + std::mutex gameStatusMutex; + + bool gameRunning = false; + }; +}; // namespace Carbon diff --git a/CarbonLauncher/include/guimanager.h b/CarbonLauncher/include/guimanager.h new file mode 100644 index 0000000..ef22944 --- /dev/null +++ b/CarbonLauncher/include/guimanager.h @@ -0,0 +1,29 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define GLFW_INCLUDE_NONE + +#include + +#include + +#include + +namespace Carbon { + class GUIManager { + public: + GUIManager(HINSTANCE hInstance); + ~GUIManager(); + + void RenderCallback(std::function callback) { + this->renderCallback = callback; + } + + void Run(); + + GLFWwindow* window; + std::function renderCallback; + }; +}; // namespace Carbon + +void _GUI(); diff --git a/CarbonLauncher/include/pipemanager.h b/CarbonLauncher/include/pipemanager.h new file mode 100644 index 0000000..f424b17 --- /dev/null +++ b/CarbonLauncher/include/pipemanager.h @@ -0,0 +1,45 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include +#include +#include + +namespace Carbon { + enum class PacketType { + LOADED, // Sent when the game is loaded + STATECHANGE, // Sent when the game state changes (e.g. menu -> game) + LOG, // (e.g.game log [will be implemented later]) + UNKNOWNTYPE // Unknown packet type + }; + + class Packet { + public: + PacketType type = PacketType::UNKNOWNTYPE; + std::optional data; + }; + + class PipeManager { + public: + PipeManager(); + ~PipeManager(); + + bool pipeInitialized = false; + + std::mutex pipeMutex; + std::queue packets = {}; + + std::queue& GetPackets() { + std::lock_guard lock(this->pipeMutex); + return this->packets; + } + + private: + std::thread pipeReader; + }; +}; // namespace Carbon diff --git a/CarbonLauncher/include/state.h b/CarbonLauncher/include/state.h new file mode 100644 index 0000000..8dd4046 --- /dev/null +++ b/CarbonLauncher/include/state.h @@ -0,0 +1,18 @@ +#pragma once + +#include "guimanager.h" +#include "discordmanager.h" +#include "gamemanager.h" +#include "pipemanager.h" + +namespace Carbon { + class CarbonState_t { + public: + Carbon::GUIManager* guiManager; + Carbon::DiscordManager* discordManager; + Carbon::GameManager* gameManager; + Carbon::PipeManager* pipeManager; + }; +}; // namespace Carbon + +extern Carbon::CarbonState_t C; diff --git a/CarbonLauncher/include/utils.h b/CarbonLauncher/include/utils.h deleted file mode 100644 index 33e437b..0000000 --- a/CarbonLauncher/include/utils.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include - -#define WIN32_LEAN_AND_MEAN -#include - -#include -#include - -#undef CreateWindow - -static HWND g_hWnd = nullptr; - -/* - * Get the last error message as a string. - * - * @return The last error message as a string. - */ -std::string GetLastErrorAsString(); - -/* - * Get the process ID of a process by name. - * - * @param processName The name of the process to get the ID of. - * @return The process ID of the process with the given name, or 0 if the process was not found. - */ -DWORD GetProcID(const std::string& processName); - -/* - * Inject a DLL into a process. - * - * @param targetPID The process ID of the target process. - * @param window The window to set as the foreground window after injection. - * @return True if the DLL was successfully injected, false otherwise. - */ -[[nodiscard]] bool Inject(DWORD targetPID, HWND window, const std::string& toLoad); - -/* - * Enumerate windows callback function. - * - * @param hWnd The window handle. - * @param lParam The process ID to compare against. - * @return False if the process ID matches, true otherwise. - */ -BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam); - -std::string GetExeDirectory(); - -GLFWwindow* CreateWindow(int width, int height, const char* title); -void InitImGui(GLFWwindow* window); - -void Render(GLFWwindow* window, std::function renderFunc); - -bool IsGameOpen(const char* name); diff --git a/CarbonLauncher/src/discordmanager.cpp b/CarbonLauncher/src/discordmanager.cpp new file mode 100644 index 0000000..6ac9d6e --- /dev/null +++ b/CarbonLauncher/src/discordmanager.cpp @@ -0,0 +1,89 @@ +#include "discordmanager.h" +#include "guimanager.h" + +#include + +constexpr auto discordClientId = 1315436867545595904; + +using namespace Carbon; + +DiscordManager::DiscordManager() { + discord::Core* core{}; + auto result = discord::Core::Create(discordClientId, DiscordCreateFlags_NoRequireDiscord, &core); + + this->core.reset(core); + + if (!this->core) { + spdlog::warn("Failed to create Discord core"); + } + + else { + auto dCore = this->core.get(); + + dCore->SetLogHook(discord::LogLevel::Debug, [](discord::LogLevel level, const char* message) { + spdlog::debug("[Discord] {}", message); + }); + + dCore->SetLogHook(discord::LogLevel::Info, [](discord::LogLevel level, const char* message) { + spdlog::info("[Discord] {}", message); + }); + + dCore->SetLogHook(discord::LogLevel::Warn, [](discord::LogLevel level, const char* message) { + spdlog::warn("[Discord] {}", message); + }); + + dCore->SetLogHook(discord::LogLevel::Error, [](discord::LogLevel level, const char* message) { + spdlog::error("[Discord] {}", message); + }); + + this->core->ActivityManager().RegisterCommand("carbonlauncher://run"); + + this->currentActivity = {}; + auto& activity = this->GetActivity(); + + activity.SetDetails("The latest modded launcher for Scrap Mechanic!"); + activity.SetState("Not in game... https://github.com/ScrappySM/CarbonLauncher!"); + + activity.GetAssets().SetLargeImage("carbonlauncher"); + activity.GetAssets().SetLargeText("Carbon Launcher"); + activity.GetAssets().SetSmallImage("carbonlauncher"); + activity.GetAssets().SetSmallText("Carbon Launcher"); + + activity.SetType(discord::ActivityType::Playing); + + activity.SetSupportedPlatforms((uint32_t)discord::ActivitySupportedPlatformFlags::Desktop); + + this->core->ActivityManager().UpdateActivity(activity, [](discord::Result result) { + if (result != discord::Result::Ok) { + spdlog::error("Failed to update Discord RPC (error: {})", (int)result); + } + }); + } + + + spdlog::info("Created Discord instance"); +} + +DiscordManager::~DiscordManager() { + spdlog::info("Destroying Discord instance"); +} + +void DiscordManager::UpdateActivity() const { + spdlog::info("Updating activity"); + + this->core->ActivityManager().UpdateActivity(this->currentActivity, [](discord::Result result) { + if (result != discord::Result::Ok) { + spdlog::error("Failed to update activity"); + } + }); +} + +discord::Activity& DiscordManager::GetActivity() { + spdlog::info("Getting activity"); + + return this->currentActivity; +} + +void DiscordManager::Update() { + this->core->RunCallbacks(); +} \ No newline at end of file diff --git a/CarbonLauncher/src/gamemanager.cpp b/CarbonLauncher/src/gamemanager.cpp new file mode 100644 index 0000000..a04e57e --- /dev/null +++ b/CarbonLauncher/src/gamemanager.cpp @@ -0,0 +1,80 @@ +#include "gamemanager.h" + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include + +using namespace Carbon; + +GameManager::GameManager() { + this->gameStatusThread = std::thread([this]() { + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + continue; + } + + PROCESSENTRY32 entry{}; + entry.dwSize = sizeof(entry); + + if (!Process32First(snapshot, &entry)) { + CloseHandle(snapshot); + continue; + } + + bool found = false; + do { + if (std::wstring(entry.szExeFile) == L"ScrapMechanic.exe") { + found = true; + break; + } + } while (Process32Next(snapshot, &entry)); + + CloseHandle(snapshot); + + std::lock_guard lock(this->gameStatusMutex); + this->gameRunning = found; + } + }); +} + +GameManager::~GameManager() { + this->gameStatusThread.join(); +} + +bool GameManager::IsGameRunning() { + std::lock_guard lock(this->gameStatusMutex); + return this->gameRunning; +} + +void GameManager::StartGame() { + // steam://rungameid/387990 + ShellExecute(NULL, L"open", L"steam://rungameid/387990", NULL, NULL, SW_SHOWNORMAL); +} + +void GameManager::KillGame() { + auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + return; + } + PROCESSENTRY32 entry{}; + entry.dwSize = sizeof(entry); + if (!Process32First(snapshot, &entry)) { + CloseHandle(snapshot); + return; + } + do { + if (std::wstring(entry.szExeFile) == L"ScrapMechanic.exe") { + HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID); + if (process) { + TerminateProcess(process, 0); + CloseHandle(process); + } + } + } while (Process32Next(snapshot, &entry)); + CloseHandle(snapshot); +} diff --git a/CarbonLauncher/src/guimanager.cpp b/CarbonLauncher/src/guimanager.cpp new file mode 100644 index 0000000..a1457d2 --- /dev/null +++ b/CarbonLauncher/src/guimanager.cpp @@ -0,0 +1,157 @@ +#pragma comment(lib, "dwmapi.lib") +#define GLFW_INCLUDE_NONE +#define GLFW_EXPOSE_NATIVE_WIN32 +#define WIN32_LEAN_AND_MEAN + +#include "guimanager.h" +#include "state.h" + +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +using namespace Carbon; + +GUIManager::GUIManager(HINSTANCE hInstance) { + // Initialize GLFW + if (!glfwInit()) { + MessageBox(NULL, L"GLFW Initialization Failed!", L"Error!", MB_ICONEXCLAMATION | MB_OK); + return; + } + + glfwSetErrorCallback([](int error, const char* description) { + spdlog::error("GLFW Error {}: {}", error, description); + }); + + // Create the OpenGL context + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // Create the GLFW window + this->window = glfwCreateWindow(1280, 720, "Carbon Launcher", NULL, NULL); + if (!this->window) { + spdlog::error("GLFW Window Creation Failed!"); + glfwTerminate(); + return; + } + + glfwMakeContextCurrent(this->window); + + // Initialize GLAD + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + spdlog::error("GLAD Initialization Failed!"); + glfwTerminate(); + return; + } + + // Enable dark mode (Windows only) + BOOL darkMode = TRUE; + HWND hWnd = glfwGetWin32Window(this->window); + DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &darkMode, sizeof(darkMode)); + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + ImGui::StyleColorsDark(); + + io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 18.0f); + + ImGui_ImplGlfw_InitForOpenGL(this->window, true); + ImGui_ImplOpenGL3_Init("#version 460"); + glfwSwapInterval(1); +} + +GUIManager::~GUIManager() { + // Terminate GLFW + glfwTerminate(); + + // Terminate ImGui + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); +} + +void GUIManager::Run() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + if (this->renderCallback) { + this->renderCallback(); + } + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(window); + } +} + +void _GUI() { + using namespace Carbon; + + C.discordManager->Update(); + + // Begin main menu bar + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Exit")) { + glfwSetWindowShouldClose(C.guiManager->window, GLFW_TRUE); + } + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + + int w, h; + glfwGetWindowSize(C.guiManager->window, &w, &h); + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2((float)w, (float)h)); + ImGui::Begin("Carbon Launcher", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_MenuBar); + + // Begin tabs + if (ImGui::BeginTabBar("CarbonTabs", ImGuiTabBarFlags_None)) { + // Begin the first tab + if (ImGui::BeginTabItem("Home")) { + if (ImGui::Button("Start Game")) { + C.gameManager->StartGame(); + } + if (ImGui::Button("Kill Game")) { + C.gameManager->KillGame(); + } + + ImGui::Text("Game Running: %s", C.gameManager->IsGameRunning() ? "true" : "false"); + ImGui::Text("Packets received: %d", C.pipeManager->GetPackets().size()); + + ImGui::EndTabItem(); + } + // Begin the second tab + if (ImGui::BeginTabItem("Settings")) { + ImGui::Text("Settings"); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + + ImGui::End(); +} diff --git a/CarbonLauncher/src/main.cpp b/CarbonLauncher/src/main.cpp index a819f69..89da2af 100644 --- a/CarbonLauncher/src/main.cpp +++ b/CarbonLauncher/src/main.cpp @@ -1,481 +1,28 @@ -#define GLFW_INCLUDE_NONE -#define GLFW_EXPOSE_NATIVE_WIN32 -#define WIN32_LEAN_AND_MEAN -#pragma comment(lib, "dwmapi.lib") - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include - -#undef CreateWindow -#include "utils.h" - -// Discord game SDK -#include -#include - -#define USE_DUMMY_GAME - -struct State_t { - std::vector enabledMods; - -struct DiscordState_t { - discord::User currentUser; - discord::Activity currentActivity; - - std::unique_ptr core; -} DiscordState; -}; - -State_t State; - -constexpr auto discordClientId = 1315436867545595904; - - -int main(int argc, char* argv[]) { - // Check for other processes (CarbonLauncher.exe) - auto snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (snap == INVALID_HANDLE_VALUE) { - spdlog::error("Failed to create snapshot"); - return -1; - } - - PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; - if (!Process32First(snap, &pe32)) { - spdlog::error("Failed to get first process"); - return -1; - } - - do { - if (strcmp(pe32.szExeFile, "CarbonLauncher.exe") == 0) { - static int count = 0; - count++; - - if (count > 1) { - spdlog::error("Another instance of Carbon Launcher is already running"); - return -1; - } - } - } while (Process32Next(snap, &pe32)); - - State.DiscordState = {}; - - discord::Core* core{}; - auto result = discord::Core::Create(discordClientId, DiscordCreateFlags_NoRequireDiscord, &core); - State.DiscordState.core.reset(core); - if (!State.DiscordState.core) { - spdlog::warn("Failed to create Discord core"); - //std::exit(-1); - } - else { - auto dCore = State.DiscordState.core.get(); - - dCore->SetLogHook(discord::LogLevel::Debug, [](discord::LogLevel level, const char* message) { - spdlog::debug("[Discord] {}", message); - }); - - dCore->SetLogHook(discord::LogLevel::Info, [](discord::LogLevel level, const char* message) { - spdlog::info("[Discord] {}", message); - }); - - dCore->SetLogHook(discord::LogLevel::Warn, [](discord::LogLevel level, const char* message) { - spdlog::warn("[Discord] {}", message); - }); - - dCore->SetLogHook(discord::LogLevel::Error, [](discord::LogLevel level, const char* message) { - spdlog::error("[Discord] {}", message); - }); - - State.DiscordState.core->ActivityManager().RegisterCommand("carbonlauncher://run"); - - // Show Carbon Launcher as RPC - State.DiscordState.currentActivity = {}; - auto& activity = State.DiscordState.currentActivity; - activity.SetDetails("In the launcher"); - activity.SetState("The latest SM modded launcher"); - activity.GetAssets().SetLargeText("Carbon Launcher"); - activity.GetAssets().SetSmallImage("carbon_launcher"); - activity.SetType(discord::ActivityType::Playing); - - activity.SetSupportedPlatforms((uint32_t)discord::ActivitySupportedPlatformFlags::Desktop); - - State.DiscordState.core->ActivityManager().UpdateActivity(activity, [](discord::Result result) { - if (result == discord::Result::Ok) { - spdlog::info("Discord RPC updated"); - } - else { - spdlog::error("Failed to update Discord RPC"); - } - }); - } - - GLFWwindow* window = CreateWindow(1280, 720, "Carbon Launcher"); - InitImGui(window); - - HWND hWnd = glfwGetWin32Window(window); - BOOL compositionEnabled = TRUE; - DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &compositionEnabled, sizeof(BOOL)); - - // Create the `mods` directory if it doesn't exist - std::string modsDir = GetExeDirectory() + "\\mods"; - std::filesystem::create_directory(modsDir); - - // Create the `logs` directory if it doesn't exist - std::string logsDir = GetExeDirectory() + "\\logs"; - std::filesystem::create_directory(logsDir); - - // Create the `settings` directory if it doesn't exist - std::string settingsDir = GetExeDirectory() + "\\settings"; - std::filesystem::create_directory(settingsDir); - - ImGui::GetIO().IniFilename = nullptr; - - // Load from settings/enabled.txt - auto loadEnabledMods = [&]() { - std::ifstream file(settingsDir + "\\enabled.txt"); - if (file.is_open()) { - std::string line; - while (std::getline(file, line)) { - State.enabledMods.push_back(line); - } - } - }; - - auto saveEnabledMods = [&]() { - std::ofstream file(settingsDir + "\\enabled.txt"); - if (file.is_open()) { - for (const auto& mod : State.enabledMods) { - file << mod.string() << std::endl; - } - } - }; - - loadEnabledMods(); - - Render(window, [&]() { - if (State.DiscordState.core) - State.DiscordState.core->RunCallbacks(); - - int w, h = 0; - glfwGetWindowSize(window, &w, &h); - - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2((float)w, (float)h)); - bool popen = true; - ImGui::Begin("Carbon Launcher", &popen, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar); - - bool shouldOpenPopup = false; - if (ImGui::BeginMainMenuBar()) { - if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("Exit")) - glfwSetWindowShouldClose(window, GLFW_TRUE); - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Help")) { - if (ImGui::MenuItem("About")) { - shouldOpenPopup = true; - } - - ImGui::EndMenu(); - } - - ImGui::EndMainMenuBar(); - } - - static std::chrono::time_point popupOpenTime; - - if (shouldOpenPopup) { - ImGui::OpenPopup("Info"); - popupOpenTime = std::chrono::system_clock::now(); - } - - bool isInfoPopupOpen = true; - if (ImGui::BeginPopupModal("Info", &isInfoPopupOpen, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Carbon Launcher"); - ImGui::Separator(); - ImGui::Text("Version 0.1.0"); - ImGui::Text("By Ben McAvoy"); - - if (!isInfoPopupOpen) - ImGui::CloseCurrentPopup(); - - ImGui::EndPopup(); - } - - // Create a childwindow with three tabs, "Mods" and "Settings" and "Logs". - if (ImGui::BeginTabBar("Tabs")) { - if (ImGui::BeginTabItem("Mods")) { - auto dirIter = std::filesystem::directory_iterator(modsDir); - for (const auto& entry : dirIter) { - ImGui::BeginChild(entry.path().string().c_str(), ImVec2(0, 150), true); - ImGui::TextWrapped(entry.path().filename().string().c_str()); - - bool isModEnabled = std::find(State.enabledMods.begin(), State.enabledMods.end(), entry.path()) != State.enabledMods.end(); - if (ImGui::Checkbox("Enabled", &isModEnabled)) { - if (isModEnabled) { - State.enabledMods.push_back(entry.path()); - } - else { - State.enabledMods.erase(std::remove(State.enabledMods.begin(), State.enabledMods.end(), entry.path()), State.enabledMods.end()); - } - - saveEnabledMods(); - } - - ImGui::EndChild(); - } - - if (dirIter == std::filesystem::directory_iterator()) - ImGui::Text("No mods found :("); - - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Settings")) { - ImGui::BeginChild("Directories", ImVec2(0, 0), false); - if (ImGui::TreeNode("Directories")) { - if (ImGui::Button("Open Mods Directory")) { - ShellExecuteA(NULL, "open", modsDir.c_str(), NULL, NULL, SW_SHOWNORMAL); - } - - if (ImGui::Button("Open Logs Directory")) { - ShellExecuteA(NULL, "open", logsDir.c_str(), NULL, NULL, SW_SHOWNORMAL); - } - - if (ImGui::Button("Open Settings Directory")) { - ShellExecuteA(NULL, "open", settingsDir.c_str(), NULL, NULL, SW_SHOWNORMAL); - } - - ImGui::TreePop(); - } - - ImGui::EndChild(); - - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Logs")) { - ImGui::Text("Logs"); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } - - static bool oldIsGameRunning = false; - static bool isGameRunning = false; - static std::chrono::time_point timeSinceCheckedOpen = std::chrono::system_clock::now() - std::chrono::seconds(2); - if (std::chrono::duration_cast(std::chrono::system_clock::now() - timeSinceCheckedOpen).count() > 1) { - timeSinceCheckedOpen = std::chrono::system_clock::now(); - -#ifdef USE_DUMMY_GAME - isGameRunning = IsGameOpen("DummyGame.exe"); -#else - isGameRunning = IsGameOpen("ScrapMechanic.exe"); -#endif - } - - // If isGameRunning changes, update the activity - if (oldIsGameRunning != isGameRunning) { - //activity.SetDetails(isGameRunning ? "Playing Scrap Mechanic" : "In the launcher"); This gets set by game state anyway - - std::string modCount = fmt::format("{} mods enabled", State.enabledMods.size()); - //activity.SetState(isGameRunning ? modCount.c_str() : "The latest SM modded launcher"); - - if (State.DiscordState.core) { - auto& activity = State.DiscordState.currentActivity; - if (!isGameRunning) { - activity.SetState("The latest SM modded launcher"); - } - - State.DiscordState.core->ActivityManager().UpdateActivity(activity, [](discord::Result result) { - if (result == discord::Result::Ok) { - spdlog::info("Discord RPC updated"); - } - else { - spdlog::error("Failed to update Discord RPC"); - } - }); - } - - oldIsGameRunning = isGameRunning; - } - - static float buttonSize = ImGui::CalcItemWidth(); - ImVec2 size = ImVec2(buttonSize, 20); - const char* text = isGameRunning ? "Stop" : "Start"; - ImVec2 pos = ImVec2((w - size.x) / 2, size.y); - ImGui::SetCursorPosX(pos.x); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10); - if (ImGui::Button(text, ImVec2(size.x + 20, size.y + 10))) { - if (isGameRunning) { - //ShellExecuteA(NULL, "open", "taskkill", "/F /IM ScrapMechanic.exe", NULL, SW_SHOWNORMAL); - -#ifdef USE_DUMMY_GAME - ShellExecuteA(NULL, "open", "taskkill", "/F /IM DummyGame.exe", NULL, SW_SHOWMINIMIZED); -#else - ShellExecuteA(NULL, "open", "taskkill", "/F /IM ScrapMechanic.exe", NULL, SW_SHOWMINIMIZED); -#endif - } - else { - // ShellExecuteA(NULL, "open", "steam://run/387990/-dev", NULL, NULL, SW_SHOWMINIMIZED); - -#ifdef USE_DUMMY_GAME - std::string dummyGamePath = GetExeDirectory() + "\\DummyGame.exe"; - ShellExecuteA(NULL, "open", dummyGamePath.c_str(), NULL, NULL, SW_SHOWMINIMIZED); -#else - ShellExecuteA(NULL, "open", "steam://run/387990/-dev", NULL, NULL, SW_SHOWMINIMIZED); -#endif - - // Management of injection after message from CarbonSupervisor. - std::thread([&] { - //DWORD targetPID = GetProcID("ScrapMechanic.exe"); - -#ifdef USE_DUMMY_GAME - DWORD targetPID = GetProcID("DummyGame.exe"); -#else - DWORD targetPID = GetProcID("ScrapMechanic.exe"); -#endif - - while (targetPID == 0) { - spdlog::info("Waiting for ScrapMechanic.exe to start"); - std::this_thread::sleep_for(std::chrono::seconds(2)); - //targetPID = GetProcID("ScrapMechanic.exe"); - -#ifdef USE_DUMMY_GAME - targetPID = GetProcID("DummyGame.exe"); -#else - targetPID = GetProcID("ScrapMechanic.exe"); -#endif - } - - std::string supervisorPath = GetExeDirectory() + "\\CarbonSupervisor.dll"; - if (!Inject(targetPID, hWnd, supervisorPath)) { - spdlog::error("Failed to inject CarbonSupervisor.dll"); - return 1; - } - - // Allow some initialization time - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // *Connect* to the supervisor pipe - HANDLE hPipe = CreateFileA("\\\\.\\pipe\\CarbonSupervisor", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); - if (hPipe == INVALID_HANDLE_VALUE) { - spdlog::error("Failed to open named pipe: {}", GetLastErrorAsString()); - return 1; - } - - // Infinite loop receiving messages from the supervisor - char buffer[1024]; - DWORD bytesRead; - - // Infinitely try to read from the pipe - while (true) { - memset(buffer, 0, sizeof(buffer)); - if (!ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, nullptr)) { - spdlog::error("Failed to read from named pipe: {}", GetLastErrorAsString()); - return 1; - } - buffer[bytesRead] = '\0'; // Null-terminate after each read. - std::string message = buffer; - - spdlog::info("Received message: {}", message); - - if (message == "loaded") { - // Inject mods - std::vector enabledModsClone = State.enabledMods; - for (auto& mod : enabledModsClone) { - spdlog::info("Injecting mod: {}", mod.string()); - if (!Inject(targetPID, hWnd, mod.string())) { - spdlog::error("Failed to inject mod: {}", mod.string()); - continue; - } - } - } - - if (message.length() == 1 && isdigit(message[0])) { - if (message == "2") { - spdlog::info("TODO: Say in game"); - } - else if (message == "3") { - spdlog::info("TODO: Say in menu"); - } - else if (message == "1") { - spdlog::info("TODO: Say in loading screen"); - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - return 0; - }).detach(); - } - } - - // Drop up menu for the kill button, should only be shown when the game is running - if (isGameRunning) { - ImGui::SetCursorPosX(pos.x + size.x + 24); - ImGui::SetCursorPosY(h - pos.y - 24); - - bool isKillPopupOpen = false; - if (ImGui::Button("^", ImVec2(24, size.y + 10))) { - isKillPopupOpen = true; - } - - if (isKillPopupOpen) - ImGui::OpenPopup("KillGame"); - - if (ImGui::BeginPopupModal("KillGame", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Are you sure you want to kill the game?"); - ImGui::Separator(); - - if (ImGui::Button("Yes")) { - //ShellExecuteA(NULL, "open", "taskkill", "/F /IM ScrapMechanic.exe", NULL, SW_SHOWMINIMIZED); - -#ifdef USE_DUMMY_GAME - ShellExecuteA(NULL, "open", "taskkill", "/F /IM DummyGame.exe", NULL, SW_SHOWMINIMIZED); -#else - ShellExecuteA(NULL, "open", "taskkill", "/F /IM ScrapMechanic.exe", NULL, SW_SHOWMINIMIZED); -#endif - - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - - if (ImGui::Button("No")) { - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - } - - ImGui::End(); - - if (!popen) - glfwSetWindowShouldClose(window, GLFW_TRUE); - }); - - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - glfwDestroyWindow(window); - glfwTerminate(); +#include "guimanager.h" +#include "discordmanager.h" +#include "gamemanager.h" +#include "pipemanager.h" +#include "state.h" + +using namespace Carbon; + +/* + * Main entry point for the Carbon Launcher + * + * @param hInstance The instance of the application + * @param hPrevInstance The previous instance of the application + * @param lpCmdLine The command line arguments + * @param nCmdShow The command show + * @return The exit code of the application + */ +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { + C.guiManager = new GUIManager(hInstance); + C.discordManager = new DiscordManager(); + C.gameManager = new GameManager(); + C.pipeManager = new PipeManager(); + + C.guiManager->RenderCallback(_GUI); + C.guiManager->Run(); return 0; } \ No newline at end of file diff --git a/CarbonLauncher/src/pipemanager.cpp b/CarbonLauncher/src/pipemanager.cpp new file mode 100644 index 0000000..b4c22d7 --- /dev/null +++ b/CarbonLauncher/src/pipemanager.cpp @@ -0,0 +1,83 @@ +#include "pipemanager.h" + +#include + +using namespace Carbon; + +PipeManager::PipeManager() { + this->pipeReader = std::thread([this]() { + // Create a pipe that *other* processes can connect to + auto pipe = CreateNamedPipe( + L"\\\\.\\pipe\\CarbonPipe", + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + 4096, + 4096, + 0, + NULL + ); + + if (pipe == INVALID_HANDLE_VALUE) { + spdlog::error("Failed to create pipe"); + return; + } + + // Wait for a connection + if (!ConnectNamedPipe(pipe, NULL)) { + spdlog::error("Failed to connect to pipe"); + return; + } + + while (true) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Read the message + char buffer[4096]; + DWORD bytesRead = 0; + if (!ReadFile(pipe, buffer, sizeof(buffer), &bytesRead, NULL)) { + spdlog::error("Failed to read from pipe"); + return; + } + + // Parse the message + Packet packet; + + // Split on `-:-` + std::string data(buffer, bytesRead); + auto split = data.find("-:-"); + if (split == std::string::npos) { + spdlog::error("Invalid packet received"); + continue; + } + + auto type = data.substr(0, split); + auto payload = data.substr(split + 3); + + if (type == "LOADED") { + packet.type = PacketType::LOADED; + } + else if (type == "STATECHANGE") { + packet.type = PacketType::STATECHANGE; + } + else if (type == "LOG") { + packet.type = PacketType::LOG; + } + else { + spdlog::error("Invalid packet type received"); + continue; + } + + packet.data = payload; + + // Lock the mutex and add the packet + std::lock_guard lock(this->pipeMutex); + this->packets.push(packet); + } + }); +} + +PipeManager::~PipeManager() { + this->pipeReader.join(); + this->packets = {}; +} \ No newline at end of file diff --git a/CarbonLauncher/src/state.cpp b/CarbonLauncher/src/state.cpp new file mode 100644 index 0000000..1fcddf1 --- /dev/null +++ b/CarbonLauncher/src/state.cpp @@ -0,0 +1,3 @@ +#include "state.h" +using namespace Carbon; +CarbonState_t C; \ No newline at end of file diff --git a/CarbonLauncher/src/utils.cpp b/CarbonLauncher/src/utils.cpp deleted file mode 100644 index 4b485df..0000000 --- a/CarbonLauncher/src/utils.cpp +++ /dev/null @@ -1,248 +0,0 @@ -#define GLFW_INCLUDE_NONE -#define GLFW_EXPOSE_NATIVE_WIN32 - -#include "utils.h" - -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -#include -#include - -#undef CreateWindow - -#include - -/* - * Get the last error message as a string. - * - * @return The last error message as a string. - */ -std::string GetLastErrorAsString() { - DWORD errorMessageID = GetLastError(); - if (errorMessageID == 0) { - return std::string(); - } - LPSTR messageBuffer = nullptr; - size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); - std::string message(messageBuffer, size); - LocalFree(messageBuffer); - return message; -} - -/* - * Get the process ID of a process by name. - * - * @param processName The name of the process to get the ID of. - * @return The process ID of the process with the given name, or 0 if the process was not found. - */ -DWORD GetProcID(const std::string& processName) { - PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) }; - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - - if (snapshot == INVALID_HANDLE_VALUE) { - spdlog::error("Failed to create snapshot: {}", GetLastErrorAsString()); - return 0; - } - - if (!Process32First(snapshot, &processEntry)) { - spdlog::error("Failed to get first process: {}", GetLastErrorAsString()); - CloseHandle(snapshot); - return 0; - } - - while (Process32Next(snapshot, &processEntry)) { - if (processName == processEntry.szExeFile) { - CloseHandle(snapshot); - return processEntry.th32ProcessID; - } - } - - CloseHandle(snapshot); - return 0; -} - -/* - * Inject a DLL into a process. - * - * @param targetPID The process ID of the target process. - * @param window The window to set as the foreground window after injection. - * @return True if the DLL was successfully injected, false otherwise. - */ -[[nodiscard]] bool Inject(DWORD targetPID, HWND window, const std::string& toLoad) { - HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID); - if (process == NULL) { - spdlog::error("Failed to open process: {}", GetLastErrorAsString()); - return false; - } - - //char buffer[MAX_PATH]; - //GetModuleFileName(NULL, buffer, MAX_PATH); - //std::string dllPath = std::string(buffer, strlen(buffer)); - //dllPath = dllPath.substr(0, dllPath.find_last_of('\\') + 1) + toLoad; - - std::string dllPath = toLoad; - - if (!std::filesystem::exists(dllPath)) { - spdlog::error("DLL does not exist: {}", dllPath); - CloseHandle(process); - return false; - } - - spdlog::info("Injecting DLL: {}", dllPath); - - LPVOID remoteMemory = VirtualAllocEx(process, NULL, dllPath.size() + 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if (remoteMemory == NULL) { - spdlog::error("Failed to allocate memory in target process: {}", GetLastErrorAsString()); - CloseHandle(process); - return false; - } - - if (!WriteProcessMemory(process, remoteMemory, dllPath.c_str(), dllPath.size() + 1, NULL)) { - spdlog::error("Failed to write memory in target process: {}", GetLastErrorAsString()); - CloseHandle(process); - return false; - } - - HANDLE thread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, remoteMemory, 0, NULL); - if (thread == NULL) { - spdlog::error("Failed to create remote thread: {}", GetLastErrorAsString()); - CloseHandle(process); - return false; - } - - WaitForSingleObject(thread, INFINITE); - - CloseHandle(thread); - CloseHandle(process); - - return true; -} - -/* - * Enumerate windows callback function. - * - * @param hWnd The window handle. - * @param lParam The process ID to compare against. - * @return False if the process ID matches, true otherwise. - */ -BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) { - DWORD pid = 0; - GetWindowThreadProcessId(hWnd, &pid); - - if (pid == lParam) { - g_hWnd = hWnd; - return FALSE; - } - - return TRUE; -} - -std::string GetExeDirectory() { - char buffer[MAX_PATH]; - GetModuleFileNameA(NULL, buffer, MAX_PATH); - std::string::size_type pos = std::string(buffer).find_last_of("\\/"); - return std::string(buffer).substr(0, pos); -} - -GLFWwindow* CreateWindow(int width, int height, const char* title) { - if (!glfwInit()) { - return nullptr; - } - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); - - GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL); - if (!window) { - glfwTerminate(); - return nullptr; - } - - glfwMakeContextCurrent(window); - - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - glfwDestroyWindow(window); - glfwTerminate(); - return nullptr; - } - - return window; -} - - -void InitImGui(GLFWwindow* window) { - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - // Load Segoe UI font - //io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\CascadiaCode.ttf", 16.0f); - io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 18.0f); - - ImGui::StyleColorsDark(); - - ImGuiStyle& style = ImGui::GetStyle(); - style.WindowRounding = 6.0f; - style.FrameRounding = 4.0f; - style.GrabRounding = 4.0f; - style.TabRounding = 4.0f; - style.ScrollbarRounding = 4.0f; - style.WindowBorderSize = 0.0f; - style.FrameBorderSize = 0.0f; - style.PopupBorderSize = 0.0f; - style.GrabMinSize = 8.0f; - style.ChildRounding = 4.0f; - - ImGui_ImplGlfw_InitForOpenGL(window, true); - ImGui_ImplOpenGL3_Init("#version 460"); -} - -bool IsGameOpen(const char* name) { - HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnap == INVALID_HANDLE_VALUE) { - return false; - } - PROCESSENTRY32 pe32; - pe32.dwSize = sizeof(PROCESSENTRY32); - if (!Process32First(hSnap, &pe32)) { - CloseHandle(hSnap); - return false; - } - do { - if (strcmp(pe32.szExeFile, name) == 0) { - CloseHandle(hSnap); - return true; - } - } while (Process32Next(hSnap, &pe32)); - CloseHandle(hSnap); - return false; -} - -void Render(GLFWwindow* window, std::function renderFunc) { - while (!glfwWindowShouldClose(window)) { - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - renderFunc(); - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwMakeContextCurrent(window); - glfwSwapBuffers(window); - glfwPollEvents(); - } -} diff --git a/CarbonLauncher/vcpkg.json b/CarbonLauncher/vcpkg.json index 42242e0..a0fd874 100644 --- a/CarbonLauncher/vcpkg.json +++ b/CarbonLauncher/vcpkg.json @@ -17,4 +17,4 @@ "spdlog", "discord-game-sdk" ] -} +} \ No newline at end of file diff --git a/CarbonSupervisor/CarbonSupervisor.vcxproj b/CarbonSupervisor/CarbonSupervisor.vcxproj deleted file mode 100644 index 0379183..0000000 --- a/CarbonSupervisor/CarbonSupervisor.vcxproj +++ /dev/null @@ -1,139 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 17.0 - Win32Proj - {1f14e774-5a60-4742-9534-712e254faa60} - CarbonSupervisor - 10.0 - - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - true - true - - - - - - - - - \ No newline at end of file diff --git a/CarbonSupervisor/src/main.cpp b/CarbonSupervisor/src/main.cpp deleted file mode 100644 index 058e408..0000000 --- a/CarbonSupervisor/src/main.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#define WIN32_LEAN_AND_MEAN -#include - -#include -#include -#include -#include - -typedef unsigned int uint4_t; - -struct Contraption { - /* 0x000 */ char pad_056[0x17C]; - /* 0x17C */ uint4_t gameStateType; - /* 0x180 */ char pad_004[0x20]; - /* 0x1A0 */ HWND hWnd; -}; - -template -T FetchClass(uintptr_t address) { - return *reinterpret_cast(address); -} - -/* - * Thread entry point for the DLL. - * @param lpParameter The parameter passed to the thread. - */ -DWORD WINAPI ThreadProc(LPVOID lpParameter) { - HMODULE hModule = static_cast(lpParameter); - - // *Create* a named pipe for other processes to connect to - HANDLE hPipe = CreateNamedPipeA("\\\\.\\pipe\\CarbonSupervisor", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, nullptr); - if (hPipe == INVALID_HANDLE_VALUE) { - MessageBoxA(nullptr, "Failed to open named pipe", "CarbonSupervisor", MB_OK); - return 1; - } - - uintptr_t contraptionAddr = (uintptr_t)GetModuleHandle(nullptr) + 0x12674B8; - Contraption* contraption = FetchClass(contraptionAddr); - - while (contraption == nullptr) - contraption = FetchClass(contraptionAddr); - - while (contraption->gameStateType < 1 || contraption->gameStateType > 3 || contraption == nullptr || contraption->hWnd == nullptr) { - contraption = FetchClass(contraptionAddr); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - while (contraption->gameStateType == 1) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // Open the supervisor pipe and send `loaded` - char buffer[] = "loaded"; - DWORD bytesWritten; - - if (!WriteFile(hPipe, buffer, sizeof(buffer), &bytesWritten, nullptr)) { - MessageBoxA(nullptr, "Failed to write to named pipe", "CarbonSupervisor", MB_OK); - return 1; - } - - for (;;) { - static uint4_t lastGameStateType = contraption->gameStateType; - - if (contraption->gameStateType != lastGameStateType) { - lastGameStateType = contraption->gameStateType; - - std::cout << "\n\n\n\n\nChanged game state type: " << contraption->gameStateType << "\n\n\n\n\n"; - - std::cout << "Game state type: " << contraption->gameStateType << std::endl; - - // Send the game state type to the supervisor - memset(buffer, 0, sizeof(buffer)); - //buffer[0] = contraption->gameStateType; - //_itoa(contraption->gameStateType, buffer, 10); - _itoa_s(contraption->gameStateType, buffer, 10); - // buffer -> "1" -> 0x31 - - if (!WriteFile(hPipe, buffer, sizeof(buffer), &bytesWritten, nullptr)) { - MessageBoxA(nullptr, "Failed to write to named pipe", "CarbonSupervisor", MB_OK); - return 1; - } - - if (contraption->gameStateType == 3) { - break; - } - - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - - // TODO: Send log messages - - FreeLibraryAndExitThread(hModule, 0); - return 0; -} - - -/* - * DLL Entry Point. - * - * @param hModule The handle to the DLL module. - * @param dwReason The reason for the DLL entry point being called. - * @param lpReserved Reserved. - * @return True if the DLL was successfully loaded, false otherwise. - */ -BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { - switch (dwReason) { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hModule); - CreateThread(nullptr, 0, ThreadProc, hModule, 0, nullptr); - - break; - case DLL_PROCESS_DETACH: - break; - case DLL_THREAD_ATTACH: - break; - case DLL_THREAD_DETACH: - break; - } - - return TRUE; -} diff --git a/CarbonSupervisor/vcpkg-configuration.json b/CarbonSupervisor/vcpkg-configuration.json deleted file mode 100644 index c94aa08..0000000 --- a/CarbonSupervisor/vcpkg-configuration.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "default-registry": { - "kind": "git", - "baseline": "b2a47d316de1f3625ea43a7ca3e42dd28c52ece7", - "repository": "https://github.com/microsoft/vcpkg" - }, - "registries": [ - { - "kind": "artifact", - "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", - "name": "microsoft" - } - ] -} diff --git a/CarbonSupervisor/vcpkg.json b/CarbonSupervisor/vcpkg.json deleted file mode 100644 index 0967ef4..0000000 --- a/CarbonSupervisor/vcpkg.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/DummyGame/DummyGame.vcxproj b/DummyGame/DummyGame.vcxproj deleted file mode 100644 index 4917232..0000000 --- a/DummyGame/DummyGame.vcxproj +++ /dev/null @@ -1,150 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 17.0 - Win32Proj - {a56ac389-85ad-4e6f-9e64-e859dd676099} - DummyGame - 10.0 - - - - Application - true - v143 - MultiByte - - - Application - false - v143 - true - MultiByte - - - Application - true - v143 - MultiByte - - - Application - false - v143 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - true - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - include - /utf-8 %(AdditionalOptions) - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - include - /utf-8 %(AdditionalOptions) - - - Console - true - true - true - - - - - - - - - \ No newline at end of file diff --git a/DummyGame/src/main.cpp b/DummyGame/src/main.cpp deleted file mode 100644 index dcf0f72..0000000 --- a/DummyGame/src/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -typedef unsigned int uint4_t; - -// LoadLibraryA function pointer -typedef HMODULE(WINAPI* LoadLibraryA_t)(LPCSTR lpLibFileName); - -struct Contraption_t { - /* 0x000 */ char pad_056[0x17C]; // this is genuinely random in the game - /* 0x17C */ uint4_t gameStateType = 0; // This is random at first but managed - /* 0x180 */ char pad_004[0x20]; // this is genuinely random in the game - /* 0x1A0 */ HWND hWnd; // this is random in the game at first but managed -}; - -#ifdef NDEBUG -static char pad[0x12674B8 - 0x1DC30] = { 0 }; // offset it to replicate the game's memory layout -#else -static_assert(false, "This is only for the release build"); -#endif - -static Contraption_t* Contraption = new Contraption_t(); - -int main() { - spdlog::set_level(spdlog::level::trace); - - // Stop the compiler optimizing the pad array - if (pad[0] == 1) { - spdlog::critical("This should never be printed"); - } - - // Get a pointer to the pointer and print it so we can adjust it to be the same as the game - Contraption_t** ContraptionPtr = &Contraption; - - spdlog::info("Contraption: {}", reinterpret_cast((uintptr_t)ContraptionPtr - (uintptr_t)GetModuleHandle(nullptr))); - - // Wait 5s - std::this_thread::sleep_for(std::chrono::seconds(5)); - - HINSTANCE hInst = GetModuleHandle(nullptr); - HWND newHwnd = CreateWindowExA(0, "STATIC", "Dummy Game", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720, NULL, NULL, hInst, NULL); - - // Save hwnd to the Contraption struct and set the game state type to 1 (loading screen) - Contraption->hWnd = (HWND)newHwnd; - Contraption->gameStateType = 1; - - // Wait 2s - std::this_thread::sleep_for(std::chrono::seconds(2)); - - // Set the game state type to 2 (in-game) - Contraption->gameStateType = 2; - - // Hook LoadLibraryA so we can detect and block DLL injection (we need to block it - // because since we aren't the real game it will crash, this is only for testing so seeing - // the mods injecting isn't important) - MH_Initialize(); - MH_CreateHook(&LoadLibraryA, (LPVOID)(LoadLibraryA_t)[](LPCSTR lpLibFileName) -> HMODULE { - std::string libFileName = lpLibFileName; - spdlog::trace("Blocked DLL injection: {}", libFileName); - return NULL; - }, nullptr); - - MH_EnableHook(&LoadLibraryA); - - while (!(GetAsyncKeyState(VK_HOME) & 1 && GetAsyncKeyState(VK_END)) & 1) { - // Fx sets contraption game state type to x - if (GetAsyncKeyState(VK_F1) & 1) { - Contraption->gameStateType = 1; - spdlog::trace("Game state type is now 1"); - } - - if (GetAsyncKeyState(VK_F2) & 1) { - Contraption->gameStateType = 2; - spdlog::trace("Game state type is now 2"); - } - - if (GetAsyncKeyState(VK_F3) & 1) { - Contraption->gameStateType = 3; - spdlog::trace("Game state type is now 3"); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - MH_DisableHook(&LoadLibraryA); - MH_Uninitialize(); - - return 0; -} diff --git a/DummyGame/vcpkg-configuration.json b/DummyGame/vcpkg-configuration.json deleted file mode 100644 index ba06dad..0000000 --- a/DummyGame/vcpkg-configuration.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "default-registry": { - "kind": "git", - "baseline": "2960d7d80e8d09c84ae8abf15c12196c2ca7d39a", - "repository": "https://github.com/microsoft/vcpkg" - }, - "registries": [ - { - "kind": "artifact", - "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", - "name": "microsoft" - } - ] -} diff --git a/DummyGame/vcpkg.json b/DummyGame/vcpkg.json deleted file mode 100644 index fe2b23d..0000000 --- a/DummyGame/vcpkg.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "dependencies": [ - "spdlog", - "minhook" - ] -}