Explorar el Código

Rework serialization

Works pretty much like before but everything has a version.

Serialization is also separated from the objects.

Data made in previous versions of MPS are not compatible.
habeebweeb hace 4 años
padre
commit
ef1a34afcd
Se han modificado 54 ficheros con 1619 adiciones y 906 borrados
  1. 1 48
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/DragPoint/DragPointLight.cs
  2. 11 16
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/DragPoint/DragPointProp.cs
  3. 1 1
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/SceneManagerPanes/SceneManagerScenePane.cs
  4. 3 14
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Windows/MessageWindow.cs
  5. 11 15
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Windows/SceneModalWindow.cs
  6. 70 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/MPSScene.cs
  7. 1 34
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/CameraManager.cs
  8. 1 37
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManager.cs
  9. 0 19
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/BloomEffectManager.cs
  10. 0 16
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/BlurEffectManager.cs
  11. 0 21
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/DepthOfFieldManager.cs
  12. 0 21
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/FogEffectManager.cs
  13. 1 1
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/IEffectManager.cs
  14. 0 9
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/SepiaToneEffectManager.cs
  15. 0 19
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/VignetteEffectManager.cs
  16. 1 37
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EnvironmentManager.cs
  17. 0 8
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/ISerializable.cs
  18. 1 23
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/LightManager.cs
  19. 1 37
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/MeidoManager.cs
  20. 6 24
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/MessageWindowManager.cs
  21. 16 15
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/PropManager.cs
  22. 32 79
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/SceneManager.cs
  23. 9 255
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Meido/Meido.cs
  24. 10 22
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Meido/MeidoDragPointManager.cs
  25. 80 128
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/MeidoPhotoStudio.cs
  26. 10 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/ISerializer.cs
  27. 10 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/ISimpleSerializer.cs
  28. 42 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SceneMetadata.cs
  29. 48 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serialization.cs
  30. 15 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializer.cs
  31. 27 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/AttachPointInfoSerializer.cs
  32. 29 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/CameraInfoSerializer.cs
  33. 41 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/DragPointLightSerializer.cs
  34. 34 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/BloomEffectSerializer.cs
  35. 28 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/BlurEffectSerializer.cs
  36. 36 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/DepthOfFieldEffectSerializer.cs
  37. 36 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/FogEffectSerializer.cs
  38. 24 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/SepiaToneEffectSerializer.cs
  39. 34 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/VignetteEffectSerializer.cs
  40. 33 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/LightPropertySerializer.cs
  41. 46 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/CameraManagerSerializer.cs
  42. 43 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/EffectManagerSerializer.cs
  43. 66 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/EnvironmentManagerSerializer.cs
  44. 43 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/LightManagerSerializer.cs
  45. 86 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/MeidoManagerSerializer.cs
  46. 33 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/MessageWindowManagerSerializer.cs
  47. 69 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/PropManagerSerializer.cs
  48. 277 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/MeidoSerializer.cs
  49. 15 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializer.cs
  50. 66 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/DragPointPropDTOSerializer.cs
  51. 25 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/PoseInfoSerializer.cs
  52. 33 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/PropInfoSerializer.cs
  53. 55 0
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/TransformDTOSerializer.cs
  54. 59 7
      COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Utility.cs

+ 1 - 48
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/DragPoint/DragPointLight.cs

@@ -117,30 +117,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             }
         }
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            foreach (LightProperty lightProperty in LightProperties)
-            {
-                lightProperty.Serialize(binaryWriter);
-            }
-            binaryWriter.WriteVector3(MyObject.position);
-            binaryWriter.Write((int)SelectedLightType);
-            binaryWriter.Write(IsColourMode);
-            binaryWriter.Write(IsDisabled);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            for (int i = 0; i < LightProperties.Length; i++)
-            {
-                LightProperties[i] = LightProperty.Deserialize(binaryReader);
-            }
-            MyObject.position = binaryReader.ReadVector3();
-            SetLightType((MPSLightType)binaryReader.ReadInt32());
-            IsColourMode = binaryReader.ReadBoolean();
-            IsDisabled = binaryReader.ReadBoolean();
-        }
-
         public static void SetLightProperties(Light light, LightProperty prop)
         {
             light.transform.rotation = prop.Rotation;
@@ -279,7 +255,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
     public class LightProperty
     {
-        public static readonly Vector3 DefaultPosition = new Vector3(0f, 1.9f, 0.4f);
+        public static readonly Vector3 DefaultPosition = new(0f, 1.9f, 0.4f);
         public static readonly Quaternion DefaultRotation = Quaternion.Euler(40f, 180f, 0f);
         public Quaternion Rotation { get; set; } = DefaultRotation;
         public float Intensity { get; set; } = 0.95f;
@@ -287,28 +263,5 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public float SpotAngle { get; set; } = 50f;
         public float ShadowStrength { get; set; } = 0.10f;
         public Color LightColour { get; set; } = Color.white;
-
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.WriteQuaternion(Rotation);
-            binaryWriter.Write(Intensity);
-            binaryWriter.Write(Range);
-            binaryWriter.Write(SpotAngle);
-            binaryWriter.Write(ShadowStrength);
-            binaryWriter.WriteColour(LightColour);
-        }
-
-        public static LightProperty Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            return new LightProperty()
-            {
-                Rotation = binaryReader.ReadQuaternion(),
-                Intensity = binaryReader.ReadSingle(),
-                Range = binaryReader.ReadSingle(),
-                SpotAngle = binaryReader.ReadSingle(),
-                ShadowStrength = binaryReader.ReadSingle(),
-                LightColour = binaryReader.ReadColour()
-            };
-        }
     }
 }

+ 11 - 16
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/DragPoint/DragPointProp.cs

@@ -1,7 +1,6 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using HarmonyLib;
 using UnityEngine;
 using UnityEngine.Rendering;
 
@@ -89,30 +88,26 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public enum PropType { Mod, MyRoom, Bg, Odogu }
 
         public PropType Type { get; }
-        public string IconFile { get; set; }
-        public string Filename { get; set; }
-        public string SubFilename { get; set; }
-        public int MyRoomID { get; set; }
+        public string IconFile { get; init; }
+        public string Filename { get; init; }
+        public string SubFilename { get; init; }
+        public int MyRoomID { get; init; }
 
         public PropInfo(PropType type) => Type = type;
 
-        public static PropInfo FromModItem(ModItem modItem)
+        public static PropInfo FromModItem(ModItem modItem) => new(PropType.Mod)
         {
-            var info = new PropInfo(PropType.Mod)
-            {
-                Filename = modItem.IsOfficialMod ? Path.GetFileName(modItem.MenuFile) : modItem.MenuFile
-            };
-            if (modItem.IsOfficialMod) info.SubFilename = modItem.BaseMenuFile;
-            return info;
-        }
+            Filename = modItem.IsOfficialMod ? Path.GetFileName(modItem.MenuFile) : modItem.MenuFile,
+            SubFilename = modItem.BaseMenuFile
+        };
 
-        public static PropInfo FromMyRoom(MyRoomItem myRoomItem) => new PropInfo(PropType.MyRoom)
+        public static PropInfo FromMyRoom(MyRoomItem myRoomItem) => new(PropType.MyRoom)
         {
             MyRoomID = myRoomItem.ID, Filename = myRoomItem.PrefabName
         };
 
-        public static PropInfo FromBg(string name) => new PropInfo(PropType.Bg) { Filename = name };
+        public static PropInfo FromBg(string name) => new(PropType.Bg) { Filename = name };
 
-        public static PropInfo FromGameProp(string name) => new PropInfo(PropType.Odogu) { Filename = name };
+        public static PropInfo FromGameProp(string name) => new(PropType.Odogu) { Filename = name };
     }
 }

+ 1 - 1
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/SceneManagerPanes/SceneManagerScenePane.cs

@@ -62,7 +62,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                     }
                     else if (currentScene < sceneManager.SceneList.Count)
                     {
-                        SceneManager.Scene scene = sceneManager.SceneList[currentScene];
+                        var scene = sceneManager.SceneList[currentScene];
                         if (GUILayout.Button(scene.Thumbnail, sceneImageStyle, sceneLayoutOptions))
                         {
                             sceneManager.SelectScene(currentScene);

+ 3 - 14
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Windows/MessageWindow.cs

@@ -20,7 +20,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             }
         }
         private int fontSize = 25;
-        private bool showingMessage;
 
         public MessageWindow(MessageWindowManager messageWindowManager)
         {
@@ -39,13 +38,9 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             okButton.ControlEvent += ShowMessage;
         }
 
-        public void ToggleVisibility()
+        private void ToggleVisibility()
         {
-            if (showingMessage)
-            {
-                messageWindowManager.CloseMessagePanel();
-                showingMessage = false;
-            }
+            if (messageWindowManager.ShowingMessage) messageWindowManager.CloseMessagePanel();
             else Visible = !Visible;
         }
 
@@ -58,7 +53,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         private void ShowMessage(object sender, EventArgs args)
         {
             Visible = false;
-            showingMessage = true;
             messageWindowManager.ShowMessage(nameTextField.Value, messageTextArea.Value);
         }
 
@@ -87,12 +81,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
         public override void Deactivate()
         {
-            if (showingMessage)
-            {
-                messageWindowManager.CloseMessagePanel();
-                showingMessage = false;
-            }
-
+            messageWindowManager.CloseMessagePanel();
             Visible = false;
         }
     }

+ 11 - 15
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Windows/SceneModalWindow.cs

@@ -94,7 +94,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             // thumbnail
             if (!directoryMode)
             {
-                SceneManager.Scene scene = sceneManager.CurrentScene;
+                MPSScene scene = sceneManager.CurrentScene;
                 Texture2D thumb = scene.Thumbnail;
 
                 float scale = Mathf.Min(
@@ -117,22 +117,18 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
                 Rect labelBox = GUILayoutUtility.GetLastRect();
 
-                if (scene.NumberOfMaids != SceneManager.Scene.initialNumberOfMaids)
-                {
-                    int numberOfMaids = scene.NumberOfMaids;
-                    string infoString = numberOfMaids == MeidoPhotoStudio.kankyoMagic
-                        ? infoKankyo
-                        : string.Format(numberOfMaids == 1 ? infoMaidSingular : infoMaidPlural, numberOfMaids);
+                var infoString = scene.Environment
+                    ? infoKankyo
+                    : string.Format(scene.NumberOfMaids == 1 ? infoMaidSingular : infoMaidPlural, scene.NumberOfMaids);
 
-                    Vector2 labelSize = labelStyle.CalcSize(new GUIContent(infoString));
+                Vector2 labelSize = labelStyle.CalcSize(new GUIContent(infoString));
 
-                    labelBox = new Rect(
-                        labelBox.x + 10, labelBox.y + labelBox.height - (labelSize.y + 10),
-                        labelSize.x + 10, labelSize.y + 2
-                    );
+                labelBox = new Rect(
+                    labelBox.x + 10, labelBox.y + labelBox.height - (labelSize.y + 10),
+                    labelSize.x + 10, labelSize.y + 2
+                );
 
-                    GUI.Label(labelBox, infoString, labelStyle);
-                }
+                GUI.Label(labelBox, infoString, labelStyle);
 
                 GUILayout.FlexibleSpace();
                 GUILayout.EndHorizontal();
@@ -234,7 +230,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                     sceneManager.DeleteScene();
                     deleteScene = false;
                 }
-                else sceneManager.LoadScene();
+                else sceneManager.LoadScene(sceneManager.CurrentScene);
 
                 Modal.Close();
             }

+ 70 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/MPSScene.cs

@@ -0,0 +1,70 @@
+using System.IO;
+using System.Text;
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class MPSScene
+    {
+        public Texture2D Thumbnail { get; }
+        public FileInfo FileInfo { get; }
+        public bool Environment { get; private set; }
+        public int NumberOfMaids { get; private set; }
+
+        private byte[] data;
+
+        public byte[] Data
+        {
+            get
+            {
+                if (data == null) Preload();
+                return data;
+            }
+            private set => data = value;
+        }
+
+        public MPSScene(string path, Texture2D thumbnail = null)
+        {
+            FileInfo = new FileInfo(path);
+
+            if (!thumbnail)
+            {
+                thumbnail = new Texture2D(1, 1, TextureFormat.ARGB32, false);
+                thumbnail.LoadImage(File.ReadAllBytes(FileInfo.FullName));
+            }
+
+            Thumbnail = thumbnail;
+        }
+
+        public void Preload()
+        {
+            if (data != null) return;
+
+            using var fileStream = FileInfo.OpenRead();
+            Utility.SeekPngEnd(fileStream);
+
+            using var memoryStream = new MemoryStream();
+
+            fileStream.CopyTo(memoryStream);
+            memoryStream.Position = 0L;
+
+            using var binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
+
+            var sceneHeader = MeidoPhotoStudio.SceneHeader;
+            if (!Utility.BytesEqual(binaryReader.ReadBytes(sceneHeader.Length), sceneHeader))
+            {
+                Utility.LogWarning($"'{FileInfo.FullName}' is not a MPS Scene");
+                return;
+            }
+
+            (_, Environment, _, NumberOfMaids) = SceneMetadata.ReadMetadata(binaryReader);
+
+            Data = memoryStream.ToArray();
+        }
+
+        public void Destroy()
+        {
+            if (Thumbnail) Object.DestroyImmediate(Thumbnail);
+        }
+    }
+}

+ 1 - 34
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/CameraManager.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.IO;
 using UnityEngine;
 using Object = UnityEngine.Object;
 
