/** * [CSRD] SRCDS Data Dumper * * Uses SourceMod dumping commands to dump useful information for plugin developers. */ #pragma semicolon 1 #include #tryinclude #pragma newdecls required #include #include #include #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; }