|
@@ -0,0 +1,273 @@
|
|
|
+/**
|
|
|
+ * [CSRD] SRCDS Data Dumper
|
|
|
+ *
|
|
|
+ * Uses SourceMod dumping commands to dump useful information for plugin developers.
|
|
|
+ */
|
|
|
+#pragma semicolon 1
|
|
|
+#include <sourcemod>
|
|
|
+
|
|
|
+#tryinclude <steamtools>
|
|
|
+
|
|
|
+#pragma newdecls required
|
|
|
+#include <stocksoup/log_server>
|
|
|
+#include <stocksoup/files>
|
|
|
+#include <stocksoup/version>
|
|
|
+
|
|
|
+#define PLUGIN_VERSION "1.0.0"
|
|
|
+public Plugin myinfo = {
|
|
|
+ name = "[CSRD] SRCDS Automatic Data Dumper",
|
|
|
+ author = "nosoop",
|
|
|
+ description = "Automatically dumps useful engine data for plugin stuff.",
|
|
|
+
|
|
|
+ #if defined _steamtools_included
|
|
|
+ version = PLUGIN_VERSION,
|
|
|
+ #else
|
|
|
+ version = PLUGIN_VERSION ... "-no-steamtools",
|
|
|
+ #endif
|
|
|
+
|
|
|
+ url = "https://git.csrd.science/"
|
|
|
+}
|
|
|
+
|
|
|
+#define REDUMP_MARKER_FILE "data/.force-data-dump"
|
|
|
+#define REDUMP_OUTPUT_DIRECTORY "data/datadump"
|
|
|
+
|
|
|
+// Use a map with as few entities as possible to ensure enough free edicts
|
|
|
+#define LOW_ENTITY_MAP "itemtest"
|
|
|
+
|
|
|
+enum DumpAction {
|
|
|
+ DumpAction_None = 0, // no checks are performed
|
|
|
+ DumpAction_FileCheck, // check if dump indicator is present
|
|
|
+ DumpAction_PendingDump // pending dump on current map
|
|
|
+};
|
|
|
+
|
|
|
+DumpAction g_DumpAction;
|
|
|
+
|
|
|
+public APLRes AskPluginLoad2(Handle hPluginSelf, bool bLateLoaded, char[] error, int err_max) {
|
|
|
+ if (bLateLoaded) {
|
|
|
+ g_DumpAction = DumpAction_None;
|
|
|
+ } else {
|
|
|
+ g_DumpAction = DumpAction_FileCheck;
|
|
|
+ }
|
|
|
+
|
|
|
+ return APLRes_Success;
|
|
|
+}
|
|
|
+
|
|
|
+public void OnPluginStart() {
|
|
|
+ RegAdminCmd("csrd_force_data_dump", ForceDataDump, ADMFLAG_ROOT);
|
|
|
+}
|
|
|
+
|
|
|
+public void OnMapStart() {
|
|
|
+ char path[PLATFORM_MAX_PATH];
|
|
|
+ BuildPath(Path_SM, path, sizeof(path), "%s", REDUMP_MARKER_FILE);
|
|
|
+
|
|
|
+ switch (g_DumpAction) {
|
|
|
+ /**
|
|
|
+ * Stage 1: Check if the marker file exists; change map if necessary.
|
|
|
+ */
|
|
|
+ case DumpAction_FileCheck: {
|
|
|
+ if (FileExists(path, false)) {
|
|
|
+ g_DumpAction = DumpAction_PendingDump;
|
|
|
+ ForceChangeLevel(LOW_ENTITY_MAP, "Starting server data dump");
|
|
|
+ } else {
|
|
|
+ g_DumpAction = DumpAction_None;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Stage 2: Validate map change and ensure dump is pending; start dumping if so.
|
|
|
+ */
|
|
|
+ case DumpAction_PendingDump: {
|
|
|
+ char currentMap[PLATFORM_MAX_PATH];
|
|
|
+ GetCurrentMap(currentMap, sizeof(currentMap));
|
|
|
+
|
|
|
+ if (StrEqual(LOW_ENTITY_MAP, currentMap) && FileExists(path, false)) {
|
|
|
+ PerformDataDump();
|
|
|
+
|
|
|
+ DeleteFile(path);
|
|
|
+
|
|
|
+ int version = GetNetworkPatchVersion();
|
|
|
+ LogMessage("Dumped server data for network patchversion %d", version);
|
|
|
+
|
|
|
+ RequestFrame(ReloadServer);
|
|
|
+ } else {
|
|
|
+ LogError("Failed to change to map %s", LOW_ENTITY_MAP);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void PerformDataDump() {
|
|
|
+ char outputDirectory[PLATFORM_MAX_PATH];
|
|
|
+ BuildPath(Path_SM, outputDirectory, sizeof(outputDirectory), REDUMP_OUTPUT_DIRECTORY);
|
|
|
+
|
|
|
+ CreateDirectories(outputDirectory, 0b111101000); // u+rwx,g+rx
|
|
|
+
|
|
|
+ int version = GetNetworkPatchVersion();
|
|
|
+
|
|
|
+ // does not spawn entities
|
|
|
+ ServerCommand("sm_dump_netprops %s/%d.netprops.txt", outputDirectory, version);
|
|
|
+
|
|
|
+ DumpServerCommands("%s/%d.commands.txt", outputDirectory, version);
|
|
|
+ DumpServerConVars("%s/%d.convars.txt", outputDirectory, version);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * spawns all entities, server will crash on map change -- make sure there are 1400-ish
|
|
|
+ * free edicts
|
|
|
+ */
|
|
|
+ ServerCommand("sm_dump_datamaps %s/%d.datamaps.txt", outputDirectory, version);
|
|
|
+
|
|
|
+ // TODO replace this with just filtering the appropriate lines from datamaps?
|
|
|
+ // match expr /^[A-Za-z0-9_]+ - [A-Za-z0-9_]+$/
|
|
|
+ ServerCommand("sm_dump_classes %s/%d.classes.txt", outputDirectory, version);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * TODO touch .server-version file in REDUMP_OUTPUT_DIRECTORY to notify incron of completed
|
|
|
+ * dump
|
|
|
+ */
|
|
|
+}
|
|
|
+
|
|
|
+#if defined _steamtools_included
|
|
|
+/**
|
|
|
+ * Steam master server has requested a restart; game version probably updated.
|
|
|
+ * Create the file indicating a data dump should be performed.
|
|
|
+ */
|
|
|
+public Action Steam_RestartRequested() {
|
|
|
+ PrepareRedump();
|
|
|
+ return Plugin_Continue;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+public void ReloadServer(any trash_professor_abacus) {
|
|
|
+ ServerCommand("quit");
|
|
|
+}
|
|
|
+
|
|
|
+public Action ForceDataDump(int client, int argc) {
|
|
|
+ PrepareRedump();
|
|
|
+
|
|
|
+ ReplyToCommand(client, "[SM] Prepared dump process. Reload plugin or restart server.");
|
|
|
+
|
|
|
+ return Plugin_Continue;
|
|
|
+}
|
|
|
+
|
|
|
+/* Dump utilities */
|
|
|
+
|
|
|
+void DumpServerCommands(const char[] format, any ...) {
|
|
|
+ char outputPath[PLATFORM_MAX_PATH];
|
|
|
+ VFormat(outputPath, sizeof(outputPath), format, 2);
|
|
|
+
|
|
|
+ File outputFile = OpenFile(outputPath, "w");
|
|
|
+
|
|
|
+ char commandName[128], commandDescription[512];
|
|
|
+ bool bIsCommand;
|
|
|
+ int flags;
|
|
|
+
|
|
|
+ outputFile.WriteLine("\"%s\",\"%s\",\"%s\"", "Names", "Flags", "Help Text");
|
|
|
+
|
|
|
+ Handle hConCommand = FindFirstConCommand(commandName, sizeof(commandName), bIsCommand,
|
|
|
+ flags, commandDescription, sizeof(commandDescription));
|
|
|
+
|
|
|
+ do {
|
|
|
+ if (bIsCommand) {
|
|
|
+ ReplaceString(commandDescription, sizeof(commandDescription), "\"", "'");
|
|
|
+ outputFile.WriteLine("\"%s\",\"%s\",\"%s\"", commandName,
|
|
|
+ GetCommandFlagString(flags), commandDescription);
|
|
|
+ }
|
|
|
+ } while ( FindNextConCommand(hConCommand, commandName, sizeof(commandName),
|
|
|
+ bIsCommand, flags, commandDescription, sizeof(commandDescription)) );
|
|
|
+
|
|
|
+ delete outputFile;
|
|
|
+}
|
|
|
+
|
|
|
+void DumpServerConVars(const char[] format, any ...) {
|
|
|
+ char outputPath[PLATFORM_MAX_PATH];
|
|
|
+ VFormat(outputPath, sizeof(outputPath), format, 2);
|
|
|
+
|
|
|
+ File outputFile = OpenFile(outputPath, "w");
|
|
|
+
|
|
|
+ char commandName[128], commandDescription[512];
|
|
|
+ bool bIsCommand;
|
|
|
+ int flags;
|
|
|
+
|
|
|
+ outputFile.WriteLine("\"%s\",\"%s\",\"%s\",\"%s\"", "Names", "Defaults", "Flags",
|
|
|
+ "Help Text");
|
|
|
+
|
|
|
+ Handle hConCommand = FindFirstConCommand(commandName, sizeof(commandName), bIsCommand,
|
|
|
+ flags, commandDescription, sizeof(commandDescription));
|
|
|
+
|
|
|
+ do {
|
|
|
+ if (!bIsCommand) {
|
|
|
+ char defaultValue[128];
|
|
|
+ FindConVar(commandName).GetDefault(defaultValue, sizeof(defaultValue));
|
|
|
+
|
|
|
+ ReplaceString(commandDescription, sizeof(commandDescription), "\"", "'");
|
|
|
+ outputFile.WriteLine("\"%s\",\"%s\",\"%s\",\"%s\"", commandName,
|
|
|
+ defaultValue, GetCommandFlagString(flags), commandDescription);
|
|
|
+ }
|
|
|
+ } while ( FindNextConCommand(hConCommand, commandName, sizeof(commandName),
|
|
|
+ bIsCommand, flags, commandDescription, sizeof(commandDescription)) );
|
|
|
+
|
|
|
+ delete outputFile;
|
|
|
+}
|
|
|
+
|
|
|
+char[] GetCommandFlagString(int flags) {
|
|
|
+ static char s_flagStrings[][] = {
|
|
|
+ "UNREGISTERED",
|
|
|
+ "DEVELOPMENTONLY",
|
|
|
+ "GAMEDLL",
|
|
|
+ "CLIENTDLL",
|
|
|
+ "HIDDEN",
|
|
|
+ "PROTECTED",
|
|
|
+ "SPONLY",
|
|
|
+ "ARCHIVE",
|
|
|
+ "NOTIFY",
|
|
|
+ "USERINFO",
|
|
|
+ "PRINTABLEONLY",
|
|
|
+ "UNLOGGED",
|
|
|
+ "NEVER_AS_STRING",
|
|
|
+ "REPLICATED",
|
|
|
+ "CHEAT",
|
|
|
+ "SS", // split screen
|
|
|
+ "DEMO",
|
|
|
+ "DONTRECORD",
|
|
|
+ "PLUGIN_OR_SS_ADDED", // split screen; same value as FCVAR_PLUGIN
|
|
|
+ "RELEASE",
|
|
|
+ "RELOAD_MATERIALS",
|
|
|
+ "RELOAD_TEXTURES",
|
|
|
+ "NOT_CONNECTED",
|
|
|
+ "MATERIAL_SYSTEM_THREAD",
|
|
|
+ "ARCHIVE_GAMECONSOLE",
|
|
|
+ "ACCESSIBLE_FROM_THREADS",
|
|
|
+ "SERVER_CAN_EXECUTE",
|
|
|
+ "SERVER_CANNOT_QUERY",
|
|
|
+ "CLIENTCMD_CAN_EXECUTE"
|
|
|
+ };
|
|
|
+
|
|
|
+ char buffer[512];
|
|
|
+
|
|
|
+ for (int i = 0; i < sizeof(s_flagStrings); i++) {
|
|
|
+ int flagbit = (1 << i);
|
|
|
+
|
|
|
+ if (flags & flagbit) {
|
|
|
+ if (strlen(buffer)) {
|
|
|
+ StrCat(buffer, sizeof(buffer), " ");
|
|
|
+ }
|
|
|
+ StrCat(buffer, sizeof(buffer), s_flagStrings[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return buffer;
|
|
|
+}
|
|
|
+
|
|
|
+/* Dump marker functrions */
|
|
|
+
|
|
|
+void PrepareRedump() {
|
|
|
+ char path[PLATFORM_MAX_PATH];
|
|
|
+ BuildPath(Path_SM, path, sizeof(path), "%s", REDUMP_MARKER_FILE);
|
|
|
+
|
|
|
+ TouchFile(path);
|
|
|
+}
|
|
|
+
|
|
|
+void TouchFile(const char[] file) {
|
|
|
+ File f = OpenFile(file, "w");
|
|
|
+ delete f;
|
|
|
+}
|