source: OniSplit/Xml/XmlImporter.cs

Last change on this file was 1114, checked in by iritscen, 5 years ago

Adding OniSplit source code (v0.9.99.0). Many thanks to Neo for all his work over the years.

File size: 24.9 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.IO;
5using System.Xml;
6using Oni.Imaging;
7using Oni.Metadata;
8using Oni.Sound;
9
10namespace Oni.Xml
11{
12 internal class XmlImporter : Importer
13 {
14 private static readonly Func<string, float> floatConverter = XmlConvert.ToSingle;
15 private static readonly Func<string, byte> byteConverter = XmlConvert.ToByte;
16 protected XmlReader xml;
17 private readonly string[] args;
18 private string baseDir;
19 private string filePath;
20 private bool firstInstance;
21 private Dictionary<string, ImporterDescriptor> localRefs;
22 private Dictionary<string, ImporterDescriptor> externalRefs;
23 private ImporterDescriptor currentDescriptor;
24 private BinaryWriter currentWriter;
25
26 #region protected struct RawArray
27
28 protected struct RawArray
29 {
30 private int offset;
31 private int count;
32
33 public RawArray(int offset, int count)
34 {
35 this.offset = offset;
36 this.count = count;
37 }
38
39 public int Offset => offset;
40 public int Count => count;
41 }
42
43 #endregion
44
45 public XmlImporter(string[] args)
46 {
47 this.args = args;
48 }
49
50 public override void Import(string filePath, string outputDirPath)
51 {
52 this.filePath = filePath;
53
54 BeginImport();
55
56 using (xml = CreateXmlReader(filePath))
57 {
58 while (xml.IsStartElement())
59 {
60 switch (xml.LocalName)
61 {
62 case "Objects":
63 ReadObjects();
64 break;
65
66 case "Texture":
67 ReadTexture();
68 break;
69
70 case "ImpactEffects":
71 ReadImpactEffects();
72 break;
73
74 case "SoundAnimation":
75 ReadSoundAnimation();
76 break;
77
78 case "TextureMaterials":
79 ReadTextureMaterials();
80 break;
81
82 case "Particle":
83 ReadParticle();
84 break;
85
86 case "AmbientSound":
87 case "ImpulseSound":
88 case "SoundGroup":
89 ReadSoundData();
90 break;
91
92 case "Animation":
93 ReadAnimation();
94 break;
95
96 default:
97 ReadInstance();
98 break;
99 }
100 }
101 }
102
103 Write(outputDirPath);
104 }
105
106 public override void BeginImport()
107 {
108 base.BeginImport();
109
110 baseDir = Path.GetDirectoryName(filePath);
111 localRefs = new Dictionary<string, ImporterDescriptor>(StringComparer.Ordinal);
112 externalRefs = new Dictionary<string, ImporterDescriptor>(StringComparer.Ordinal);
113 firstInstance = true;
114 }
115
116 private static XmlReader CreateXmlReader(string filePath)
117 {
118 var settings = new XmlReaderSettings()
119 {
120 CloseInput = true,
121 IgnoreWhitespace = true,
122 IgnoreProcessingInstructions = true,
123 IgnoreComments = true
124 };
125
126 var xml = XmlReader.Create(filePath, settings);
127
128 try
129 {
130 if (!xml.Read())
131 throw new InvalidDataException("Not an Oni XML file");
132
133 xml.MoveToContent();
134
135 if (!xml.IsStartElement("Oni"))
136 throw new InvalidDataException("Not an Oni XML file");
137
138 if (xml.IsEmptyElement)
139 throw new InvalidDataException("No instances found");
140
141 xml.ReadStartElement();
142 xml.MoveToContent();
143 }
144 catch
145 {
146#if NETCORE
147 xml.Dispose();
148#else
149 xml.Close();
150#endif
151 throw;
152 }
153
154 return xml;
155 }
156
157 private void ReadInstance()
158 {
159 var xmlid = xml.GetAttribute("id");
160 var tagName = xml.GetAttribute("type");
161
162 if (tagName == null)
163 tagName = xml.LocalName;
164
165 var tag = (TemplateTag)Enum.Parse(typeof(TemplateTag), tagName);
166 var metadata = InstanceMetadata.GetMetadata(InstanceFileHeader.OniPCTemplateChecksum);
167 var template = metadata.GetTemplate(tag);
168
169 string name = null;
170
171 if (firstInstance)
172 {
173 name = Path.GetFileNameWithoutExtension(filePath);
174
175 if (!name.StartsWith(tagName, StringComparison.Ordinal))
176 name = tagName + name;
177
178 firstInstance = false;
179 }
180
181 var writer = BeginXmlInstance(tag, name, xmlid);
182 template.Type.Accept(new XmlToBinaryVisitor(this, xml, writer));
183 EndXmlInstance();
184 }
185
186 private void ReadAnimation()
187 {
188 var name = Path.GetFileNameWithoutExtension(filePath);
189
190 var writer = BeginXmlInstance(TemplateTag.TRAM, name, "0");
191 var animation = Totoro.AnimationXmlReader.Read(xml, Path.GetDirectoryName(filePath));
192 Totoro.AnimationDatWriter.Write(animation, this, writer);
193 EndXmlInstance();
194 }
195
196 private void ReadParticle()
197 {
198 var name = Path.GetFileNameWithoutExtension(filePath);
199
200 if (!name.StartsWith("BINA3RAP", StringComparison.Ordinal))
201 name = "BINA3RAP" + name;
202
203 xml.ReadStartElement();
204
205 int rawDataOffset = RawWriter.Align32();
206
207 RawWriter.Write((int)BinaryTag.PAR3);
208 RawWriter.Write(0);
209
210 ParticleXmlImporter.Import(xml, RawWriter);
211
212 int rawDataLength = RawWriter.Position - rawDataOffset;
213 RawWriter.WriteAt(rawDataOffset + 4, rawDataLength - 8);
214
215 var writer = BeginXmlInstance(TemplateTag.BINA, name, "0");
216 writer.Write(rawDataLength);
217 writer.Write(rawDataOffset);
218 EndXmlInstance();
219
220 xml.ReadEndElement();
221 }
222
223 private void ReadSoundData()
224 {
225 var name = Path.GetFileNameWithoutExtension(filePath);
226
227 int rawDataOffset = RawWriter.Align32();
228
229 var osbdImporter = new OsbdXmlImporter(xml, RawWriter);
230 osbdImporter.Import();
231
232 int rawDataLength = RawWriter.Position - rawDataOffset;
233 RawWriter.WriteAt(rawDataOffset + 4, rawDataLength - 8);
234
235 var writer = BeginXmlInstance(TemplateTag.OSBD, name, "0");
236 writer.Write(rawDataLength);
237 writer.Write(rawDataOffset);
238 EndXmlInstance();
239
240 xml.ReadEndElement();
241 }
242
243 private void ReadObjects()
244 {
245 var name = Path.GetFileNameWithoutExtension(filePath);
246
247 if (!name.StartsWith("BINACJBO", StringComparison.Ordinal))
248 name = "BINACJBO" + name;
249
250 xml.ReadStartElement();
251
252 int rawDataOffset = RawWriter.Align32();
253
254 RawWriter.Write((int)BinaryTag.OBJC);
255 RawWriter.Write(0);
256
257 ObjcXmlImporter.Import(xml, RawWriter);
258
259 int rawDataLength = RawWriter.Position - rawDataOffset;
260 RawWriter.WriteAt(rawDataOffset + 4, rawDataLength - 8);
261
262 var writer = BeginXmlInstance(TemplateTag.BINA, name, "0");
263 writer.Write(rawDataLength);
264 writer.Write(rawDataOffset);
265 EndXmlInstance();
266
267 xml.ReadEndElement();
268 }
269
270 private void ReadTextureMaterials()
271 {
272 var name = Path.GetFileNameWithoutExtension(filePath);
273
274 if (!name.StartsWith("BINADBMT", StringComparison.Ordinal))
275 name = "BINADBMT" + name;
276
277 xml.ReadStartElement();
278
279 int rawDataOffset = RawWriter.Align32();
280
281 RawWriter.Write((int)BinaryTag.TMBD);
282 RawWriter.Write(0);
283
284 TmbdXmlImporter.Import(xml, RawWriter);
285
286 int rawDataLength = RawWriter.Position - rawDataOffset;
287 RawWriter.WriteAt(rawDataOffset + 4, rawDataLength - 8);
288
289 var writer = BeginXmlInstance(TemplateTag.BINA, name, "0");
290 writer.Write(rawDataLength);
291 writer.Write(rawDataOffset);
292 EndXmlInstance();
293
294 xml.ReadEndElement();
295 }
296
297 private void ReadImpactEffects()
298 {
299 var name = Path.GetFileNameWithoutExtension(filePath);
300
301 if (!name.StartsWith("BINAEINO", StringComparison.Ordinal))
302 name = "BINAEINO" + name;
303
304 xml.ReadStartElement();
305
306 int rawDataOffset = RawWriter.Align32();
307
308 RawWriter.Write((int)BinaryTag.ONIE);
309 RawWriter.Write(0);
310
311 OnieXmlImporter.Import(xml, RawWriter);
312
313 int rawDataLength = RawWriter.Position - rawDataOffset;
314 RawWriter.WriteAt(rawDataOffset + 4, rawDataLength - 8);
315
316 var writer = BeginXmlInstance(TemplateTag.BINA, name, "0");
317 writer.Write(rawDataLength);
318 writer.Write(rawDataOffset);
319 EndXmlInstance();
320
321 xml.ReadEndElement();
322 }
323
324 private void ReadSoundAnimation()
325 {
326 var name = Path.GetFileNameWithoutExtension(filePath);
327
328 if (!name.StartsWith("BINADBAS", StringComparison.Ordinal))
329 name = "BINADBAS" + name;
330
331 int rawDataOffset = RawWriter.Align32();
332
333 RawWriter.Write((int)BinaryTag.SABD);
334 RawWriter.Write(0);
335
336 SabdXmlImporter.Import(xml, RawWriter);
337
338 int rawDataLength = RawWriter.Position - rawDataOffset;
339 RawWriter.WriteAt(rawDataOffset + 4, rawDataLength - 8);
340
341 var writer = BeginXmlInstance(TemplateTag.BINA, name, "0");
342 writer.Write(rawDataLength);
343 writer.Write(rawDataOffset);
344 EndXmlInstance();
345 }
346
347 private void ReadTexture()
348 {
349 var textureImporter = new Motoko.TextureXmlImporter(this, xml, filePath);
350 textureImporter.Import();
351 }
352
353 public BinaryWriter BeginXmlInstance(TemplateTag tag, string name, string xmlid)
354 {
355 if (!localRefs.TryGetValue(xmlid, out currentDescriptor))
356 {
357 currentDescriptor = ImporterFile.CreateInstance(tag, name);
358 localRefs.Add(xmlid, currentDescriptor);
359 }
360 else if (currentDescriptor.Tag != tag)
361 {
362 throw new InvalidDataException(string.Format("{0} was expected to be of type {1} but it's type is {2}", xmlid, tag, currentDescriptor.Tag));
363 }
364
365 currentWriter = currentDescriptor.OpenWrite();
366
367 return currentWriter;
368 }
369
370 public void EndXmlInstance()
371 {
372 currentWriter.Dispose();
373 }
374
375#region private class XmlToBinaryVisitor
376
377 private class XmlToBinaryVisitor : IMetaTypeVisitor
378 {
379 private readonly XmlImporter importer;
380 private readonly XmlReader xml;
381 private readonly BinaryWriter writer;
382
383 public XmlToBinaryVisitor(XmlImporter importer, XmlReader xml, BinaryWriter writer)
384 {
385 this.importer = importer;
386 this.xml = xml;
387 this.writer = writer;
388 }
389
390#region IMetaTypeVisitor Members
391
392 void IMetaTypeVisitor.VisitEnum(MetaEnum type)
393 {
394 type.XmlToBinary(xml, writer);
395 }
396
397 void IMetaTypeVisitor.VisitByte(MetaByte type)
398 {
399 writer.Write(XmlConvert.ToByte(xml.ReadElementContentAsString()));
400 }
401
402 void IMetaTypeVisitor.VisitInt16(MetaInt16 type)
403 {
404 writer.Write(XmlConvert.ToInt16(xml.ReadElementContentAsString()));
405 }
406
407 void IMetaTypeVisitor.VisitUInt16(MetaUInt16 type)
408 {
409 writer.Write(XmlConvert.ToUInt16(xml.ReadElementContentAsString()));
410 }
411
412 void IMetaTypeVisitor.VisitInt32(MetaInt32 type)
413 {
414 writer.Write(xml.ReadElementContentAsInt());
415 }
416
417 void IMetaTypeVisitor.VisitUInt32(MetaUInt32 type)
418 {
419 writer.Write(XmlConvert.ToUInt32(xml.ReadElementContentAsString()));
420 }
421
422 void IMetaTypeVisitor.VisitInt64(MetaInt64 type)
423 {
424 writer.Write(xml.ReadElementContentAsLong());
425 }
426
427 void IMetaTypeVisitor.VisitUInt64(MetaUInt64 type)
428 {
429 writer.Write(XmlConvert.ToUInt64(xml.ReadElementContentAsString()));
430 }
431
432 void IMetaTypeVisitor.VisitFloat(MetaFloat type)
433 {
434 writer.Write(xml.ReadElementContentAsFloat());
435 }
436
437 void IMetaTypeVisitor.VisitColor(MetaColor type)
438 {
439 byte[] values = xml.ReadElementContentAsArray(byteConverter);
440
441 if (values.Length > 3)
442 writer.Write(new Color(values[0], values[1], values[2], values[3]));
443 else
444 writer.Write(new Color(values[0], values[1], values[2]));
445 }
446
447 void IMetaTypeVisitor.VisitVector2(MetaVector2 type)
448 {
449 writer.Write(xml.ReadElementContentAsVector2());
450 }
451
452 void IMetaTypeVisitor.VisitVector3(MetaVector3 type)
453 {
454 writer.Write(xml.ReadElementContentAsVector3());
455 }
456
457 void IMetaTypeVisitor.VisitMatrix4x3(MetaMatrix4x3 type)
458 {
459 writer.Write(xml.ReadElementContentAsArray(floatConverter, 12));
460 }
461
462 void IMetaTypeVisitor.VisitPlane(MetaPlane type)
463 {
464 writer.Write(xml.ReadElementContentAsArray(floatConverter, 4));
465 }
466
467 void IMetaTypeVisitor.VisitQuaternion(MetaQuaternion type)
468 {
469 writer.Write(xml.ReadElementContentAsArray(floatConverter, 4));
470 }
471
472 void IMetaTypeVisitor.VisitBoundingSphere(MetaBoundingSphere type)
473 {
474 ReadFields(type.Fields);
475 }
476
477 void IMetaTypeVisitor.VisitBoundingBox(MetaBoundingBox type)
478 {
479 ReadFields(type.Fields);
480 }
481
482 void IMetaTypeVisitor.VisitRawOffset(MetaRawOffset type)
483 {
484 //writer.Write(xml.ReadElementContentAsInt());
485 throw new NotImplementedException();
486 }
487
488 void IMetaTypeVisitor.VisitSepOffset(MetaSepOffset type)
489 {
490 //writer.Write(xml.ReadElementContentAsInt());
491 throw new NotImplementedException();
492 }
493
494 void IMetaTypeVisitor.VisitString(MetaString type)
495 {
496 writer.Write(xml.ReadElementContentAsString(), type.Count);
497 }
498
499 void IMetaTypeVisitor.VisitPadding(MetaPadding type)
500 {
501 writer.Write(type.FillByte, type.Count);
502 }
503
504 void IMetaTypeVisitor.VisitPointer(MetaPointer type)
505 {
506 var xmlid = xml.ReadElementContentAsString();
507
508 if (xmlid != null)
509 xmlid = xmlid.Trim();
510
511 if (string.IsNullOrEmpty(xmlid))
512 {
513 writer.Write(0);
514 return;
515 }
516
517 writer.Write(importer.ResolveReference(xmlid, type.Tag));
518 }
519
520 void IMetaTypeVisitor.VisitStruct(MetaStruct type)
521 {
522 ReadFields(type.Fields);
523 }
524
525 void IMetaTypeVisitor.VisitArray(MetaArray type)
526 {
527 int count = ReadArray(type.ElementType, type.Count);
528
529 if (count < type.Count)
530 writer.Skip((type.Count - count) * type.ElementType.Size);
531 }
532
533 void IMetaTypeVisitor.VisitVarArray(MetaVarArray type)
534 {
535 int countFieldPosition = writer.Position;
536 int count;
537
538 if (type.CountField.Type == MetaType.Int16)
539 {
540 writer.WriteInt16(0);
541 count = ReadArray(type.ElementType, UInt16.MaxValue);
542 }
543 else
544 {
545 writer.Write(0);
546 count = ReadArray(type.ElementType, Int32.MaxValue);
547 }
548
549 int position = writer.Position;
550 writer.Position = countFieldPosition;
551
552 if (type.CountField.Type == MetaType.Int16)
553 writer.WriteUInt16(count);
554 else
555 writer.Write(count);
556
557 writer.Position = position;
558 }
559
560#endregion
561
562 private void ReadFields(IEnumerable<Field> fields)
563 {
564 xml.ReadStartElement();
565 xml.MoveToContent();
566
567 foreach (var field in fields)
568 {
569 try
570 {
571 field.Type.Accept(this);
572 }
573 catch (Exception ex)
574 {
575 throw new InvalidOperationException(string.Format("Cannot read field '{0}'", field.Name), ex);
576 }
577 }
578
579 xml.ReadEndElement();
580 }
581
582 protected void ReadStruct(MetaStruct s)
583 {
584 foreach (var field in s.Fields)
585 {
586 try
587 {
588 field.Type.Accept(this);
589 }
590 catch (Exception ex)
591 {
592 throw new InvalidOperationException(string.Format("Cannot read field '{0}'", field.Name), ex);
593 }
594 }
595 }
596
597 private int ReadArray(MetaType elementType, int maxCount)
598 {
599 if (xml.IsEmptyElement)
600 {
601 xml.Read();
602 return 0;
603 }
604
605 xml.ReadStartElement();
606 xml.MoveToContent();
607
608 string elementName = xml.LocalName;
609 int count = 0;
610
611 for (; count < maxCount && xml.IsStartElement(elementName); count++)
612 elementType.Accept(this);
613
614 xml.ReadEndElement();
615
616 return count;
617 }
618
619 protected int ReadRawElement(string name, MetaType elementType)
620 {
621 if (!xml.IsStartElement(name))
622 return 0;
623
624 if (xml.IsEmptyElement)
625 {
626 xml.ReadStartElement();
627 return 0;
628 }
629
630 int rawOffset = importer.RawWriter.Align32();
631
632 elementType.Accept(new RawXmlImporter(xml, importer.RawWriter));
633
634 return rawOffset;
635 }
636
637 protected RawArray ReadRawArray(string name, MetaType elementType)
638 {
639 if (!xml.IsStartElement(name))
640 return new RawArray();
641
642 if (xml.IsEmptyElement)
643 {
644 xml.ReadStartElement();
645 return new RawArray();
646 }
647
648 xml.ReadStartElement();
649
650 int rawOffset = importer.RawWriter.Align32();
651
652 var rawImporter = new RawXmlImporter(xml, importer.RawWriter);
653 int elementCount = 0;
654
655 while (xml.IsStartElement(elementType.Name))
656 {
657 elementType.Accept(rawImporter);
658 elementCount++;
659 }
660
661 xml.ReadEndElement();
662
663 return new RawArray(rawOffset, elementCount);
664 }
665 }
666
667#endregion
668
669 private ImporterDescriptor ResolveReference(string xmlid, TemplateTag tag)
670 {
671 ImporterDescriptor descriptor;
672
673 if (xmlid[0] == '#')
674 descriptor = ResolveLocalReference(xmlid.Substring(1), tag);
675 else
676 descriptor = ResolveExternalReference(xmlid, tag);
677
678 return descriptor;
679 }
680
681 private ImporterDescriptor ResolveLocalReference(string xmlid, TemplateTag tag)
682 {
683 ImporterDescriptor descriptor;
684
685 if (!localRefs.TryGetValue(xmlid, out descriptor))
686 {
687 descriptor = ImporterFile.CreateInstance(tag);
688 localRefs.Add(xmlid, descriptor);
689 }
690 else if (tag != TemplateTag.NONE && tag != descriptor.Tag)
691 {
692 throw new InvalidDataException(string.Format("{0} was expected to be of type {1} but it's type is {2}", xmlid, tag, descriptor.Tag));
693 }
694
695 return descriptor;
696 }
697
698 private ImporterDescriptor ResolveExternalReference(string xmlid, TemplateTag tag)
699 {
700 ImporterDescriptor descriptor;
701
702 if (!externalRefs.TryGetValue(xmlid, out descriptor))
703 {
704 if (xmlid.EndsWith(".xml", StringComparison.Ordinal)
705 || xmlid.EndsWith(".dae", StringComparison.Ordinal)
706 || xmlid.EndsWith(".obj", StringComparison.Ordinal)
707 || xmlid.EndsWith(".tga", StringComparison.Ordinal))
708 {
709 string filePath = Path.Combine(baseDir, xmlid);
710
711 if (!File.Exists(filePath))
712 throw new InvalidDataException(string.Format("Cannot find referenced file '{0}'", filePath));
713
714 if (tag == TemplateTag.TRCM)
715 {
716 var bodyImporter = new Totoro.BodyDaeImporter(args);
717 descriptor = bodyImporter.Import(filePath, ImporterFile);
718 }
719 else if (tag == TemplateTag.M3GM
720 && (currentDescriptor.Tag == TemplateTag.ONWC
721 || currentDescriptor.Tag == TemplateTag.CONS
722 || currentDescriptor.Tag == TemplateTag.DOOR
723 || currentDescriptor.Tag == TemplateTag.OFGA))
724 {
725 var geometryImporter = new Motoko.GeometryImporter(args);
726 descriptor = geometryImporter.Import(filePath, ImporterFile);
727 }
728 else
729 {
730 AddDependency(filePath, tag);
731 var name = Importer.DecodeFileName(Path.GetFileNameWithoutExtension(filePath));
732 descriptor = ImporterFile.CreateInstance(tag, name);
733 }
734 }
735 else
736 {
737 if (tag != TemplateTag.NONE)
738 {
739 //
740 // If the link has a known type tag then make sure the xml id
741 // is prefixed with the tag name.
742 //
743
744 var typeName = tag.ToString();
745
746 if (!xmlid.StartsWith(typeName, StringComparison.Ordinal))
747 xmlid = typeName + xmlid;
748 }
749 else
750 {
751 //
752 // IGPG contains a link that can point to either TXMP or PSpc.
753 // In this case the xml id should be already prefixed with
754 // the tag name because we have no way to guess what type the link is.
755 //
756
757 var tagName = xmlid.Substring(0, 4);
758 tag = (TemplateTag)Enum.Parse(typeof(TemplateTag), tagName);
759 }
760
761 descriptor = ImporterFile.CreateInstance(tag, xmlid);
762 }
763
764 externalRefs.Add(xmlid, descriptor);
765 }
766
767 return descriptor;
768 }
769 }
770}
Note: See TracBrowser for help on using the repository browser.