@@ -8,7 +6,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 {
     using Input = InputManager;
     using UInput = Input;
-    public class CameraManager : IManager, ISerializable
+    public class CameraManager : IManager
     {
         public const string header = "CAMERA";
         private static readonly CameraMain mainCamera = CameraUtility.MainCamera;
@@ -52,37 +50,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             Activate();
         }
 
-        public void Serialize(BinaryWriter binaryWriter) => Serialize(binaryWriter, false);
-
-        public void Serialize(BinaryWriter binaryWriter, bool kankyo)
-        {
-            binaryWriter.Write(header);
-
-            binaryWriter.Write(kankyo);
-
-            binaryWriter.WriteVector3(mainCamera.GetTargetPos());
-            binaryWriter.Write(mainCamera.GetDistance());
-            binaryWriter.WriteQuaternion(mainCamera.transform.rotation);
-
-            CameraUtility.StopAll();
-        }
-
-        public void Deserialize(BinaryReader binaryReader)
-        {
-            var kankyo = binaryReader.ReadBoolean();
-
-            Vector3 cameraPosition = binaryReader.ReadVector3();
-            var cameraDistance = binaryReader.ReadSingle();
-            Quaternion cameraRotation = binaryReader.ReadQuaternion();
-
-            if (kankyo) return;
-
-            mainCamera.SetTargetPos(cameraPosition);
-            mainCamera.SetDistance(cameraDistance);
-            mainCamera.transform.rotation = cameraRotation;
-            CameraUtility.StopAll();
-        }
-
         public void Activate()
         {
             cameraObject = new GameObject("subCamera");

+ 1 - 37
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManager.cs

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
-    public class EffectManager : IManager, ISerializable
+    public class EffectManager : IManager
     {
         public const string header = "EFFECT";
         public const string footer = "END_EFFECT";
@@ -20,42 +20,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             return manager;
         }
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            foreach (IEffectManager effectManager in EffectManagers.Values) effectManager.Serialize(binaryWriter);
-            binaryWriter.Write(footer);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            string header;
-            while ((header = binaryReader.ReadString()) != footer)
-            {
-                switch (header)
-                {
-                    case BloomEffectManager.header:
-                        Get<BloomEffectManager>().Deserialize(binaryReader);
-                        break;
-                    case DepthOfFieldEffectManager.header:
-                        Get<DepthOfFieldEffectManager>().Deserialize(binaryReader);
-                        break;
-                    case VignetteEffectManager.header:
-                        Get<VignetteEffectManager>().Deserialize(binaryReader);
-                        break;
-                    case FogEffectManager.header:
-                        Get<FogEffectManager>().Deserialize(binaryReader);
-                        break;
-                    case SepiaToneEffectManger.header:
-                        Get<SepiaToneEffectManger>().Deserialize(binaryReader);
-                        break;
-                    case BlurEffectManager.header:
-                        Get<BlurEffectManager>().Deserialize(binaryReader);
-                        break;
-                }
-            }
-        }
-
         public void Activate()
         {
             foreach (IEffectManager effectManager in EffectManagers.Values) effectManager.Activate();

+ 0 - 19
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/BloomEffectManager.cs

@@ -83,25 +83,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
         static BloomEffectManager() => backup_m_fBloomDefIntensity = BloomDefIntensity;
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(BloomValue);
-            binaryWriter.Write(BlurIterations);
-            binaryWriter.WriteColour(BloomThresholdColour);
-            binaryWriter.Write(BloomHDR);
-            binaryWriter.Write(Active);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            BloomValue = binaryReader.ReadSingle();
-            BlurIterations = binaryReader.ReadInt32();
-            BloomThresholdColour = binaryReader.ReadColour();
-            BloomHDR = binaryReader.ReadBoolean();
-            SetEffectActive(binaryReader.ReadBoolean());
-        }
-
         public void Activate()
         {
             if (Bloom == null)

+ 0 - 16
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/BlurEffectManager.cs

@@ -1,6 +1,3 @@
-using System.IO;
-using UnityEngine;
-
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
     public class BlurEffectManager : IEffectManager
@@ -55,19 +52,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             Active = false;
         }
 
-        public void Deserialize(BinaryReader binaryReader)
-        {
-            BlurSize = binaryReader.ReadSingle();
-            SetEffectActive(binaryReader.ReadBoolean());
-        }
-
-        public void Serialize(BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(BlurSize);
-            binaryWriter.Write(Active);
-        }
-
         public void SetEffectActive(bool active)
         {
             if (Blur.enabled = Active = active) BlurSize = BlurSize;

+ 0 - 21
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/DepthOfFieldManager.cs

@@ -41,27 +41,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             set => visualizeFocus = DepthOfField.visualizeFocus = value;
         }
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(FocalLength);
-            binaryWriter.Write(FocalSize);
-            binaryWriter.Write(Aperture);
-            binaryWriter.Write(MaxBlurSize);
-            binaryWriter.Write(VisualizeFocus);
-            binaryWriter.Write(Active);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            FocalLength = binaryReader.ReadSingle();
-            FocalSize = binaryReader.ReadSingle();
-            Aperture = binaryReader.ReadSingle();
-            MaxBlurSize = binaryReader.ReadSingle();
-            VisualizeFocus = binaryReader.ReadBoolean();
-            SetEffectActive(binaryReader.ReadBoolean());
-        }
-
         public void Activate()
         {
             if (DepthOfField == null)

+ 0 - 21
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/FogEffectManager.cs

@@ -71,27 +71,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             set => fogColour = Fog.globalFogColor = value;
         }
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(Distance);
-            binaryWriter.Write(Density);
-            binaryWriter.Write(HeightScale);
-            binaryWriter.Write(Height);
-            binaryWriter.WriteColour(FogColour);
-            binaryWriter.Write(Active);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            Distance = binaryReader.ReadSingle();
-            Density = binaryReader.ReadSingle();
-            HeightScale = binaryReader.ReadSingle();
-            Height = binaryReader.ReadSingle();
-            FogColour = binaryReader.ReadColour();
-            SetEffectActive(binaryReader.ReadBoolean());
-        }
-
         public void Activate()
         {
             if (Fog == null)

+ 1 - 1
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/IEffectManager.cs

@@ -1,6 +1,6 @@
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
-    public interface IEffectManager : IManager, ISerializable
+    public interface IEffectManager : IManager
     {
         bool Ready { get; }
         bool Active { get; }

+ 0 - 9
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/SepiaToneEffectManager.cs

@@ -1,4 +1,3 @@
-using System.IO;
 using UnityEngine;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
@@ -24,14 +23,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
         public void Deactivate() => SetEffectActive(false);
 
-        public void Deserialize(BinaryReader binaryReader) => SetEffectActive(binaryReader.ReadBoolean());
-
-        public void Serialize(BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(Active);
-        }
-
         public void SetEffectActive(bool active) => SepiaTone.enabled = Active = active;
 
         public void Reset() { }

+ 0 - 19
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EffectManagers/VignetteEffectManager.cs

@@ -35,25 +35,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             set => chromaticAberration = Vignette.chromaticAberration = value;
         }
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(Intensity);
-            binaryWriter.Write(Blur);
-            binaryWriter.Write(BlurSpread);
-            binaryWriter.Write(ChromaticAberration);
-            binaryWriter.Write(Active);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            Intensity = binaryReader.ReadSingle();
-            Blur = binaryReader.ReadSingle();
-            BlurSpread = binaryReader.ReadSingle();
-            ChromaticAberration = binaryReader.ReadSingle();
-            SetEffectActive(binaryReader.ReadBoolean());
-        }
-
         public void Activate()
         {
             if (Vignette == null)

+ 1 - 37
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/EnvironmentManager.cs

@@ -1,13 +1,11 @@
 using System;
-using System.Collections.Generic;
-using System.IO;
 using UnityEngine;
 using Object = UnityEngine.Object;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
     using Input = InputManager;
-    public class EnvironmentManager : IManager, ISerializable
+    public class EnvironmentManager : IManager
     {
         private static readonly BgMgr bgMgr = GameMain.Instance.BgMgr;
         public const string header = "ENVIRONMENT";
@@ -97,40 +95,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             CubeActiveChange -= OnCubeActive;
         }
 
-        public void Serialize(BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(CurrentBgAsset);
-            binaryWriter.WriteVector3(bg.position);
-            binaryWriter.WriteQuaternion(bg.rotation);
-            binaryWriter.WriteVector3(bg.localScale);
-        }
-        
-        public void Deserialize(BinaryReader binaryReader) 
-        { 
-            var bgAsset = binaryReader.ReadString();
-            var isCreative = Utility.IsGuidString(bgAsset);
-            List<string> bgList = isCreative
-                ? Constants.MyRoomCustomBGList.ConvertAll(kvp => kvp.Key)
-                : Constants.BGList;
-
-            var assetIndex = bgList.FindIndex(
-                asset => asset.Equals(bgAsset, StringComparison.InvariantCultureIgnoreCase)
-            );
-            if (assetIndex < 0)
-            {
-                Utility.LogWarning($"Could not load BG '{bgAsset}'");
-                isCreative = false;
-                bgAsset = defaultBg;
-            }
-            else bgAsset = bgList[assetIndex];
-
-            ChangeBackground(bgAsset, isCreative);
-            bg.position = binaryReader.ReadVector3();
-            bg.rotation = binaryReader.ReadQuaternion();
-            bg.localScale = binaryReader.ReadVector3();
-        }
-
         public void ChangeBackground(string assetName, bool creative = false)
         {
             if (creative) bgMgr.ChangeBgMyRoom(assetName);

+ 0 - 8
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/ISerializable.cs

@@ -1,8 +0,0 @@
-namespace COM3D2.MeidoPhotoStudio.Plugin
-{
-    public interface ISerializable
-    {
-        void Serialize(System.IO.BinaryWriter binaryWriter);
-        void Deserialize(System.IO.BinaryReader binaryReader);
-    }
-}

+ 1 - 23
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/LightManager.cs

@@ -5,7 +5,7 @@ using UnityEngine;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
-    public class LightManager : IManager, ISerializable
+    public class LightManager : IManager
     {
         public const string header = "LIGHT";
         private static bool cubeActive = true;
@@ -43,28 +43,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
         public LightManager() => Activate();
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(lightList.Count);
-            foreach (DragPointLight light in lightList)
-            {
-                light.Serialize(binaryWriter);
-            }
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            ClearLights();
-            int numberOfLights = binaryReader.ReadInt32();
-            lightList[0].Deserialize(binaryReader);
-            for (int i = 1; i < numberOfLights; i++)
-            {
-                AddLight();
-                lightList[i].Deserialize(binaryReader);
-            }
-        }
-
         public void Activate()
         {
             GameMain.Instance.MainCamera.GetComponent<Camera>().backgroundColor = Color.black;

+ 1 - 37
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/MeidoManager.cs

@@ -1,12 +1,11 @@
 using System;
 using System.Collections.Generic;
-using System.Reflection;
 using System.Linq;
 using UnityEngine;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
-    public class MeidoManager : IManager, ISerializable
+    public class MeidoManager : IManager
     {
         public const string header = "MEIDO";
         private static readonly CharacterMgr characterMgr = GameMain.Instance.CharacterMgr;
@@ -137,41 +136,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             if (InputManager.GetKeyDown(MpsKey.MeidoUndressing)) UndressAll();
         }
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            // Only true for MM scenes converted to MPS scenes
-            binaryWriter.Write(false);
-            binaryWriter.Write(Meido.meidoDataVersion);
-            binaryWriter.Write(ActiveMeidoList.Count);
-            foreach (Meido meido in ActiveMeidoList)
-            {
-                meido.Serialize(binaryWriter);
-            }
-            // Global hair/skirt gravity
-            binaryWriter.Write(GlobalGravity);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            bool isMMScene = binaryReader.ReadBoolean();
-            int dataVersion = binaryReader.ReadInt32();
-            int numberOfMaids = binaryReader.ReadInt32();
-            for (int i = 0; i < numberOfMaids; i++)
-            {
-                if (i >= ActiveMeidoList.Count)
-                {
-                    long skip = binaryReader.ReadInt64(); // meido buffer length
-                    binaryReader.BaseStream.Seek(skip, System.IO.SeekOrigin.Current);
-                    continue;
-                }
-                Meido meido = ActiveMeidoList[i];
-                meido.Deserialize(binaryReader, dataVersion, isMMScene);
-            }
-            // Global hair/skirt gravity
-            GlobalGravity = binaryReader.ReadBoolean();
-        }
-
         private void UnloadMeidos()
         {
             SelectedMeido = 0;

+ 6 - 24
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/MessageWindowManager.cs

@@ -2,7 +2,7 @@ using UnityEngine;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
-    public class MessageWindowManager : IManager, ISerializable
+    public class MessageWindowManager : IManager
     {
         public const string header = "TEXTBOX";
         public static readonly SliderProp fontBounds = new SliderProp(25f, 60f);
@@ -13,8 +13,9 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         private readonly UILabel nameLabel;
         private readonly GameObject msgGameObject;
         public bool ShowingMessage { get; private set; }
-        private string messageName = string.Empty;
-        private string messageText = string.Empty;
+        public string MessageName { get; private set; } = string.Empty;
+        public string MessageText { get; private set; } = string.Empty;
+
         public int FontSize
         {
             get => msgLabel.fontSize;
@@ -49,25 +50,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             SetPhotoMessageWindowActive(false);
         }
 
-        public void Serialize(System.IO.BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(header);
-            binaryWriter.Write(ShowingMessage);
-            binaryWriter.Write(FontSize);
-            binaryWriter.WriteNullableString(messageName);
-            binaryWriter.WriteNullableString(messageText);
-        }
-
-        public void Deserialize(System.IO.BinaryReader binaryReader)
-        {
-            CloseMessagePanel();
-            var showingMessage = binaryReader.ReadBoolean();
-            FontSize = binaryReader.ReadInt32();
-            messageName = binaryReader.ReadNullableString();
-            messageText = binaryReader.ReadNullableString();
-            if (showingMessage) ShowMessage(messageName, messageText);
-        }
-
         public void Update() { }
 
         private void SetPhotoMessageWindowActive(bool active)
@@ -100,8 +82,8 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
         public void ShowMessage(string name, string message)
         {
-            messageName = name;
-            messageText = message;
+            MessageName = name;
+            MessageText = message;
             ShowingMessage = true;
             msgWnd.OpenMessageWindowPanel();
             msgLabel.ProcessText();

+ 16 - 15
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/PropManager.cs

@@ -10,7 +10,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 {
     using static ModelUtility;
 
-    public class PropManager : IManager, ISerializable
+    public class PropManager : IManager
     {
         public const string header = "PROP";
         private static readonly ConfigEntry<bool> modItemsOnly;
@@ -116,7 +116,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             Activate();
         }
 
-        public void AddFromPropInfo(PropInfo propInfo)
+        public bool AddFromPropInfo(PropInfo propInfo)
         {
             switch (propInfo.Type)
             {
@@ -127,20 +127,15 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                         modItem = ModItem.OfficialMod(ModFileToFullPath[propInfo.Filename]);
                         modItem.BaseMenuFile = propInfo.SubFilename;
                     }
-                    else
-                        modItem = ModItem.Mod(propInfo.Filename);
+                    else modItem = ModItem.Mod(propInfo.Filename);
 
-                    AddModProp(modItem);
-                    break;
+                    return AddModProp(modItem);
                 case PropInfo.PropType.MyRoom:
-                    AddMyRoomProp(new MyRoomItem { ID = propInfo.MyRoomID, PrefabName = propInfo.Filename });
-                    break;
+                    return AddMyRoomProp(new MyRoomItem { ID = propInfo.MyRoomID, PrefabName = propInfo.Filename });
                 case PropInfo.PropType.Bg:
-                    AddBgProp(propInfo.Filename);
-                    break;
+                    return AddBgProp(propInfo.Filename);
                 case PropInfo.PropType.Odogu:
-                    AddGameProp(propInfo.Filename);
-                    break;
+                    return AddGameProp(propInfo.Filename);
                 default: throw new ArgumentOutOfRangeException();
             }
         }
@@ -233,6 +228,15 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             EmitPropListChange();
         }
 
+        public void AttachProp(DragPointProp prop, AttachPoint point, int index)
+        {
+            if ((uint) index >= (uint) meidoManager.ActiveMeidoList.Count) return;
+
+            var meido = meidoManager.ActiveMeidoList[index];
+
+            prop.AttachTo(meido, point);
+        }
+
         private DragPointProp AttachDragPoint(GameObject model)
         {
             var dragPoint = DragPoint.Make<DragPointProp>(PrimitiveType.Cube, Vector3.one * 0.12f);
@@ -312,8 +316,5 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             CubeSmallChange -= OnCubeSmall;
             CubeActiveChange -= OnCubeActive;
         }
-
-        public void Serialize(BinaryWriter binaryWriter) => Utility.LogMessage("no prop serialization :(");
-        public void Deserialize(BinaryReader binaryReader) => Utility.LogMessage("no prop deserialization :(");
     }
 }

+ 32 - 79
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Managers/SceneManager.cs

@@ -4,13 +4,15 @@ using System.Collections.Generic;
 using System.Linq;
 using UnityEngine;
 using BepInEx.Configuration;
-using System.Text;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
     using Input = InputManager;
     public class SceneManager : IManager
     {
+
+        private static byte[] tempSceneData;
+        private static string TempScenePath => Path.Combine(Constants.configPath, "mpstempscene");
         public static bool Busy { get; private set; }
         public static readonly Vector2 sceneDimensions = new Vector2(480, 270);
         private static readonly ConfigEntry<bool> sortDescending;
@@ -24,7 +26,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             get => sortDescending.Value;
             set => sortDescending.Value = value;
         }
-        public List<Scene> SceneList { get; } = new List<Scene>();
+        public List<MPSScene> SceneList { get; } = new();
         public int CurrentDirectoryIndex { get; private set; } = -1;
         public string CurrentDirectoryName => CurrentDirectoryList[CurrentDirectoryIndex];
         public List<string> CurrentDirectoryList
@@ -38,7 +40,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             private set => currentSortMode.Value = value;
         }
         public int CurrentSceneIndex { get; private set; } = -1;
-        public Scene CurrentScene => SceneList.Count == 0 ? null : SceneList[CurrentSceneIndex];
+        public MPSScene CurrentScene => SceneList.Count == 0 ? null : SceneList[CurrentSceneIndex];
         public enum SortMode
         {
             Name, DateCreated, DateModified
@@ -141,7 +143,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public void SelectScene(int sceneIndex)
         {
             CurrentSceneIndex = Mathf.Clamp(sceneIndex, 0, SceneList.Count - 1);
-            CurrentScene.GetNumberOfMaids();
+            CurrentScene.Preload();
         }
 
         public void AddDirectory(string directoryName)
@@ -183,7 +185,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public void SortScenes(SortMode sortMode)
         {
             CurrentSortMode = sortMode;
-            Comparison<Scene> comparator = CurrentSortMode switch
+            Comparison<MPSScene> comparator = CurrentSortMode switch
             {
                 SortMode.DateModified => SortByDateModified,
                 SortMode.DateCreated => SortByDateCreated,
@@ -202,22 +204,19 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             CurrentSceneIndex = Mathf.Clamp(CurrentSceneIndex, 0, SceneList.Count - 1);
         }
 
-        public void LoadScene()
-        {
-            meidoPhotoStudio.ApplyScene(CurrentScene.FileInfo.FullName);
-        }
+        public void LoadScene(MPSScene scene) => meidoPhotoStudio.LoadScene(scene.Data);
 
-        private int SortByName(Scene a, Scene b)
+        private int SortByName(MPSScene a, MPSScene b)
         {
             return SortDirection * LexicographicStringComparer.Comparison(a.FileInfo.Name, b.FileInfo.Name);
         }
 
-        private int SortByDateCreated(Scene a, Scene b)
+        private int SortByDateCreated(MPSScene a, MPSScene b)
         {
             return SortDirection * DateTime.Compare(a.FileInfo.CreationTime, b.FileInfo.CreationTime);
         }
 
-        private int SortByDateModified(Scene a, Scene b)
+        private int SortByDateModified(MPSScene a, MPSScene b)
         {
             return SortDirection * DateTime.Compare(a.FileInfo.LastWriteTime, b.FileInfo.LastWriteTime);
         }
@@ -233,7 +232,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
             foreach (string filename in Directory.GetFiles(CurrentScenesDirectory))
             {
-                if (Path.GetExtension(filename) == ".png") SceneList.Add(new Scene(filename));
+                if (Path.GetExtension(filename) == ".png") SceneList.Add(new MPSScene(filename));
             }
 
             SortScenes(CurrentSortMode);
@@ -245,34 +244,46 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         {
             string baseDirectoryName = KankyoMode ? Constants.kankyoDirectory : Constants.sceneDirectory;
             CurrentDirectoryList.Sort((a, b)
-                => a.Equals(baseDirectoryName, StringComparison.InvariantCultureIgnoreCase) ? -1 : a.CompareTo(b));
+                => a.Equals(baseDirectoryName, StringComparison.InvariantCultureIgnoreCase) 
+                    ? -1 : LexicographicStringComparer.Comparison(a, b));
         }
 
         private void ClearSceneList()
         {
-            foreach (Scene scene in SceneList) scene.Destroy();
+            foreach (MPSScene scene in SceneList) scene.Destroy();
             SceneList.Clear();
         }
 
         private void QuickSaveScene()
         {
             if (Busy) return;
-            byte[] data = meidoPhotoStudio.SerializeScene(kankyo: false);
+            
+            var data = meidoPhotoStudio.SaveScene();
             if (data == null) return;
-            File.WriteAllBytes(Path.Combine(Constants.configPath, "mpstempscene"), data);
+
+            tempSceneData = data;
+            
+            File.WriteAllBytes(TempScenePath, data);
         }
 
         private void QuickLoadScene()
         {
             if (Busy) return;
-            meidoPhotoStudio.ApplyScene(Path.Combine(Constants.configPath, "mpstempscene"));
+
+            if (tempSceneData == null)
+            {
+                if (File.Exists(TempScenePath)) tempSceneData = File.ReadAllBytes(TempScenePath);
+                else return;
+            }
+
+            meidoPhotoStudio.LoadScene(tempSceneData);
         }
 
         private void SaveSceneToFile(Texture2D screenshot, bool overwrite = false)
         {
             Busy = true;
 
-            byte[] sceneData = meidoPhotoStudio.SerializeScene(KankyoMode);
+            byte[] sceneData = meidoPhotoStudio.SaveScene(KankyoMode);
 
             if (sceneData != null)
             {
@@ -280,7 +291,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                 string fileName = $"{scenePrefix}{Utility.Timestamp}.png";
                 string savePath = Path.Combine(CurrentScenesDirectory, fileName);
 
-                if (overwrite && CurrentScene != null) savePath = CurrentScene.FileInfo.FullName;
+                if (overwrite && CurrentScene?.FileInfo != null) savePath = CurrentScene.FileInfo.FullName;
                 else overwrite = false;
 
                 Utility.ResizeToFit(screenshot, (int)sceneDimensions.x, (int)sceneDimensions.y);
@@ -292,8 +303,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                     fileStream.Write(sceneData, 0, sceneData.Length);
                 }
 
-                UnityEngine.Object.DestroyImmediate(screenshot);
-
                 if (overwrite)
                 {
                     File.SetCreationTime(savePath, CurrentScene.FileInfo.CreationTime);
@@ -301,67 +310,11 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                     SceneList.RemoveAt(CurrentSceneIndex);
                 }
 
-                SceneList.Add(new Scene(savePath));
+                SceneList.Add(new MPSScene(savePath));
                 SortScenes(CurrentSortMode);
             }
 
             Busy = false;
         }
-
-        public class Scene
-        {
-            public const int initialNumberOfMaids = -2;
-            public Texture2D Thumbnail { get; }
-            public FileInfo FileInfo { get; set; }
-            public int NumberOfMaids { get; private set; } = initialNumberOfMaids;
-
-            public Scene(string filePath)
-            {
-                FileInfo = new FileInfo(filePath);
-                Thumbnail = new Texture2D(1, 1, TextureFormat.ARGB32, false);
-                Thumbnail.LoadImage(File.ReadAllBytes(FileInfo.FullName));
-            }
-
-            public void GetNumberOfMaids()
-            {
-                if (NumberOfMaids != initialNumberOfMaids) return;
-
-                string filePath = FileInfo.FullName;
-
-                byte[] sceneData = MeidoPhotoStudio.DecompressScene(filePath);
-
-                if (sceneData == null) return;
-
-                using BinaryReader binaryReader = new BinaryReader(new MemoryStream(sceneData), Encoding.UTF8);
-
-                try
-                {
-                    if (binaryReader.ReadString() != "MPS_SCENE")
-                    {
-                        Utility.LogWarning($"'{filePath}' is not a {MeidoPhotoStudio.pluginName} scene");
-                        return;
-                    }
-
-                    if (binaryReader.ReadInt32() > MeidoPhotoStudio.sceneVersion)
-                    {
-                        Utility.LogWarning(
-                            $"'{filePath}' is made in a newer version of {MeidoPhotoStudio.pluginName}"
-                        );
-                        return;
-                    }
-
-                    NumberOfMaids = binaryReader.ReadInt32();
-                }
-                catch (Exception e)
-                {
-                    Utility.LogWarning($"Failed to deserialize scene '{filePath}' because {e.Message}");
-                }
-            }
-
-            public void Destroy()
-            {
-                if (Thumbnail != null) UnityEngine.Object.DestroyImmediate(Thumbnail);
-            }
-        }
     }
 }

