From e3bbd29b331164fc1bae132a55f0a8776fa1d9be Mon Sep 17 00:00:00 2001 From: dexy Date: Mon, 30 Dec 2019 05:23:09 +1100 Subject: [PATCH] XML/RBF conversion --- CodeWalker.Core/CodeWalker.Core.csproj | 1 + .../GameFiles/MetaTypes/MetaXml.cs | 32 ++- CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs | 188 +++++++++++++++--- CodeWalker.Core/GameFiles/MetaTypes/XmlRbf.cs | 142 +++++++++++++ ExploreForm.cs | 8 +- Forms/MetaForm.cs | 16 +- 6 files changed, 351 insertions(+), 36 deletions(-) create mode 100644 CodeWalker.Core/GameFiles/MetaTypes/XmlRbf.cs diff --git a/CodeWalker.Core/CodeWalker.Core.csproj b/CodeWalker.Core/CodeWalker.Core.csproj index c44e460..43dc750 100644 --- a/CodeWalker.Core/CodeWalker.Core.csproj +++ b/CodeWalker.Core/CodeWalker.Core.csproj @@ -99,6 +99,7 @@ + diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index a99e056..77c5ecc 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -1517,9 +1517,37 @@ namespace CodeWalker.GameFiles private static void WriteNode(StringBuilder sb, int indent, RbfStructure rs) { + var attStr = ""; + if (rs.Attributes.Count > 0) + { + var asb = new StringBuilder(); + foreach (var attr in rs.Attributes) + { + if (attr is RbfString str) + { + asb.Append($" {attr.Name}=\"{str.Value}\""); + } + else if (attr is RbfFloat flt) + { + asb.Append($" {attr.Name}=\"{FloatUtil.ToString(flt.Value)}\""); + } + else if (attr is RbfUint32 unt) + { + asb.Append($" {attr.Name}=\"{unt.Value.ToString()}\""); + } + else if (attr is RbfBoolean bln) + { + asb.Append($" {attr.Name}=\"{bln.Value.ToString()}\""); + } + else + { } + } + attStr = $"{asb.ToString()}"; + } + if (rs.Children.Count == 0) { - SelfClosingTag(sb, indent, rs.Name); + SelfClosingTag(sb, indent, rs.Name + attStr); return; } @@ -1527,7 +1555,7 @@ namespace CodeWalker.GameFiles bool oneline = ((rs.Children.Count == 1) && (rs.Children[0].Name == null)); - OpenTag(sb, indent, rs.Name, !oneline); + OpenTag(sb, indent, rs.Name + attStr, !oneline); foreach (var child in rs.Children) diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs b/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs index fab40dc..4469ec0 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Rbf.cs @@ -42,6 +42,7 @@ namespace CodeWalker.GameFiles public RbfStructure current { get; set; } public Stack stack { get; set; } public List descriptors { get; set; } + public Dictionary outDescriptors { get; private set; } = new Dictionary(); public RbfStructure Load(string fileName) { @@ -130,7 +131,7 @@ namespace CodeWalker.GameFiles private void ParseElement(DataReader reader, int descriptorIndex, byte dataType) { var descriptor = descriptors[descriptorIndex]; - switch (dataType) //(descriptor.Type) + switch (dataType) { case 0: // open element... { @@ -139,22 +140,15 @@ namespace CodeWalker.GameFiles if (current != null) { - current.Children.Add(structureValue); + current.AddChild(structureValue); stack.Push(current); } current = structureValue; - // 6 bytes var x1 = reader.ReadInt16(); var x2 = reader.ReadInt16(); - var x3 = reader.ReadInt16(); - //if (x1 != 0) - // throw new Exception("unexpected"); - //if (x2 != 0) - // throw new Exception("unexpected"); - //if (x3 != 0) - // throw new Exception("unexpected"); + current.PendingAttributes = reader.ReadInt16(); break; } case 0x10: @@ -162,7 +156,7 @@ namespace CodeWalker.GameFiles var intValue = new RbfUint32(); intValue.Name = descriptor.Name; intValue.Value = reader.ReadUInt32(); - current.Children.Add(intValue); + current.AddChild(intValue); break; } case 0x20: @@ -170,7 +164,7 @@ namespace CodeWalker.GameFiles var booleanValue = new RbfBoolean(); booleanValue.Name = descriptor.Name; booleanValue.Value = true; - current.Children.Add(booleanValue); + current.AddChild(booleanValue); break; } case 0x30: @@ -178,7 +172,7 @@ namespace CodeWalker.GameFiles var booleanValue = new RbfBoolean(); booleanValue.Name = descriptor.Name; booleanValue.Value = false; - current.Children.Add(booleanValue); + current.AddChild(booleanValue); break; } case 0x40: @@ -186,7 +180,7 @@ namespace CodeWalker.GameFiles var floatValue = new RbfFloat(); floatValue.Name = descriptor.Name; floatValue.Value = reader.ReadSingle(); - current.Children.Add(floatValue); + current.AddChild(floatValue); break; } case 0x50: @@ -196,7 +190,7 @@ namespace CodeWalker.GameFiles floatVectorValue.X = reader.ReadSingle(); floatVectorValue.Y = reader.ReadSingle(); floatVectorValue.Z = reader.ReadSingle(); - current.Children.Add(floatVectorValue); + current.AddChild(floatVectorValue); break; } case 0x60: @@ -207,7 +201,7 @@ namespace CodeWalker.GameFiles var stringValue = new RbfString(); stringValue.Name = descriptor.Name; stringValue.Value = value; - current.Children.Add(stringValue); + current.AddChild(stringValue); break; } default: @@ -225,6 +219,66 @@ namespace CodeWalker.GameFiles return isrbf; } + + public byte GetDescriptorIndex(IRbfType t, out bool isNew) + { + var key = $"{t.Name}_{t.DataType}"; + isNew = false; + + if (!outDescriptors.TryGetValue(key, out var idx)) + { + idx = outDescriptors.Count; + outDescriptors.Add(key, idx); + + isNew = true; + } + + return (byte)idx; + } + + + public byte[] Save() + { + var ms = new MemoryStream(); + Save(ms); + + var buf = new byte[ms.Length]; + ms.Position = 0; + ms.Read(buf, 0, buf.Length); + + return buf; + } + + public void Save(string fileName) + { + using (var fileStream = new FileStream(fileName, FileMode.Create)) + { + Save(fileStream); + } + } + + public void Save(Stream stream) + { + outDescriptors = new Dictionary(); + + var writer = new DataWriter(stream); + writer.Write(RBF_IDENT); + + current.Save(this, writer); + } + + + public void WriteRecordId(IRbfType type, DataWriter writer) + { + writer.Write(GetDescriptorIndex(type, out var isNew)); + writer.Write((byte)type.DataType); + + if (isNew) + { + writer.Write((ushort)type.Name.Length); + writer.Write(Encoding.ASCII.GetBytes(type.Name)); + } + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfEntryDescription @@ -236,23 +290,56 @@ namespace CodeWalker.GameFiles [TypeConverter(typeof(ExpandableObjectConverter))] public interface IRbfType { string Name { get; set; } - } - [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfBoolean : IRbfType - { - public string Name { get; set; } - public bool Value { get; set; } - public override string ToString() { return Name + ": " + Value.ToString(); } + byte DataType { get; } + void Save(RbfFile file, DataWriter writer); } [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfBytes : IRbfType { public string Name { get; set; } public byte[] Value { get; set; } + public byte DataType => 0; + public void Save(RbfFile root, DataWriter writer) + { + writer.Write((byte)0xFD); + writer.Write((byte)0xFF); + writer.Write(Value.Length); + writer.Write(Value); + } + public override string ToString() { return Name + ": " + Value.ToString(); } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfUint32 : IRbfType + { + public string Name { get; set; } + public uint Value { get; set; } + public byte DataType => 0x10; + public void Save(RbfFile file, DataWriter writer) + { + file.WriteRecordId(this, writer); + writer.Write(Value); + } + public override string ToString() { return Name + ": " + Value.ToString(); } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfBoolean : IRbfType + { + public string Name { get; set; } + public bool Value { get; set; } + public byte DataType => (byte)((Value) ? 0x20 : 0x30); + public void Save(RbfFile file, DataWriter writer) + { + file.WriteRecordId(this, writer); + } public override string ToString() { return Name + ": " + Value.ToString(); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfFloat : IRbfType { public string Name { get; set; } public float Value { get; set; } + public byte DataType => 0x40; + public void Save(RbfFile file, DataWriter writer) + { + file.WriteRecordId(this, writer); + writer.Write(Value); + } public override string ToString() { return Name + ": " + Value.ToString(); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfFloat3 : IRbfType @@ -261,18 +348,36 @@ namespace CodeWalker.GameFiles public float X { get; set; } public float Y { get; set; } public float Z { get; set; } + public byte DataType => 0x50; + public void Save(RbfFile file, DataWriter writer) + { + file.WriteRecordId(this, writer); + writer.Write(X); + writer.Write(Y); + writer.Write(Z); + } public override string ToString() { return string.Format("{0}: X:{1}, Y:{2}, Z:{3}", Name, X, Y, Z); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfString : IRbfType { public string Name { get; set; } public string Value { get; set; } + public byte DataType => 0x60; + public void Save(RbfFile file, DataWriter writer) + { + file.WriteRecordId(this, writer); + writer.Write((short)Value.Length); + writer.Write(Encoding.ASCII.GetBytes(Value)); + } public override string ToString() { return Name + ": " + Value.ToString(); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfStructure : IRbfType { public string Name { get; set; } public List Children { get; set; } = new List(); + public List Attributes { get; set; } = new List(); + internal int PendingAttributes { get; set; } + public byte DataType => 0; public override string ToString() { return Name + ": {" + Children.Count.ToString() + "}"; } public IRbfType FindChild(string name) { @@ -283,12 +388,39 @@ namespace CodeWalker.GameFiles } return null; } - } - [TypeConverter(typeof(ExpandableObjectConverter))] public class RbfUint32 : IRbfType - { - public string Name { get; set; } - public uint Value { get; set; } - public override string ToString() { return Name + ": " + Value.ToString(); } + public void Save(RbfFile root, DataWriter writer) + { + root.WriteRecordId(this, writer); + + writer.Write(new byte[4]); // 00 + + // count of non-primitive fields in this (... attributes??) + writer.Write((short)Attributes.Count); //writer.Write((short)Children.TakeWhile(a => !(a is RbfBytes || a is RbfStructure)).Count()); + + foreach (var attr in Attributes) + { + attr.Save(root, writer); + } + foreach (var child in Children) + { + child.Save(root, writer); + } + + writer.Write((byte)0xFF); + writer.Write((byte)0xFF); + } + internal void AddChild(IRbfType value) + { + if (PendingAttributes > 0) + { + PendingAttributes--; + Attributes.Add(value); + } + else + { + Children.Add(value); + } + } } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/XmlRbf.cs b/CodeWalker.Core/GameFiles/MetaTypes/XmlRbf.cs new file mode 100644 index 0000000..3265777 --- /dev/null +++ b/CodeWalker.Core/GameFiles/MetaTypes/XmlRbf.cs @@ -0,0 +1,142 @@ +using SharpDX; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; + +namespace CodeWalker.GameFiles +{ + public class XmlRbf + { + + public static RbfFile GetRbf(XmlDocument doc) + { + var rbf = new RbfFile(); + + using (var reader = new XmlNodeReader(doc)) + { + reader.MoveToContent(); + rbf.current = (RbfStructure) Traverse(XDocument.Load(reader).Root); + } + + return rbf; + } + + private static IRbfType Traverse(XNode node) + { + if (node is XElement element) + { + if (element.Attribute("value") != null) + { + var val = element.Attribute("value").Value; + if (!string.IsNullOrEmpty(val)) + { + var rval = CreateValueNode(element.Name.LocalName, val); + if (rval != null) + { + return rval; + } + } + } + else if ((element.Attributes().Count() == 3) && (element.Attribute("x") != null) && (element.Attribute("y") != null) && (element.Attribute("z") != null)) + { + FloatUtil.TryParse(element.Attribute("x").Value, out float x); + FloatUtil.TryParse(element.Attribute("y").Value, out float y); + FloatUtil.TryParse(element.Attribute("z").Value, out float z); + return new RbfFloat3() + { + Name = element.Name.LocalName, + X = x, + Y = y, + Z = z + }; + } + else if ((element.Elements().Count() == 0) && (element.Attributes().Count() == 0)) //else if (element.Name == "type" || element.Name == "key" || element.Name == "platform") + { + return new RbfString() + { + Name = element.Name.LocalName, + Value = element.Value + }; + } + + var n = new RbfStructure(); + n.Name = element.Name.LocalName; + n.Children = element.Nodes().Select(c => Traverse(c)).ToList(); + + foreach (var attr in element.Attributes()) + { + var val = attr.Value; + var aval = CreateValueNode(attr.Name.LocalName, val); + if (aval != null) + { + n.Attributes.Add(aval); + } + } + + return n; + } + else if (node is XText text) + { + return new RbfBytes() + { + Name = "", + Value = Encoding.ASCII.GetBytes(text.Value).Concat(new byte[] { 0x00 }).ToArray() + }; + } + + return null; + } + + + private static IRbfType CreateValueNode(string name, string val) + { + if (val == "True") + { + return new RbfBoolean() + { + Name = name, + Value = true + }; + } + else if (val == "False") + { + return new RbfBoolean() + { + Name = name, + Value = false + }; + } + else if (val.StartsWith("0x")) + { + uint.TryParse(val.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint u); + return new RbfUint32() + { + Name = name, + Value = u + }; + } + else if (FloatUtil.TryParse(val, out float f)) + { + return new RbfFloat() + { + Name = name, + Value = f + }; + } + else + { + return new RbfString() + { + Name = name, + Value = val + }; + } + } + } +} diff --git a/ExploreForm.cs b/ExploreForm.cs index 9c4c143..215f3ad 100644 --- a/ExploreForm.cs +++ b/ExploreForm.cs @@ -2504,7 +2504,13 @@ namespace CodeWalker } case MetaFormat.RBF: { - //todo! + var rbf = XmlRbf.GetRbf(doc); + if (rbf.current == null) + { + MessageBox.Show(fname + ": Schema not supported.", "Cannot import RBF XML"); + continue; + } + data = rbf.Save(); break; } case MetaFormat.AudioRel: diff --git a/Forms/MetaForm.cs b/Forms/MetaForm.cs index 81169ec..a292ca2 100644 --- a/Forms/MetaForm.cs +++ b/Forms/MetaForm.cs @@ -373,11 +373,14 @@ namespace CodeWalker.Forms data = pso.Save(); break; case MetaFormat.RBF: - MessageBox.Show("Sorry, RBF import is not supported.", "Cannot import RBF XML"); - return false; - case MetaFormat.CacheFile: - MessageBox.Show("Sorry, CacheFile import is not supported.", "Cannot import CacheFile XML"); - return false; + var rbf = XmlRbf.GetRbf(doc); + if (rbf.current == null) + { + MessageBox.Show("Schema not supported.", "Cannot import RBF XML"); + return false; + } + data = rbf.Save(); + break; case MetaFormat.Ynd: var ynd = XmlYnd.GetYnd(doc); if (ynd.NodeDictionary == null) @@ -387,6 +390,9 @@ namespace CodeWalker.Forms } data = ynd.Save(); break; + case MetaFormat.CacheFile: + MessageBox.Show("Sorry, CacheFile import is not supported.", "Cannot import CacheFile XML"); + return false; } } #if !DEBUG