Skip to content

Commit fe515c8

Browse files
committed
Initial support for prefab libraries added to JACK beta version
1 parent f1d34f0 commit fe515c8

File tree

5 files changed

+268
-2
lines changed

5 files changed

+268
-2
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System.IO;
2+
using System.Linq;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
using Sledge.Formats.Map.Formats;
5+
using Sledge.Formats.Map.Objects;
6+
7+
namespace Sledge.Formats.Map.Tests.Formats;
8+
9+
[TestClass]
10+
public class TestJackhammerPrefabLibrary
11+
{
12+
/*
13+
// Commented out because I don't want to include the default JACK files due to licensing.
14+
// Put them into the jack_prefab as embedded resources if you want to run this test.
15+
[DataTestMethod]
16+
[DataRow("Computers.jol")]
17+
[DataRow("Crates.jol")]
18+
[DataRow("Old Stuff.jol")]
19+
[DataRow("Random Objects.jol")]
20+
[DataRow("Usable Objects.jol")]
21+
public void TestDefaultPrefabs(string name)
22+
{
23+
using var file = typeof(TestJackhammerPrefabLibrary).Assembly.GetManifestResourceStream($"Sledge.Formats.Map.Tests.Resources.jack_prefab.{name}");
24+
var library = new JackhammerPrefabLibrary(file);
25+
Console.WriteLine(library.Description);
26+
foreach (var p in library.Prefabs)
27+
{
28+
Console.WriteLine(p.Name + " : " + p.Description);
29+
}
30+
}
31+
*/
32+
33+
// todo: need to create the boxes.jol file for the test, however as of writing it requires the paid version
34+
[TestMethod]
35+
public void TestSimplePrefab()
36+
{
37+
using var file = typeof(TestJackhammerPrefabLibrary).Assembly.GetManifestResourceStream("Sledge.Formats.Map.Tests.Resources.jack_prefab.boxes.jol");
38+
var library = new JackhammerPrefabLibrary(file);
39+
40+
Assert.AreEqual(2, library.Prefabs.Count);
41+
Assert.AreEqual("some boxes", library.Description);
42+
43+
Assert.AreEqual("box1", library.Prefabs[0].Name);
44+
Assert.AreEqual("box number one", library.Prefabs[0].Description);
45+
46+
Assert.AreEqual("box2", library.Prefabs[1].Name);
47+
Assert.AreEqual("second box", library.Prefabs[1].Description);
48+
}
49+
50+
// todo: writing not supported yet. need to know how the unknown fields are handled by the app to find out more
51+
[TestMethod]
52+
public void TestWritingPrefab()
53+
{
54+
using var file = typeof(TestJackhammerPrefabLibrary).Assembly.GetManifestResourceStream("Sledge.Formats.Map.Tests.Resources.jack_prefab.boxes.jol");
55+
var library = new JackhammerPrefabLibrary(file);
56+
57+
var newLib = new JackhammerPrefabLibrary
58+
{
59+
Description = "write test"
60+
};
61+
62+
var testMap = new MapFile();
63+
testMap.Worldspawn.Children.Add(new Entity
64+
{
65+
ClassName = "this_is_a_test"
66+
});
67+
68+
newLib.Prefabs.Add(new Prefab("test1", "testing", library.Prefabs[0].Map));
69+
newLib.Prefabs.Add(new Prefab("test2", "more testing", library.Prefabs[1].Map));
70+
newLib.Prefabs.Add(new Prefab("test3", "even more testing", library.Prefabs[1].Map));
71+
newLib.Prefabs.Add(new Prefab("test4", "final test", testMap));
72+
73+
var ms = new MemoryStream();
74+
newLib.Write(ms);
75+
ms.Position = 0;
76+
77+
var openLib = new JackhammerPrefabLibrary(ms);
78+
79+
Assert.AreEqual(4, openLib.Prefabs.Count);
80+
Assert.AreEqual("write test", openLib.Description);
81+
82+
Assert.AreEqual("test1", openLib.Prefabs[0].Name);
83+
Assert.AreEqual("testing", openLib.Prefabs[0].Description);
84+
85+
Assert.AreEqual("test2", openLib.Prefabs[1].Name);
86+
Assert.AreEqual("more testing", openLib.Prefabs[1].Description);
87+
88+
Assert.AreEqual("test3", openLib.Prefabs[2].Name);
89+
Assert.AreEqual("even more testing", openLib.Prefabs[2].Description);
90+
91+
Assert.AreEqual("test4", openLib.Prefabs[3].Name);
92+
Assert.AreEqual("final test", openLib.Prefabs[3].Description);
93+
Assert.AreEqual(1, openLib.Prefabs[3].Map.Worldspawn.Children.Count);
94+
Assert.AreEqual("this_is_a_test", openLib.Prefabs[3].Map.Worldspawn.Children.OfType<Entity>().First().ClassName);
95+
}
96+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
todo: replace this file with boxes.jol once you are able to create prefabs in free JACK
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using Sledge.Formats.Map.Objects;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
7+
namespace Sledge.Formats.Map.Formats
8+
{
9+
public class JackhammerPrefabLibrary
10+
{
11+
private const int Version = 1;
12+
13+
public string Description { get; set; }
14+
public List<Prefab> Prefabs { get; set; }
15+
16+
public static JackhammerPrefabLibrary FromFile(string file)
17+
{
18+
using (var stream = File.OpenRead(file))
19+
{
20+
return new JackhammerPrefabLibrary(stream);
21+
}
22+
}
23+
24+
public JackhammerPrefabLibrary()
25+
{
26+
Description = "";
27+
Prefabs = new List<Prefab>();
28+
}
29+
30+
public JackhammerPrefabLibrary(Stream stream)
31+
{
32+
Prefabs = new List<Prefab>();
33+
using (var br = new BinaryReader(stream))
34+
{
35+
var header = br.ReadFixedLengthString(Encoding.ASCII, 4);
36+
Util.Assert(header == "JHOL", $"Incorrect prefab library header. Expected 'JHOL', got '{header}'.");
37+
38+
var version = br.ReadInt32();
39+
Util.Assert(version == Version, $"Unsupported prefab library version number. Expected {Version}, got {version}.");
40+
41+
// read header data
42+
var descriptionStringNum = br.ReadInt32();
43+
_ = br.ReadInt32(); // unknown
44+
_ = br.ReadInt32(); // unknown
45+
var singleEntryLength = br.ReadInt32();
46+
var entryDataOffset = br.ReadInt32();
47+
var entryDataLength = br.ReadInt32();
48+
var mapDataOffset = br.ReadInt32();
49+
var mapDataLength = br.ReadInt32();
50+
var imageDataOffset = br.ReadInt32();
51+
var imageDataLength = br.ReadInt32();
52+
var stringDataOffset = br.ReadInt32();
53+
var stringDataLength = br.ReadInt32();
54+
55+
// read strings
56+
br.BaseStream.Seek(stringDataOffset, SeekOrigin.Begin);
57+
58+
var stringCount = br.ReadInt32();
59+
60+
// for whatever reason the offset to each string is stored next, we don't need those
61+
br.BaseStream.Seek(stringCount * 4, SeekOrigin.Current);
62+
63+
var strings = new List<string>
64+
{
65+
"" // string numbers seem to be indexed from 1 so add a blank string for index 0
66+
};
67+
for (var i = 0; i < stringCount; i++)
68+
{
69+
var len = br.ReadInt32();
70+
strings.Add(br.ReadFixedLengthString(Encoding.ASCII, len));
71+
}
72+
73+
Description = strings[descriptionStringNum];
74+
75+
// read entries
76+
br.BaseStream.Seek(entryDataOffset, SeekOrigin.Begin);
77+
78+
var entries = new List<Entry>();
79+
80+
var numEntries = entryDataLength / singleEntryLength;
81+
for (var i = 0; i < numEntries; i++)
82+
{
83+
var name = strings[br.ReadInt32()];
84+
var desc = strings[br.ReadInt32()];
85+
_ = br.ReadInt32(); // unknown
86+
_ = br.ReadInt32(); // unknown
87+
var entryMapOffset = br.ReadInt32();
88+
var entryMapLength = br.ReadInt32();
89+
var entryMapType = br.ReadFixedLengthString(Encoding.ASCII, 4);
90+
var entryImageOffset = br.ReadInt32();
91+
var entryImageLength = br.ReadInt32();
92+
var entryImageType = br.ReadFixedLengthString(Encoding.ASCII, 4);
93+
94+
if (entryMapType != "JHMF")
95+
{
96+
throw new NotSupportedException("Unexpected non-JMF format in Jackhammer prefab library.");
97+
}
98+
99+
// i suspect this is an origin or bounding box, but not sure
100+
(_, _, _) = (br.ReadDouble(), br.ReadDouble(), br.ReadDouble());
101+
102+
var numWorldspawns = br.ReadInt32(); // ?? no idea, always seems to be 1
103+
var numSolids = br.ReadInt32();
104+
var numPointEnts = br.ReadInt32();
105+
var numSolidEnts = br.ReadInt32();
106+
_ = br.ReadInt32(); // unknown
107+
_ = br.ReadInt32(); // unknown
108+
_ = br.ReadInt32(); // unknown
109+
var numUniqueTextures = br.ReadInt32();
110+
111+
entries.Add(new Entry
112+
{
113+
Name = name,
114+
Description = desc,
115+
MapOffset = entryMapOffset,
116+
MapLength = entryMapLength,
117+
MapType = entryMapType,
118+
ImageOffset = entryImageOffset,
119+
ImageLength = entryImageLength,
120+
ImageType = entryImageType
121+
});
122+
}
123+
124+
// read maps and preview images
125+
var jmf = new JackhammerJmfFormat();
126+
127+
foreach (var entry in entries)
128+
{
129+
br.BaseStream.Seek(imageDataOffset + entry.ImageOffset, SeekOrigin.Begin);
130+
var img = br.ReadBytes(entry.ImageLength);
131+
using (var substream = new SubStream(br.BaseStream, mapDataOffset + entry.MapOffset, entry.MapLength))
132+
{
133+
Prefabs.Add(new Prefab(entry.Name, entry.Description, jmf.Read(substream))
134+
{
135+
PreviewImage = img
136+
});
137+
}
138+
}
139+
}
140+
}
141+
142+
public void WriteToFile(string file)
143+
{
144+
using (var stream = File.OpenWrite(file))
145+
{
146+
Write(stream);
147+
}
148+
}
149+
150+
public void Write(Stream stream)
151+
{
152+
throw new NotImplementedException();
153+
}
154+
155+
// record class to hold an entry temporarily
156+
private class Entry
157+
{
158+
public string Name { get; set; }
159+
public string Description { get; set; }
160+
public int MapOffset { get; set; }
161+
public int MapLength { get; set; }
162+
public string MapType { get; set; }
163+
public int ImageOffset { get; set; }
164+
public int ImageLength { get; set; }
165+
public string ImageType { get; set; }
166+
}
167+
}
168+
}

Sledge.Formats.Map/Objects/Prefab.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ public class Prefab
55
public string Name { get; set; }
66
public string Description { get; set; }
77
public MapFile Map { get; set; }
8+
public byte[] PreviewImage { get; set; }
89

910
public Prefab()
1011
{

Sledge.Formats.Map/Sledge.Formats.Map.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
<RepositoryUrl>https://github.com/LogicAndTrick/sledge-formats</RepositoryUrl>
1212
<RepositoryType>Git</RepositoryType>
1313
<PackageTags>half-life quake valve hammer worldcraft jackhammer jack rmf vmf map jmf</PackageTags>
14-
<PackageReleaseNotes>Fix incorrect loading of texture rotation and shift values for pre-2.2 RMF files</PackageReleaseNotes>
15-
<Version>1.2.4</Version>
14+
<PackageReleaseNotes>Initial support for JACK prefab libraries (the JACK version with prefabs is in beta, so compatibility might change)</PackageReleaseNotes>
15+
<Version>1.2.5</Version>
1616
<DebugType>full</DebugType>
1717
<DebugSymbols>true</DebugSymbols>
1818
</PropertyGroup>

0 commit comments

Comments
 (0)