#include #include "console.hpp" #include "loader/component_loader.hpp" #include "game/game.hpp" #include "command.hpp" #include #include #include #include namespace game_console { void print(int type, const std::string& data); } namespace console { namespace { using message_queue = std::queue; utils::concurrency::container messages; bool native_console() { static const auto flag = utils::flags::has_flag("nativeconsole"); return flag; } void hide_console() { auto* const con_window = GetConsoleWindow(); DWORD process; GetWindowThreadProcessId(con_window, &process); if (!native_console() && (process == GetCurrentProcessId() || IsDebuggerPresent())) { ShowWindow(con_window, SW_HIDE); } } std::string format(va_list* ap, const char* message) { static thread_local char buffer[0x1000]; const auto count = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), message, *ap); if (count < 0) return {}; return {buffer, static_cast(count)}; } void dispatch_message(const int type, const std::string& message) { if (native_console()) { printf("%s\n", message.data()); return; } game_console::print(type, message); messages.access([&message](message_queue& msgs) { msgs.emplace(message); }); } void append_text(const char* text) { dispatch_message(con_type_info, text); } } class component final : public component_interface { public: component() { hide_console(); if (native_console()) { setvbuf(stdout, nullptr, _IONBF, 0); setvbuf(stderr, nullptr, _IONBF, 0); } else { (void)_pipe(this->handles_, 1024, _O_TEXT); (void)_dup2(this->handles_[1], 1); (void)_dup2(this->handles_[1], 2); } } void post_start() override { this->terminate_runner_ = false; this->console_runner_ = utils::thread::create_named_thread("Console IO", [this] { if (native_console()) { this->native_input(); } else { this->runner(); } }); } void pre_destroy() override { this->terminate_runner_ = true; printf("\r\n"); _flushall(); if (this->console_runner_.joinable()) { this->console_runner_.join(); } if (this->console_thread_.joinable()) { this->console_thread_.join(); } #ifndef NATIVE_CONSOLE _close(this->handles_[0]); _close(this->handles_[1]); #endif messages.access([&](message_queue& msgs) { msgs = {}; }); } void post_unpack() override { // Redirect input (]command) utils::hook::jump(SELECT_VALUE(0x1403E34C0, 0x1405141E0), append_text); // H1(1.4) this->initialize(); } private: volatile bool console_initialized_ = false; volatile bool terminate_runner_ = false; std::thread console_runner_; std::thread console_thread_; int handles_[2]{}; void initialize() { this->console_thread_ = utils::thread::create_named_thread("Console", [this]() { if (!native_console() && (game::environment::is_dedi() || !utils::flags::has_flag("noconsole"))) { game::Sys_ShowConsole(); } if (!game::environment::is_dedi()) { // Hide that shit ShowWindow(console::get_window(), SW_MINIMIZE); } { messages.access([&](message_queue&) { this->console_initialized_ = true; }); } MSG msg; while (!this->terminate_runner_) { if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE)) { if (msg.message == WM_QUIT) { command::execute("quit", false); break; } TranslateMessage(&msg); DispatchMessage(&msg); } else { this->log_messages(); std::this_thread::sleep_for(1ms); } } }); } void log_messages() { /*while*/ if (this->console_initialized_ && !messages.get_raw().empty()) { std::queue message_queue_copy; { messages.access([&](message_queue& msgs) { message_queue_copy = std::move(msgs); msgs = {}; }); } while (!message_queue_copy.empty()) { log_message(message_queue_copy.front()); message_queue_copy.pop(); } } fflush(stdout); fflush(stderr); } static void log_message(const std::string& message) { OutputDebugStringA(message.data()); game::Conbuf_AppendText(message.data()); } void runner() { char buffer[1024]; while (!this->terminate_runner_ && this->handles_[0]) { const auto len = _read(this->handles_[0], buffer, sizeof(buffer)); if (len > 0) { dispatch_message(con_type_info, std::string(buffer, len)); } else { std::this_thread::sleep_for(1ms); } } std::this_thread::yield(); } void native_input() { std::string cmd; while (!this->terminate_runner_) { std::getline(std::cin, cmd); command::execute(cmd); } std::this_thread::yield(); } }; HWND get_window() { return *reinterpret_cast((SELECT_VALUE(0x14CF56C00, 0x14DDFC2D0))); // H1(1.4) } void set_title(std::string title) { SetWindowText(get_window(), title.data()); } void set_size(const int width, const int height) { RECT rect; GetWindowRect(get_window(), &rect); SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0); auto* const logo_window = *reinterpret_cast(SELECT_VALUE(0x14CF56C10, 0x14DDFC2E0)); // H1(1.4) SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0); } void print(const int type, const char* fmt, ...) { va_list ap; va_start(ap, fmt); const auto result = format(&ap, fmt); va_end(ap); dispatch_message(type, result); } } //REGISTER_COMPONENT(console::component)