tModLoader v0.11.8.9
A mod to make and play Terraria mods
TagIO.cs
Go to the documentation of this file.
1using Ionic.Zlib;
2using System;
3using System.Collections;
4using System.Collections.Generic;
5using System.IO;
6using System.Linq;
7using System.Text;
8
10{
11 public static class TagIO
12 {
13 private abstract class PayloadHandler
14 {
15 public abstract Type PayloadType { get; }
16 public abstract object Default();
17 public abstract object Read(BinaryReader r);
18 public abstract void Write(BinaryWriter w, object v);
19 public abstract IList ReadList(BinaryReader r, int size);
20 public abstract void WriteList(BinaryWriter w, IList list);
21 public abstract object Clone(object o);
22 public abstract IList CloneList(IList list);
23 }
24
25 private class PayloadHandler<T> : PayloadHandler
26 {
27 internal Func<BinaryReader, T> reader;
28 internal Action<BinaryWriter, T> writer;
29
31 this.reader = reader;
32 this.writer = writer;
33 }
34
35 public override Type PayloadType => typeof(T);
36 public override object Read(BinaryReader r) => reader(r);
37 public override void Write(BinaryWriter w, object v) => writer(w, (T)v);
38
39 public override IList ReadList(BinaryReader r, int size) {
40 var list = new List<T>(size);
41 for (int i = 0; i < size; i++)
42 list.Add(reader(r));
43
44 return list;
45 }
46
47 public override void WriteList(BinaryWriter w, IList list) => WriteList(w, (IList<T>)list);
48 public void WriteList(BinaryWriter w, IList<T> list) {
49 foreach (T t in list)
50 writer(w, t);
51 }
52
53 public override object Clone(object o) => o;
54 public override IList CloneList(IList list) => CloneList((IList<T>)list);
55 public virtual IList CloneList(IList<T> list) => new List<T>(list);
56
57 public override object Default() => default(T);
58 }
59
60 private class ClassPayloadHandler<T> : PayloadHandler<T> where T : class
61 {
62 private Func<T, T> clone;
63 private Func<T> makeDefault;
64
66 Func<T, T> clone, Func<T> makeDefault = null) :
67 base(reader, writer) {
68 this.clone = clone;
69 this.makeDefault = makeDefault;
70 }
71
72 public override object Clone(object o) => clone((T)o);
73 public override IList CloneList(IList<T> list) => list.Select(clone).ToList();
74 public override object Default() => makeDefault();
75 }
76
77 private static readonly PayloadHandler[] PayloadHandlers = {
78 null,
79 new PayloadHandler<byte>(r => r.ReadByte(), (w, v) => w.Write(v)),
80 new PayloadHandler<short>(r => r.ReadInt16(), (w, v) => w.Write(v)),
81 new PayloadHandler<int>(r => r.ReadInt32(), (w, v) => w.Write(v)),
82 new PayloadHandler<long>(r => r.ReadInt64(), (w, v) => w.Write(v)),
83 new PayloadHandler<float>(r => r.ReadSingle(), (w, v) => w.Write(v)),
84 new PayloadHandler<double>(r => r.ReadDouble(), (w, v) => w.Write(v)),
86 r => r.ReadBytes(r.ReadInt32()),
87 (w, v) => {
88 w.Write(v.Length);
89 w.Write(v);
90 },
91 v => (byte[]) v.Clone(),
92 () => new byte[0]),
94 r => Encoding.UTF8.GetString(r.ReadBytes(r.ReadInt16())),
95 (w, v) => {
96 var b = Encoding.UTF8.GetBytes(v);
97 w.Write((short)b.Length);
98 w.Write(b);
99 },
100 v => v,
101 () => ""),
103 r => GetHandler(r.ReadByte()).ReadList(r, r.ReadInt32()),
104 (w, v) => {
105 int id;
106 try {
107 id = GetPayloadId(v.GetType().GetGenericArguments()[0]);
108 }
109 catch (IOException) {
110 throw new IOException("Invalid NBT list type: "+v.GetType());
111 }
112 w.Write((byte)id);
113 w.Write(v.Count);
114 PayloadHandlers[id].WriteList(w, v);
115 },
116 v => {
117 try {
118 return GetHandler(GetPayloadId(v.GetType().GetGenericArguments()[0])).CloneList(v);
119 }
120 catch (IOException) {
121 throw new ArgumentException("Invalid NBT list type: "+v.GetType());
122 }
123 }),
124 new ClassPayloadHandler<TagCompound>(
125 r => {
126 var compound = new TagCompound();
127 string name;
128 object tag;
129 while ((tag = ReadTag(r, out name)) != null)
130 compound.Set(name, tag);
131
132 return compound;
133 },
134 (w, v) => {
135 foreach (var entry in v)
136 if (entry.Value != null)
137 WriteTag(entry.Key, entry.Value, w);
138
139 w.Write((byte)0);
140 },
141 v => (TagCompound) v.Clone(),
142 () => new TagCompound()),
143 new ClassPayloadHandler<int[]>(
144 r => {
145 var ia = new int[r.ReadInt32()];
146 for (int i = 0; i < ia.Length; i++)
147 ia[i] = r.ReadInt32();
148 return ia;
149 },
150 (w, v) => {
151 w.Write(v.Length);
152 foreach (int i in v)
153 w.Write(i);
154 },
155 v => (int[]) v.Clone(),
156 () => new int[0])
157 };
158
159 private static readonly Dictionary<Type, int> PayloadIDs =
160 Enumerable.Range(1, PayloadHandlers.Length - 1).ToDictionary(i => PayloadHandlers[i].PayloadType);
161
162 private static PayloadHandler<string> StringHandler = (PayloadHandler<string>)PayloadHandlers[8];
163
164 private static PayloadHandler GetHandler(int id) {
165 if (id < 1 || id >= PayloadHandlers.Length)
166 throw new IOException("Invalid NBT payload id: " + id);
167
168 return PayloadHandlers[id];
169 }
170
171 private static int GetPayloadId(Type t) {
172 int id;
173 if (PayloadIDs.TryGetValue(t, out id))
174 return id;
175
176 if (typeof(IList).IsAssignableFrom(t))
177 return 9;
178
179 throw new IOException($"Invalid NBT payload type '{t}'");
180 }
181
182 public static object Serialize(object value) {
183 var type = value.GetType();
184
185 TagSerializer serializer;
186 if (TagSerializer.TryGetSerializer(type, out serializer))
187 return serializer.Serialize(value);
188
189 //does a base level typecheck with throw
190 if (GetPayloadId(type) != 9)
191 return value;
192
193 var elemType = type.GetGenericArguments()[0];
194 if (TagSerializer.TryGetSerializer(elemType, out serializer))
195 return serializer.SerializeList((IList)value);
196
197 if (GetPayloadId(elemType) != 9)
198 return value;//already a valid NBT list type
199
200 //list of lists conversion
201 var list = value as IList<IList> ?? ((IList)value).Cast<IList>().ToList();
202 for (int i = 0; i < list.Count; i++)
203 list[i] = (IList)Serialize(list[i]);
204
205 return list;
206 }
207
208 public static T Deserialize<T>(object tag) {
209 if (tag is T) return (T)tag;
210 return (T)Deserialize(typeof(T), tag);
211 }
212
213 public static object Deserialize(Type type, object tag) {
214 if (type.IsInstanceOfType(tag))
215 return tag;
216
217 TagSerializer serializer;
218 if (TagSerializer.TryGetSerializer(type, out serializer)) {
219 if (tag == null)
220 tag = Deserialize(serializer.TagType, null);
221
222 return serializer.Deserialize(tag);
223 }
224
225 //normal nbt type with missing value
226 if (tag == null) {
227 if (type.GetGenericArguments().Length == 0)
228 return GetHandler(GetPayloadId(type)).Default();
229
230 if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
231 return null;
232 }
233
234 //list conversion required
235 if ((tag == null || tag is IList) &&
236 type.GetGenericArguments().Length == 1) {
237 var elemType = type.GetGenericArguments()[0];
238 var newListType = typeof(List<>).MakeGenericType(elemType);
239 if (type.IsAssignableFrom(newListType)) {//if the desired type is a superclass of List<elemType>
240 if (tag == null)
241 return newListType.GetConstructor(new Type[0]).Invoke(new object[0]);
242
243 if (TagSerializer.TryGetSerializer(elemType, out serializer))
244 return serializer.DeserializeList((IList)tag);
245
246 //create a strongly typed nested list
247 var oldList = (IList)tag;
248 var newList = (IList)newListType.GetConstructor(new[] { typeof(int) }).Invoke(new object[] { oldList.Count });
249 foreach (var elem in oldList)
250 newList.Add(Deserialize(elemType, elem));
251
252 return newList;
253 }
254 }
255
256 if (tag == null)//unable to create an empty list subclassing the desired type
257 throw new IOException($"Invalid NBT payload type '{type}'");
258
259 throw new InvalidCastException($"Unable to cast object of type '{tag.GetType()}' to type '{type}'");
260 }
261
262 public static T Clone<T>(T o) => (T)GetHandler(GetPayloadId(o.GetType())).Clone(o);
263
264 public static object ReadTag(BinaryReader r, out string name) {
265 int id = r.ReadByte();
266 if (id == 0) {
267 name = null;
268 return null;
269 }
270
271 name = StringHandler.reader(r);
272 return PayloadHandlers[id].Read(r);
273 }
274
275 public static void WriteTag(string name, object tag, BinaryWriter w) {
276 int id = GetPayloadId(tag.GetType());
277 w.Write((byte)id);
278 StringHandler.writer(w, name);
279 PayloadHandlers[id].Write(w, tag);
280 }
281
282 public static TagCompound FromFile(string path, bool compressed = true) {
283 try {
284 using (Stream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
285 return FromStream(fs, compressed);
286 }
287 catch (IOException e) {
288 throw new IOException("Failed to read NBT file: " + path, e);
289 }
290 }
291
292 public static TagCompound FromStream(Stream stream, bool compressed = true) {
293 if (compressed) stream = new GZipStream(stream, CompressionMode.Decompress);
294 return Read(new BigEndianReader(stream));
295 }
296
297 public static TagCompound Read(BinaryReader reader) {
298 string name;
299 var tag = ReadTag(reader, out name);
300 if (!(tag is TagCompound))
301 throw new IOException("Root tag not a TagCompound");
302
303 return (TagCompound)tag;
304 }
305
306 public static void ToFile(TagCompound root, string path, bool compress = true) {
307 try {
308 using (Stream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
309 ToStream(root, fs, compress);
310 }
311 catch (IOException e) {
312 throw new IOException("Failed to read NBT file: " + path, e);
313 }
314 }
315
316 public static void ToStream(TagCompound root, Stream stream, bool compress = true) {
317 if (compress) stream = new GZipStream(stream, CompressionMode.Compress, true);
318 Write(root, new BigEndianWriter(stream));
319 if (compress) stream.Close();
320 }
321
322 public static void Write(TagCompound root, BinaryWriter writer) => WriteTag("", root, writer);
323 }
324}
ClassPayloadHandler(Func< BinaryReader, T > reader, Action< BinaryWriter, T > writer, Func< T, T > clone, Func< T > makeDefault=null)
Definition: TagIO.cs:65
override IList CloneList(IList< T > list)
override void Write(BinaryWriter w, object v)
override IList ReadList(BinaryReader r, int size)
Definition: TagIO.cs:39
virtual IList CloneList(IList< T > list)
abstract void Write(BinaryWriter w, object v)
override void WriteList(BinaryWriter w, IList list)
abstract IList ReadList(BinaryReader r, int size)
override object Read(BinaryReader r)
override IList CloneList(IList list)
void WriteList(BinaryWriter w, IList< T > list)
Definition: TagIO.cs:48
PayloadHandler(Func< BinaryReader, T > reader, Action< BinaryWriter, T > writer)
Definition: TagIO.cs:30
abstract void WriteList(BinaryWriter w, IList list)
abstract IList CloneList(IList list)
abstract object Read(BinaryReader r)
static readonly PayloadHandler[] PayloadHandlers
Definition: TagIO.cs:77
static int GetPayloadId(Type t)
Definition: TagIO.cs:171
static void Write(TagCompound root, BinaryWriter writer)
static void WriteTag(string name, object tag, BinaryWriter w)
Definition: TagIO.cs:275
static object Deserialize(Type type, object tag)
Definition: TagIO.cs:213
static void ToStream(TagCompound root, Stream stream, bool compress=true)
Definition: TagIO.cs:316
static TagCompound FromStream(Stream stream, bool compressed=true)
Definition: TagIO.cs:292
static PayloadHandler GetHandler(int id)
Definition: TagIO.cs:164
static object ReadTag(BinaryReader r, out string name)
Definition: TagIO.cs:264
static TagCompound Read(BinaryReader reader)
Definition: TagIO.cs:297
static object Serialize(object value)
Definition: TagIO.cs:182
static TagCompound FromFile(string path, bool compressed=true)
Definition: TagIO.cs:282
static void ToFile(TagCompound root, string path, bool compress=true)
Definition: TagIO.cs:306
abstract object Serialize(object value)
static bool TryGetSerializer(Type type, out TagSerializer serializer)
abstract IList SerializeList(IList value)
abstract object Deserialize(object tag)