diff --git a/Sledge.Formats.Map.Tests/Formats/TestWorldcraftPrefabLibrary.cs b/Sledge.Formats.Map.Tests/Formats/TestWorldcraftPrefabLibrary.cs index 5ee12f1..58369d3 100644 --- a/Sledge.Formats.Map.Tests/Formats/TestWorldcraftPrefabLibrary.cs +++ b/Sledge.Formats.Map.Tests/Formats/TestWorldcraftPrefabLibrary.cs @@ -1,22 +1,85 @@ -using System; -using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Sledge.Formats.Map.Formats; +using Sledge.Formats.Map.Objects; -namespace Sledge.Formats.Map.Tests.Formats +namespace Sledge.Formats.Map.Tests.Formats; + +[TestClass] +public class TestWorldcraftPrefabLibrary { - [TestClass] - public class TestWorldcraftPrefabLibrary + [TestMethod] + public void TestSimplePrefab() + { + using var file = typeof(TestJackhammerFormat).Assembly.GetManifestResourceStream("Sledge.Formats.Map.Tests.Resources.prefab.boxes.ol"); + var library = new WorldcraftPrefabLibrary(file); + + Assert.AreEqual(2, library.Prefabs.Count); + Assert.AreEqual("some boxes", library.Description); + + Assert.AreEqual("box1", library.Prefabs[0].Name); + Assert.AreEqual("box number one", library.Prefabs[0].Description); + + Assert.AreEqual("box2", library.Prefabs[1].Name); + Assert.AreEqual("second box", library.Prefabs[1].Description); + } + + [TestMethod] + public void TestMultiVersionPrefab() + { + using var file = typeof(TestJackhammerFormat).Assembly.GetManifestResourceStream("Sledge.Formats.Map.Tests.Resources.prefab.multi_version.ol"); + var library = new WorldcraftPrefabLibrary(file); + + Assert.AreEqual(3, library.Prefabs.Count); + Assert.AreEqual("1.5b", library.Prefabs[0].Name); + Assert.AreEqual("2.1", library.Prefabs[1].Name); + Assert.AreEqual("3.3", library.Prefabs[2].Name); + } + + [TestMethod] + public void TestWritingPrefab() { - [TestMethod] - public void TestPrefabLoading() + using var file = typeof(TestJackhammerFormat).Assembly.GetManifestResourceStream("Sledge.Formats.Map.Tests.Resources.prefab.boxes.ol"); + var library = new WorldcraftPrefabLibrary(file); + + var newLib = new WorldcraftPrefabLibrary + { + Description = "write test" + }; + + var testMap = new MapFile(); + testMap.Worldspawn.Children.Add(new Entity { - var file = @"D:\Downloads\worldcraft\Worldcraft 3.3\prefabs\computers.ol"; - var lib = WorldcraftPrefabLibrary.FromFile(file); - Assert.AreEqual(14, lib.Prefabs.Count); - } + ClassName = "this_is_a_test" + }); + + newLib.Prefabs.Add(new Prefab("test1", "testing", library.Prefabs[0].Map)); + newLib.Prefabs.Add(new Prefab("test2", "more testing", library.Prefabs[1].Map)); + newLib.Prefabs.Add(new Prefab("test3", "even more testing", library.Prefabs[1].Map)); + newLib.Prefabs.Add(new Prefab("test4", "final test", testMap)); + + var ms = new MemoryStream(); + newLib.Write(ms); + ms.Position = 0; + + var openLib = new WorldcraftPrefabLibrary(ms); + + Assert.AreEqual(4, openLib.Prefabs.Count); + Assert.AreEqual("write test", openLib.Description); + + Assert.AreEqual("test1", openLib.Prefabs[0].Name); + Assert.AreEqual("testing", openLib.Prefabs[0].Description); + + Assert.AreEqual("test2", openLib.Prefabs[1].Name); + Assert.AreEqual("more testing", openLib.Prefabs[1].Description); + + Assert.AreEqual("test3", openLib.Prefabs[2].Name); + Assert.AreEqual("even more testing", openLib.Prefabs[2].Description); + + Assert.AreEqual("test4", openLib.Prefabs[3].Name); + Assert.AreEqual("final test", openLib.Prefabs[3].Description); + Assert.AreEqual(1, openLib.Prefabs[3].Map.Worldspawn.Children.Count); + Assert.AreEqual("this_is_a_test", openLib.Prefabs[3].Map.Worldspawn.Children.OfType().First().ClassName); } -} +} \ No newline at end of file diff --git a/Sledge.Formats.Map.Tests/Resources/prefab/boxes.ol b/Sledge.Formats.Map.Tests/Resources/prefab/boxes.ol new file mode 100644 index 0000000..a8ac182 Binary files /dev/null and b/Sledge.Formats.Map.Tests/Resources/prefab/boxes.ol differ diff --git a/Sledge.Formats.Map.Tests/Resources/prefab/multi_version.ol b/Sledge.Formats.Map.Tests/Resources/prefab/multi_version.ol new file mode 100644 index 0000000..28ef7b1 Binary files /dev/null and b/Sledge.Formats.Map.Tests/Resources/prefab/multi_version.ol differ diff --git a/Sledge.Formats.Map.Tests/Sledge.Formats.Map.Tests.csproj b/Sledge.Formats.Map.Tests/Sledge.Formats.Map.Tests.csproj index 6ef4f3f..d954c21 100644 --- a/Sledge.Formats.Map.Tests/Sledge.Formats.Map.Tests.csproj +++ b/Sledge.Formats.Map.Tests/Sledge.Formats.Map.Tests.csproj @@ -10,11 +10,17 @@ + + + + + Never + diff --git a/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs b/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs index dc3b60c..d34d360 100644 --- a/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs +++ b/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs @@ -8,6 +8,10 @@ namespace Sledge.Formats.Map.Formats { public class WorldcraftPrefabLibrary { + private static readonly string PrefabLibraryHeader = "Worldcraft Prefab Library\r\n" + (char)0x1A; + private const float Version = 0.1f; + + public string Description { get; set; } public List Prefabs { get; set; } public static WorldcraftPrefabLibrary FromFile(string file) @@ -18,22 +22,28 @@ public static WorldcraftPrefabLibrary FromFile(string file) } } + public WorldcraftPrefabLibrary() + { + Description = ""; + Prefabs = new List(); + } + public WorldcraftPrefabLibrary(Stream stream) { Prefabs = new List(); using (var br = new BinaryReader(stream)) { - var header = br.ReadFixedLengthString(Encoding.ASCII, 28); - var prefabLibraryHeader = "Worldcraft Prefab Library\r\n" + (char)0x1A; - Util.Assert(header == prefabLibraryHeader, $"Incorrect prefab library header. Expected '{prefabLibraryHeader}', got '{header}'."); + var header = br.ReadFixedLengthString(Encoding.ASCII, PrefabLibraryHeader.Length); + Util.Assert(header == PrefabLibraryHeader, $"Incorrect prefab library header. Expected '{PrefabLibraryHeader}', got '{header}'."); var version = br.ReadSingle(); - Util.Assert(Math.Abs(version - 0.1f) < 0.01, $"Unsupported prefab library version number. Expected 0.1, got {version}."); + Util.Assert(Math.Abs(version - Version) < 0.01, $"Unsupported prefab library version number. Expected {Version}, got {version}."); var rmf = new WorldcraftRmfFormat(); var offset = br.ReadUInt32(); var num = br.ReadUInt32(); + Description = br.ReadFixedLengthString(Encoding.ASCII, 500); br.BaseStream.Seek(offset, SeekOrigin.Begin); for (var i = 0; i < num; i++) @@ -41,34 +51,71 @@ public WorldcraftPrefabLibrary(Stream stream) var objOffset = br.ReadUInt32(); var objLength = br.ReadUInt32(); var name = br.ReadFixedLengthString(Encoding.ASCII, 31); - var desc = br.ReadFixedLengthString(Encoding.ASCII, 205); - var _ = br.ReadBytes(300); + var desc = br.ReadFixedLengthString(Encoding.ASCII, 500); + br.ReadBytes(5); // unknown/padding using (var substream = new SubStream(br.BaseStream, objOffset, objLength)) { - - Prefabs.Add(new Prefab - { - Name = name, - Description = desc, - Map = rmf.Read(substream) - }); + Prefabs.Add(new Prefab(name, desc, rmf.Read(substream))); } } } } - public void Save(string file) + public void WriteToFile(string file) { using (var stream = File.OpenWrite(file)) { - Save(stream); + Write(stream); } } - public void Save(Stream stream) + public void Write(Stream stream) { - throw new NotImplementedException("Writing prefab libraries is not currently supported."); - } - } + if (!stream.CanSeek) throw new ArgumentException("stream must be seekable.", nameof(stream)); + using (var bw = new BinaryWriter(stream, Encoding.ASCII, true)) + { + var rmf = new WorldcraftRmfFormat(); + + var offsets = new List<(uint offs, uint len)>(); + + bw.WriteFixedLengthString(Encoding.ASCII, PrefabLibraryHeader.Length, PrefabLibraryHeader); + bw.Write(Version); + var infoOffsetPosition = stream.Position; + bw.Write(0); // offset, write this at the end + bw.Write(Prefabs.Count); + bw.WriteFixedLengthString(Encoding.ASCII, 500, Description); + bw.Write(0); // unknown, worldcraft always has 4 extra bytes after the description + + // write all the map data + foreach (var prefab in Prefabs) + { + var pos = (uint) stream.Position; + rmf.Write(stream, prefab.Map, null); + var len = (uint) stream.Position - pos; + offsets.Add((pos, len)); + } + + // now go back and write the index offset to the header + var startOffset = (uint) stream.Position; + stream.Seek(infoOffsetPosition, SeekOrigin.Begin); + bw.Write(startOffset); + stream.Seek(startOffset, SeekOrigin.Begin); + + // now write the index + for (var i = 0; i < Prefabs.Count; i++) + { + var prefab = Prefabs[i]; + var (offs, len) = offsets[i]; + + bw.Write(offs); + bw.Write(len); + bw.WriteFixedLengthString(Encoding.ASCII, 31, prefab.Name); + bw.WriteFixedLengthString(Encoding.ASCII, 500, prefab.Description); + bw.Write(new byte[5]); // unknown/padding + } + + } + } + } } diff --git a/Sledge.Formats.Map/Objects/Prefab.cs b/Sledge.Formats.Map/Objects/Prefab.cs index 2e441bc..967319a 100644 --- a/Sledge.Formats.Map/Objects/Prefab.cs +++ b/Sledge.Formats.Map/Objects/Prefab.cs @@ -5,5 +5,17 @@ public class Prefab public string Name { get; set; } public string Description { get; set; } public MapFile Map { get; set; } + + public Prefab() + { + // + } + + public Prefab(string name, string description, MapFile map) + { + Name = name; + Description = description; + Map = map; + } } }