diff --git a/Assets/Scripts/OpenTS2/Content/DBPF/ObjectModuleAsset.cs b/Assets/Scripts/OpenTS2/Content/DBPF/ObjectModuleAsset.cs
new file mode 100644
index 0000000..5fffbf7
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Content/DBPF/ObjectModuleAsset.cs
@@ -0,0 +1,10 @@
+namespace OpenTS2.Content.DBPF
+{
+ ///
+ /// Called cEdithObjectModule in game.
+ ///
+ public class ObjectModuleAsset : AbstractAsset
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Content/DBPF/ObjectModuleAsset.cs.meta b/Assets/Scripts/OpenTS2/Content/DBPF/ObjectModuleAsset.cs.meta
new file mode 100644
index 0000000..5509275
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Content/DBPF/ObjectModuleAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 88d31fc370514416b28927df14c0e41d
+timeCreated: 1727972177
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Content/DBPF/SimsObjectAsset.cs b/Assets/Scripts/OpenTS2/Content/DBPF/SimsObjectAsset.cs
new file mode 100644
index 0000000..ee876dd
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Content/DBPF/SimsObjectAsset.cs
@@ -0,0 +1,7 @@
+namespace OpenTS2.Content.DBPF
+{
+ public class SimsObjectAsset : AbstractAsset
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Content/DBPF/SimsObjectAsset.cs.meta b/Assets/Scripts/OpenTS2/Content/DBPF/SimsObjectAsset.cs.meta
new file mode 100644
index 0000000..f5d0cae
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Content/DBPF/SimsObjectAsset.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ac6c2c1af0484515b3e9c91dc91159dc
+timeCreated: 1727122236
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectModuleCodec.cs b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectModuleCodec.cs
new file mode 100644
index 0000000..da0271f
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectModuleCodec.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+using OpenTS2.Common;
+using OpenTS2.Content;
+using OpenTS2.Content.DBPF;
+using OpenTS2.Files.Utils;
+using UnityEngine;
+
+namespace OpenTS2.Files.Formats.DBPF
+{
+ ///
+ /// Codec for what is known in game as cEdithObjectModule. Likely a container for different types of objects.
+ ///
+ [Codec(TypeIDs.OBJM)]
+ public class ObjectModuleCodec : AbstractCodec
+ {
+ public override AbstractAsset Deserialize(byte[] bytes, ResourceKey tgi, DBPFFile sourceFile)
+ {
+ var asset = new ObjectModuleAsset();
+ var stream = new MemoryStream(bytes);
+ var reader = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN);
+
+ // Skip first 64 bytes.
+ reader.Seek(SeekOrigin.Begin, 64);
+
+ // First int, ignored.
+ reader.Seek(SeekOrigin.Current, 4);
+ // Version number.
+ int version = reader.ReadInt32();
+ Debug.Log($"Version: 0x{version:X}");
+ // Type identifier.
+ uint type = reader.ReadUInt32();
+ if (type != 0x4F626A4D)
+ {
+ // Corresponds to the string "ObjM"
+ throw new NotImplementedException("ObjM file does not have `ObjM` magic bytes");
+ }
+
+ // Next is the number of objects.
+ int numObjects = reader.ReadInt32();
+ Debug.Log($"numObjects: {numObjects}");
+ for (var i = 0; i < numObjects; i++)
+ {
+ int selectorSaveType = reader.ReadInt32();
+ int missingObjectSaveType = reader.ReadInt32();
+
+ Debug.Log($"selectorSaveType: {selectorSaveType}, missingObjectSaveType: {missingObjectSaveType}");
+ break;
+ }
+
+ return asset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectModuleCodec.cs.meta b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectModuleCodec.cs.meta
new file mode 100644
index 0000000..f924f39
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectModuleCodec.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a49ebb5f202a4e24a7d135e40c194bc8
+timeCreated: 1727969785
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectSaveTypeTableCodec.cs b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectSaveTypeTableCodec.cs
index ef045f1..0fb903a 100644
--- a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectSaveTypeTableCodec.cs
+++ b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/ObjectSaveTypeTableCodec.cs
@@ -5,6 +5,7 @@
using OpenTS2.Content;
using OpenTS2.Content.DBPF;
using OpenTS2.Files.Utils;
+using UnityEngine;
namespace OpenTS2.Files.Formats.DBPF
{
diff --git a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/SimsObjectCodec.cs b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/SimsObjectCodec.cs
new file mode 100644
index 0000000..fc2a12b
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/SimsObjectCodec.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using OpenTS2.Common;
+using OpenTS2.Content;
+using OpenTS2.Content.DBPF;
+using OpenTS2.Files.Utils;
+using UnityEngine;
+
+namespace OpenTS2.Files.Formats.DBPF
+{
+ ///
+ /// Codec for what is known in game as cTSObject. Contains attributes and locations of objects.
+ ///
+ [Codec(TypeIDs.XOBJ)]
+ public class SimsObjectCodec : AbstractCodec
+ {
+ public override AbstractAsset Deserialize(byte[] bytes, ResourceKey tgi, DBPFFile sourceFile)
+ {
+ var asset = new SimsObjectAsset();
+ var stream = new MemoryStream(bytes);
+ var reader = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN);
+
+ // Skip first 64 bytes.
+ reader.Seek(SeekOrigin.Begin, 64);
+
+ // TODO: this is for versions = 0xAD, see if we need to handle lower.
+
+ // 4 skipped/unused floats
+ for (int i = 0; i < 4; i++)
+ {
+ reader.ReadFloat();
+ }
+
+ var tileLocationY = reader.ReadFloat();
+ var tileLocationX = reader.ReadFloat();
+ var level = reader.ReadInt32();
+ // ignored int16
+ reader.ReadUInt16();
+ var elevation = reader.ReadFloat();
+ var objectGroupId = reader.ReadInt32();
+ var unknown = reader.ReadInt16();
+
+ Debug.Log($"tileLocationY: {tileLocationY}, tileLocationX: {tileLocationX}, level: {level}, " +
+ $"elevation: {elevation}, objectGroupId: {objectGroupId}, unknown: {unknown}");
+
+ var numAttrs = reader.ReadInt16();
+ var attrs = new short[numAttrs];
+ for (var i = 0; i < numAttrs; i++)
+ {
+ attrs[i] = reader.ReadInt16();
+ }
+ Debug.Log($"numAttrs: {numAttrs}, attrs: [{string.Join(", ", attrs)}]");
+
+ var numSemiAttrs = reader.ReadInt16();
+ var semiAttrs = new short[numSemiAttrs];
+ for (var i = 0; i < numSemiAttrs; i++)
+ {
+ semiAttrs[i] = reader.ReadInt16();
+ }
+ Debug.Log($"numSemiAttrs: {numAttrs}, semiAttrs: [{string.Join(", ", semiAttrs)}]");
+
+ Debug.Log($" Data array offset: {reader.Position:X}");
+
+ /*
+ // 8 unknown shorts called "data".
+ var dataArray = new short[8];
+ for (var i = 0; i < dataArray.Length; i++)
+ {
+ dataArray[i] = reader.ReadInt16();
+ }
+ Debug.Log($"dataArray: [{string.Join(", ", dataArray)}]");
+
+ // Next is a number of shorts that depends on the exact version of the file.
+ uint numShorts = 0x57 + 6;
+ for (var i = 0; i < numShorts; i++)
+ {
+ reader.ReadInt16();
+ }
+
+ // Another 8 shorts, called the "tempTokenFields".
+ for (var i = 0; i < 8; i++)
+ {
+ reader.ReadInt16();
+ }
+
+ // Next is the number of object arrays. Each being a short array itself.
+ var numObjectArrays = reader.ReadInt16();
+ var shortArrays = new List(numObjectArrays);
+ for (var i = 0; i < numObjectArrays; i++)
+ {
+ var objectArray = new short[reader.ReadInt16()];
+ for (var j = 0; j < objectArray.Length; j++)
+ {
+ objectArray[j] = reader.ReadInt16();
+ }
+ shortArrays.Add(objectArray);
+ }
+ Debug.Log($"numObjectArrays: {numObjectArrays}");
+
+ // An array of shorts. Unknown.
+ var numSecondShortArray = reader.ReadInt16();
+ for (var i = 0; i < numSecondShortArray; i++)
+ {
+ reader.ReadInt16();
+ }
+ Debug.Log($"numSecondShortArrays: {numSecondShortArray}");
+
+ var ownershipValue = reader.ReadInt32();
+ Debug.Log($"ownershipValue: {ownershipValue}");
+
+ Debug.Log($" Position before strings: 0x{reader.Position:X}");
+ // A number of material subsitution strings.
+ var numMaterialSubstitues = reader.ReadInt16();
+ Debug.Log($" numMaterialSubstitues: {numMaterialSubstitues}");
+ for (var i = 0; i < numMaterialSubstitues; i++)
+ {
+ var materialSubstitute = reader.ReadVariableLengthPascalString();
+ Debug.Log($"materialSubstitute: {materialSubstitute}");
+ }
+
+ var persistentFlag = reader.ReadUInt16();
+ Debug.Log($"persistentFlag: {persistentFlag}");
+
+ // Slots...
+ var slotsFlag = reader.ReadInt16();
+ Debug.Log($"slotsFlag: {slotsFlag}");
+
+ var numSlots = reader.ReadInt16();
+ for (var i = 0; i < numSlots; i++)
+ {
+ var slotValue = reader.ReadInt16();
+ }
+ Debug.Log($"numSlots: {numSlots}");
+
+ var numEffects = reader.ReadInt16();
+ Debug.Log($"numEffects: {numEffects}");
+
+ Debug.Log($"Position after numEffects: 0x{reader.Position:X}");
+
+ var numbOverrides = reader.ReadInt16();
+ Debug.Log($"numOverides: {numbOverrides}");
+
+ for (int i = 0; i < numbOverrides; i++)
+ {
+ var overrideString1 = reader.ReadVariableLengthPascalString();
+ var overrideString2 = reader.ReadVariableLengthPascalString();
+ var overrideString3 = reader.ReadVariableLengthPascalString();
+ Debug.Log($"{overrideString1} / {overrideString2} / {overrideString3}");
+ }*/
+
+ return asset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Files/Formats/DBPF/SimsObjectCodec.cs.meta b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/SimsObjectCodec.cs.meta
new file mode 100644
index 0000000..4595416
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Files/Formats/DBPF/SimsObjectCodec.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 56e83332c7cd425b96ef041bca7ee9fc
+timeCreated: 1727121939
\ No newline at end of file
diff --git a/Assets/Tests/OpenTS2/Files/Formats/DBPF/ObjectModuleCodecTest.cs b/Assets/Tests/OpenTS2/Files/Formats/DBPF/ObjectModuleCodecTest.cs
new file mode 100644
index 0000000..546a939
--- /dev/null
+++ b/Assets/Tests/OpenTS2/Files/Formats/DBPF/ObjectModuleCodecTest.cs
@@ -0,0 +1,26 @@
+using NUnit.Framework;
+using OpenTS2.Common;
+using OpenTS2.Content;
+using OpenTS2.Content.DBPF;
+using OpenTS2.Files.Formats.DBPF;
+
+public class ObjectModuleCodecTest
+{
+ private uint _groupID;
+
+ [SetUp]
+ public void SetUp()
+ {
+ TestMain.Initialize();
+ _groupID = ContentProvider.Get().AddPackage("TestAssets/Codecs/ObjCodecs.package").GroupID;
+ }
+
+ [Test]
+ public void TestSuccessfullyLoadsObjectModule()
+ {
+ var objectModuleAsset = ContentProvider.Get()
+ .GetAsset(new ResourceKey(0x1, _groupID, TypeIDs.OBJM));
+
+ Assert.That(objectModuleAsset, Is.Not.Null);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Tests/OpenTS2/Files/Formats/DBPF/ObjectModuleCodecTest.cs.meta b/Assets/Tests/OpenTS2/Files/Formats/DBPF/ObjectModuleCodecTest.cs.meta
new file mode 100644
index 0000000..3236872
--- /dev/null
+++ b/Assets/Tests/OpenTS2/Files/Formats/DBPF/ObjectModuleCodecTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c290fd6845f2442c94c96b4a69ddddc3
+timeCreated: 1727972116
\ No newline at end of file
diff --git a/Assets/Tests/OpenTS2/Files/Formats/DBPF/SimsObjectCodecTest.cs b/Assets/Tests/OpenTS2/Files/Formats/DBPF/SimsObjectCodecTest.cs
new file mode 100644
index 0000000..f26c8ca
--- /dev/null
+++ b/Assets/Tests/OpenTS2/Files/Formats/DBPF/SimsObjectCodecTest.cs
@@ -0,0 +1,26 @@
+using System.Linq;
+using NUnit.Framework;
+using OpenTS2.Common;
+using OpenTS2.Content;
+using OpenTS2.Content.DBPF;
+using OpenTS2.Files.Formats.DBPF;
+
+public class SimsObjectCodecTest
+{
+ private uint _groupID;
+
+ [SetUp]
+ public void SetUp()
+ {
+ TestMain.Initialize();
+ _groupID = ContentProvider.Get().AddPackage("TestAssets/Codecs/ObjCodecs.package").GroupID;
+ }
+
+ [Test]
+ public void TestSuccessfullyLoadsSimsObject()
+ {
+ var objectAsset = ContentProvider.Get().GetAsset(new ResourceKey(0x158, _groupID, TypeIDs.XOBJ));
+
+ Assert.That(objectAsset, Is.Not.Null);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Tests/OpenTS2/Files/Formats/DBPF/SimsObjectCodecTest.cs.meta b/Assets/Tests/OpenTS2/Files/Formats/DBPF/SimsObjectCodecTest.cs.meta
new file mode 100644
index 0000000..0811534
--- /dev/null
+++ b/Assets/Tests/OpenTS2/Files/Formats/DBPF/SimsObjectCodecTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 13359c34be754888864a20da2042ea57
+timeCreated: 1727122336
\ No newline at end of file