diff --git a/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs b/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs index 016f01e..4e39af9 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/Gxt2File.cs @@ -157,6 +157,21 @@ namespace CodeWalker.GameFiles return res; } + public static uint TryFindHash(string text) + { + lock (syncRoot) + { + foreach (var kvp in Index) + { + if (kvp.Value == text) + { + return kvp.Key; + } + } + } + return 0; + } + } diff --git a/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs index e862631..6741a48 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml; namespace CodeWalker.GameFiles { @@ -48,6 +49,7 @@ namespace CodeWalker.GameFiles public bool HasChanged { get; set; } = false; public List SaveWarnings = null; + public bool BuildStructsOnSave { get; set; } = true; public YndFile() : base(null, GameFileType.Ynd) @@ -85,39 +87,7 @@ namespace CodeWalker.GameFiles NodeDictionary = rd.ReadBlock(); - if (NodeDictionary != null) - { - if (NodeDictionary.Nodes != null) - { - var nodes = NodeDictionary.Nodes; - Nodes = new YndNode[nodes.Length]; - for (int i = 0; i < nodes.Length; i++) - { - var n = new YndNode(); - n.Init(this, nodes[i]); - Nodes[i] = n; - if (n.NodeID != i) - { } //never hit here - nodeid's have to match the index! - } - } - if ((NodeDictionary.JunctionRefs != null) && (NodeDictionary.Junctions != null)) - { - var juncrefs = NodeDictionary.JunctionRefs; - var juncs = NodeDictionary.Junctions; - Junctions = new YndJunction[juncrefs.Length]; - for (int i = 0; i < juncrefs.Length; i++) - { - var juncref = NodeDictionary.JunctionRefs[i]; - if (juncref.JunctionID >= juncs.Length) - { continue; } - - var j = new YndJunction(); - j.Init(this, juncs[juncref.JunctionID], juncref); - j.Heightmap = new YndJunctionHeightmap(NodeDictionary.JunctionHeightmapBytes, j); - Junctions[i] = j; - } - } - } + InitNodesFromDictionary(); UpdateAllNodePositions(); @@ -143,8 +113,10 @@ namespace CodeWalker.GameFiles public byte[] Save() { - - BuildStructs(); + if (BuildStructsOnSave) + { + BuildStructs(); + } byte[] data = ResourceBuilder.Build(NodeDictionary, 1); //ynd is version 1... @@ -243,6 +215,44 @@ namespace CodeWalker.GameFiles + + public void InitNodesFromDictionary() + { + if (NodeDictionary != null) + { + if (NodeDictionary.Nodes != null) + { + var nodes = NodeDictionary.Nodes; + Nodes = new YndNode[nodes.Length]; + for (int i = 0; i < nodes.Length; i++) + { + var n = new YndNode(); + n.Init(this, nodes[i]); + Nodes[i] = n; + if (n.NodeID != i) + { } //never hit here - nodeid's have to match the index! + } + } + if ((NodeDictionary.JunctionRefs != null) && (NodeDictionary.Junctions != null)) + { + var juncrefs = NodeDictionary.JunctionRefs; + var juncs = NodeDictionary.Junctions; + Junctions = new YndJunction[juncrefs.Length]; + for (int i = 0; i < juncrefs.Length; i++) + { + var juncref = juncrefs[i]; + if (juncref.JunctionID >= juncs.Length) + { continue; } + + var j = new YndJunction(); + j.Init(this, juncs[juncref.JunctionID], juncref); + j.Heightmap = new YndJunctionHeightmap(NodeDictionary.JunctionHeightmapBytes, j); + Junctions[i] = j; + } + } + } + } + public YndNode AddNode() { int cnt = Nodes?.Length ?? 0; @@ -1304,6 +1314,87 @@ namespace CodeWalker.GameFiles Vector4[] GetNodePositions(); } - + + + + + + + + + + public class YndXml : MetaXmlBase + { + + public static string GetXml(YndFile ynd) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(XmlHeader); + + if ((ynd != null) && (ynd.NodeDictionary != null)) + { + var name = "NodeDictionary"; + + OpenTag(sb, 0, name); + + ynd.NodeDictionary.WriteXml(sb, 1); + + CloseTag(sb, 0, name); + } + + return sb.ToString(); + } + + } + + + public class XmlYnd + { + + public static YndFile GetYnd(string xml) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + return GetYnd(doc); + } + + public static YndFile GetYnd(XmlDocument doc) + { + YndFile ynd = new YndFile(); + ynd.NodeDictionary = new NodeDictionary(); + ynd.NodeDictionary.ReadXml(doc.DocumentElement); + ynd.InitNodesFromDictionary(); + ynd.BuildStructsOnSave = false; //structs don't need to be rebuilt here! + return ynd; + } + + + + + + public static TextHash GetTextHash(string str) + { + if (string.IsNullOrEmpty(str)) + { + return 0; + } + if (str.StartsWith("hash_")) + { + return Convert.ToUInt32(str.Substring(5), 16); + } + else + { + uint h = GlobalText.TryFindHash(str); + if (h != 0) + { + return h; + } + + return JenkHash.GenHash(str); + } + } + + } + } diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index 2c92b80..c6b296e 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -228,6 +228,9 @@ namespace CodeWalker.GameFiles UpdateStatus("Loading archetypes..."); InitArchetypeDicts(); + UpdateStatus("Loading strings..."); + InitStringDicts(); + IsInited = true; } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index 7248341..a39a167 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -51,6 +51,11 @@ namespace CodeWalker.GameFiles RelFile rel = RpfFile.GetFile(e, data); return GetXml(rel, out filename); } + else if (fnl.EndsWith(".ynd")) + { + YndFile ynd = RpfFile.GetFile(e, data); + return GetXml(ynd, out filename); + } filename = fn; return string.Empty; } @@ -110,6 +115,12 @@ namespace CodeWalker.GameFiles filename = fn + ".xml"; return RelXml.GetXml(rel); } + public static string GetXml(YndFile ynd, out string filename) + { + var fn = (ynd?.RpfFileEntry?.Name) ?? ""; + filename = fn + ".xml"; + return YndXml.GetXml(ynd); + } @@ -1073,7 +1084,7 @@ namespace CodeWalker.GameFiles OpenTag(sb, indent, arrTag); if (atyp == null) { - ErrorXml(sb, indent, ename + ": Array type not found: " + HashString(arrEntry.ReferenceKey)); + ErrorXml(sb, indent, ename + ": Array type not found: " + HashString((MetaHash)arrEntry.ReferenceKey)); } else { @@ -1367,7 +1378,7 @@ namespace CodeWalker.GameFiles return strval ?? ""; case 7: case 8: - var hashVal = MetaTypes.SwapBytes(MetaTypes.ConvertData(data, eoffset)); + var hashVal = (MetaHash)MetaTypes.SwapBytes(MetaTypes.ConvertData(data, eoffset)); return HashString(hashVal); } @@ -1813,14 +1824,14 @@ namespace CodeWalker.GameFiles public static string HashString(MetaName h) { + uint uh = (uint)h; + if (uh == 0) return ""; + if (Enum.IsDefined(typeof(MetaName), h)) { return h.ToString(); } - uint uh = (uint)h; - if (uh == 0) return ""; - var str = JenkIndex.TryGetString(uh); if (!string.IsNullOrEmpty(str)) return str; @@ -1832,6 +1843,8 @@ namespace CodeWalker.GameFiles } public static string HashString(MetaHash h) { + if (h == 0) return ""; + var str = JenkIndex.TryGetString(h); if (string.IsNullOrEmpty(str)) @@ -1846,12 +1859,23 @@ namespace CodeWalker.GameFiles //todo: make sure JenkIndex is built! //todo: do extra hash lookup here - if (h == 0) return ""; - if (!string.IsNullOrEmpty(str)) return str; return "hash_" + h.Hex; } + public static string HashString(TextHash h) + { + uint uh = h.Hash; + if (uh == 0) return ""; + + var str = GlobalText.TryGetString(uh); + if (!string.IsNullOrEmpty(str)) return str; + + //TODO: do extra hash lookup here + //if(Lookup.TryGetValue(uh, out str)) ... + + return "hash_" + uh.ToString("X").PadLeft(8, '0'); + } public static string UintString(uint h) @@ -1900,6 +1924,7 @@ namespace CodeWalker.GameFiles RBF = 3, CacheFile = 4, AudioRel = 5, + Ynd = 6, } } diff --git a/CodeWalker.Core/GameFiles/Resources/Node.cs b/CodeWalker.Core/GameFiles/Resources/Node.cs index 3acfccb..7cf724b 100644 --- a/CodeWalker.Core/GameFiles/Resources/Node.cs +++ b/CodeWalker.Core/GameFiles/Resources/Node.cs @@ -30,11 +30,11 @@ using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; - +using System.Xml; namespace CodeWalker.GameFiles { - [TypeConverter(typeof(ExpandableObjectConverter))] public class NodeDictionary : ResourceFileBase + [TypeConverter(typeof(ExpandableObjectConverter))] public class NodeDictionary : ResourceFileBase, IMetaXmlItem { public override long BlockLength { @@ -186,6 +186,155 @@ namespace CodeWalker.GameFiles return list.ToArray(); } + + + + + public void WriteXml(StringBuilder sb, int indent) + { + YndXml.ValueTag(sb, indent, "VehicleNodeCount", NodesCountVehicle.ToString()); + YndXml.ValueTag(sb, indent, "PedNodeCount", NodesCountPed.ToString()); + + XmlNodeWrapper[] nodes = null; + int nodecount = Nodes?.Length ?? 0; + if (nodecount > 0) + { + nodes = new XmlNodeWrapper[nodecount]; + for (int i = 0; i < nodecount; i++) + { + nodes[i] = new XmlNodeWrapper(Nodes[i], Links); + } + } + YndXml.WriteItemArray(sb, nodes, indent, "Nodes"); + + + XmlJunctionWrapper[] juncs = null; + int junccount = Junctions?.Length ?? 0; + if (junccount > 0) + { + juncs = new XmlJunctionWrapper[junccount]; + for (int i = 0; i < junccount; i++) + { + juncs[i] = new XmlJunctionWrapper(Junctions[i], JunctionHeightmapBytes); + } + } + YndXml.WriteItemArray(sb, juncs, indent, "Junctions"); + + YndXml.WriteItemArray(sb, JunctionRefs, indent, "JunctionRefs"); + + } + public void ReadXml(XmlNode node) + { + NodesCountVehicle = Xml.GetChildUIntAttribute(node, "VehicleNodeCount", "value"); + NodesCountPed = Xml.GetChildUIntAttribute(node, "PedNodeCount", "value"); + + List nodelist = new List(); + List linklist = new List(); + List junclist = new List(); + List jhmblist = new List(); + List jreflist = new List(); + + var nodesnode = node.SelectSingleNode("Nodes"); + if (nodesnode != null) + { + var nodeitems = nodesnode.SelectNodes("Item"); + foreach (XmlNode nodeitem in nodeitems) + { + XmlNodeWrapper n = new XmlNodeWrapper(linklist); + n.ReadXml(nodeitem); + nodelist.Add(n.Node); + } + } + + var juncsnode = node.SelectSingleNode("Junctions"); + if (juncsnode != null) + { + var juncitems = juncsnode.SelectNodes("Item"); + foreach (XmlNode juncitem in juncitems) + { + XmlJunctionWrapper j = new XmlJunctionWrapper(jhmblist); + j.ReadXml(juncitem); + junclist.Add(j.Junction); + } + } + + var jrefsnode = node.SelectSingleNode("JunctionRefs"); + if (jrefsnode != null) + { + var jrefitems = jrefsnode.SelectNodes("Item"); + foreach (XmlNode jrefitem in jrefitems) + { + NodeJunctionRef jref = new NodeJunctionRef(); + jref.ReadXml(jrefitem); + jreflist.Add(jref); + } + } + + NodesCount = (uint)nodelist.Count; + Nodes = nodelist.ToArray(); + LinksCount = (uint)linklist.Count; + Links = linklist.ToArray(); + JunctionsCount = (uint)junclist.Count; + Junctions = junclist.ToArray(); + JunctionHeightmapBytesCount = (uint)jhmblist.Count; + JunctionHeightmapBytes = jhmblist.ToArray(); + JunctionRefsCount0 = (ushort)jreflist.Count; + JunctionRefsCount1 = JunctionRefsCount0; + JunctionRefs = jreflist.ToArray(); + + } + + + class XmlNodeWrapper : IMetaXmlItem + { + public Node Node; + private NodeLink[] AllLinks; + private List AllLinksList; + + public XmlNodeWrapper(Node node, NodeLink[] allLinks) + { + Node = node; + AllLinks = allLinks; + } + public XmlNodeWrapper(List allLinksList) + { + AllLinksList = allLinksList; + } + public void WriteXml(StringBuilder sb, int indent) + { + Node.WriteXml(sb, indent, AllLinks); + } + public void ReadXml(XmlNode node) + { + Node = new Node(); + Node.ReadXml(node, AllLinksList); + } + } + class XmlJunctionWrapper : IMetaXmlItem + { + public NodeJunction Junction; + private byte[] AllHeightmapData; + private List AllHeightmapDataList; + + public XmlJunctionWrapper(NodeJunction junc, byte[] allHeightmapData) + { + Junction = junc; + AllHeightmapData = allHeightmapData; + } + public XmlJunctionWrapper(List allHeightmapDataList) + { + AllHeightmapDataList = allHeightmapDataList; + } + public void WriteXml(StringBuilder sb, int indent) + { + Junction.WriteXml(sb, indent, AllHeightmapData); + } + public void ReadXml(XmlNode node) + { + Junction = new NodeJunction(); + Junction.ReadXml(node, AllHeightmapDataList); + } + } } [TypeConverter(typeof(ExpandableObjectConverter))] public struct Node @@ -218,15 +367,80 @@ namespace CodeWalker.GameFiles // Unk22.ToString() + ", " + Unk24.ToString() + ", " + Unk26.ToString(); return AreaID.ToString() + ", " + NodeID.ToString() + ", " + StreetName.ToString();// + ", X:" + - //PositionX.ToString() + ", Y:" + PositionY.ToString() + ", " + PositionZ.ToString();// + ", " + - //Flags0.ToString() + ", " + Flags1.ToString() + ", Z:" + - //Flags2.ToString() + ", " + LinkCountFlags.ToString() + ", " + - //Flags3.ToString() + ", " + Flags4.ToString(); + //PositionX.ToString() + ", Y:" + PositionY.ToString() + ", " + PositionZ.ToString();// + ", " + + //Flags0.ToString() + ", " + Flags1.ToString() + ", Z:" + + //Flags2.ToString() + ", " + LinkCountFlags.ToString() + ", " + + //Flags3.ToString() + ", " + Flags4.ToString(); } + + public void WriteXml(StringBuilder sb, int indent, NodeLink[] allLinks) + { + Vector3 p = new Vector3(); + p.X = PositionX / 4.0f; + p.Y = PositionY / 4.0f; + p.Z = PositionZ / 32.0f; + int linkCount = LinkCountFlags.Value >> 3; + int linkCountUnk = LinkCountFlags.Value & 7; + + YndXml.ValueTag(sb, indent, "AreaID", AreaID.ToString()); + YndXml.ValueTag(sb, indent, "NodeID", NodeID.ToString()); + YndXml.StringTag(sb, indent, "StreetName", YndXml.HashString(StreetName)); + YndXml.SelfClosingTag(sb, indent, "Position " + FloatUtil.GetVector3XmlString(p)); + YndXml.ValueTag(sb, indent, "Flags0", Flags0.Value.ToString()); + YndXml.ValueTag(sb, indent, "Flags1", Flags1.Value.ToString()); + YndXml.ValueTag(sb, indent, "Flags2", Flags2.Value.ToString()); + YndXml.ValueTag(sb, indent, "Flags3", Flags3.Value.ToString()); + YndXml.ValueTag(sb, indent, "Flags4", Flags4.Value.ToString()); + YndXml.ValueTag(sb, indent, "Flags5", linkCountUnk.ToString()); + + NodeLink[] links = null; + if (linkCount > 0) + { + links = new NodeLink[linkCount]; + for (int i = 0; i < linkCount; i++) + { + links[i] = allLinks[LinkID + i]; + } + } + YndXml.WriteItemArray(sb, links, indent, "Links"); + + } + public void ReadXml(XmlNode node, List allLinksList) + { + AreaID = (ushort)Xml.GetChildUIntAttribute(node, "AreaID", "value"); + NodeID = (ushort)Xml.GetChildUIntAttribute(node, "NodeID", "value"); + StreetName = XmlYnd.GetTextHash(Xml.GetChildInnerText(node, "StreetName")); + Vector3 p = Xml.GetChildVector3Attributes(node, "Position", "x", "y", "z"); + PositionX = (short)(p.X * 4.0f); + PositionY = (short)(p.Y * 4.0f); + PositionZ = (short)(p.Z * 32.0f); + Flags0 = (byte)Xml.GetChildUIntAttribute(node, "Flags0", "value"); + Flags1 = (byte)Xml.GetChildUIntAttribute(node, "Flags1", "value"); + Flags2 = (byte)Xml.GetChildUIntAttribute(node, "Flags2", "value"); + Flags3 = (byte)Xml.GetChildUIntAttribute(node, "Flags3", "value"); + Flags4 = (byte)Xml.GetChildUIntAttribute(node, "Flags4", "value"); + int linkCountUnk = (byte)Xml.GetChildUIntAttribute(node, "Flags5", "value"); + + LinkID = (ushort)allLinksList.Count; + int linkCount = 0; + var linksnode = node.SelectSingleNode("Links"); + if (linksnode != null) + { + var linkitems = linksnode.SelectNodes("Item"); + foreach (XmlNode linkitem in linkitems) + { + NodeLink link = new NodeLink(); + link.ReadXml(linkitem); + allLinksList.Add(link); + linkCount++; + } + } + LinkCountFlags = (byte)((linkCount << 3) + (linkCountUnk & 7)); + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public struct NodeLink + [TypeConverter(typeof(ExpandableObjectConverter))] public struct NodeLink : IMetaXmlItem { public ushort AreaID { get; set; } public ushort NodeID { get; set; } @@ -239,6 +453,25 @@ namespace CodeWalker.GameFiles { return AreaID.ToString() + ", " + NodeID.ToString() + ", " + Flags0.Value.ToString() + ", " + Flags1.Value.ToString() + ", " + Flags2.Value.ToString() + ", " + LinkLength.Value.ToString(); } + + public void WriteXml(StringBuilder sb, int indent) + { + YndXml.ValueTag(sb, indent, "ToAreaID", AreaID.ToString()); + YndXml.ValueTag(sb, indent, "ToNodeID", NodeID.ToString()); + YndXml.ValueTag(sb, indent, "Flags0", Flags0.Value.ToString()); + YndXml.ValueTag(sb, indent, "Flags1", Flags1.Value.ToString()); + YndXml.ValueTag(sb, indent, "Flags2", Flags2.Value.ToString()); + YndXml.ValueTag(sb, indent, "LinkLength", LinkLength.Value.ToString()); + } + public void ReadXml(XmlNode node) + { + AreaID = (ushort)Xml.GetChildUIntAttribute(node, "ToAreaID", "value"); + NodeID = (ushort)Xml.GetChildUIntAttribute(node, "ToNodeID", "value"); + Flags0 = (byte)Xml.GetChildUIntAttribute(node, "Flags0", "value"); + Flags1 = (byte)Xml.GetChildUIntAttribute(node, "Flags1", "value"); + Flags2 = (byte)Xml.GetChildUIntAttribute(node, "Flags2", "value"); + LinkLength = (byte)Xml.GetChildUIntAttribute(node, "LinkLength", "value"); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public struct NodeJunction @@ -255,9 +488,54 @@ namespace CodeWalker.GameFiles { return PositionX.ToString() + ", " + PositionY.ToString() + ": " + MinZ.ToString() + ", " + MaxZ.ToString() + ": " + HeightmapDimX.ToString() + " x " + HeightmapDimY.ToString(); } + + public void WriteXml(StringBuilder sb, int indent, byte[] allHeightmapData) + { + Vector2 p = new Vector2(); + p.X = PositionX / 4.0f; + p.Y = PositionY / 4.0f; + float minz = MinZ / 32.0f; + float maxz = MaxZ / 32.0f; + + YndXml.SelfClosingTag(sb, indent, "Position " + FloatUtil.GetVector2XmlString(p)); + YndXml.ValueTag(sb, indent, "MinZ", FloatUtil.ToString(minz)); + YndXml.ValueTag(sb, indent, "MaxZ", FloatUtil.ToString(maxz)); + YndXml.ValueTag(sb, indent, "SizeX", HeightmapDimX.ToString()); + YndXml.ValueTag(sb, indent, "SizeY", HeightmapDimY.ToString()); + + byte[] hmdata = null; + int hmbcount = HeightmapDimX * HeightmapDimY; + if (hmbcount > 0) + { + hmdata = new byte[hmbcount]; + Buffer.BlockCopy(allHeightmapData, HeightmapPtr, hmdata, 0, hmbcount); + } + YndXml.WriteRawArray(sb, hmdata, indent, "Heightmap", "", RelXml.FormatHexByte, Math.Max(HeightmapDimX, (byte)1)); + + } + public void ReadXml(XmlNode node, List allHeightmapDataList) + { + Vector2 p = Xml.GetChildVector2Attributes(node, "Position", "x", "y"); + float minz = Xml.GetChildFloatAttribute(node, "MinZ", "value"); + float maxz = Xml.GetChildFloatAttribute(node, "MaxZ", "value"); + HeightmapDimX = (byte)Xml.GetChildUIntAttribute(node, "SizeX", "value"); + HeightmapDimY = (byte)Xml.GetChildUIntAttribute(node, "SizeY", "value"); + PositionX = (short)(p.X * 4.0f); + PositionY = (short)(p.Y * 4.0f); + MinZ = (short)(minz * 32.0f); + MaxZ = (short)(maxz * 32.0f); + + byte[] hmdata = Xml.GetChildRawByteArray(node, "Heightmap"); + HeightmapPtr = (ushort)allHeightmapDataList.Count; + if (hmdata != null) + { + allHeightmapDataList.AddRange(hmdata); + } + + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public struct NodeJunctionRef + [TypeConverter(typeof(ExpandableObjectConverter))] public struct NodeJunctionRef : IMetaXmlItem { public ushort AreaID { get; set; } public ushort NodeID { get; set; } @@ -268,6 +546,21 @@ namespace CodeWalker.GameFiles { return AreaID.ToString() + ", " + NodeID.ToString() + ", " + JunctionID.ToString(); } + + public void WriteXml(StringBuilder sb, int indent) + { + YndXml.ValueTag(sb, indent, "AreaID", AreaID.ToString()); + YndXml.ValueTag(sb, indent, "NodeID", NodeID.ToString()); + YndXml.ValueTag(sb, indent, "JunctionID", JunctionID.ToString()); + YndXml.ValueTag(sb, indent, "Unk0", Unk0.ToString()); + } + public void ReadXml(XmlNode node) + { + AreaID = (ushort)Xml.GetChildUIntAttribute(node, "AreaID", "value"); + NodeID = (ushort)Xml.GetChildUIntAttribute(node, "NodeID", "value"); + JunctionID = (ushort)Xml.GetChildUIntAttribute(node, "JunctionID", "value"); + Unk0 = (ushort)Xml.GetChildUIntAttribute(node, "Unk0", "value"); + } } diff --git a/ExploreForm.cs b/ExploreForm.cs index 1904db2..ef1fa9c 100644 --- a/ExploreForm.cs +++ b/ExploreForm.cs @@ -231,7 +231,7 @@ namespace CodeWalker InitFileType(".ymt", "Metadata (Binary)", 6, FileTypeAction.ViewYmt); InitFileType(".pso", "Metadata (PSO)", 6, FileTypeAction.ViewJPso); InitFileType(".gfx", "Scaleform Flash", 7); - InitFileType(".ynd", "Path Nodes", 8); + InitFileType(".ynd", "Path Nodes", 8, FileTypeAction.ViewYnd); InitFileType(".ynv", "Nav Mesh", 9, FileTypeAction.ViewModel); InitFileType(".yvr", "Vehicle Record", 9, FileTypeAction.ViewYvr); InitFileType(".ywr", "Waypoint Record", 9, FileTypeAction.ViewYwr); @@ -1302,6 +1302,7 @@ namespace CodeWalker case FileTypeAction.ViewYwr: case FileTypeAction.ViewYvr: case FileTypeAction.ViewYcd: + case FileTypeAction.ViewYnd: case FileTypeAction.ViewCacheDat: return true; case FileTypeAction.ViewHex: @@ -1324,6 +1325,7 @@ namespace CodeWalker case FileTypeAction.ViewJPso: case FileTypeAction.ViewCut: case FileTypeAction.ViewRel: + case FileTypeAction.ViewYnd: return true; } return false; @@ -1425,6 +1427,9 @@ namespace CodeWalker case FileTypeAction.ViewYcd: ViewYcd(name, path, data, fe); break; + case FileTypeAction.ViewYnd: + ViewYnd(name, path, data, fe); + break; case FileTypeAction.ViewCacheDat: ViewCacheDat(name, path, data, fe); break; @@ -1627,6 +1632,13 @@ namespace CodeWalker f.Show(); f.LoadYcd(ycd); } + private void ViewYnd(string name, string path, byte[] data, RpfFileEntry e) + { + var ynd = RpfFile.GetFile(e, data); + MetaForm f = new MetaForm(this); + f.Show(); + f.LoadMeta(ynd); + } private void ViewCacheDat(string name, string path, byte[] data, RpfFileEntry e) { var cachedat = RpfFile.GetFile(e, data); @@ -2407,6 +2419,10 @@ namespace CodeWalker { mformat = MetaFormat.AudioRel; } + if (fnamel.EndsWith(".ynd.xml")) + { + mformat = MetaFormat.Ynd; + } fname = fname.Substring(0, fname.Length - trimlength); fnamel = fnamel.Substring(0, fnamel.Length - trimlength); @@ -2460,6 +2476,17 @@ namespace CodeWalker data = rel.Save(); break; } + case MetaFormat.Ynd: + { + var ynd = XmlYnd.GetYnd(doc); + if (ynd.NodeDictionary == null) + { + MessageBox.Show(fname + ": Schema not supported.", "Cannot import YND XML"); + continue; + } + data = ynd.Save(); + break; + } } @@ -4131,7 +4158,8 @@ namespace CodeWalker ViewYwr = 15, ViewYvr = 16, ViewYcd = 17, - ViewCacheDat = 18, + ViewYnd = 18, + ViewCacheDat = 19, } diff --git a/Forms/MetaForm.cs b/Forms/MetaForm.cs index e7d9e10..81169ec 100644 --- a/Forms/MetaForm.cs +++ b/Forms/MetaForm.cs @@ -303,6 +303,20 @@ namespace CodeWalker.Forms if (cut.Pso != null) metaFormat = MetaFormat.PSO; } } + public void LoadMeta(YndFile ynd) + { + var fn = ((ynd?.RpfFileEntry?.Name) ?? "") + ".xml"; + Xml = MetaXml.GetXml(ynd, out fn); + FileName = fn; + RawPropertyGrid.SelectedObject = ynd; + rpfFileEntry = ynd?.RpfFileEntry; + modified = false; + metaFormat = MetaFormat.XML; + if (ynd?.RpfFileEntry != null) + { + metaFormat = MetaFormat.Ynd; + } + } public void LoadMeta(CacheDatFile cachedat) { var fn = ((cachedat?.FileEntry?.Name) ?? "") + ".xml"; @@ -364,6 +378,15 @@ namespace CodeWalker.Forms case MetaFormat.CacheFile: MessageBox.Show("Sorry, CacheFile import is not supported.", "Cannot import CacheFile XML"); return false; + case MetaFormat.Ynd: + var ynd = XmlYnd.GetYnd(doc); + if (ynd.NodeDictionary == null) + { + MessageBox.Show("Schema not supported.", "Cannot import YND XML"); + return false; + } + data = ynd.Save(); + break; } } #if !DEBUG