+ 9 - 255
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Meido/Meido.cs

@@ -10,7 +10,7 @@ using static TBody;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
-    public class Meido : ISerializable
+    public class Meido
     {
         private bool initialized;
         private float[] BlendSetValueBackup;
@@ -20,27 +20,20 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public DragPointGravity SkirtGravityControl { get; private set; }
         public bool HairGravityActive
         {
-            get => HairGravityControl.Valid && HairGravityControl.Active;
+            get => HairGravityControl.Active;
             set
             {
-                if (HairGravityControl.Valid && value != HairGravityControl.Active)
-                {
-                    HairGravityControl.gameObject.SetActive(value);
-                }
+                if (HairGravityControl.Valid) HairGravityControl.gameObject.SetActive(value);
             }
         }
         public bool SkirtGravityActive
         {
-            get => SkirtGravityControl.Valid && SkirtGravityControl.Active;
+            get => SkirtGravityControl.Active;
             set
             {
-                if (SkirtGravityControl.Valid && value != SkirtGravityControl.Active)
-                {
-                    SkirtGravityControl.gameObject.SetActive(value);
-                }
+                if (SkirtGravityControl.Valid) SkirtGravityControl.gameObject.SetActive(value);
             }
         }
-        public const int meidoDataVersion = 1000;
         public static readonly string defaultFaceBlendSet = "通常";
         public static readonly string[] faceKeys = new string[24]
         {
@@ -399,10 +392,13 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             Maid.SetAutoTwistAll(true);
         }
 
+        public KeyValuePair<bool, bool> SetFrameBinary(byte[] poseBuffer)
+            => GetCacheBoneData().SetFrameBinary(poseBuffer);
+
         public void CopyPose(Meido fromMeido)
         {
             Stop = true;
-            GetCacheBoneData().SetFrameBinary(fromMeido.SerializePose(frameBinary: true));
+            SetFrameBinary(fromMeido.SerializePose(frameBinary: true));
             SetMune(fromMeido.Body.GetMuneYureL() != 0f, left: true);
             SetMune(fromMeido.Body.GetMuneYureR() != 0f, left: false);
         }
@@ -742,231 +738,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             );
             GravityMove?.Invoke(this, args);
         }
