r5sdk/r5dev/squirrel/sqvm.cpp
Kawe Mazidjatari a618990937 Detour code refactor
This change was planned for a long time. This moves all REGISTER calls to a single translation unit, this is required as we currently added a very dirty workaround for not registering duplicates by checking if VFTable pointer was already present in the vector... Registering from single translation unit prevents duplicate instances that gets created if header is included by more cpp files.
Reworking this reduced 100kb+ of compiled code. This commit also reworked the way functions/variables/constant gets logged with their addresses; the new code formats them on the fly, and allows for resize at any time. Formatting is no longer required by programmer.

TODO: currently there are some compile errors for dedicated and client dll's. These will be resolved very soon as they need to be properly worked out still (server & client only stuff needs to be properly split). Use the 'main' (stable) branch for the time being if you need to compile these dll's.
2023-01-25 02:26:52 +01:00

400 lines
11 KiB
C++

//=============================================================================//
//
// Purpose: Squirrel VM
//
//=============================================================================//
#include "core/stdafx.h"
#include "core/logdef.h"
#include "tier0/platform_internal.h"
#include "tier0/commandline.h"
#include "tier1/cvar.h"
#include "tier1/IConVar.h"
#ifdef DEDICATED
#include "engine/server/sv_rcon.h"
#else // DEDICATED
#include "client/cdll_engine_int.h"
#include "vgui/vgui_debugpanel.h"
#include "gameui/IConsole.h"
#endif
#include "squirrel/sqtype.h"
#include "squirrel/sqvm.h"
#include "squirrel/sqinit.h"
#include "squirrel/sqstdaux.h"
#include "squirrel/sqstate.h"
//---------------------------------------------------------------------------------
// Purpose: prints the output of each VM to the console
// Input : *sqvm -
// *fmt -
// ... -
//---------------------------------------------------------------------------------
SQRESULT SQVM_PrintFunc(HSQUIRRELVM v, SQChar* fmt, ...)
{
SQCONTEXT context;
int nResponseId;
// We use the sqvm pointer as index for SDK usage as the function prototype has to match assembly.
switch (static_cast<SQCONTEXT>(reinterpret_cast<int>(v)))
{
case SQCONTEXT::SERVER:
context = SQCONTEXT::SERVER;
nResponseId = -3;
break;
case SQCONTEXT::CLIENT:
context = SQCONTEXT::CLIENT;
nResponseId = -2;
break;
case SQCONTEXT::UI:
context = SQCONTEXT::UI;
nResponseId = -1;
break;
case SQCONTEXT::NONE:
context = SQCONTEXT::NONE;
nResponseId = -4;
break;
default:
context = v->GetContext();
switch (context)
{
case SQCONTEXT::SERVER:
nResponseId = -3;
break;
case SQCONTEXT::CLIENT:
nResponseId = -2;
break;
case SQCONTEXT::UI:
nResponseId = -1;
break;
case SQCONTEXT::NONE:
nResponseId = -4;
break;
default:
nResponseId = -4;
break;
}
break;
}
static SQChar buf[4096] = {};
static std::string vmStr;
static std::regex rxAnsiExp("\\\033\\[.*?m");
static std::shared_ptr<spdlog::logger> iconsole = spdlog::get("game_console");
static std::shared_ptr<spdlog::logger> wconsole = spdlog::get("win_console");
static std::shared_ptr<spdlog::logger> sqlogger = spdlog::get("sqvm_info");
g_LogMutex.lock();
const char* pszUpTime = Plat_GetProcessUpTime();
{/////////////////////////////
va_list args{};
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
buf[sizeof(buf) - 1] = '\0';
va_end(args);
}/////////////////////////////
vmStr = pszUpTime;
vmStr.append(SQVM_LOG_T[static_cast<SQInteger>(context)]);
vmStr.append(buf);
if (sq_showvmoutput->GetInt() > 0) {
sqlogger->debug(vmStr);
}
// Always show script errors.
bool bLogLevelOverride = (g_bSQAuxError || g_bSQAuxBadLogic && v == g_pErrorVM);
if (sq_showvmoutput->GetInt() > 1 || bLogLevelOverride)
{
bool bColorOverride = false;
bool bError = false;
if (g_bSQAuxError)
{
bColorOverride = true;
if (strstr(buf, "SCRIPT ERROR:") || strstr(buf, " -> ")) {
bError = true;
}
}
if (g_bSQAuxBadLogic)
{
if (strstr(buf, "There was a problem processing game logic."))
{
bColorOverride = true;
bError = true;
g_bSQAuxBadLogic = false;
}
}
if (!g_bSpdLog_UseAnsiClr)
{
wconsole->debug(vmStr);
#ifdef DEDICATED
RCONServer()->Send(vmStr, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG, nResponseId);
#endif // DEDICATED
}
else // Use ANSI escape codes for the external console.
{
static std::string vmStrAnsi;
vmStrAnsi = pszUpTime;
if (bColorOverride)
{
if (bError) {
vmStrAnsi.append(SQVM_ERROR_ANSI_LOG_T[static_cast<SQInteger>(context)]);
}
else {
vmStrAnsi.append(SQVM_WARNING_ANSI_LOG_T[static_cast<SQInteger>(context)]);
}
}
else {
vmStrAnsi.append(SQVM_ANSI_LOG_T[static_cast<SQInteger>(context)]);
}
vmStrAnsi.append(buf);
wconsole->debug(vmStrAnsi);
#ifdef DEDICATED
RCONServer()->Send(vmStrAnsi, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG, nResponseId);
#endif // DEDICATED
}
#ifndef DEDICATED
vmStr = std::regex_replace(vmStr, rxAnsiExp, "");
iconsole->debug(vmStr);
if (sq_showvmoutput->GetInt() > 2 || bLogLevelOverride)
{
ImVec4 color;
if (bColorOverride)
{
if (bError) {
color = ImVec4(1.00f, 0.00f, 0.00f, 0.80f);
}
else {
color = ImVec4(1.00f, 1.00f, 0.00f, 0.80f);
}
}
else
{
switch (context)
{
case SQCONTEXT::SERVER:
color = ImVec4(0.59f, 0.58f, 0.73f, 1.00f);
break;
case SQCONTEXT::CLIENT:
color = ImVec4(0.59f, 0.58f, 0.63f, 1.00f);
break;
case SQCONTEXT::UI:
color = ImVec4(0.59f, 0.48f, 0.53f, 1.00f);
break;
default:
color = ImVec4(0.59f, 0.58f, 0.63f, 1.00f);
break;
}
}
g_pConsole->AddLog(ConLog_t(g_spd_sys_w_oss.str(), color));
g_pOverlay->AddLog(static_cast<EGlobalContext_t>(nResponseId), g_spd_sys_w_oss.str());
}
#endif // !DEDICATED
}
g_spd_sys_w_oss.str("");
g_spd_sys_w_oss.clear();
g_LogMutex.unlock();
return SQ_OK;
}
//---------------------------------------------------------------------------------
// Purpose: prints the warning output of each VM to the console
// Input : *sqvm -
// a2 -
// a3 -
// *nStringSize -
// **ppString -
//---------------------------------------------------------------------------------
SQRESULT SQVM_WarningFunc(HSQUIRRELVM v, SQInteger a2, SQInteger a3, SQInteger* nStringSize, SQChar** ppString)
{
static void* retaddr = reinterpret_cast<void*>(p_SQVM_WarningCmd.Offset(0x10).FindPatternSelf("85 ?? ?? 99", CMemory::Direction::DOWN).GetPtr());
int nResponseId;
SQCONTEXT context;
SQRESULT result = v_SQVM_WarningFunc(v, a2, a3, nStringSize, ppString);
if (retaddr != _ReturnAddress() || !sq_showvmwarning->GetBool()) // Check if its SQVM_Warning calling.
{
return result;
}
g_LogMutex.lock();
const char* pszUpTime = Plat_GetProcessUpTime();
#ifdef GAMEDLL_S3
context = v->GetContext();
#else // Nothing equal to 'rdx + 18h' exist in the vm structs for anything below S3.
context = SQVM_GetContextIndex(v);
#endif
switch (context)
{
case SQCONTEXT::SERVER:
nResponseId = -3;
break;
case SQCONTEXT::CLIENT:
nResponseId = -2;
break;
case SQCONTEXT::UI:
nResponseId = -1;
break;
case SQCONTEXT::NONE:
nResponseId = -4;
break;
default:
nResponseId = -4;
break;
}
static std::shared_ptr<spdlog::logger> iconsole = spdlog::get("game_console");
static std::shared_ptr<spdlog::logger> wconsole = spdlog::get("win_console");
static std::shared_ptr<spdlog::logger> sqlogger = spdlog::get("sqvm_warn");
std::string vmStr = pszUpTime;
vmStr.append(SQVM_LOG_T[static_cast<int>(context)]);
std::string svConstructor(*ppString, *nStringSize); // Get string from memory via std::string constructor.
vmStr.append(svConstructor);
sqlogger->debug(vmStr); // Emit to file.
if (sq_showvmwarning->GetInt() > 1)
{
if (!g_bSpdLog_UseAnsiClr)
{
wconsole->debug(vmStr);
#ifdef DEDICATED
RCONServer()->Send(vmStr, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG, nResponseId);
#endif // DEDICATED
}
else
{
std::string vmStrAnsi = pszUpTime;
vmStrAnsi.append(SQVM_WARNING_ANSI_LOG_T[static_cast<int>(context)]);
vmStrAnsi.append(svConstructor);
wconsole->debug(vmStrAnsi);
#ifdef DEDICATED
RCONServer()->Send(vmStrAnsi, "", sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG, nResponseId);
#endif // DEDICATED
}
#ifndef DEDICATED
iconsole->debug(vmStr); // Emit to in-game console.
g_pConsole->AddLog(ConLog_t(g_spd_sys_w_oss.str(), ImVec4(1.00f, 1.00f, 0.00f, 0.80f)));
g_pOverlay->AddLog(EGlobalContext_t::WARNING_C, g_spd_sys_w_oss.str());
#endif // !DEDICATED
}
g_spd_sys_w_oss.str("");
g_spd_sys_w_oss.clear();
g_LogMutex.unlock();
return result;
}
//---------------------------------------------------------------------------------
// Purpose: prints the compile error and context to the console
// Input : *sqvm -
// *pszError -
// *pszFile -
// nLine -
// nColumn -
//---------------------------------------------------------------------------------
void SQVM_CompileError(HSQUIRRELVM v, const SQChar* pszError, const SQChar* pszFile, SQUnsignedInteger nLine, SQInteger nColumn)
{
static SQCONTEXT context{};
static char szContextBuf[256]{};
#if !defined (GAMEDLL_S0) && !defined (GAMEDLL_S1) && !defined (GAMEDLL_S2)
context = v->GetContext();
#else // Nothing equal to 'rdx + 18h' exist in the vm structs for anything below S3.
context = SQVM_GetContextIndex(v);
#endif
v_SQVM_GetErrorLine(pszFile, nLine, szContextBuf, sizeof(szContextBuf) - 1);
Error(static_cast<eDLL_T>(context), NO_ERROR, "%s SCRIPT COMPILE ERROR: %s\n", SQVM_GetContextName(context), pszError);
Error(static_cast<eDLL_T>(context), NO_ERROR, " -> %s\n\n", szContextBuf);
Error(static_cast<eDLL_T>(context), NO_ERROR, "%s line [%d] column [%d]\n", pszFile, nLine, nColumn);
}
//---------------------------------------------------------------------------------
// Purpose: prints the logic error and context to the console
// Input : bPrompt -
//---------------------------------------------------------------------------------
void SQVM_LogicError(SQBool bPrompt)
{
if ((*g_flErrorTimeStamp) > 0.0 && (bPrompt || Plat_FloatTime() > (*g_flErrorTimeStamp) + 0.0))
{
g_bSQAuxBadLogic = true;
}
else
{
g_bSQAuxBadLogic = false;
g_pErrorVM = nullptr;
}
v_SQVM_LogicError(bPrompt);
}
//---------------------------------------------------------------------------------
// Purpose: Returns the VM name by context
// Input : context -
// Output : const SQChar*
//---------------------------------------------------------------------------------
const SQChar* SQVM_GetContextName(SQCONTEXT context)
{
switch (context)
{
case SQCONTEXT::SERVER:
return "SERVER";
case SQCONTEXT::CLIENT:
return "CLIENT";
case SQCONTEXT::UI:
return "UI";
default:
return nullptr;
}
}
//---------------------------------------------------------------------------------
// Purpose: Returns the VM context by name
// Input : *sqvm -
// Output : const SQCONTEXT*
//---------------------------------------------------------------------------------
const SQCONTEXT SQVM_GetContextIndex(HSQUIRRELVM v)
{
if (strcmp(v->_sharedstate->_contextname, "SERVER") == 0)
return SQCONTEXT::SERVER;
if (strcmp(v->_sharedstate->_contextname, "CLIENT") == 0)
return SQCONTEXT::CLIENT;
if (strcmp(v->_sharedstate->_contextname, "UI") == 0)
return SQCONTEXT::UI;
return SQCONTEXT::NONE;
}
//---------------------------------------------------------------------------------
void HSQVM::Attach() const
{
DetourAttach((LPVOID*)&v_SQVM_PrintFunc, &SQVM_PrintFunc);
DetourAttach((LPVOID*)&v_SQVM_WarningFunc, &SQVM_WarningFunc);
DetourAttach((LPVOID*)&v_SQVM_CompileError, &SQVM_CompileError);
DetourAttach((LPVOID*)&v_SQVM_LogicError, &SQVM_LogicError);
}
//---------------------------------------------------------------------------------
void HSQVM::Detach() const
{
DetourDetach((LPVOID*)&v_SQVM_PrintFunc, &SQVM_PrintFunc);
DetourDetach((LPVOID*)&v_SQVM_WarningFunc, &SQVM_WarningFunc);
DetourDetach((LPVOID*)&v_SQVM_CompileError, &SQVM_CompileError);
DetourDetach((LPVOID*)&v_SQVM_LogicError, &SQVM_LogicError);
}