csgo-plugins/CSGO_ParticleSystemFix/scripting/CSGO_ParticleSystemFix.sp

468 lines
14 KiB
SourcePawn
Raw Normal View History

2020-03-25 19:04:16 +01:00
#pragma semicolon 1
#pragma newdecls required
#include <sdktools>
#define PLATFORM_LINE_LENGTH 1024
#define NORMAL_LINE_LENGTH 256
#define PLUGIN_CONFIG "CSGO_ParticleSystemFix.games"
#define PLUGIN_LOGFILE "logs/CSGO_ParticleSystemFix.log"
/**
* @section List of operation systems.
**/
enum EngineOS {
OS_Unknown,
OS_Windows,
OS_Linux
};
/**
* @section Struct of operation types for server arrays.
**/
enum struct ServerData
{
/* Internal Particles */
ArrayList Particles;
/* OS */
EngineOS Platform;
/* Gamedata */
Handle Config;
}
/**
* @endsection
**/
ServerData gServerData;
/**
* Variables to store SDK calls handlers.
**/
Handle hSDKCallDestructorParticleDictionary;
Handle hSDKCallTableDeleteAllStrings;
/**
* Variables to store virtual SDK offsets.
**/
Address pParticleSystemDictionary;
int ParticleSystem_Count;
char g_sLogPath[PLATFORM_MAX_PATH];
public Plugin myinfo = {
name = "[CS:GO] Particle System Fix",
description = "",
author = "gubka && PŠΣ™ SHUFEN",
version = "1.0",
url = "https://forums.alliedmods.net/showthread.php?t=313951 && https://possession.jp"
};
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
RegPluginLibrary("CSGO_ParticleSystemFix");
CreateNative("UncacheAllParticleSystems", Native_UncacheAllParticleSystems);
return APLRes_Success;
}
public int Native_UncacheAllParticleSystems(Handle plugin, int numParams) {
ParticlesOnPurge();
}
/**
* @brief Particles module init function.
**/
public void OnPluginStart() {
BuildPath(Path_SM, g_sLogPath, sizeof(g_sLogPath), PLUGIN_LOGFILE);
if (GetEngineVersion() != Engine_CSGO) {
LogToFileEx(g_sLogPath, "[System Init] Engine error: This plugin only works on Counter-Strike: Global Offensive.");
return;
}
gServerData.Config = LoadGameConfigFile(PLUGIN_CONFIG);
/*_________________________________________________________________________________________________________________________________________*/
// Starts the preparation of an SDK call
StartPrepSDKCall(SDKCall_Raw);
PrepSDKCall_SetFromConf(gServerData.Config, SDKConf_Signature, "CParticleSystemDictionary::~CParticleSystemDictionary");
// Validate call
if ((hSDKCallDestructorParticleDictionary = EndPrepSDKCall()) == null) {
// Log failure
LogToFileEx(g_sLogPath, "[GameData Validation] Failed to load SDK call \"CParticleSystemDictionary::~CParticleSystemDictionary\". Update signature in \"%s\"", PLUGIN_CONFIG);
return;
}
/*_________________________________________________________________________________________________________________________________________*/
// Starts the preparation of an SDK call
StartPrepSDKCall(SDKCall_Raw);
PrepSDKCall_SetFromConf(gServerData.Config, SDKConf_Signature, "CNetworkStringTable::DeleteAllStrings");
// Validate call
if ((hSDKCallTableDeleteAllStrings = EndPrepSDKCall()) == null) {
// Log failure
LogToFileEx(g_sLogPath, "[GameData Validation] Failed to load SDK call \"CNetworkStringTable::DeleteAllStrings\". Update signature in \"%s\"", PLUGIN_CONFIG);
return;
}
/*_________________________________________________________________________________________________________________________________________*/
// Load other offsets
fnInitGameConfAddress(gServerData.Config, pParticleSystemDictionary, "m_pParticleSystemDictionary");
fnInitGameConfOffset(gServerData.Config, ParticleSystem_Count, "CParticleSystemDictionary::Count");
/*_________________________________________________________________________________________________________________________________________*/
//fnInitGameConfOffset(gServerData.Config, view_as<int>(gServerData.Platform), "CServer::OS");
LogToFileEx(g_sLogPath, "[System Init] Loaded \"Particle System Fix\"");
}
public void OnMapStart() {
// Precache CS:GO internal particles
ParticlesOnLoad();
// Load map particles
LoadMapExtraFiles();
}
public void OnMapEnd() {
ParticlesOnPurge();
}
/**
* @brief Particles module load function.
**/
void ParticlesOnLoad() {
// Initialize buffer char
static char sBuffer[PLATFORM_LINE_LENGTH];
// Validate that particles wasn't precache yet
bool bSave = LockStringTables(false);
Address pTable = CNetworkStringTableContainer_FindTable("ParticleEffectNames");
int iCount = LoadFromAddress(pParticleSystemDictionary + view_as<Address>(ParticleSystem_Count), NumberType_Int16);
if (pTable != Address_Null && !iCount) { /// Validate that table is exist and it empty
// Opens the file
File hFile = OpenFile("particles/particles_manifest.txt", "rt", true);
// If doesn't exist stop
if (hFile == null) {
LogToFileEx(g_sLogPath, "[Config Validation] Error opening file: \"particles/particles_manifest.txt\"");
return;
}
// Read lines in the file
while (hFile.ReadLine(sBuffer, sizeof(sBuffer))) {
// Checks if string has correct quotes
int iQuotes = CountCharInString(sBuffer, '"');
if (iQuotes == 4) {
// Trim string
TrimString(sBuffer);
// Copy value string
strcopy(sBuffer, sizeof(sBuffer), sBuffer[strlen("\"file\"")]);
// Trim string
TrimString(sBuffer);
// Strips a quote pair off a string
StripQuotes(sBuffer);
// Precache model
int i = 0; if (sBuffer[i] == '!') i++;
PrecacheGeneric(sBuffer[i], true);
SDKCall(hSDKCallTableDeleteAllStrings, pTable); /// HACK~HACK
/// Clear tables after each file because some of them contains
/// huge amount of particles and we work around the limit
}
}
delete hFile;
}
// Initialize the table index
static int tableIndex = INVALID_STRING_TABLE;
// Validate table
if (tableIndex == INVALID_STRING_TABLE) {
// Searches for a string table
tableIndex = FindStringTable("ParticleEffectNames");
}
// If array hasn't been created, then create
if (gServerData.Particles == null) {
// Initialize a particle list array
gServerData.Particles = CreateArray(NORMAL_LINE_LENGTH);
// i = table string
iCount = GetStringTableNumStrings(tableIndex);
for (int i = 0; i < iCount; i++) {
// Gets the string at a given index
ReadStringTable(tableIndex, i, sBuffer, sizeof(sBuffer));
// Push data into array
gServerData.Particles.PushString(sBuffer);
}
} else {
// i = particle name
iCount = gServerData.Particles.Length;
for (int i = 0; i < iCount; i++) {
// Gets the string at a given index
gServerData.Particles.GetString(i, sBuffer, sizeof(sBuffer));
// Push data into table
AddToStringTable(tableIndex, sBuffer);
}
}
/*
// Refresh tables
pTable = CNetworkStringTableContainer_FindTable("ExtraParticleFilesTable");
if (pTable != Address_Null) {
SDKCall(hSDKCallTableDeleteAllStrings, pTable);
}
pTable = CNetworkStringTableContainer_FindTable("genericprecache");
if (pTable != Address_Null) {
SDKCall(hSDKCallTableDeleteAllStrings, pTable);
}
*/
LockStringTables(bSave);
}
void LoadMapExtraFiles() {
char sMapName[PLATFORM_MAX_PATH];
GetCurrentMap(sMapName, sizeof(sMapName));
LoadPerMapParticleManifest(sMapName);
}
void LoadPerMapParticleManifest(const char[] sMapName) {
KeyValues hKv = GetParticleManifestKv(sMapName);
if (hKv == null) {
return;
}
ParseParticleManifestKv(hKv, true, true);
delete hKv;
}
char sDirExt[][][] = { {
"particles/", "maps/", "particles/", "maps/"
}, {
"_manifest_override.txt", "_particles_override.txt", "_manifest.txt", "_particles.txt"
}
};
KeyValues GetParticleManifestKv(const char[] sMapName) {
char sFileName[PLATFORM_MAX_PATH];
for (int i = 0; i < sizeof(sDirExt[]); i++) {
Format(sFileName, sizeof(sFileName), "%s%s%s", sDirExt[0][i], sMapName, sDirExt[1][i]);
if (FileExists(sFileName, true)) {
if (FileExists(sFileName, false))
AddFileToDownloadsTable(sFileName);
return CreateManifestKv(sFileName);
}
}
return null;
}
KeyValues CreateManifestKv(const char[] sPath) {
KeyValues hKv = CreateKeyValues("particles_manifest");
//hKv.SetEscapeSequences(true);
if (hKv.ImportFromFile(sPath)) {
return hKv;
}
return null;
}
void ParseParticleManifestKv(KeyValues hKv, bool perMap = false, bool awayPreloads = false) {
char sPCF[PLATFORM_MAX_PATH];
if (hKv.GotoFirstSubKey(false))
do {
hKv.GetString(NULL_STRING, sPCF, sizeof(sPCF));
if (sPCF[0] == '\0')
continue;
bool preload = FindCharInString(sPCF, '!') == 0;
if (preload)
strcopy(sPCF, sizeof(sPCF), sPCF[1]);
if (FileExists(sPCF, true)) {
if (perMap && FileExists(sPCF, false))
AddFileToDownloadsTable(sPCF);
PrecacheGeneric(sPCF, awayPreloads || preload);
}
} while (hKv.GotoNextKey(false));
}
/**
* @brief Particles module purge function.
**/
void ParticlesOnPurge() {
// @link https://github.com/VSES/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/particles/particles.cpp#L81
SDKCall(hSDKCallDestructorParticleDictionary, pParticleSystemDictionary);
/*_________________________________________________________________________________________________________________________________________*/
// Clear particles in the effect table
bool bSave = LockStringTables(false);
Address pTable = CNetworkStringTableContainer_FindTable("ParticleEffectNames");
if (pTable != Address_Null) {
SDKCall(hSDKCallTableDeleteAllStrings, pTable);
}
// Clear particles in the extra effect table
pTable = CNetworkStringTableContainer_FindTable("ExtraParticleFilesTable");
if (pTable != Address_Null) {
SDKCall(hSDKCallTableDeleteAllStrings, pTable);
}
// Clear particles in the generic precache table
pTable = CNetworkStringTableContainer_FindTable("genericprecache");
if (pTable != Address_Null) {
SDKCall(hSDKCallTableDeleteAllStrings, pTable);
}
LockStringTables(bSave);
}
stock Address CNetworkStringTableContainer_FindTable(const char[] tableName) {
Address pNetworkstringtable = GetNetworkStringTableAddr();
if (pNetworkstringtable == Address_Null)
return Address_Null;
static Handle hFindTable = INVALID_HANDLE;
if (hFindTable == INVALID_HANDLE) {
if (gServerData.Config == INVALID_HANDLE)
return Address_Null;
StartPrepSDKCall(SDKCall_Raw);
if (!PrepSDKCall_SetFromConf(gServerData.Config, SDKConf_Virtual, "CNetworkStringTableContainer::FindTable")) {
LogToFileEx(g_sLogPath, "[Find Table] Cant find the method CNetworkStringTableContainer::FindTable.");
return Address_Null;
}
PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); // tableName
PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
hFindTable = EndPrepSDKCall();
if (hFindTable == INVALID_HANDLE) {
LogToFileEx(g_sLogPath, "[Find Table] Method CNetworkStringTableContainer::FindTable was not loaded right.");
return Address_Null;
}
}
return SDKCall(hFindTable, pNetworkstringtable, tableName);
}
stock Address GetNetworkStringTableAddr() {
static Address pEngineServerStringTable = Address_Null;
if (pEngineServerStringTable == Address_Null) {
if (gServerData.Config == null)
return Address_Null;
char sInterfaceName[64];
if (!GameConfGetKeyValue(gServerData.Config, "VEngineServerStringTable", sInterfaceName, sizeof(sInterfaceName)))
strcopy(sInterfaceName, sizeof(sInterfaceName), "VEngineServerStringTable001");
pEngineServerStringTable = CreateEngineInterface(sInterfaceName);
}
return pEngineServerStringTable;
}
stock Address CreateEngineInterface(const char[] sInterfaceKey, Address ptr = Address_Null) {
static Handle hCreateInterface = null;
if (hCreateInterface == null) {
if (gServerData.Config == null)
return Address_Null;
StartPrepSDKCall(SDKCall_Static);
if (!PrepSDKCall_SetFromConf(gServerData.Config, SDKConf_Signature, "CreateInterface")) {
LogToFileEx(g_sLogPath, "[Create Engine Interface] Failed to get CreateInterface");
return Address_Null;
}
PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain, VDECODE_FLAG_ALLOWNULL);
PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
hCreateInterface = EndPrepSDKCall();
if (hCreateInterface == null) {
LogToFileEx(g_sLogPath, "[Create Engine Interface] Function CreateInterface was not loaded right.");
return Address_Null;
}
}
if (gServerData.Config == null)
return Address_Null;
char sInterfaceName[64];
if (!GameConfGetKeyValue(gServerData.Config, sInterfaceKey, sInterfaceName, sizeof(sInterfaceName)))
strcopy(sInterfaceName, sizeof(sInterfaceName), sInterfaceKey);
Address addr = SDKCall(hCreateInterface, sInterfaceName, ptr);
if (addr == Address_Null) {
LogToFileEx(g_sLogPath, "[Create Engine Interface] Failed to get pointer to interface %s(%s)", sInterfaceKey, sInterfaceName);
return Address_Null;
}
return addr;
}
/**
* @brief Finds the amount of all occurrences of a character in a string.
*
* @param sBuffer Input string buffer.
* @param cSymbol The character to search for.
* @return The amount of characters in the string, or -1 if the characters were not found.
*/
int CountCharInString(char[] sBuffer, char cSymbol) {
// Initialize index
int iCount;
// i = char index
int iLen = strlen(sBuffer);
for (int i = 0; i < iLen; i++) {
// Validate char
if (sBuffer[i] == cSymbol) {
// Increment amount
iCount++;
}
}
// Return amount
return iCount ? iCount : -1;
}
/**
* @brief Returns an offset value from a given config.
*
* @param gameConf The game config handle.
* @param iOffset An offset, or -1 on failure.
* @param sKey Key to retrieve from the offset section.
**/
stock void fnInitGameConfOffset(Handle gameConf, int &iOffset, char[] sKey) {
// Validate offset
if ((iOffset = GameConfGetOffset(gameConf, sKey)) == -1) {
LogToFileEx(g_sLogPath, "[GameData Validation] Failed to get offset: \"%s\"", sKey);
}
}
/**
* @brief Returns an address value from a given config.
*
* @param gameConf The game config handle.
* @param pAddress An address, or null on failure.
* @param sKey Key to retrieve from the address section.
**/
stock void fnInitGameConfAddress(Handle gameConf, Address &pAddress, char[] sKey) {
// Validate address
if ((pAddress = GameConfGetAddress(gameConf, sKey)) == Address_Null) {
LogToFileEx(g_sLogPath, "[GameData Validation] Failed to get address: \"%s\"", sKey);
}
}