-
-        public void Serialize(BinaryWriter binaryWriter)
-        {
-            using (MemoryStream memoryStream = new MemoryStream())
-            using (BinaryWriter tempWriter = new BinaryWriter(memoryStream))
-            {
-                // transform
-                tempWriter.WriteVector3(Maid.transform.position);
-                tempWriter.WriteQuaternion(Maid.transform.rotation);
-                tempWriter.WriteVector3(Maid.transform.localScale);
-                // pose
-                byte[] poseBuffer = SerializePose(true);
-                tempWriter.Write(poseBuffer.Length);
-                tempWriter.Write(poseBuffer);
-                CachedPose.Serialize(tempWriter);
-                // eye direction
-                tempWriter.WriteQuaternion(Body.quaDefEyeL * Quaternion.Inverse(DefaultEyeRotL));
-                tempWriter.WriteQuaternion(Body.quaDefEyeR * Quaternion.Inverse(DefaultEyeRotR));
-                // free look
-                tempWriter.Write(FreeLook);
-                if (FreeLook)
-                {
-                    tempWriter.WriteVector3(Body.offsetLookTarget);
-                    tempWriter.WriteVector3(Utility.GetFieldValue<TBody, Vector3>(Body, "HeadEulerAngle"));
-                }
-
-                // Head/eye to camera
-                tempWriter.Write(HeadToCam);
-                tempWriter.Write(EyeToCam);
-                // face
-                SerializeFace(tempWriter);
-                // body visible
-                tempWriter.Write(Body.GetMask(SlotID.body));
-                // clothing
-                foreach (SlotID clothingSlot in MaidDressingPane.ClothingSlots)
-                {
-                    bool value = true;
-                    if (clothingSlot == SlotID.wear)
-                    {
-                        if (MaidDressingPane.WearSlots.Any(slot => Body.GetSlotLoaded(slot)))
-                        {
-                            value = MaidDressingPane.WearSlots.Any(slot => Body.GetMask(slot));
-                        }
-                    }
-                    else if (clothingSlot == SlotID.megane)
-                    {
-                        SlotID[] slots = new[] { SlotID.megane, SlotID.accHead };
-                        if (slots.Any(slot => Body.GetSlotLoaded(slot)))
-                        {
-                            value = slots.Any(slot => Body.GetMask(slot));
-                        }
-                    }
-                    else if (Body.GetSlotLoaded(clothingSlot)) { value = Body.GetMask(clothingSlot); }
-
-                    tempWriter.Write(value);
-                }
-
-                // hair/skirt gravity
-                tempWriter.Write(HairGravityActive);
-                if (HairGravityActive) tempWriter.WriteVector3(HairGravityControl.Control.transform.localPosition);
-                tempWriter.Write(SkirtGravityActive);
-                if (SkirtGravityActive) tempWriter.WriteVector3(SkirtGravityControl.Control.transform.localPosition);
-
-                // zurashi and mekure
-                tempWriter.Write(CurlingFront);
-                tempWriter.Write(CurlingBack);
-                tempWriter.Write(PantsuShift);
-
-                bool hasKousokuUpper = Body.GetSlotLoaded(SlotID.kousoku_upper);
-                tempWriter.Write(hasKousokuUpper);
-                if (hasKousokuUpper) tempWriter.Write(Maid.GetProp(MPN.kousoku_upper).strTempFileName);
-
-                bool hasKousokuLower = Body.GetSlotLoaded(SlotID.kousoku_lower);
-                tempWriter.Write(hasKousokuLower);
-                if (hasKousokuLower) tempWriter.Write(Maid.GetProp(MPN.kousoku_lower).strTempFileName);
-
-                binaryWriter.Write(memoryStream.Length);
-                binaryWriter.Write(memoryStream.ToArray());
-            }
-        }
-
-        private void SerializeFace(BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write("MPS_FACE");
-            foreach (string hash in faceKeys.Concat(faceToggleKeys))
-            {
-                try
-                {
-                    float value = GetFaceBlendValue(hash);
-                    binaryWriter.Write(hash);
-                    binaryWriter.Write(value);
-                }
-                catch { }
-            }
-            binaryWriter.Write("END_FACE");
-        }
-
-        public void Deserialize(BinaryReader binaryReader) => Deserialize(binaryReader, meidoDataVersion, false);
-
-        public void Deserialize(BinaryReader binaryReader, int dataVersion, bool mmScene)
-        {
-            Maid.GetAnimation().Stop();
-            DetachAllMpnAttach();
-
-            binaryReader.ReadInt64(); // meido buffer length
-            // transform
-            Maid.transform.position = binaryReader.ReadVector3();
-            Maid.transform.rotation = binaryReader.ReadQuaternion();
-            Maid.transform.localScale = binaryReader.ReadVector3();
-            IKManager.SetDragPointScale(Maid.transform.localScale.x);
-            // pose
-
-            KeyValuePair<bool, bool> muneSetting = new KeyValuePair<bool, bool>(true, true);
-            if (mmScene) IKManager.Deserialize(binaryReader);
-            else
-            {
-                int poseBufferLength = binaryReader.ReadInt32();
-                byte[] poseBuffer = binaryReader.ReadBytes(poseBufferLength);
-                muneSetting = GetCacheBoneData().SetFrameBinary(poseBuffer);
-            }
-
-            SetMune(!muneSetting.Key, left: true);
-            SetMune(!muneSetting.Value, left: false);
-
-            CachedPose = PoseInfo.Deserialize(binaryReader);
-            // eye direction
-            Body.quaDefEyeL = binaryReader.ReadQuaternion() * DefaultEyeRotL;
-            Body.quaDefEyeR = binaryReader.ReadQuaternion() * DefaultEyeRotR;
-            // free look
-            FreeLook = binaryReader.ReadBoolean();
-            if (FreeLook)
-            {
-                Body.offsetLookTarget = binaryReader.ReadVector3();
-                // Head angle cannot be resolved with just the offsetLookTarget
-                if (!mmScene)
-                {
-                    Utility.SetFieldValue(Body, "HeadEulerAngleG", Vector3.zero);
-                    Utility.SetFieldValue(Body, "HeadEulerAngle", binaryReader.ReadVector3());
-                }
-            }
-            // Head/eye to camera
-            HeadToCam = binaryReader.ReadBoolean();
-            EyeToCam = binaryReader.ReadBoolean();
-            // face
-            DeserializeFace(binaryReader);
-            // body visible
-            SetBodyMask(binaryReader.ReadBoolean());
-            // clothing
-            foreach (SlotID clothingSlot in MaidDressingPane.ClothingSlots)
-            {
-                bool value = binaryReader.ReadBoolean();
-                if (mmScene) continue;
-                if (clothingSlot == SlotID.wear)
-                {
-                    Body.SetMask(SlotID.wear, value);
-                    Body.SetMask(SlotID.mizugi, value);
-                    Body.SetMask(SlotID.onepiece, value);
-                }
-                else if (clothingSlot == SlotID.megane)
-                {
-                    Body.SetMask(SlotID.megane, value);
-                    Body.SetMask(SlotID.accHead, value);
-                }
-                else if (Body.GetSlotLoaded(clothingSlot))
-                {
-                    Body.SetMask(clothingSlot, value);
-                }
-            }
-            // hair/skirt gravity
-            bool hairGravityActive = binaryReader.ReadBoolean();
-            if (hairGravityActive)
-            {
-                HairGravityActive = true;
-                ApplyGravity(binaryReader.ReadVector3(), skirt: false);
-            }
-            bool skirtGravityActive = binaryReader.ReadBoolean();
-            if (skirtGravityActive)
-            {
-                SkirtGravityActive = true;
-                ApplyGravity(binaryReader.ReadVector3(), skirt: true);
-            }
-
-            // zurashi and mekure
-            bool curlingFront = binaryReader.ReadBoolean();
-            bool curlingBack = binaryReader.ReadBoolean();
-            bool curlingPantsu = binaryReader.ReadBoolean();
-            if (!mmScene)
-            {
-                if (CurlingFront != curlingFront) SetCurling(Curl.Front, curlingFront);
-                if (CurlingBack != curlingBack) SetCurling(Curl.Back, curlingBack);
-                SetCurling(Curl.Shift, curlingPantsu);
-            }
-
-            bool hasKousokuUpper = binaryReader.ReadBoolean();
-            if (hasKousokuUpper)
-            {
-                try
-                {
-                    SetMpnProp(new MpnAttachProp(MPN.kousoku_upper, binaryReader.ReadString()), false);
-                }
-                catch { }
-            }
-
-            bool hasKousokuLower = binaryReader.ReadBoolean();
-            if (hasKousokuLower)
-            {
-                try
-                {
-                    SetMpnProp(new MpnAttachProp(MPN.kousoku_lower, binaryReader.ReadString()), false);
-                }
-                catch { }
-            }
-            // OnUpdateMeido();
-        }
-
-        private void DeserializeFace(BinaryReader binaryReader)
-        {
-            StopBlink();
-            binaryReader.ReadString(); // read face header
-            string header;
-            while ((header = binaryReader.ReadString()) != "END_FACE")
-            {
-                SetFaceBlendValue(header, binaryReader.ReadSingle());
-            }
-        }
     }
 
     public class GravityEventArgs : EventArgs
@@ -996,22 +767,5 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             Pose = pose;
             CustomPose = customPose;
         }
-
-        public void Serialize(BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write(PoseGroup);
-            binaryWriter.Write(Pose);
-            binaryWriter.Write(CustomPose);
-        }
-
-        public static PoseInfo Deserialize(BinaryReader binaryReader)
-        {
-            return new PoseInfo
-            (
-                binaryReader.ReadString(),
-                binaryReader.ReadString(),
-                binaryReader.ReadBoolean()
-            );
-        }
     }
 }

+ 10 - 22
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Meido/MeidoDragPointManager.cs

@@ -675,39 +675,27 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
     public readonly struct AttachPointInfo
     {
-        public AttachPoint AttachPoint { get; }
+        private readonly AttachPoint attachPoint;
+        public AttachPoint AttachPoint => attachPoint;
         public string MaidGuid { get; }
-        public int MaidIndex { get; }
-        private static readonly AttachPointInfo empty = new AttachPointInfo(AttachPoint.None, string.Empty, -1);
+        
+        private readonly int maidIndex;
+        public int MaidIndex => maidIndex;
+        private static readonly AttachPointInfo empty = new(AttachPoint.None, string.Empty, -1);
         public static ref readonly AttachPointInfo Empty => ref empty;
 
         public AttachPointInfo(AttachPoint attachPoint, Meido meido)
         {
-            AttachPoint = attachPoint;
+            this.attachPoint = attachPoint;
             MaidGuid = meido.Maid.status.guid;
-            MaidIndex = meido.Slot;
+            maidIndex = meido.Slot;
         }
 
         public AttachPointInfo(AttachPoint attachPoint, string maidGuid, int maidIndex)
         {
-            AttachPoint = attachPoint;
+            this.attachPoint = attachPoint;
             MaidGuid = maidGuid;
-            MaidIndex = maidIndex;
-        }
-
-        public void Serialize(BinaryWriter binaryWriter)
-        {
-            binaryWriter.Write((int)AttachPoint);
-            binaryWriter.Write(MaidIndex);
-        }
-
-        public static AttachPointInfo Deserialize(BinaryReader binaryReader)
-        {
-            return new AttachPointInfo(
-                (AttachPoint)binaryReader.ReadInt32(),
-                string.Empty,
-                binaryReader.ReadInt32()
-            );
+            this.maidIndex = maidIndex;
         }
     }
 }

+ 80 - 128
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/MeidoPhotoStudio.cs

@@ -2,9 +2,9 @@ using System;
 using System.IO;
 using System.Collections;
 using System.Collections.Generic;
+using System.Text;
 using UnityEngine;
 using UnityEngine.SceneManagement;
-using Ionic.Zlib;
 using BepInEx;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
@@ -14,14 +14,16 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
     [BepInDependency("org.bepinex.plugins.unityinjectorloader", BepInDependency.DependencyFlags.SoftDependency)]
     public class MeidoPhotoStudio : BaseUnityPlugin
     {
+        public static readonly byte[] SceneHeader = Encoding.UTF8.GetBytes("MPSSCENE");
         private static readonly CameraMain mainCamera = GameMain.Instance.MainCamera;
         private static event EventHandler<ScreenshotEventArgs> ScreenshotEvent;
         private const string pluginGuid = "com.habeebweeb.com3d2.meidophotostudio";
         public const string pluginName = "MeidoPhotoStudio";
         public const string pluginVersion = "1.0.0";
-        public const int sceneVersion = 1000;
+        public const string pluginSubVersion = "beta.3";
+        public const short sceneVersion = 1;
         public const int kankyoMagic = -765;
-        public static string pluginString = $"{pluginName} {pluginVersion}";
+        public static readonly string pluginString = $"{pluginName} {pluginVersion}";
         public static bool EditMode => currentScene == Constants.Scene.Edit;
         public static event EventHandler<ScreenshotEventArgs> NotifyRawScreenshot;
         private WindowManager windowManager;
@@ -42,6 +44,8 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         {
             Input.Register(MpsKey.Screenshot, KeyCode.S, "Take screenshot");
             Input.Register(MpsKey.Activate, KeyCode.F6, "Activate/deactivate MeidoPhotoStudio");
+
+            if (!string.IsNullOrEmpty(pluginSubVersion)) pluginString += $"-{pluginSubVersion}";
         }
 
         private void Awake()
@@ -71,160 +75,108 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             ResetCalcNearClip();
         }
 
