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