Skip to content

Instantly share code, notes, and snippets.

@wrdg
Last active February 6, 2024 21:11
Show Gist options
  • Save wrdg/1893dd2e24faed933d6f643f8078fa78 to your computer and use it in GitHub Desktop.
Save wrdg/1893dd2e24faed933d6f643f8078fa78 to your computer and use it in GitHub Desktop.
DayZ SA Enforce runtime script execution

DayZ SA Runtime Script Execution

Allows for runtime script execution for DayZ SA through loading new script modules. There are two methods that can be used, both for convenience, fire and forget method style execution and complex instance execution. This script is public, and will most likely be used in various different mods/addons, so renaming would need to be considered to prevent collision. This is currently setup for execution on the Mission script module, mainly for simplicity and to allow for access to all vanilla script modules.

Execution only happens on the side you execute on! If you execute on script on server it will only effect the server, and does not propagate to clients! You will need RPCs to pass anything to clients.

Usage

Using the fire and forget method might require you to use escape characters if you're executing programatically, if you're reading from a EditBoxWidget or MultilineEditBoxWidget, you should not have to escape characters. Note, that the # character can not be used, as it's used for string translation and can NOT be escaped!

Example of method style execution:

BashExecutor.ExecuteEnf("Print(\"Hello World!\");");

Using file execution can be a bit more complexed, as it allows you to have instances of new types which can allow for more complexed code. You must store the ScriptModule and output class if you're wanting to hold the instance to use it in other methods, or else the reference counter will clean up the instance. Refer to 1_Core/proto/EnScript.c class ScriptModule for more information on CallFunction and CallFunctionParams.

Example of instance execution:

modded class MissionServer
{
    // strong ref to prevent the ref counter clean up
    protected ref ScriptModule m_CustomBashModule;
    protected ref Class m_CustomBashInstance;

    override void OnMissionStart()
    {
        super.OnMissionStart(); // propagate hierarchy

        // load our module and intialize
        m_CustomBashModule = BashExecutor.ExecuteEnf_File("$profile:PathToCustomScript.c", m_CustomBashInstance);
    }

    override void InvokeOnConnect(PlayerBase player, PlayerIdentity identity)
    {
        super.InvokeOnConnect(player, identity); // propagate hierarchy

        // execute method called PlayerConnected with parameter of PlayerBase
        m_CustomBashModule.CallFunction(m_CustomBashInstance, "PlayerConnected", null, player);
    }
}
class CustomPlayerConnected
{
    private static ref CustomPlayerConnected s_Instance;

    void Init() {}

    void PlayerConnected(PlayerBase player)
    {
        Print("A new player connected! " + player);
    }

    static CustomPlayerConnected GetInstance()
    {
        if (!s_Instance)
        {
            s_Instance = new CustomPlayerConnected();
        }

        return s_Instance;
    }
}

Additional Info

DaOne helped quite a bit with the initial idea, as it's being used similarly in VPP Admin Tools. If you have any issues or questions, I'm sorry, neither me nor DaOne are going to be providing support on how to use this, the above documentation should suffice. If you're having a hard time understanding or using this, then you probably do not need it. This gist is subject to change.

class BashExecutor
{
// fire and forget, method style execution
static void ExecuteEnf(string code)
{
FileHandle file;
MakeDirectory("$profile:Bash/"); // create directory for logging and execution purposes
string script_path = string.Format("$profile:Bash/script_%1.c", GetDate());
file = OpenFile(script_path, FileMode.WRITE);
if (file)
{
string ex_script = "static void main()\n{\n" + code + "\n}";
FPrintln(file, ex_script);
CloseFile(file);
}
// execute at highest module vanilla script module
ScriptModule bash_module = ScriptModule.LoadScript(GetGame().GetMission().MissionScript, script_path, true);
if (!bash_module)
{
Print("[SCRIPT EXEC] Invalid syntax! Review your script.");
return;
}
bash_module.Call(null, "main", null); // execute
}
// uses BashScriptTemplate
static ScriptModule ExecuteEnf_File(string path, out Class instance)
{
if (!FileExist(path))
{
Print("[SCRIPT EXEC] Unable to find file at path " + path);
return null;
}
// execute at highest script module
ScriptModule bash_module = ScriptModule.LoadScript(GetGame().GetMission().MissionScript, path, true);
if (!bash_module)
{
if (!bash_module)
{
Print("[SCRIPT EXEC] Invalid syntax! Review your script.");
return null;
}
}
bash_module.CallFunction(null, "GetInstance", instance, null); // get our instance, so we can reference it (return ScriptModule needs to be stored! store as a member and mark it as strong ref)
bash_module.CallFunction(instance, "Init", null, null); // call the main function (not necessary but quick instance execution)
return bash_module; // return our script module
}
private static string GetDate()
{
int year, month, day;
int hour, minute, second;
GetYearMonthDay(year, month, day);
GetHourMinuteSecond(hour, minute, second);
return year.ToStringLen(4) + "-" + month.ToStringLen(2) + "-" + day.ToStringLen(2) + "_" + hour.ToStringLen(2) + minute.ToStringLen(2) + second.ToStringLen(2);
}
}
class BashScript
{
private static ref BashScript s_Instance; //! DO NOT REMOVE
void BashScript()
{
// can have a constructor
}
void ~BashScript()
{
// can have a destructor
}
//* entry method
void Init()
{
// intialization code can go here
}
//! DO NOT REMOVE
static BashScript GetInstance()
{
if (!s_Instance)
{
s_Instance = new BashScript();
}
return s_Instance;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment