mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
409 lines
15 KiB
C++
409 lines
15 KiB
C++
#include "core/stdafx.h"
|
|
#include "core/init.h"
|
|
#include "tier0/cvar.h"
|
|
#include "windows/id3dx.h"
|
|
#include "windows/console.h"
|
|
#include "gameui/IConsole.h"
|
|
#include "client/IVEngineClient.h"
|
|
|
|
/******************************************************************************
|
|
-------------------------------------------------------------------------------
|
|
File : IConsole.cpp
|
|
Date : 18:07:2021
|
|
Author : Kawe Mazidjatari
|
|
Purpose: Implements the in-game console frontend
|
|
-------------------------------------------------------------------------------
|
|
History:
|
|
- 15:06:2021 | 14:56 : Created by Kawe Mazidjatari
|
|
- 07:08:2021 | 15:22 : Multithread 'CommandExecute' operations to prevent deadlock in render thread
|
|
- 07:08:2021 | 15:25 : Fix a race condition that occured when detaching the 'CommandExecute' thread
|
|
|
|
******************************************************************************/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CConsole::CConsole()
|
|
{
|
|
ClearLog();
|
|
memset(m_szInputBuf, 0, sizeof(m_szInputBuf));
|
|
|
|
m_nHistoryPos = -1;
|
|
m_bAutoScroll = true;
|
|
m_bScrollToBottom = false;
|
|
m_bThemeSet = false;
|
|
|
|
m_ivCommands.push_back("CLEAR");
|
|
m_ivCommands.push_back("HELP");
|
|
m_ivCommands.push_back("HISTORY");
|
|
AddLog("[DEBUG] THREAD ID: %ld\n", g_dThreadId);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CConsole::~CConsole()
|
|
{
|
|
ClearLog();
|
|
for (int i = 0; i < m_ivHistory.Size; i++)
|
|
{
|
|
free(m_ivHistory[i]);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: draws the console frontend
|
|
//-----------------------------------------------------------------------------
|
|
void CConsole::Draw(const char* title, bool* bDraw)
|
|
{
|
|
bool copy_to_clipboard = false;
|
|
static auto iConsoleConfig = &g_pImGuiConfig->IConsole_Config;
|
|
static auto iBrowserConfig = &g_pImGuiConfig->IBrowser_Config;
|
|
|
|
if (!m_bThemeSet)
|
|
{
|
|
SetStyleVar();
|
|
m_bThemeSet = true;
|
|
}
|
|
if (iConsoleConfig->m_bAutoClear && Items.Size > iConsoleConfig->m_nAutoClearLimit) // Check if Auto-Clear is enabled and if its above our limit.
|
|
{
|
|
ClearLog();
|
|
m_ivHistory.clear();
|
|
}
|
|
|
|
//ImGui::ShowStyleEditor();
|
|
|
|
ImGui::SetNextWindowSize(ImVec2(1000, 600), ImGuiCond_FirstUseEver);
|
|
ImGui::SetWindowPos(ImVec2(-1000, 50), ImGuiCond_FirstUseEver);
|
|
|
|
if (!ImGui::Begin(title, bDraw))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
if (*bDraw == NULL)
|
|
{
|
|
g_bShowConsole = false;
|
|
}
|
|
|
|
// Reserve enough left-over height and width for 1 separator + 1 input text
|
|
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
|
|
const float footer_width_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetWindowWidth();
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
ImGui::Separator();
|
|
if (ImGui::BeginPopup("Options"))
|
|
{
|
|
ImGui::Checkbox("Auto-Scroll", &m_bAutoScroll);
|
|
|
|
if (ImGui::Checkbox("Auto-Clear", &iConsoleConfig->m_bAutoClear))
|
|
{
|
|
g_pImGuiConfig->Save();
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::PushItemWidth(100);
|
|
|
|
if (ImGui::InputInt("Limit##AutoClearAfterCertainIndexCGameConsole", &iConsoleConfig->m_nAutoClearLimit))
|
|
{
|
|
g_pImGuiConfig->Save();
|
|
}
|
|
|
|
ImGui::PopItemWidth();
|
|
|
|
if (ImGui::SmallButton("Clear"))
|
|
{
|
|
ClearLog();
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
copy_to_clipboard = ImGui::SmallButton("Copy");
|
|
|
|
ImGui::Text("Console Hotkey:");
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Hotkey("##OpenIConsoleBind0", &iConsoleConfig->m_nBind0, ImVec2(80, 80)))
|
|
{
|
|
g_pImGuiConfig->Save();
|
|
}
|
|
|
|
ImGui::Text("Browser Hotkey:");
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::Hotkey("##OpenIBrowserBind0", &iBrowserConfig->m_nBind0, ImVec2(80, 80)))
|
|
{
|
|
g_pImGuiConfig->Save();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
if (ImGui::Button("Options"))
|
|
{
|
|
ImGui::OpenPopup("Options");
|
|
}
|
|
ImGui::SameLine();
|
|
m_itFilter.Draw("Filter [\"-incl,-excl\"] [\"error\"]", footer_width_to_reserve - 500);
|
|
ImGui::Separator();
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ 4.f, 6.f });
|
|
if (copy_to_clipboard)
|
|
{
|
|
ImGui::LogToClipboard();
|
|
}
|
|
for (int i = 0; i < Items.Size; i++)
|
|
{
|
|
const char* item = Items[i];
|
|
if (!m_itFilter.PassFilter(item))
|
|
{
|
|
continue;
|
|
}
|
|
///////////////////////////////////////////////////////////////////
|
|
ImVec4 color;
|
|
bool has_color = false;
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// General
|
|
if (strstr(item, "[INFO]")) { color = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); has_color = true; }
|
|
if (strstr(item, "[ERROR]")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "[DEBUG]")) { color = ImVec4(0.00f, 0.30f, 1.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "[WARNING]")) { color = ImVec4(1.00f, 1.00f, 0.00f, 0.80f); has_color = true; }
|
|
if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.00f, 0.80f, 0.60f, 1.00f); has_color = true; }
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Script virtual machines per game dll
|
|
if (strstr(item, "Script(S):")) { color = ImVec4(0.59f, 0.58f, 0.73f, 1.00f); has_color = true; }
|
|
if (strstr(item, "Script(C):")) { color = ImVec4(0.59f, 0.58f, 0.63f, 1.00f); has_color = true; }
|
|
if (strstr(item, "Script(U):")) { color = ImVec4(0.59f, 0.48f, 0.53f, 1.00f); has_color = true; }
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Native per game dll
|
|
if (strstr(item, "Native(S):")) { color = ImVec4(0.59f, 0.58f, 0.73f, 1.00f); has_color = true; }
|
|
if (strstr(item, "Native(C):")) { color = ImVec4(0.59f, 0.58f, 0.63f, 1.00f); has_color = true; }
|
|
if (strstr(item, "Native(U):")) { color = ImVec4(0.59f, 0.48f, 0.53f, 1.00f); has_color = true; }
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Native per sys dll
|
|
if (strstr(item, "Native(E):")) { color = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); has_color = true; }
|
|
if (strstr(item, "Native(F):")) { color = ImVec4(0.32f, 0.64f, 0.72f, 1.00f); has_color = true; }
|
|
if (strstr(item, "Native(R):")) { color = ImVec4(0.36f, 0.70f, 0.35f, 1.00f); has_color = true; }
|
|
if (strstr(item, "Native(M):")) { color = ImVec4(0.75f, 0.41f, 0.67f, 1.00f); has_color = true; }
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Callbacks
|
|
//if (strstr(item, "CodeCallback_")) { color = ImVec4(0.00f, 0.30f, 1.00f, 1.00f); has_color = true; }
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Squirrel VM script errors
|
|
if (strstr(item, ".gnut")) { color = ImVec4(1.00f, 1.00f, 1.00f, 0.60f); has_color = true; }
|
|
if (strstr(item, ".nut")) { color = ImVec4(1.00f, 1.00f, 1.00f, 0.60f); has_color = true; }
|
|
if (strstr(item, "[CLIENT]")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "[SERVER]")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "[UI]")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "SCRIPT ERROR")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "SCRIPT COMPILE")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, ".gnut #")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, ".nut #")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "): -> ")) { color = ImVec4(1.00f, 0.00f, 0.00f, 1.00f); has_color = true; }
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Squirrel VM script debug
|
|
if (strstr(item, "CALLSTACK")) { color = ImVec4(1.00f, 1.00f, 0.00f, 0.80f); has_color = true; }
|
|
if (strstr(item, "LOCALS")) { color = ImVec4(1.00f, 1.00f, 0.00f, 0.80f); has_color = true; }
|
|
if (strstr(item, "*FUNCTION")) { color = ImVec4(1.00f, 1.00f, 0.00f, 0.80f); has_color = true; }
|
|
if (strstr(item, "DIAGPRINTS")) { color = ImVec4(1.00f, 1.00f, 0.00f, 0.80f); has_color = true; }
|
|
if (strstr(item, " File : ")) { color = ImVec4(0.00f, 0.30f, 1.00f, 1.00f); has_color = true; }
|
|
if (strstr(item, "<><>GRX<><>")) { color = ImVec4(0.00f, 0.30f, 1.00f, 1.00f); has_color = true; }
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Filters
|
|
//if (strstr(item, ") -> ")) { color = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); has_color = true; }
|
|
|
|
if (has_color) { ImGui::PushStyleColor(ImGuiCol_Text, color); }
|
|
ImGui::TextWrapped(item);
|
|
if (has_color) { ImGui::PopStyleColor(); }
|
|
}
|
|
if (copy_to_clipboard) { ImGui::LogFinish(); }
|
|
|
|
if (m_bScrollToBottom || (m_bAutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) { ImGui::SetScrollHereY(1.0f); }
|
|
m_bScrollToBottom = false;
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
ImGui::PopStyleVar();
|
|
ImGui::EndChild();
|
|
ImGui::Separator();
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Console
|
|
bool reclaim_focus = false;
|
|
ImGui::PushItemWidth(footer_width_to_reserve - 80);
|
|
if (ImGui::IsWindowAppearing()) { ImGui::SetKeyboardFocusHere(); }
|
|
ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory;
|
|
|
|
if (ImGui::InputText("##input", m_szInputBuf, IM_ARRAYSIZE(m_szInputBuf), input_text_flags, &TextEditCallbackStub, (void*)this))
|
|
{
|
|
char* s = m_szInputBuf;
|
|
const char* replace = "";
|
|
if (strstr(m_szInputBuf, "`")) { strcpy_s(s, sizeof(replace), replace); }
|
|
Strtrim(s);
|
|
if (s[0]) { ProcessCommand(s); }
|
|
strcpy_s(s, sizeof(replace), replace);
|
|
reclaim_focus = true;
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Submit"))
|
|
{
|
|
char* s = m_szInputBuf;
|
|
const char* replace = "";
|
|
if (s[0]) { ProcessCommand(s); }
|
|
strcpy_s(s, sizeof(replace), replace);
|
|
reclaim_focus = true;
|
|
}
|
|
|
|
// Auto-focus on window apparition.
|
|
ImGui::SetItemDefaultFocus();
|
|
|
|
// Auto focus previous widget.
|
|
if (reclaim_focus)
|
|
{
|
|
ImGui::SetKeyboardFocusHere();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: executes submitted commands in a separate thread
|
|
//-----------------------------------------------------------------------------
|
|
void CConsole::ProcessCommand(const char* command_line)
|
|
{
|
|
AddLog("# %s\n", command_line);
|
|
|
|
std::thread t(IVEngineClient_CommandExecute, this, command_line);
|
|
t.detach(); // Detach from render thread.
|
|
|
|
// This is to avoid a race condition.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
m_nHistoryPos = -1;
|
|
for (int i = m_ivHistory.Size - 1; i >= 0; i--)
|
|
{
|
|
if (Stricmp(m_ivHistory[i], command_line) == 0)
|
|
{
|
|
delete m_ivHistory[i];
|
|
m_ivHistory.erase(m_ivHistory.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_ivHistory.push_back(Strdup(command_line));
|
|
if (Stricmp(command_line, "CLEAR") == 0)
|
|
{
|
|
ClearLog();
|
|
}
|
|
else if (Stricmp(command_line, "HELP") == 0)
|
|
{
|
|
AddLog("Frontend commands:");
|
|
for (int i = 0; i < m_ivCommands.Size; i++)
|
|
{
|
|
AddLog("- %s", m_ivCommands[i]);
|
|
}
|
|
|
|
AddLog("Log types:");
|
|
AddLog("Script(S): = Server (Script VM)");
|
|
AddLog("Script(C): = Client (Script VM)");
|
|
AddLog("Script(U): = UI (Script VM)");
|
|
|
|
AddLog("Native(S): = Server dll (Code)");
|
|
AddLog("Native(C): = Client dll (code)");
|
|
AddLog("Native(U): = UI dll (code)");
|
|
|
|
AddLog("Native(E): = Engine dll (code)");
|
|
AddLog("Native(F): = FileSystem dll (code)");
|
|
AddLog("Native(R): = RTech dll (code)");
|
|
AddLog("Native(M): = MaterialSystem dll (code)");
|
|
}
|
|
else if (Stricmp(command_line, "HISTORY") == 0)
|
|
{
|
|
int first = m_ivHistory.Size - 10;
|
|
for (int i = first > 0 ? first : 0; i < m_ivHistory.Size; i++)
|
|
{
|
|
AddLog("%3d: %s\n", i, m_ivHistory[i]);
|
|
}
|
|
}
|
|
|
|
m_bScrollToBottom = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: text edit callback
|
|
//-----------------------------------------------------------------------------
|
|
int CConsole::TextEditCallback(ImGuiInputTextCallbackData* data)
|
|
{
|
|
switch (data->EventFlag)
|
|
{
|
|
case ImGuiInputTextFlags_CallbackCompletion:
|
|
{
|
|
// Locate beginning of current word.
|
|
const char* word_end = data->Buf + data->CursorPos;
|
|
const char* word_start = word_end;
|
|
while (word_start > data->Buf)
|
|
{
|
|
const char c = word_start[-1];
|
|
if (c == ' ' || c == '\t' || c == ',' || c == ';')
|
|
{
|
|
break;
|
|
}
|
|
word_start--;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiInputTextFlags_CallbackHistory:
|
|
{
|
|
const int prev_history_pos = m_nHistoryPos;
|
|
if (data->EventKey == ImGuiKey_UpArrow)
|
|
{
|
|
if (m_nHistoryPos == -1) { m_nHistoryPos = m_ivHistory.Size - 1; }
|
|
else if (m_nHistoryPos > 0) { m_nHistoryPos--; }
|
|
}
|
|
else if (data->EventKey == ImGuiKey_DownArrow)
|
|
{
|
|
if (m_nHistoryPos != -1)
|
|
{
|
|
if (++m_nHistoryPos >= m_ivHistory.Size)
|
|
{
|
|
m_nHistoryPos = -1;
|
|
}
|
|
}
|
|
}
|
|
if (prev_history_pos != m_nHistoryPos)
|
|
{
|
|
const char* history_str = (m_nHistoryPos >= 0) ? m_ivHistory[m_nHistoryPos] : "";
|
|
data->DeleteChars(0, data->BufTextLen);
|
|
data->InsertChars(0, history_str);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//#############################################################################
|
|
// ENTRYPOINT
|
|
//#############################################################################
|
|
|
|
CConsole* g_GameConsole = nullptr;
|
|
void DrawConsole(bool* bDraw)
|
|
{
|
|
static CConsole console;
|
|
static bool AssignPtr = []()
|
|
{
|
|
g_GameConsole = &console;
|
|
return true;
|
|
} ();
|
|
console.Draw("Console", bDraw);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|