Advertisement
Diskretor

Untitled

Jun 27th, 2018
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.70 KB | None | 0 0
  1. #define PLUGIN_VERSION "1.1"
  2.  
  3. /*=======================================================================================
  4. Plugin Info:
  5.  
  6. * Name : [ANY] ConVars Anomaly Fixer
  7. * Author : Alex Dragokas
  8. * Descr. : Check in-game ConVars that differ from the cfg-files values (due to the Valve bug) and fix them before map start
  9. * Link : https://forums.alliedmods.net/showthread.php?p=2593843
  10.  
  11. ========================================================================================
  12. Change Log:
  13.  
  14. 1.1 (27-May-2018)
  15. - Improved log format for fix/error msg a little bit.
  16. - Added "convar_cvar_check_areas" ConVar to choose the area you need to check for (1 - difference in values, 2 - nonexistent convars, 3 - overflows, 4 - All {by default}).
  17. - Little checks, like cfg file presence.
  18.  
  19. 1.0 (25-May-2018)
  20. - Initial release
  21.  
  22. ========================================================================================
  23. Description:
  24.  
  25. Cfg Console Variables anomaly happens time by time, and caused by Valve bug:
  26. values can not be read from cfg-files and remains by default if you reach some limit
  27. of the number of lines in server.cfg / or cfg-files in total.
  28.  
  29. Also, this plugin check:
  30. - if some cfg files contain wrong values that exceed max/min value allowed.
  31. - unused ConVars.
  32.  
  33. Commands:
  34. 1. "sm_convar_anomaly_show" - to compare values in cfg files with actual in-game Cvar to find anomalies/unused cvars.
  35. 2. "sm_convar_anomaly_fix" - to attempt to fix Cvar anomalies.
  36.  
  37. By default, log-file is located at: "addons/sourcemod/logs/CVar_Anomaly.log"
  38.  
  39. Settings (ConVars):
  40. 1. "convar_anomaly_autofix" - fix Cvar anomaly automatically before each map start? (0 - off {default}, 1 - on).
  41. 2. "convar_anomaly_logpos" - where to write log? (1 - client conlose, 2 - server console, 4 - file, 1+2+4=7 - all places).
  42. 3. "convar_cfg_files_include" - if you need more cfg-files to process, place them here, separated by star (*)
  43. 4. "convar_cfg_files_exclude" - if you need exclude some cfg-files from processing, place them here, separated by star (*)
  44. 5. "convar_cvar_names_exclude" - if you need concrete ConVars to exclude from processing, place them here, separated by star (*)
  45.  
  46. Warning: some plugins / game specificially change its ConVars in-game process, so the final report is not necessarily accurate.
  47.  
  48. Using:
  49. To enable automatic fix, set "convar_anomaly_autofix" in cfg/sourcemod/sm_convar_anomaly.cfg file to 1.
  50.  
  51. ======================================================================================*/
  52.  
  53. #pragma semicolon 1
  54. #pragma newdecls required
  55.  
  56. #include <sourcemod>
  57. #include <regex>
  58.  
  59. #define MAX_CVAR_NAME_LENGTH 100
  60. #define MAX_CVAR_VALUE_LENGTH 400
  61. #define MAX_CVAR_ITEMS 50 // for parsing own .cfg
  62. #define CVAR_FLAGS FCVAR_NOTIFY
  63.  
  64. ArrayList g_hArrayCvarList, g_hArrayCvarValues, g_hArrayCfg, g_hArrayCvarExclude, g_hArrayCfgExclude;
  65. ConVar g_hCvarAutoFix, g_hCvarLogPos, g_hCvarCfgsInclude, g_hCvarCfgsExclude, g_hCvarCVarExclude, g_hCvarAreas;
  66. int g_iLogPos, g_iCheckAreas;
  67. bool g_bAutoFix;
  68. File g_hLog;
  69.  
  70. char g_sLogPath[PLATFORM_MAX_PATH] = "addons/sourcemod/logs/CVar_Anomaly.log";
  71.  
  72. /* ====================================================================================================
  73. PLUGIN INFO / START / END
  74. ====================================================================================================*/
  75.  
  76. public Plugin myinfo =
  77. {
  78. name = "[ANY] ConVars Anomaly Fixer",
  79. author = "Dragokas",
  80. description = "Check in-game ConVars that differ from the cfg-files values (due to the Valve bug) and fix them before each map start",
  81. version = PLUGIN_VERSION,
  82. url = "https://forums.alliedmods.net/showthread.php?p=2593843"
  83. }
  84.  
  85. public void OnPluginStart()
  86. {
  87. // Commands
  88. //
  89. RegAdminCmd("sm_convar_anomaly_show", CmdConfigsCompare2, ADMFLAG_ROOT, "Compares cfg files values with actual Cvar values to find anomalies and print difference/missing/overflow Cvars.");
  90. RegAdminCmd("sm_convar_anomaly_fix", CmdConfigsFix, ADMFLAG_ROOT, "Attempting to overwrite anomalous ConVars by cfg files values.");
  91.  
  92. // CVars
  93. //
  94. g_hCvarAutoFix = CreateConVar( "convar_anomaly_autofix", "0", "Fix Cvar anomaly automatically before each map start? (0 - off, 1 - on)", CVAR_FLAGS );
  95. g_hCvarLogPos = CreateConVar( "convar_anomaly_logpos", "7", "Where to write log? (1 - client conlose, 2 - server console, 4 - file, 1+2+4=7 - all places)", CVAR_FLAGS );
  96. g_hCvarCfgsInclude = CreateConVar( "convar_cfg_files_include", "cfg/server.cfg*cfg/autoexec.cfg", "List of additional cfg-files to process, separated by star (*)", CVAR_FLAGS );
  97. g_hCvarCfgsExclude = CreateConVar( "convar_cfg_files_exclude", "", "List of cfg-files to exclude from processing, separated by star (*)", CVAR_FLAGS );
  98. g_hCvarCVarExclude = CreateConVar( "convar_cvar_names_exclude", "sv_tags*sm*exec*setmaster", "List of ConVar names (or service commands from server.cfg) to exclude from processing, separated by star (*)", CVAR_FLAGS );
  99. g_hCvarAreas = CreateConVar( "convar_cvar_check_areas", "4", "Areas to check for (1 - difference in values, 2 - nonexistent convars, 3 - overflows, 4 - All).", CVAR_FLAGS );
  100. CreateConVar( "convar_anomaly_version", PLUGIN_VERSION, "ConVars Anomaly Fixer plugin version", FCVAR_DONTRECORD);
  101.  
  102. // Init ArrayLists
  103. //
  104. g_hArrayCfg = new ArrayList(PLATFORM_MAX_PATH);
  105. g_hArrayCfgExclude = new ArrayList(PLATFORM_MAX_PATH);
  106. g_hArrayCvarExclude = new ArrayList(MAX_CVAR_NAME_LENGTH);
  107. g_hArrayCvarList = new ArrayList(MAX_CVAR_NAME_LENGTH);
  108. g_hArrayCvarValues = new ArrayList(MAX_CVAR_VALUE_LENGTH);
  109.  
  110. AutoExecConfigSmart(true, "sm_convar_anomaly");
  111.  
  112. HookConVarChange(g_hCvarAutoFix, ConVarChanged);
  113. HookConVarChange(g_hCvarLogPos, ConVarChanged);
  114. HookConVarChange(g_hCvarCfgsInclude, ConVarChanged);
  115. HookConVarChange(g_hCvarCfgsExclude, ConVarChanged);
  116. HookConVarChange(g_hCvarCVarExclude, ConVarChanged);
  117. HookConVarChange(g_hCvarAreas, ConVarChanged);
  118.  
  119. GetCvars();
  120. }
  121.  
  122. // Smart version of AutoExecConfig() that not depends on bugged valve functions
  123. //
  124. void AutoExecConfigSmart(bool autoCreate, const char[] name, const char[] folder = ".")
  125. {
  126. char sFile[PLATFORM_MAX_PATH];
  127. Format(sFile, sizeof(sFile), "cfg/sourcemod/%s/%s.cfg", folder, name);
  128.  
  129. if (FileExists(sFile))
  130. CheckCfgAnomaly(0, sFile, true);
  131. else {
  132. if (StrEqual(folder, "."))
  133. AutoExecConfig(autoCreate, name);
  134. else
  135. AutoExecConfig(autoCreate, name, folder);
  136. }
  137. }
  138.  
  139. public void ConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
  140. {
  141. GetCvars();
  142. }
  143.  
  144. void GetCvars()
  145. {
  146. g_iLogPos = g_hCvarLogPos.IntValue;
  147. g_bAutoFix = g_hCvarAutoFix.BoolValue;
  148. g_iCheckAreas = g_hCvarAreas.IntValue;
  149.  
  150. if (g_iCheckAreas == 0) // old version compatibility
  151. g_iCheckAreas = 4;
  152.  
  153. SplitCvarToArrayList(g_hCvarCfgsInclude, "*", g_hArrayCfg);
  154. SplitCvarToArrayList(g_hCvarCfgsExclude, "*", g_hArrayCfgExclude);
  155. SplitCvarToArrayList(g_hCvarCVarExclude, "*", g_hArrayCvarExclude);
  156. }
  157.  
  158. void SplitCvarToArrayList(ConVar cvar, char[] delim, ArrayList al)
  159. {
  160. char aItem[MAX_CVAR_ITEMS][PLATFORM_MAX_PATH];
  161. char sValue[MAX_CVAR_VALUE_LENGTH];
  162. int iCount;
  163.  
  164. al.Clear();
  165. cvar.GetString(sValue, sizeof(sValue));
  166. if (strlen(sValue) != 0) {
  167. iCount = ExplodeString(sValue, delim, aItem, MAX_CVAR_ITEMS, PLATFORM_MAX_PATH);
  168. for (int i = 0; i < iCount; i++) {
  169. al.PushString(aItem[i]);
  170. }
  171. }
  172. }
  173.  
  174. // apply ConVar fix before OnMapStart() forward, so another plugins will be able to precache objects with correct (fixed) names.
  175. //
  176. public void OnAutoConfigsBuffered()
  177. {
  178. if (g_bAutoFix)
  179. CompareConfigs2(0, true);
  180. }
  181.  
  182. public Action CmdConfigsCompare2(int client, int args)
  183. {
  184. CompareConfigs2(client, false);
  185. return Plugin_Handled;
  186. }
  187.  
  188. public Action CmdConfigsFix(int client, int args)
  189. {
  190. CompareConfigs2(client, true);
  191. return Plugin_Handled;
  192. }
  193.  
  194. // Check anomaly in all cfg-files and optionally make in-game fix
  195. //
  196. bool CompareConfigs2(int client, bool doFix)
  197. {
  198. char sLine[100];
  199.  
  200. OpenLog("a");
  201. FormatTime(sLine, sizeof(sLine), "%F, %X", GetTime());
  202. Format(sLine, sizeof(sLine), "\n\n\n%s - %s\n", sLine, doFix ? "FIX" : "show");
  203. StringToLog(sLine);
  204.  
  205. char sDir[PLATFORM_MAX_PATH];
  206. strcopy(sDir, sizeof(sDir), "cfg/sourcemod/");
  207.  
  208. DirectoryListing hDir = OpenDirectory(sDir);
  209. if( hDir == null )
  210. {
  211. PrintConsoles(client, "Could not open the directory \"cfg/sourcemod\".");
  212. return false;
  213. }
  214.  
  215. char sFile[PLATFORM_MAX_PATH];
  216. FileType filetype;
  217. int pos, iFiles, iCvars = 0;
  218.  
  219. while( hDir.GetNext(sFile, sizeof(sFile), filetype) )
  220. {
  221. if( filetype == FileType_File )
  222. {
  223. pos = FindCharInString(sFile, '.', true);
  224. if( pos != -1 && strcmp(sFile[pos], ".cfg", false) == 0 )
  225. {
  226. Format(sFile, sizeof(sFile), "cfg/sourcemod/%s", sFile);
  227.  
  228. if (g_hArrayCfgExclude.FindString(sFile) == -1) {
  229. iFiles++;
  230. iCvars += CheckCfgAnomaly(client, sFile, doFix);
  231. }
  232. }
  233. }
  234. }
  235.  
  236. // process additional cfgs
  237. for (int i = 0; i < g_hArrayCfg.Length; i++) {
  238. iFiles++;
  239. g_hArrayCfg.GetString(i, sFile, sizeof(sFile));
  240. iCvars += CheckCfgAnomaly(client, sFile, doFix);
  241. }
  242.  
  243. PrintConsoles(client, "Total Files: %i (ConVars: %i)", iFiles, iCvars);
  244.  
  245. g_hArrayCvarList.Clear();
  246. g_hArrayCvarValues.Clear();
  247. delete hDir;
  248.  
  249. CloseLog();
  250. return true;
  251. }
  252.  
  253. // Check anomaly by specified file and optionally make in-game fix
  254. //
  255. int CheckCfgAnomaly(int client, const char sFile[PLATFORM_MAX_PATH], bool doFix)
  256. {
  257. int iCount, iTotal;
  258. char sCvarName[MAX_CVAR_NAME_LENGTH];
  259. char sCfgValue[MAX_CVAR_VALUE_LENGTH];
  260. char sCvarValueOld[MAX_CVAR_VALUE_LENGTH];
  261. char sCvarValueCur[MAX_CVAR_VALUE_LENGTH];
  262. char sCvarValueDef[MAX_CVAR_VALUE_LENGTH];
  263. ConVar hCvar;
  264. float min, max, fCfgValue;
  265. bool hasMin, hasMax;
  266.  
  267. g_hArrayCvarList.Clear();
  268. g_hArrayCvarValues.Clear();
  269.  
  270. // parse cfg
  271. if (!ProcessConfigA(client, ".", sFile))
  272. return 0;
  273.  
  274. // compare to actual
  275. for (int i = 0; i < g_hArrayCvarList.Length; i++) {
  276. iTotal++;
  277. g_hArrayCvarList.GetString(i, sCvarName, sizeof(sCvarName));
  278.  
  279. if (g_hArrayCvarExclude.FindString(sCvarName) != -1)
  280. continue;
  281.  
  282. if ((hCvar = FindConVar(sCvarName)) != null) {
  283. iCount++;
  284. g_hArrayCvarValues.GetString(i, sCfgValue, sizeof(sCfgValue));
  285. hCvar.GetString(sCvarValueCur, sizeof(sCvarValueCur));
  286. hCvar.GetDefault(sCvarValueDef, sizeof(sCvarValueDef));
  287.  
  288. if (!doFix && (g_iCheckAreas == 4 || g_iCheckAreas == 3)) {
  289. // check for min/max value excess
  290. hasMin = hCvar.GetBounds(ConVarBound_Lower, min);
  291. hasMax = hCvar.GetBounds(ConVarBound_Upper, max);
  292.  
  293. if (hasMin || hasMax) {
  294. if (!IsNumeric(sCfgValue)) {
  295. PrintConsoles(client, "Cfg value should be numeric. Cfg: %s, Cvar: %s, CfgValue: %s", sFile, sCvarName, sCfgValue);
  296. } else {
  297. fCfgValue = StringToFloat(sCfgValue);
  298. if (hasMin && fCfgValue < min)
  299. PrintConsoles(client, "Cfg value is too small. Cfg: %s, Cvar: %s, CfgValue: %s (min: %f)", sFile, sCvarName, sCfgValue, min);
  300. if (hasMax && fCfgValue > max)
  301. PrintConsoles(client, "Cfg value is too big. Cfg: %s, Cvar: %s, CfgValue: %s (max: %f)", sFile, sCvarName, sCfgValue, max);
  302. }
  303. }
  304. if (StrEqual(sCfgValue, "-2147483648")) {
  305. PrintConsoles(client, "Cfg value is too big. Cfg: %s, Cvar: %s, Value: %s, CfgValue: %s (max: 2147483647)", sFile, sCvarName, sCvarValueCur, sCfgValue);
  306. }
  307. }
  308.  
  309. if ((g_iCheckAreas == 4 || g_iCheckAreas == 1) && !IsCvarValuesEqual(sCvarValueCur, sCfgValue)) {
  310.  
  311. if (!doFix)
  312. PrintConsoles(client, "ConVar value is different. Cfg: %s, Cvar: %s, Value: %s (default: %s), should be: %s",
  313. sFile, sCvarName, sCvarValueCur, sCvarValueDef, sCfgValue);
  314.  
  315. if (doFix) {
  316. strcopy(sCvarValueOld, sizeof(sCvarValueOld), sCvarValueCur);
  317. hCvar.SetString(sCfgValue, true, false);
  318.  
  319. // check result again
  320. hCvar.GetString(sCvarValueCur, sizeof(sCvarValueCur));
  321. if (!StrEqual(sCvarValueCur, sCfgValue)) {
  322. PrintConsoles(client, "FAILURE. Unable to fix value of convar: %s (Value: %s, should be: %s)", sCvarName, sCvarValueCur, sCfgValue);
  323. } else {
  324. PrintConsoles(client, "Successfully changed convar: %s (Old value: %s, New value: %s)", sCvarName, sCvarValueOld, sCvarValueCur);
  325. }
  326. }
  327. }
  328. } else {
  329. if (!doFix && (g_iCheckAreas == 4 || g_iCheckAreas == 2) )
  330. PrintConsoles(client, "ConVar is not used: %s, cfg: %s", sCvarName, sFile);
  331. }
  332. }
  333. return iTotal;
  334. }
  335.  
  336. // compare two string values by float rules
  337. //
  338. bool IsCvarValuesEqual(char[] sValue1, char[] sValue2)
  339. {
  340. bool err1, err2;
  341. float fValue1, fValue2;
  342. fValue1 = ParseFloat(sValue1, err1);
  343. fValue2 = ParseFloat(sValue2, err2);
  344.  
  345. if (!err1 && !err2)
  346. if (AreFloatAlmostEqual(fValue1, fValue2))
  347. return (true);
  348.  
  349. if (err1 ^ err2)
  350. return (false);
  351.  
  352. return (StrEqual(sValue1, sValue2, false));
  353. }
  354.  
  355. // compare two float numbers (thanks to @hmmmmm {alliedmods.net})
  356. //
  357. bool AreFloatAlmostEqual(float a, float b, float precision = 0.001)
  358. {
  359. return FloatAbs( a - b ) <= precision;
  360. }
  361.  
  362. // convert string to float and return err == false on success.
  363. //
  364. float ParseFloat(char[] Str, bool &err)
  365. {
  366. if (IsNumeric(Str)) {
  367. err = false;
  368. return (StringToFloat(Str));
  369. } else {
  370. err = true;
  371. }
  372. return 0.0;
  373. }
  374.  
  375. // parse cfg file and save Cvar names and values to ArrayLists: g_hArrayCvarList / g_hArrayCvarValues (thanks @SilverShot for initial parser work)
  376. //
  377. bool ProcessConfigA(int client, const char sFolder[PLATFORM_MAX_PATH], const char sFile[PLATFORM_MAX_PATH])
  378. {
  379. char sPath[PLATFORM_MAX_PATH];
  380. Format(sPath, sizeof(sPath), "%s/%s", sFolder, sFile);
  381.  
  382. if (!FileExists(sPath))
  383. {
  384. PrintConsoles(client, "Can't process cfg file. Not exist: \"%s\".", sPath);
  385. return false;
  386. }
  387.  
  388. File hFile = OpenFile(sPath, "r");
  389. if( hFile == null )
  390. {
  391. PrintConsoles(client, "Failed to open \"%s\".", sPath);
  392. return false;
  393. }
  394.  
  395. char sValue[MAX_CVAR_NAME_LENGTH + MAX_CVAR_VALUE_LENGTH];
  396. char sLine[MAX_CVAR_NAME_LENGTH + MAX_CVAR_VALUE_LENGTH];
  397. int pos;
  398.  
  399. while( !hFile.EndOfFile() && hFile.ReadLine(sLine, sizeof(sLine)) )
  400. {
  401. TrimString(sLine);
  402.  
  403. if( sLine[0] != '\x0' && sLine[0] != '/' && sLine[1] != '/' )
  404. {
  405. if( strlen(sLine) > 5 )
  406. {
  407. pos = FindCharInString(sLine, ' ');
  408. if( pos != -1 )
  409. {
  410. strcopy(sValue, sizeof(sValue), sLine[pos + 1]);
  411. sLine[pos] = '\x0';
  412.  
  413. if (StrEqual(sLine, "sm_cvar")) {
  414. strcopy(sLine, sizeof(sLine), sValue); // value => initial line
  415. pos = FindCharInString(sLine, ' '); // repeat same parsing
  416. if( pos == -1 ) {
  417. continue;
  418. } else {
  419. strcopy(sValue, sizeof(sValue), sLine[pos + 1]);
  420. sLine[pos] = '\x0';
  421. }
  422. }
  423. sValue = UnQuote(sValue);
  424. g_hArrayCvarList.PushString(sLine);
  425. g_hArrayCvarValues.PushString(sValue);
  426. }
  427. }
  428. }
  429. }
  430. delete hFile;
  431. return true;
  432. }
  433.  
  434. // value for cvar can be quoted (CvarName "value") or not quoted (CvarName Value).
  435. //
  436. char[] UnQuote(char[] Str)
  437. {
  438. int pos;
  439. char EndChar;
  440. char buf[MAX_CVAR_VALUE_LENGTH];
  441. strcopy(buf, sizeof(buf), Str);
  442. TrimString(buf);
  443. if (buf[0] == '\"') {
  444. EndChar = '\"';
  445. strcopy(buf, sizeof(buf), buf[1]);
  446. } else {
  447. EndChar = ' ';
  448. }
  449. pos = FindCharInString(buf, EndChar);
  450. if( pos != -1 ) {
  451. buf[pos] = '\x0';
  452. }
  453. return buf;
  454. }
  455.  
  456. // fix issue with trying to display in colsole cvar value with % specifier, or escape character
  457. //
  458. char[] EscapeString(char[] Str)
  459. {
  460. char buf[MAX_CVAR_VALUE_LENGTH];
  461. strcopy(buf, sizeof(buf), Str);
  462. ReplaceString(buf, sizeof(buf), "%", "%%");
  463. ReplaceString(buf, sizeof(buf), "\\", "\\\\");
  464. return buf;
  465. }
  466.  
  467. // check if string is a valid number
  468. //
  469. bool IsNumeric(char[] Str)
  470. {
  471. static Regex regex;
  472. if (regex == null)
  473. regex = new Regex("^(((\\+|-)?\\d+(\\.\\d+)?)|((\\+|-)?\\.\\d+))(e(\\+|-)\\d+)?$", PCRE_CASELESS);
  474. if (strlen(Str) == 0) return (false);
  475. return (regex.Match(Str) > 0);
  476. }
  477.  
  478. // print log to rcon/client consoles and file
  479. //
  480. void PrintConsoles(int client, const char[] format, any ...)
  481. {
  482. char buffer[500], buf2[500];
  483. VFormat(buffer, sizeof(buffer), format, 3);
  484. Format(buf2, sizeof(buf2), "[CVar_Anomaly]: %s", buffer);
  485. if ((g_iLogPos & 1) && client != 0 && IsClientInGame(client))
  486. PrintToConsole(client, EscapeString(buf2));
  487. if (g_iLogPos & 2)
  488. PrintToServer(EscapeString(buf2));
  489. if (g_iLogPos & 4)
  490. StringToLog(buf2);
  491. }
  492. void OpenLog(char[] access)
  493. {
  494. if (g_hLog != null) return;
  495. g_hLog = OpenFile(g_sLogPath, access);
  496. if( g_hLog == null )
  497. {
  498. PrintToServer("[CVar_Anomaly] Failed to open or create log file: %s (access: %s)", g_sLogPath, access);
  499. return;
  500. }
  501. }
  502. void CloseLog()
  503. {
  504. g_hLog.Close();
  505. g_hLog = null;
  506. }
  507. void StringToLog(char[] Str)
  508. {
  509. g_hLog.WriteLine(Str);
  510. FlushFile(g_hLog);
  511. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement