diff --git a/Assets/Samples/XR Interaction Toolkit/2.4.3/Starter Assets/XRI Default Input Actions.inputactions b/Assets/Samples/XR Interaction Toolkit/2.4.3/Starter Assets/XRI Default Input Actions.inputactions
index 7fecfee1f5c34b9b79f1d00015ec35091fa8006a..3251980096ec64765380bca12d0f1669d598c037 100644
--- a/Assets/Samples/XR Interaction Toolkit/2.4.3/Starter Assets/XRI Default Input Actions.inputactions	
+++ b/Assets/Samples/XR Interaction Toolkit/2.4.3/Starter Assets/XRI Default Input Actions.inputactions	
@@ -701,6 +701,24 @@
                     "processors": "",
                     "interactions": "",
                     "initialStateCheck": true
+                },
+                {
+                    "name": "save",
+                    "type": "Button",
+                    "id": "0031af44-5ced-4637-b320-dd79ef962b71",
+                    "expectedControlType": "Button",
+                    "processors": "",
+                    "interactions": "",
+                    "initialStateCheck": false
+                },
+                {
+                    "name": "load",
+                    "type": "Button",
+                    "id": "fe45058d-fbb0-4593-9fe2-d0a266c224a7",
+                    "expectedControlType": "Button",
+                    "processors": "",
+                    "interactions": "",
+                    "initialStateCheck": false
                 }
             ],
             "bindings": [
@@ -846,6 +864,28 @@
                     "action": "UI Scroll",
                     "isComposite": false,
                     "isPartOfComposite": false
+                },
+                {
+                    "name": "",
+                    "id": "20571414-9fa7-433d-89db-6df6c520f979",
+                    "path": "<Keyboard>/#(S)",
+                    "interactions": "",
+                    "processors": "",
+                    "groups": "",
+                    "action": "save",
+                    "isComposite": false,
+                    "isPartOfComposite": false
+                },
+                {
+                    "name": "",
+                    "id": "02fe76c0-36c4-4441-8b32-249460b6eb2f",
+                    "path": "<Keyboard>/#(L)",
+                    "interactions": "",
+                    "processors": "",
+                    "groups": "",
+                    "action": "load",
+                    "isComposite": false,
+                    "isPartOfComposite": false
                 }
             ]
         },
diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity
index 8a303a4b9bf9fb24dcd0520b2d5393e613377d5b..39efbdad3bb923eece8987b8616ca0c5ac45c23f 100644
--- a/Assets/Scenes/SampleScene.unity
+++ b/Assets/Scenes/SampleScene.unity
@@ -4472,8 +4472,9 @@ GameObject:
   - component: {fileID: 426651920}
   - component: {fileID: 426651919}
   - component: {fileID: 426651921}
+  - component: {fileID: 426651922}
   m_Layer: 0
-  m_Name: Spawn Block Handler
+  m_Name: Program Controller
   m_TagString: Untagged
   m_Icon: {fileID: 0}
   m_NavMeshLayer: 0
@@ -4522,6 +4523,18 @@ MonoBehaviour:
   m_Name: 
   m_EditorClassIdentifier: 
   editStateAction: {fileID: 2052445844685558139, guid: c348712bda248c246b8c49b3db54643f, type: 3}
+--- !u!114 &426651922
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 426651918}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 3972891316024844e9f4db4ba6131e3b, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
 --- !u!1001 &427008753
 PrefabInstance:
   m_ObjectHideFlags: 0
@@ -18754,7 +18767,7 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 1984668758913251958, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 44.7
       objectReference: {fileID: 0}
     - target: {fileID: 1984668758913251958, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.y
@@ -18782,7 +18795,7 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 1984668759388320319, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 40
       objectReference: {fileID: 0}
     - target: {fileID: 1984668759388320319, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.y
@@ -18818,7 +18831,7 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 1984668759906836222, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 40
       objectReference: {fileID: 0}
     - target: {fileID: 1984668759906836222, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.y
@@ -18838,7 +18851,7 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 1984668759991436502, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 40
       objectReference: {fileID: 0}
     - target: {fileID: 1984668759991436502, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.y
@@ -18874,7 +18887,7 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 1984668760453204319, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 44.7
       objectReference: {fileID: 0}
     - target: {fileID: 1984668760453204319, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.y
@@ -18894,7 +18907,7 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 1984668760497519799, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 40
       objectReference: {fileID: 0}
     - target: {fileID: 1984668760497519799, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.y
@@ -18922,7 +18935,7 @@ PrefabInstance:
       objectReference: {fileID: 0}
     - target: {fileID: 1984668760711245992, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.x
-      value: 0
+      value: 40
       objectReference: {fileID: 0}
     - target: {fileID: 1984668760711245992, guid: 65eb6b98091a5734ba9d9ca871cef69a, type: 3}
       propertyPath: m_AnchoredPosition.y
diff --git a/Assets/Scrips/Controllers.meta b/Assets/Scrips/Controllers.meta
new file mode 100644
index 0000000000000000000000000000000000000000..0b7a1c0db089d98bb4decbe05718e0a298e7b1ee
--- /dev/null
+++ b/Assets/Scrips/Controllers.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f9874c2068d26194b9102aa670dcb6e6
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scrips/Controllers/ProgramController.cs b/Assets/Scrips/Controllers/ProgramController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..845ec377061cbcee93314d8edd19ec16ed47d41d
--- /dev/null
+++ b/Assets/Scrips/Controllers/ProgramController.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using UnityEngine;
+using UnityEngine.InputSystem;
+
+public class ProgramController : MonoBehaviour
+{
+    public SpawnBlockEvent spawnBlockEvent;
+    public Transform UserTransform;
+
+    public InputActionReference saveEventAction;
+    public InputActionReference loadEventAction;
+
+
+    private List<GameObject> _spawnedBlocks = new();
+
+    private string _savePath;
+
+    private void Awake()
+    {
+        this._savePath = Application.persistentDataPath + "/program.save";
+        saveEventAction.action.Enable();
+        saveEventAction.action.performed += SaveProgram;
+
+        loadEventAction.action.Enable();
+        loadEventAction.action.performed += LoadProgram;
+
+        // InputSystem.onDeviceChange += OnDeviceChange;
+    }
+
+    private void OnDestroy()
+    {
+        saveEventAction.action.Disable();
+        saveEventAction.action.performed -= SaveProgram;
+
+        loadEventAction.action.Disable();
+        loadEventAction.action.performed -= LoadProgram;
+
+        // InputSystem.onDeviceChange -= OnDeviceChange;
+    }
+
+    private void OnDeviceChange(InputDevice device, InputDeviceChange change)
+    {
+        switch (change)
+        {
+            case InputDeviceChange.Disconnected:
+                saveEventAction.action.Disable();
+                saveEventAction.action.performed -= SaveProgram;
+                break;
+            case InputDeviceChange.Reconnected:
+                saveEventAction.action.Enable();
+                saveEventAction.action.performed += SaveProgram;
+                break;
+        }
+    }
+
+    // Start is called before the first frame update
+    void Start()
+    {
+
+    }
+
+    // Update is called once per frame
+    void Update()
+    {
+
+    }
+
+    public void RegisterBlock(GameObject block)
+    {
+        _spawnedBlocks.Add(block);
+    }
+
+    private Save CreateSave()
+    {
+        Save save = new();
+        save.UserTransform = this.UserTransform.position;
+
+        foreach (GameObject block in _spawnedBlocks)
+        {
+            save.Blocks.Add(block.transform.position);
+        }
+
+        return save;
+    }
+
+    public void SaveProgram(InputAction.CallbackContext context)
+    {
+        Debug.Log("saving");
+        Save save = CreateSave();
+        SurrogateSelector surrogateSelector = new();
+        surrogateSelector.AddSurrogate(typeof(Vector3), new StreamingContext(StreamingContextStates.All), new Vector3SerializationSurrogate());
+
+        BinaryFormatter binaryFormatter = new();
+        binaryFormatter.SurrogateSelector = surrogateSelector;
+
+        FileStream fs = File.Create(this._savePath);
+        binaryFormatter.Serialize(fs, save);
+        fs.Close();
+        Debug.Log("finished saving");
+    }
+
+    public void LoadProgram(InputAction.CallbackContext context)
+    {
+        Debug.Log("loading");
+        if (File.Exists(this._savePath))
+        {
+            if (_spawnedBlocks.Count > 0) _spawnedBlocks.Clear();
+
+            SurrogateSelector surrogateSelector = new();
+            surrogateSelector.AddSurrogate(typeof(Vector3), new StreamingContext(StreamingContextStates.All), new Vector3SerializationSurrogate());
+
+            BinaryFormatter binaryFormatter = new();
+            binaryFormatter.SurrogateSelector = surrogateSelector;
+            FileStream fs = File.Open(this._savePath, FileMode.Open);
+
+            Save save = (Save)binaryFormatter.Deserialize(fs);
+            fs.Close();
+
+            spawnBlockEvent.LoadBlocks(save.Blocks);
+        }
+    }
+
+}
diff --git a/Assets/Scrips/Controllers/ProgramController.cs.meta b/Assets/Scrips/Controllers/ProgramController.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..85b3b2fb91d28286deda5f16d58e26ee74c03bcb
--- /dev/null
+++ b/Assets/Scrips/Controllers/ProgramController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3972891316024844e9f4db4ba6131e3b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scrips/Events/SpawnBlockEvent.cs b/Assets/Scrips/Events/SpawnBlockEvent.cs
index a219c7cd59dc5f41e8aa30b85ea09d9d2a4b191b..87a69d15497cb232c226b16fa76bc97a88d080c1 100644
--- a/Assets/Scrips/Events/SpawnBlockEvent.cs
+++ b/Assets/Scrips/Events/SpawnBlockEvent.cs
@@ -11,6 +11,8 @@ public class SpawnBlockEvent : MonoBehaviour
     public Transform inputTransform;
     public InputActionReference triggerEventAction;
 
+    public ProgramController programController;
+
     private void Awake()
     {
         spawnPrefab.GetComponent<BlockRotate>().inputTransform = this.inputTransform;
@@ -40,10 +42,24 @@ public class SpawnBlockEvent : MonoBehaviour
                 Vector3 adjustment = Vector3.Scale(hit.normal, objectCollider.bounds.size / 2);
                 spawnedObject.transform.position += adjustment;
                 spawnedObject.transform.parent = this.transform;
+
+                programController.RegisterBlock(spawnedObject);
             }
         }
     }
 
+    public void LoadBlocks(List<Vector3> blocks)
+    {
+        foreach (Vector3 blockPosition in blocks)
+        {
+            GameObject spawnedObject = Instantiate(spawnPrefab, blockPosition, Quaternion.identity);
+            spawnedObject.transform.parent = this.transform;
+
+            programController.RegisterBlock(spawnedObject);
+
+        }
+    }
+
     private void OnDeviceChange(InputDevice device, InputDeviceChange change)
     {
         switch (change)
@@ -67,6 +83,8 @@ public class SpawnBlockEvent : MonoBehaviour
         Vector3 adjustment = Vector3.Scale(this.transform.up, objectCollider.bounds.size / 2);
         spawnedObject.transform.position += adjustment;
         spawnedObject.transform.parent = this.transform;
+
+        programController.RegisterBlock(spawnedObject);
     }
 
     // Update is called once per frame
diff --git a/Assets/Scrips/Utils.meta b/Assets/Scrips/Utils.meta
new file mode 100644
index 0000000000000000000000000000000000000000..dba55af07b112089dc587e6b134ba114ccacb47d
--- /dev/null
+++ b/Assets/Scrips/Utils.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: da4de8fb2afe4654ea22f19e9ad20191
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scrips/Utils/MeshSerializationSurrogate.cs b/Assets/Scrips/Utils/MeshSerializationSurrogate.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a28d542ea885661e03ad31b3fbaac4854e1affd4
--- /dev/null
+++ b/Assets/Scrips/Utils/MeshSerializationSurrogate.cs
@@ -0,0 +1,31 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using UnityEngine;
+
+public class MeshSerializationSurrogate : ISerializationSurrogate
+{
+    private float[] createFlattenedArray(Vector3[] vertices)
+    {
+        float[] flattenedVertices = new float[vertices.Length * 3];
+        for (int i = 0; i < vertices.Length; i++)
+        {
+            flattenedVertices[i * 3] = vertices[i].x;
+            flattenedVertices[i * 3 + 1] = vertices[i].y;
+            flattenedVertices[i * 3 + 2] = vertices[i].z;
+        }
+        return flattenedVertices;
+    }
+
+
+    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
+    {
+        Mesh _mesh = (Mesh)obj;
+        info.AddValue("vertices", createFlattenedArray(_mesh.vertices));
+    }
+
+    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
+    {
+        throw new System.NotImplementedException();
+    }
+}
diff --git a/Assets/Scrips/Utils/MeshSerializationSurrogate.cs.meta b/Assets/Scrips/Utils/MeshSerializationSurrogate.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..e0bc1e39ba78524f9dcfac106702a1a9eb749a25
--- /dev/null
+++ b/Assets/Scrips/Utils/MeshSerializationSurrogate.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 103fa22612a46bc4c9fef566e3dad455
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scrips/Utils/Save.cs b/Assets/Scrips/Utils/Save.cs
new file mode 100644
index 0000000000000000000000000000000000000000..67fdb3526a6262457a15a355a61072e7ae61481f
--- /dev/null
+++ b/Assets/Scrips/Utils/Save.cs
@@ -0,0 +1,10 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+[System.Serializable]
+public class Save
+{
+    public Vector3 UserTransform;
+    public List<Vector3> Blocks = new();
+}
diff --git a/Assets/Scrips/Utils/Save.cs.meta b/Assets/Scrips/Utils/Save.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..9527f58222960d935bfb83cd12c7b49ef1f4ef5e
--- /dev/null
+++ b/Assets/Scrips/Utils/Save.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: febfe6e3d5336ff49ade0c5698217ab4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scrips/Utils/TransformSerializationSurrogate.cs b/Assets/Scrips/Utils/TransformSerializationSurrogate.cs
new file mode 100644
index 0000000000000000000000000000000000000000..40ecc8036392f43c32fb5ac43ac99a8fb8c87d3c
--- /dev/null
+++ b/Assets/Scrips/Utils/TransformSerializationSurrogate.cs
@@ -0,0 +1,53 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using UnityEngine;
+
+public class TransformSerializationSurrogate : ISerializationSurrogate
+{
+    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
+    {
+        Transform _transform = (Transform)obj;
+        Vector3 pos = _transform.position;
+        info.AddValue("posX", pos.x);
+        info.AddValue("posY", pos.y);
+        info.AddValue("posZ", pos.z);
+
+        Vector3 rot = _transform.eulerAngles;
+        info.AddValue("rotX", rot.x);
+        info.AddValue("rotY", rot.y);
+        info.AddValue("rotZ", rot.z);
+
+        Vector3 scale = _transform.localScale;
+        info.AddValue("scaleX", scale.x);
+        info.AddValue("scaleY", scale.y);
+        info.AddValue("scaleZ", scale.z);
+    }
+
+    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
+    {
+        GameObject _obj = (GameObject)obj;
+        Transform _transform = _obj.transform;
+        Vector3 pos = _transform.position;
+
+        pos.x = (float)info.GetValue("posX", typeof(float));
+        pos.y = (float)info.GetValue("posY", typeof(float));
+        pos.z = (float)info.GetValue("posZ", typeof(float));
+        _transform.position = pos;
+        Vector3 rot = new(
+            (float)info.GetValue("rotX", typeof(float)),
+            (float)info.GetValue("rotY", typeof(float)),
+            (float)info.GetValue("rotZ", typeof(float))
+        );
+        _transform.eulerAngles = rot;
+        Vector3 scale = new(
+            (float)info.GetValue("scaleX", typeof(float)),
+            (float)info.GetValue("scaleY", typeof(float)),
+            (float)info.GetValue("scaleZ", typeof(float))
+        );
+        _transform.localScale = scale;
+
+        obj = _transform;
+        return obj;
+    }
+}
diff --git a/Assets/Scrips/Utils/TransformSerializationSurrogate.cs.meta b/Assets/Scrips/Utils/TransformSerializationSurrogate.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..040a429baa92c5ba127c52538844d82a58906b8f
--- /dev/null
+++ b/Assets/Scrips/Utils/TransformSerializationSurrogate.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 69acdbf0094ffc643aa7206dfc735da8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Assets/Scrips/Utils/Vector3SerializationSurrogate.cs b/Assets/Scrips/Utils/Vector3SerializationSurrogate.cs
new file mode 100644
index 0000000000000000000000000000000000000000..98d7a76b682f21ca6688fd67fa3d460749f46857
--- /dev/null
+++ b/Assets/Scrips/Utils/Vector3SerializationSurrogate.cs
@@ -0,0 +1,27 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using UnityEngine;
+
+// source: https://raptorkwok.medium.com/unity-save-and-load-data-adf2c52f4de7
+sealed class Vector3SerializationSurrogate : ISerializationSurrogate
+{
+    // Method called to serialize a Vector3 object
+    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
+    {
+        Vector3 v3 = (Vector3)obj;
+        info.AddValue("x", v3.x);
+        info.AddValue("y", v3.y);
+        info.AddValue("z", v3.z);
+    }
+    // Method called to deserialize a Vector3 object
+    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
+    {
+        Vector3 v3 = (Vector3)obj;
+        v3.x = (float)info.GetValue("x", typeof(float));
+        v3.y = (float)info.GetValue("y", typeof(float));
+        v3.z = (float)info.GetValue("z", typeof(float));
+        obj = v3;
+        return obj;
+    }
+}
diff --git a/Assets/Scrips/Utils/Vector3SerializationSurrogate.cs.meta b/Assets/Scrips/Utils/Vector3SerializationSurrogate.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..9ce07a79e524c219ec6e090e231f55b3efd85e6f
--- /dev/null
+++ b/Assets/Scrips/Utils/Vector3SerializationSurrogate.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ece260a147b323e449a0519644b0ece7
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: