mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Check for cvar or state first before checking the current thread. The cvar/state check is slightly cheaper.
600 lines
16 KiB
C++
600 lines
16 KiB
C++
//=============================================================================//
|
|
//
|
|
// Purpose: Callback functions for ConVar's.
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "core/stdafx.h"
|
|
#include "core/init.h"
|
|
#include "windows/id3dx.h"
|
|
#include "tier0/fasttimer.h"
|
|
#include "tier1/cvar.h"
|
|
#include "tier1/fmtstr.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "engine/server/sv_rcon.h"
|
|
#endif // !CLIENT_DLL
|
|
#ifndef DEDICATED
|
|
#include "engine/client/cl_rcon.h"
|
|
#include "engine/client/cdll_engine_int.h"
|
|
#include "engine/client/clientstate.h"
|
|
#endif // !DEDICATED
|
|
#include "engine/client/client.h"
|
|
#include "engine/net.h"
|
|
#include "engine/host_cmd.h"
|
|
#include "engine/host_state.h"
|
|
#include "engine/enginetrace.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "engine/server/server.h"
|
|
#endif // !CLIENT_DLL
|
|
|
|
#include "rtech/pak/pakencode.h"
|
|
#include "rtech/pak/pakdecode.h"
|
|
#include "rtech/pak/pakparse.h"
|
|
#include "rtech/pak/pakstate.h"
|
|
#include "rtech/pak/paktools.h"
|
|
|
|
#include "rtech/playlists/playlists.h"
|
|
|
|
#include "filesystem/basefilesystem.h"
|
|
#include "filesystem/filesystem.h"
|
|
#include "vpklib/packedstore.h"
|
|
#include "vscript/vscript.h"
|
|
#include "localize/localize.h"
|
|
#include "ebisusdk/EbisuSDK.h"
|
|
#ifndef DEDICATED
|
|
#include "geforce/reflex.h"
|
|
#include "gameui/IBrowser.h"
|
|
#include "gameui/IConsole.h"
|
|
#endif // !DEDICATED
|
|
#ifndef CLIENT_DLL
|
|
#include "networksystem/bansystem.h"
|
|
#endif // !CLIENT_DLL
|
|
#include "public/edict.h"
|
|
#include "public/worldsize.h"
|
|
#include "mathlib/crc32.h"
|
|
#include "mathlib/mathlib.h"
|
|
#include "common/completion.h"
|
|
#include "common/callback.h"
|
|
#ifndef DEDICATED
|
|
#include "materialsystem/cmaterialglue.h"
|
|
#endif // !DEDICATED
|
|
#include "public/bspflags.h"
|
|
#include "public/cmodel.h"
|
|
#include "public/idebugoverlay.h"
|
|
#include "public/localize/ilocalize.h"
|
|
#ifndef CLIENT_DLL
|
|
#include "game/server/detour_impl.h"
|
|
#include "game/server/gameinterface.h"
|
|
#endif // !CLIENT_DLL
|
|
#ifndef DEDICATED
|
|
#include "game/client/cliententitylist.h"
|
|
#include "game/client/viewrender.h"
|
|
#endif // !DEDICATED
|
|
|
|
|
|
/*
|
|
=====================
|
|
MP_GameMode_Changed_f
|
|
=====================
|
|
*/
|
|
void MP_GameMode_Changed_f(IConVar* pConVar, const char* pOldString)
|
|
{
|
|
v_SetupGamemode(mp_gamemode->GetString());
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
/*
|
|
=====================
|
|
Host_Changelevel_f
|
|
|
|
Goes to a new map,
|
|
taking all clients along
|
|
=====================
|
|
*/
|
|
void Host_Changelevel_f(const CCommand& args)
|
|
{
|
|
const int argCount = args.ArgC();
|
|
|
|
if (argCount >= 2
|
|
&& IsOriginInitialized()
|
|
&& g_pServer->IsActive())
|
|
{
|
|
const char* levelName = args[1];
|
|
const char* landMarkName = argCount > 2 ? args[2] : "";
|
|
|
|
v_SetLaunchOptions(args);
|
|
v_HostState_ChangeLevelMP(levelName, landMarkName);
|
|
}
|
|
}
|
|
#endif // !CLIENT_DLL
|
|
|
|
// TODO: move this to 'packedstore.cpp' and move everything in that file to 'packetstorebuilder.cpp'
|
|
static ConVar fs_packedstore_workspace("fs_packedstore_workspace", "ship", FCVAR_DEVELOPMENTONLY, "Determines the current VPK workspace.");
|
|
static ConVar fs_packedstore_compression_level("fs_packedstore_compression_level", "default", FCVAR_DEVELOPMENTONLY, "Determines the VPK compression level.", "fastest faster default better uber");
|
|
static ConVar fs_packedstore_max_helper_threads("fs_packedstore_max_helper_threads", "-1", FCVAR_DEVELOPMENTONLY, "Max # of additional \"helper\" threads to create during compression.", true, -1, true, LZHAM_MAX_HELPER_THREADS, "Must range between [-1,LZHAM_MAX_HELPER_THREADS], where -1=max practical");
|
|
|
|
/*
|
|
=====================
|
|
VPK_Pack_f
|
|
|
|
Packs VPK files into
|
|
'PLATFORM' VPK directory.
|
|
=====================
|
|
*/
|
|
void VPK_Pack_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() < 4)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const char* workspacePath = fs_packedstore_workspace.GetString();
|
|
|
|
if (!FileSystem()->IsDirectory(workspacePath, "PLATFORM"))
|
|
{
|
|
Error(eDLL_T::FS, NO_ERROR, "Workspace path \"%s\" doesn't exist!\n", workspacePath);
|
|
return;
|
|
}
|
|
|
|
VPKPair_t pair(args.Arg(1), args.Arg(2), args.Arg(3), NULL);
|
|
Msg(eDLL_T::FS, "*** Starting VPK build command for: '%s'\n", pair.m_DirName.String());
|
|
|
|
CFastTimer timer;
|
|
timer.Start();
|
|
|
|
CPackedStoreBuilder builder;
|
|
|
|
builder.InitLzEncoder(fs_packedstore_max_helper_threads.GetInt(), fs_packedstore_compression_level.GetString());
|
|
builder.PackStore(pair, workspacePath, "vpk/");
|
|
|
|
timer.End();
|
|
Msg(eDLL_T::FS, "*** Time elapsed: '%lf' seconds\n", timer.GetDuration().GetSeconds());
|
|
Msg(eDLL_T::FS, "\n");
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
VPK_Unpack_f
|
|
|
|
Unpacks VPK files into
|
|
workspace directory.
|
|
=====================
|
|
*/
|
|
void VPK_Unpack_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CUtlString fileName = args.Arg(1);
|
|
VPKDir_t vpk(fileName, (args.ArgC() > 2));
|
|
|
|
if (vpk.Failed())
|
|
{
|
|
Error(eDLL_T::FS, NO_ERROR, "Failed to parse directory tree file \"%s\"!\n", fileName.String());
|
|
return;
|
|
}
|
|
|
|
Msg(eDLL_T::FS, "*** Starting VPK extraction command for: '%s'\n", fileName.String());
|
|
|
|
CFastTimer timer;
|
|
timer.Start();
|
|
|
|
CPackedStoreBuilder builder;
|
|
|
|
builder.InitLzDecoder();
|
|
builder.UnpackStore(vpk, fs_packedstore_workspace.GetString());
|
|
|
|
timer.End();
|
|
Msg(eDLL_T::FS, "*** Time elapsed: '%lf' seconds\n", timer.GetDuration().GetSeconds());
|
|
Msg(eDLL_T::FS, "\n");
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
VPK_Mount_f
|
|
|
|
Mounts input VPK file for
|
|
internal FileSystem usage
|
|
=====================
|
|
*/
|
|
void VPK_Mount_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FileSystem()->MountVPKFile(args.Arg(1));
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
VPK_Unmount_f
|
|
|
|
Unmounts input VPK file
|
|
and clears its cache
|
|
=====================
|
|
*/
|
|
void VPK_Unmount_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FileSystem()->UnmountVPKFile(args.Arg(1));
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
NET_UseSocketsForLoopbackChanged_f
|
|
|
|
Use random AES encryption
|
|
key for game packets
|
|
=====================
|
|
*/
|
|
void NET_UseSocketsForLoopbackChanged_f(IConVar* pConVar, const char* pOldString)
|
|
{
|
|
if (ConVar* pConVarRef = g_pCVar->FindVar(pConVar->GetName()))
|
|
{
|
|
if (strcmp(pOldString, pConVarRef->GetString()) == NULL)
|
|
return; // Same value.
|
|
|
|
#ifndef CLIENT_DLL
|
|
// Reboot the RCON server to switch address type.
|
|
if (RCONServer()->IsInitialized())
|
|
{
|
|
Msg(eDLL_T::SERVER, "Rebooting RCON server...\n");
|
|
RCONServer()->Shutdown();
|
|
RCONServer()->Init();
|
|
}
|
|
#endif // !CLIENT_DLL
|
|
}
|
|
}
|
|
|
|
void LanguageChanged_f(IConVar* pConVar, const char* pOldString)
|
|
{
|
|
if (ConVar* pConVarRef = g_pCVar->FindVar(pConVar->GetName()))
|
|
{
|
|
const char* pNewString = pConVarRef->GetString();
|
|
|
|
if (strcmp(pOldString, pConVarRef->GetString()) == NULL)
|
|
return; // Same language.
|
|
|
|
if (!Localize_IsLanguageSupported(pNewString))
|
|
{
|
|
// if new text isn't valid but the old value is, reset the value
|
|
if (Localize_IsLanguageSupported(pOldString))
|
|
pNewString = pOldString;
|
|
else
|
|
{
|
|
// this shouldn't really happen, but if neither the old nor new values are valid, set to english
|
|
Assert(0);
|
|
pNewString = g_LanguageNames[0];
|
|
}
|
|
}
|
|
|
|
pConVarRef->SetValue(pNewString);
|
|
g_MasterServer.SetLanguage(pNewString);
|
|
}
|
|
}
|
|
|
|
#ifndef DEDICATED
|
|
/*
|
|
=====================
|
|
Mat_CrossHair_f
|
|
|
|
Print the material under the crosshair.
|
|
=====================
|
|
*/
|
|
void Mat_CrossHair_f(const CCommand& args)
|
|
{
|
|
CMaterialGlue* material = v_GetMaterialAtCrossHair();
|
|
if (material)
|
|
{
|
|
Msg(eDLL_T::MS, "______________________________________________________________\n");
|
|
Msg(eDLL_T::MS, "-+ Material --------------------------------------------------\n");
|
|
Msg(eDLL_T::MS, " |-- ADDR: '%llX'\n", material);
|
|
Msg(eDLL_T::MS, " |-- GUID: '%llX'\n", material->assetGuid);
|
|
Msg(eDLL_T::MS, " |-- Num Streaming Textures: '%d'\n", material->numStreamingTextureHandles);
|
|
Msg(eDLL_T::MS, " |-- Material width: '%d'\n", material->width);
|
|
Msg(eDLL_T::MS, " |-- Material height: '%d'\n", material->height);
|
|
Msg(eDLL_T::MS, " |-- Samplers: '%08X'\n", material->samplers);
|
|
|
|
std::function<void(CMaterialGlue*, const char*)> fnPrintChild = [](CMaterialGlue* material, const char* print)
|
|
{
|
|
Msg(eDLL_T::MS, " |-+\n");
|
|
Msg(eDLL_T::MS, " | |-+ Child material ----------------------------------------\n");
|
|
Msg(eDLL_T::MS, print, material);
|
|
Msg(eDLL_T::MS, " | |-- GUID: '%llX'\n", material->assetGuid);
|
|
Msg(eDLL_T::MS, " | |-- Material name: '%s'\n", material->name);
|
|
};
|
|
|
|
Msg(eDLL_T::MS, " |-- Material name: '%s'\n", material->name);
|
|
Msg(eDLL_T::MS, " |-- Material surface name 1: '%s'\n", material->surfaceProp);
|
|
Msg(eDLL_T::MS, " |-- Material surface name 2: '%s'\n", material->surfaceProp2);
|
|
Msg(eDLL_T::MS, " |-- DX buffer: '%llX'\n", material->dxBuffer);
|
|
Msg(eDLL_T::MS, " |-- DX buffer VFTable: '%llX'\n", material->unkD3DPointer);
|
|
|
|
material->depthShadowMaterial
|
|
? fnPrintChild(material->depthShadowMaterial, " | |-+ DepthShadow: '%llX'\n")
|
|
: Msg(eDLL_T::MS, " | |-+ DepthShadow: 'NULL'\n");
|
|
material->depthPrepassMaterial
|
|
? fnPrintChild(material->depthPrepassMaterial, " | |-+ DepthPrepass: '%llX'\n")
|
|
: Msg(eDLL_T::MS, " | |-+ DepthPrepass: 'NULL'\n");
|
|
material->depthVSMMaterial
|
|
? fnPrintChild(material->depthVSMMaterial, " | |-+ DepthVSM: '%llX'\n")
|
|
: Msg(eDLL_T::MS, " | |-+ DepthVSM: 'NULL'\n");
|
|
material->depthShadowTightMaterial
|
|
? fnPrintChild(material->depthShadowTightMaterial, " | |-+ DepthShadowTight: '%llX'\n")
|
|
: Msg(eDLL_T::MS, " | |-+ DepthShadowTight: 'NULL'\n");
|
|
material->colpassMaterial
|
|
? fnPrintChild(material->colpassMaterial, " | |-+ ColPass: '%llX'\n")
|
|
: Msg(eDLL_T::MS, " | |-+ ColPass: 'NULL'\n");
|
|
|
|
Msg(eDLL_T::MS, "-+ Texture GUID map ------------------------------------------\n");
|
|
Msg(eDLL_T::MS, " |-- Texture handles: '%llX'\n", material->textureHandles);
|
|
Msg(eDLL_T::MS, " |-- Streaming texture handles: '%llX'\n", material->streamingTextureHandles);
|
|
|
|
Msg(eDLL_T::MS, "--------------------------------------------------------------\n");
|
|
}
|
|
else
|
|
{
|
|
Msg(eDLL_T::MS, "%s: No material found >:(\n", __FUNCTION__);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
Line_f
|
|
|
|
Draws a line at
|
|
start<x1 y1 z1> end<x2 y2 z2>.
|
|
=====================
|
|
*/
|
|
void Line_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() != 7)
|
|
{
|
|
Msg(eDLL_T::CLIENT, "Usage 'line': start(vector) end(vector)\n");
|
|
return;
|
|
}
|
|
|
|
Vector3D start, end;
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
start[i] = float(atof(args[i + 1]));
|
|
end[i] = float(atof(args[i + 4]));
|
|
}
|
|
|
|
g_pDebugOverlay->AddLineOverlay(start, end, 255, 255, 0, !r_debug_draw_depth_test.GetBool(), 100);
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
Sphere_f
|
|
|
|
Draws a sphere at origin(x1 y1 z1)
|
|
radius(float) theta(int) phi(int).
|
|
=====================
|
|
*/
|
|
void Sphere_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() != 7)
|
|
{
|
|
Msg(eDLL_T::CLIENT, "Usage 'sphere': origin(vector) radius(float) theta(int) phi(int)\n");
|
|
return;
|
|
}
|
|
|
|
Vector3D start;
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
start[i] = float(atof(args[i + 1]));
|
|
}
|
|
|
|
float radius = float(atof(args[4]));
|
|
int theta = atoi(args[5]);
|
|
int phi = atoi(args[6]);
|
|
|
|
g_pDebugOverlay->AddSphereOverlay(start, radius, theta, phi, 20, 210, 255, 0, 100);
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
Capsule_f
|
|
|
|
Draws a capsule at start<x1 y1 z1>
|
|
end<x2 y2 z2> radius <x3 y3 z3>.
|
|
=====================
|
|
*/
|
|
void Capsule_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() != 10)
|
|
{
|
|
Msg(eDLL_T::CLIENT, "Usage 'capsule': start(vector) end(vector) radius(vector)\n");
|
|
return;
|
|
}
|
|
|
|
Vector3D start, end, radius;
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
start[i] = float(atof(args[i + 1]));
|
|
end[i] = float(atof(args[i + 4]));
|
|
radius[i] = float(atof(args[i + 7]));
|
|
}
|
|
g_pDebugOverlay->AddCapsuleOverlay(start, end, radius, { 0,0,0 }, { 0,0,0 }, 141, 233, 135, 0, 100);
|
|
}
|
|
#endif // !DEDICATED
|
|
|
|
// TODO: move to other file?
|
|
static ConVar bhit_depth_test("bhit_depth_test", "0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Use depth test for bullet ray trace overlay");
|
|
static ConVar bhit_abs_origin("bhit_abs_origin", "1", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Draw entity's predicted abs origin upon bullet impact for trajectory debugging (requires 'r_visualizetraces' to be set!)");
|
|
/*
|
|
=====================
|
|
BHit_f
|
|
|
|
Bullet trajectory tracing
|
|
from shooter to target entity.
|
|
=====================
|
|
*/
|
|
void BHit_f(const CCommand& args)
|
|
{
|
|
#ifndef CLIENT_DLL // Stubbed to suppress server warnings as this is a GAMEDLL command!
|
|
if (args.ArgC() != 9)
|
|
return;
|
|
|
|
if (!bhit_enable->GetBool())
|
|
return;
|
|
|
|
if (sv_visualizetraces->GetBool())
|
|
{
|
|
Vector3D vecAbsStart;
|
|
Vector3D vecAbsEnd;
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
vecAbsStart[i] = float(atof(args[i + 4]));
|
|
|
|
QAngle vecBulletAngles;
|
|
for (int i = 0; i < 2; ++i)
|
|
vecBulletAngles[i] = float(atof(args[i + 7]));
|
|
|
|
vecBulletAngles.z = 180.f; // Flipped axis.
|
|
AngleVectors(vecBulletAngles, &vecAbsEnd);
|
|
|
|
vecAbsEnd.MulAdd(vecAbsStart, vecAbsEnd, MAX_COORD_RANGE);
|
|
|
|
Ray_t ray(vecAbsStart, vecAbsEnd);
|
|
trace_t trace;
|
|
|
|
g_pEngineTraceServer->TraceRay(ray, TRACE_MASK_NPCWORLDSTATIC, &trace);
|
|
|
|
g_pDebugOverlay->AddLineOverlay(trace.startpos, trace.endpos, 0, 255, 0, !bhit_depth_test.GetBool(), sv_visualizetraces_duration->GetFloat());
|
|
g_pDebugOverlay->AddLineOverlay(trace.endpos, vecAbsEnd, 255, 0, 0, !bhit_depth_test.GetBool(), sv_visualizetraces_duration->GetFloat());
|
|
}
|
|
#endif // !CLIENT_DLL
|
|
|
|
#ifndef DEDICATED
|
|
if (bhit_abs_origin.GetBool() && r_visualizetraces->GetBool())
|
|
{
|
|
const int iEnt = atoi(args[2]);
|
|
if (const IClientEntity* pEntity = g_pClientEntityList->GetClientEntity(iEnt))
|
|
{
|
|
g_pDebugOverlay->AddSphereOverlay( // Render a debug sphere at the client's predicted entity origin.
|
|
pEntity->GetAbsOrigin(), 10.f, 8, 6, 20, 60, 255, 0, r_visualizetraces_duration->GetFloat());
|
|
}
|
|
}
|
|
#endif // !DEDICATED
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CVHelp_f
|
|
|
|
Show help text for a
|
|
particular convar/concommand
|
|
=====================
|
|
*/
|
|
void CVHelp_f(const CCommand& args)
|
|
{
|
|
cv->CvarHelp(args);
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CVList_f
|
|
|
|
List all ConCommandBases
|
|
=====================
|
|
*/
|
|
void CVList_f(const CCommand& args)
|
|
{
|
|
cv->CvarList(args);
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CVDiff_f
|
|
|
|
List all ConVar's
|
|
who's values deviate
|
|
from default value
|
|
=====================
|
|
*/
|
|
void CVDiff_f(const CCommand& args)
|
|
{
|
|
cv->CvarDifferences(args);
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CVFlag_f
|
|
|
|
List all ConVar's
|
|
with specified flags
|
|
=====================
|
|
*/
|
|
void CVFlag_f(const CCommand& args)
|
|
{
|
|
cv->CvarFindFlags_f(args);
|
|
}
|
|
|
|
#ifndef DEDICATED
|
|
static double s_flScriptExecTimeBase = 0.0f;
|
|
static int s_nScriptExecCount = 0;
|
|
#endif // !DEDICATED
|
|
/*
|
|
=====================
|
|
Cmd_Exec_f
|
|
|
|
executes a cfg file
|
|
=====================
|
|
*/
|
|
#ifndef DEDICATED
|
|
static ConVar sv_quota_scriptExecsPerSecond("sv_quota_scriptExecsPerSecond", "3", FCVAR_REPLICATED | FCVAR_RELEASE,
|
|
"How many script executions per second clients are allowed to submit, 0 to disable the limitation thereof.", true, 0.f, false, 0.f);
|
|
#endif // !DEDICATED
|
|
|
|
void Cmd_Exec_f(const CCommand& args)
|
|
{
|
|
#ifndef DEDICATED
|
|
// Prevent users from running neo strafe commands and other quick hacks.
|
|
// TODO: when reBar becomes a thing, we should verify this function and
|
|
// flag users that patch them out.
|
|
if (g_pClientState->IsActive() && !ThreadInServerFrameThread())
|
|
{
|
|
const int execQuota = sv_quota_scriptExecsPerSecond.GetInt();
|
|
|
|
if (execQuota > 0)
|
|
{
|
|
const double flCurrentTime = Plat_FloatTime();
|
|
|
|
// Reset every second.
|
|
if ((flCurrentTime - s_flScriptExecTimeBase) > 1.0)
|
|
{
|
|
s_flScriptExecTimeBase = flCurrentTime;
|
|
s_nScriptExecCount = 0;
|
|
}
|
|
|
|
if (s_nScriptExecCount >= execQuota)
|
|
{
|
|
DevWarning(eDLL_T::ENGINE, "Client is simulating and exec count = %d of %d; dropped exec command: %s\n",
|
|
s_nScriptExecCount, execQuota, args.ArgS());
|
|
|
|
return;
|
|
}
|
|
|
|
s_nScriptExecCount++;
|
|
}
|
|
}
|
|
#endif // !DEDICATED
|
|
v__Cmd_Exec_f(args);
|
|
}
|
|
|
|
|
|
void VCallback::Detour(const bool bAttach) const
|
|
{
|
|
DetourSetup(&v__Cmd_Exec_f, &Cmd_Exec_f, bAttach);
|
|
}
|