Init
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"Games"
|
||||
{
|
||||
"#default"
|
||||
{
|
||||
"#supported"
|
||||
{
|
||||
"game" "csgo"
|
||||
}
|
||||
|
||||
"Keys"
|
||||
{
|
||||
"VEngineServerStringTable" "VEngineServerStringTable001"
|
||||
}
|
||||
|
||||
"Offsets"
|
||||
{
|
||||
"CServer::OS"
|
||||
{
|
||||
"windows" "1"
|
||||
"linux" "2"
|
||||
}
|
||||
"CParticleSystemDictionary::Count" // Str: "PrecacheStandardParticleSystems()"
|
||||
{
|
||||
"windows" "38"
|
||||
"linux" "38"
|
||||
}
|
||||
// @link https://github.com/VSES/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/particles/particles.cpp#L2659
|
||||
"CNetworkStringTableContainer::FindTable"
|
||||
{
|
||||
"windows" "3"
|
||||
"linux" "4"
|
||||
}
|
||||
}
|
||||
|
||||
// Sigs from the lib ( https://forums.alliedmods.net/showthread.php?t=309074 )
|
||||
// You can update them only by yourself using tutorial in the link
|
||||
"Signatures"
|
||||
{
|
||||
"CreateInterface"
|
||||
{
|
||||
"library" "engine"
|
||||
"windows" "@CreateInterface"
|
||||
"linux" "@CreateInterface"
|
||||
"mac" "@CreateInterface"
|
||||
}
|
||||
|
||||
/*
|
||||
* Info: Every custom particle precached during a map is not removed from precache table on the map end.
|
||||
* Then, if a lot of maps that uses custom particles are running in my server the precache table will be filled
|
||||
* and the custom particles used on the next maps will not show right but erros will appear in their place.
|
||||
*/
|
||||
"CParticleSystemDictionary::~CParticleSystemDictionary" // Str: "CParticleSystemMgr::InitAttributeTable has an out-of-date attribute list! (element %d not set up)\n" | "CParticleSystemMgr::InitAttributeTable" -> "CParticleSystemMgr::CParticleSystemMgr" -> "CParticleSystemMgr::~CParticleSystemMgr"
|
||||
{
|
||||
"library" "server"
|
||||
"windows" "\x55\x8B\xEC\x51\x56\x57\x8B\xF9\x33\xF6\x8B\x47\x58"
|
||||
"linux" "\x55\x89\xE5\x57\x56\x53\x83\xEC\x2C\x8B\x7D\x08\x8B\x47\x58\x85\xC0"
|
||||
}
|
||||
"CParticleSystemDefinition::ParseChildren" // Str: "DmeParticleSystemDefinition" and "children" and "preventNameBasedLookup"
|
||||
{
|
||||
"library" "server"
|
||||
"windows" "\x55\x8B\xEC\x83\xEC\x20\x53\x56\x57\x8B\xF9\x51"
|
||||
"linux" "\x55\x89\xE5\x57\x56\x53\x83\xEC\x2C\x8B\x45\x0C\xC7\x44\x24\x04"
|
||||
}
|
||||
"CNetworkStringTable::DeleteAllStrings" // Str: "___clientsideitemsplaceholder0___"
|
||||
{
|
||||
"library" "engine"
|
||||
"windows" "\x56\x8B\xF1\x57\x8B\x4E\x40"
|
||||
"linux" "\x55\x89\xE5\x53\x83\xEC\x14\x8B\x5D\x08\x8B\x43\x40"
|
||||
}
|
||||
}
|
||||
|
||||
// Addr from the lib ( https://forums.alliedmods.net/showthread.php?t=309074 )
|
||||
// You can update them only by yourself using tutorial in the link
|
||||
"Addresses"
|
||||
{
|
||||
"m_pParticleSystemDictionary"
|
||||
{
|
||||
"signature" "CParticleSystemDefinition::ParseChildren"
|
||||
"linux"
|
||||
{
|
||||
"read" "375"
|
||||
"read" "0"
|
||||
"read" "140"
|
||||
}
|
||||
"windows"
|
||||
{
|
||||
"read" "401"
|
||||
"read" "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user