diff --git a/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs index c9b22a8..5797d66 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YbnFile.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml; namespace CodeWalker.GameFiles { @@ -87,4 +88,115 @@ namespace CodeWalker.GameFiles } } + + + + + public class YbnXml : MetaXmlBase + { + + public static string GetXml(YbnFile ybn) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(XmlHeader); + + var name = "BoundsFile"; + OpenTag(sb, 0, name); + + if (ybn?.Bounds != null) + { + Bounds.WriteXmlNode(ybn.Bounds, sb, 1); + } + + CloseTag(sb, 0, name); + + return sb.ToString(); + } + + + public static string FormatBoundMaterialColour(BoundMaterialColour c) //for use with WriteItemArray + { + return c.R.ToString() + ", " + c.G.ToString() + ", " + c.B.ToString() + ", " + c.A.ToString(); + } + + } + + public class XmlYbn + { + + public static YbnFile GetYbn(string xml) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + return GetYbn(doc); + } + + public static YbnFile GetYbn(XmlDocument doc) + { + YbnFile r = new YbnFile(); + + var node = doc.DocumentElement; + var bnode = node?.SelectSingleNode("Bounds"); + if (bnode != null) + { + r.Bounds = Bounds.ReadXmlNode(bnode, r); + } + + return r; + } + + + + public static BoundMaterialColour[] GetRawBoundMaterialColourArray(XmlNode node) + { + if (node == null) return null; + byte r, g, b, a; + var items = new List(); + var split = node.InnerText.Split('\n');// Regex.Split(node.InnerText, @"[\s\r\n\t]"); + for (int i = 0; i < split.Length; i++) + { + var s = split[i]?.Trim(); + if (string.IsNullOrEmpty(s)) continue; + var split2 = s.Split(',');// Regex.Split(s, @"[\s\t]"); + int c = 0; + r = 0; g = 0; b = 0; a = 0; + for (int n = 0; n < split2.Length; n++) + { + var ts = split2[n]?.Trim(); + if (string.IsNullOrEmpty(ts)) continue; + byte v = 0; + byte.TryParse(ts, out v); + switch (c) + { + case 0: r = v; break; + case 1: g = v; break; + case 2: b = v; break; + case 3: a = v; break; + } + c++; + } + if (c >= 2) + { + var val = new BoundMaterialColour(); + val.R = r; + val.G = g; + val.B = b; + val.A = a; + items.Add(val); + } + } + + return (items.Count > 0) ? items.ToArray() : null; + } + public static BoundMaterialColour[] GetChildRawBoundMaterialColourArray(XmlNode node, string name) + { + var cnode = node.SelectSingleNode(name); + return GetRawBoundMaterialColourArray(cnode); + } + + + } + + + } diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index 9de4787..0a1dbff 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -3516,7 +3516,8 @@ namespace CodeWalker.GameFiles } public void TestYbns() { - bool savetest = true; + bool xmltest = true; + bool savetest = false; bool reloadtest = false; var errorfiles = new List(); foreach (RpfFile file in AllRpfs) @@ -3538,6 +3539,14 @@ namespace CodeWalker.GameFiles UpdateStatus("Error! " + ex.ToString()); errorfiles.Add(entry); } + if (xmltest && (ybn != null) && (ybn.Bounds != null)) + { + var xml = YbnXml.GetXml(ybn); + var ybn2 = XmlYbn.GetYbn(xml); + var xml2 = YbnXml.GetXml(ybn2); + if (xml.Length != xml2.Length) + { } + } if (savetest && (ybn != null) && (ybn.Bounds != null)) { var fentry = entry as RpfFileEntry; diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index 1525fdd..d2a80c2 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -61,6 +61,11 @@ namespace CodeWalker.GameFiles YcdFile ycd = RpfFile.GetFile(e, data); return GetXml(ycd, out filename); } + else if (fnl.EndsWith(".ybn")) + { + YbnFile ybn = RpfFile.GetFile(e, data); + return GetXml(ybn, out filename); + } filename = fn; return string.Empty; } @@ -132,6 +137,12 @@ namespace CodeWalker.GameFiles filename = fn + ".xml"; return YcdXml.GetXml(ycd); } + public static string GetXml(YbnFile ybn, out string filename) + { + var fn = (ybn?.RpfFileEntry?.Name) ?? ""; + filename = fn + ".xml"; + return YbnXml.GetXml(ybn); + } @@ -1815,6 +1826,27 @@ namespace CodeWalker.GameFiles SelfClosingTag(sb, ind, name); } } + public static void WriteCustomItemArray(StringBuilder sb, T[] arr, int ind, string name) where T : IMetaXmlItem + { + var itemCount = arr?.Length ?? 0; + if (itemCount > 0) + { + OpenTag(sb, ind, name); + var cind = ind + 1; + for (int i = 0; i < itemCount; i++) + { + if (arr[i] != null) + { + arr[i].WriteXml(sb, cind); + } + } + CloseTag(sb, ind, name); + } + else + { + SelfClosingTag(sb, ind, name); + } + } public static void WriteHashItemArray(StringBuilder sb, MetaHash[] arr, int ind, string name) { var itemCount = arr?.Length ?? 0; @@ -1996,6 +2028,7 @@ namespace CodeWalker.GameFiles AudioRel = 5, Ynd = 6, Ycd = 7, + Ybn = 8, } } diff --git a/CodeWalker.Core/GameFiles/Resources/Bounds.cs b/CodeWalker.Core/GameFiles/Resources/Bounds.cs index 31078b3..7af8c55 100644 --- a/CodeWalker.Core/GameFiles/Resources/Bounds.cs +++ b/CodeWalker.Core/GameFiles/Resources/Bounds.cs @@ -30,6 +30,7 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Xml; using System.Text; using System.Threading.Tasks; using CodeWalker.World; @@ -259,9 +260,6 @@ namespace CodeWalker.GameFiles } } - /// - /// Reads the data-block from a stream. - /// public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -368,10 +366,6 @@ namespace CodeWalker.GameFiles } } - - /// - /// Writes the data-block to a stream. - /// public override void Write(ResourceDataWriter writer, params object[] parameters) { base.Write(writer, parameters); @@ -399,13 +393,95 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_60h); writer.Write(this.Volume); } + public virtual void WriteXml(StringBuilder sb, int indent) + { + YbnXml.SelfClosingTag(sb, indent, "BoxMin " + FloatUtil.GetVector3XmlString(BoxMin)); + YbnXml.SelfClosingTag(sb, indent, "BoxMax " + FloatUtil.GetVector3XmlString(BoxMax)); + YbnXml.SelfClosingTag(sb, indent, "BoxCenter " + FloatUtil.GetVector3XmlString(BoxCenter)); + YbnXml.SelfClosingTag(sb, indent, "SphereCenter " + FloatUtil.GetVector3XmlString(SphereCenter)); + YbnXml.ValueTag(sb, indent, "SphereRadius", FloatUtil.ToString(SphereRadius)); + YbnXml.ValueTag(sb, indent, "Margin", FloatUtil.ToString(Margin)); + YbnXml.ValueTag(sb, indent, "Volume", FloatUtil.ToString(Volume)); + YbnXml.SelfClosingTag(sb, indent, "Inertia " + FloatUtil.GetVector3XmlString(Unknown_60h)); + YbnXml.ValueTag(sb, indent, "MaterialIndex", MaterialIndex.ToString()); + YbnXml.ValueTag(sb, indent, "MaterialColourIndex", MaterialColorIndex.ToString()); + YbnXml.ValueTag(sb, indent, "ProceduralID", ProceduralId.ToString()); + YbnXml.ValueTag(sb, indent, "RoomID", RoomId.ToString()); + YbnXml.ValueTag(sb, indent, "PedDensity", PedDensity.ToString()); + YbnXml.ValueTag(sb, indent, "UnkFlags", UnkFlags.ToString()); + YbnXml.ValueTag(sb, indent, "PolyFlags", PolyFlags.ToString()); + YbnXml.ValueTag(sb, indent, "UnkType", Unknown_3Ch.ToString()); + if (Parent != null) + { + YbnXml.SelfClosingTag(sb, indent, "CompositePosition " + FloatUtil.GetVector3XmlString(Position)); + YbnXml.SelfClosingTag(sb, indent, "CompositeRotation " + FloatUtil.GetVector4XmlString(Orientation.ToVector4())); + YbnXml.SelfClosingTag(sb, indent, "CompositeScale " + FloatUtil.GetVector3XmlString(Scale)); + YbnXml.StringTag(sb, indent, "CompositeFlags1", CompositeFlags1.Flags1.ToString()); + YbnXml.StringTag(sb, indent, "CompositeFlags2", CompositeFlags1.Flags2.ToString()); + } + } + public virtual void ReadXml(XmlNode node) + { + BoxMin = Xml.GetChildVector3Attributes(node, "BoxMin", "x", "y", "z"); + BoxMax = Xml.GetChildVector3Attributes(node, "BoxMax", "x", "y", "z"); + BoxCenter = Xml.GetChildVector3Attributes(node, "BoxCenter", "x", "y", "z"); + SphereCenter = Xml.GetChildVector3Attributes(node, "SphereCenter", "x", "y", "z"); + SphereRadius = Xml.GetChildFloatAttribute(node, "SphereRadius", "value"); + Margin = Xml.GetChildFloatAttribute(node, "Margin", "value"); + Volume = Xml.GetChildFloatAttribute(node, "Volume", "value"); + Unknown_60h = Xml.GetChildVector3Attributes(node, "Inertia", "x", "y", "z"); + MaterialIndex = (byte)Xml.GetChildUIntAttribute(node, "MaterialIndex", "value"); + MaterialColorIndex = (byte)Xml.GetChildUIntAttribute(node, "MaterialColourIndex", "value"); + ProceduralId = (byte)Xml.GetChildUIntAttribute(node, "ProceduralID", "value"); + RoomId = (byte)Xml.GetChildUIntAttribute(node, "RoomID", "value"); + PedDensity = (byte)Xml.GetChildUIntAttribute(node, "PedDensity", "value"); + UnkFlags = (byte)Xml.GetChildUIntAttribute(node, "UnkFlags", "value"); + PolyFlags = (byte)Xml.GetChildUIntAttribute(node, "PolyFlags", "value"); + Unknown_3Ch = (byte)Xml.GetChildUIntAttribute(node, "UnkType", "value"); + if (Parent != null) + { + Position = Xml.GetChildVector3Attributes(node, "CompositePosition", "x", "y", "z"); + Orientation = Xml.GetChildVector4Attributes(node, "CompositeRotation", "x", "y", "z", "w").ToQuaternion(); + Scale = Xml.GetChildVector3Attributes(node, "CompositeScale", "x", "y", "z"); + var f = new BoundCompositeChildrenFlags(); + f.Flags1 = Xml.GetChildEnumInnerText(node, "CompositeFlags1"); + f.Flags2 = Xml.GetChildEnumInnerText(node, "CompositeFlags2"); + CompositeFlags1 = f; + CompositeFlags2 = f; + } + } + public static void WriteXmlNode(Bounds b, StringBuilder sb, int indent, string name = "Bounds") + { + if (b == null) return; + YbnXml.OpenTag(sb, indent, name + " type=\"" + b.Type.ToString() + "\""); + b.WriteXml(sb, indent + 1); + YbnXml.CloseTag(sb, indent, name); + } + public static Bounds ReadXmlNode(XmlNode node, object owner = null, BoundComposite parent = null) + { + if (node == null) return null; + var typestr = Xml.GetStringAttribute(node, "type"); + var type = Xml.GetEnumValue(typestr); + var b = Create(type); + if (b != null) + { + b.Type = type; + b.Owner = owner; + b.Parent = parent; + b.ReadXml(node); + } + return b; + } public IResourceSystemBlock GetType(ResourceDataReader reader, params object[] parameters) { reader.Position += 16; var type = (BoundsType)reader.ReadByte(); reader.Position -= 17; - + return Create(type); + } + public static Bounds Create(BoundsType type) + { switch (type) { case BoundsType.Sphere: return new BoundSphere(); @@ -426,6 +502,7 @@ namespace CodeWalker.GameFiles if (other == null) return; SphereRadius = other.SphereRadius; SphereCenter = other.SphereCenter; + BoxCenter = other.BoxCenter; BoxMin = other.BoxMin; BoxMax = other.BoxMax; Margin = other.Margin; @@ -880,9 +957,6 @@ namespace CodeWalker.GameFiles private BoundVertex[] VertexObjects = null; //for use by the editor, created as needed by GetVertexObject() - /// - /// Reads the data-block from a stream. - /// public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -984,83 +1058,6 @@ namespace CodeWalker.GameFiles } } - - private void ReadPolygons(ResourceDataReader reader) - { - if(PolygonsCount==0) - { return; } - - Polygons = new BoundPolygon[PolygonsCount]; - uint polybytecount = PolygonsCount * 16; - var polygonData = reader.ReadBytesAt(PolygonsPointer, polybytecount); - for (int i = 0; i < PolygonsCount; i++) - { - var offset = i * 16; - byte b0 = polygonData[offset]; - polygonData[offset] = (byte)(b0 & 0xF8);//mask it off - BoundPolygonType type = (BoundPolygonType)(b0 & 7); - BoundPolygon p = CreatePolygon(type); - if (p != null) - { - p.Index = i; - p.Read(polygonData, offset); - } - Polygons[i] = p; - } - } - - public BoundVertex GetVertexObject(int index) - { - //gets a cached object which references a single vertex in this geometry - if (Vertices == null) return null; - if ((index < 0) || (index >= Vertices.Length)) return null; - if ((VertexObjects == null) || (VertexObjects.Length != Vertices.Length)) - { - VertexObjects = new BoundVertex[Vertices.Length]; - } - if (index >= VertexObjects.Length) return null; - var r = VertexObjects[index]; - if (r == null) - { - r = new BoundVertex(this, index); - VertexObjects[index] = r; - } - return r; - } - public Vector3 GetVertex(int index) - { - return ((index >= 0) && (index < Vertices.Length)) ? Vertices[index] : Vector3.Zero; - } - public Vector3 GetVertexPos(int index) - { - var v = GetVertex(index) + CenterGeom; - return Vector3.Transform(v, Transform).XYZ(); - } - public void SetVertexPos(int index, Vector3 v) - { - if ((index >= 0) && (index < Vertices.Length)) - { - var t = Vector3.Transform(v, TransformInv).XYZ() - CenterGeom; - Vertices[index] = t; - } - } - public BoundMaterialColour GetVertexColour(int index) - { - return ((VertexColours != null) && (index >= 0) && (index < VertexColours.Length)) ? VertexColours[index] : new BoundMaterialColour(); - } - public void SetVertexColour(int index, BoundMaterialColour c) - { - if ((VertexColours != null) && (index >= 0) && (index < VertexColours.Length)) - { - VertexColours[index] = c; - } - } - - - - /// - /// Writes the data-block to a stream. - /// public override void Write(ResourceDataWriter writer, params object[] parameters) { base.Write(writer, parameters); @@ -1122,10 +1119,95 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_128h); writer.Write(this.Unknown_12Ch); } + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + + YbnXml.SelfClosingTag(sb, indent, "GeometryCenter " + FloatUtil.GetVector3XmlString(CenterGeom)); + YbnXml.ValueTag(sb, indent, "UnkFloat1", FloatUtil.ToString(Unknown_9Ch)); + YbnXml.ValueTag(sb, indent, "UnkFloat2", FloatUtil.ToString(Unknown_ACh)); + + if (Materials != null) + { + YbnXml.WriteItemArray(sb, Materials, indent, "Materials"); + } + if (MaterialColours != null) + { + YbnXml.WriteRawArray(sb, MaterialColours, indent, "MaterialColours", "", YbnXml.FormatBoundMaterialColour, 1); + } + if (Vertices != null) + { + YbnXml.WriteRawArray(sb, Vertices, indent, "Vertices", "", YbnXml.FormatVector3, 1); + } + if (Vertices2 != null) + { + YbnXml.WriteRawArray(sb, Vertices2, indent, "Vertices2", "", YbnXml.FormatVector3, 1); + } + if (VertexColours != null) + { + YbnXml.WriteRawArray(sb, VertexColours, indent, "VertexColours", "", YbnXml.FormatBoundMaterialColour, 1); + } + if (Polygons != null) + { + YbnXml.WriteCustomItemArray(sb, Polygons, indent, "Polygons"); + } + if (Unknown1Data != null) + { + YbnXml.OpenTag(sb, indent, "UnkData"); + Unknown1Data.WriteXml(sb, indent + 1); + YbnXml.CloseTag(sb, indent, "UnkData"); + } + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + + CenterGeom = Xml.GetChildVector3Attributes(node, "GeometryCenter", "x", "y", "z"); + Unknown_9Ch = Xml.GetChildFloatAttribute(node, "UnkFloat1", "value"); + Unknown_ACh = Xml.GetChildFloatAttribute(node, "UnkFloat2", "value"); + + Materials = XmlMeta.ReadItemArray(node, "Materials"); + MaterialColours = XmlYbn.GetChildRawBoundMaterialColourArray(node, "MaterialColours"); + Vertices = Xml.GetChildRawVector3ArrayNullable(node, "Vertices"); + Vertices2 = Xml.GetChildRawVector3ArrayNullable(node, "Vertices2"); + VertexColours = XmlYbn.GetChildRawBoundMaterialColourArray(node, "VertexColours"); + + var pnode = node.SelectSingleNode("Polygons"); + if (pnode != null) + { + var inodes = pnode.ChildNodes; + if (inodes?.Count > 0) + { + var polylist = new List(); + foreach (XmlNode inode in inodes) + { + var typestr = inode.Name; + var type = Xml.GetEnumValue(typestr); + var poly = CreatePolygon(type); + if (poly != null) + { + poly.ReadXml(inode); + polylist.Add(poly); + } + } + Polygons = polylist.ToArray(); + } + } + + var unode = node.SelectSingleNode("UnkData"); + if (unode != null) + { + Unknown1Data = new BoundGeomUnknown1(); + Unknown1Data.ReadXml(unode); + } + + BuildMaterials(); + CalculateQuantum(); + UpdateEdgeIndices(); + UpdateTriangleAreas(); + + } - /// - /// Returns a list of data blocks which are referenced by this block. - /// public override IResourceBlock[] GetReferences() { BuildMaterials(); @@ -1215,6 +1297,82 @@ namespace CodeWalker.GameFiles return list.ToArray(); } + + + private void ReadPolygons(ResourceDataReader reader) + { + if(PolygonsCount==0) + { return; } + + Polygons = new BoundPolygon[PolygonsCount]; + uint polybytecount = PolygonsCount * 16; + var polygonData = reader.ReadBytesAt(PolygonsPointer, polybytecount); + for (int i = 0; i < PolygonsCount; i++) + { + var offset = i * 16; + byte b0 = polygonData[offset]; + polygonData[offset] = (byte)(b0 & 0xF8);//mask it off + BoundPolygonType type = (BoundPolygonType)(b0 & 7); + BoundPolygon p = CreatePolygon(type); + if (p != null) + { + p.Index = i; + p.Read(polygonData, offset); + } + Polygons[i] = p; + } + } + + public BoundVertex GetVertexObject(int index) + { + //gets a cached object which references a single vertex in this geometry + if (Vertices == null) return null; + if ((index < 0) || (index >= Vertices.Length)) return null; + if ((VertexObjects == null) || (VertexObjects.Length != Vertices.Length)) + { + VertexObjects = new BoundVertex[Vertices.Length]; + } + if (index >= VertexObjects.Length) return null; + var r = VertexObjects[index]; + if (r == null) + { + r = new BoundVertex(this, index); + VertexObjects[index] = r; + } + return r; + } + public Vector3 GetVertex(int index) + { + return ((index >= 0) && (index < Vertices.Length)) ? Vertices[index] : Vector3.Zero; + } + public Vector3 GetVertexPos(int index) + { + var v = GetVertex(index) + CenterGeom; + return Vector3.Transform(v, Transform).XYZ(); + } + public void SetVertexPos(int index, Vector3 v) + { + if ((index >= 0) && (index < Vertices.Length)) + { + var t = Vector3.Transform(v, TransformInv).XYZ() - CenterGeom; + Vertices[index] = t; + } + } + public BoundMaterialColour GetVertexColour(int index) + { + return ((VertexColours != null) && (index >= 0) && (index < VertexColours.Length)) ? VertexColours[index] : new BoundMaterialColour(); + } + public void SetVertexColour(int index, BoundMaterialColour c) + { + if ((VertexColours != null) && (index >= 0) && (index < VertexColours.Length)) + { + VertexColours[index] = c; + } + } + + + + public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { var res = new SpaceSphereIntersectResult(); @@ -1468,19 +1626,28 @@ namespace CodeWalker.GameFiles - public BoundMaterial_s GetMaterial(int polyIndex) + public int GetMaterialIndex(int polyIndex) { var matind = 0; if ((PolygonMaterialIndices != null) && (polyIndex < PolygonMaterialIndices.Length)) { matind = PolygonMaterialIndices[polyIndex]; } - if ((Materials != null) && (matind < Materials.Length)) + return matind; + } + public BoundMaterial_s GetMaterialByIndex(int matIndex) + { + if ((Materials != null) && (matIndex < Materials.Length)) { - return Materials[matind]; + return Materials[matIndex]; } return new BoundMaterial_s(); } + public BoundMaterial_s GetMaterial(int polyIndex) + { + var matind = GetMaterialIndex(polyIndex); + return GetMaterialByIndex(matind); + } public void SetMaterial(int polyIndex, BoundMaterial_s mat) { //updates the shared material for the given poly. @@ -1911,9 +2078,6 @@ namespace CodeWalker.GameFiles // reference data public BVH BVH { get; set; } - /// - /// Reads the data-block from a stream. - /// public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -1940,10 +2104,6 @@ namespace CodeWalker.GameFiles //this can happen in some ydr's for some reason } } - - /// - /// Writes the data-block to a stream. - /// public override void Write(ResourceDataWriter writer, params object[] parameters) { base.Write(writer, parameters); @@ -1962,9 +2122,6 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_14Ch); } - /// - /// Returns a list of data blocks which are referenced by this block. - /// public override IResourceBlock[] GetReferences() { BuildBVH(); @@ -2231,9 +2388,6 @@ namespace CodeWalker.GameFiles private ResourceSystemStructBlock ChildrenFlags2Block = null; - /// - /// Reads the data-block from a stream. - /// public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -2317,10 +2471,6 @@ namespace CodeWalker.GameFiles //{ }//some props ydr's } - - /// - /// Writes the data-block to a stream. - /// public override void Write(ResourceDataWriter writer, params object[] parameters) { base.Write(writer, parameters); @@ -2348,10 +2498,55 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_A4h); writer.Write(this.BVHPointer); } + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + var c = Children?.data_items; + if ((c == null) || (c.Length == 0)) + { + YbnXml.SelfClosingTag(sb, indent, "Children"); + } + else + { + var cind = indent + 1; + YbnXml.OpenTag(sb, indent, "Children"); + foreach (var child in c) + { + Bounds.WriteXmlNode(child, sb, cind, "Item"); + } + YbnXml.CloseTag(sb, indent, "Children"); + } + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + + var cnode = node.SelectSingleNode("Children"); + if (cnode != null) + { + var cnodes = cnode.SelectNodes("Item"); + if (cnodes?.Count > 0) + { + var blist = new List(); + foreach (XmlNode inode in cnodes) + { + var b = Bounds.ReadXmlNode(inode, Owner, this); + blist.Add(b); + } + var arr = blist.ToArray(); + Children = new ResourcePointerArray64(); + Children.data_items = arr; + + BuildBVH(); + UpdateChildrenFlags(); + UpdateChildrenBounds(); + UpdateChildrenTransformations(); + } + } + + + } - /// - /// Returns a list of data blocks which are referenced by this block. - /// public override IResourceBlock[] GetReferences() { BuildBVH(); @@ -2414,7 +2609,7 @@ namespace CodeWalker.GameFiles } else { - //why are we here? yft's hit this... + //why are we here? yft's hit this... (and when loading XML!) if (!(Owner is FragPhysicsLOD) && !(Owner is FragPhysArchetype) && !(Owner is VerletCloth)) { } } @@ -2663,7 +2858,7 @@ namespace CodeWalker.GameFiles Box = 3, Cylinder = 4, } - [TC(typeof(EXP))] public abstract class BoundPolygon + [TC(typeof(EXP))] public abstract class BoundPolygon : IMetaXmlItem { public BoundPolygonType Type { get; set; } public BoundGeometry Owner { get; set; } //for browsing/editing convenience @@ -2680,6 +2875,10 @@ namespace CodeWalker.GameFiles } } public BoundMaterial_s? MaterialCustom; //for editing, when assigning a new material. + public int MaterialIndex + { + get { return Owner?.GetMaterialIndex(Index) ?? -1; } + } public Vector3[] VertexPositions { get @@ -2720,6 +2919,8 @@ namespace CodeWalker.GameFiles public abstract void GatherVertices(Dictionary verts); public abstract void Read(byte[] bytes, int offset); public abstract void Write(BinaryWriter bw); + public abstract void WriteXml(StringBuilder sb, int indent); + public abstract void ReadXml(XmlNode node); public virtual string Title { get @@ -2930,6 +3131,30 @@ namespace CodeWalker.GameFiles bw.Write(edgeIndex2); bw.Write(edgeIndex3); } + public override void WriteXml(StringBuilder sb, int indent) + { + var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" v3=\"{4}\" f1=\"{5}\" f2=\"{6}\" f3=\"{7}\"", + Type, + MaterialIndex, + vertIndex1, + vertIndex2, + vertIndex3, + vertFlag1 ? 1 : 0, + vertFlag2 ? 1 : 0, + vertFlag3 ? 1 : 0 + ); + YbnXml.SelfClosingTag(sb, indent, s); + } + public override void ReadXml(XmlNode node) + { + Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s(); + vertIndex1 = Xml.GetIntAttribute(node, "v1"); + vertIndex2 = Xml.GetIntAttribute(node, "v2"); + vertIndex3 = Xml.GetIntAttribute(node, "v3"); + vertFlag1 = Xml.GetIntAttribute(node, "f1") != 0; + vertFlag2 = Xml.GetIntAttribute(node, "f2") != 0; + vertFlag3 = Xml.GetIntAttribute(node, "f3") != 0; + } public override string ToString() { return base.ToString() + ": " + vertIndex1.ToString() + ", " + vertIndex2.ToString() + ", " + vertIndex3.ToString(); @@ -3030,6 +3255,22 @@ namespace CodeWalker.GameFiles bw.Write(unused0); bw.Write(unused1); } + public override void WriteXml(StringBuilder sb, int indent) + { + var s = string.Format("{0} m=\"{1}\" v=\"{2}\" radius=\"{3}\"", + Type, + MaterialIndex, + sphereIndex, + FloatUtil.ToString(sphereRadius) + ); + YbnXml.SelfClosingTag(sb, indent, s); + } + public override void ReadXml(XmlNode node) + { + Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s(); + sphereIndex = (ushort)Xml.GetUIntAttribute(node, "v"); + sphereRadius = Xml.GetFloatAttribute(node, "radius"); + } public override string ToString() { return base.ToString() + ": " + sphereIndex.ToString() + ", " + sphereRadius.ToString(); @@ -3184,6 +3425,24 @@ namespace CodeWalker.GameFiles bw.Write(unused0); bw.Write(unused1); } + public override void WriteXml(StringBuilder sb, int indent) + { + var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" radius=\"{4}\"", + Type, + Material, + capsuleIndex1, + capsuleIndex2, + FloatUtil.ToString(capsuleRadius) + ); + YbnXml.SelfClosingTag(sb, indent, s); + } + public override void ReadXml(XmlNode node) + { + Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s(); + capsuleIndex1 = (ushort)Xml.GetUIntAttribute(node, "v1"); + capsuleIndex2 = (ushort)Xml.GetUIntAttribute(node, "v2"); + capsuleRadius = Xml.GetFloatAttribute(node, "radius"); + } public override string ToString() { return base.ToString() + ": " + capsuleIndex1.ToString() + ", " + capsuleIndex2.ToString() + ", " + capsuleRadius.ToString(); @@ -3367,6 +3626,26 @@ namespace CodeWalker.GameFiles bw.Write(boxIndex4); bw.Write(unused0); } + public override void WriteXml(StringBuilder sb, int indent) + { + var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" v3=\"{4}\" v4=\"{5}\"", + Type, + Material, + boxIndex1, + boxIndex2, + boxIndex3, + boxIndex4 + ); + YbnXml.SelfClosingTag(sb, indent, s); + } + public override void ReadXml(XmlNode node) + { + Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s(); + boxIndex1 = (short)Xml.GetIntAttribute(node, "v1"); + boxIndex2 = (short)Xml.GetIntAttribute(node, "v2"); + boxIndex3 = (short)Xml.GetIntAttribute(node, "v3"); + boxIndex4 = (short)Xml.GetIntAttribute(node, "v4"); + } public override string ToString() { return base.ToString() + ": " + boxIndex1.ToString() + ", " + boxIndex2.ToString() + ", " + boxIndex3.ToString() + ", " + boxIndex4.ToString(); @@ -3521,6 +3800,24 @@ namespace CodeWalker.GameFiles bw.Write(unused0); bw.Write(unused1); } + public override void WriteXml(StringBuilder sb, int indent) + { + var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" radius=\"{4}\"", + Type, + Material, + cylinderIndex1, + cylinderIndex2, + FloatUtil.ToString(cylinderRadius) + ); + YbnXml.SelfClosingTag(sb, indent, s); + } + public override void ReadXml(XmlNode node) + { + Material = Owner?.GetMaterialByIndex(Xml.GetIntAttribute(node, "m")) ?? new BoundMaterial_s(); + cylinderIndex1 = (ushort)Xml.GetUIntAttribute(node, "v1"); + cylinderIndex2 = (ushort)Xml.GetUIntAttribute(node, "v2"); + cylinderRadius = Xml.GetFloatAttribute(node, "radius"); + } public override string ToString() { return base.ToString() + ": " + cylinderIndex1.ToString() + ", " + cylinderIndex2.ToString() + ", " + cylinderRadius.ToString(); @@ -3615,7 +3912,7 @@ namespace CodeWalker.GameFiles } } - [TC(typeof(EXP))] public class BoundGeomUnknown1 : ResourceSystemBlock + [TC(typeof(EXP))] public class BoundGeomUnknown1 : ResourceSystemBlock, IMetaXmlItem { public uint[][] Items { get; private set; } @@ -3654,7 +3951,6 @@ namespace CodeWalker.GameFiles } } } - public override void Write(ResourceDataWriter writer, params object[] parameters) { @@ -3668,6 +3964,50 @@ namespace CodeWalker.GameFiles } } + public void WriteXml(StringBuilder sb, int indent) + { + if (Items == null) return; + foreach (var item in Items) + { + YbnXml.Indent(sb, indent); + if (item != null) + { + bool newline = true; + foreach (var val in item) + { + if (!newline) sb.Append(", "); + sb.Append(val.ToString()); + newline = false; + } + } + sb.AppendLine(); + } + } + public void ReadXml(XmlNode node) + { + var collist = new List(); + var rowlist = new List(); + var str = node.InnerText; + var split = str.Split('\n'); + for (int i = 0; i < split.Length; i++) + { + var s = split[i]?.Trim(); + //if (string.IsNullOrEmpty(s)) continue; + var split2 = s.Split(',');// Regex.Split(s, @"[\s\t]"); + rowlist.Clear(); + for (int n = 0; n < split2.Length; n++) + { + var ts = split2[n]?.Trim(); + if (string.IsNullOrEmpty(ts)) continue; + if (uint.TryParse(ts, out uint u)) + { + rowlist.Add(u); + } + } + collist.Add(rowlist.ToArray()); + } + Items = collist.ToArray(); + } public override IResourceBlock[] GetReferences() { @@ -4240,14 +4580,12 @@ namespace CodeWalker.GameFiles FLAG_NO_NETWORK_SPAWN = 1 << 14, FLAG_NO_CAM_COLLISION_ALLOW_CLIPPING = 1 << 15, } - [TC(typeof(EXP))] public struct BoundMaterial_s + [TC(typeof(EXP))] public struct BoundMaterial_s : IMetaXmlItem { public uint Data1; public uint Data2; - #region Public Properties - public BoundsMaterialType Type { get => (BoundsMaterialType)(Data1 & 0xFFu); @@ -4272,18 +4610,6 @@ namespace CodeWalker.GameFiles set => Data1 = ((Data1 & 0xFF1FFFFFu) | ((value & 0x7u) << 21)); } - //public byte Flags1 - //{ - // get => (byte)((Data1 >> 24) & 0xFFu); - // set => Data1 = ((Data1 & 0xFFFFFFu) | ((value & 0xFFu) << 24)); - //} - - //public byte Flags2 - //{ - // get => (byte)((Data2 >> 24) & 0xFFu); - // set => Data2 = ((Data2 & 0xFFFFFFu) | ((value & 0xFFu) << 24)); - //} - public EBoundMaterialFlags Flags { get => (EBoundMaterialFlags)(((Data1 >> 24) & 0xFFu) | ((Data2 & 0xFFu) << 8)); @@ -4297,7 +4623,7 @@ namespace CodeWalker.GameFiles public byte MaterialColorIndex { get => (byte)((Data2 >> 8) & 0xFFu); - set => Data2 = ((Data2 & 0xFFFF00FFu) | (value & 0xFFu)); + set => Data2 = ((Data2 & 0xFFFF00FFu) | ((value & 0xFFu) << 8)); } public ushort Unk4 @@ -4306,6 +4632,28 @@ namespace CodeWalker.GameFiles set => Data2 = ((Data2 & 0x0000FFFFu) | ((value & 0xFFFFu) << 16)); } + + public void WriteXml(StringBuilder sb, int indent) + { + YbnXml.ValueTag(sb, indent, "Type", Type.Index.ToString()); + YbnXml.ValueTag(sb, indent, "ProceduralID", ProceduralId.ToString()); + YbnXml.ValueTag(sb, indent, "RoomID", RoomId.ToString()); + YbnXml.ValueTag(sb, indent, "PedDensity", PedDensity.ToString()); + YbnXml.StringTag(sb, indent, "Flags", Flags.ToString()); + YbnXml.ValueTag(sb, indent, "MaterialColourIndex", MaterialColorIndex.ToString()); + YbnXml.ValueTag(sb, indent, "Unk", Unk4.ToString()); + } + public void ReadXml(XmlNode node) + { + Type = (byte)Xml.GetChildUIntAttribute(node, "Type", "value"); + ProceduralId = (byte)Xml.GetChildUIntAttribute(node, "ProceduralID", "value"); + RoomId = (byte)Xml.GetChildUIntAttribute(node, "RoomID", "value"); + PedDensity = (byte)Xml.GetChildUIntAttribute(node, "PedDensity", "value"); + Flags = Xml.GetChildEnumInnerText(node, "Flags"); + MaterialColorIndex = (byte)Xml.GetChildUIntAttribute(node, "MaterialColourIndex", "value"); + Unk4 = (ushort)Xml.GetChildUIntAttribute(node, "Unk", "value"); + } + public override string ToString() { return Data1.ToString() + ", " + Data2.ToString() + ", " @@ -4313,7 +4661,6 @@ namespace CodeWalker.GameFiles + Flags.ToString() + ", " + MaterialColorIndex.ToString() + ", " + Unk4.ToString(); } - #endregion } [TC(typeof(EXP))] public struct BoundMaterialColour { diff --git a/CodeWalker.Core/Utils/Xml.cs b/CodeWalker.Core/Utils/Xml.cs index 22c0d0c..0b1d6c8 100644 --- a/CodeWalker.Core/Utils/Xml.cs +++ b/CodeWalker.Core/Utils/Xml.cs @@ -374,6 +374,12 @@ namespace CodeWalker var cnode = node.SelectSingleNode(name); return GetRawVector3Array(cnode); } + public static Vector3[] GetChildRawVector3ArrayNullable(XmlNode node, string name) + { + var cnode = node.SelectSingleNode(name); + var arr = GetRawVector3Array(cnode); + return ((arr != null) && (arr.Length > 0)) ? arr : null; + } public static Vector4[] GetRawVector4Array(XmlNode node) { diff --git a/ExploreForm.cs b/ExploreForm.cs index 215f3ad..47ff2da 100644 --- a/ExploreForm.cs +++ b/ExploreForm.cs @@ -230,10 +230,10 @@ namespace CodeWalker InitFileType(".sps", "Shader Preset", 5, FileTypeAction.ViewText); InitFileType(".xml", "XML File", 6, FileTypeAction.ViewXml); InitFileType(".meta", "Metadata (XML)", 6, FileTypeAction.ViewXml); - InitFileType(".ymt", "Metadata (Binary)", 6, FileTypeAction.ViewYmt); - InitFileType(".pso", "Metadata (PSO)", 6, FileTypeAction.ViewJPso); + InitFileType(".ymt", "Metadata (Binary)", 6, FileTypeAction.ViewYmt, true); + InitFileType(".pso", "Metadata (PSO)", 6, FileTypeAction.ViewJPso, true); InitFileType(".gfx", "Scaleform Flash", 7); - InitFileType(".ynd", "Path Nodes", 8, FileTypeAction.ViewYnd); + InitFileType(".ynd", "Path Nodes", 8, FileTypeAction.ViewYnd, true); InitFileType(".ynv", "Nav Mesh", 9, FileTypeAction.ViewModel); InitFileType(".yvr", "Vehicle Record", 9, FileTypeAction.ViewYvr); InitFileType(".ywr", "Waypoint Record", 9, FileTypeAction.ViewYwr); @@ -247,9 +247,9 @@ namespace CodeWalker InitFileType(".yft", "Fragment", 11, FileTypeAction.ViewModel); InitFileType(".ydr", "Drawable", 11, FileTypeAction.ViewModel); InitFileType(".ydd", "Drawable Dictionary", 12, FileTypeAction.ViewModel); - InitFileType(".cut", "Cutscene", 12, FileTypeAction.ViewCut); + InitFileType(".cut", "Cutscene", 12, FileTypeAction.ViewCut, true); InitFileType(".ysc", "Script", 13); - InitFileType(".ymf", "Manifest", 14, FileTypeAction.ViewYmf); + InitFileType(".ymf", "Manifest", 14, FileTypeAction.ViewYmf, true); InitFileType(".bik", "Bink Video", 15); InitFileType(".jpg", "JPEG Image", 16); InitFileType(".jpeg", "JPEG Image", 16); @@ -258,21 +258,21 @@ namespace CodeWalker InitFileType(".dds", "DirectDraw Surface", 16); InitFileType(".ytd", "Texture Dictionary", 16, FileTypeAction.ViewYtd); InitFileType(".mrf", "MRF File", 18); - InitFileType(".ycd", "Clip Dictionary", 18, FileTypeAction.ViewYcd); + InitFileType(".ycd", "Clip Dictionary", 18, FileTypeAction.ViewYcd, true); InitFileType(".ypt", "Particle Effect", 18, FileTypeAction.ViewModel); - InitFileType(".ybn", "Static Collisions", 19, FileTypeAction.ViewModel); + InitFileType(".ybn", "Static Collisions", 19, FileTypeAction.ViewModel, true); InitFileType(".ide", "Item Definitions", 20, FileTypeAction.ViewText); - InitFileType(".ytyp", "Archetype Definitions", 20, FileTypeAction.ViewYtyp); - InitFileType(".ymap", "Map Data", 21, FileTypeAction.ViewYmap); + InitFileType(".ytyp", "Archetype Definitions", 20, FileTypeAction.ViewYtyp, true); + InitFileType(".ymap", "Map Data", 21, FileTypeAction.ViewYmap, true); InitFileType(".ipl", "Item Placements", 21, FileTypeAction.ViewText); InitFileType(".awc", "Audio Wave Container", 22, FileTypeAction.ViewAwc); - InitFileType(".rel", "Audio Data (REL)", 23, FileTypeAction.ViewRel); + InitFileType(".rel", "Audio Data (REL)", 23, FileTypeAction.ViewRel, true); InitSubFileType(".dat", "cache_y.dat", "Cache File", 6, FileTypeAction.ViewCacheDat); } - private void InitFileType(string ext, string name, int imgidx, FileTypeAction defaultAction = FileTypeAction.ViewHex) + private void InitFileType(string ext, string name, int imgidx, FileTypeAction defaultAction = FileTypeAction.ViewHex, bool xmlConvertible = false) { - var ft = new FileTypeInfo(ext, name, imgidx, defaultAction); + var ft = new FileTypeInfo(ext, name, imgidx, defaultAction, xmlConvertible); FileTypes[ext] = ft; } private void InitSubFileType(string ext, string subext, string name, int imgidx, FileTypeAction defaultAction = FileTypeAction.ViewHex) @@ -280,7 +280,7 @@ namespace CodeWalker FileTypeInfo pti = null; if (FileTypes.TryGetValue(ext, out pti)) { - var ft = new FileTypeInfo(subext, name, imgidx, defaultAction); + var ft = new FileTypeInfo(subext, name, imgidx, defaultAction, pti.XmlConvertible); pti.AddSubType(ft); } } @@ -308,7 +308,7 @@ namespace CodeWalker } else { - ft = new FileTypeInfo(ext, ext.Substring(1).ToUpperInvariant() + " File", 4, FileTypeAction.ViewHex); + ft = new FileTypeInfo(ext, ext.Substring(1).ToUpperInvariant() + " File", 4, FileTypeAction.ViewHex, false); FileTypes[ft.Extension] = ft; //save it for later! return ft; } @@ -1323,20 +1323,7 @@ namespace CodeWalker { if (item == null) return false; if (item.FileType == null) return false; - switch (item.FileType.DefaultAction) - { - case FileTypeAction.ViewYmt: - case FileTypeAction.ViewYmf: - case FileTypeAction.ViewYmap: - case FileTypeAction.ViewYtyp: - case FileTypeAction.ViewJPso: - case FileTypeAction.ViewCut: - case FileTypeAction.ViewRel: - case FileTypeAction.ViewYnd: - case FileTypeAction.ViewYcd: - return true; - } - return false; + return item.FileType.XmlConvertible; } @@ -2465,6 +2452,10 @@ namespace CodeWalker { mformat = MetaFormat.Ycd; } + if (fnamel.EndsWith(".ybn.xml")) + { + mformat = MetaFormat.Ybn; + } fname = fname.Substring(0, fname.Length - trimlength); fnamel = fnamel.Substring(0, fnamel.Length - trimlength); @@ -2546,6 +2537,17 @@ namespace CodeWalker data = ycd.Save(); break; } + case MetaFormat.Ybn: + { + var ybn = XmlYbn.GetYbn(doc); + if (ybn.Bounds == null) + { + MessageBox.Show(fname + ": Schema not supported.", "Cannot import YBN XML"); + continue; + } + data = ybn.Save(); + break; + } } @@ -4181,13 +4183,15 @@ namespace CodeWalker public int ImageIndex { get; set; } public FileTypeAction DefaultAction { get; set; } public List SubTypes { get; set; } + public bool XmlConvertible { get; set; } - public FileTypeInfo(string extension, string name, int imageindex, FileTypeAction defaultAction) + public FileTypeInfo(string extension, string name, int imageindex, FileTypeAction defaultAction, bool xmlConvertible) { Name = name; Extension = extension; ImageIndex = imageindex; DefaultAction = defaultAction; + XmlConvertible = xmlConvertible; } public void AddSubType(FileTypeInfo t)