From 202b5986653f12168f7bca57ab10181892b223fa Mon Sep 17 00:00:00 2001 From: Duude92 Date: Wed, 31 Jan 2024 13:11:01 +0200 Subject: [PATCH 1/3] Add support for saving prefab into WorldcraftPrefabLibrary --- .../Formats/WorldcraftPrefabLibrary.cs | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs b/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs index dc3b60c..8d495e0 100644 --- a/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs +++ b/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs @@ -68,7 +68,66 @@ public void Save(string file) public void Save(Stream stream) { - throw new NotImplementedException("Writing prefab libraries is not currently supported."); + using (var bw = new BinaryWriter(stream)) + { + var prefabLibraryHeader = "Worldcraft Prefab Library\r\n" + (char)0x1A; + var version = 0.1f; + var rmf = new WorldcraftRmfFormat(); + + var objectOffset = 544; + List meta = new List(); + + bw.WriteFixedLengthString(Encoding.ASCII, 28, prefabLibraryHeader); + bw.Write(version); + + foreach (var prefab in Prefabs) + { + stream.Position = objectOffset; + using (var mapStream = new MemoryStream()) + { + rmf.Write(mapStream, prefab.Map, "2.2"); + var prefabMeta = new PrefabMeta() + { + StartOffset = (uint)objectOffset, + DataLenght =(uint) mapStream.Length, + Name = prefab.Name, + Description = prefab.Description, + }; + meta.Add(prefabMeta); + + mapStream.Position = objectOffset; + mapStream.WriteTo(stream); + + + objectOffset+= (int)mapStream.Length; + } + } + + bw.Seek(32, SeekOrigin.Begin); + bw.Write(objectOffset); + bw.Write(Prefabs.Count); + bw.WriteFixedLengthString(Encoding.ASCII, 500, ""); //PrefabLibraryDescription + bw.Seek(objectOffset, SeekOrigin.Begin); + + + + foreach (var prefabMeta in meta) + { + bw.Write(prefabMeta.StartOffset); + bw.Write(prefabMeta.DataLenght); + bw.WriteFixedLengthString(Encoding.ASCII, 31, prefabMeta.Name); + bw.WriteFixedLengthString(Encoding.ASCII, 205, prefabMeta.Description); + bw.WriteFixedLengthString(Encoding.ASCII, 300, ""); + } + + } + } + private struct PrefabMeta + { + public uint StartOffset; + public uint DataLenght; + public string Name; + public string Description; } - } + } } From 56aaaafcdf14b25925b1940aefee58c0ab874cad Mon Sep 17 00:00:00 2001 From: Duude92 Date: Thu, 1 Feb 2024 10:24:35 +0200 Subject: [PATCH 2/3] Added LibraryDescription read and write --- Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs b/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs index 8d495e0..8908e29 100644 --- a/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs +++ b/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs @@ -8,6 +8,7 @@ namespace Sledge.Formats.Map.Formats { public class WorldcraftPrefabLibrary { + public string LibraryDescription { get; set; } public List Prefabs { get; set; } public static WorldcraftPrefabLibrary FromFile(string file) @@ -34,6 +35,7 @@ public WorldcraftPrefabLibrary(Stream stream) var offset = br.ReadUInt32(); var num = br.ReadUInt32(); + LibraryDescription = br.ReadFixedLengthString(Encoding.ASCII, 500); br.BaseStream.Seek(offset, SeekOrigin.Begin); for (var i = 0; i < num; i++) @@ -106,7 +108,7 @@ public void Save(Stream stream) bw.Seek(32, SeekOrigin.Begin); bw.Write(objectOffset); bw.Write(Prefabs.Count); - bw.WriteFixedLengthString(Encoding.ASCII, 500, ""); //PrefabLibraryDescription + bw.WriteFixedLengthString(Encoding.ASCII, 500, LibraryDescription); //PrefabLibraryDescription bw.Seek(objectOffset, SeekOrigin.Begin); From 1940b894d55881535e4a29868881e59d5da18fcf Mon Sep 17 00:00:00 2001 From: Daniel Walder Date: Sat, 3 Feb 2024 14:21:38 +1000 Subject: [PATCH 3/3] Add unit tests and clean up code --- .../Formats/TestWorldcraftPrefabLibrary.cs | 91 +++++++++++--- .../Resources/prefab/boxes.ol | Bin 0 -> 6726 bytes .../Resources/prefab/multi_version.ol | Bin 0 -> 8137 bytes .../Sledge.Formats.Map.Tests.csproj | 6 + .../Formats/WorldcraftPrefabLibrary.cs | 114 ++++++++---------- Sledge.Formats.Map/Objects/Prefab.cs | 12 ++ 6 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 Sledge.Formats.Map.Tests/Resources/prefab/boxes.ol create mode 100644 Sledge.Formats.Map.Tests/Resources/prefab/multi_version.ol 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 0000000000000000000000000000000000000000..a8ac182fd81aa7224dfed4c50ee8677a025cf39e GIT binary patch literal 6726 zcmeHMO-vI(6n>NjwP0)%e@cw$(HIYa2R(?k{EIX;wF#G+SfGt0K%tea$bqVfF@}p# z6Fs1qc&L;_4<6KugqX+y5)T|n$akW~QcOACZNKVv(MZvpsSp6eDeB$wAxM zH~rZ`ljoA!9QUKU%4lxcK@$&~Y2=ijfU$G@(9IS)z*rgo7-*rr7g}iaL9@U=ztv1d zO8gPu5A2xl^#I0mJW1miLSDU zt;d$o7E|NGUM(l`y848Tm}`H(Wq2D#4O^V6YQ(%Lo(p}o5`j@#V)#>^is5 z;loPW#{Ks!VX>~#-ERiKFUIG!&?L`4B6#3=FEUUs)7&2C`0PxX%jao?*H-5V_O)TZ z2+aKaJQgdw=Of)+X)j&9fha#Nx!rER?@UuugAbnnwZu4}@wh=ZHGj#!7*?hBaqq?l;9ev5YQOwAf!JE2BdVZ%(!TBNwlzU(H(W<_T20B2E zneVX-Yn#$V%zOb2Mw$q}@-EApFDl=HSH7s482RKqMlmyAz{=L> zB1Xz_$)!OKIMtVG_;!o+1-0H>7~$WeO3OcF5uj*>!RA=AAabf+A2o1(->11 z=z=1(iR<|yeEbs@cZ;ydl|u3b{DWsTM~rll`C^PWbxE_nIbA9_A7n1TR`JvH#UvQ1x1G=f<152>!P~7rE!5%X%C9Lp%6Y z+pg=FjW6_*&uJn0zJrJlc&p^jGH5yM>>cQ74@I4k-jD%cqb@7okPMup5u8GDJ;5iZ ns5HhWonA!E@;5NBr!2ESs`IQT7V3!fcH-Xzc|d09-(%n>w3~+k literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..28ef7b1e0fef50e5e83674622c218c5002e766bb GIT binary patch literal 8137 zcmeI%d0bRg6u|N8xTK(FiYY{jnM-6OlBSl*z6nA|VulH*AW4CsPDUxNZCK_~SZUZw zVcDXUZIW2FX#1Y&SY~9Xh1#Yy{mu-xt`m*YhyR-Q@#~y--pNr z)y95RQl+(){n_o?s1JYeRgzyiJXJ|fUygTM6tB9HtB>zWk5`??$E#Ikv5x$8H^!=F z#_`oWUvkS+HqMaq`L1#1$spR6$0`et(k|O`VX4Zec&)XTew50v*fwqCaE2w(tHN5- z-5D=AU8w>~hNE4MspJioIgV#%STejfTBac3ps(6It-@MaT4D968)B&`n`)8ktNAfe z<;{vx=^?2~+R6^+SXv*-vGk*V$y4e<8IF=Z!keiG{{y}Ca^877cF0gA zwOcE!evh!jHh5jSMV`OhajlsanTL1l>WU;+`l#aC*E2rvj?d57$%!^|ewx)E$Lw}F z&OI<9B{5o>ydn}a3)19%kv+9z#}4V((K8E$o%u~S zE;(O$egD#r8QU5dm;3yt8JC_r>0e^=`t=xfj*M5w|DNBpl9e%<UpjHAn7qiFM6&Tvgw$YkuN< zPyPQ({iV+OJ~v~VQGYWwbIjOQ?Wuob>#y&5CAF^qTcBg9t?OEF&9T+bowQe-^joa$ z+uDXU>QhxjiCmcDQ&m`4YD!#8OjL^NrT(Axou3eozv7ZjF8hCdk~G^}ICd(SuJQ=p z9)$BXfJ>~{mv+f9WB2)Co_V2MO))Q0bJ=8G%vkC#o1Qxzn>l9e%=gTTqiFnhqPcPR zR$14)p>?5au5L;zlseR0sojf9ey|i{x}oe z&>aEjfwRyPXQLN-qYwJx90WqX&+CWt5QOv5AM!=Me6tsV3osCaa3Ka`2!`S!T#R8D z4&I2VP=p~I@`pSUQHVwiViAXUj6?zwF$zgY#wC!qy`zzeOECs%7>mm=4woYx<8cKh zAOo3@;!ngROhz_xFa^1|5>t_ft03Q%=A!`9P>3Q-#|+Gb71v-Eu0=7f!}XYrIVizg z%)@*vz(Op7Y@J#$E^dGULBZ-4_DiuCx8gP|!R=UzWmt|CxC3{h3@dRL?#4Y>h1Iwh ze2UhbQnPp2E{uj}3SR&*C|3#PfIoFXAPphQJe7XAKSF{+R43Ye&>5$r3%bG=emDdEI1}B_9RcWp zv(OV~qZfLk5BlO91maxu!+8k8`RI>e3_u7jfcy|S2p3{7hF~Zz!o?Vd;TVBXgdrRe zh(r{k5rbI7As!=t_ft8g{)QGjVEL=mQA24+HjwY>(ja4m{)9j?c0%s~m}VjkvW0TyBrZorMW b2{+>wlwvV%#cf!E+p!eOupBFJ2W0ybdU05Q literal 0 HcmV?d00001 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 8908e29..d34d360 100644 --- a/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs +++ b/Sledge.Formats.Map/Formats/WorldcraftPrefabLibrary.cs @@ -8,7 +8,10 @@ namespace Sledge.Formats.Map.Formats { public class WorldcraftPrefabLibrary { - public string LibraryDescription { get; set; } + 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) @@ -19,23 +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(); - LibraryDescription = br.ReadFixedLengthString(Encoding.ASCII, 500); + Description = br.ReadFixedLengthString(Encoding.ASCII, 500); br.BaseStream.Seek(offset, SeekOrigin.Begin); for (var i = 0; i < num; i++) @@ -43,93 +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) { - using (var bw = new BinaryWriter(stream)) + if (!stream.CanSeek) throw new ArgumentException("stream must be seekable.", nameof(stream)); + using (var bw = new BinaryWriter(stream, Encoding.ASCII, true)) { - var prefabLibraryHeader = "Worldcraft Prefab Library\r\n" + (char)0x1A; - var version = 0.1f; var rmf = new WorldcraftRmfFormat(); - var objectOffset = 544; - List meta = new List(); + var offsets = new List<(uint offs, uint len)>(); - bw.WriteFixedLengthString(Encoding.ASCII, 28, prefabLibraryHeader); - bw.Write(version); + 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) { - stream.Position = objectOffset; - using (var mapStream = new MemoryStream()) - { - rmf.Write(mapStream, prefab.Map, "2.2"); - var prefabMeta = new PrefabMeta() - { - StartOffset = (uint)objectOffset, - DataLenght =(uint) mapStream.Length, - Name = prefab.Name, - Description = prefab.Description, - }; - meta.Add(prefabMeta); - - mapStream.Position = objectOffset; - mapStream.WriteTo(stream); - - - objectOffset+= (int)mapStream.Length; - } + var pos = (uint) stream.Position; + rmf.Write(stream, prefab.Map, null); + var len = (uint) stream.Position - pos; + offsets.Add((pos, len)); } - bw.Seek(32, SeekOrigin.Begin); - bw.Write(objectOffset); - bw.Write(Prefabs.Count); - bw.WriteFixedLengthString(Encoding.ASCII, 500, LibraryDescription); //PrefabLibraryDescription - bw.Seek(objectOffset, SeekOrigin.Begin); - + // 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); - - foreach (var prefabMeta in meta) + // now write the index + for (var i = 0; i < Prefabs.Count; i++) { - bw.Write(prefabMeta.StartOffset); - bw.Write(prefabMeta.DataLenght); - bw.WriteFixedLengthString(Encoding.ASCII, 31, prefabMeta.Name); - bw.WriteFixedLengthString(Encoding.ASCII, 205, prefabMeta.Description); - bw.WriteFixedLengthString(Encoding.ASCII, 300, ""); + 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 } } } - private struct PrefabMeta - { - public uint StartOffset; - public uint DataLenght; - public string Name; - public string Description; - } } } 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; + } } }