diff --git a/.gitignore b/.gitignore index c3423138..f5f82379 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /build -/.vscode \ No newline at end of file +/.vscode +/.vs +/out \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d31c5846..76903965 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,19 +1,46 @@ cmake_minimum_required(VERSION 3.5.0) -project(Millennium LANGUAGES CXX) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lunistring -liconv -lbrotlicommon -lcurl -lssh2 -lssl -lcrypto -lz -static-libgcc -static-libstdc++ -lstdc++ -lpthread") +# build boxer statically set(BUILD_SHARED_LIBS OFF) -add_subdirectory(vendor/boxer) +cmake_policy(SET CMP0091 NEW) + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +project(Millennium LANGUAGES CXX) -if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - add_compile_options(-Wno-format-security) +if (WIN32) + # debug output paths + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "C:/Program Files (x86)/Steam") + set(LIBRARY_OUTPUT_DIRECTORY "C:/Program Files (x86)/Steam") endif() +set(PRIVATE_PRODUCT_NAME "Millennium [Steam Homebrew]") + +# Set version information +add_compile_definitions( + VER_COMPANYNAME_STR="Steam Homebrew." + VER_PRIVATEBUILD=1.0.1 + VER_PRERELEASE=1.0.1 + VER_DEBUG=1.0.1 + VER_FILEVERSION_STR="2.1.6" + VER_FILEVERSION=2,1,6 + VER_PRODUCTVERSION_STR="2.1.6" + VER_PRODUCTVERSION=2,1,6 + VER_FILEDESCRIPTION_STR="A plugin loader for the modern Steam Client." + VER_INTERNALNAME_STR="${PRIVATE_PRODUCT_NAME}" + VER_LEGALCOPYRIGHT_STR="Copyright \(c\) Steam Homebrew." + VER_LEGALTRADEMARKS1_STR="Copyright \(c\) Steam Homebrew." + VER_LEGALTRADEMARKS2_STR="Copyright \(c\) Steam Homebrew." + VER_ORIGINALFILENAME_STR="${PRIVATE_PRODUCT_NAME}" + VER_PRODUCTNAME_STR="${PRIVATE_PRODUCT_NAME}" +) + +# Compile as release and compile MSVC as MT (static) +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") -include(FetchContent) -FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git GIT_TAG 3b15fa82ea74739b574d705fea44959b58142eb8) -FetchContent_MakeAvailable(cpr) +# build boxer (message-box lib) +add_subdirectory(vendor/boxer) # add_compile_options(-g -Og -lz) set(CMAKE_CXX_STANDARD 17) @@ -31,43 +58,38 @@ include_directories( ) add_compile_definitions( - CURL_STATICLIB - FMT_HEADER_ONLY + CURL_STATICLIB # static lcurl + + # prevent websocketpp from using boost _WEBSOCKETPP_CPP11_THREAD_ - ASIO_HAS_STD_INVOKE_RESULT _WEBSOCKETPP_CPP11_TYPE_TRAITS_ _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ - ASIO_STANDALONE - _CRT_SECURE_NO_WARNINGS -) -# find_package(minizip REQUIRED) -find_package(Python COMPONENTS Development) -find_package(Threads REQUIRED) + ASIO_STANDALONE # static asio + ASIO_HAS_STD_INVOKE_RESULT # prevent use of boost -if (Python_FOUND) - message(STATUS "PYTHON include directory: ${Python_INCLUDE_DIRS}") - message(STATUS "PYTHON libraries: ${Python_LIBRARIES}") -else() - message("Python was not found. download & install here: https://www.python.org/ftp/python/3.11.8/python-3.11.8.exe") -endif() + FMT_HEADER_ONLY # implicit static link + _CRT_SECURE_NO_WARNINGS +) -message(STATUS "LIBGIT include directory: ${LIBGIT2_INCLUDE_DIRS}") -message(STATUS "LIBGIT libraries: ${LIBGIT2_LIBRARIES}") +find_package(cpr CONFIG REQUIRED) # used for web requests. +find_package(Python REQUIRED COMPONENTS Interpreter Development) # used to run plugin backends +find_package(unofficial-git2 CONFIG REQUIRED) # update millennium modules include_directories( - ${Python_INCLUDE_DIRS} + # ${Python_INCLUDE_DIRS} + "C:/Program Files (x86)/Python311-32/include" ${LIBGIT2_INCLUDE_DIRS} "src/__builtins__" ) -set(CMAKE_BUILD_TYPE Debug) set(SOURCE_FILES "src/core/main.cpp" "src/core/loader.cpp" - "src/core/backend/co_spawn.cpp" - "src/core/impl/c_python.cpp" - "src/core/impl/javascript.cpp" + "src/core/py_controller/co_spawn.cpp" + "src/core/ffi/c_python.cpp" + "src/core/ffi/javascript.cpp" + "src/core/ffi/gil.cc" "src/utilities/log.cpp" "src/core/ipc/pipe.cpp" "src/generic/stream_parser.cc" @@ -75,10 +97,10 @@ set(SOURCE_FILES "src/deps/module.cc" "src/core/hooks/web_load.cc" "src/core/co_initialize/co_stub.cc" + version.rc ) if (WIN32) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "C:/Program Files (x86)/Steam") add_library(Millennium SHARED "${SOURCE_FILES}") # add_executable(Millennium "${SOURCE_FILES}") elseif(UNIX) @@ -87,24 +109,24 @@ endif() set_target_properties(Millennium PROPERTIES OUTPUT_NAME "user32") set_target_properties(Millennium PROPERTIES PREFIX "") +set_target_properties(Millennium PROPERTIES NO_EXPORT TRUE) + +if(MSVC) + # prevent MSVC from generating .lib and .exp archives + set_target_properties(Millennium PROPERTIES ARCHIVE_OUTPUT_NAME "" LINK_FLAGS "/NOEXP") +endif() if(WIN32) # link windows defined socket headers - target_link_libraries(Millennium - Ws2_32.lib - wsock32 - ${CMAKE_SOURCE_DIR}/vendor/win32/python311.lib - ${CMAKE_SOURCE_DIR}/vendor/win32/git2.lib - ) -elseif(UNIX) - target_link_libraries(Millennium - ${Python_LIBRARIES} - git2 - ) + target_link_libraries(Millennium Ws2_32.lib wsock32) endif() target_link_libraries(Millennium - Boxer - cpr::cpr - Threads::Threads + Boxer + cpr::cpr + # ${Python_LIBRARIES} + # "D:/Development/millennium/vendor/win32/python311.lib" + # "C:/Users/Admin/Downloads/Python/Python-3.11.8/PCbuild/win32/python311.lib" + "C:/Users/Admin/Downloads/Python-3.11.8/Python-3.11.8/PCbuild/win32/python311.lib" + unofficial::git2::git2 ) \ No newline at end of file diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 100644 index 00000000..2be4ebb2 --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "default", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "C:/Users/Admin/vcpkg/scripts/buildsystems/vcpkg.cmake", + "VCPKG_TARGET_TRIPLET": "x86-windows-static" + } + } + ] +} \ No newline at end of file diff --git a/src/__builtins__/bootstrap.js b/src/__builtins__/bootstrap.js deleted file mode 100644 index 632d266c..00000000 --- a/src/__builtins__/bootstrap.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * This module is in place to load all plugin frontends - * @ src\__builtins__\bootstrap.js - */ - -class EventEmitter { - constructor() { - this.events = {}; - this.pendingEvents = {}; // Store emitted events temporarily - } - - on(eventName, listener) { - this.events[eventName] = this.events[eventName] || []; - this.events[eventName].push(listener); - - // If there are pending events for this eventName, emit them now - if (this.pendingEvents[eventName]) { - this.pendingEvents[eventName].forEach(args => this.emit(eventName, ...args)); - delete this.pendingEvents[eventName]; - } - } - - emit(eventName, ...args) { - if (this.events[eventName]) { - this.events[eventName].forEach(listener => listener(...args)); - } else { - // If there are no listeners yet, store the emitted event for later delivery - this.pendingEvents[eventName] = this.pendingEvents[eventName] || []; - this.pendingEvents[eventName].push(args); - } - } - - off(eventName, listener) { - if (this.events[eventName]) { - this.events[eventName] = this.events[eventName].filter(registeredListener => registeredListener !== listener); - } - } -} -const emitter = new EventEmitter(); -console.log('%c Millennium ', 'background: black; color: white', "Bootstrapping modules..."); - -// connect to millennium websocket IPC -window.CURRENT_IPC_CALL_COUNT = 0; - -function connectIPCWebSocket() { - window.MILLENNIUM_IPC_SOCKET = new WebSocket('ws://localhost:12906'); - - window.MILLENNIUM_IPC_SOCKET.onopen = function(event) { - console.log('%c Millennium ', 'background: black; color: white', "Successfully connected to Millennium!"); - emitter.emit('loaded', true); - }; - - window.MILLENNIUM_IPC_SOCKET.onclose = function(event) { - console.error('IPC connection was peacefully broken.', event); - setTimeout(connectIPCWebSocket, 100); - }; - - window.MILLENNIUM_IPC_SOCKET.onerror = function(error) { - console.error('IPC connection was forcefully broken.', error); - }; -} - -connectIPCWebSocket(); - -// var originalConsoleLog = console.log; - -/** - * seemingly fully functional, though needs a rewrite as its tacky - * @todo hook a function that specifies when react is loaded, or use other methods to figure out when react has loaded. - * @param {*} message - */ -function __millennium_module_init__() { - if (window.SP_REACTDOM === undefined) { - setTimeout(() => __millennium_module_init__(), 1) - } - else { - console.log('%c Millennium ', 'background: black; color: white', "Bootstrapping builtin modules..."); - // this variable points @ src_builtins_\api.js - - emitter.on('loaded', () => { - SCRIPT_RAW_TEXT - }); - } -} -__millennium_module_init__() \ No newline at end of file diff --git a/src/__builtins__/executor.cc b/src/__builtins__/executor.cc index 12874079..262af7bf 100644 --- a/src/__builtins__/executor.cc +++ b/src/__builtins__/executor.cc @@ -1,6 +1,6 @@ #include "executor.h" -#include -#include +#include +#include #include #include #include @@ -9,25 +9,25 @@ #include #include -PyObject* py_get_user_settings(PyObject* self, PyObject* args) +PyObject* GetUserSettings(PyObject* self, PyObject* args) { - const auto data = stream_buffer::file::readJsonSync(stream_buffer::steam_path().string()); - PyObject* py_dict = PyDict_New(); + const auto SettingsStore = SystemIO::ReadJsonSync(SystemIO::GetSteamPath().string()); + PyObject* resultBuffer = PyDict_New(); - for (auto it = data.begin(); it != data.end(); ++it) + for (auto it = SettingsStore.begin(); it != SettingsStore.end(); ++it) { PyObject* key = PyUnicode_FromString(it.key().c_str()); PyObject* value = PyUnicode_FromString(it.value().get().c_str()); - PyDict_SetItem(py_dict, key, value); + PyDict_SetItem(resultBuffer, key, value); Py_DECREF(key); Py_DECREF(value); } - return py_dict; + return resultBuffer; } -PyObject* py_set_user_settings_key(PyObject* self, PyObject* args) +PyObject* SetUserSettings(PyObject* self, PyObject* args) { const char* key; const char* value; @@ -37,79 +37,95 @@ PyObject* py_set_user_settings_key(PyObject* self, PyObject* args) return NULL; } - auto data = stream_buffer::file::readJsonSync(stream_buffer::steam_path().string()); + auto data = SystemIO::ReadJsonSync(SystemIO::GetSteamPath().string()); data[key] = value; - stream_buffer::file::writeFileSync(stream_buffer::steam_path().string(), data.dump(4)); + SystemIO::WriteFileSync(SystemIO::GetSteamPath().string(), data.dump(4)); Py_RETURN_NONE; } -PyObject* call_frontend_method(PyObject* self, PyObject* args, PyObject* kwargs) +PyObject* CallFrontendMethod(PyObject* self, PyObject* args, PyObject* kwargs) { - const char* method_name = NULL; - PyObject* params_list = NULL; + const char* methodName = NULL; + PyObject* parameterList = NULL; - static const char* kwlist[] = { "method_name", "params", NULL }; + static const char* keywordArgsList[] = { "method_name", "params", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", (char**)kwlist, &method_name, ¶ms_list)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", (char**)keywordArgsList, &methodName, ¶meterList)) + { return NULL; } - std::vector params; + std::vector params; - if (params_list != NULL) + if (parameterList != NULL) { - if (!PyList_Check(params_list)) { + if (!PyList_Check(parameterList)) + { PyErr_SetString(PyExc_TypeError, "params must be a list"); return NULL; } - Py_ssize_t list_size = PyList_Size(params_list); - for (Py_ssize_t i = 0; i < list_size; ++i) { - PyObject* item = PyList_GetItem(params_list, i); - const char* value_str = PyUnicode_AsUTF8(PyObject_Str(item)); - const char* value_type = Py_TYPE(item)->tp_name; + Py_ssize_t listSize = PyList_Size(parameterList); + + for (Py_ssize_t i = 0; i < listSize; ++i) + { + PyObject* listItem = PyList_GetItem(parameterList, i); + const std::string strValue = PyUnicode_AsUTF8(PyObject_Str(listItem)); + const std::string valueType = Py_TYPE(listItem)->tp_name; - if (strcmp(value_type, "str") != 0 && strcmp(value_type, "bool") != 0 && strcmp(value_type, "int") != 0) { + if (valueType != "str" && valueType != "bool" && valueType != "int") + { PyErr_SetString(PyExc_TypeError, "Millennium's IPC can only handle [bool, str, int]"); return NULL; } - params.push_back({ value_str, value_type }); + params.push_back({ strValue, valueType }); } } PyObject* globals = PyModule_GetDict(PyImport_AddModule("__main__")); - PyObject* result = PyRun_String("MILLENNIUM_PLUGIN_SECRET_NAME", Py_eval_input, globals, globals); + PyObject* pluginNameObj = PyRun_String("MILLENNIUM_PLUGIN_SECRET_NAME", Py_eval_input, globals, globals); - if (result == nullptr || PyErr_Occurred()) { - console.err("error getting plugin name, can't make IPC request. this is likely a millennium bug."); + if (pluginNameObj == nullptr || PyErr_Occurred()) + { + Logger.Error("error getting plugin name, can't make IPC request. this is likely a millennium bug."); return NULL; } - std::string script = javascript::construct_fncall(PyUnicode_AsUTF8(PyObject_Str(result)), method_name, params); + const std::string pluginName = PyUnicode_AsUTF8(PyObject_Str(pluginNameObj)); + const std::string script = JavaScript::ConstructFunctionCall(pluginName.c_str(), methodName, params); - return javascript::evaluate_lock(script); + return JavaScript::EvaluateFromSocket(script); } -PyObject* py_get_millennium_version(PyObject* self, PyObject* args) { return PyUnicode_FromString(g_mversion); } -PyObject* py_steam_path(PyObject* self, PyObject* args) { return PyUnicode_FromString(stream_buffer::steam_path().string().c_str()); } +PyObject* GetVersionInfo(PyObject* self, PyObject* args) +{ + return PyUnicode_FromString(g_millenniumVersion); +} -PyObject* remove_browser_module(PyObject* self, PyObject* args) { - int js_index; +PyObject* GetSteamPath(PyObject* self, PyObject* args) +{ + return PyUnicode_FromString(SystemIO::GetSteamPath().string().c_str()); +} + +PyObject* RemoveBrowserModule(PyObject* self, PyObject* args) +{ + int moduleId; - if (!PyArg_ParseTuple(args, "i", &js_index)) { + if (!PyArg_ParseTuple(args, "i", &moduleId)) + { return NULL; } - auto list = webkit_handler::get().h_list_ptr; bool success = false; + const auto moduleList = WebkitHandler::get().m_hookListPtr; - for (auto it = (*list).begin(); it != (*list).end();) + for (auto it = moduleList->begin(); it != moduleList->end();) { - if (it->id == js_index) + if (it->id == moduleId) { - it = list->erase(it); + it = moduleList->erase(it); success = true; } else ++it; @@ -117,76 +133,86 @@ PyObject* remove_browser_module(PyObject* self, PyObject* args) { return PyBool_FromLong(success); } -int add_module(PyObject* args, webkit_handler::type_t type) { - const char* item_str; +unsigned long long AddBrowserModule(PyObject* args, WebkitHandler::TagTypes type) +{ + const char* moduleItem; - if (!PyArg_ParseTuple(args, "s", &item_str)) { + if (!PyArg_ParseTuple(args, "s", &moduleItem)) + { return 0; } hook_tag++; - auto path = stream_buffer::steam_path() / "steamui" / item_str; + auto path = SystemIO::GetSteamPath() / "steamui" / moduleItem; - webkit_handler::get().h_list_ptr->push_back({ path.generic_string(), type, hook_tag }); + WebkitHandler::get().m_hookListPtr->push_back({ path.generic_string(), type, hook_tag }); return hook_tag; } -PyObject* add_browser_css(PyObject* self, PyObject* args) { return PyLong_FromLong(add_module(args, webkit_handler::type_t::STYLESHEET)); } -PyObject* add_browser_js(PyObject* self, PyObject* args) { return PyLong_FromLong(add_module(args, webkit_handler::type_t::JAVASCRIPT)); } +PyObject* AddBrowserCss(PyObject* self, PyObject* args) +{ + return PyLong_FromLong((long)AddBrowserModule(args, WebkitHandler::TagTypes::STYLESHEET)); +} + +PyObject* AddBrowserJs(PyObject* self, PyObject* args) +{ + return PyLong_FromLong((long)AddBrowserModule(args, WebkitHandler::TagTypes::JAVASCRIPT)); +} /* This portion of the API is undocumented but you can use it. */ -PyObject* change_plugin_status(PyObject* self, PyObject* args) +PyObject* TogglePluginStatus(PyObject* self, PyObject* args) { - console.log("updating a plugins status."); - const char* plugin_name; // string parameter - PyObject* new_status_obj; // bool parameter - int new_status; + Logger.Log("updating a plugins status."); - if (!PyArg_ParseTuple(args, "sO", &plugin_name, &new_status_obj)) { + const char* pluginName; + PyObject* statusObj; + int newToggleStatus; + + if (!PyArg_ParseTuple(args, "sO", &pluginName, &statusObj)) + { return NULL; // Parsing failed } - if (!PyBool_Check(new_status_obj)) { + if (!PyBool_Check(statusObj)) + { PyErr_SetString(PyExc_TypeError, "Second argument must be a boolean"); return NULL; } - // Convert the PyObject to a C boolean value - new_status = PyObject_IsTrue(new_status_obj); + newToggleStatus = PyObject_IsTrue(statusObj); - printf("Plugin Name: %s\n", plugin_name); - printf("New Status: %d\n", new_status); - - if (!new_status) { - console.log("requested to shutdown plugin [{}]", plugin_name); - std::thread(std::bind(&plugin_manager::shutdown_plugin, &plugin_manager::get(), plugin_name)).detach(); + if (!newToggleStatus) + { + Logger.Log("requested to shutdown plugin [{}]", pluginName); + std::thread(std::bind(&PythonManager::ShutdownPlugin, &PythonManager::GetInstance(), pluginName)).detach(); } - else { - console.log("requested to enable plugin [{}]", plugin_name); + else + { + Logger.Log("requested to enable plugin [{}]", pluginName); } Py_RETURN_NONE; } -PyMethodDef* get_module_handle() +PyMethodDef* GetMillenniumModule() { - static PyMethodDef module_methods[] = + static PyMethodDef moduleMethods[] = { - { "add_browser_css", add_browser_css, METH_VARARGS, NULL }, - { "add_browser_js", add_browser_js, METH_VARARGS, NULL }, - { "remove_browser_module", remove_browser_module, METH_VARARGS, NULL }, + { "add_browser_css", AddBrowserCss, METH_VARARGS, NULL }, + { "add_browser_js", AddBrowserJs, METH_VARARGS, NULL }, + { "remove_browser_module", RemoveBrowserModule, METH_VARARGS, NULL }, - { "get_user_settings", py_get_user_settings, METH_NOARGS, NULL }, - { "set_user_settings_key", py_set_user_settings_key, METH_VARARGS, NULL }, - { "version", py_get_millennium_version, METH_NOARGS, NULL }, - { "steam_path", py_steam_path, METH_NOARGS, NULL }, - { "call_frontend_method", (PyCFunction)call_frontend_method, METH_VARARGS | METH_KEYWORDS, NULL }, + { "get_user_settings", GetUserSettings, METH_NOARGS, NULL }, + { "set_user_settings_key", SetUserSettings, METH_VARARGS, NULL }, + { "version", GetVersionInfo, METH_NOARGS, NULL }, + { "steam_path", GetSteamPath, METH_NOARGS, NULL }, + { "call_frontend_method", (PyCFunction)CallFrontendMethod, METH_VARARGS | METH_KEYWORDS, NULL }, - { "change_plugin_status", change_plugin_status, METH_VARARGS, NULL }, + { "change_plugin_status", TogglePluginStatus, METH_VARARGS, NULL }, {NULL, NULL, 0, NULL} // Sentinel }; - return module_methods; + return moduleMethods; } diff --git a/src/__builtins__/executor.h b/src/__builtins__/executor.h index 5d791c7e..79de4246 100644 --- a/src/__builtins__/executor.h +++ b/src/__builtins__/executor.h @@ -1,4 +1,4 @@ #pragma once -#include +#include -PyMethodDef* get_module_handle(); \ No newline at end of file +PyMethodDef* GetMillenniumModule(); \ No newline at end of file diff --git a/src/__builtins__/incbin.h b/src/__builtins__/incbin.h deleted file mode 100644 index bd8d4cd3..00000000 --- a/src/__builtins__/incbin.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include - -INCTXT(frontend_boot, "bootstrap.js"); -static const char* fboot_script = gfrontend_bootData; \ No newline at end of file diff --git a/src/_win32/cmd.h b/src/_win32/cmd.h index 6fc96723..12891e79 100644 --- a/src/_win32/cmd.h +++ b/src/_win32/cmd.h @@ -2,6 +2,7 @@ #include #include #ifdef _WIN32 +#include #include #include #endif diff --git a/src/_win32/thread.h b/src/_win32/thread.h index 4ce92c60..701b8360 100644 --- a/src/_win32/thread.h +++ b/src/_win32/thread.h @@ -2,16 +2,18 @@ #include #include #include +#include -std::vector get_threads_sync() { - +std::vector DiscoverProcessThreads() +{ std::vector out; DWORD processId = GetCurrentProcessId(); DWORD currentThreadId = GetCurrentThreadId(); // Get ID of the current thread HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (hThreadSnap == INVALID_HANDLE_VALUE) { + if (hThreadSnap == INVALID_HANDLE_VALUE) + { std::cout << "Failed to create thread snapshot." << std::endl; return out; } @@ -19,65 +21,97 @@ std::vector get_threads_sync() { THREADENTRY32 te32; te32.dwSize = sizeof(THREADENTRY32); - if (!Thread32First(hThreadSnap, &te32)) { + if (!Thread32First(hThreadSnap, &te32)) + { std::cout << "Failed to retrieve the first thread." << std::endl; CloseHandle(hThreadSnap); return out; } - do { - if (te32.th32OwnerProcessID == processId && te32.th32ThreadID != currentThreadId) { + do + { + if (te32.th32OwnerProcessID == processId && te32.th32ThreadID != currentThreadId) + { out.push_back(te32.th32ThreadID); } - } while (Thread32Next(hThreadSnap, &te32)); + } + while (Thread32Next(hThreadSnap, &te32)); CloseHandle(hThreadSnap); return out; } -bool hook_steam_thread() +bool PauseSteamCore() { - std::vector threads = get_threads_sync(); - int froze = 0, failed = 0; + std::vector threads = DiscoverProcessThreads(); + + int frozeThreadRefCount = 0; + int failedFreezeRefCount = 0; - for (const auto& thread : threads) { + for (const auto& thread : threads) + { HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, thread); - if (hThread == NULL) { + if (hThread == NULL) + { std::cout << "Failed to open thread." << std::endl; - failed++; + failedFreezeRefCount++; continue; } // Suspend the thread - if (SuspendThread(hThread) == -1) { + if (SuspendThread(hThread) == -1) + { CloseHandle(hThread); - failed++; + failedFreezeRefCount++; } - else { - froze++; + else + { + frozeThreadRefCount++; break; // temp fix } } - console.log("[thread-hook] suspended main threads [{}/{}]", froze, failed); + + Logger.Log("[thread-hook] suspended main threads [{}/{}]", frozeThreadRefCount, failedFreezeRefCount); return true; } -bool unhook_steam_thread() +bool UnpauseSteamCore() { - std::vector threads = get_threads_sync(); + std::vector threads = DiscoverProcessThreads(); - for (const auto& thread : threads) { + for (const auto& thread : threads) + { HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, thread); - if (hThread == NULL) { + if (hThread == NULL) + { std::cout << "Failed to open thread." << std::endl; continue; } - // Suspend the thread - if (ResumeThread(hThread) == -1) { + if (ResumeThread(hThread) == -1) + { std::cout << "Failed to resume thread." << std::endl; CloseHandle(hThread); } } return true; +} + +std::string GetProcessName(DWORD processID) +{ + char szProcessName[MAX_PATH] = ""; + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); + + if (hProcess != NULL) + { + HMODULE hMod; + DWORD cbNeeded; + + if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) + { + GetModuleBaseNameA(hProcess, hMod, szProcessName, sizeof(szProcessName)); + } + CloseHandle(hProcess); + } + return szProcessName; } \ No newline at end of file diff --git a/src/core/backend/bind_stdout.hpp b/src/core/backend/bind_stdout.hpp deleted file mode 100644 index 223c400b..00000000 --- a/src/core/backend/bind_stdout.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include "co_spawn.hpp" -#include -#include - -extern "C" void custom_output(std::string pname, const char* message) { - if (std::string(message) != "\n" && std::string(message) != " ") console.py(pname, message); -} - -extern "C" void custom_err_output(std::string pname, const char* message) -{ -#ifdef _WIN32 - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, FOREGROUND_RED); -#endif - std::cout << message; -#ifdef _WIN32 - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); -#endif -} - -extern "C" { - static PyObject* custom_stdout_write(PyObject* self, PyObject* args) { - const char* message; - if (!PyArg_ParseTuple(args, "s", &message)) { return NULL; } - - custom_output(plugin_manager::get().get_plugin_name(PyThreadState_Get()), message); - return Py_BuildValue(""); - } - - static PyObject* custom_stderr_write(PyObject* self, PyObject* args) { - const char* message; - if (!PyArg_ParseTuple(args, "s", &message)) { return NULL; } - - custom_err_output(plugin_manager::get().get_plugin_name(PyThreadState_Get()), message); - return Py_BuildValue(""); - } - - static PyMethodDef module_methods[] = { - {"write", custom_stdout_write, METH_VARARGS, "Custom stdout write function"}, {NULL, NULL, 0, NULL} - }; - - static PyMethodDef stderr_methods[] = { - {"write", custom_stderr_write, METH_VARARGS, "Custom stderr write function"}, {NULL, NULL, 0, NULL} - }; - - static struct PyModuleDef custom_stdout_module = { PyModuleDef_HEAD_INIT, "hook_stdout", NULL, -1, module_methods }; - static struct PyModuleDef custom_stderr_module = { PyModuleDef_HEAD_INIT, "hook_stderr", NULL, -1, stderr_methods }; - - PyMODINIT_FUNC PyInit_custom_stderr(void) { - return PyModule_Create(&custom_stderr_module); - } - - PyMODINIT_FUNC PyInit_custom_stdout(void) { - return PyModule_Create(&custom_stdout_module); - } - - const void redirect_output() { - PyObject* sys = PyImport_ImportModule("sys"); - - PyObject_SetAttrString(sys, "stdout", PyImport_ImportModule("hook_stdout")); - PyObject_SetAttrString(sys, "stderr", PyImport_ImportModule("hook_stderr")); - } -} \ No newline at end of file diff --git a/src/core/backend/co_spawn.cpp b/src/core/backend/co_spawn.cpp deleted file mode 100644 index 9004d6cb..00000000 --- a/src/core/backend/co_spawn.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include -#include <__builtins__/executor.h> -#include -#include "co_spawn.hpp" -#include "bind_stdout.hpp" -#include -#include -#include - -static struct PyModuleDef module_def = { PyModuleDef_HEAD_INIT, "Millennium", NULL, -1, (PyMethodDef*)get_module_handle() }; - -PyMODINIT_FUNC PyInit_Millennium(void) { - return PyModule_Create(&module_def); -} - -plugin_manager::plugin_manager() -{ - console.head("init plugin manager:"); - this->instance_count = 0; - - // initialize global modules - console.log_item("hook", "locking standard output..."); PyImport_AppendInittab("hook_stdout", &PyInit_custom_stdout); - console.log_item("hook", "locking standard error..."); PyImport_AppendInittab("hook_stderr", &PyInit_custom_stderr); - console.log_item("hook", "inserting Millennium..."); PyImport_AppendInittab("Millennium", &PyInit_Millennium); - - console.log_item("status", "done appending init tabs!"); - console.log_item("status", "initializing python..."); - - PyStatus status; - PyConfig config; - PyConfig_InitPythonConfig(&config); - - /* Read all configuration at once */ - status = PyConfig_Read(&config); - - if (PyStatus_Exception(status)) { - console.err("couldn't read config {}", status.err_msg); - goto done; - } - - config.write_bytecode = 0; - -#ifdef _WIN32 - config.module_search_paths_set = 1; - - PyWideStringList_Append(&config.module_search_paths, std::wstring(pythonPath.begin(), pythonPath.end()).c_str()); - PyWideStringList_Append(&config.module_search_paths, std::wstring(pythonLibs.begin(), pythonLibs.end()).c_str()); -#endif - - status = Py_InitializeFromConfig(&config); - - if (PyStatus_Exception(status)) { - console.err("couldn't initialize from config {}", status.err_msg); - goto done; - } - - _save = PyEval_SaveThread(); -done: - PyConfig_Clear(&config); -} - -plugin_manager::~plugin_manager() -{ - console.err("::~plugin_manager() was destroyed."); - PyEval_RestoreThread(_save); - // Py_FinalizeEx(); -} - -bool plugin_manager::create_instance(stream_buffer::plugin_mgr::plugin_t& plugin, std::function callback) -{ - const std::string name = plugin.name; - this->instance_count++; - - auto thread = std::thread([this, name, callback, &plugin] - { - PyThreadState* auxts = PyThreadState_New(PyInterpreterState_Main()); - PyEval_RestoreThread(auxts); - PyThreadState* subts = Py_NewInterpreter(); - - PyThreadState_Swap(subts); - { - this->instances_.push_back({ name, subts, auxts }); - redirect_output(); - callback(plugin); - } - //Py_EndInterpreter(subts); closes intepretor - - PyThreadState_Clear(auxts); - PyThreadState_Swap(auxts); - PyThreadState_DeleteCurrent(); - }); - - this->thread_pool.push_back(std::move(thread)); - return true; -} - -bool plugin_manager::shutdown_plugin(std::string plugin_name) -{ - bool success = false; - - for (auto it = this->instances_.begin(); it != this->instances_.end(); ++it) { - const auto& [name, thread_ptr, _auxts] = *it; - - if (name != plugin_name) { - continue; - } - - success = true; - - if (thread_ptr == nullptr) { - console.err(fmt::format("couldn't get thread state ptr from plugin [{}], maybe it crashed or exited early? ", plugin_name)); - success = false; - } - - PyThreadState* auxts = PyThreadState_New(PyInterpreterState_Main()); - PyEval_RestoreThread(auxts); - PyGILState_STATE gstate = PyGILState_Ensure(); - PyThreadState_Swap(thread_ptr); - - if (thread_ptr == NULL) { - console.err("script execution was queried but the receiving parties thread state was nullptr"); - success = false; - } - - if (PyRun_SimpleString("plugin._unload()") != 0) { - PyErr_Print(); - console.err("millennium failed to shutdown [{}]", plugin_name); - success = false; - } - - // close the python interpretor. GIL must be held and the interpretor must be IDLE - Py_EndInterpreter(thread_ptr); - PyThreadState_Clear(auxts); - PyThreadState_Swap(auxts); - PyGILState_Release(gstate); - PyThreadState_DeleteCurrent(); - - this->instances_.erase(it); - break; - } - - console.log("Successfully shut down [{}].\nRemaining: ", plugin_name); - - for (const auto& plugin: this->instances_) { - console.log(plugin.name); - } - - javascript::reload_shared_context(); - return true; -} - -void plugin_manager::wait_for_exit() -{ - for (auto& thread : this->thread_pool) { - thread.detach(); - } -} - -thread_state plugin_manager::get_thread_state(std::string pluginName) -{ - for (const auto& [name, thread_ptr, auxts] : this->instances_) { - if (name == pluginName) { - return { - name, thread_ptr, auxts - }; - } - } - return {}; -} - -std::string plugin_manager::get_plugin_name(PyThreadState* thread) -{ - for (const auto& [name, thread_ptr, auxts] : this->instances_) { - - if (thread_ptr == nullptr) { - console.err("thread_ptr was nullptr"); - return {}; - } - - if (thread_ptr == thread) return name; - } - return {}; -} \ No newline at end of file diff --git a/src/core/backend/co_spawn.hpp b/src/core/backend/co_spawn.hpp deleted file mode 100644 index 0bc2c665..00000000 --- a/src/core/backend/co_spawn.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include - -struct thread_state { - std::string name; - PyThreadState* thread_state; - PyThreadState* auxts; -}; - -class plugin_manager -{ -private: - PyThreadState* _save; - short instance_count; - - std::vector thread_pool; - std::vector instances_; - -public: - plugin_manager(); - ~plugin_manager(); - - bool create_instance(stream_buffer::plugin_mgr::plugin_t& plugin, std::function callback); - void wait_for_exit(); - - thread_state get_thread_state(std::string pluginName); - std::string get_plugin_name(PyThreadState* thread); - - bool shutdown_plugin(std::string plugin_name); - - static plugin_manager& get() { - static plugin_manager instance; - return instance; - } -}; \ No newline at end of file diff --git a/src/core/co_initialize/co_stub.cc b/src/core/co_initialize/co_stub.cc index 5eb856de..055c0ffe 100644 --- a/src/core/co_initialize/co_stub.cc +++ b/src/core/co_initialize/co_stub.cc @@ -2,14 +2,13 @@ #include #include #include -#include +#include #include #include #include -#include <__builtins__/incbin.h> #include -constexpr const char* bootstrap_module = R"( +constexpr const char* bootstrapModule = R"( /** * This module is in place to load all plugin frontends * @ src\__builtins__\bootstrap.js @@ -74,8 +73,6 @@ function connectIPCWebSocket() { connectIPCWebSocket(); -// var originalConsoleLog = console.log; - /** * seemingly fully functional, though needs a rewrite as its tacky * @todo hook a function that specifies when react is loaded, or use other methods to figure out when react has loaded. @@ -100,147 +97,155 @@ __millennium_module_init__() /// @brief sets up the python interpretor to use virtual environment site packages, aswell as custom python path. /// @param spath /// @return void -const void add_site_packages(std::vector spath) +const void AddSitePackages(std::vector spath) { - PyObject *sys_module = PyImport_ImportModule("sys"); - if (!sys_module) { - console.err("couldn't import sysmodule"); + PyObject *sysModule = PyImport_ImportModule("sys"); + if (!sysModule) + { + Logger.Error("couldn't import sysmodule"); return; } - PyObject *sys_path = PyObject_GetAttrString(sys_module, "path"); - if (sys_path) { + PyObject *systemPath = PyObject_GetAttrString(sysModule, "path"); + + if (systemPath) + { #ifdef _WIN32 - PyList_SetSlice(sys_path, 0, PyList_Size(sys_path), NULL); + // Wipe the system path clean when on windows + // - Prevents clashing installed python versions + PyList_SetSlice(systemPath, 0, PyList_Size(systemPath), NULL); #endif - for (const auto& path : spath) { - PyList_Append(sys_path, PyUnicode_FromString(path.c_str())); + for (const auto& systemPathItem : spath) + { + PyList_Append(systemPath, PyUnicode_FromString(systemPathItem.generic_string().c_str())); } - Py_DECREF(sys_path); + Py_DECREF(systemPath); } - Py_DECREF(sys_module); + Py_DECREF(sysModule); } /// @brief initializes the current plugin. creates a plugin instance and calls _load() /// @param global_dict -void initialize_plugin(PyObject* global_dict) { - PyObject *plugin_class = PyDict_GetItemString(global_dict, "Plugin"); +void StartPluginBackend(PyObject* global_dict) +{ + PyObject *pluginComponent = PyDict_GetItemString(global_dict, "Plugin"); - if (!plugin_class || !PyCallable_Check(plugin_class)) { - PyErr_Print(); return; + if (!pluginComponent || !PyCallable_Check(pluginComponent)) + { + PyErr_Print(); + return; } - PyObject *plugin_instance = PyObject_CallObject(plugin_class, NULL); + PyObject *pluginComponentInstance = PyObject_CallObject(pluginComponent, NULL); - if (!plugin_instance) { - PyErr_Print(); return; + if (!pluginComponentInstance) + { + PyErr_Print(); + return; } - PyDict_SetItemString(global_dict, "plugin", plugin_instance); - PyObject *load_method = PyObject_GetAttrString(plugin_instance, "_load"); + PyDict_SetItemString(global_dict, "plugin", pluginComponentInstance); + PyObject *loadMethodAttribute = PyObject_GetAttrString(pluginComponentInstance, "_load"); - if (!load_method || !PyCallable_Check(load_method)) { - PyErr_Print(); return; + if (!loadMethodAttribute || !PyCallable_Check(loadMethodAttribute)) + { + PyErr_Print(); + return; } - PyObject_CallObject(load_method, NULL); - Py_DECREF(load_method); - Py_DECREF(plugin_instance); + PyObject_CallObject(loadMethodAttribute, NULL); + Py_DECREF(loadMethodAttribute); + Py_DECREF(pluginComponentInstance); } -void plugin_start_cb(stream_buffer::plugin_mgr::plugin_t& plugin) { +void BackendStartCallback(SettingsStore::PluginTypeSchema& plugin) { - const std::string _module = plugin.backend_abs.generic_string(); - const std::string base = plugin.base_dir.generic_string(); + const std::string backendMainModule = plugin.backendAbsoluteDirectory.generic_string(); - PyObject* global_dict = PyModule_GetDict((PyObject*)PyImport_AddModule("__main__")); + PyObject* globalDictionary = PyModule_GetDict((PyObject*)PyImport_AddModule("__main__")); + PyDict_SetItemString(globalDictionary, "MILLENNIUM_PLUGIN_SECRET_NAME", PyUnicode_FromString(plugin.pluginName.c_str())); - // bind the plugin name to the relative interpretor - PyDict_SetItemString(global_dict, "MILLENNIUM_PLUGIN_SECRET_NAME", PyUnicode_FromString(plugin.name.c_str())); - - std::vector sys_paths = { - fmt::format("{}/backend", base), -#ifdef _WIN32 - pythonPath, pythonLibs -#endif + std::vector sysPath = { + plugin.pluginBaseDirectory / "backend", pythonPath, pythonLibs }; // include venv paths to interpretor - if (plugin.pjson.contains("venv") && plugin.pjson["venv"].is_string()) + if (plugin.pluginJson.contains("venv") && plugin.pluginJson["venv"].is_string()) { - const auto venv = fmt::format("{}/{}", base, plugin.pjson["venv"].get()); + const auto pluginVirtualEnv = plugin.pluginBaseDirectory / plugin.pluginJson["venv"]; // configure virtual environment for the current plugin - sys_paths.push_back(fmt::format("{}/Lib/site-packages", venv)); - sys_paths.push_back(fmt::format("{}/Lib/site-packages/win32", venv)); - sys_paths.push_back(fmt::format("{}/Lib/site-packages/win32/lib", venv)); - sys_paths.push_back(fmt::format("{}/Lib/site-packages/Pythonwin", venv)); + sysPath.push_back(pluginVirtualEnv / "Lib" / "site-packages"); + sysPath.push_back(pluginVirtualEnv / "Lib" / "site-packages" / "win32"); + sysPath.push_back(pluginVirtualEnv / "Lib" / "site-packages" / "win32" / "Lib"); + sysPath.push_back(pluginVirtualEnv / "Lib" / "site-packages" / "Pythonwin"); } - add_site_packages(sys_paths); + AddSitePackages(sysPath); -#ifdef __linux__ - PyRun_SimpleString("import sys\nprint(sys.path)"); -#endif - - PyObject *obj = Py_BuildValue("s", _module.c_str()); - FILE *file = _Py_fopen_obj(obj, "r+"); + PyObject *mainModuleObj = Py_BuildValue("s", backendMainModule.c_str()); + FILE *mainModuleFilePtr = _Py_fopen_obj(mainModuleObj, "r+"); - if (file == NULL) { - console.err("failed to fopen file @ {}", _module); + if (mainModuleFilePtr == NULL) + { + Logger.Error("failed to fopen file @ {}", backendMainModule); return; } - if (PyRun_SimpleFile(file, _module.c_str()) != 0) { - console.err("millennium failed to startup [{}]", plugin.name); + if (PyRun_SimpleFile(mainModuleFilePtr, backendMainModule.c_str()) != 0) + { + Logger.Error("millennium failed to startup [{}]", plugin.pluginName); return; } - initialize_plugin(global_dict); + StartPluginBackend(globalDictionary); } -const std::string make_script(std::string filename) { +const std::string ConstructScriptElement(std::string filename) +{ return fmt::format("\nif (!document.querySelectorAll(`script[src='{}'][type='module']`).length) {{ document.head.appendChild(Object.assign(document.createElement('script'), {{ src: '{}', type: 'module', id: 'millennium-injected' }})); }}", filename, filename); } -const std::string setup_bootstrap_module() { - - auto plugins = stream_buffer::plugin_mgr::parse_all(); - std::string import_table; - - for (auto& plugin : plugins) { +const std::string ConstructOnLoadModule() +{ + std::unique_ptr settingsStore = std::make_unique(); + std::vector plugins = settingsStore->ParseAllPlugins(); - if (!stream_buffer::plugin_mgr::is_enabled(plugin.name)) { - - console.log("bootstrap skipping frontend {} [disabled]", plugin.name); + std::string scriptImportTable; + + for (auto& plugin : plugins) + { + if (!settingsStore->IsEnabledPlugin(plugin.pluginName)) + { + Logger.Log("bootstrap skipping frontend {} [disabled]", plugin.pluginName); continue; } - import_table.append(make_script(fmt::format("https://steamloopback.host/{}", plugin.frontend_abs))); + + scriptImportTable.append(ConstructScriptElement(fmt::format("https://steamloopback.host/{}", plugin.frontendAbsoluteDirectory))); } - std::string script = bootstrap_module; - std::size_t pos = script.find("SCRIPT_RAW_TEXT"); + std::string scriptLoader = bootstrapModule; + std::size_t importTablePos = scriptLoader.find("SCRIPT_RAW_TEXT"); - if (pos != std::string::npos) { - // Replace the found "SCRIPT_RAW_TEXT" with import_table - script.replace(pos, strlen("SCRIPT_RAW_TEXT"), import_table); + if (importTablePos != std::string::npos) + { + scriptLoader.replace(importTablePos, strlen("SCRIPT_RAW_TEXT"), scriptImportTable); } - else { - console.err("couldn't shim bootstrap module with plugin frontends. " + else + { + Logger.Error("couldn't shim bootstrap module with plugin frontends. " "SCRIPT_RAW_TEXT was not found in the target string"); } - return script; + return scriptLoader; } -const void inject_shims(void) +const void InjectFrontendShims(void) { - tunnel::post_shared({ {"id", 1 }, {"method", "Page.enable"} }); - tunnel::post_shared({ {"id", 8567}, {"method", "Page.addScriptToEvaluateOnNewDocument"}, {"params", {{ "source", setup_bootstrap_module() }}} }); - // tunnel::post_shared({ {"id", 70 }, {"method", "Debugger.enable"} }); - // tunnel::post_shared({ {"id", 68 }, {"method", "Runtime.enable"} }); - // tunnel::post_shared({ {"id", 68 }, {"method", "Fetch.enable"} }); - tunnel::post_shared({ {"id", 69 }, {"method", "Page.reload"} }); - // tunnel::post_shared({ {"id", 71 }, {"method", "Debugger.pause"} }); + short messageId = 0; + + Sockets::PostShared({ {"id", messageId++ }, {"method", "Page.enable"} }); + Sockets::PostShared({ {"id", messageId++ }, {"method", "Page.addScriptToEvaluateOnNewDocument"}, {"params", {{ "source", ConstructOnLoadModule() }}} }); + Sockets::PostShared({ {"id", messageId++ }, {"method", "Page.reload"} }); } diff --git a/src/core/ffi/c_python.cpp b/src/core/ffi/c_python.cpp new file mode 100644 index 00000000..58993534 --- /dev/null +++ b/src/core/ffi/c_python.cpp @@ -0,0 +1,136 @@ +#include "ffi.hpp" +#include +#include + +std::string Python::ConstructFunctionCall(nlohmann::basic_json<> data) +{ + std::string strFunctionCall = data["methodName"]; + strFunctionCall += "("; + + if (data.contains("argumentList")) + { + int index = 0, argumentLength = data["argumentList"].size(); + + for (auto it = data["argumentList"].begin(); it != data["argumentList"].end(); ++it, ++index) + { + auto& key = it.key(); + auto& value = it.value(); + + if (value.is_boolean()) + { + strFunctionCall += fmt::format("{}={}", key, value ? "True" : "False"); + } + else + { + strFunctionCall += fmt::format("{}={}", key, value.dump()); + } + + if (index < argumentLength - 1) + { + strFunctionCall += ", "; + } + } + } + strFunctionCall += ")"; + return strFunctionCall; +} + +const Python::EvalResult EvaluatePython(std::string plugin_name, std::string script) +{ + // Execute the script and assign the return value to a variable + PyObject* global_dict = PyModule_GetDict(PyImport_AddModule("__main__")); + PyObject* rv_object = PyRun_String(script.c_str(), Py_eval_input, global_dict, global_dict); + + // An exception occurred + if (!rv_object && PyErr_Occurred()) + { + PyObject* typeObj, *valueObj, *traceBackObj; + PyErr_Fetch(&typeObj, &valueObj, &traceBackObj); + PyErr_NormalizeException(&typeObj, &valueObj, &traceBackObj); + + PyObject* pStrErrorMessage = PyObject_Str(valueObj); + if (pStrErrorMessage) + { + const char* errorMessage = PyUnicode_AsUTF8(pStrErrorMessage); + + if (errorMessage) + { + Logger.Error(fmt::format("[interop-dispatch] Error calling backend function: {}", errorMessage)); + return { errorMessage, Python::ReturnTypes::Error }; + } + else + { + return { "unknown error", Python::ReturnTypes::Error}; + } + } + } + + if (rv_object == nullptr || rv_object == Py_None) + { + return { "0", Python::ReturnTypes::Integer }; // whitelist NoneType + } + if (PyBool_Check(rv_object)) + { + return { PyLong_AsLong(rv_object) == 0 ? "False" : "True", Python::ReturnTypes::Boolean }; + } + else if (PyLong_Check(rv_object)) + { + return { std::to_string(PyLong_AsLong(rv_object)), Python::ReturnTypes::Integer }; + } + else if (PyUnicode_Check(rv_object)) + { + return { PyUnicode_AsUTF8(rv_object), Python::ReturnTypes::String }; + } + + PyObject* objectType = PyObject_Type(rv_object); + PyObject* strObjectType = PyObject_Str(objectType); + const char* strTypeName = PyUnicode_AsUTF8(strObjectType); + PyErr_Clear(); // Clear any Python exception + Py_XDECREF(objectType); + Py_XDECREF(strObjectType); + + return { fmt::format("Millennium expected return type [int, str, bool] but received [{}]", strTypeName) , Python::ReturnTypes::Error }; +} + +Python::EvalResult Python::LockGILAndEvaluate(std::string plugin_name, std::string script) +{ + auto [pluginName, threadState] = PythonManager::GetInstance().GetPythonThreadStateFromName(plugin_name); + + if (threadState == nullptr) + { + Logger.Error(fmt::format("couldn't get thread state ptr from plugin [{}], maybe it crashed or exited early? ", plugin_name)); + return { "overstepped partying thread state", Error }; + } + + std::shared_ptr pythonGilLock = std::make_shared(); + + pythonGilLock->HoldAndLockGILOnThread(threadState); + + if (threadState == NULL) { + Logger.Error("script execution was queried but the receiving parties thread state was nullptr"); + return { "thread state was nullptr", Error }; + } + + Python::EvalResult response = EvaluatePython(plugin_name, script); + + pythonGilLock->ReleaseAndUnLockGIL(); + return response; +} + +void Python::LockGILAndDiscardEvaluate(std::string plugin_name, std::string script) +{ + auto [pluginName, state] = PythonManager::GetInstance().GetPythonThreadStateFromName(plugin_name); + + if (state == nullptr) + { + Logger.Error(fmt::format("couldn't get thread state ptr from plugin [{}], maybe it crashed or exited early? ", plugin_name)); + return; + } + + std::shared_ptr pythonGilLock = std::make_shared(); + pythonGilLock->HoldAndLockGILOnThread(state); + + PyRun_SimpleString(script.c_str()); + + pythonGilLock->ReleaseAndUnLockGIL(); +} diff --git a/src/core/ffi/ffi.hpp b/src/core/ffi/ffi.hpp new file mode 100644 index 00000000..3841c48e --- /dev/null +++ b/src/core/ffi/ffi.hpp @@ -0,0 +1,104 @@ +#pragma once +#include +#include +#include +#include +#include + +class PythonGIL : public std::enable_shared_from_this +{ +private: + PyGILState_STATE m_interpreterGIL{}; + PyThreadState* m_interpreterThreadState = nullptr; + PyInterpreterState* m_mainInterpreter = nullptr; + +public: + const void HoldAndLockGIL(); + const void HoldAndLockGILOnThread(PyThreadState* threadState); + const void ReleaseAndUnLockGIL(); + + PythonGIL(); + ~PythonGIL(); +}; + +namespace Python { + + enum ReturnTypes { + Boolean, + String, + Integer, + Error, + Unknown // non-atomic ADT's + }; + + struct EvalResult { + std::string plain; + ReturnTypes type; + }; + + std::string ConstructFunctionCall(nlohmann::basic_json<> data); + + EvalResult LockGILAndEvaluate(std::string pluginName, std::string script); + void LockGILAndDiscardEvaluate(std::string pluginName, std::string script); +} + +namespace JavaScript { + + using EventHandler = std::function; + + class SharedJSMessageEmitter { + private: + SharedJSMessageEmitter() {} + std::unordered_map>> events; + int nextListenerId = 0; + + public: + SharedJSMessageEmitter(const SharedJSMessageEmitter&) = delete; + SharedJSMessageEmitter& operator=(const SharedJSMessageEmitter&) = delete; + + static SharedJSMessageEmitter& InstanceRef() + { + static SharedJSMessageEmitter InstanceRef; + return InstanceRef; + } + + int OnMessage(const std::string& event, EventHandler handler) + { + int listenerId = nextListenerId++; + events[event].push_back(std::make_pair(listenerId, handler)); + return listenerId; + } + + void RemoveListener(const std::string& event, int listenerId) + { + auto it = events.find(event); + if (it != events.end()) + { + auto& handlers = it->second; + handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [listenerId](const auto& handler) { return handler.first == listenerId; }), handlers.end()); + } + } + + void EmitMessage(const std::string& event, const nlohmann::json& data) + { + auto it = events.find(event); + if (it != events.end()) + { + const auto& handlers = it->second; + for (const auto& handler : handlers) + { + handler.second(data, handler.first); + } + } + } + }; + + struct JsFunctionConstructTypes + { + std::string pluginName, type; + }; + + const std::string ConstructFunctionCall(const char* value, const char* methodName, std::vector params); + + PyObject* EvaluateFromSocket(std::string script); +} \ No newline at end of file diff --git a/src/core/ffi/gil.cc b/src/core/ffi/gil.cc new file mode 100644 index 00000000..ad9bb6f2 --- /dev/null +++ b/src/core/ffi/gil.cc @@ -0,0 +1,35 @@ +#include "ffi.hpp" + +PythonGIL::PythonGIL() +{ + m_mainInterpreter = PyInterpreterState_Main(); + m_interpreterThreadState = PyThreadState_New(m_mainInterpreter); +} + +const void PythonGIL::HoldAndLockGIL() +{ + PyEval_RestoreThread(m_interpreterThreadState); + m_interpreterGIL = PyGILState_Ensure(); +} + +const void PythonGIL::HoldAndLockGILOnThread(PyThreadState* threadState) +{ + PyEval_RestoreThread(m_interpreterThreadState); + m_interpreterGIL = PyGILState_Ensure(); + PyThreadState_Swap(threadState); +} + +PythonGIL::~PythonGIL() +{ + PyThreadState_Clear(m_interpreterThreadState); + PyThreadState_Swap(m_interpreterThreadState); + + PyGILState_Release(m_interpreterGIL); + PyThreadState_DeleteCurrent(); +} + +const void PythonGIL::ReleaseAndUnLockGIL() +{ + std::shared_ptr self = shared_from_this(); + self.reset(); +} \ No newline at end of file diff --git a/src/core/ffi/javascript.cpp b/src/core/ffi/javascript.cpp new file mode 100644 index 00000000..6d1afa7d --- /dev/null +++ b/src/core/ffi/javascript.cpp @@ -0,0 +1,119 @@ +#include "ffi.hpp" +#include +#include +#include + +struct EvalResult { + nlohmann::basic_json<> json; + bool successfulCall; +}; + +#define SHARED_JS_EVALUATE_ID 54999 + +const EvalResult ExecuteOnSharedJsContext(std::string javaScriptEval) +{ + // Create a promise to store the result + std::promise promise; + + bool messageSendSuccess = Sockets::PostShared(nlohmann::json({ + { "id", SHARED_JS_EVALUATE_ID }, + { "method", "Runtime.evaluate" }, + { "params", { + { "expression", javaScriptEval }, + { "awaitPromise", true } + }} + })); + + if (!messageSendSuccess) + { + throw std::runtime_error("couldn't send message to socket"); + } + + // Register message handler to capture the result + JavaScript::SharedJSMessageEmitter::InstanceRef().OnMessage("msg", [&](const nlohmann::json& eventMessage, int listenerId) + { + try + { + if (eventMessage.contains("id") && eventMessage["id"] == SHARED_JS_EVALUATE_ID) + { + if (eventMessage["result"].contains("exceptionDetails")) + { + promise.set_value({ + eventMessage["result"]["exceptionDetails"]["exception"]["description"], false + }); + return; + } + + promise.set_value({ eventMessage["result"]["result"], true }); + + // Set the value of the promise with the result + JavaScript::SharedJSMessageEmitter::InstanceRef().RemoveListener("msg", listenerId); + } + } + catch (nlohmann::detail::exception& ex) + { + Logger.Error(fmt::format("JavaScript::SharedJSMessageEmitter error -> {}", ex.what())); + } + catch (std::future_error& ex) + { + Logger.Error(ex.what()); + } + }); + + // Return the result obtained from the promise + return promise.get_future().get(); +} + +const std::string JavaScript::ConstructFunctionCall(const char* plugin, const char* methodName, std::vector fnParams) +{ + // plugin module exports are stored in a map on the window object PLUGIN_LIST + std::string strFunctionFormatted = fmt::format("PLUGIN_LIST['{}'].{}(", plugin, methodName); + + for (auto iterator = fnParams.begin(); iterator != fnParams.end(); ++iterator) + { + auto& param = *iterator; + + if (param.type == "str") { strFunctionFormatted += fmt::format("\"{}\"", param.pluginName); } + else if (param.type == "bool") { strFunctionFormatted += param.pluginName == "True" ? "true" : "false"; } //python decided to be quirky with caps + else { strFunctionFormatted += param.pluginName; } + + if (std::next(iterator) != fnParams.end()) { + strFunctionFormatted += ", "; + } + } + strFunctionFormatted += ");"; return strFunctionFormatted; +} + +PyObject* JavaScript::EvaluateFromSocket(std::string script) +{ + try + { + EvalResult response = ExecuteOnSharedJsContext(script); + + if (!response.successfulCall) { + PyErr_SetString(PyExc_RuntimeError, response.json.get().c_str()); + return NULL; + } + + std::string type = response.json["type"]; + + if (type == "string") return PyUnicode_FromString(response.json["value"].get().c_str()); + else if (type == "bool") return PyBool_FromLong(response.json["value"]); + else if (type == "int") return PyLong_FromLong(response.json["value"]); + else + return PyUnicode_FromString("Js function returned unaccepted type. accepted types [string, bool, int]"); + + } + catch (nlohmann::detail::exception& ex) + { + std::string message = fmt::format("Millennium couldn't decode the response from {}, reason: {}", script, ex.what()); + return PyUnicode_FromString(message.c_str()); + } + catch (std::exception&) + { + PyErr_SetString(PyExc_ConnectionError, "frontend is not loaded!"); + return NULL; + } + + Py_RETURN_NONE; +} diff --git a/src/core/hooks/web_load.cc b/src/core/hooks/web_load.cc index 9b4d298f..92418041 100644 --- a/src/core/hooks/web_load.cc +++ b/src/core/hooks/web_load.cc @@ -1,150 +1,202 @@ #include "web_load.h" #include #include -#include +#include #include #include -webkit_handler webkit_handler::get() { - static webkit_handler _w; - return _w; +WebkitHandler WebkitHandler::get() +{ + static WebkitHandler webkitHandler; + return webkitHandler; } -void webkit_handler::setup_hook() +void WebkitHandler::SetupGlobalHooks() { - tunnel::post_global({ + Sockets::PostGlobal({ { "id", 3242 }, { "method", "Fetch.enable" }, { "params", { { "patterns", { { { "urlPattern", "https://*.*.com/public/shared/css/buttons.css*" }, { "resourceType", "Stylesheet" }, { "requestStage", "Response" } }, { { "urlPattern", "https://*.*.com/public/shared/javascript/shared_global.js*" }, { "resourceType", "Script" }, { "requestStage", "Response" } }, - { { "urlPattern", fmt::format("{}*", this->virt_url) }, { "requestStage", "Request" } } + { { "urlPattern", fmt::format("{}*", this->m_javaScriptVirtualUrl) }, { "requestStage", "Request" } } } }}} }); } -bool webkit_handler::is_ftp_call(nlohmann::basic_json<> message) { +bool WebkitHandler::IsGetBodyCall(nlohmann::basic_json<> message) +{ return message["params"]["request"]["url"].get() - .find(this->virt_url) != std::string::npos; + .find(this->m_javaScriptVirtualUrl) != std::string::npos; } -std::string webkit_handler::js_hook(std::string body) +std::string WebkitHandler::HandleJsHook(std::string body) { - std::string inject; - for (auto& item : *h_list_ptr) + std::string scriptTagInject; + + for (auto& hookItem : *m_hookListPtr) { - if (item.type != type_t::JAVASCRIPT) { + if (hookItem.type != TagTypes::JAVASCRIPT) + { continue; } - std::filesystem::path relativePath = std::filesystem::relative(item.path, stream_buffer::steam_path() / "steamui"); - inject.append(fmt::format( + std::filesystem::path relativePath = std::filesystem::relative(hookItem.path, SystemIO::GetSteamPath() / "steamui"); + + scriptTagInject.append(fmt::format( "document.head.appendChild(Object.assign(document.createElement('script'), {{ src: '{}{}', type: 'module', id: 'millennium-injected' }}));\n", - this->virt_url, relativePath.generic_string() + this->m_javaScriptVirtualUrl, relativePath.generic_string() )); } - return inject + body; + return scriptTagInject + body; } -std::string webkit_handler::css_hook(std::string body) +std::string WebkitHandler::HandleCssHook(std::string body) { - std::string inject; - for (auto& item : *h_list_ptr) + std::string styleTagInject; + + for (auto& hookItem : *m_hookListPtr) { - if (item.type != type_t::STYLESHEET) { + if (hookItem.type != TagTypes::STYLESHEET) + { continue; } - std::filesystem::path relativePath = std::filesystem::relative(item.path, stream_buffer::steam_path() / "steamui"); - inject.append(fmt::format("@import \"{}{}\";\n", this->looback, relativePath.generic_string())); + + std::filesystem::path relativePath = std::filesystem::relative(hookItem.path, SystemIO::GetSteamPath() / "steamui"); + styleTagInject.append(fmt::format("@import \"{}{}\";\n", this->m_steamLoopback, relativePath.generic_string())); } - return inject + body; + return styleTagInject + body; } -void webkit_handler::handle_hook(nlohmann::basic_json<> message) +std::filesystem::path WebkitHandler::ConvertToLoopBack(std::string requestUrl) { - try { - static short id = -69; + std::size_t pos = requestUrl.find(this->m_javaScriptVirtualUrl); - if (message["method"] == "Fetch.requestPaused" && !this->is_ftp_call(message)) - { - id--; - (*request_map).push_back({ id, message["params"]["requestId"].get(), message["params"]["resourceType"].get() }); - - tunnel::post_global({ - { "id", id }, - { "method", "Fetch.getResponseBody" }, - { "params", { { "requestId", message["params"]["requestId"] } }} - }); - } + if (pos != std::string::npos) + { + requestUrl.erase(pos, std::string(this->m_javaScriptVirtualUrl).length()); + } + + return SystemIO::GetSteamPath() / "steamui" / requestUrl; +} + +void WebkitHandler::RetrieveRequestFromDisk(nlohmann::basic_json<> message) +{ + std::filesystem::path localFilePath = this->ConvertToLoopBack(message["params"]["request"]["url"]); + std::ifstream localFileStream(localFilePath); - else if (message["method"] == "Fetch.requestPaused" && this->is_ftp_call(message)) { + bool failed = !localFileStream.is_open(); - std::string requestUrl = message["params"]["request"]["url"]; - std::size_t pos = requestUrl.find(this->virt_url); + if (failed) + { + Logger.Error("failed to open file [readJsonSync]"); + } - if (pos != std::string::npos) { requestUrl.erase(pos, std::string(this->virt_url).length()); } + const std::string fileContent((std::istreambuf_iterator(localFileStream)), std::istreambuf_iterator()); - const auto path = stream_buffer::steam_path() / "steamui" / requestUrl; - bool failed = false; - - std::ifstream file(path.string()); - if (!file.is_open()) { - console.err("failed to open file [readJsonSync]"); - failed = true; - } + const std::string successMessage = "MILLENNIUM-VIRTUAL"; + const std::string failedMessage = fmt::format("Millennium couldn't read {}", localFilePath.string()); + + const nlohmann::json responseHeaders = nlohmann::json::array + ({ + { {"name", "Access-Control-Allow-Origin"}, {"value", "*"} }, + { {"name", "Content-Type"}, {"value", "application/javascript"} } + }); + + Sockets::PostGlobal({ + { "id", 63453 }, + { "method", "Fetch.fulfillRequest" }, + { "params", { + { "requestId", message["params"]["requestId"] }, + { "responseCode", failed ? 404 : 200 }, + { "responsePhrase", failed ? failedMessage : successMessage }, + { "responseHeaders", responseHeaders }, + { "body", Base64Encode(fileContent) } + }} + }); +} - std::string fileContent((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - auto status = failed ? "millennium couldn't read " + path.generic_string() : "MILLENNIUM-VIRTUAL"; - - tunnel::post_global({ - { "id", 63453 }, - { "method", "Fetch.fulfillRequest" }, - { "params", { - { "requestId", message["params"]["requestId"] }, - { "responseCode", failed ? 404 : 200 }, - { "responsePhrase", status }, - - { "responseHeaders", nlohmann::json::array({ - { {"name", "Access-Control-Allow-Origin"}, {"value", "*"} }, - { {"name", "Content-Type"}, {"value", "application/javascript"} } - }) }, - { "body", base64_encode(fileContent) } - }} - }); +void WebkitHandler::GetResponseBody(nlohmann::basic_json<> message) +{ + hookMessageId -= 1; + m_requestMap->push_back({ hookMessageId, message["params"]["requestId"], message["params"]["resourceType"] }); + + Sockets::PostGlobal({ + { "id", hookMessageId }, + { "method", "Fetch.getResponseBody" }, + { "params", { { "requestId", message["params"]["requestId"] } }} + }); +} + +void WebkitHandler::HandleHooks(nlohmann::basic_json<> message) +{ + for (auto requestIterator = m_requestMap->begin(); requestIterator != m_requestMap->end();) + { + auto [id, request_id, type] = (*requestIterator); + + if (message["id"] != id || !message["result"]["base64Encoded"]) + { + requestIterator++; + continue; } - for (auto it = request_map->begin(); it != request_map->end();) + std::string hookedBodyResponse = {}; + + if (type == "Script") + { + hookedBodyResponse = this->HandleJsHook(base64_decode(message["result"]["body"])); + } + else if (type == "Stylesheet") { - auto [id, request_id, type] = (*it); + hookedBodyResponse = this->HandleCssHook(base64_decode(message["result"]["body"])); + } - if (message["id"] != id || !message["result"]["base64Encoded"]) { - it++; continue; - } + Sockets::PostGlobal({ + { "id", 63453 }, + { "method", "Fetch.fulfillRequest" }, + { "params", { + { "requestId", request_id }, + { "responseCode", 200 }, + { "body", Base64Encode(hookedBodyResponse) } + }} + }); + + requestIterator = m_requestMap->erase(requestIterator); + } +} - std::string body = {}; - if (type == "Script" ) body = this->js_hook (base64_decode(message["result"]["body"])); - else if (type == "Stylesheet") body = this->css_hook(base64_decode(message["result"]["body"])); - - tunnel::post_global({ - { "id", 63453 }, - { "method", "Fetch.fulfillRequest" }, - { "params", { - { "requestId", request_id }, - { "responseCode", 200 }, - { "body", base64_encode(body) } - }} - }); - - it = request_map->erase(it); +void WebkitHandler::DispatchSocketMessage(nlohmann::basic_json<> message) +{ + try + { + if (message["method"] == "Fetch.requestPaused") + { + switch (this->IsGetBodyCall(message)) + { + case true: + { + this->RetrieveRequestFromDisk(message); + break; + } + case false: + { + this->GetResponseBody(message); + break; + } + } } + + this->HandleHooks(message); } - catch (const nlohmann::detail::exception& ex) { - console.err("error hooking webkit -> {}", ex.what()); + catch (const nlohmann::detail::exception& ex) + { + Logger.Error("error hooking WebKit -> {}", ex.what()); } - catch (const std::exception& ex) { - console.err("error hooking webkit -> {}", ex.what()); + catch (const std::exception& ex) + { + Logger.Error("error hooking WebKit -> {}", ex.what()); } } diff --git a/src/core/hooks/web_load.h b/src/core/hooks/web_load.h index 260f3d8b..3847810c 100644 --- a/src/core/hooks/web_load.h +++ b/src/core/hooks/web_load.h @@ -5,43 +5,51 @@ #include static unsigned long long hook_tag; -static std::mutex request_map_mutex; - -class webkit_handler { -private: - // must share the same base url, or be whitelisted. - const char* virt_url = "https://s.ytimg.com/millennium-virtual/"; - const char* looback = "https://steamloopback.host/"; - - bool is_ftp_call(nlohmann::basic_json<> message); - - std::string css_hook(std::string body); - std::string js_hook(std::string body); - - struct web_hook_t { - short id; - std::string request_id; - std::string type; - }; - - std::shared_ptr> request_map = std::make_shared>(); +class WebkitHandler +{ public: - static webkit_handler get(); + static WebkitHandler get(); - enum type_t { - STYLESHEET, + enum TagTypes { + STYLESHEET, JAVASCRIPT }; - struct shook_t { + struct HookType { std::string path; - type_t type; + TagTypes type; unsigned long long id; }; - std::shared_ptr> h_list_ptr = std::make_shared>(); + std::shared_ptr> m_hookListPtr = std::make_shared>(); + + void DispatchSocketMessage(nlohmann::basic_json<> message); + void SetupGlobalHooks(); + +private: + long long hookMessageId = -69; + + // must share the same base url, or be whitelisted. + const char* m_javaScriptVirtualUrl = "https://s.ytimg.com/millennium-virtual/"; + const char* m_steamLoopback = "https://steamloopback.host/"; + + bool IsGetBodyCall(nlohmann::basic_json<> message); + + std::string HandleCssHook(std::string body); + std::string HandleJsHook(std::string body); + void HandleHooks(nlohmann::basic_json<> message); + + void RetrieveRequestFromDisk(nlohmann::basic_json<> message); + void GetResponseBody(nlohmann::basic_json<> message); + + std::filesystem::path ConvertToLoopBack(std::string requestUrl); + + struct WebHookItem { + long long id; + std::string requestId; + std::string type; + }; - void handle_hook(nlohmann::basic_json<> message); - void setup_hook(); + std::shared_ptr> m_requestMap = std::make_shared>(); }; \ No newline at end of file diff --git a/src/core/impl/c_python.cpp b/src/core/impl/c_python.cpp deleted file mode 100644 index ac3a8164..00000000 --- a/src/core/impl/c_python.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "mutex_impl.hpp" -#include -#include - -std::string python::construct_fncall(nlohmann::basic_json<> data) -{ - std::string out = data["methodName"]; - out += "("; - - if (data.contains("argumentList")) - { - int index = 0, total_items = data["argumentList"].size(); - - for (auto it = data["argumentList"].begin(); it != data["argumentList"].end(); ++it, ++index) - { - auto& key = it.key(); - auto& value = it.value(); - - if (value.is_boolean()) { out += fmt::format("{}={}", key, value ? "True" : "False"); } - else { out += fmt::format("{}={}", key, value.dump()); } - - if (index < total_items - 1) { - out += ", "; - } - } - } - out += ")"; - return out; -} - -const python::return_t lock_gil(std::string plugin_name, std::string script) -{ - // Execute the script and assign the return value to a variable - PyObject* global_dict = PyModule_GetDict(PyImport_AddModule("__main__")); - PyObject* rv_object = PyRun_String(script.c_str(), Py_eval_input, global_dict, global_dict); - - // An exception occurred - if (!rv_object && PyErr_Occurred()) { - - PyObject* ptype, * pvalue, * ptraceback; - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); - - PyObject* pStrErrorMessage = PyObject_Str(pvalue); - if (pStrErrorMessage) { - - const char* errorMessage = PyUnicode_AsUTF8(pStrErrorMessage); - if (errorMessage) { - console.err(fmt::format("[interop-dispatch] Error calling backend function: {}", errorMessage)); - } - return { errorMessage, python::type_t::_err }; - } - } - - if (rv_object == nullptr || rv_object == Py_None) { - return { "0", python::type_t::_int }; // whitelist NoneType - } - if (PyBool_Check(rv_object)) { - return { PyLong_AsLong(rv_object) == 0 ? "False" : "True", python::type_t::_bool }; - } - else if (PyLong_Check(rv_object)) { - return { std::to_string(PyLong_AsLong(rv_object)), python::type_t::_int }; - } - else if (PyUnicode_Check(rv_object)) { - return { PyUnicode_AsUTF8(rv_object), python::type_t::_string }; - } - - // Print the typename - PyObject* object_type = PyObject_Type(rv_object); - PyObject* object_type_str = PyObject_Str(object_type); - const char* type_name = PyUnicode_AsUTF8(object_type_str); - PyErr_Clear(); // Clear any Python exception - Py_XDECREF(object_type); - Py_XDECREF(object_type_str); - - return { fmt::format("Millennium expected return type [int, str, bool] but recevied [{}]", type_name) , python::type_t::_err }; -} - -python::return_t python::evaluate_lock(std::string plugin_name, std::string script) -{ - auto [name, state, _auxts] = plugin_manager::get().get_thread_state(plugin_name); - - if (state == nullptr) { - console.err(fmt::format("couldn't get thread state ptr from plugin [{}], maybe it crashed or exited early? ", plugin_name)); - return { "overstepped partying thread state", _err }; - } - - PyThreadState* auxts = PyThreadState_New(PyInterpreterState_Main()); - PyEval_RestoreThread(auxts); - - // switch global lock to current thread swap - PyGILState_STATE gstate = PyGILState_Ensure(); - - // switch thread state to respective backend - PyThreadState_Swap(state); - - if (state == NULL) { - console.err("script execution was queried but the receiving parties thread state was nullptr"); - return { "thread state was nullptr", _err }; - } - - python::return_t response = lock_gil(plugin_name, script); - - PyThreadState_Clear(auxts); - PyThreadState_Swap(auxts); - - PyGILState_Release(gstate); - PyThreadState_DeleteCurrent(); - return response; -} - -void python::run_lock(std::string plugin_name, std::string script) -{ - auto [name, state, _] = plugin_manager::get().get_thread_state(plugin_name); - - if (state == nullptr || state == NULL) { - console.err(fmt::format("couldn't get thread state ptr from plugin [{}], maybe it crashed or exited early? ", plugin_name)); - return; - } - const auto interp_state = PyInterpreterState_Main(); - - PyThreadState* auxts = PyThreadState_New(interp_state); - PyEval_RestoreThread(auxts); - - PyGILState_STATE gstate = PyGILState_Ensure(); - PyThreadState_Swap(state); - - // PyRun_SimpleString(fmt::format( - // "try:\n\t{}\nexcept Exception as e:\n\tprint(f'[Millennium] An error occured running {} lock: {{e}}')", - // script, script - // ).c_str()); - - // Get the current frame - PyRun_SimpleString(script.c_str()); - - PyThreadState_Clear(auxts); - PyThreadState_Swap(auxts); - - PyGILState_Release(gstate); - PyThreadState_DeleteCurrent(); -} diff --git a/src/core/impl/javascript.cpp b/src/core/impl/javascript.cpp deleted file mode 100644 index b2fb35f5..00000000 --- a/src/core/impl/javascript.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "mutex_impl.hpp" -#include -#include -#include - -struct shared_response { - nlohmann::basic_json<> json; - bool _success; -}; - -const shared_response execute_shared(std::string javascript) { - // Create a promise to store the result - std::promise promise; - - bool success = tunnel::post_shared(nlohmann::json({ - {"id", 54999}, - {"method", "Runtime.evaluate"}, - {"params", { - {"expression", javascript}, - {"awaitPromise", true} - }} - })); - - if (!success) { - throw std::runtime_error("couldn't send message to socket"); - } - - // Register message handler to capture the result - javascript::emitter::instance().on("msg", [&](const nlohmann::json& event_msg, int listenerId) { - try { - if (event_msg.contains("id") && event_msg["id"] == 54999) { - - if (event_msg["result"].contains("exceptionDetails")) - { - promise.set_value({ - event_msg["result"]["exceptionDetails"]["exception"]["description"], false - }); - return; - } - - promise.set_value({ event_msg["result"]["result"], true }); - - // Set the value of the promise with the result - javascript::emitter::instance().off("msg", listenerId); - } - } - catch (nlohmann::detail::exception& ex) { - console.err(fmt::format("javascript::emitter err -> {}", ex.what())); - } - catch (std::future_error& ex) { - console.err(ex.what()); - } - }); - - // Return the result obtained from the promise - return promise.get_future().get(); -} - -const void javascript::reload_shared_context() { - return (void)tunnel::post_shared({ {"id", 89}, {"method", "Page.reload"}, {"sessionId", sessionId} }); -} - -const std::string javascript::construct_fncall( - const char* plugin, const char* method_name, std::vector params) -{ - // plugin module exports are stored in a map on the window object PLUGIN_LIST - std::string out = fmt::format("PLUGIN_LIST['{}'].{}(", plugin, method_name); - - for (auto it = params.begin(); it != params.end(); ++it) - { - auto& param = *it; - - if (param.type == "str") { out += fmt::format("\"{}\"", param.name); } - else if (param.type == "bool") { out += param.name == "True" ? "true" : "false"; } //python decided to be quirky with caps - else { out += param.name; } - - if (std::next(it) != params.end()) { - out += ", "; - } - } - out += ");"; return out; -} - -PyObject* javascript::evaluate_lock(std::string script) -{ - try - { - auto response = execute_shared(script); - - if (!response._success) { - PyErr_SetString(PyExc_RuntimeError, response.json.get().c_str()); - return NULL; - } - - std::string type = response.json["type"]; - - if (type == "string") return PyUnicode_FromString(response.json["value"].get().c_str()); - else if (type == "bool") return PyBool_FromLong(response.json["value"]); - else if (type == "int") return PyLong_FromLong(response.json["value"]); - else - return PyUnicode_FromString("Js function returned unaccepted type. accepted types [string, bool, int]"); - - } - catch (nlohmann::detail::exception& ex) - { - std::string message = fmt::format("Millennium couldn't decode the response from {}", script); - return PyUnicode_FromString(message.c_str()); - } - catch (std::exception& ex) - { - PyErr_SetString(PyExc_ConnectionError, "frontend is not loaded!"); - return NULL; - } - - Py_RETURN_NONE; -} diff --git a/src/core/impl/mutex_impl.hpp b/src/core/impl/mutex_impl.hpp deleted file mode 100644 index 53bf8127..00000000 --- a/src/core/impl/mutex_impl.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -namespace python { - - enum type_t { - _bool, - _string, - _int, - _err, - unkown // non-atomic ADT's - }; - - struct return_t { - std::string plain; - type_t type; - }; - - std::string construct_fncall(nlohmann::basic_json<> data); - - return_t evaluate_lock(std::string plugin_name, std::string script); - void run_lock(std::string plugin_name, std::string script); -} -namespace javascript { - - using EventHandler = std::function; - - class emitter { - private: - emitter() {} - std::unordered_map>> events; - int nextListenerId = 0; - - public: - emitter(const emitter&) = delete; - emitter& operator=(const emitter&) = delete; - - static emitter& instance() { - static emitter instance; - return instance; - } - - int on(const std::string& event, EventHandler handler) { - int listenerId = nextListenerId++; - events[event].push_back(std::make_pair(listenerId, handler)); - return listenerId; - } - - void off(const std::string& event, int listenerId) { - auto it = events.find(event); - if (it != events.end()) { - auto& handlers = it->second; - handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [listenerId](const auto& handler) { - return handler.first == listenerId; - }), handlers.end()); - } - } - - void emit(const std::string& event, const nlohmann::json& data) { - auto it = events.find(event); - if (it != events.end()) { - const auto& handlers = it->second; - for (const auto& handler : handlers) { - handler.second(data, handler.first); - } - } - } - }; - - struct param_types - { - std::string name, type; - }; - - const void reload_shared_context(); - - const std::string construct_fncall(const char* value, const char* method_name, - std::vector params); - - PyObject* evaluate_lock(std::string script); -} \ No newline at end of file diff --git a/src/core/ipc/pipe.cpp b/src/core/ipc/pipe.cpp index 58157108..7e9079d4 100644 --- a/src/core/ipc/pipe.cpp +++ b/src/core/ipc/pipe.cpp @@ -2,45 +2,47 @@ #include #include "pipe.hpp" -#include +#include #include #include #include -#define d6d base64_decode -#define d6e base64_encode +typedef websocketpp::server server; -static nlohmann::json call_server_method(nlohmann::basic_json<> message) +static nlohmann::json CallServerMethod(nlohmann::basic_json<> message) { - const std::string call_script = python::construct_fncall(message["data"]); + const std::string fnCallScript = Python::ConstructFunctionCall(message["data"]); - if (!message["data"].contains("pluginName")) { - console.err("no plugin backend specified, doing nothing..."); + if (!message["data"].contains("pluginName")) + { + Logger.Error("no plugin backend specified, doing nothing..."); + return {}; } - python::return_t response = python::evaluate_lock(message["data"]["pluginName"], call_script); - nlohmann::json json_ret; + Python::EvalResult response = Python::LockGILAndEvaluate(message["data"]["pluginName"], fnCallScript); + nlohmann::json responseMessage; switch (response.type) { - case python::type_t::_bool: { json_ret["returnValue"] = (response.plain == "True" ? true : false); break; } - case python::type_t::_string: { json_ret["returnValue"] = d6e(response.plain); break; } - case python::type_t::_int: { json_ret["returnValue"] = stoi(response.plain); break; } - - case python::type_t::_err: { - json_ret["failedRequest"] = true; - json_ret["failMessage"] = response.plain; + case Python::ReturnTypes::Boolean: { responseMessage["returnValue"] = (response.plain == "True" ? true : false); break; } + case Python::ReturnTypes::String: { responseMessage["returnValue"] = Base64Encode(response.plain); break; } + case Python::ReturnTypes::Integer: { responseMessage["returnValue"] = stoi(response.plain); break; } + + case Python::ReturnTypes::Error: + { + responseMessage["failedRequest"] = true; + responseMessage["failMessage"] = response.plain; break; } } - json_ret["id"] = message["iteration"]; - return json_ret; + responseMessage["id"] = message["iteration"]; + return responseMessage; } -static nlohmann::json front_end_loaded(nlohmann::basic_json<> message) +static nlohmann::json OnFrontEndLoaded(nlohmann::basic_json<> message) { - python::run_lock( + Python::LockGILAndDiscardEvaluate( message["data"]["pluginName"], "plugin._front_end_loaded()" ); @@ -51,39 +53,46 @@ static nlohmann::json front_end_loaded(nlohmann::basic_json<> message) }); } -typedef websocketpp::server server; - -void on_message(server* serv, websocketpp::connection_hdl hdl, server::message_ptr msg) { +void OnMessage(server* serv, websocketpp::connection_hdl hdl, server::message_ptr msg) { server::connection_ptr con = serv->get_con_from_hdl(hdl); try { auto json_data = nlohmann::json::parse(msg->get_payload()); - std::string response; - - switch (json_data["id"].get()) { - case ipc_pipe::type_t::call_server_method: { - response = (::call_server_method(json_data).dump()); break; + std::string responseMessage; + + switch (json_data["id"].get()) + { + case IPCMain::Builtins::CALL_SERVER_METHOD: + { + responseMessage = CallServerMethod(json_data).dump(); + break; } - case ipc_pipe::type_t::front_end_loaded: { - response = (::front_end_loaded(json_data).dump()); break; + case IPCMain::Builtins::FRONT_END_LOADED: + { + responseMessage = OnFrontEndLoaded(json_data).dump(); + break; } } - con->send(response, msg->get_opcode()); + con->send(responseMessage, msg->get_opcode()); } - catch (nlohmann::detail::exception& ex) { + catch (nlohmann::detail::exception& ex) + { con->send(std::string(ex.what()), msg->get_opcode()); } - catch (std::exception& ex) { + catch (std::exception& ex) + { con->send(std::string(ex.what()), msg->get_opcode()); } } -void on_open(server* s, websocketpp::connection_hdl hdl) { +void OnOpen(server* s, websocketpp::connection_hdl hdl) +{ s->start_accept(); } -const int open_webserver() { +const int open_webserver() +{ server wss; try { @@ -91,21 +100,21 @@ const int open_webserver() { wss.clear_access_channels(websocketpp::log::alevel::all); wss.init_asio(); - wss.set_message_handler(bind(on_message, &wss, std::placeholders::_1, std::placeholders::_2)); - wss.set_open_handler(bind(&on_open, &wss, std::placeholders::_1)); + wss.set_message_handler(bind(OnMessage, &wss, std::placeholders::_1, std::placeholders::_2)); + wss.set_open_handler(bind(&OnOpen, &wss, std::placeholders::_1)); wss.listen(12906); wss.start_accept(); wss.run(); } catch (const std::exception& e) { - console.err("[pipecon] uncaught error -> {}", e.what()); + Logger.Error("[pipecon] uncaught error -> {}", e.what()); return false; } return true; } -const int ipc_pipe::_create() +const int IPCMain::OpenConnection() { std::thread(open_webserver).detach(); return true; diff --git a/src/core/ipc/pipe.hpp b/src/core/ipc/pipe.hpp index b0c1e41d..8b091f6d 100644 --- a/src/core/ipc/pipe.hpp +++ b/src/core/ipc/pipe.hpp @@ -1,13 +1,16 @@ #pragma once #include -extern "C" { - namespace ipc_pipe { - enum type_t { - call_server_method, - front_end_loaded +extern "C" +{ + namespace IPCMain + { + enum Builtins + { + CALL_SERVER_METHOD, + FRONT_END_LOADED }; - const int _create(); + const int OpenConnection(); } } \ No newline at end of file diff --git a/src/core/loader.cpp b/src/core/loader.cpp index 9bdc731b..8af8e6f1 100644 --- a/src/core/loader.cpp +++ b/src/core/loader.cpp @@ -3,227 +3,229 @@ #include #include #include <__builtins__/executor.h> -#include +#include #include -#include +#include #include #include #include #include -websocketpp::client* plugin_client, *browser_c; -websocketpp::connection_hdl plugin_handle, browser_h; - -extern std::string sessionId; -static std::chrono::system_clock::time_point bootstrap_start; - -void set_start_time(std::chrono::system_clock::time_point time) { - bootstrap_start = time; -} +using namespace std::placeholders; +websocketpp::client* sharedClient, *browserClient; +websocketpp::connection_hdl sharedHandle, browserHandle; // @ src\core\co_initialize\co_stub.cc -const void inject_shims(void); -void plugin_start_cb(stream_buffer::plugin_mgr::plugin_t& plugin); -const std::string setup_bootstrap_module(); +const void InjectFrontendShims(void); +void BackendStartCallback(SettingsStore::PluginTypeSchema& plugin); -enum socket_t { +enum eSocketTypes { SHARED, GLOBAL }; -bool post_virtual(nlohmann::json data, socket_t type) { - if ((type == socket_t::SHARED ? plugin_client : browser_c) == nullptr) { - console.err("not connected to steam, cant post message"); +bool PostSocket(nlohmann::json data, eSocketTypes type) { + + const auto client = (type == eSocketTypes::SHARED ? sharedClient : browserClient); + const auto handle = (type == eSocketTypes::SHARED ? sharedHandle : browserHandle); + + if (client == nullptr) { + Logger.Error("not connected to steam, cant post message"); return false; } - (type == socket_t::SHARED ? plugin_client : browser_c)->send( - (type == socket_t::SHARED ? plugin_handle : browser_h), data.dump(), websocketpp::frame::opcode::text); - + client->send(handle, data.dump(), websocketpp::frame::opcode::text); return true; } -bool tunnel::post_shared(nlohmann::json data) { - return post_virtual(data, socket_t::SHARED); -} -bool tunnel::post_global(nlohmann::json data) { - return post_virtual(data, socket_t::GLOBAL); +bool Sockets::PostShared(nlohmann::json data) +{ + return PostSocket(data, eSocketTypes::SHARED); } -namespace browser { +bool Sockets::PostGlobal(nlohmann::json data) +{ + return PostSocket(data, eSocketTypes::GLOBAL); +} - const void on_message(websocketpp::client* c, - websocketpp::connection_hdl hdl, - websocketpp::config::asio_client::message_type::ptr msg) - { +class CEFBrowser +{ + std::unique_ptr webKitHandler; - const auto json = nlohmann::json::parse(msg->get_payload()); - webkit_handler::get().handle_hook(json); - } +public: - const void on_connect(websocketpp::client* _c, websocketpp::connection_hdl _hdl) + const void onMessage(websocketpp::client* c, websocketpp::connection_hdl hdl, websocketpp::config::asio_client::message_type::ptr msg) { - browser_c = _c; browser_h = _hdl; - console.log("established browser connection @ {:p}", static_cast(&_c)); - - // setup webkit handler to hook default modules - webkit_handler::get().setup_hook(); + const auto json = nlohmann::json::parse(msg->get_payload()); + webKitHandler->DispatchSocketMessage(json); } -} -const void check_ready_state(nlohmann::json json) { - - //std::cout << json.dump(4) << std::endl; + const void onConnect(websocketpp::client* client, websocketpp::connection_hdl handle) + { + browserClient = client; + browserHandle = handle; - if (!json.contains("params") || !json["params"].is_object()) { - return; + Logger.Log("established browser connection @ {:p}", static_cast(&client)); + webKitHandler->SetupGlobalHooks(); } - if (!json["params"].contains("args") || !json["params"]["args"].is_array()) { - return; + CEFBrowser() : webKitHandler(&WebkitHandler::get()) + { } - if (json["params"]["args"].size() == 0) { - return; + ~CEFBrowser() { + Logger.Error("wdawdawdawdawd"); } +}; - const std::string message = json["params"]["args"][0]["value"]; - - if (message.find("Init localization") != std::string::npos) { - - std::cout << json.dump(4) << std::endl; +class SharedJSContext +{ +public: - console.err("shared js context start detected!"); - tunnel::post_shared({ {"id", 71 }, {"method", "Debugger.pause"} }); + const void onMessage(websocketpp::client*c, websocketpp::connection_hdl hdl, websocketpp::config::asio_client::message_type::ptr msg) + { } -} -namespace shared_context { - - const void on_message(websocketpp::client* c, - websocketpp::connection_hdl hdl, - websocketpp::config::asio_client::message_type::ptr msg) + const void onConnect(websocketpp::client* client, websocketpp::connection_hdl handle) { - try { - const auto json = nlohmann::json::parse(msg->get_payload()); + sharedClient = client; + sharedHandle = handle; - //std::cout << json.dump(4) << std::endl; + Logger.Log("established shared connection @ {:p}", static_cast(&sharedClient)); + InjectFrontendShims(); + } +}; - if (json.contains("method") && json["method"] == "Runtime.consoleAPICalled") { - check_ready_state(json); - } +struct ConnectSocketProps +{ + std::string commonName; + std::function fetchSocketUrl; - if (json.contains("method") && json["method"] == "Debugger.paused") { + std::function*, + websocketpp::connection_hdl)> onConnect; - const nlohmann::json debugger_eval = { {"id", 50 }, {"method", "Runtime.evaluate"}, {"params", { - { "expression", setup_bootstrap_module() } - }} }; + std::function*, + websocketpp::connection_hdl, + std::shared_ptr)> onMessage; - std::cout << "sending debugger eval message -> " << debugger_eval.dump(4) << std::endl; - tunnel::post_shared(debugger_eval); - } - } - catch (nlohmann::detail::exception& ex) { - console.err(ex.what()); - } - } - - const void on_connect(websocketpp::client* _c, websocketpp::connection_hdl _hdl) - { - plugin_client = _c; plugin_handle = _hdl; - console.log("established shared connection @ {:p}", static_cast(&plugin_client)); + PluginLoader* pluginLoader; +}; - // setup plugin frontends - inject_shims(); - } -} +void ConnectSocket(ConnectSocketProps socketProps) +{ + const auto [commonName, fetchSocketUrl, onConnect, onMessage, pluginLoader] = socketProps; -void connect_socket( - std::string common_name, - std::function fetch_socket, - std::function *_c, websocketpp::connection_hdl _hdl)> on_connect, - std::function *c, websocketpp::connection_hdl hdl, std::shared_ptr msg)> on_message -) { - while (true) { - const std::string ctx = fetch_socket(); + while (true) + { + const std::string socketUrl = fetchSocketUrl(); try { - websocketpp::client cl; - cl.set_access_channels(websocketpp::log::alevel::none); - cl.init_asio(); - - cl.set_open_handler(bind(on_connect, &cl, std::placeholders::_1)); - cl.set_message_handler(bind(on_message, &cl, std::placeholders::_1, std::placeholders::_2)); + websocketpp::client socketClient; + socketClient.set_access_channels(websocketpp::log::alevel::none); + socketClient.init_asio(); + socketClient.set_open_handler(bind(onConnect, &socketClient, std::placeholders::_1)); + socketClient.set_message_handler(bind(onMessage, &socketClient, std::placeholders::_1, std::placeholders::_2)); websocketpp::lib::error_code ec; - websocketpp::client::connection_ptr con = cl.get_connection(ctx, ec); + websocketpp::client::connection_ptr con = socketClient.get_connection(socketUrl, ec); if (ec) { - console.err("could not create connection because: {}", ec.message()); - return; + throw websocketpp::exception(ec.message()); } - cl.connect(con); cl.run(); + socketClient.connect(con); + socketClient.run(); } catch (websocketpp::exception& ex) { - console.err("Failed to connect to steam."); - console.err(ex.what()); + Logger.Error("webSocket exception thrown -> {}", ex.what()); } - console.err("{} tunnel collapsed, attempting to reopen...", common_name); + Logger.Error("{} tunnel collapsed, attempting to reopen...", commonName); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } -void _connect_browser() { - connect_socket("browser", get_steam_context, browser::on_connect, browser::on_message); +PluginLoader::PluginLoader(std::chrono::system_clock::time_point startTime) : m_startTime(startTime), m_pluginsPtr(nullptr) +{ + m_settingsStorePtr = std::make_unique(); + m_pluginsPtr = std::make_shared>(m_settingsStorePtr->ParseAllPlugins()); + + m_settingsStorePtr->InitializeSettingsStore(); + IPCMain::OpenConnection(); + + this->PrintActivePlugins(); +} + +const std::thread PluginLoader::ConnectSharedJSContext(void* sharedJsHandler) +{ + ConnectSocketProps sharedProps; + + sharedProps.commonName = "SharedJSContext"; + sharedProps.fetchSocketUrl = GetSharedJsContext; + sharedProps.onConnect = std::bind(&SharedJSContext::onConnect, (SharedJSContext*)sharedJsHandler, _1, _2); + sharedProps.onMessage = std::bind(&SharedJSContext::onMessage, (SharedJSContext*)sharedJsHandler, _1, _2, _3); + sharedProps.pluginLoader = this; + + return std::thread(ConnectSocket, sharedProps); } -void _connect_shared() { - connect_socket("sharedctx", get_shared_js, shared_context::on_connect, shared_context::on_message); +const std::thread PluginLoader::ConnectCEFBrowser(void* cefBrowserHandler) +{ + ConnectSocketProps browserProps; + + browserProps.commonName = "CEFBrowser"; + browserProps.fetchSocketUrl = GetSteamBrowserContext; + browserProps.onConnect = std::bind(&CEFBrowser::onConnect, (CEFBrowser*)cefBrowserHandler, _1, _2); + browserProps.onMessage = std::bind(&CEFBrowser::onMessage, (CEFBrowser*)cefBrowserHandler, _1, _2, _3); + browserProps.pluginLoader = this; + + return std::thread(ConnectSocket, browserProps); } -void plugin::bootstrap() +const void PluginLoader::StartFrontEnds() { - console.head("backend:"); + CEFBrowser cefBrowserHandler; + SharedJSContext sharedJsHandler; + + std::thread browserSocketThread = this->ConnectCEFBrowser(&cefBrowserHandler); + std::thread sharedSocketThread = this->ConnectSharedJSContext(&sharedJsHandler); - int error; - error = stream_buffer::setup_config(); - console.log_item("setup_config", !error ? "failed" : "success"); + auto duration = std::chrono::duration_cast(std::chrono::system_clock::now() - this->m_startTime); + Logger.LogItem("exec", fmt::format("finished in [{}ms]\n", duration.count()), true); - error = ipc_pipe::_create(); - console.log_item("pipemon", !error ? "failed" : "success", true); + browserSocketThread.join(); + sharedSocketThread.join(); - auto plugins = std::make_shared>(stream_buffer::plugin_mgr::parse_all()); - console.head("hosting query:"); + Logger.Log("browser thread and shared thread exited..."); + this->StartFrontEnds(); +} + +/* debug function, just for developers */ +const void PluginLoader::PrintActivePlugins() +{ + Logger.LogHead("hosting query:"); - for (auto it = (*plugins).begin(); it != (*plugins).end(); ++it) + for (auto it = (*this->m_pluginsPtr).begin(); it != (*this->m_pluginsPtr).end(); ++it) { - const auto name = (*it).name; - console.log_item(name, stream_buffer::plugin_mgr::is_enabled(name) ? "enabled" : "disabled", std::next(it) == (*plugins).end()); + const auto pluginName = (*it).pluginName; + Logger.LogItem(pluginName, m_settingsStorePtr->IsEnabledPlugin(pluginName) ? "enabled" : "disabled", std::next(it) == (*this->m_pluginsPtr).end()); } +} - for (auto& plugin : *plugins) - { - plugin_manager& manager = plugin_manager::get(); +const void PluginLoader::StartBackEnds() +{ + PythonManager& manager = PythonManager::GetInstance(); - if (!stream_buffer::plugin_mgr::is_enabled(plugin.name)) { + for (auto& plugin : *this->m_pluginsPtr) + { + if (!m_settingsStorePtr->IsEnabledPlugin(plugin.pluginName)) + { continue; } - std::function cb = std::bind(plugin_start_cb, std::placeholders::_1); - std::thread(std::bind(&plugin_manager::create_instance, &manager, std::ref(plugin), cb)).detach(); + std::function cb = std::bind(BackendStartCallback, std::placeholders::_1); + std::thread(std::bind(&PythonManager::CreatePythonInstance, &manager, std::ref(plugin), cb)).detach(); } - - - std::thread t_browser(_connect_browser); - std::thread t_shared(_connect_shared); - - // Calculate the duration - auto duration = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - bootstrap_start); - console.log_item("exec", fmt::format("finished in [{}ms]\n", duration.count()), true); - - t_browser.join(); - t_shared.join(); - console.log("browser thread and shared thread exited..."); } diff --git a/src/core/loader.hpp b/src/core/loader.hpp index 64760c60..2d0ca652 100644 --- a/src/core/loader.hpp +++ b/src/core/loader.hpp @@ -9,28 +9,26 @@ #include #include -static std::string sessionId; +class PluginLoader { +public: -struct shared { - websocketpp::client - * client; - websocketpp::connection_hdl handle; -}; + PluginLoader(std::chrono::system_clock::time_point startTime); -class plugin { -public: + const void StartBackEnds(); + const void StartFrontEnds(); - static plugin get() { - static plugin _p; - return _p; - } +private: + const void PrintActivePlugins(); - void bootstrap(); -}; + const std::thread ConnectSharedJSContext(void* sharedJsHandler); + const std::thread ConnectCEFBrowser(void* cefBrowserHandler); -namespace tunnel { - bool post_shared(nlohmann::json data); - bool post_global(nlohmann::json data); -} + std::unique_ptr m_settingsStorePtr; + std::shared_ptr> m_pluginsPtr; + std::chrono::system_clock::time_point m_startTime; +}; -void set_start_time(std::chrono::system_clock::time_point time); \ No newline at end of file +namespace Sockets { + bool PostShared(nlohmann::json data); + bool PostGlobal(nlohmann::json data); +} \ No newline at end of file diff --git a/src/core/main.cpp b/src/core/main.cpp index cd1595f7..fc002e61 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -12,84 +12,111 @@ #include #ifdef _WIN32 #include <_win32/thread.h> +#include +#include +#include +#include #endif #include #include #include #include #include -/** - * @brief constructor of hooked module to setup the environment - * - * - writes the debugger flag to steam so it launches with remote debugging - * - allocated the dev console if applicable on windows - * - * @return success (discarded from kernel) -*/ -const bool __attribute__((constructor)) setup_env() + +class Preload { -#ifdef _WIN32 - constexpr const char* filePath = ".cef-enable-remote-debugging"; -#elif __linux__ - std::string filePath = fmt::format("{}/.local/share/Steam/.cef-enable-remote-debugging", std::getenv("HOME")); -#endif +public: + + Preload() + { + this->VerifyEnvironment(); + PauseSteamCore(); + } - if (!std::filesystem::exists(filePath)) { - std::ofstream(filePath).close(); + ~Preload() + { + UnpauseSteamCore(); + } + + const void VerifyEnvironment() + { + const auto filePath = SystemIO::GetSteamPath() / ".cef-enable-remote-debugging"; + + // Steam's CEF Remote Debugger isn't exposed to port 8080 + if (!std::filesystem::exists(filePath)) + { + std::ofstream(filePath).close(); + + boxer::show("Successfully initialized Millennium!. You can now manually restart Steam...", "Message"); + std::exit(0); + } + } - boxer::show("Successfully initialized Millennium!. You can now manually restart Steam...", "Message"); + const void Start() + { + const bool bBuiltInSuccess = Dependencies::GitAuditPackage("@builtins", builtinsModulesAbsolutePath, builtinsRepository); + + if (!bBuiltInSuccess) + { + Logger.Error("failed to audit builtin modules..."); + return; + } + + // python modules only need to be audited on windows systems. + // linux users need their own installation of python + #ifdef _WIN32 + const bool bPythonModulesSuccess = Dependencies::GitAuditPackage("@packages", pythonModulesBaseDir.generic_string(), pythonModulesRepository); - std::exit(0); - return false; + if (!bPythonModulesSuccess) + { + Logger.Error("failed to audit python modules..."); + return; + } + #endif } - return true; -} +}; -/** - * @brief wrapper for start function - * - * - ensures modules are up to date - * - starts the plugin backend and frontends -*/ -const void bootstrap() +/* Wrapped cross platform entrypoint */ +const static void EntryMain() { - set_start_time(std::chrono::high_resolution_clock::now()); - -#ifdef _WIN32 - hook_steam_thread(); -#endif + std::string processName = GetProcessName(GetCurrentProcessId()); - const bool __builtins__ = dependencies::audit_package("@builtins", millennium_modules_path, builtins_repo); - const bool __rmods__ = dependencies::audit_package("@packages", modules_basedir.generic_string(), modules_repo); + if (processName != "steam.exe") + { + return; // don't load into non Steam Applications. + } - if (!__builtins__ || !__rmods__) { - return; + const auto startTime = std::chrono::system_clock::now(); + { + std::unique_ptr preload = std::make_unique(); + preload->Start(); } -#ifdef _WIN32 - unhook_steam_thread(); -#endif - plugin::get().bootstrap(); + std::unique_ptr loader = std::make_unique(startTime); + + loader->StartBackEnds(); + loader->StartFrontEnds(); } #ifdef _WIN32 -int __attribute__((__stdcall__)) DllMain(void*, unsigned long fdwReason, void*) +int __stdcall DllMain(void*, unsigned long fdwReason, void*) { - if (fdwReason == DLL_PROCESS_ATTACH) { - std::thread(bootstrap).detach(); + if (fdwReason == DLL_PROCESS_ATTACH) + { + std::thread(EntryMain).detach(); } return true; } -int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) +int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdShow) { - bootstrap(); + EntryMain(); return 1; } #elif __linux__ int main() { - bootstrap(); + __main__(); return 1; } #endif \ No newline at end of file diff --git a/src/core/py_controller/bind_stdout.hpp b/src/core/py_controller/bind_stdout.hpp new file mode 100644 index 00000000..c036fdff --- /dev/null +++ b/src/core/py_controller/bind_stdout.hpp @@ -0,0 +1,85 @@ +#include +#include +#include "co_spawn.hpp" +#include +#include + +extern "C" void PrintPythonMessage(std::string pname, const char* message) +{ + const std::string logMessage = message; + + if (logMessage != "\n" && logMessage != " ") + { + Logger.LogPluginMessage(pname, message); + } +} + +extern "C" void PrintPythonError(std::string pname, const char* message) +{ + #ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(hConsole, FOREGROUND_RED); + #endif + std::cout << message; + #ifdef _WIN32 + SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + #endif +} + +extern "C" +{ + static PyObject* CustomStdoutWrite(PyObject* self, PyObject* args) + { + const char* message; + if (!PyArg_ParseTuple(args, "s", &message)) + { + return NULL; + } + + PrintPythonMessage(PythonManager::GetInstance().GetPluginNameFromThreadState(PyThreadState_Get()), message); + return Py_BuildValue(""); + } + + static PyObject* CustomStderrWrite(PyObject* self, PyObject* args) + { + const char* message; + if (!PyArg_ParseTuple(args, "s", &message)) + { + return NULL; + } + + PrintPythonError(PythonManager::GetInstance().GetPluginNameFromThreadState(PyThreadState_Get()), message); + return Py_BuildValue(""); + } + + static PyMethodDef module_methods[] = + { + {"write", CustomStdoutWrite, METH_VARARGS, "Custom stdout write function"}, {NULL, NULL, 0, NULL} + }; + + static PyMethodDef stderr_methods[] = + { + {"write", CustomStderrWrite, METH_VARARGS, "Custom stderr write function"}, {NULL, NULL, 0, NULL} + }; + + static struct PyModuleDef custom_stdout_module = { PyModuleDef_HEAD_INIT, "hook_stdout", NULL, -1, module_methods }; + static struct PyModuleDef custom_stderr_module = { PyModuleDef_HEAD_INIT, "hook_stderr", NULL, -1, stderr_methods }; + + PyObject* PyInit_CustomStderr(void) + { + return PyModule_Create(&custom_stderr_module); + } + + PyObject* PyInit_CustomStdout(void) + { + return PyModule_Create(&custom_stdout_module); + } + + const void RedirectOutput() + { + PyObject* sys = PyImport_ImportModule("sys"); + + PyObject_SetAttrString(sys, "stdout", PyImport_ImportModule("hook_stdout")); + PyObject_SetAttrString(sys, "stderr", PyImport_ImportModule("hook_stderr")); + } +} \ No newline at end of file diff --git a/src/core/py_controller/co_spawn.cpp b/src/core/py_controller/co_spawn.cpp new file mode 100644 index 00000000..7cd9d3cb --- /dev/null +++ b/src/core/py_controller/co_spawn.cpp @@ -0,0 +1,240 @@ +#include +#include +#include <__builtins__/executor.h> +#include +#include "co_spawn.hpp" +#include "bind_stdout.hpp" +#include +#include +#include + +PyObject* PyInit_Millennium(void) +{ + static struct PyModuleDef module_def = + { + PyModuleDef_HEAD_INIT, "Millennium", NULL, -1, (PyMethodDef*)GetMillenniumModule() + }; + + return PyModule_Create(&module_def); +} + +const std::string GetPythonVersion() +{ + PyThreadState* mainThreadState = PyThreadState_New(PyInterpreterState_Main()); + PyEval_RestoreThread(mainThreadState); + PyThreadState* newInterp = Py_NewInterpreter(); + std::string pythonVersion; + + // Run the code and get the result + PyObject* platformModule = PyImport_ImportModule("platform"); + if (platformModule) + { + PyObject* versionFn = PyObject_GetAttrString(platformModule, "python_version"); + if (versionFn && PyCallable_Check(versionFn)) + { + PyObject* version_result = PyObject_CallObject(versionFn, nullptr); + if (version_result) + { + pythonVersion = PyUnicode_AsUTF8(version_result); + Py_DECREF(version_result); + } + Py_DECREF(versionFn); + } + Py_DECREF(platformModule); + } + + Py_EndInterpreter(newInterp); + PyThreadState_Clear(mainThreadState); + PyThreadState_Swap(mainThreadState); + PyThreadState_DeleteCurrent(); + + return pythonVersion; +} + +PythonManager::PythonManager() : m_InterpretorThreadSave(nullptr) +{ + Logger.LogHead("init plugin manager:"); + this->m_instanceCount = 0; + + // initialize global modules + Logger.LogItem("hook", "locking standard output..."); PyImport_AppendInittab("hook_stdout", &PyInit_CustomStdout); + Logger.LogItem("hook", "locking standard error..."); PyImport_AppendInittab("hook_stderr", &PyInit_CustomStderr); + Logger.LogItem("hook", "inserting Millennium..."); PyImport_AppendInittab("Millennium", &PyInit_Millennium); + + Logger.LogItem("status", "done appending init tabs!"); + Logger.LogItem("python", "initializing python..."); + + PyStatus status; + PyConfig config; + PyConfig_InitPythonConfig(&config); + + /* Read all configuration at once */ + status = PyConfig_Read(&config); + + if (PyStatus_Exception(status)) { + Logger.Error("couldn't read config {}", status.err_msg); + goto done; + } + + config.write_bytecode = 0; + +#ifdef _WIN32 + config.module_search_paths_set = 1; + + PyWideStringList_Append(&config.module_search_paths, std::wstring(pythonPath.begin(), pythonPath.end()).c_str()); + PyWideStringList_Append(&config.module_search_paths, std::wstring(pythonLibs.begin(), pythonLibs.end()).c_str()); +#endif + + status = Py_InitializeFromConfig(&config); + + if (PyStatus_Exception(status)) { + Logger.Error("couldn't initialize from config {}", status.err_msg); + goto done; + } + + m_InterpretorThreadSave = PyEval_SaveThread(); +done: + PyConfig_Clear(&config); + + const std::string version = GetPythonVersion(); + Logger.LogItem("python", fmt::format("initialized python {}", version)); + + if (version != "3.11.8") { + Logger.Warn("Millennium is intended to run python 3.11.8. You may be prone to stability issues..."); + } +} + +PythonManager::~PythonManager() +{ + Logger.Error("PythonManager was destroyed."); + PyEval_RestoreThread(m_InterpretorThreadSave); + // Py_FinalizeEx(); +} + +bool PythonManager::CreatePythonInstance(SettingsStore::PluginTypeSchema& plugin, std::function callback) +{ + const std::string pluginName = plugin.pluginName; + this->m_instanceCount++; + + auto thread = std::thread([this, pluginName, callback, &plugin] + { + PyThreadState* threadStateMain = PyThreadState_New(PyInterpreterState_Main()); + PyEval_RestoreThread(threadStateMain); + PyThreadState* interpreterState = Py_NewInterpreter(); + + PyThreadState_Swap(interpreterState); + { + this->m_pythonInstances.push_back({ pluginName, interpreterState }); + RedirectOutput(); + callback(plugin); + } + //Py_EndInterpreter(subts); closes intepretor + + PyThreadState_Clear(threadStateMain); + PyThreadState_Swap(threadStateMain); + PyThreadState_DeleteCurrent(); + }); + + this->m_threadPool.push_back(std::move(thread)); + return true; +} + +bool PythonManager::ShutdownPlugin(std::string plugin_name) +{ + bool success = false; + + for (auto it = this->m_pythonInstances.begin(); it != this->m_pythonInstances.end(); ++it) + { + const auto& [pluginName, thread_ptr] = *it; + + if (pluginName != plugin_name) + { + continue; + } + + success = true; + + if (thread_ptr == nullptr) + { + Logger.Error(fmt::format("couldn't get thread state ptr from plugin [{}], maybe it crashed or exited early? ", plugin_name)); + success = false; + } + + PyThreadState* threadStateMain = PyThreadState_New(PyInterpreterState_Main()); + PyEval_RestoreThread(threadStateMain); + PyGILState_STATE GILState = PyGILState_Ensure(); + PyThreadState_Swap(thread_ptr); + + if (thread_ptr == NULL) + { + Logger.Error("script execution was queried but the receiving parties thread state was nullptr"); + success = false; + } + + if (PyRun_SimpleString("plugin._unload()") != 0) + { + PyErr_Print(); + Logger.Error("millennium failed to shutdown [{}]", plugin_name); + success = false; + } + + if (PyRun_SimpleString("import sys\nsys.exit()") != 0) + { + PyErr_Print(); + Logger.Error("millennium failed to shutdown [{}]", plugin_name); + success = false; + } + + // close the python interpretor. GIL must be held and the interpretor must be IDLE + Py_EndInterpreter(thread_ptr); + PyThreadState_Clear(threadStateMain); + PyThreadState_Swap(threadStateMain); + PyGILState_Release(GILState); + PyThreadState_DeleteCurrent(); + + this->m_pythonInstances.erase(it); + break; + } + + Logger.Log("Successfully shut down [{}].\nRemaining: ", plugin_name); + + for (const auto& plugin: this->m_pythonInstances) + { + Logger.Log(plugin.pluginName); + } + + return true; +} + +void PythonManager::WaitForExit() +{ + for (auto& thread : this->m_threadPool) { + thread.detach(); + } +} + +PythonThreadState PythonManager::GetPythonThreadStateFromName(std::string pluginName) +{ + for (const auto& [pluginName, thread_ptr] : this->m_pythonInstances) { + if (pluginName == pluginName) { + return { + pluginName, thread_ptr + }; + } + } + return {}; +} + +std::string PythonManager::GetPluginNameFromThreadState(PyThreadState* thread) +{ + for (const auto& [pluginName, thread_ptr] : this->m_pythonInstances) { + + if (thread_ptr == nullptr) { + Logger.Error("thread_ptr was nullptr"); + return {}; + } + + if (thread_ptr == thread) return pluginName; + } + return {}; +} \ No newline at end of file diff --git a/src/core/py_controller/co_spawn.hpp b/src/core/py_controller/co_spawn.hpp new file mode 100644 index 00000000..e5ed61d3 --- /dev/null +++ b/src/core/py_controller/co_spawn.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +struct PythonThreadState { + std::string pluginName; + PyThreadState* thread_state; +}; + +class PythonManager +{ +private: + PyThreadState* m_InterpretorThreadSave; + short m_instanceCount; + + std::vector m_threadPool; + std::vector m_pythonInstances; + +public: + PythonManager(); + ~PythonManager(); + + bool CreatePythonInstance(SettingsStore::PluginTypeSchema& plugin, std::function callback); + void WaitForExit(); + + PythonThreadState GetPythonThreadStateFromName(std::string pluginName); + std::string GetPluginNameFromThreadState(PyThreadState* thread); + + bool ShutdownPlugin(std::string plugin_name); + + static PythonManager& GetInstance() { + static PythonManager InstanceRef; + return InstanceRef; + } +}; \ No newline at end of file diff --git a/src/deps/deps.h b/src/deps/deps.h index 1fb2177a..0758e5b8 100644 --- a/src/deps/deps.h +++ b/src/deps/deps.h @@ -1,7 +1,7 @@ #pragma once #include -namespace dependencies { - bool audit_package(std::string common_name, std::string package_path, std::string remote_object); - bool embed_python(); +namespace Dependencies +{ + bool GitAuditPackage(std::string commonName, std::string packagePath, std::string remoteObject); } \ No newline at end of file diff --git a/src/deps/module.cc b/src/deps/module.cc index 7f74d68b..88bf0994 100644 --- a/src/deps/module.cc +++ b/src/deps/module.cc @@ -8,7 +8,7 @@ using namespace std::chrono; -namespace dependencies { +namespace Dependencies { /** * @brief fetch head reference callback @@ -16,68 +16,68 @@ namespace dependencies { * copies the oid of the reference into the payload to use in the updater * @return success [C style bool] */ - int fetchhead_ref_cb(const char *name, const char *url, - const git_oid *oid, unsigned int is_merge, void *payload) + int FetchHeadRefCallback(const char*, const char*, const git_oid *oid, unsigned int refCallbackIsMerge, void *payload) { - if (is_merge) + if (refCallbackIsMerge) { - console.log_item("fetchhead_ref_cb", "caught type [merge]"); + Logger.LogItem("fetchhead_ref_cb", "caught type [merge]"); git_oid_cpy((git_oid *)payload, oid); } return 0; } - const int clone_repository(git_repository* repo, std::string package_path, std::string remote_object) { - int error; - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - options.checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + const int CloneRepository(git_repository* repository, std::string packageLocalPath, std::string remoteObject) + { + git_clone_options gitCloneOpts = GIT_CLONE_OPTIONS_INIT; + gitCloneOpts.checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; // can't clone into a non-empty directory - if (std::filesystem::exists(package_path) && !std::filesystem::is_empty(package_path)) { - console.log_item("status", "flusing module path..."); - std::uintmax_t removed_count = std::filesystem::remove_all(package_path); + if (std::filesystem::exists(packageLocalPath) && !std::filesystem::is_empty(packageLocalPath)) + { + std::uintmax_t removedCount = std::filesystem::remove_all(packageLocalPath); + Logger.LogItem("status", fmt::format("flushed {} items", removedCount)); } - else { - console.log_item("status", "ready to clone module..."); + else + { + Logger.LogItem("status", "ready to clone module..."); } - console.log_item("status", "cloning modules..."); - error = git_clone(&repo, remote_object.c_str(), package_path.c_str(), &options); + Logger.LogItem("status", "cloning modules..."); + int cloneSuccessStatus = git_clone(&repository, remoteObject.c_str(), packageLocalPath.c_str(), &gitCloneOpts); - if (error != 0) { - const git_error* e = git_error_last(); - const std::string message = fmt::format("Error cloning frontend modules -> {}", e->message); - console.log_item("status", fmt::format("failed to clone [{}]", e->message), true); + if (cloneSuccessStatus != 0) + { + const git_error* lastError = git_error_last(); + const std::string strErrorMessage = fmt::format("Error cloning frontend modules -> {}", lastError->message); - boxer::show(message.c_str(), "Fatal Error", boxer::Style::Error); - return error; + Logger.LogItem("status", strErrorMessage, true); + boxer::show(strErrorMessage.c_str(), "Fatal Error", boxer::Style::Error); } - //console.log_item("status", "finished module audit!", true); - return error; + return cloneSuccessStatus; } - const int fetch_head(git_repository* repo, std::string package_path) { - int error; - console.log_item("status", "checking for module updates..."); - // Fetch the latest changes from the remote + const int FetchHead(git_repository* repo, std::string package_path) + { + int gitErrorCode; git_remote *remote; - error = git_remote_lookup(&remote, repo, "origin"); + + Logger.LogItem("status", "checking for module updates..."); + gitErrorCode = git_remote_lookup(&remote, repo, "origin"); - if (error < 0) { - console.log_item("error", fmt::format("failed lookup -> {}", git_error_last()->message)); + if (gitErrorCode < 0) + { + Logger.LogItem("error", fmt::format("failed lookup -> {}", git_error_last()->message)); return 0; } git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - error = git_remote_fetch(remote, - NULL, /* refspecs, NULL to use the configured ones */ - &options, /* options, empty for defaults */ - "pull"); /* reflog mesage, usually "fetch" or "pull", you can leave it NULL for "fetch" */ + gitErrorCode = git_remote_fetch(remote, NULL, &options, "pull"); - if (error < 0) { + if (gitErrorCode < 0) + { const git_error * last_error = git_error_last(); - console.log_item("error", fmt::format("failed fetch -> -> klass: {}, message: {}", last_error->klass, last_error->message)); + Logger.LogItem("error", fmt::format("failed fetch -> -> klass: {}, message: {}", last_error->klass, last_error->message)); // Couldn't connect to GitHub, and modules dont already exist. if (last_error->klass == GIT_ERROR_NET && !std::filesystem::exists(package_path.c_str())) @@ -88,59 +88,63 @@ namespace dependencies { } git_oid branchOidToMerge; - git_annotated_commit *their_heads[1]; + git_annotated_commit *annotatedCommit[1]; - git_repository_fetchhead_foreach(repo, fetchhead_ref_cb, &branchOidToMerge); - error = git_annotated_commit_lookup(&their_heads[0], repo, &branchOidToMerge); + git_repository_fetchhead_foreach(repo, FetchHeadRefCallback, &branchOidToMerge); + gitErrorCode = git_annotated_commit_lookup(&annotatedCommit[0], repo, &branchOidToMerge); - if (error < 0) { - console.err("error looking up annotated commit -> {}", git_error_last()->message); + if (gitErrorCode < 0) + { + Logger.Error("error looking up annotated commit -> {}", git_error_last()->message); return 1; } - git_merge_analysis_t anout; - git_merge_preference_t pout; + git_merge_analysis_t analysisOut; + git_merge_preference_t preferenceOut; - console.log_item("status", "analyzing repository information..."); - error = git_merge_analysis(&anout, &pout, repo, (const git_annotated_commit **) their_heads, 1); + Logger.LogItem("status", "analyzing repository information..."); + gitErrorCode = git_merge_analysis(&analysisOut, &preferenceOut, repo, (const git_annotated_commit **) annotatedCommit, 1); - if (error < 0) { - console.log_item("error", fmt::format("couldn't analyze -> {}", git_error_last()->message)); + if (gitErrorCode < 0) + { + Logger.LogItem("error", fmt::format("couldn't analyze -> {}", git_error_last()->message)); return 1; } - if (anout & GIT_MERGE_ANALYSIS_UP_TO_DATE) + if (analysisOut & GIT_MERGE_ANALYSIS_UP_TO_DATE) { - console.log_item("status", "modules seem to be up-to-date!"); + Logger.LogItem("status", "modules seem to be up-to-date!"); - git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(annotatedCommit[0]); git_repository_state_cleanup(repo); git_remote_free(remote); return 0; } - else if (anout & GIT_MERGE_ANALYSIS_FASTFORWARD) + else if (analysisOut & GIT_MERGE_ANALYSIS_FASTFORWARD) { - console.log_item("status", "fast-forwarding modules..."); + Logger.LogItem("status", "fast-forwarding modules..."); - git_reference *ref; - git_reference *newref; + git_reference *referenceOut; + git_reference *newTargetReference; - const char *name = "refs/heads/master"; + const char* pluginName = "refs/heads/master"; - if (git_reference_lookup(&ref, repo, name) == 0) - git_reference_set_target(&newref, ref, &branchOidToMerge, "pull: Fast-forward"); + if (git_reference_lookup(&referenceOut, repo, pluginName) == 0) + { + git_reference_set_target(&newTargetReference, referenceOut, &branchOidToMerge, "pull: Fast-forward"); + } - git_reset_from_annotated(repo, their_heads[0], GIT_RESET_HARD, NULL); + git_reset_from_annotated(repo, annotatedCommit[0], GIT_RESET_HARD, NULL); - git_reference_free(ref); + git_reference_free(referenceOut); git_repository_state_cleanup(repo); } - git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(annotatedCommit[0]); git_repository_state_cleanup(repo); git_remote_free(remote); - return error; + return gitErrorCode; } /** @@ -151,38 +155,40 @@ namespace dependencies { * * @return success status */ - bool audit_package(std::string common_name, std::string package_path, std::string remote_object) + bool GitAuditPackage(std::string common_name, std::string package_path, std::string remote_object) { - const auto start = steady_clock::now(); + const auto startTime = steady_clock::now(); git_libgit2_init(); - console.head(fmt::format("libgit - {} [{} ms]", common_name, duration_cast(steady_clock::now() - start).count())); - console.log_item("modules", package_path); + Logger.LogHead(fmt::format("libgit - {} [{} ms]", common_name, duration_cast(steady_clock::now() - startTime).count())); + Logger.LogItem("modules", package_path); git_repository* repo = nullptr; - int error = git_repository_open(&repo, package_path.c_str()); + int repositoryOpenStatus = git_repository_open(&repo, package_path.c_str()); - switch (error) { - case GIT_ENOTFOUND: { - console.log_item("modules", "repo not found..."); - error = clone_repository(repo, package_path, remote_object); + switch (repositoryOpenStatus) { + case GIT_ENOTFOUND: + { + Logger.LogItem("modules", "repo not found..."); + repositoryOpenStatus = CloneRepository(repo, package_path, remote_object); break; } - case 0: { - console.log_item("modules", "fetching head for updates..."); - error = fetch_head(repo, package_path); + case 0: // no error occured + { + Logger.LogItem("modules", "fetching head for updates..."); + repositoryOpenStatus = FetchHead(repo, package_path); break; } default: { const git_error *e = git_error_last(); - console.log_item("error", fmt::format("couldn't evaluate repo -> {}", e->message)); + Logger.LogItem("error", fmt::format("couldn't evaluate repo -> {}", e->message)); } } - console.log_item("status", fmt::format("done: [{} ms]; ec: [{}]", duration_cast(steady_clock::now() - start).count(), error), true); + Logger.LogItem("status", fmt::format("done: [{} ms]; ec: [{}]", duration_cast(steady_clock::now() - startTime).count(), repositoryOpenStatus), true); // Free resources git_repository_free(repo); git_libgit2_shutdown(); - return error == 0; + return repositoryOpenStatus == 0; } } \ No newline at end of file diff --git a/src/generic/base.h b/src/generic/base.h index de90a88f..feb8fe2a 100644 --- a/src/generic/base.h +++ b/src/generic/base.h @@ -2,14 +2,14 @@ #include #include "stream_parser.h" -static constexpr const char* g_mversion = "1.1.5 (alpha)"; -static constexpr const char* builtins_repo = "https://github.com/SteamClientHomebrew/__builtins__.git"; -static constexpr const char* modules_repo = "https://github.com/SteamClientHomebrew/Packages.git"; +static constexpr const char* g_millenniumVersion = "1.1.5 (alpha)"; +static constexpr const char* builtinsRepository = "https://github.com/SteamClientHomebrew/__builtins__.git"; +static constexpr const char* pythonModulesRepository = "https://github.com/SteamClientHomebrew/Packages.git"; -static std::string millennium_modules_path = (stream_buffer::steam_path() / "steamui" / "plugins" / "__millennium__").string(); -static std::filesystem::path modules_basedir = stream_buffer::steam_path() / ".millennium" / "@modules"; +static std::string builtinsModulesAbsolutePath = (SystemIO::GetSteamPath() / "steamui" / "plugins" / "__millennium__").string(); +static std::filesystem::path pythonModulesBaseDir = SystemIO::GetSteamPath() / ".millennium" / "@modules"; -static const auto pythonPath = (modules_basedir).generic_string(); -static const auto pythonLibs = (modules_basedir / "python311.zip").generic_string(); +static const auto pythonPath = (pythonModulesBaseDir).generic_string(); +static const auto pythonLibs = (pythonModulesBaseDir / "python311.zip").generic_string(); -static const std::filesystem::path logs_dir = stream_buffer::steam_path() / ".millennium/debug.log"; \ No newline at end of file +static const std::filesystem::path logsDirectory = SystemIO::GetSteamPath() / ".millennium/debug.log"; \ No newline at end of file diff --git a/src/generic/stream_parser.cc b/src/generic/stream_parser.cc index a0847f26..ede4ffa0 100644 --- a/src/generic/stream_parser.cc +++ b/src/generic/stream_parser.cc @@ -6,228 +6,253 @@ #include "base.h" #ifdef _WIN32 +#include #include #endif -namespace fs = std::filesystem; +namespace FileSystem = std::filesystem; -namespace stream_buffer +void SettingsStore::SetSettings(std::string file_data) { - std::filesystem::path steam_path() { -#ifdef _WIN32 - char buffer[MAX_PATH]; - DWORD bufferSize = GetEnvironmentVariableA("SteamPath", buffer, MAX_PATH); + const auto path = SystemIO::GetSteamPath() / ".millennium" / "settings.json"; - const std::string path = std::string(buffer, bufferSize); - return path.empty() ? "C:/Program Files (x86)/Steam" : path; -#elif __linux__ - return fmt::format("{}/.steam/steam", std::getenv("HOME")); -#endif + if (!FileSystem::exists(path)) + { + FileSystem::create_directories(path.parent_path()); + std::ofstream outputFile(path.string()); + outputFile << "{}"; } + SystemIO::WriteFileSync(path, file_data); +} - nlohmann::json get_config() { - const auto path = steam_path() / ".millennium" / "settings.json"; - - if (!fs::exists(path)) { - fs::create_directories(path.parent_path()); - std::ofstream outputFile(path.string()); - outputFile << "{}"; - } +nlohmann::json SettingsStore::GetSettings() +{ + const auto path = SystemIO::GetSteamPath() / ".millennium" / "settings.json"; - bool success; - const auto json = file::readJsonSync(path.string(), &success); + if (!FileSystem::exists(path)) + { + FileSystem::create_directories(path.parent_path()); + std::ofstream outputFile(path.string()); + outputFile << "{}"; + } - if (!success) { - std::ofstream outputFile(path.string()); - outputFile << "{}"; - } + bool success; + const auto json = SystemIO::ReadJsonSync(path.string(), &success); - return success ? json : nlohmann::json({}); + if (!success) + { + std::ofstream outputFile(path.string()); + outputFile << "{}"; } - void set_config(std::string file_data) { - const auto path = steam_path() / ".millennium" / "settings.json"; + return success ? json : nlohmann::json({}); +} - if (!fs::exists(path)) { - fs::create_directories(path.parent_path()); - std::ofstream outputFile(path.string()); - outputFile << "{}"; - } - file::writeFileSync(path, file_data); - } +int SettingsStore::InitializeSettingsStore() +{ + auto SettingsStore = this->GetSettings(); - int setup_config() + if (SettingsStore.contains("enabled") && SettingsStore["enabled"].is_array()) { - auto json = get_config(); - - if (json.contains("enabled") && json["enabled"].is_array()) { - bool found = false; - for (const auto& item : json["enabled"]) { - if (item == "millennium__internal") { - found = true; break; - } - } - if (!found) { - json["enabled"].push_back("millennium__internal"); + bool found = false; + + for (const auto& enabledPlugin : SettingsStore["enabled"]) + { + if (enabledPlugin == "millennium__internal") + { + found = true; + break; } } - else { - json["enabled"] = nlohmann::json::array({ "millennium__internal" }); + if (!found) + { + SettingsStore["enabled"].push_back("millennium__internal"); } - set_config(json.dump(4)); - return true; + } + else + { + SettingsStore["enabled"] = nlohmann::json::array({ "millennium__internal" }); } - bool plugin_mgr::set_plugin_status(std::string pname, bool enabled) { + SetSettings(SettingsStore.dump(4)); + return true; +} - console.log("updating plugin status"); - auto json = stream_buffer::get_config(); +bool SettingsStore::TogglePluginStatus(std::string pname, bool enabled) +{ + Logger.Log("updating plugin status"); + auto SettingsStore = this->GetSettings(); - if (enabled) { - if (json.contains("enabled") && json["enabled"].is_array()) { - console.log("pushed back plugin"); - json["enabled"].get>().push_back(pname); - } - else { - console.log("created enabled list"); - json["enabled"] = nlohmann::json::array({pname}); - } + if (enabled) + { + if (SettingsStore.contains("enabled") && SettingsStore["enabled"].is_array()) + { + SettingsStore["enabled"].get>().push_back(pname); } - // disable the plugin else { - if (json.contains("enabled") && json["enabled"].is_array()) { - auto l = json["enabled"].get>(); - // Find and erase pname from the vector - auto it = std::find(l.begin(), l.end(), pname); - if (it != l.end()) { l.erase(it); } - // Update the JSON object - json["enabled"] = l; - console.log("popped plugin from list"); - } - else { - console.err("couldn't disable [{}] as its not enabled.", pname); - } + SettingsStore["enabled"] = nlohmann::json::array({ pname }); } - - std::cout << "updated config -> " << json.dump(4) << std::endl; - set_config(json.dump(4)); - return true; } - - std::vector plugin_mgr::get_enabled() + // disable the plugin + else if (!enabled && SettingsStore.contains("enabled") && SettingsStore["enabled"].is_array()) { - auto json = stream_buffer::get_config(); - return json["enabled"].get>(); + auto EnabledPlugins = SettingsStore["enabled"].get>(); + auto Iterator = std::find(EnabledPlugins.begin(), EnabledPlugins.end(), pname); + + if (Iterator != EnabledPlugins.end()) + { + EnabledPlugins.erase(Iterator); + } + + SettingsStore["enabled"] = EnabledPlugins; } - bool plugin_mgr::is_enabled(std::string plugin_name) { + std::cout << "updated config -> " << SettingsStore.dump(4) << std::endl; + SetSettings(SettingsStore.dump(4)); + return true; +} - for (const auto& plugin : plugin_mgr::get_enabled()) { - if (plugin == plugin_name) { - return true; - } +std::vector SettingsStore::GetEnabledPlugins() +{ + auto json = this->GetSettings(); + return json["enabled"].get>(); +} + +bool SettingsStore::IsEnabledPlugin(std::string plugin_name) +{ + for (const auto& plugin : SettingsStore::GetEnabledPlugins()) + { + if (plugin == plugin_name) + { + return true; } - return false; } + return false; +} - std::vector plugin_mgr::parse_all() - { - const auto plugin_path = steam_path() / "steamui" / "plugins"; - std::vector plugins; +std::vector SettingsStore::ParseAllPlugins() +{ + const auto plugin_path = SystemIO::GetSteamPath() / "steamui" / "plugins"; + std::vector plugins; - try { - for (const auto& entry : std::filesystem::directory_iterator(plugin_path)) + try + { + for (const auto& entry : std::filesystem::directory_iterator(plugin_path)) + { + if (!entry.is_directory()) { - if (entry.is_directory()) - { - const auto skin_json = entry.path() / "plugin.json"; - - if (!fs::exists(skin_json)) { - continue; - } - - try { - const auto json = file::readJsonSync(skin_json.string()); - - stream_buffer::plugin_mgr::plugin_t plugin; - plugin.base_dir = entry.path(); - plugin.backend_abs = entry.path() / "backend" / "main.py"; - plugin.name = (json.contains("name") && json["name"].is_string() ? json["name"].get() : entry.path().filename().string()); - plugin.frontend_abs = fmt::format("plugins/{}/dist/index.js", entry.path().filename().string()); - plugin.pjson = json; - - plugins.push_back(plugin); - } - catch (file::io_except& ex) { - console.err(ex.what()); - } - } + continue; } - } catch (const std::exception& ex) { - std::cout << "Error: " << ex.what() << std::endl; - } - return plugins; - } - - namespace file { - - nlohmann::json readJsonSync(const std::string& filename, bool* success) { + const auto skin_json = entry.path() / "plugin.json"; - std::ifstream file(filename); - if (!file.is_open()) { - if (success != nullptr) *success = false; + if (!FileSystem::exists(skin_json)) + { + continue; } - std::string fileContent((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + try { + const auto json = SystemIO::ReadJsonSync(skin_json.string()); - if (nlohmann::json::accept(fileContent)) { - if (success != nullptr) *success = true; - return nlohmann::json::parse(fileContent); + PluginTypeSchema plugin; + plugin.pluginBaseDirectory = entry.path(); + plugin.backendAbsoluteDirectory = entry.path() / "backend" / "main.py"; + plugin.pluginName = (json.contains("name") && json["name"].is_string() ? json["name"].get() : entry.path().filename().string()); + plugin.frontendAbsoluteDirectory = fmt::format("plugins/{}/dist/index.js", entry.path().filename().string()); + plugin.pluginJson = json; + + plugins.push_back(plugin); } - else { - console.err("error reading [{}]", filename); - if (success != nullptr) *success = false; - return {}; + catch (SystemIO::FileException& ex) { + Logger.Error(ex.what()); } } + } + catch (const std::exception& ex) { + std::cout << "Error: " << ex.what() << std::endl; + } - std::string readFileSync(const std::string& filename) { - std::ifstream file(filename); - if (!file.is_open()) { - console.err("failed to open file [readJsonSync]"); - return {}; - } + return plugins; +} + +namespace SystemIO { + + std::filesystem::path GetSteamPath() { +#ifdef _WIN32 + char buffer[MAX_PATH]; + DWORD bufferSize = GetEnvironmentVariableA("SteamPath", buffer, MAX_PATH); + + const std::string path = std::string(buffer, bufferSize); + return path.empty() ? "C:/Program Files (x86)/Steam" : path; +#elif __linux__ + return fmt::format("{}/.steam/steam", std::getenv("HOME")); +#endif + } - std::string fileContent((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - return fileContent; + nlohmann::json ReadJsonSync(const std::string& filename, bool* success) + { + std::ifstream outputLogStream(filename); + + if (!outputLogStream.is_open()) + { + if (success != nullptr) + *success = false; } - void writeFileSync(const std::filesystem::path& filePath, std::string content) { - std::ofstream outFile(filePath); + std::string fileContent((std::istreambuf_iterator(outputLogStream)), std::istreambuf_iterator()); - if (outFile.is_open()) { - outFile << content; - outFile.close(); - } + if (nlohmann::json::accept(fileContent)) { + if (success != nullptr) *success = true; + return nlohmann::json::parse(fileContent); + } + else { + Logger.Error("error reading [{}]", filename); + if (success != nullptr) *success = false; + return {}; } + } - void writeFileBytesSync(const std::filesystem::path& filePath, const std::vector& fileContent) { - console.log(fmt::format("writing file to: {}", filePath.string())); + std::string ReadFileSync(const std::string& filename) + { + std::ifstream outputLogStream(filename); + if (!outputLogStream.is_open()) + { + Logger.Error("failed to open file [readJsonSync]"); + return {}; + } - std::ofstream fileStream(filePath, std::ios::binary); - if (!fileStream) { - console.log(fmt::format("Failed to open file for writing: {}", filePath.string())); - return; - } + std::string fileContent((std::istreambuf_iterator(outputLogStream)), std::istreambuf_iterator()); + return fileContent; + } - fileStream.write(reinterpret_cast(fileContent.data()), fileContent.size()); + void WriteFileSync(const std::filesystem::path& filePath, std::string content) + { + std::ofstream outFile(filePath); - if (!fileStream) { - console.log(fmt::format("Error writing to file: {}", filePath.string())); - } + if (outFile.is_open()) { + outFile << content; + outFile.close(); + } + } + + void WriteFileBytesSync(const std::filesystem::path& filePath, const std::vector& fileContent) + { + Logger.Log(fmt::format("writing file to: {}", filePath.string())); - fileStream.close(); + std::ofstream fileStream(filePath, std::ios::binary); + if (!fileStream) + { + Logger.Log(fmt::format("Failed to open file for writing: {}", filePath.string())); + return; } + + fileStream.write(reinterpret_cast(fileContent.data()), fileContent.size()); + + if (!fileStream) + { + Logger.Log(fmt::format("Error writing to file: {}", filePath.string())); + } + + fileStream.close(); } } \ No newline at end of file diff --git a/src/generic/stream_parser.h b/src/generic/stream_parser.h index 6fa414a8..68cf2ff1 100644 --- a/src/generic/stream_parser.h +++ b/src/generic/stream_parser.h @@ -4,46 +4,45 @@ #include #include -namespace stream_buffer +class SettingsStore { - namespace plugin_mgr +public: + struct PluginTypeSchema { - struct plugin_t - { - std::filesystem::path base_dir; - std::filesystem::path backend_abs; - std::string name; - std::string frontend_abs; - nlohmann::basic_json<> pjson; - }; + std::filesystem::path pluginBaseDirectory; + std::filesystem::path backendAbsoluteDirectory; + std::string pluginName; + std::string frontendAbsoluteDirectory; + nlohmann::basic_json<> pluginJson; + }; - std::vector get_enabled(); - bool is_enabled(std::string plugin_name); + std::vector GetEnabledPlugins(); + bool IsEnabledPlugin(std::string pluginName); - bool set_plugin_status(std::string pname, bool enabled); - std::vector parse_all(); - }; + bool TogglePluginStatus(std::string pluginName, bool enabled); + std::vector ParseAllPlugins(); - void set_config(std::string file_data); - int setup_config(); + void SetSettings(std::string settingsData); + nlohmann::json GetSettings(); - std::filesystem::path steam_path(); + int InitializeSettingsStore(); +}; - namespace file - { - class io_except : public std::exception { - public: - io_except(const std::string& message) : msg(message) {} - virtual const char* what() const noexcept override { - return msg.c_str(); - } - private: - std::string msg; - }; +namespace SystemIO +{ + class FileException : public std::exception { + public: + FileException(const std::string& message) : msg(message) {} + virtual const char* what() const noexcept override { + return msg.c_str(); + } + private: + std::string msg; + }; - nlohmann::json readJsonSync(const std::string& filename, bool* success = nullptr); - std::string readFileSync(const std::string& filename); - void writeFileSync(const std::filesystem::path& filePath, std::string content); - void writeFileBytesSync(const std::filesystem::path& filePath, const std::vector& fileContent); - } + std::filesystem::path GetSteamPath(); + nlohmann::json ReadJsonSync(const std::string& filename, bool* success = nullptr); + std::string ReadFileSync(const std::string& filename); + void WriteFileSync(const std::filesystem::path& filePath, std::string content); + void WriteFileBytesSync(const std::filesystem::path& filePath, const std::vector& fileContent); } \ No newline at end of file diff --git a/src/socket/await_pipe.h b/src/socket/await_pipe.h index 47cd43cf..37ea174d 100644 --- a/src/socket/await_pipe.h +++ b/src/socket/await_pipe.h @@ -2,20 +2,24 @@ #include #include -const std::string get_steam_context() { - nlohmann::basic_json<> instance = nlohmann::json::parse(http_get("http://localhost:8080/json/version")); - +const std::string GetSteamBrowserContext() +{ + nlohmann::basic_json<> instance = nlohmann::json::parse(GetRequest("http://localhost:8080/json/version")); return instance["webSocketDebuggerUrl"]; } -const std::string get_shared_js() { +const std::string GetSharedJsContext() +{ std::string context; - while (context.empty()) { - nlohmann::basic_json<> instances = nlohmann::json::parse(http_get("http://localhost:8080/json")); + while (context.empty()) + { + nlohmann::basic_json<> instances = nlohmann::json::parse(GetRequest("http://localhost:8080/json")); - for (const auto& instance : instances) { - if (instance["title"] == "SharedJSContext") { + for (const auto& instance : instances) + { + if (instance["title"] == "SharedJSContext") + { context = instance["webSocketDebuggerUrl"].get(); break; } diff --git a/src/utilities/encoding.h b/src/utilities/encoding.h index b0243107..e8d14165 100644 --- a/src/utilities/encoding.h +++ b/src/utilities/encoding.h @@ -62,7 +62,7 @@ static std::string base64_decode(const std::string &in) { * - Encodes the input string into base64 format. * - Adds padding characters ('=') if necessary to make the length a multiple of 4. */ -static std::string base64_encode(const std::string &in) { +static std::string Base64Encode(const std::string &in) { std::string out; int val = 0, valb = -6; diff --git a/src/utilities/http.h b/src/utilities/http.h index d6caa8b0..a516c5dd 100644 --- a/src/utilities/http.h +++ b/src/utilities/http.h @@ -5,13 +5,14 @@ #include #include -static std::string http_get(const char* url) { +static std::string GetRequest(const char* url) +{ cpr::Response response = cpr::Get(cpr::Url{url}, cpr::UserAgent{"millennium.patcher/1.0"}); - if (response.error) { - //console.err("[http] {}", response.error.message); + if (response.error) + { std::this_thread::sleep_for(std::chrono::milliseconds(10)); - return http_get(url); + return GetRequest(url); } return response.text; diff --git a/src/utilities/log.cpp b/src/utilities/log.cpp index 8522f16d..bc71ee62 100644 --- a/src/utilities/log.cpp +++ b/src/utilities/log.cpp @@ -11,9 +11,9 @@ #include <_win32/cmd.h> #endif -output_console console; +OutputLogger Logger; -std::string output_console::get_time() +std::string OutputLogger::GetLocalTime() { auto now = std::chrono::system_clock::now(); auto time = std::chrono::system_clock::to_time_t(now); @@ -25,142 +25,113 @@ std::string output_console::get_time() return fmt::format("[{}]", ss.str()); } -void output_console::_print(std::string type, const std::string& message) +void OutputLogger::PrintMessage(std::string type, const std::string& message) { std::lock_guard lock(logMutex); - std::cout << get_time() << type << message << "\n"; - *file << get_time() << type << message << "\n"; - - (*file).flush(); + std::cout << GetLocalTime() << type << message << "\n"; + *outputLogStream << GetLocalTime() << type << message << "\n"; + outputLogStream->flush(); } -output_console::output_console() +OutputLogger::OutputLogger() { -#ifdef _WIN32 - if (steam::get().params.has("-dev") && static_cast(AllocConsole())) { + #ifdef _WIN32 + if (steam::get().params.has("-dev") && static_cast(AllocConsole())) + { void(freopen("CONOUT$", "w", stdout)); void(freopen("CONOUT$", "w", stderr)); } SetConsoleOutputCP(CP_UTF8); -#endif - file = std::make_shared("output.txt"); + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + #endif + + outputLogStream = std::make_shared("output.txt"); // Check if the file stream is open - if (!file->is_open()) { + if (!outputLogStream->is_open()) + { std::cerr << "Failed to open the file." << std::endl; return; } - -#ifdef _WIN32 + #ifdef _WIN32 std::filesystem::path fileName = ".millennium/debug.log"; -#elif __linux__ + #elif __linux__ std::filesystem::path fileName = fmt::format("{}/.steam/steam/.millennium/debug.log", std::getenv("HOME")); -#endif + #endif - try { + try + { std::filesystem::create_directories(fileName.parent_path()); } catch (const std::exception& e) { std::cout << "Error: " << e.what() << std::endl; } } -output_console::~output_console() { - file->close(); -} -void output_console::log(std::string val) { - _print(" [info] ", val); -} - -void output_console::py(std::string pname, std::string val) +OutputLogger::~OutputLogger() { - #ifdef _WIN32 - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN); - #endif - std::cout << get_time() << " [" << pname << "] "; - #ifdef _WIN32 - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); - #endif - std::cout << val << "\n"; - (*file) << get_time() << " [" << pname << "] " << val << "\n"; - (*file).flush(); + outputLogStream->close(); } -void output_console::warn(std::string val) +void OutputLogger::LogPluginMessage(std::string pluginName, std::string strMessage) { #ifdef _WIN32 - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN); + { + SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN); + } #endif - _print(" [warn] ", val); + std::cout << GetLocalTime() << " [" << pluginName << "] "; #ifdef _WIN32 - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + { + SetConsoleTextAttribute(hConsole, COLOR_WHITE); + } #endif + std::cout << strMessage << "\n"; + + (*outputLogStream) << GetLocalTime() << " [" << pluginName << "] " << strMessage << "\n"; + (*outputLogStream).flush(); } -void output_console::err(std::string val) +void OutputLogger::LogHead(std::string strHeadTitle) { + const auto message = fmt::format("\n[┬] {}", strHeadTitle); + #ifdef _WIN32 - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, FOREGROUND_RED); + { + SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN); + } #endif - _print(" [error] ", val); + std::cout << "\n[┬] "; #ifdef _WIN32 - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + { + SetConsoleTextAttribute(hConsole, COLOR_WHITE); + } #endif + std::cout << strHeadTitle << "\n"; + *outputLogStream << message << "\n"; + outputLogStream->flush(); } -template -void output_console::warn(std::string fmt, Args&&... args) +void OutputLogger::LogItem(std::string pluginName, std::string strMessage, bool end) { + std::string connectorPiece = end ? "└" : "├"; + const auto message = fmt::format(" {}──[{}]: {}", connectorPiece, pluginName, strMessage); + #ifdef _WIN32 - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN); + { + SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN); + } #endif - _print(" [warn] ", fmt::format(fmt, std::forward(args)...)); + std::cout << " " << connectorPiece << "──[" < -#include #ifdef _WIN32 +#include #include #endif #include #include -#include -/// -/// incredibly shitty logger class -/// -class output_console { +#define COLOR_WHITE FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE + +class OutputLogger +{ private: - std::shared_ptr file; + HANDLE hConsole; + std::mutex logMutex; std::shared_ptr teeStreamPtr; - std::mutex logMutex; + std::shared_ptr outputLogStream; - std::string get_time(); - void _print(std::string type, const std::string& message); + std::string GetLocalTime(); + void PrintMessage(std::string type, const std::string& message); public: - output_console(const output_console&) = delete; - output_console& operator=(const output_console&) = delete; + OutputLogger(const OutputLogger&) = delete; + OutputLogger& operator=(const OutputLogger&) = delete; - // output_console& instance() { - // static output_console instance; - // return instance; - // } - bool consoleAllocated; + OutputLogger(); + ~OutputLogger(); - output_console(); - ~output_console(); - - void py(std::string pname, std::string val); + void LogPluginMessage(std::string pname, std::string val); template - void log(std::string fmt, Args&&... args); - void log(std::string val); + void Log(std::string fmt, Args&&... args) + { + PrintMessage(" [info] ", (sizeof...(args) == 0) ? fmt : fmt::format(fmt, std::forward(args)...)); + } template - void err(std::string fmt, Args&&... args); - void err(std::string val); + void Error(std::string fmt, Args&&... args) + { + #ifdef _WIN32 + { + SetConsoleTextAttribute(hConsole, FOREGROUND_RED); + } + #endif + PrintMessage(" [error] ", (sizeof...(args) == 0) ? fmt : fmt::format(fmt, std::forward(args)...)); + #ifdef _WIN32 + { + SetConsoleTextAttribute(hConsole, COLOR_WHITE); + } + #endif + } template - void warn(std::string fmt, Args&&... args); - void warn(std::string val); - - void head(std::string val); - void log_item(std::string name, std::string data, bool end=false); + void Warn(std::string fmt, Args&&... args) + { + #ifdef _WIN32 + { + SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN); + } + #endif + PrintMessage(" [warn] ", (sizeof...(args) == 0) ? fmt : fmt::format(fmt, std::forward(args)...)); + #ifdef _WIN32 + { + SetConsoleTextAttribute(hConsole, COLOR_WHITE); + } + #endif + } + + void LogHead(std::string val); + void LogItem(std::string pluginName, std::string data, bool end=false); }; -// static output_console console; - -template -void output_console::log(std::string fmt, Args&&... args) { - _print(" [info] ", fmt::format(fmt, std::forward(args)...)); -} - -template -void output_console::err(std::string fmt, Args&&... args) -{ -#ifdef _WIN32 - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, FOREGROUND_RED); -#endif - _print(" [error] ", fmt::format(fmt, std::forward(args)...)); -#ifdef _WIN32 - SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); -#endif -} - -extern output_console console; - - - -// // #define LOG_DEBUG(...) output_console::instance()->debug(__VA_ARGS__) -// #define LOG_INFO(...) output_console::instance()->info(__VA_ARGS__) -// #define LOG_WARN(...) output_console::instance()->warn(__VA_ARGS__) -// #define LOG_ERROR(...) output_console::instance()->error(__VA_ARGS__) -// #define LOG_HEAD(...) output_console::instance()->head(__VA_ARGS__) -// #define LOG_ITEM(...) output_console::instance()->log_item(__VA_ARGS__) \ No newline at end of file +extern OutputLogger Logger; \ No newline at end of file diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 00000000..07324961 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,14 @@ +{ + "default-registry": { + "kind": "git", + "baseline": "66045de4dcc5da3d1029c02b606307f5951dcb22", + "repository": "https://github.com/microsoft/vcpkg" + }, + "registries": [ + { + "kind": "artifact", + "location": "https://aka.ms/vcpkg-ce-default", + "name": "microsoft" + } + ] +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..30520112 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,7 @@ +{ + "dependencies": [ + "libgit2", + "python3", + "cpr" + ] +} diff --git a/version.rc b/version.rc new file mode 100644 index 00000000..13976edc --- /dev/null +++ b/version.rc @@ -0,0 +1,52 @@ +#include +//#define VER_FILEVERSION 3,10,349,0 +//#define VER_FILEVERSION_STR "3.10.349.0\0" +// +//#define VER_PRODUCTVERSION 3,10,0,0 +//#define VER_PRODUCTVERSION_STR "3.10\0" + +#ifndef DEBUG +#define VER_DEBUG 0 +#else +#define VER_DEBUG VS_FF_DEBUG +#endif + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG) +FILEOS VOS__WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", VER_COMPANYNAME_STR + VALUE "FileDescription", VER_FILEDESCRIPTION_STR + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", VER_INTERNALNAME_STR + VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR + VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR + VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR + VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR + VALUE "ProductName", VER_PRODUCTNAME_STR + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + /* The following line should only be modified for localized versions. */ + /* It consists of any number of WORD,WORD pairs, with each pair */ + /* describing a language,codepage combination supported by the file. */ + /* */ + /* For example, a file might have values "0x409,1252" indicating that it */ + /* supports English language (0x409) in the Windows ANSI codepage (1252). */ + + VALUE "Translation", 0x409, 1252 + + END +END \ No newline at end of file