Terraria ModLoader  0.11.4
A framework for Terraria mods
TagIO.cs
Go to the documentation of this file.
1 using Ionic.Zlib;
2 using System;
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Text;
8 
9 namespace Terraria.ModLoader.IO
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 
30  public PayloadHandler(Func<BinaryReader, T> reader, Action<BinaryWriter, T> writer) {
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 
65  public ClassPayloadHandler(Func<BinaryReader, T> reader, Action<BinaryWriter, T> writer,
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)),
85  new ClassPayloadHandler<byte[]>(
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  }),
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 }
static TagCompound FromStream(Stream stream, bool compressed=true)
Definition: TagIO.cs:292
PayloadHandler(Func< BinaryReader, T > reader, Action< BinaryWriter, T > writer)
Definition: TagIO.cs:30
static TagCompound FromFile(string path, bool compressed=true)
Definition: TagIO.cs:282
static int GetPayloadId(Type t)
Definition: TagIO.cs:171
override IList ReadList(BinaryReader r, int size)
Definition: TagIO.cs:39
static object ReadTag(BinaryReader r, out string name)
Definition: TagIO.cs:264
void WriteList(BinaryWriter w, IList< T > list)
Definition: TagIO.cs:48
static object Deserialize(Type type, object tag)
Definition: TagIO.cs:213
static PayloadHandler GetHandler(int id)
Definition: TagIO.cs:164
ClassPayloadHandler(Func< BinaryReader, T > reader, Action< BinaryWriter, T > writer, Func< T, T > clone, Func< T > makeDefault=null)
Definition: TagIO.cs:65
static void WriteTag(string name, object tag, BinaryWriter w)
Definition: TagIO.cs:275
static TagCompound Read(BinaryReader reader)
Definition: TagIO.cs:297
static void ToStream(TagCompound root, Stream stream, bool compress=true)
Definition: TagIO.cs:316
static object Serialize(object value)
Definition: TagIO.cs:182
static void ToFile(TagCompound root, string path, bool compress=true)
Definition: TagIO.cs:306