-        public byte[] SerializeScene(bool kankyo = false)
+        public byte[] SaveScene(bool environment = false)
         {
             if (meidoManager.Busy) return null;
 
-            byte[] compressedData;
-
-            using (MemoryStream memoryStream = new MemoryStream())
-            using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
-            using (BinaryWriter binaryWriter = new BinaryWriter(deflateStream, System.Text.Encoding.UTF8))
-            {
-                binaryWriter.Write("MPS_SCENE");
-                binaryWriter.Write(sceneVersion);
+            using var memoryStream = new MemoryStream();
+            using var headerWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
 
-                binaryWriter.Write(kankyo ? kankyoMagic : meidoManager.ActiveMeidoList.Count);
+            headerWriter.Write(SceneHeader);
 
-                effectManager.Serialize(binaryWriter);
-                environmentManager.Serialize(binaryWriter);
-                lightManager.Serialize(binaryWriter);
+            new SceneMetadata{
+                Version = sceneVersion, Environment = environment, 
+                MaidCount = environment ? kankyoMagic : meidoManager.ActiveMeidoList.Count
+            }.WriteMetadata(headerWriter);
 
-                if (!kankyo)
-                {
-                    messageWindowManager.Serialize(binaryWriter);
-                    // meidomanager has to be serialized before propmanager because reattached props will be in the 
-                    // wrong place after a maid's pose is deserialized.
-                    meidoManager.Serialize(binaryWriter);
-                }
+            using var compressionStream = memoryStream.GetCompressionStream();
+            using var dataWriter = new BinaryWriter(compressionStream, Encoding.UTF8);
 
-                propManager.Serialize(binaryWriter);
+            if (!environment)
+            {
+                Serialization.Get<MeidoManager>().Serialize(meidoManager, dataWriter);
+                Serialization.Get<MessageWindowManager>().Serialize(messageWindowManager, dataWriter);
+                Serialization.Get<CameraManager>().Serialize(cameraManager, dataWriter);
+            }
 
-                binaryWriter.Write("END");
+            Serialization.Get<LightManager>().Serialize(lightManager, dataWriter);
+            Serialization.Get<EffectManager>().Serialize(effectManager, dataWriter);
+            Serialization.Get<EnvironmentManager>().Serialize(environmentManager, dataWriter);
+            Serialization.Get<PropManager>().Serialize(propManager, dataWriter);
 
-                deflateStream.Close();
+            dataWriter.Write("END");
 
-                compressedData = memoryStream.ToArray();
-            }
+            compressionStream.Close();
 
-            return compressedData;
+            var data = memoryStream.ToArray();
+            return data;
         }
 
-        public static byte[] DecompressScene(string filePath)
+        public void LoadScene(byte[] buffer)
         {
-            if (!File.Exists(filePath))
+            if (meidoManager.Busy)
             {
-                Utility.LogWarning($"Scene file '{filePath}' does not exist.");
-                return null;
+                Utility.LogMessage("Could not apply scene. Meidos are Busy");
+                return;
             }
 
-            byte[] compressedData;
-
-            using (FileStream fileStream = File.OpenRead(filePath))
+            using var memoryStream = new MemoryStream(buffer);
+            using var headerReader = new BinaryReader(memoryStream, Encoding.UTF8);
+            
+            if (!Utility.BytesEqual(headerReader.ReadBytes(SceneHeader.Length), SceneHeader))
             {
-                if (Utility.IsPngFile(fileStream))
-                {
-                    if (!Utility.SeekPngEnd(fileStream))
-                    {
-                        Utility.LogWarning($"'{filePath}' is not a PNG file");
-                        return null;
-                    }
-
-                    if (fileStream.Position == fileStream.Length)
-                    {
-                        Utility.LogWarning($"'{filePath}' contains no scene data");
-                        return null;
-                    }
-
-                    int dataLength = (int)(fileStream.Length - fileStream.Position);
-
-                    compressedData = new byte[dataLength];
-
-                    fileStream.Read(compressedData, 0, dataLength);
-                }
-                else
-                {
-                    compressedData = new byte[fileStream.Length];
-                    fileStream.Read(compressedData, 0, compressedData.Length);
-                }
+                Utility.LogError("Not a MPS scene!");
+                return;
             }
 
-            return DeflateStream.UncompressBuffer(compressedData);
-        }
-
-        public void ApplyScene(string filePath)
-        {
-            if (meidoManager.Busy) return;
-
-            byte[] sceneBinary = DecompressScene(filePath);
+            var metadata = SceneMetadata.ReadMetadata(headerReader);
 
-            if (sceneBinary == null) return;
+            using var uncompressed = memoryStream.Decompress();
+            using var dataReader = new BinaryReader(uncompressed, Encoding.UTF8);
 
-            string header = string.Empty;
-            string previousHeader = string.Empty;
-
-            using (MemoryStream memoryStream = new MemoryStream(sceneBinary))
-            using (BinaryReader binaryReader = new BinaryReader(memoryStream, System.Text.Encoding.UTF8))
+            var header = string.Empty;
+            var previousHeader = string.Empty;
+            try
             {
-                try
+                while ((header = dataReader.ReadString()) != "END")
                 {
-                    if (binaryReader.ReadString() != "MPS_SCENE")
-                    {
-                        Utility.LogWarning($"'{filePath}' is not a {pluginName} scene");
-                        return;
-                    }
-
-                    var version = binaryReader.ReadInt32();
-
-                    if (version > sceneVersion)
+                    switch (header)
                     {
-                        Utility.LogWarning($"'{filePath}' is newer than the current version"
-                        + $"Scene's version: {version}, current version: {sceneVersion}");
-                        return;
+                        case MeidoManager.header: 
+                            Serialization.Get<MeidoManager>().Deserialize(meidoManager, dataReader, metadata);
+                            break;
+                        case MessageWindowManager.header:
+                            Serialization.Get<MessageWindowManager>().Deserialize(messageWindowManager, dataReader, metadata);
+                            break;
+                        case CameraManager.header:
+                            Serialization.Get<CameraManager>().Deserialize(cameraManager, dataReader, metadata);
+                            break;
+                        case LightManager.header:
+                            Serialization.Get<LightManager>().Deserialize(lightManager, dataReader, metadata);
+                            break;
+                        case EffectManager.header:
+                            Serialization.Get<EffectManager>().Deserialize(effectManager, dataReader, metadata);
+                            break;
+                        case EnvironmentManager.header:
+                            Serialization.Get<EnvironmentManager>().Deserialize(environmentManager, dataReader, metadata);
+                            break;
+                        case PropManager.header:
+                            Serialization.Get<PropManager>().Deserialize(propManager, dataReader, metadata);
+                            break;
+                        default: throw new Exception($"Unknown header '{header}'");
                     }
 
-                    binaryReader.ReadInt32(); // Number of Maids
-
-                    while ((header = binaryReader.ReadString()) != "END")
-                    {
-                        switch (header)
-                        {
-                            case MessageWindowManager.header:
-                                messageWindowManager.Deserialize(binaryReader);
-                                break;
-                            case EnvironmentManager.header:
-                                environmentManager.Deserialize(binaryReader);
-                                break;
-                            case CameraManager.header:
-                                environmentManager.Deserialize(binaryReader);
-                                break;
-                            case MeidoManager.header:
-                                meidoManager.Deserialize(binaryReader);
-                                break;
-                            case PropManager.header:
-                                propManager.Deserialize(binaryReader);
-                                break;
-                            case LightManager.header:
-                                lightManager.Deserialize(binaryReader);
-                                break;
-                            case EffectManager.header:
-                                effectManager.Deserialize(binaryReader);
-                                break;
-                            default: throw new Exception($"Unknown header '{header}'");
-                        }
-                        previousHeader = header;
-                    }
-                }
-                catch (Exception e)
-                {
-                    Utility.LogError(
-                        $"Failed to deserialize scene '{filePath}' because {e.Message}"
-                        + $"\nCurrent header: '{header}'. Last header: '{previousHeader}'"
-                    );
-                    Utility.LogError(e.StackTrace);
-                    return;
+                    previousHeader = header;
                 }
             }
+            catch (Exception e)
+            {
+                Utility.LogError(
+                    $"Failed to deserialize scene TEST because {e.Message}"
+                    + $"\nCurrent header: '{header}'. Last header: '{previousHeader}'"
+                );
+                Utility.LogError(e.StackTrace);
+            }
         }
 
         public static void TakeScreenshot(ScreenshotEventArgs args) => ScreenshotEvent?.Invoke(null, args);

+ 10 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/ISerializer.cs

@@ -0,0 +1,10 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public interface ISerializer
+    {
+        void Serialize(object thing, BinaryWriter writer);
+        void Deserialize(object thing, BinaryReader reader, SceneMetadata metadata);
+    }
+}

+ 10 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/ISimpleSerializer.cs

@@ -0,0 +1,10 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public interface ISimpleSerializer
+    {
+        void Serialize(object obj, BinaryWriter writer);
+        object Deserialize(BinaryReader reader, SceneMetadata metadata);
+    }
+}

+ 42 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SceneMetadata.cs

@@ -0,0 +1,42 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class SceneMetadata
+    {
+        public short Version { get; init; }
+        public bool Environment { get; init; }
+        public bool MMConverted { get; init; }
+        public int MaidCount { get; init; }
+
+
+        public void WriteMetadata(BinaryWriter writer)
+        {
+            writer.Write(Version);
+            writer.Write(Environment);
+            writer.Write(MaidCount);
+            writer.Write(MMConverted);
+        }
+
+        public static SceneMetadata ReadMetadata(BinaryReader reader)
+        {
+            return new()
+            {
+                Version = reader.ReadVersion(),
+                Environment = reader.ReadBoolean(),
+                MaidCount = reader.ReadInt32(),
+                MMConverted = reader.ReadBoolean()
+            };
+        }
+
+        public void Deconstruct(
+            out short version, out bool environment, out bool mmConverted, out int maidCount
+        )
+        {
+            version = Version;
+            environment = Environment;
+            mmConverted = MMConverted;
+            maidCount = MaidCount;
+        }
+    }
+}

+ 48 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serialization.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public static class Serialization
+    {
+        private static readonly Dictionary<Type, ISerializer> serializers = new();
+
+        private static readonly Dictionary<Type, ISimpleSerializer> simpleSerializers = new();
+
+        static Serialization()
+        {
+            var types = (from t in typeof(MeidoPhotoStudio).Assembly.GetTypes()
+                let baseType = t.BaseType
+                where !t.IsAbstract && !t.IsInterface && baseType != null && baseType.IsGenericType
+                select new { type = t, baseType }).ToArray();
+
+            var concreteSerializers = from t in types
+                where t.baseType.GetGenericTypeDefinition() == typeof(Serializer<>)
+                select new { t.type, arg = t.baseType.GetGenericArguments().First() };
+
+            foreach (var serializer in concreteSerializers)
+                serializers[serializer.arg] = (ISerializer) Activator.CreateInstance(serializer.type);
+
+            var concreteSimpleSerializers = from t in types
+                where t.baseType.GetGenericTypeDefinition() == typeof(SimpleSerializer<>)
+                select new { t.type, arg = t.baseType.GetGenericArguments().First() };
+
+            foreach (var serializer in concreteSimpleSerializers)
+                simpleSerializers[serializer.arg] = (ISimpleSerializer) Activator.CreateInstance(serializer.type);
+        }
+
+        public static Serializer<T> Get<T>() => serializers[typeof(T)] as Serializer<T>;
+
+        public static ISerializer Get(Type type) => serializers[type];
+
+        public static SimpleSerializer<T> GetSimple<T>() => simpleSerializers[typeof(T)] as SimpleSerializer<T>;
+
+        public static ISimpleSerializer GetSimple(Type type) => simpleSerializers[type];
+
+        public static short ReadVersion(this BinaryReader reader) => reader.ReadInt16();
+
+        public static void WriteVersion(this BinaryWriter writer, short version) => writer.Write(version);
+    }
+}

+ 15 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializer.cs

@@ -0,0 +1,15 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public abstract class Serializer<T> : ISerializer
+    {
+        void ISerializer.Serialize(object obj, BinaryWriter writer) => Serialize((T) obj, writer);
+
+        void ISerializer.Deserialize(object obj, BinaryReader reader, SceneMetadata metadata)
+            => Deserialize((T) obj, reader, metadata);
+
+        public abstract void Serialize(T obj, BinaryWriter writer);
+        public abstract void Deserialize(T obj, BinaryReader reader, SceneMetadata metadata);
+    }
+}

+ 27 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/AttachPointInfoSerializer.cs

@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class AttachPointInfoSerializer : SimpleSerializer<AttachPointInfo>
+    {
+        private const short version = 1;
+
+        public override void Serialize(AttachPointInfo info, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            writer.Write((int) info.AttachPoint);
+            writer.Write(info.MaidIndex);
+        }
+
+        public override AttachPointInfo Deserialize(BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var attachPoint = (AttachPoint) reader.ReadInt32();
+            var maidIndex = reader.ReadInt32();
+
+            return new AttachPointInfo(attachPoint, string.Empty, maidIndex);
+        }
+    }
+}

+ 29 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/CameraInfoSerializer.cs

@@ -0,0 +1,29 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class CameraInfoSerializer : Serializer<CameraInfo>
+    {
+        private const short version = 1;
+
+        public override void Serialize(CameraInfo info, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            writer.Write(info.TargetPos);
+            writer.Write(info.Angle);
+            writer.Write(info.Distance);
+            writer.Write(info.FOV);
+        }
+
+        public override void Deserialize(CameraInfo info, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            info.TargetPos = reader.ReadVector3();
+            info.Angle = reader.ReadQuaternion();
+            info.Distance = reader.ReadSingle();
+            info.FOV = reader.ReadSingle();
+        }
+    }
+}

+ 41 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/DragPointLightSerializer.cs

@@ -0,0 +1,41 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class DragPointLightSerializer : Serializer<DragPointLight>
+    {
+        private const short version = 1;
+        private static Serializer<LightProperty> LightPropertySerializer => Serialization.Get<LightProperty>();
+
+        public override void Serialize(DragPointLight light, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            LightProperty[] lightList = GetLightProperties(light);
+
+            for (var i = 0; i < 3; i++) LightPropertySerializer.Serialize(lightList[i], writer);
+
+            writer.Write(light.MyObject.position);
+            writer.Write((int) light.SelectedLightType);
+            writer.Write(light.IsColourMode);
+            writer.Write(light.IsDisabled);
+        }
+
+        public override void Deserialize(DragPointLight light, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            LightProperty[] lightList = GetLightProperties(light);
+            
+            for (var i = 0; i < 3; i++) LightPropertySerializer.Deserialize(lightList[i], reader, metadata);
+
+            light.MyObject.position = reader.ReadVector3();
+            light.SetLightType((DragPointLight.MPSLightType) reader.ReadInt32());
+            light.IsColourMode = reader.ReadBoolean();
+            light.IsDisabled = reader.ReadBoolean();
+        }
+
+        private static LightProperty[] GetLightProperties(DragPointLight light)
+            => Utility.GetFieldValue<DragPointLight, LightProperty[]>(light, "LightProperties");
+    }
+}

+ 34 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/BloomEffectSerializer.cs

@@ -0,0 +1,34 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class BloomEffectSerializer : Serializer<BloomEffectManager>
+    {
+        private const short version = 1;
+
+        public override void Serialize(BloomEffectManager effect, BinaryWriter writer)
+        {
+            writer.Write(BloomEffectManager.header);
+            writer.WriteVersion(version);
+
+            writer.Write(effect.Active);
+            writer.Write(effect.BloomValue);
+            writer.Write(effect.BlurIterations);
+            writer.Write(effect.BloomThresholdColour);
+            writer.Write(effect.BloomHDR);
+        }
+
+        public override void Deserialize(BloomEffectManager effect, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var active = reader.ReadBoolean();
+            effect.BloomValue = reader.ReadSingle();
+            effect.BlurIterations = reader.ReadInt32();
+            effect.BloomThresholdColour = reader.ReadColour();
+            effect.BloomHDR = reader.ReadBoolean();
+
+            effect.SetEffectActive(active);
+        }
+    }
+}

+ 28 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/BlurEffectSerializer.cs

@@ -0,0 +1,28 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class BlurEffectSerializer : Serializer<BlurEffectManager>
+    {
+        private const short version = 1;
+
+        public override void Serialize(BlurEffectManager effect, BinaryWriter writer)
+        {
+            writer.Write(BlurEffectManager.header);
+            writer.WriteVersion(version);
+
+            writer.Write(effect.Active);
+            writer.Write(effect.BlurSize);
+        }
+
+        public override void Deserialize(BlurEffectManager effect, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var active = reader.ReadBoolean();
+            effect.BlurSize = reader.ReadSingle();
+
+            effect.SetEffectActive(active);
+        }
+    }
+}

+ 36 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/DepthOfFieldEffectSerializer.cs

@@ -0,0 +1,36 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class DepthOfFieldEffectSerializer : Serializer<DepthOfFieldEffectManager>
+    {
+        private const short version = 1;
+
+        public override void Serialize(DepthOfFieldEffectManager effect, BinaryWriter writer)
+        {
+            writer.Write(DepthOfFieldEffectManager.header);
+            writer.WriteVersion(version);
+
+            writer.Write(effect.Active);
+            writer.Write(effect.FocalLength);
+            writer.Write(effect.FocalSize);
+            writer.Write(effect.Aperture);
+            writer.Write(effect.MaxBlurSize);
+            writer.Write(effect.VisualizeFocus);
+        }
+
+        public override void Deserialize(DepthOfFieldEffectManager effect, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var active = reader.ReadBoolean();
+            effect.FocalLength = reader.ReadSingle();
+            effect.FocalSize = reader.ReadSingle();
+            effect.Aperture = reader.ReadSingle();
+            effect.MaxBlurSize = reader.ReadSingle();
+            effect.VisualizeFocus = reader.ReadBoolean();
+
+            effect.SetEffectActive(active);
+        }
+    }
+}

+ 36 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/FogEffectSerializer.cs

@@ -0,0 +1,36 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class FogEffectSerializer : Serializer<FogEffectManager>
+    {
+        private const short version = 1;
+
+        public override void Serialize(FogEffectManager effect, BinaryWriter writer)
+        {
+            writer.Write(FogEffectManager.header);
+            writer.WriteVersion(version);
+
+            writer.Write(effect.Active);
+            writer.Write(effect.Distance);
+            writer.Write(effect.Density);
+            writer.Write(effect.HeightScale);
+            writer.Write(effect.Height);
+            writer.WriteColour(effect.FogColour);
+        }
+
+        public override void Deserialize(FogEffectManager effect, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var active = reader.ReadBoolean();
+            effect.Distance = reader.ReadSingle();
+            effect.Density = reader.ReadSingle();
+            effect.HeightScale = reader.ReadSingle();
+            effect.Height = reader.ReadSingle();
+            effect.FogColour = reader.ReadColour();
+
+            effect.SetEffectActive(active);
+        }
+    }
+}

+ 24 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/SepiaToneEffectSerializer.cs

@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class SepiaToneEffectSerializer : Serializer<SepiaToneEffectManger>
+    {
+        private const short version = 1;
+
+        public override void Serialize(SepiaToneEffectManger effect, BinaryWriter writer)
+        {
+            writer.Write(SepiaToneEffectManger.header);
+            writer.WriteVersion(version);
+
+            writer.Write(effect.Active);
+        }
+
+        public override void Deserialize(SepiaToneEffectManger effect, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            effect.SetEffectActive(reader.ReadBoolean());
+        }
+    }
+}

+ 34 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/EffectSerializers/VignetteEffectSerializer.cs

@@ -0,0 +1,34 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class VignetteEffectSerializer : Serializer<VignetteEffectManager>
+    {
+        private const short version = 1;
+
+        public override void Serialize(VignetteEffectManager manager, BinaryWriter writer)
+        {
+            writer.Write(VignetteEffectManager.header);
+            writer.WriteVersion(version);
+
+            writer.Write(manager.Active);
+            writer.Write(manager.Intensity);
+            writer.Write(manager.Blur);
+            writer.Write(manager.BlurSpread);
+            writer.Write(manager.ChromaticAberration);
+        }
+
+        public override void Deserialize(VignetteEffectManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var active = reader.ReadBoolean();
+            manager.Intensity = reader.ReadSingle();
+            manager.Blur = reader.ReadSingle();
+            manager.BlurSpread = reader.ReadSingle();
+            manager.ChromaticAberration = reader.ReadSingle();
+
+            manager.SetEffectActive(active);
+        }
+    }
+}

+ 33 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/LightPropertySerializer.cs

@@ -0,0 +1,33 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class LightPropertySerializer : Serializer<LightProperty>
+    {
+        private const short version = 1;
+
+        public override void Serialize(LightProperty prop, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            writer.Write(prop.Rotation);
+            writer.Write(prop.Intensity);
+            writer.Write(prop.Range);
+            writer.Write(prop.SpotAngle);
+            writer.Write(prop.ShadowStrength);
+            writer.Write(prop.LightColour);
+        }
+
+        public override void Deserialize(LightProperty prop, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            prop.Rotation = reader.ReadQuaternion();
+            prop.Intensity = reader.ReadSingle();
+            prop.Range = reader.ReadSingle();
+            prop.SpotAngle = reader.ReadSingle();
+            prop.ShadowStrength = reader.ReadSingle();
+            prop.LightColour = reader.ReadColour();
+        }
+    }
+}

+ 46 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/CameraManagerSerializer.cs

@@ -0,0 +1,46 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class CameraManagerSerializer : Serializer<CameraManager>
+    {
+        private const short version = 1;
+        private static Serializer<CameraInfo> InfoSerializer => Serialization.Get<CameraInfo>();
+        private static readonly CameraInfo dummyInfo = new();
+
+        public override void Serialize(CameraManager manager, BinaryWriter writer)
+        {
+            writer.Write(CameraManager.header);
+            writer.WriteVersion(version);
+
+            CameraInfo[] cameraInfos = GetCameraInfos(manager);
+            cameraInfos[manager.CurrentCameraIndex].UpdateInfo(CameraUtility.MainCamera);
+
+            writer.Write(manager.CurrentCameraIndex);
+            writer.Write(manager.CameraCount);
+            foreach (var info in cameraInfos) InfoSerializer.Serialize(info, writer);
+        }
+
+        public override void Deserialize(CameraManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var camera = CameraUtility.MainCamera;
+
+            manager.CurrentCameraIndex = reader.ReadInt32();
+
+            var cameraCount = reader.ReadInt32();
+
+            CameraInfo[] cameraInfos = GetCameraInfos(manager);
+            for (var i = 0; i < cameraCount; i++)
+                InfoSerializer.Deserialize(i >= manager.CameraCount ? dummyInfo : cameraInfos[i], reader, metadata);
+
+            if (metadata.Environment) return;
+
+            cameraInfos[manager.CurrentCameraIndex].Apply(camera);
+        }
+
+        private static CameraInfo[] GetCameraInfos(CameraManager manager)
+            => Utility.GetFieldValue<CameraManager, CameraInfo[]>(manager, "cameraInfos");
+    }
+}

+ 43 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/EffectManagerSerializer.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class EffectManagerSerializer : Serializer<EffectManager>
+    {
+        private const short version = 1;
+
+        public override void Serialize(EffectManager manager, BinaryWriter writer)
+        {
+            writer.Write(EffectManager.header);
+            writer.WriteVersion(version);
+
+            foreach (var effectManager in GetEffectManagers(manager).Values)
+                Serialization.Get(effectManager.GetType()).Serialize(effectManager, writer);
+
+            writer.Write(EffectManager.footer);
+        }
+
+        public override void Deserialize(EffectManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            Dictionary<string, IEffectManager> headerToManager = GetEffectManagers(manager).ToDictionary(
+                x => (string) x.Key.GetField("header").GetValue(null),
+                y => y.Value
+            );
+
+            string header;
+            while ((header = reader.ReadString()) != EffectManager.footer)
+            {
+                var effectManager = headerToManager[header];
+                Serialization.Get(effectManager.GetType()).Deserialize(effectManager, reader, metadata);
+            }
+        }
+
+        private static Dictionary<Type, IEffectManager> GetEffectManagers(EffectManager manager)
+            => Utility.GetFieldValue<EffectManager, Dictionary<Type, IEffectManager>>(manager, "EffectManagers");
+    }
+}

+ 66 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/EnvironmentManagerSerializer.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class EnvironmentManagerSerializer : Serializer<EnvironmentManager>
+    {
+        private const short version = 1;
+
+        private static SimpleSerializer<TransformDTO> TransformDtoSerializer => Serialization.GetSimple<TransformDTO>();
+
+        public override void Serialize(EnvironmentManager manager, BinaryWriter writer)
+        {
+            writer.Write(EnvironmentManager.header);
+            writer.WriteVersion(version);
+
+            writer.Write(manager.CurrentBgAsset);
+
+            TransformDtoSerializer.Serialize(new TransformDTO(GetBgTransform(manager)), writer);
+        }
+
+        public override void Deserialize(EnvironmentManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var bg = GetBgTransform(manager);
+
+            var bgAsset = reader.ReadString();
+
+            var transformDto = TransformDtoSerializer.Deserialize(reader, metadata);
+
+            var creativeBg = Utility.IsGuidString(bgAsset);
+
+            List<string> bgList = creativeBg
+                ? Constants.MyRoomCustomBGList.ConvertAll(kvp => kvp.Key)
+                : Constants.BGList;
+
+            var assetIndex = bgList.FindIndex(
+                asset => asset.Equals(bgAsset, StringComparison.InvariantCultureIgnoreCase)
+            );
+
+            var validBg = assetIndex >= 0;
+
+            if (validBg) bgAsset = bgList[assetIndex];
+            else
+            {
+                Utility.LogWarning($"Could not load BG '{bgAsset}'");
+                creativeBg = false;
+                bgAsset = EnvironmentManager.defaultBg;
+            }
+
+            manager.ChangeBackground(bgAsset, creativeBg);
+
+            if (!validBg) return;
+
+            bg.position = transformDto.Position;
+            bg.rotation = transformDto.Rotation;
+            bg.localScale = transformDto.LocalScale;
+        }
+
+        private static Transform GetBgTransform(EnvironmentManager manager)
+            => Utility.GetFieldValue<EnvironmentManager, Transform>(manager, "bg");
+    }
+}

+ 43 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/LightManagerSerializer.cs

