@@ -48,6 +48,7 @@ namespace GamecraftModdingAPI | |||
// init utility | |||
Logging.MetaDebugLog($"Initializing Utility"); | |||
Utility.GameState.Init(); | |||
Utility.VersionTracking.Init(); | |||
// create default event emitters | |||
Logging.MetaDebugLog($"Initializing Events"); | |||
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.ApplicationInitialized, "GamecraftModdingAPIApplicationInitializedEventEmitter", false)); | |||
@@ -0,0 +1,87 @@ | |||
using System; | |||
using System.Text; | |||
using System.Reflection; | |||
using RobocraftX.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using Harmony; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Persistence | |||
{ | |||
[HarmonyPatch] | |||
class DeserializeFromDiskEntitiesEnginePatch | |||
{ | |||
internal static EntitiesDB entitiesDB = null; | |||
private static readonly byte[] frameStart = Encoding.UTF8.GetBytes("\0\0\0GamecraftModdingAPI\0\0\0"); | |||
public static void Prefix(ref ISerializationData ____serializationData, ref FasterList<byte> ____bytesStream, ref IEntitySerialization ____entitySerializer, bool ____spawnBlocksOnly) | |||
{ | |||
if (____spawnBlocksOnly) return; // only run after second deserialization call (when all vanilla stuff is already deserialized) | |||
uint originalPos = ____serializationData.dataPos; | |||
Logging.MetaDebugLog($"dataPos: {originalPos}"); | |||
BinaryBufferReader bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out uint count), ____serializationData.dataPos); | |||
byte[] frameBuffer = new byte[frameStart.Length]; | |||
Logging.MetaDebugLog($"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(); | |||
while (frameBuffer[i] == frameStart[i] && bbr.Position < count - frameStart.Length + i) | |||
{ | |||
i++; | |||
if (i == frameStart.Length) break; | |||
frameBuffer[i] = bbr.ReadByte(); | |||
} | |||
if (i == frameStart.Length) break; | |||
} | |||
// abort if at end of file | |||
if (bbr.Position >= count - frameStart.Length) | |||
{ | |||
Logging.MetaLog("Skipping deserialization (no frame found)"); | |||
return; | |||
} | |||
//____serializationData.dataPos = bbr.Position; | |||
Logging.MetaDebugLog($"dataPos (after frame): {bbr.Position}"); | |||
uint customComponentsCount = bbr.ReadUint(); | |||
for (uint c = 0; c < customComponentsCount; c++) | |||
{ | |||
// determine component from info | |||
uint nameLength = bbr.ReadUint(); | |||
byte[] nameBytes = new byte[nameLength]; | |||
bbr.ReadBytes(nameBytes, nameLength); | |||
string name = Encoding.UTF8.GetString(nameBytes); | |||
Logging.MetaDebugLog($"Component name: {name} (len: {nameLength})"); | |||
uint componentEnd = bbr.ReadUint(); | |||
____serializationData.dataPos = bbr.Position; | |||
if (SerializerManager.ExistsSerializer(name)) | |||
{ | |||
// deserialize component | |||
IEntitySerializer serial = SerializerManager.GetSerializer(name); | |||
if (!serial.Deserialize(ref ____serializationData, ____entitySerializer)) | |||
{ | |||
Logging.MetaDebugLog("Component deserialization failed!"); | |||
} | |||
} | |||
else | |||
{ | |||
Logging.MetaDebugLog("Skipping component deserialization: not found!"); | |||
} | |||
bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out count), componentEnd); | |||
} | |||
____serializationData.dataPos = originalPos; // change back to original end point (just in case) | |||
Logging.MetaDebugLog("Deserialization complete"); | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return AccessTools.Method("RobocraftX.SaveAndLoad.DeserializeFromDiskEntitiesEngine:LoadingFinished");//AccessTools.TypeByName("RobocraftX.SaveAndLoad.DeserializeFromDiskEntities") | |||
} | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Persistence | |||
{ | |||
public interface IEntitySerializer : IDeserializationFactory, IQueryingEntitiesEngine | |||
{ | |||
IEntityFactory EntityFactory { set; } | |||
bool Serialize(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer); | |||
bool Deserialize(ref ISerializationData serializationData, IEntitySerialization entitySerializer); | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
using System; | |||
using RobocraftX.SaveAndLoad; | |||
using Svelto.ECS; | |||
using Harmony; | |||
namespace GamecraftModdingAPI.Persistence | |||
{ | |||
[HarmonyPatch(typeof(SaveAndLoadCompositionRoot), "Compose")] | |||
class SaveAndLoadCompositionRootPatch | |||
{ | |||
public static void Prefix(EnginesRoot enginesRoot) | |||
{ | |||
SerializerManager.RegisterSerializers(enginesRoot); | |||
} | |||
} | |||
} |
@@ -0,0 +1,81 @@ | |||
using System; | |||
using System.Text; | |||
using System.Reflection; | |||
using RobocraftX.Common; | |||
using RobocraftX.SaveAndLoad; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Utility; | |||
using Harmony; | |||
namespace GamecraftModdingAPI.Persistence | |||
{ | |||
[HarmonyPatch] | |||
class SaveGameEnginePatch | |||
{ | |||
private static readonly byte[] frameStart = Encoding.UTF8.GetBytes("\0\0\0GamecraftModdingAPI\0\0\0"); | |||
public static void Postfix(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer) | |||
{ | |||
Logging.MetaDebugLog("Running Postfix on SerializeGameToBuffer: serializing custom components..."); | |||
if (SerializerManager.GetSerializersCount() == 0) | |||
{ | |||
Logging.MetaDebugLog("Skipping component serialization: no serializers registered!"); | |||
return; | |||
} | |||
serializationData.data.ExpandBy((uint)frameStart.Length); | |||
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint buffLen), serializationData.dataPos); | |||
uint originalPos = serializationData.dataPos; | |||
Logging.MetaDebugLog($"dataPos: {originalPos}"); | |||
// Add frame start so it's easier to find GamecraftModdingAPI-serialized components | |||
for (int i = 0; i < frameStart.Length; i++) | |||
{ | |||
bbw.Write(frameStart[i]); | |||
} | |||
Logging.MetaDebugLog($"dataPos (after frame start): {bbw.Position}"); | |||
serializationData.data.ExpandBy(4u); | |||
bbw.Write((uint)SerializerManager.GetSerializersCount()); | |||
string[] serializerKeys = SerializerManager.GetSerializerNames(); | |||
for (uint c = 0; c < serializerKeys.Length; c++) | |||
{ | |||
Logging.MetaDebugLog($"dataPos (loop start): {bbw.Position}"); | |||
// write component info | |||
serializationData.data.ExpandBy(4u + (uint)serializerKeys[c].Length); | |||
bbw.Write((uint)serializerKeys[c].Length); | |||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}"); | |||
byte[] nameBytes = Encoding.UTF8.GetBytes(serializerKeys[c]); | |||
for (int i = 0; i < nameBytes.Length; i++) | |||
{ | |||
bbw.Write(nameBytes[i]); | |||
} | |||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}"); | |||
serializationData.data.ExpandBy(4u); | |||
serializationData.dataPos = bbw.Position + 4u; | |||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}"); | |||
Logging.MetaDebugLog($"dataPos (appears to be): {serializationData.dataPos}"); | |||
// serialize component | |||
IEntitySerializer serializer = SerializerManager.GetSerializer(serializerKeys[c]); | |||
if (!serializer.Serialize(ref serializationData, entitiesDB, entitySerializer)) | |||
{ | |||
Logging.MetaDebugLog("Component serialization failed!"); | |||
} | |||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}"); | |||
bbw.Write((uint)serializationData.dataPos); | |||
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}"); | |||
bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out buffLen), serializationData.dataPos); | |||
Logging.MetaDebugLog($"dataPos (loop end): {bbw.Position}"); | |||
} | |||
serializationData.data.Trim(); | |||
Logging.MetaDebugLog($"dataPos (end): {bbw.Position}"); | |||
Logging.MetaDebugLog("Serialization complete"); | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return typeof(SaveGameEngine).GetMethod("SerializeGameToBuffer"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,72 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Persistence | |||
{ | |||
public static class SerializerManager | |||
{ | |||
private static Dictionary<string, IEntitySerializer> _serializers = new Dictionary<string, IEntitySerializer>(); | |||
private static Dictionary<string, Action<IEntitySerialization>> _registrations = new Dictionary<string, Action<IEntitySerialization>>(); | |||
private static EnginesRoot _lastEnginesRoot; | |||
public static void AddSerializer<T>(IEntitySerializer serializer) where T : ISerializableEntityDescriptor, new() | |||
{ | |||
string name = typeof(T).FullName; | |||
_serializers[name] = serializer; | |||
_registrations[name] = (IEntitySerialization ies) => { ies.RegisterSerializationFactory<T>(serializer); }; | |||
if (_lastEnginesRoot != null) | |||
{ | |||
serializer.EntityFactory = _lastEnginesRoot.GenerateEntityFactory(); | |||
_registrations[name].Invoke(_lastEnginesRoot.GenerateEntitySerializer()); | |||
_lastEnginesRoot.AddEngine(serializer); | |||
} | |||
} | |||
public static bool ExistsSerializer(string name) | |||
{ | |||
return _serializers.ContainsKey(name); | |||
} | |||
public static bool ExistsSerializer<T>(IEntitySerializer serializer) where T : ISerializableEntityDescriptor, new() | |||
{ | |||
return ExistsSerializer(typeof(T).FullName); | |||
} | |||
public static IEntitySerializer GetSerializer(string name) | |||
{ | |||
return _serializers[name]; | |||
} | |||
public static string[] GetSerializerNames() | |||
{ | |||
return _serializers.Keys.ToArray(); | |||
} | |||
public static int GetSerializersCount() | |||
{ | |||
return _serializers.Count; | |||
} | |||
public static void RegisterSerializers(EnginesRoot enginesRoot) | |||
{ | |||
_lastEnginesRoot = enginesRoot; | |||
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); | |||
IEntitySerialization ies = enginesRoot.GenerateEntitySerializer(); | |||
foreach (string key in _serializers.Keys) | |||
{ | |||
Logging.MetaDebugLog($"Registering IEntitySerializer for {key}"); | |||
_serializers[key].EntityFactory = factory; | |||
_registrations[key].Invoke(ies); | |||
enginesRoot.AddEngine(_serializers[key]); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using RobocraftX.Common; | |||
namespace GamecraftModdingAPI.Persistence | |||
{ | |||
public class SimpleEntitySerializer<Descriptor> : IEntitySerializer where Descriptor : ISerializableEntityDescriptor, new() | |||
{ | |||
public delegate EGID[] GetEntitiesToSerialize(EntitiesDB entitiesDB); | |||
private GetEntitiesToSerialize getEntitiesToSerialize; | |||
protected int serializationType; | |||
public IEntityFactory EntityFactory { set; protected get; } | |||
public EntitiesDB entitiesDB { set; protected get; } | |||
public EntityComponentInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, int serializationType, IEntitySerialization entitySerialization) | |||
{ | |||
EntityComponentInitializer esi = EntityFactory.BuildEntity<Descriptor>(egid); | |||
entitySerialization.DeserializeEntityComponents(serializationData, entityDescriptor, ref esi, serializationType); | |||
return esi; | |||
} | |||
public bool Deserialize(ref ISerializationData serializationData, IEntitySerialization entitySerializer) | |||
{ | |||
BinaryBufferReader bbr = new BinaryBufferReader(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos); | |||
uint entityCount = bbr.ReadUint(); | |||
serializationData.dataPos = bbr.Position; | |||
for (uint i = 0; i < entityCount; i++) | |||
{ | |||
entitySerializer.DeserializeEntity(serializationData, serializationType); | |||
} | |||
return true; | |||
} | |||
public void Ready() { } | |||
public bool Serialize(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer) | |||
{ | |||
serializationData.data.ExpandBy(4u); | |||
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos); | |||
EGID[] toSerialize = getEntitiesToSerialize(entitiesDB); | |||
bbw.Write((uint)toSerialize.Length); | |||
serializationData.dataPos = bbw.Position; | |||
for (uint i = 0; i < toSerialize.Length; i++) | |||
{ | |||
entitySerializer.SerializeEntity(toSerialize[i], serializationData, serializationType); | |||
} | |||
return true; | |||
} | |||
public SimpleEntitySerializer(GetEntitiesToSerialize getEntitiesToSerialize) | |||
{ | |||
this.getEntitiesToSerialize = getEntitiesToSerialize; | |||
serializationType = (int)SerializationType.Storage; | |||
} | |||
} | |||
} |
@@ -44,6 +44,7 @@ namespace GamecraftModdingAPI.Tests | |||
FileLog.Reset(); | |||
HarmonyInstance.DEBUG = true; | |||
GamecraftModdingAPI.Main.Init(); | |||
Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}"); | |||
// in case Steam is not installed/running | |||
// this will crash the game slightly later during startup | |||
//SteamInitPatch.ForcePassSteamCheck = true; | |||
@@ -55,6 +56,8 @@ namespace GamecraftModdingAPI.Tests | |||
Logging.MetaDebugLog("Audio Mixers: "+string.Join(",", AudioTools.GetMixers())); | |||
//AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :( | |||
Utility.VersionTracking.Enable(); | |||
// debug/test handlers | |||
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("App Inited event!"); }, () => { }, | |||
EventType.ApplicationInitialized, "appinit API debug")); | |||
@@ -13,5 +13,7 @@ namespace GamecraftModdingAPI.Utility | |||
public static readonly ExclusiveGroup eventsExclusiveGroup = new ExclusiveGroup(); | |||
public static uint eventID; | |||
public static readonly ExclusiveGroup versionGroup = new ExclusiveGroup("GamecraftModdingAPIVersion"); | |||
} | |||
} |
@@ -0,0 +1,96 @@ | |||
using System; | |||
using System.Reflection; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Persistence; | |||
using GamecraftModdingAPI.Events; | |||
namespace GamecraftModdingAPI.Utility | |||
{ | |||
public static class VersionTracking | |||
{ | |||
private static readonly VersionTrackingEngine versionEngine = new VersionTrackingEngine(); | |||
private static bool isEnabled = false; | |||
public static uint GetVersion() | |||
{ | |||
if (!isEnabled) return 0u; | |||
return versionEngine.GetGameVersion(); | |||
} | |||
public static void Enable() | |||
{ | |||
EventManager.AddEventEmitter(versionEngine); | |||
isEnabled = true; | |||
} | |||
public static void Disable() | |||
{ | |||
EventManager.AddEventEmitter(versionEngine); | |||
isEnabled = false; | |||
} | |||
public static void Init() | |||
{ | |||
SerializerManager.AddSerializer<ModVersionDescriptor>(new SimpleEntitySerializer<ModVersionDescriptor>( | |||
(_) => { return new EGID[1] { new EGID(0u, ApiExclusiveGroups.versionGroup) }; } | |||
)); | |||
} | |||
} | |||
internal class VersionTrackingEngine : IEventEmitterEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIVersionTrackingGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public int type => -1; | |||
public bool isRemovable => false; | |||
public IEntityFactory Factory { set; private get; } | |||
public void Dispose() { } | |||
public void Ready() | |||
{ | |||
EGID egid = new EGID(0u, ApiExclusiveGroups.versionGroup); | |||
if (!entitiesDB.Exists<ModVersionStruct>(egid)) | |||
{ | |||
Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version; | |||
int v = (currentVersion.Major * 1000) + (currentVersion.Minor); | |||
Factory.BuildEntity<ModVersionDescriptor>(egid).Init<ModVersionStruct>(new ModVersionStruct | |||
{ | |||
version = (uint)v | |||
}); | |||
} | |||
} | |||
public uint GetGameVersion() | |||
{ | |||
return entitiesDB.QueryUniqueEntity<ModVersionStruct>(ApiExclusiveGroups.versionGroup).version; | |||
} | |||
public void Emit() { } | |||
} | |||
public struct ModVersionStruct : IEntityComponent | |||
{ | |||
public uint version; | |||
} | |||
public class ModVersionDescriptor: SerializableEntityDescriptor<ModVersionDescriptor._ModVersionDescriptor> | |||
{ | |||
[HashName("GamecraftModdingAPIVersionV0")] | |||
public class _ModVersionDescriptor : IEntityDescriptor | |||
{ | |||
public IComponentBuilder[] componentsToBuild { get; } = new IComponentBuilder[]{ | |||
new SerializableComponentBuilder<SerializationType, ModVersionStruct>(((int)SerializationType.Network, new DefaultSerializer<ModVersionStruct>()), ((int)SerializationType.Storage, new DefaultSerializer<ModVersionStruct>())), | |||
}; | |||
} | |||
} | |||
} |