@@ -0,0 +1,204 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Net; | |||
using System.Text; | |||
using GamecraftModdingAPI.Commands; | |||
using GamecraftModdingAPI.Utility; | |||
using Svelto.ECS; | |||
using Microsoft.Scripting.Hosting; | |||
using GamecraftScripting.Environment; | |||
using GamecraftScripting.Serialization; | |||
namespace GamecraftScripting.Commands | |||
{ | |||
public class ExecuteCommandEngine : ICustomCommandEngine | |||
{ | |||
public const string URI_IMMEDIATE = "immediate://"; | |||
public const string URI_HTTP = "http://"; | |||
public const string URI_HTTPS = "https://"; | |||
public const string URI_INTERNAL = "internal://"; | |||
public const string URI_BUILTIN = "exmods://"; | |||
public const string URI_FILE = "file://"; | |||
public const string URI_ANSWER = "42://"; | |||
public string Description { get; } = "Execute some Python, intelligently guessing how to execute it"; | |||
public string Name { get; } = "ExecutePython"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public void Dispose() | |||
{ | |||
CommandRegistrationHelper.Unregister(Name); | |||
} | |||
public void Ready() | |||
{ | |||
// TODO command registration | |||
CommandRegistrationHelper.Register<string>(Name, ExecutePythonCommand, Description); | |||
// create insecure Python namespace | |||
ScriptEngine pyEngine = PythonEnvironment.CreateEngine(); | |||
ScriptScope pyScope = PythonEnvironment.CreateScope(pyEngine); | |||
PythonEnvironment.CurrentEngine = pyEngine; | |||
PythonEnvironment.CurrentScope = pyScope; | |||
} | |||
private void ExecutePythonCommand(string name) | |||
{ | |||
string uriName = urifyName(name.Trim()); | |||
if (uriName == null) | |||
{ | |||
Logging.CommandLogError("Unable to guess script type. Please add a scheme to the start of the command argument to avoid this. \nValid schemes include: \n" + URI_IMMEDIATE.Replace('/', '\\') + " (for Python code) \n" + URI_FILE.Replace('/', '\\') + " (for Python file) "); | |||
return; | |||
} | |||
string scriptContents = getScript(uriName); | |||
if (scriptContents == null) | |||
{ | |||
Logging.CommandLogError("Invalid script or URI scheme. Please replace the scheme at the start of the command argument. Valid schemes include: \n" + URI_IMMEDIATE.Replace('/', '\\') + "\\\\ (for Python code) \n" + URI_FILE.Replace('/', '\\') + "\\\\ (for Python file) "); | |||
return; | |||
} | |||
Execute(uriName, scriptContents); | |||
} | |||
private string urifyName(string name) | |||
{ | |||
if (name.Contains("://")) return name; // already is a uri, hopefully a valid uri | |||
if (name.Contains(":\\\\")) return name.Replace(":\\\\", "://"); // :\\ is easier to input in CLI | |||
if (!name.EndsWith(".py", StringComparison.InvariantCultureIgnoreCase)) | |||
return URI_IMMEDIATE + name; // probably not a script file, hopefully it's some code | |||
name = name.TrimEnd('\\', '/'); // I still hate that Windows filenames use the escape character as a separator | |||
// search scripts included in game save | |||
ScriptStruct[] integratedScripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint integratedScriptsCount); | |||
for (int i = 0; i < integratedScriptsCount; i++) | |||
{ | |||
if (integratedScripts[i].name == name) | |||
{ | |||
// matching script found in game save | |||
return URI_INTERNAL + name; | |||
} | |||
} | |||
// search scripts on disk | |||
try | |||
{ | |||
FileInfo file = new FileInfo(name); | |||
if (file.Exists) | |||
{ | |||
return URI_FILE + name; | |||
} | |||
} | |||
catch (Exception e) when ( | |||
e is System.Security.SecurityException | |||
|| e is ArgumentException | |||
|| e is UnauthorizedAccessException | |||
|| e is PathTooLongException | |||
|| e is NotSupportedException) | |||
{ } | |||
return null; | |||
} | |||
private string getScheme(string uriName) | |||
{ | |||
string[] splitName = uriName.Split(new string[] { @"://" }, StringSplitOptions.None); | |||
if (splitName.Length == 2) | |||
{ | |||
return splitName[0] + "://"; | |||
} | |||
return null; | |||
} | |||
private string getPath(string uriName) | |||
{ | |||
string[] splitName = uriName.Split(new string[] { @"://" }, StringSplitOptions.None); | |||
if (splitName.Length == 2) | |||
{ | |||
return splitName[1]; | |||
} | |||
return null; | |||
} | |||
private string getScript(string uriName) | |||
{ | |||
string uri_scheme = getScheme(uriName); | |||
if (uri_scheme == null) return null; | |||
string uri_path = getPath(uriName); | |||
if (uri_path == null) return null; | |||
switch (uri_scheme) | |||
{ | |||
case URI_IMMEDIATE: | |||
return uri_path; | |||
case URI_HTTP: // download from web | |||
case URI_HTTPS: | |||
#if DEBUG | |||
Logging.CommandLogWarning("Executing code from the Internet is dangerous! \nWeb scripts are only available in DEVELOPMENT versions of GamecraftScripting."); | |||
try | |||
{ | |||
WebRequest req = WebRequest.Create(uriName); | |||
req.Method = "GET"; | |||
HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); | |||
byte[] body = new byte[int.Parse(resp.GetResponseHeader("Content-Length"))]; | |||
resp.GetResponseStream().Read(body, 0, body.Length); | |||
return Encoding.ASCII.GetString(body); | |||
} | |||
catch (Exception) | |||
{ | |||
return null; | |||
} | |||
#else | |||
Logging.CommandLogError("Executing code from the Internet is dangerous! \nWeb scripts are only available in DEVELOPMENT versions of GamecraftScripting."); | |||
return null; | |||
#endif | |||
case URI_INTERNAL: // found in game file (already deserialized to memory) | |||
ScriptStruct[] internalScripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint scriptCount); | |||
for (int i = 0; i < scriptCount; i++) | |||
{ | |||
if (internalScripts[i].name == uri_path) | |||
{ | |||
return internalScripts[i].script; | |||
} | |||
} | |||
return null; | |||
case URI_BUILTIN: | |||
// TODO: for future implementation | |||
return null; | |||
case URI_FILE: | |||
try | |||
{ | |||
return File.ReadAllText(uri_path); | |||
} | |||
catch (Exception) | |||
{ | |||
return null; | |||
} | |||
case URI_ANSWER: | |||
return "print 42"; | |||
default: | |||
return null; | |||
} | |||
} | |||
private void Execute(string uriName, string script, bool logError = true) | |||
{ | |||
ScriptSource src = PythonEnvironment.CurrentEngine.CreateScriptSourceFromString(script); | |||
try | |||
{ | |||
src.Execute(PythonEnvironment.CurrentScope); | |||
} | |||
catch (Exception e) | |||
{ | |||
if (logError) | |||
{ | |||
Logging.CommandLogError($"Python error in '{uriName}': \n{e.Message}"); | |||
} | |||
else | |||
{ | |||
throw e; | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -9,6 +9,7 @@ using Svelto.ECS; | |||
using Microsoft.Scripting.Hosting; | |||
using GamecraftScripting.Environment; | |||
using GamecraftScripting.Serialization; | |||
namespace GamecraftScripting.Commands | |||
{ | |||
@@ -24,12 +25,16 @@ namespace GamecraftScripting.Commands | |||
{ | |||
CommandRegistrationHelper.Unregister("RunPythonScript"); | |||
CommandRegistrationHelper.Unregister("RunPython"); | |||
CommandRegistrationHelper.Unregister("RunGamePython"); | |||
CommandRegistrationHelper.Unregister("ListGameScripts"); | |||
} | |||
public void Ready() | |||
{ | |||
CommandRegistrationHelper.Register<string>("RunPythonScript", RunPythonScript, "Run a python script stored on your computer"); | |||
CommandRegistrationHelper.Register<string>("RunPython", RunPythonString, "Run a python argument"); | |||
CommandRegistrationHelper.Register<string>("RunGamePython", RunIntegratedScript, "Run a python script stored in the game file"); | |||
CommandRegistrationHelper.Register("ListGameScripts", ListIntegratedScripts, "List python scripts stored in the game file"); | |||
// create insecure Python namespace | |||
ScriptEngine pyEngine = PythonEnvironment.CreateEngine(); | |||
ScriptScope pyScope = PythonEnvironment.CreateScope(pyEngine); | |||
@@ -38,6 +43,37 @@ namespace GamecraftScripting.Commands | |||
//Logging.MetaLog(string.Join(", ", pyEngine.GetSearchPaths().ToArray())); | |||
} | |||
private void ListIntegratedScripts() | |||
{ | |||
string result = ""; | |||
ScriptStruct[] scripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint count); | |||
for (uint i = 0u; i < count; i++) | |||
{ | |||
result += scripts[i].name + " \n"; | |||
} | |||
Logging.CommandLog($"Found {count} integrated script(s) \n{result}"); | |||
} | |||
private void RunIntegratedScript(string scriptName) | |||
{ | |||
if (scriptName == null) return; | |||
if (scriptName.StartsWith("exmods://", StringComparison.InvariantCultureIgnoreCase)) | |||
{ | |||
// TODO: Lookup for built-in scripts | |||
return; | |||
} | |||
ScriptStruct[] scripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint count); | |||
for (uint i = 0u; i < count; i++) | |||
{ | |||
if (scripts[i].name == scriptName) | |||
{ | |||
ExecuteScript(scripts[i].script); | |||
return; | |||
} | |||
} | |||
Logging.CommandLogError("Script not found"); | |||
} | |||
private void RunPythonScript(string scriptPath) | |||
{ | |||
string script = File.ReadAllText(scriptPath); | |||
@@ -0,0 +1,36 @@ | |||
using System; | |||
using System.IO; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Commands; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftScripting.Commands | |||
{ | |||
public class SerializationCommandEngine : ICustomCommandEngine | |||
{ | |||
public string Description => "Save a script into a Gamecraft game file"; | |||
public string Name => "IntegrateScript"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
internal static IEntityFactory entityFactory = null; | |||
public void Dispose() | |||
{ | |||
CommandRegistrationHelper.Unregister(Name); | |||
} | |||
public void Ready() | |||
{ | |||
CommandRegistrationHelper.Register<string>(Name, persistScript, Description); | |||
} | |||
private void persistScript(string filepath) | |||
{ | |||
if (entityFactory == null) return; | |||
EGID scriptId = Serialization.ScriptBuilder.BuildScriptEntity(filepath, File.ReadAllText(filepath), entityFactory); | |||
Logging.MetaLog($"Created persistent script with id {scriptId.entityID}"); | |||
} | |||
} | |||
} |
@@ -90,9 +90,6 @@ | |||
<Reference Include="Havok.Physics.Hybrid"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Havok.Physics.Hybrid.dll</HintPath> | |||
</Reference> | |||
<Reference Include="HdgRemoteDebugRuntime"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\HdgRemoteDebugRuntime.dll</HintPath> | |||
</Reference> | |||
<Reference Include="IllusionInjector"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\IllusionInjector.dll</HintPath> | |||
</Reference> | |||
@@ -123,9 +120,6 @@ | |||
<Reference Include="Rewired_Windows"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Windows.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Robocraft.MainGame.AutoEnterSimulation"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Robocraft.MainGame.AutoEnterSimulation.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.AccountPreferences"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.AccountPreferences.dll</HintPath> | |||
</Reference> | |||
@@ -150,15 +144,9 @@ | |||
<Reference Include="RobocraftX.Crosshair"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Crosshair.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.EntityStreamUtility"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.EntityStreamUtility.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.FrontEnd"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.FrontEnd.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.GameSignalHandling"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GameSignalHandling.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.GUI.DebugDisplay"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.DebugDisplay.dll</HintPath> | |||
</Reference> | |||
@@ -171,18 +159,12 @@ | |||
<Reference Include="RobocraftX.GUI.ScaleGhost"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.ScaleGhost.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.GUI.SignalLabel"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.SignalLabel.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.GUIs.WorkshopPrefabs"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUIs.WorkshopPrefabs.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.Input"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Input.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.Inventory"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Inventory.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.MachineEditor"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MachineEditor.dll</HintPath> | |||
</Reference> | |||
@@ -216,9 +198,6 @@ | |||
<Reference Include="RobocraftX.Player"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Player.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.Priority"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Priority.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.Rendering"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Rendering.dll</HintPath> | |||
</Reference> | |||
@@ -288,12 +267,6 @@ | |||
<Reference Include="Unity.Entities.Hybrid"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.Hybrid.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Unity.Entities.Properties"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.Properties.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Unity.Entities.StaticTypeRegistry"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.StaticTypeRegistry.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Unity.Jobs"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Jobs.dll</HintPath> | |||
</Reference> | |||
@@ -324,9 +297,6 @@ | |||
<Reference Include="Unity.RenderPipelines.Core.ShaderLibrary"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.Core.ShaderLibrary.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Unity.RenderPipelines.Lightweight.Runtime"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.Lightweight.Runtime.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll</HintPath> | |||
</Reference> | |||
@@ -396,9 +366,6 @@ | |||
<Reference Include="UnityEngine.DSPGraphModule"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.DSPGraphModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.FileSystemHttpModule"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.FileSystemHttpModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.GameCenterModule"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.GameCenterModule.dll</HintPath> | |||
</Reference> | |||
@@ -9,6 +9,7 @@ using IllusionPlugin; | |||
using UnityEngine; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Commands; | |||
using Harmony; | |||
namespace GamecraftScripting | |||
{ | |||
@@ -19,24 +20,33 @@ namespace GamecraftScripting | |||
public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; | |||
public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); | |||
private HarmonyInstance harmony = null; | |||
public void OnApplicationQuit() | |||
{ | |||
var currentAssembly = Assembly.GetExecutingAssembly(); | |||
harmony.UnpatchAll(currentAssembly.GetName().Name); | |||
harmony = null; | |||
Main.Shutdown(); | |||
} | |||
public void OnApplicationStart() | |||
{ | |||
Main.Init(); | |||
var currentAssembly = Assembly.GetExecutingAssembly(); | |||
harmony = HarmonyInstance.Create(currentAssembly.GetName().Name); | |||
harmony.PatchAll(currentAssembly); | |||
// register development commands | |||
#if DEBUG | |||
//CommandManager.AddCommand(new SimpleCustomCommandEngine(() => { Application.Quit(); }, "Exit", "Exit the game immediately")); | |||
#endif | |||
// debugging commands | |||
CommandManager.AddCommand(new Commands.DebugCommandEngine()); | |||
// functional commands | |||
CommandManager.AddCommand(new Commands.PythonRunnerCommandEngine()); | |||
CommandManager.AddCommand(new Commands.ExecuteCommandEngine()); | |||
CommandManager.AddCommand(new Commands.SerializationCommandEngine()); | |||
} | |||
public void OnFixedUpdate() { } | |||
@@ -0,0 +1,74 @@ | |||
using System; | |||
using System.Text; | |||
using System.Reflection; | |||
using Harmony; | |||
using RobocraftX.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
[HarmonyPatch] | |||
class DeserializeFromDiskEntitiesEnginePatch | |||
{ | |||
internal static IEntityFactory entityFactory = null; | |||
private static readonly byte[] frameStart = (new UTF8Encoding()).GetBytes("GamecraftScripting"); | |||
public static void Prefix(ref ISerializationData ____serializationData, ref FasterList<byte> ____bytesStream, ref IEntitySerialization ____entitySerializer) | |||
{ | |||
____entitySerializer.RegisterSerializationFactory<ScriptEntityDescriptor>(new ScriptDeserializationFactory(entityFactory)); | |||
uint scriptDataStart = ____serializationData.dataPos; | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {scriptDataStart}"); | |||
BinaryBufferReader bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out uint count), ____serializationData.dataPos); | |||
byte[] frameBuffer = new byte[frameStart.Length]; | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"serial data count: {____serializationData.data.count} capacity: {____serializationData.data.capacity}"); | |||
int i = 0; | |||
// match frame start | |||
while (frameBuffer != frameStart && bbr.Position < count-frameStart.Length) | |||
{ | |||
i = 0; | |||
frameBuffer[0] = bbr.ReadByte(); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"Buffer byte 0: {(new UTF8Encoding()).GetString(new byte[]{frameBuffer[0]})}"); | |||
while (frameBuffer[i] == frameStart[i]) | |||
{ | |||
i++; | |||
if (i == frameStart.Length) break; | |||
frameBuffer[i] = bbr.ReadByte(); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"Buffer byte {i}: {(new UTF8Encoding()).GetString(new byte[] { frameBuffer[i] })}"); | |||
} | |||
if (i == frameStart.Length) break; | |||
} | |||
// abort if at end of file | |||
if (bbr.Position >= count - frameStart.Length) | |||
{ | |||
GamecraftModdingAPI.Utility.Logging.MetaLog("Skipping script deserialization (no frame found)"); | |||
return; | |||
} | |||
____serializationData.dataPos = bbr.Position; | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos (after frame): {____serializationData.dataPos}"); | |||
uint scriptCount = bbr.ReadUint(); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"scriptCount: {scriptCount}"); | |||
uint potentialStart = bbr.ReadUint(); | |||
if (potentialStart == ____serializationData.dataPos - frameStart.Length) return; | |||
____serializationData.dataPos += 4u; | |||
for (uint j = 0; j < scriptCount; j++) | |||
{ | |||
EGID newScriptId = new EGID(j, ScriptBuilder.ScriptGroup); | |||
// why doesn't \/this\/ do anything? | |||
____entitySerializer.DeserializeNewEntity(newScriptId, ____serializationData, SerializationType.Storage); | |||
} | |||
bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out count), ____serializationData.dataPos); | |||
uint actualStart = bbr.ReadUint(); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"actualStart: {actualStart}"); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"Deserialized {scriptCount} scripts starting at {scriptDataStart} ({actualStart})"); | |||
____serializationData.dataPos = scriptDataStart; // change back to original end point (just in case) | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return AccessTools.Method("RobocraftX.SaveAndLoad.DeserializeFromDiskEntitiesEngine:LoadingFinished");//AccessTools.TypeByName("RobocraftX.SaveAndLoad.DeserializeFromDiskEntities") | |||
} | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using System; | |||
using Harmony; | |||
using RobocraftX.SaveAndLoad; | |||
using Svelto.ECS; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
[HarmonyPatch(typeof(SaveAndLoadCompositionRoot), "Compose")] | |||
class SaveAndLoadCompositionRootPatch | |||
{ | |||
public static void Prefix(EnginesRoot enginesRoot) | |||
{ | |||
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); | |||
DeserializeFromDiskEntitiesEnginePatch.entityFactory = factory; | |||
Commands.SerializationCommandEngine.entityFactory = factory; | |||
} | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
using System; | |||
using System.Text; | |||
using System.Reflection; | |||
using Harmony; | |||
using RobocraftX.Common; | |||
using RobocraftX.SaveAndLoad; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
[HarmonyPatch] | |||
class SaveGameEnginePatch | |||
{ | |||
private static readonly byte[] frameStart = (new UTF8Encoding()).GetBytes("GamecraftScripting"); | |||
public static void Postfix(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer) | |||
{ | |||
Logging.MetaLog("Running Postfix on game save serializer"); | |||
serializationData.data.ExpandBy((uint)frameStart.Length); | |||
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint buffLen), serializationData.dataPos); | |||
ScriptStruct[] scripts = entitiesDB.QueryEntities<ScriptStruct>(ScriptBuilder.ScriptGroup).ToFastAccess(out uint count); | |||
uint scriptDataStart = serializationData.dataPos; | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {scriptDataStart}"); | |||
for (int i = 0; i < frameStart.Length; i++) | |||
{ | |||
bbw.Write(frameStart[i]); | |||
} | |||
serializationData.dataPos += (uint)frameStart.Length; | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos (after frame start): {serializationData.dataPos}"); | |||
serializationData.data.ExpandBy(4u); | |||
Logging.MetaLog($"scriptCount: {count}"); | |||
bbw.Write(count); | |||
serializationData.dataPos += 4u; | |||
//foreach (byte b in BitConverter.GetBytes(count)) serializationData.data.Add(b); | |||
for (uint i = 0; i < count; i++) | |||
{ | |||
EGID scriptId = new EGID(i, ScriptBuilder.ScriptGroup); | |||
entitySerializer.SerializeEntity(scriptId, serializationData, SerializationType.Storage); | |||
} | |||
bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out buffLen), serializationData.dataPos); | |||
serializationData.data.ExpandBy(4u); | |||
bbw.Write(scriptDataStart); | |||
serializationData.dataPos += 4u; | |||
//foreach (byte b in BitConverter.GetBytes(scriptDataStart)) serializationData.data.Add(b); | |||
serializationData.data.Trim(); | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return typeof(SaveGameEngine).GetMethod("SerializeGameToBuffer"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Experimental; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
public static class ScriptBuilder | |||
{ | |||
public static readonly ExclusiveGroup ScriptGroup = new ExclusiveGroup("SCRIPT_GROUP"); | |||
private static uint nextScriptId = 0u; | |||
public static EGID BuildScriptEntity(string name, string script, IEntityFactory entityFactory) | |||
{ | |||
EGID scriptId = new EGID(nextScriptId++, ScriptGroup); | |||
EntityStructInitializer builder = entityFactory.BuildEntity<ScriptEntityDescriptor>(scriptId); | |||
builder.Init(new ScriptStruct { | |||
name = new ECSString(name), | |||
script = new ECSString(script), | |||
}); | |||
return scriptId; | |||
} | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
public class ScriptDeserializationFactory : IDeserializationFactory | |||
{ | |||
private IEntityFactory entityFactory; | |||
public ScriptDeserializationFactory(IEntityFactory entityFactory) | |||
{ | |||
this.entityFactory = entityFactory; | |||
} | |||
public EntityStructInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, SerializationType serializationType, IEntitySerialization entitySerialization) | |||
{ | |||
Logging.MetaLog(entityFactory == null); | |||
EntityStructInitializer esi = entityFactory.BuildEntity<ScriptEntityDescriptor>(egid); | |||
entitySerialization.DeserializeEntityStructs(serializationData, entityDescriptor, ref esi, serializationType); | |||
Logging.MetaLog($"Deserialized script named {esi.Get<ScriptStruct>().name.ToString()}"); | |||
return esi; | |||
} | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
public class ScriptEntityDescriptor : SerializableEntityDescriptor<ScriptEntityDescriptor._ScriptEntityDescriptor> | |||
{ | |||
[HashName("GamecraftScriptingScriptEntityDescriptorV0")] | |||
public class _ScriptEntityDescriptor : IEntityDescriptor | |||
{ | |||
public IEntityBuilder[] entitiesToBuild => _entityBuilders; | |||
private static readonly IEntityBuilder[] _entityBuilders = new IEntityBuilder[1] | |||
{ | |||
new SerializableEntityBuilder<ScriptStruct>((SerializationType.Network, new ScriptSerializer()), | |||
(SerializationType.Storage, new ScriptSerializer())) | |||
}; | |||
} | |||
public ScriptEntityDescriptor() : base() | |||
{ | |||
GamecraftModdingAPI.Utility.Logging.MetaLog("ScriptEntityDescriptor Initialized"); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"Entities to serialize: {entitiesToSerialize.Length}"); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"Entities to serialize: {entitiesToBuild.Length}"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
using System; | |||
using System.Text; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
public class ScriptSerializer : ISerializer<ScriptStruct> | |||
{ | |||
private static readonly UTF8Encoding utf8Encoding = new UTF8Encoding(); | |||
private static readonly uint padding = 42u; | |||
public uint size => 0u; | |||
public bool Deserialize(ref ScriptStruct value, ISerializationData serializationData) | |||
{ | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {serializationData.dataPos}"); | |||
BinaryBufferReader bbr = new BinaryBufferReader(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos); | |||
short nameLength = bbr.ReadShort(); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"nameLength: {nameLength}"); | |||
byte[] serialName = new byte[nameLength]; | |||
bbr.ReadBytes(serialName, (uint)nameLength); | |||
value.name.Set(utf8Encoding.GetString(serialName)); | |||
int scriptLength = bbr.ReadInt(); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"scriptLength: {scriptLength}"); | |||
byte[] serialScript = new byte[scriptLength]; | |||
bbr.ReadBytes(serialScript, (uint)scriptLength); | |||
value.script.Set(utf8Encoding.GetString(serialScript)); | |||
uint serialPadding = bbr.ReadUint(); | |||
serializationData.dataPos += (uint)(2 + nameLength + 4 + scriptLength + 4); | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"Deserializing Script successful: {serialPadding == padding}"); | |||
return true; | |||
} | |||
public bool Serialize(in ScriptStruct value, ISerializationData serializationData) | |||
{ | |||
GamecraftModdingAPI.Utility.Logging.MetaLog($"dataPos: {serializationData.dataPos}"); | |||
byte[] serialName = utf8Encoding.GetBytes(value.name); | |||
byte[] serialScript = utf8Encoding.GetBytes(value.script); | |||
uint actualSize = (uint)(2 + serialName.Length + 4 + serialScript.Length + 4); | |||
serializationData.data.ExpandBy(actualSize); | |||
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos); | |||
bbw.Write((short)serialName.Length); | |||
for (int i = 0; i < serialName.Length; i++) | |||
{ | |||
bbw.Write(serialName[i]); | |||
} | |||
bbw.Write((int)serialScript.Length); | |||
for (int i = 0; i < serialScript.Length; i++) | |||
{ | |||
bbw.Write(serialScript[i]); | |||
} | |||
bbw.Write(padding); | |||
serializationData.dataPos += actualSize; | |||
return true; | |||
} | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Experimental; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
public struct ScriptStruct : IEntityStruct | |||
{ | |||
public ECSString name; | |||
public ECSString script; | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
using System; | |||
using System.Reflection; | |||
using Harmony; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftScripting.Serialization | |||
{ | |||
#if DEBUG | |||
[HarmonyPatch] | |||
public class SerializationDescriptorMapPatch | |||
{ | |||
public static void Prefix(ISerializableEntityDescriptor descriptor) | |||
{ | |||
if (descriptor.entitiesToSerialize.Length == 0) | |||
{ | |||
if (descriptor.entitiesToBuild.Length != 0) | |||
{ | |||
Logging.MetaLog($"Descriptor: {descriptor.entitiesToBuild[0].GetType().FullName} (hash:{descriptor.hash})"); | |||
} | |||
else | |||
{ | |||
Logging.MetaLog($"Emtpy descriptor (hash: {descriptor.hash})"); | |||
} | |||
} | |||
else | |||
{ | |||
Logging.MetaLog($"Descriptor: {descriptor.entitiesToSerialize[0].GetType().FullName} (hash:{descriptor.hash}) (serializables: {descriptor.entitiesToSerialize.Length})"); | |||
} | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
//Logging.MetaLog(AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "RegisterEntityDescriptor") == null); | |||
return AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "RegisterEntityDescriptor"); | |||
} | |||
} | |||
[HarmonyPatch] | |||
public class SerializationDescriptorMapGetPatch | |||
{ | |||
public static void Prefix(uint descriptorID) | |||
{ | |||
if (descriptorID == 3520129338u) | |||
{ | |||
Logging.MetaLog("Got hash for ScriptStruct"); | |||
} | |||
Logging.MetaLog($"GetDescriptorFromHash({descriptorID})"); | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
//Logging.MetaLog(AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "RegisterEntityDescriptor") == null); | |||
return AccessTools.Method(AccessTools.Inner(AccessTools.TypeByName("Svelto.ECS.EnginesRoot"), "SerializationDescriptorMap"), "GetDescriptorFromHash"); | |||
} | |||
} | |||
#endif | |||
} |