@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class LightManagerSerializer : Serializer<LightManager>
+    {
+        private const short version = 1;
+        private static Serializer<DragPointLight> LightSerializer => Serialization.Get<DragPointLight>();
+
+        public override void Serialize(LightManager manager, BinaryWriter writer)
+        {
+            writer.Write(LightManager.header);
+            writer.WriteVersion(version);
+
+            List<DragPointLight> list = GetLightList(manager);
+            writer.Write(list.Count);
+            foreach (var light in list) LightSerializer.Serialize(light, writer);
+        }
+
+        public override void Deserialize(LightManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            manager.ClearLights();
+
+            _ = reader.ReadVersion();
+
+            var lightCount = reader.ReadInt32();
+
+            List<DragPointLight> list = GetLightList(manager);
+            
+            
+            LightSerializer.Deserialize(list[0], reader, metadata);
+            for (var i = 1; i < lightCount; i++)
+            {
+                manager.AddLight();
+                LightSerializer.Deserialize(list[i], reader, metadata);
+            }
+        }
+
+        private static List<DragPointLight> GetLightList(LightManager manager)
+            => Utility.GetFieldValue<LightManager, List<DragPointLight>>(manager, "lightList");
+    }
+}

+ 86 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/MeidoManagerSerializer.cs

@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class MeidoManagerSerializer : Serializer<MeidoManager>
+    {
+        private const short version = 1;
+        private static Serializer<Meido> MeidoSerializer => Serialization.Get<Meido>();
+
+        public override void Serialize(MeidoManager manager, BinaryWriter writer)
+        {
+            writer.Write(MeidoManager.header);
+            writer.WriteVersion(version);
+
+            List<Meido> meidoList = manager.ActiveMeidoList;
+
+            var meidoCount = meidoList.Count;
+
+            var hairPosition = Vector3.zero;
+            var skirtPosition = Vector3.zero;
+
+            var hairMeidoFound = false;
+            var skirtMeidoFound = false;
+
+            var globalGravity = manager.GlobalGravity;
+
+            writer.Write(meidoCount);
+            foreach (var meido in meidoList)
+            {
+                MeidoSerializer.Serialize(meido, writer);
+
+                if (!globalGravity || meidoCount <= 0) continue;
+
+                // Get gravity and skirt control positions to apply to meidos past the meido count
+                if (!hairMeidoFound && meido.HairGravityControl.Valid)
+                {
+                    hairPosition = meido.HairGravityControl.Control.transform.localPosition;
+                    hairMeidoFound = true;
+                }
+                else if (!skirtMeidoFound && meido.SkirtGravityControl.Valid)
+                {
+                    skirtPosition = meido.SkirtGravityControl.Control.transform.localPosition;
+                    skirtMeidoFound = true;
+                }
+            }
+
+            writer.Write(globalGravity);
+            writer.Write(hairPosition);
+            writer.Write(skirtPosition);
+        }
+
+        public override void Deserialize(MeidoManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var meidoCount = reader.ReadInt32();
+            for (var i = 0; i < meidoCount; i++)
+            {
+                if (i >= manager.ActiveMeidoList.Count)
+                {
+                    reader.BaseStream.Seek(reader.ReadInt64(), SeekOrigin.Current);
+                    continue;
+                }
+
+                MeidoSerializer.Deserialize(manager.ActiveMeidoList[i], reader, metadata);
+            }
+
+            var globalGravity = reader.ReadBoolean();
+            var hairPosition = reader.ReadVector3();
+            var skirtPosition = reader.ReadVector3();
+            Utility.SetFieldValue(manager, "globalGravity", globalGravity);
+
+            if (!globalGravity) return;
+
+            foreach (var meido in manager.ActiveMeidoList)
+            {
+                meido.HairGravityActive = true;
+                meido.SkirtGravityActive = true;
+                meido.ApplyGravity(hairPosition);
+                meido.ApplyGravity(skirtPosition, true);
+            }
+        }
+    }
+}

+ 33 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/MessageWindowManagerSerializer.cs

@@ -0,0 +1,33 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class MessageWindowManagerSerializer : Serializer<MessageWindowManager>
+    {
+        private const short version = 1;
+
+        public override void Serialize(MessageWindowManager manager, BinaryWriter writer)
+        {
+            writer.Write(MessageWindowManager.header);
+            writer.WriteVersion(version);
+
+            writer.Write(manager.ShowingMessage);
+            writer.Write(manager.FontSize);
+            writer.Write(manager.MessageName);
+            writer.Write(manager.MessageText);
+        }
+
+        public override void Deserialize(MessageWindowManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            manager.CloseMessagePanel();
+
+            _ = reader.ReadVersion();
+
+            var showingMessage = reader.ReadBoolean();
+            manager.FontSize = reader.ReadInt32();
+            var messageName = reader.ReadString();
+            var messageText = reader.ReadString();
+            if (showingMessage) manager.ShowMessage(messageName, messageText);
+        }
+    }
+}

+ 69 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/ManagerSerializers/PropManagerSerializer.cs

@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class PropManagerSerializer : Serializer<PropManager>
+    {
+        private const short version = 1;
+
+        private static SimpleSerializer<DragPointPropDTO> DragPointDtoSerializer
+            => Serialization.GetSimple<DragPointPropDTO>();
+
+        public override void Serialize(PropManager manager, BinaryWriter writer)
+        {
+            writer.Write(PropManager.header);
+            writer.WriteVersion(version);
+
+            List<DragPointProp> propList = GetPropList(manager);
+
+            writer.Write(propList.Count);
+            foreach (var prop in propList) DragPointDtoSerializer.Serialize(new DragPointPropDTO(prop), writer);
+        }
+
+        public override void Deserialize(PropManager manager, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            manager.DeleteAllProps();
+
+            List<DragPointProp> propList = GetPropList(manager);
+
+            var propCount = reader.ReadInt32();
+            var propIndex = 0;
+            for (var i = 0; i < propCount; i++)
+            {
+                var dragPointPropDto = DragPointDtoSerializer.Deserialize(reader, metadata);
+
+                if (!manager.AddFromPropInfo(dragPointPropDto.PropInfo)) continue;
+
+                Apply(manager, propList[propIndex], dragPointPropDto);
+
+                propIndex++;
+            }
+        }
+
+        private static void Apply(PropManager manager, DragPointProp prop, DragPointPropDTO dto)
+        {
+            var (transformDto, attachPointInfo, shadowCasting) = dto;
+
+            prop.ShadowCasting = shadowCasting;
+
+            var transform = prop.MyObject;
+
+            if (attachPointInfo.AttachPoint != AttachPoint.None)
+            {
+                manager.AttachProp(prop, attachPointInfo.AttachPoint, attachPointInfo.MaidIndex);
+                transform.localPosition = transformDto.LocalPosition;
+                transform.localRotation = transformDto.LocalRotation;
+            }
+
+            transform.position = transformDto.Position;
+            transform.rotation = transformDto.Rotation;
+            transform.localScale = transformDto.LocalScale;
+        }
+
+        private static List<DragPointProp> GetPropList(PropManager manager)
+            => Utility.GetFieldValue<PropManager, List<DragPointProp>>(manager, "propList");
+    }
+}

+ 277 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/Serializers/MeidoSerializer.cs

@@ -0,0 +1,277 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class MeidoSerializer : Serializer<Meido>
+    {
+        private const short version = 1;
+        private const short headVersion = 1;
+        private const short bodyVersion = 1;
+        private const short clothingVersion = 1;
+
+        private static SimpleSerializer<PoseInfo> PoseInfoSerializer => Serialization.GetSimple<PoseInfo>();
+
+        private static SimpleSerializer<TransformDTO> TransformDtoSerializer => Serialization.GetSimple<TransformDTO>();
+
+        public override void Serialize(Meido meido, BinaryWriter writer)
+        {
+            var maid = meido.Maid;
+
+            using var memoryStream = new MemoryStream();
+            using var tempWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
+
+            tempWriter.WriteVersion(version);
+
+            TransformDtoSerializer.Serialize(new TransformDTO(maid.transform), tempWriter);
+
+            SerializeHead(meido, tempWriter);
+
+            SerializeBody(meido, tempWriter);
+
+            SerializeClothing(meido, tempWriter);
+
+            writer.Write(memoryStream.Length);
+            writer.Write(memoryStream.ToArray());
+        }
+
+        public override void Deserialize(Meido meido, BinaryReader reader, SceneMetadata metadata)
+        {
+            var maid = meido.Maid;
+
+            maid.GetAnimation().Stop();
+            meido.DetachAllMpnAttach();
+            meido.StopBlink();
+
+            reader.ReadInt64(); // data length
+
+            _ = reader.ReadVersion();
+
+            var transformDto = TransformDtoSerializer.Deserialize(reader, metadata);
+            var maidTransform = maid.transform;
+            maidTransform.position = transformDto.Position;
+            maidTransform.rotation = transformDto.Rotation;
+            maidTransform.localScale = transformDto.LocalScale;
+
+            meido.IKManager.SetDragPointScale(maidTransform.localScale.x);
+
+            DeserializeHead(meido, reader, metadata);
+
+            DeserializeBody(meido, reader, metadata);
+
+            DeserializeClothing(meido, reader, metadata);
+        }
+
+        private static void SerializeHead(Meido meido, BinaryWriter writer)
+        {
+            var body = meido.Body;
+
+            writer.WriteVersion(headVersion);
+
+            // eye direction
+            writer.WriteQuaternion(body.quaDefEyeL * Quaternion.Inverse(meido.DefaultEyeRotL));
+            writer.WriteQuaternion(body.quaDefEyeR * Quaternion.Inverse(meido.DefaultEyeRotR));
+
+            // free look
+            writer.Write(meido.FreeLook);
+            writer.WriteVector3(body.offsetLookTarget);
+            writer.WriteVector3(Utility.GetFieldValue<TBody, Vector3>(body, "HeadEulerAngle"));
+
+            // Head/eye to camera
+            writer.Write(meido.HeadToCam);
+            writer.Write(meido.EyeToCam);
+
+            // face
+            Dictionary<string, float> faceDict = meido.SerializeFace();
+            writer.Write(faceDict.Count);
+            foreach (var (hash, value) in faceDict)
+            {
+                writer.Write(hash);
+                writer.Write(value);
+            }
+        }
+
+        private static void SerializeBody(Meido meido, BinaryWriter writer)
+        {
+            writer.WriteVersion(bodyVersion);
+
+            // pose
+            var poseBuffer = meido.SerializePose(true);
+            writer.Write(poseBuffer.Length);
+            writer.Write(poseBuffer);
+
+            PoseInfoSerializer.Serialize(meido.CachedPose, writer);
+        }
+
+        private static void SerializeClothing(Meido meido, BinaryWriter writer)
+        {
+            var maid = meido.Maid;
+            var body = meido.Body;
+
+            writer.WriteVersion(clothingVersion);
+
+            // body visible
+            writer.Write(body.GetMask(TBody.SlotID.body));
+
+            // clothing
+            foreach (var clothingSlot in MaidDressingPane.ClothingSlots)
+            {
+                var value = true;
+                if (clothingSlot == TBody.SlotID.wear)
+                {
+                    if (MaidDressingPane.WearSlots.Any(slot => body.GetSlotLoaded(slot)))
+                    {
+                        value = MaidDressingPane.WearSlots.Any(slot => body.GetMask(slot));
+                    }
+                }
+                else if (clothingSlot == TBody.SlotID.megane)
+                {
+                    var slots = new[] { TBody.SlotID.megane, TBody.SlotID.accHead };
+                    if (slots.Any(slot => body.GetSlotLoaded(slot))) { value = slots.Any(slot => body.GetMask(slot)); }
+                }
+                else if (body.GetSlotLoaded(clothingSlot)) value = body.GetMask(clothingSlot);
+
+                writer.Write(value);
+            }
+
+            // zurashi and mekure
+            writer.Write(meido.CurlingFront);
+            writer.Write(meido.CurlingBack);
+            writer.Write(meido.PantsuShift);
+
+            // mpn attach props
+            var hasKousokuUpper = body.GetSlotLoaded(TBody.SlotID.kousoku_upper);
+            writer.Write(hasKousokuUpper);
+            writer.Write(maid.GetProp(MPN.kousoku_upper).strTempFileName);
+
+            var hasKousokuLower = body.GetSlotLoaded(TBody.SlotID.kousoku_lower);
+            writer.Write(hasKousokuLower);
+            writer.Write(maid.GetProp(MPN.kousoku_lower).strTempFileName);
+
+            // hair/skirt gravity
+            writer.Write(meido.HairGravityActive);
+            writer.Write(meido.HairGravityControl.Control.transform.localPosition);
+
+            writer.Write(meido.SkirtGravityActive);
+            writer.Write(meido.SkirtGravityControl.Control.transform.localPosition);
+        }
+
+        private static void DeserializeHead(Meido meido, BinaryReader reader, SceneMetadata metadata)
+        {
+            var body = meido.Body;
+
+            _ = reader.ReadVersion();
+
+            body.quaDefEyeL = reader.ReadQuaternion() * meido.DefaultEyeRotL;
+            body.quaDefEyeR = reader.ReadQuaternion() * meido.DefaultEyeRotR;
+
+            var freeLook = meido.FreeLook = reader.ReadBoolean();
+            var offsetLookTarget = reader.ReadVector3();
+            var headEulerAngle = reader.ReadVector3();
+
+            if (freeLook) body.offsetLookTarget = offsetLookTarget;
+
+            if (!metadata.MMConverted)
+            {
+                Utility.SetFieldValue(body, "HeadEulerAngleG", Vector3.zero);
+                Utility.SetFieldValue(body, "HeadEulerAngle", headEulerAngle);
+            }
+
+            meido.HeadToCam = reader.ReadBoolean();
+            meido.EyeToCam = reader.ReadBoolean();
+
+            var faceBlendCount = reader.ReadInt32();
+            for (var i = 0; i < faceBlendCount; i++)
+            {
+                var hash = reader.ReadString();
+                var value = reader.ReadSingle();
+                meido.SetFaceBlendValue(hash, value);
+            }
+        }
+
+        private static void DeserializeBody(Meido meido, BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            var muneSetting = new KeyValuePair<bool, bool>(true, true);
+            if (metadata.MMConverted) meido.IKManager.Deserialize(reader);
+            else
+            {
+                var poseBufferLength = reader.ReadInt32();
+                byte[] poseBuffer = reader.ReadBytes(poseBufferLength);
+                muneSetting = meido.SetFrameBinary(poseBuffer);
+            }
+
+            var poseInfo = PoseInfoSerializer.Deserialize(reader, metadata);
+            Utility.SetPropertyValue(meido, nameof(Meido.CachedPose), poseInfo);
+            
+            meido.SetMune(!muneSetting.Key, true);
+            meido.SetMune(!muneSetting.Value);
+        }
+
+        private static void DeserializeClothing(Meido meido, BinaryReader reader, SceneMetadata metadata)
+        {
+            var body = meido.Body;
+
+            _ = reader.ReadVersion();
+
+            meido.SetBodyMask(reader.ReadBoolean());
+
+            foreach (var clothingSlot in MaidDressingPane.ClothingSlots)
+            {
+                var value = reader.ReadBoolean();
+                if (metadata.MMConverted) continue;
+
+                if (clothingSlot == TBody.SlotID.wear)
+                {
+                    body.SetMask(TBody.SlotID.wear, value);
+                    body.SetMask(TBody.SlotID.mizugi, value);
+                    body.SetMask(TBody.SlotID.onepiece, value);
+                }
+                else if (clothingSlot == TBody.SlotID.megane)
+                {
+                    body.SetMask(TBody.SlotID.megane, value);
+                    body.SetMask(TBody.SlotID.accHead, value);
+                }
+                else if (body.GetSlotLoaded(clothingSlot)) body.SetMask(clothingSlot, value);
+            }
+
+            // zurashi and mekure
+            var curlingFront = reader.ReadBoolean();
+            var curlingBack = reader.ReadBoolean();
+            var curlingPantsu = reader.ReadBoolean();
+
+            if (!metadata.MMConverted)
+            {
+                if (meido.CurlingFront != curlingFront) meido.SetCurling(Meido.Curl.Front, curlingFront);
+                if (meido.CurlingBack != curlingBack) meido.SetCurling(Meido.Curl.Back, curlingBack);
+                meido.SetCurling(Meido.Curl.Shift, curlingPantsu);
+            }
+
+            // MPN attach upper prop
+            var hasKousokuUpper = reader.ReadBoolean();
+            var upperMenuFile = reader.ReadString();
+            if (hasKousokuUpper) meido.SetMpnProp(new MpnAttachProp(MPN.kousoku_upper, upperMenuFile), false);
+
+            // MPN attach lower prop
+            var hasKousokuLower = reader.ReadBoolean();
+            var lowerMenuFile = reader.ReadString();
+            if (hasKousokuLower) meido.SetMpnProp(new MpnAttachProp(MPN.kousoku_lower, lowerMenuFile), false);
+
+            // hair gravity
+            var hairGravityActive = reader.ReadBoolean();
+            var hairPosition = reader.ReadVector3();
+            meido.HairGravityActive = hairGravityActive;
+            if (meido.HairGravityActive) meido.ApplyGravity(hairPosition);
+
+            // skirt gravity
+            var skirtGravityActive = reader.ReadBoolean();
+            var skirtPosition = reader.ReadVector3();
+            meido.SkirtGravityActive = skirtGravityActive;
+            if (meido.SkirtGravityActive) meido.ApplyGravity(skirtPosition, true);
+        }
+    }
+}

