output-info-plugin/scripting/dump_parser.sp
KiD Fearless 327bb24465 v1.8 update remove stripper_dump dependancy mostly
Fixed Invalid Memory Access error on cloned entities.

Plugin now uses OnLevelInit for it's dump files. This helps remove a lot of dependency from stripper plugins but doesn't work with late loading. If the plugin is loaded late without stripper functionality then it will disable any natives for that map.

Credits to nosoop for his SplitStringOnNewLine function

Disabled debug
2019-07-15 20:52:42 -06:00

701 lines
16 KiB
SourcePawn

#include <sourcemod>
#include <regex>
#include <dump_parser>
public Plugin myinfo =
{
name = "Output Dump Parser",
author = "KiD Fearless",
description = "Generates and parses stripper dump files.",
version = "1.8",
url = "https://steamcommunity.com/id/kidfearless/"
}
#pragma dynamic 1048576
ArrayList gA_Entites;
StringMap gSM_EntityList;
Handle gH_Forwards_OnFileReady;
Handle gH_Forwards_OnFileProcessed;
ConVar gC_Enabled;
ConVar gC_ParseOnly;
bool gB_Ready;
char gS_StripperPath[PLATFORM_MAX_PATH];
char gS_MapEntities[2097152];
bool gB_Late;
#define KEYWORDS_SIZE 6
char KEYWORDS[KEYWORDS_SIZE][32] =
{
"{",
"}",
"\"classname\"",
"wait",
"hammerid",
"\"on"
};
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
CreateNative("GetDumpStringMap", Native_GetDumpStringMap);
CreateNative("GetDumpEntityAsList", Native_GetDumpEntityAsList);
CreateNative("GetDumpEntityAsArray", Native_GetDumpEntityAsArray);
CreateNative("GetDumpEntityFromID", Native_GetDumpEntityFromID);
CreateNative("GetDumpEntityFromIDAsArray", Native_GetDumpEntityFromIDAsArray);
CreateNative("GetDumpEntities", Native_GetDumpEntities);
CreateNative("IsDumpReady", Native_IsDumpReady);
RegPluginLibrary("output_dump_parser");
gB_Late = late;
if(late)
{
gB_Ready = false;
}
return APLRes_Success;
}
public void OnPluginStart()
{
GetCommandLineParam("+stripper_path", gS_StripperPath, PLATFORM_MAX_PATH, "addons/stripper");
gH_Forwards_OnFileReady = CreateGlobalForward("OnDumpFileReady", ET_Event);
gH_Forwards_OnFileProcessed = CreateGlobalForward("OnDumpFileProcessed", ET_Event);
gC_Enabled = CreateConVar("sm_dump_parser_enabled", "1", "If 0 disables the plugin entirely. Stopping it from creating dumps and caching data.", _, true, 0.0, true, 1.0);
gC_ParseOnly = CreateConVar("sm_dump_parser_nocache", "0", "If 0 only parses dump files, does not cache values.", _, true, 0.0, true, 1.0);
gC_Enabled.AddChangeHook(OnPluginToggled);
gC_ParseOnly.AddChangeHook(OnPluginToggled);
AutoExecConfig();
gA_Entites = new ArrayList(sizeof(Entity));
gSM_EntityList = new StringMap();
}
public void OnPluginToggled(ConVar convar, const char[] oldValue, const char[] newValue)
{
bool bOld = !!StringToInt(oldValue);
bool bNew = !!StringToInt(newValue);
if(bOld && !bNew)
{
gB_Ready = false;
CleanUp();
}
else if(!bOld && bNew)
{
InitSetup();
}
}
public Action OnLevelInit(const char[] mapName, char mapEntities[2097152])
{
gS_MapEntities = mapEntities;
return Plugin_Continue;
}
public void OnConfigsExecuted()
{
if(gC_Enabled.BoolValue)
{
InitSetup();
}
}
public void OnMapEnd()
{
gB_Ready = false;
CleanUp();
}
void InitSetup(bool recursive = false)
{
char mapName[PLATFORM_MAX_PATH];
GetCurrentMap(mapName, PLATFORM_MAX_PATH);
GetMapDisplayName(mapName, mapName, PLATFORM_MAX_PATH);
char jsonPath[PLATFORM_MAX_PATH];
FormatEx(jsonPath, PLATFORM_MAX_PATH, "%s/IO", gS_StripperPath);
// Check for IO folder
if(!DirExists(jsonPath))
{
LOG("OP DIRECTORY DOESN'T EXIST");
// Create one if it doesn't exist
if(!CreateDirectory(jsonPath, 511))
{
LogError("STRIPPER DUMP PARSER COULD NOT CREATE IO FOLDER");
return;
}
}
else
{
LOG("IO DIRECTORY EXISTS...CHECKING FOR JSON FILE");
Format(jsonPath, PLATFORM_MAX_PATH, "%s/%s.JSON", jsonPath, mapName);
if(!FileExists(jsonPath))
{
LOG("JSON FILE DOESN'T EXIST... CHECKING FOR DUMPS DIRECTORY");
char stripperFile[PLATFORM_MAX_PATH];
FormatEx(stripperFile, PLATFORM_MAX_PATH, "%s/dumps/", gS_StripperPath);
if(!DirExists(stripperFile))
{
LOG("DUMPS DIRECTORY DOESN'T EXIST");
// Create one if it doesn't exist
if(!CreateDirectory(stripperFile, 511))
{
LogError("STRIPPER DUMP PARSER COULD NOT CREATE DUMPS DIRECTORY");
return;
}
}
LOG("JSON FILE DOESN'T EXIST... CHECKING FOR CFG FILES");
Format(stripperFile, PLATFORM_MAX_PATH, "%s/%s.0000.cfg", stripperFile, mapName);
// Check if a dump already exists.
if(!FileExists(stripperFile))
{
if(gB_Late)
{
if(recursive)
{
LOG("RECURSION DETECTED... STOPPING EXECUTION THIS MAP");
gB_Ready = false;
return;
}
else
{
ServerCommand("stripper_dump");
CreateTimer(1.0, Timer_Delayed_Init);
return;
}
}
else
{
// If it wasn't loaded late then we create our own dump from our copy.
File kvFile = OpenFile(stripperFile, "w");
int i, n;
char line[OUTPUT_SIZE];
while ((n = SplitStringOnKeyChar(gS_MapEntities[i], line)) != -1)
{
kvFile.WriteLine(line);
i += n;
}
delete kvFile;
}
}
// re-open the dump for reading
File mapFile = OpenFile(stripperFile, "r");
// create a temporary kv file for writing to.
char kvPath[PLATFORM_MAX_PATH];
FormatEx(kvPath, PLATFORM_MAX_PATH, "%s/dumps/%s.kv", gS_StripperPath, mapName);
File kvFile = OpenFile(kvPath, "a+");
// check for errors.
if(mapFile == null || kvFile == null)
{
LogError("ERROR: Could not open mapFile: '%s'", stripperFile);
return;
}
// Format the dump into single line kv's
char buffer[OUTPUT_SIZE];
while(!mapFile.EndOfFile())
{
if(!mapFile.ReadLine(buffer, OUTPUT_SIZE))
{
break;
}
char replace[] = "}\n";
ReplaceString(buffer, OUTPUT_SIZE, "\n", "");
ReplaceString(buffer, OUTPUT_SIZE, "}", replace);
// ReplaceString(buffer, OUTPUT_SIZE, "\" \"", "\": \"");
ReplaceString(buffer, OUTPUT_SIZE, "\e", ";");
StripLine(buffer);
kvFile.WriteString(buffer, false);
}
delete mapFile;
// reset the kv file, it's time for round 2
kvFile.Seek(0, SEEK_SET);
char kvPath2[PLATFORM_MAX_PATH];
FormatEx(kvPath2, PLATFORM_MAX_PATH, "%s2", kvPath);
File kvFile2 = OpenFile(kvPath2, "a+");
// Regular expression meant to match entities that don't have any outputs.
Regex regIdOnly = new Regex("^{(\"classname\" \"\\S*\")?,? ?(\"hammerid\" \"\\d*\")?,? ?(\"wait\" \"\\d?.?\\d?\")?,? ?}?$");
// loop through the first kv file and read each line
while(!kvFile.EndOfFile())
{
if(!kvFile.ReadLine(buffer, OUTPUT_SIZE))
{
break;
}
// replace any empty braces with empty strings
ReplaceString(buffer, OUTPUT_SIZE, "{}", "");
// replace any entiteis without outputs with empty strings
if(regIdOnly.Match(buffer) > 0)
{
char buffer2[OUTPUT_SIZE]
regIdOnly.GetSubString(0, buffer2, OUTPUT_SIZE);
ReplaceString(buffer, OUTPUT_SIZE, buffer2, "");
}
kvFile2.WriteString(buffer, false);
}
delete regIdOnly;
delete kvFile;
#if !defined NO_DEL
DeleteFile(kvPath);
#endif
// Last round, this inserts a root section to each line so that it can be read as a kv
kvFile2.Seek(0, SEEK_SET);
File JSONOutput = OpenFile(jsonPath, "a+");
while(!kvFile2.EndOfFile())
{
if(!kvFile2.ReadLine(buffer, OUTPUT_SIZE))
{
break;
}
FormatOutputString(buffer);
if(!StrEqual(buffer, "\n"))
{
ReplaceString(buffer, OUTPUT_SIZE, "{", "\"0\"{");
JSONOutput.WriteString(buffer, false);
}
}
delete JSONOutput;
delete kvFile2;
#if !defined NO_DEL
DeleteFile(kvPath2);
#endif
Ready(mapName);
}
else
{
LOG("JSON FILE EXISTS... SCANNING FILE");
Ready(mapName);
}
}
}
void Ready(char mapName[PLATFORM_MAX_PATH])
{
Call_StartForward(gH_Forwards_OnFileReady);
Call_Finish();
if(gA_Entites.Length > 0 || gSM_EntityList.Size > 0)
{
CleanUp();
}
if(gC_ParseOnly.BoolValue)
{
return;
}
// Point to the location of the formatted output list
char path[PLATFORM_MAX_PATH];
FormatEx(path, PLATFORM_MAX_PATH, "%s/IO/%s.JSON", gS_StripperPath, mapName);
// If an output list couldn't be found stop the operation
// Open the file for reading, if an error occurs then log it
if(!FileExists(path))
{
LogError("ERROR: COULD NOT FIND IO JSON FILE: %s", path);
SetFailState("NO JSON FILE FOUND. UNLOADING PLUGIN");
return;
}
File ioFile = OpenFile(path, "r");
if(ioFile == null)
{
LogError("ERROR: COULD NOT OPEN IO JSON FILE: %s", path);
return;
}
while(!IsEndOfFile(ioFile))
{
char buffer[OUTPUT_SIZE];
if(!ioFile.ReadLine(buffer, OUTPUT_SIZE))
{
break;
}
// Import the kv file from the current line.
KeyValues kv = new KeyValues("0");
if(!kv.ImportFromString(buffer))
{
LogError("Could not parse kv file: '%s'", buffer);
continue;
}
Entity ent;
// Grab it's hammer id
char hammerid[MEMBER_SIZE];
kv.GetString("hammerid", hammerid, MEMBER_SIZE);
strcopy(ent.HammerID, MEMBER_SIZE, hammerid);
char wait[MEMBER_SIZE];
kv.GetString("wait", wait, MEMBER_SIZE);
ent.Wait = StringToFloat(wait);
char classname[MEMBER_SIZE];
kv.GetString("classname", classname, MEMBER_SIZE);
strcopy(ent.Classname, MEMBER_SIZE, classname);
char counter[12] = "0";
char output[OUTPUT_SIZE];
// I don't know how this works, but it works, not gonna ask why.
ent.OutputList = new ArrayList(sizeof(Output));
// declare an int counter variable. run the GetKVString function to both check for it's existance and return it's value.
// Then ONCE it's done increment the variable and format it into the counter.
for(int i = 0; GetKVString(kv, counter, output, OUTPUT_SIZE); FormatEx(counter, 12, "%i", ++i))
{
Output out;
out.Parse(output);
ent.OutputList.PushArray(out);
}
delete kv;
int index = gA_Entites.PushArray(ent);
// associate the index with the entities hammerid
gSM_EntityList.SetValue(hammerid, index);
}
delete ioFile;
gB_Ready = true;
Call_StartForward(gH_Forwards_OnFileProcessed);
Call_Finish();
}
void CleanUp()
{
for(int i = 0; i < gA_Entites.Length; ++i)
{
Entity e;
gA_Entites.GetArray(i, e);
e.CleanUp();
}
gA_Entites.Clear();
gSM_EntityList.Clear();
}
void StripLine(char[] line, int length = OUTPUT_SIZE)
{
bool found = false;
for(int i = 0; i < KEYWORDS_SIZE; ++i)
{
if(StringContains(line, KEYWORDS[i]))
{
found = true;
break;
}
}
if(!found)
{
strcopy(line, length, "");
}
}
void FormatOutputString(char[] buffer)
{
Regex regOutput = new Regex("(\"On\\w*\")");
int current = 0
while(regOutput.Match(buffer) > 0)
{
char output[256];
regOutput.GetSubString(1, output, 256);
char num[12];
/* Convert the current match number to a string with quotes */
FormatEx(num, 12, "\"%i\"", current++);// 1 -> 0
// PrintToConsoleAll(num);
/* Replace the first occurance of the match with the match number */
ReplaceStringEx(buffer, OUTPUT_SIZE, output, num);
/* convert "2" into "2" " so that we can insert the output as the parameter instead */
Format(num, 12, "%s \"", num);
/* Strip the quotes from the output "OnStartTouch" - > OnStartTouch */
StripQuotes(output);
/* "2" "OnStartTouch; */
Format(output, 256, "%s%s;", num, output);
/* "2" " - > "2" "OnStartTouch;*/
ReplaceStringEx(buffer, OUTPUT_SIZE, num, output);
}
delete regOutput;
}
public Action Timer_Delayed_Init(Handle timer)
{
InitSetup(true);
return Plugin_Handled;
}
// credits to nosoop in his level_keyvalues plugin.
int SplitStringOnKeyChar(const char[] input, char[] buffer)
{
int i;
while(input[i] != '\n' && input[i] != 0)
{
++i;
}
if(!input[i])
{
return -1;
}
++i;
strcopy(buffer, i, input);
return i;
}
// native StringMap GetDumpStringMap();
public any Native_GetDumpStringMap(Handle plugin, int numParams)
{
if(!gB_Ready)
{
//LogError("Native called before dump file has been processed.");
return INVALID_HANDLE;
}
if(gA_Entites.Length < 1 || gSM_EntityList.Size < 1)
{
//LogError("Entity lists are empty.");
return INVALID_HANDLE;
}
return CloneHandle(gSM_EntityList, plugin);
}
// native ArrayList GetDumpEntityAsList(int ent);
public any Native_GetDumpEntityAsList(Handle plugin, int numParams)
{
if(!gB_Ready)
{
//LogError("Native called before dump file has been processed.");
return INVALID_HANDLE;
}
if(gA_Entites.Length < 1 || gSM_EntityList.Size < 1)
{
//LogError("Entity lists are empty.");
return INVALID_HANDLE;
}
int index = GetNativeCell(1);
int hammer = GetHammerFromIndex(index);
char id[MEMBER_SIZE];
IntToString(hammer, id, MEMBER_SIZE);
int position = -1;
if(!gSM_EntityList.GetValue(id, position))
{
//LogError("Could not find entity with with the index '%i', hammmerid '%i'.", index, hammer);
return INVALID_HANDLE;
}
if(position >= gA_Entites.Length || position < 0)
{
//LogError( "List position out of range");
return INVALID_HANDLE;
}
Entity temp;
gA_Entites.GetArray(position, temp);
Entity ent;
CloneEntity(temp, ent, plugin);
ArrayList list = new ArrayList(sizeof(Entity));
list.PushArray(ent);
return list;
}
// native bool GetDumpEntityAsArray(int index, any[] entity);
public any Native_GetDumpEntityAsArray(Handle plugin, int numParams)
{
if(!gB_Ready)
{
//LogError("Native called before dump file has been processed.");
return false;
}
if(gA_Entites.Length < 1 || gSM_EntityList.Size < 1)
{
//LogError("Entity lists are empty.");
return false;
}
int index = GetNativeCell(1);
int hammer = GetHammerFromIndex(index);
char id[MEMBER_SIZE];
IntToString(hammer, id, MEMBER_SIZE);
int position = -1;
if(!gSM_EntityList.GetValue(id, position))
{
//LogError("Could not find entity with with the index '%i', hammmerid '%i'.", index, hammer);
return false;
}
if(position >= gA_Entites.Length || position < 0)
{
//LogError( "List position out of range");
return false;
}
Entity temp;
gA_Entites.GetArray(position, temp);
Entity ent;
CloneEntity(temp, ent, plugin);
SetNativeArray(2, ent, sizeof(Entity));
return true;
}
// native ArrayList GetDumpEntityFromID(int ent);
public any Native_GetDumpEntityFromID(Handle plugin, int numParams)
{
if(!gB_Ready)
{
//LogError("Native called before dump file has been processed.");
return INVALID_HANDLE;
}
if(gA_Entites.Length < 1 || gSM_EntityList.Size < 1)
{
//LogError("Entity lists are empty.");
return INVALID_HANDLE;
}
int hammer = GetNativeCell(1);
char id[MEMBER_SIZE];
IntToString(hammer, id, MEMBER_SIZE);
int position = -1;
if(!gSM_EntityList.GetValue(id, position))
{
//LogError("Could not find entity with that index.");
return INVALID_HANDLE;
}
if(position >= gA_Entites.Length || position < 0)
{
//LogError("List position out of range");
return INVALID_HANDLE;
}
Entity temp;
gA_Entites.GetArray(position, temp);
Entity ent;
CloneEntity(temp, ent, plugin);
ArrayList list = new ArrayList(sizeof(Entity));
list.PushArray(ent);
return list;
}
// native bool GetDumpEntityFromIDAsArray(int hammerid, any[] entity);
public any Native_GetDumpEntityFromIDAsArray(Handle plugin, int numParams)
{
if(!gB_Ready)
{
//LogError("Native called before dump file has been processed.");
return false;
}
if(gA_Entites.Length < 1 || gSM_EntityList.Size < 1)
{
//LogError("Entity lists are empty.");
return false;
}
int hammer = GetNativeCell(1);
char id[MEMBER_SIZE];
IntToString(hammer, id, MEMBER_SIZE);
int position = -1;
if(!gSM_EntityList.GetValue(id, position))
{
//LogError("Could not find entity with that index.");
return false;
}
if(position >= gA_Entites.Length || position < 0)
{
//LogError("List position out of range");
return false;
}
Entity temp;
gA_Entites.GetArray(position, temp);
Entity ent;
CloneEntity(temp, ent, plugin);
return true;
}
// native ArrayList GetDumpEntities();
public any Native_GetDumpEntities(Handle plugin, int numParams)
{
if(!gB_Ready)
{
//LogError("Native called before dump file has been processed.");
return INVALID_HANDLE;
}
if(gA_Entites.Length < 1 || gSM_EntityList.Size < 1)
{
//LogError("Entity lists are empty.");
return INVALID_HANDLE;
}
ArrayList temp = new ArrayList(sizeof(Entity));
for(int i = 0; i < gA_Entites.Length; ++i)
{
Entity original;
gA_Entites.GetArray(i, original);
Entity cloned;
CloneEntity(original, cloned, plugin);
temp.PushArray(cloned);
}
return temp;
}
// native bool IsDumpReady();
public any Native_IsDumpReady(Handle plugin, int numParams)
{
return gB_Ready;
}