+ 15 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializer.cs

@@ -0,0 +1,15 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public abstract class SimpleSerializer<T> : ISimpleSerializer
+    {
+        void ISimpleSerializer.Serialize(object obj, BinaryWriter writer) => Serialize((T) obj, writer);
+
+        object ISimpleSerializer.Deserialize(BinaryReader reader, SceneMetadata metadata)
+            => Deserialize(reader, metadata);
+
+        public abstract void Serialize(T obj, BinaryWriter writer);
+        public abstract T Deserialize(BinaryReader reader, SceneMetadata metadata);
+    }
+}

+ 66 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/DragPointPropDTOSerializer.cs

@@ -0,0 +1,66 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class DragPointPropDTOSerializer : SimpleSerializer<DragPointPropDTO>
+    {
+        private const short version = 1;
+
+        private static SimpleSerializer<PropInfo> PropInfoSerializer => Serialization.GetSimple<PropInfo>();
+        private static SimpleSerializer<TransformDTO> TransformSerializer => Serialization.GetSimple<TransformDTO>();
+
+        private static SimpleSerializer<AttachPointInfo> AttachPointSerializer
+            => Serialization.GetSimple<AttachPointInfo>();
+
+        public override void Serialize(DragPointPropDTO dragPointDto, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            PropInfoSerializer.Serialize(dragPointDto.PropInfo, writer);
+
+            TransformSerializer.Serialize(dragPointDto.TransformDTO, writer);
+
+            AttachPointSerializer.Serialize(dragPointDto.AttachPointInfo, writer);
+
+            writer.Write(dragPointDto.ShadowCasting);
+        }
+
+        public override DragPointPropDTO Deserialize(BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            return new DragPointPropDTO
+            {
+                PropInfo = PropInfoSerializer.Deserialize(reader, metadata),
+                TransformDTO = TransformSerializer.Deserialize(reader, metadata),
+                AttachPointInfo = AttachPointSerializer.Deserialize(reader, metadata),
+                ShadowCasting = reader.ReadBoolean()
+            };
+        }
+    }
+
+    public class DragPointPropDTO
+    {
+        public TransformDTO TransformDTO { get; init; }
+        public AttachPointInfo AttachPointInfo { get; init; }
+        public PropInfo PropInfo { get; init; }
+        public bool ShadowCasting { get; init; }
+
+        public DragPointPropDTO() { }
+
+        public DragPointPropDTO(DragPointProp dragPoint)
+        {
+            TransformDTO = new TransformDTO(dragPoint.MyObject.transform);
+            ShadowCasting = dragPoint.ShadowCasting;
+            AttachPointInfo = dragPoint.AttachPointInfo;
+            PropInfo = dragPoint.Info;
+        }
+
+        public void Deconstruct(out TransformDTO transform, out AttachPointInfo attachPointInfo, out bool shadowCasting)
+        {
+            transform = TransformDTO;
+            attachPointInfo = AttachPointInfo;
+            shadowCasting = ShadowCasting;
+        }
+    }
+}

+ 25 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/PoseInfoSerializer.cs

@@ -0,0 +1,25 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class PoseInfoSerializer : SimpleSerializer<PoseInfo>
+    {
+        private const short version = 1;
+
+        public override void Serialize(PoseInfo obj, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            writer.Write(obj.PoseGroup);
+            writer.Write(obj.Pose);
+            writer.Write(obj.CustomPose);
+        }
+
+        public override PoseInfo Deserialize(BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            return new PoseInfo(reader.ReadString(), reader.ReadString(), reader.ReadBoolean());
+        }
+    }
+}

+ 33 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/PropInfoSerializer.cs

@@ -0,0 +1,33 @@
+using System.IO;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class PropInfoSerializer : SimpleSerializer<PropInfo>
+    {
+        private const short version = 1;
+
+        public override void Serialize(PropInfo info, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            writer.Write((int) info.Type);
+            writer.WriteNullableString(info.Filename);
+            writer.WriteNullableString(info.SubFilename);
+            writer.Write(info.MyRoomID);
+            writer.WriteNullableString(info.IconFile);
+        }
+
+        public override PropInfo Deserialize(BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            return new PropInfo ((PropInfo.PropType) reader.ReadInt32())
+            {
+                Filename = reader.ReadNullableString(),
+                SubFilename = reader.ReadNullableString(),
+                MyRoomID = reader.ReadInt32(),
+                IconFile = reader.ReadNullableString()
+            };
+        }
+    }
+}

+ 55 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Serialization/SimpleSerializers/TransformDTOSerializer.cs

@@ -0,0 +1,55 @@
+using System.IO;
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    public class TransformDTOSerializer : SimpleSerializer<TransformDTO>
+    {
+        private const short version = 1;
+
+        public override void Serialize(TransformDTO transform, BinaryWriter writer)
+        {
+            writer.WriteVersion(version);
+
+            writer.Write(transform.Position);
+            writer.Write(transform.Rotation);
+            writer.Write(transform.LocalPosition);
+            writer.Write(transform.LocalRotation);
+            writer.Write(transform.LocalScale);
+        }
+
+        public override TransformDTO Deserialize(BinaryReader reader, SceneMetadata metadata)
+        {
+            _ = reader.ReadVersion();
+
+            return new TransformDTO
+            {
+                Position = reader.ReadVector3(),
+                Rotation = reader.ReadQuaternion(),
+                LocalPosition = reader.ReadVector3(),
+                LocalRotation = reader.ReadQuaternion(),
+                LocalScale = reader.ReadVector3()
+            };
+        }
+    }
+
+    public class TransformDTO
+    {
+        public Vector3 Position { get; init; }
+        public Vector3 LocalPosition { get; init; }
+        public Quaternion Rotation { get; init; }
+        public Quaternion LocalRotation { get; init; }
+        public Vector3 LocalScale { get; init; }
+
+        public TransformDTO() { }
+
+        public TransformDTO(Transform transform)
+        {
+            Position = transform.position;
+            LocalPosition = transform.localPosition;
+            Rotation = transform.rotation;
+            LocalRotation = transform.localRotation;
+            LocalScale = transform.localScale;
+        }
+    }
+}

+ 59 - 7
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Utility.cs

@@ -1,14 +1,18 @@
 using System;
+using System.Collections.Generic;
 using System.Text.RegularExpressions;
 using System.IO;
 using System.Reflection;
 using UnityEngine;
 using System.Linq;
+using Ionic.Zlib;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
 {
     public static class Utility
     {
+        private const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
+            | BindingFlags.Static;
         internal static readonly byte[] pngHeader = { 137, 80, 78, 71, 13, 10, 26, 10 };
         internal static readonly byte[] pngEnd = System.Text.Encoding.ASCII.GetBytes("IEND");
         internal static readonly Regex guidRegEx = new Regex(
@@ -72,12 +76,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             return texture2D;
         }
 
-        public static FieldInfo GetFieldInfo<T>(string field)
-        {
-            const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
-                | BindingFlags.Static;
-            return typeof(T).GetField(field, bindingFlags);
-        }
+        public static FieldInfo GetFieldInfo<T>(string field) => typeof(T).GetField(field, bindingFlags);
 
         public static TValue GetFieldValue<TType, TValue>(TType instance, string field)
         {
@@ -91,6 +90,17 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             GetFieldInfo<TType>(name).SetValue(instance, value);
         }
 
+        public static PropertyInfo GetPropertyInfo<T>(string field) => typeof(T).GetProperty(field, bindingFlags);
+
+        public static TValue GetPropertyValue<TType, TValue>(TType instance, string property)
+        {
+            var propertyInfo = GetPropertyInfo<TType>(property);
+            return propertyInfo == null ? default : (TValue) propertyInfo.GetValue(instance, null);
+        }
+        
+        public static void SetPropertyValue<TType, TValue>(TType instance, string name, TValue value) 
+            => GetPropertyInfo<TType>(name).SetValue(instance, value, null);
+
         public static bool AnyMouseDown()
         {
             return Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2);
@@ -182,7 +192,6 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         {
             byte[] buffer = new byte[8];
             stream.Read(buffer, 0, 8);
-            stream.Position = 0L;
             return BytesEqual(buffer, pngHeader);
         }
 
@@ -208,6 +217,11 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             if (Path.GetExtension(name) != ".txt") name += ".txt";
             File.WriteAllLines(Path.Combine(Constants.configPath, name), list.ToArray());
         }
+
+        public static void WriteToFile(string name, byte[] data)
+        {
+            File.WriteAllBytes(Path.Combine(Constants.configPath, name), data);
+        }
     }
 
     public class MousePosition : MonoBehaviour
@@ -232,6 +246,44 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         }
     }
 
+    public static class KeyValuePairExtensions
+    {
+        public static void Deconstruct<TKey, TValue>(
+            this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value
+        )
+        {
+            key = kvp.Key;
+            value = kvp.Value;
+        }
+    }
+
+    public static class StreamExtensions
+    {
+        public static void CopyTo(this Stream stream, Stream outStream)
+        {
+            var buf = new byte[1024 * 32];
+            int length;
+            while ((length = stream.Read(buf, 0, buf.Length)) > 0) 
+                outStream.Write(buf, 0, length);
+        }
+
+        public static MemoryStream Decompress(this MemoryStream stream)
+        {
+            var dataMemoryStream = new MemoryStream();
+            using var compressionStream = new DeflateStream(stream, CompressionMode.Decompress, true);
+
+            compressionStream.CopyTo(dataMemoryStream);
+            compressionStream.Flush();
+
+            dataMemoryStream.Position = 0L;
+
+            return dataMemoryStream;
+        }
+
+        public static DeflateStream GetCompressionStream(this MemoryStream stream)
+            => new(stream, CompressionMode.Compress);
+    }
+
     public static class CameraUtility
     {
         public static CameraMain MainCamera => GameMain.Instance.MainCamera;