From c5b8b143aa936d97b75ed6182deaf6463bcda629 Mon Sep 17 00:00:00 2001 From: nordgaren Date: Wed, 25 Feb 2026 11:08:29 -0700 Subject: [PATCH 1/2] Initial update to NVA --- SoulsFormats/Formats/NVA.cs | 1195 +++++++++++++++++++++-------------- 1 file changed, 728 insertions(+), 467 deletions(-) diff --git a/SoulsFormats/Formats/NVA.cs b/SoulsFormats/Formats/NVA.cs index 9b656373..183a34b0 100644 --- a/SoulsFormats/Formats/NVA.cs +++ b/SoulsFormats/Formats/NVA.cs @@ -7,7 +7,7 @@ namespace SoulsFormats { /// - /// A file that defines the placement and properties of navmeshes in BB, DS3, and Sekiro. Extension: .nva + /// A file that defines the placement and properties of navmeshes in BB, DS3, Sekiro, and ER. Extension: .nva /// public class NVA : SoulsFile { @@ -30,6 +30,11 @@ public enum NVAVersion : uint /// Sekiro /// Sekiro = 5, + + /// + /// Elden Ring + /// + EldenRing = 8, } /// @@ -42,38 +47,73 @@ public enum NVAVersion : uint /// public NavmeshSection Navmeshes { get; set; } - /// - /// Unknown. - /// - public Section1 Entries1 { get; set; } + public FaceDataSection FaceDatas { get; set; } - /// - /// Unknown. - /// - public Section2 Entries2 { get; set; } + public NodeBankSection NodeBanks { get; set; } + + public Section3 Entries3 { get; set; } /// /// Connections between different navmeshes. /// public ConnectorSection Connectors { get; set; } + public LevelConnectorSection LevelConnectors { get; set; } + + public Section9 Entries9 { get; set; } + public Section10 Entries10 { get; set; } + public Section11 Entries11 { get; set; } + public Section13 Entries13 { get; set; } + /// - /// Unknown. + /// Keeps track of whether the empty Section 12 was present in the file. /// - public Section7 Entries7 { get; set; } + public bool HasSection12 { get; set; } = false; + + /// + /// Keeps track of the version used by the empty Section 12 for reserialization. + /// + public int Section12Version { get; set; } = 1; /// - /// Creates an empty NVA formatted for DS3. + /// Creates an empty NVA formatted for Elden Ring. /// - public NVA() + public NVA() // : this(NVAVersion.EldenRing) // for future compatability { - Version = NVAVersion.DarkSouls3; + Version = NVAVersion.EldenRing; + Entries11 = new Section11(); Navmeshes = new NavmeshSection(2); - Entries1 = new Section1(); - Entries2 = new Section2(); + FaceDatas = new FaceDataSection(); + NodeBanks = new NodeBankSection(); + Entries3 = new Section3(); Connectors = new ConnectorSection(); - Entries7 = new Section7(); + LevelConnectors = new LevelConnectorSection(); + Entries9 = new Section9(); + Entries10 = new Section10(); + HasSection12 = true; + Entries13 = new Section13(); } + + // /// + // /// Creates an empty NVA formatted for specified version. + // /// + // public NVA(NVAVersion version) + // { + // // Need to go through this and only intialize the sections needed for that version. + // // Probably need a switch statement with fallthrough + // Version = version; + // Entries11 = new Section11(); + // Navmeshes = new NavmeshSection(2); + // FaceDatas = new FaceDataSection(); + // NodeBanks = new NodeBankSection(); + // Entries3 = new Section3(); + // Connectors = new ConnectorSection(); + // LevelConnectors = new LevelConnectorSection(); + // Entries9 = new Section9(); + // Entries10 = new Section10(); + // HasSection12 = true; + // Entries13 = new Section13(); + // } /// /// Checks whether the data appears to be a file of this format. @@ -96,27 +136,57 @@ protected override void Read(BinaryReaderEx br) br.AssertASCII("NVMA"); Version = br.ReadEnum32(); br.ReadUInt32(); // File size - br.AssertInt32(Version == NVAVersion.OldBloodborne ? 8 : 9); // Section count - - Navmeshes = new NavmeshSection(br); - Entries1 = new Section1(br); - Entries2 = new Section2(br); - new Section3(br); - Connectors = new ConnectorSection(br); - var connectorPoints = new ConnectorPointSection(br); - var connectorConditions = new ConnectorConditionSection(br); - Entries7 = new Section7(br); - MapNodeSection mapNodes; - if (Version == NVAVersion.OldBloodborne) - mapNodes = new MapNodeSection(1); - else - mapNodes = new MapNodeSection(br); - - foreach (Navmesh navmesh in Navmeshes) - navmesh.TakeMapNodes(mapNodes); - - foreach (Connector connector in Connectors) - connector.TakePointsAndConds(connectorPoints, connectorConditions); + int sectionCount = br.ReadInt32(); + + NavMeshConnectionSection navMeshConns = null; + GraphConnectionSection graphConns = null; + GateNodeSection gateNodes = null; + + for (int i = 0; i < sectionCount; i++) + { + // Peek at the index of the next section + int index = br.GetInt32(br.Position); + + switch (index) + { + case 0: Navmeshes = new NavmeshSection(br); break; + case 1: FaceDatas = new FaceDataSection(br); break; + case 2: NodeBanks = new NodeBankSection(br); break; + case 3: Entries3 = new Section3(br); break; + case 4: Connectors = new ConnectorSection(br); break; + case 5: navMeshConns = new NavMeshConnectionSection(br); break; + case 6: graphConns = new GraphConnectionSection(br); break; + case 7: LevelConnectors = new LevelConnectorSection(br); break; + case 8: gateNodes = new GateNodeSection(br); break; + case 9: Entries9 = new Section9(br); break; + case 10: Entries10 = new Section10(br); break; + case 11: Entries11 = new Section11(br); break; + case 12: + br.AssertInt32(12); + Section12Version = br.ReadInt32(); + int length = br.ReadInt32(); + br.ReadInt32(); // entryCount + br.Skip(length); + HasSection12 = true; + break; + case 13: Entries13 = new Section13(br); break; + default: + throw new NotImplementedException($"Unrecognized section index: {index}"); + } + } + + // Link sub-sections back to their parent classes + if (Navmeshes != null && gateNodes != null) + { + foreach (Navmesh navmesh in Navmeshes) + navmesh.TakeGateNodes(gateNodes); + } + + if (Connectors != null && navMeshConns != null && graphConns != null) + { + foreach (Connector connector in Connectors) + connector.TakeConnections(navMeshConns, graphConns); + } } /// @@ -124,37 +194,84 @@ protected override void Read(BinaryReaderEx br) /// protected override void Write(BinaryWriterEx bw) { - var connectorPoints = new ConnectorPointSection(); - var connectorConditions = new ConnectorConditionSection(); - foreach (Connector connector in Connectors) - connector.GivePointsAndConds(connectorPoints, connectorConditions); + var navMeshConns = new NavMeshConnectionSection(); + var graphConns = new GraphConnectionSection(); + if (Connectors != null) + { + foreach (Connector connector in Connectors) + connector.GiveConnections(navMeshConns, graphConns); + } - var mapNodes = new MapNodeSection(Version == NVAVersion.Sekiro ? 2 : 1); - foreach (Navmesh navmesh in Navmeshes) - navmesh.GiveMapNodes(mapNodes); + var gateNodes = new GateNodeSection(Version == NVAVersion.Sekiro ? 2 : 1); + if (Navmeshes != null) + { + foreach (Navmesh navmesh in Navmeshes) + navmesh.GiveGateNodes(gateNodes); + } + + // Dynamically determine the section count based on present data + int sectionCount = 0; + if (Navmeshes != null) sectionCount++; + if (FaceDatas != null) sectionCount++; + if (NodeBanks != null) sectionCount++; + if (Entries3 != null) sectionCount++; + if (Connectors != null) sectionCount += 3; // Connectors (4), NavMeshConns (5), GraphConns (6) + if (LevelConnectors != null) sectionCount++; + + // GateNodes (8) - Explicitly exclude for OldBloodborne + if (Navmeshes != null && Version != NVAVersion.OldBloodborne) sectionCount++; + + if (Entries9 != null) sectionCount++; + if (Entries10 != null) sectionCount++; + if (Entries11 != null) sectionCount++; + if (HasSection12) sectionCount++; + if (Entries13 != null) sectionCount++; bw.BigEndian = false; bw.WriteASCII("NVMA"); bw.WriteUInt32((uint)Version); bw.ReserveUInt32("FileSize"); - bw.WriteInt32(Version == NVAVersion.OldBloodborne ? 8 : 9); - - Navmeshes.Write(bw, 0); - Entries1.Write(bw, 1); - Entries2.Write(bw, 2); - new Section3().Write(bw, 3); - Connectors.Write(bw, 4); - connectorPoints.Write(bw, 5); - connectorConditions.Write(bw, 6); - Entries7.Write(bw, 7); - if (Version != NVAVersion.OldBloodborne) - mapNodes.Write(bw, 8); + bw.WriteInt32(sectionCount); + + // Write sections only if they exist + if (Entries11 != null) Entries11.Write(bw, 11); + if (Navmeshes != null) Navmeshes.Write(bw, 0); + if (FaceDatas != null) FaceDatas.Write(bw, 1); + if (NodeBanks != null) NodeBanks.Write(bw, 2); + if (Entries3 != null) Entries3.Write(bw, 3); + + if (Connectors != null) + { + Connectors.Write(bw, 4); + navMeshConns.Write(bw, 5); + graphConns.Write(bw, 6); + } + + if (LevelConnectors != null) LevelConnectors.Write(bw, 7); + if (Navmeshes != null) gateNodes.Write(bw, 8); + if (Entries9 != null) Entries9.Write(bw, 9); + if (Entries10 != null) Entries10.Write(bw, 10); + + if (HasSection12) + { + bw.WriteInt32(12); + bw.WriteInt32(Section12Version); + bw.ReserveInt32("Section12Length"); + bw.WriteInt32(0); // count + + long start = bw.Position; + if (bw.Position % 0x10 != 0) + bw.WritePattern(0x10 - (int)bw.Position % 0x10, 0xFF); + bw.FillInt32("Section12Length", (int)(bw.Position - start)); + } + + if (Entries13 != null) Entries13.Write(bw, 13); bw.FillUInt32("FileSize", (uint)bw.Position); } /// - /// NVA is split up into 8 lists of different types. + /// NVA is split up into lists of different types. /// public abstract class Section : List { @@ -236,17 +353,17 @@ public class Navmesh /// /// Position of the mesh. /// - public Vector3 Position { get; set; } + public Vector4 Position { get; set; } /// /// Rotation of the mesh, in radians. /// - public Vector3 Rotation { get; set; } + public Vector4 Rotation { get; set; } /// /// Scale of the mesh. /// - public Vector3 Scale { get; set; } + public Vector4 Scale { get; set; } /// /// Unknown. @@ -257,355 +374,332 @@ public class Navmesh /// Unknown. /// public int ModelID { get; set; } - - /// - /// Unknown. - /// - public int Unk38 { get; set; } - + public int FaceDataIndex { get; set; } + public int Unk3C { get; set; } /// /// Should equal number of vertices in the model file. /// - public int VertexCount { get; set; } - - /// - /// Unknown. - /// - public List NameReferenceIDs { get; set; } + public int FaceCount { get; set; } + public int Unk4C { get; set; } + public bool IsConnectedNavmeshesInline { get; set; } + public List ConnectedNavmeshes { get; set; } /// /// Adjacent nodes in an inter-navmesh graph. /// - public List MapNodes { get; set; } - - /// - /// Unknown - /// - public bool Unk4C { get; set; } + public List GateNodes { get; set; } - private short MapNodesIndex; - private short MapNodeCount; + private short GateNodeIndex; + private short GateNodeCount; + private int ConnectedNavmeshesCount; /// /// Creates a Navmesh with default values. /// public Navmesh() { - Scale = Vector3.One; - NameReferenceIDs = new List(); - MapNodes = new List(); + Scale = Vector4.One; + ConnectedNavmeshes = new List(); + GateNodes = new List(); } internal Navmesh(BinaryReaderEx br, int version) { - Position = br.ReadVector3(); - br.AssertSingle(1); - Rotation = br.ReadVector3(); - br.AssertInt32(0); - Scale = br.ReadVector3(); - br.AssertInt32(0); + Position = br.ReadVector4(); + Rotation = br.ReadVector4(); + Scale = br.ReadVector4(); NameID = br.ReadInt32(); ModelID = br.ReadInt32(); - Unk38 = br.ReadInt32(); - br.AssertInt32(0); - VertexCount = br.ReadInt32(); - int nameRefCount = br.ReadInt32(); - MapNodesIndex = br.ReadInt16(); - MapNodeCount = br.ReadInt16(); - Unk4C = br.AssertInt32(0, 1) == 1; + FaceDataIndex = br.ReadInt32(); + Unk3C = br.ReadInt32(); + FaceCount = br.ReadInt32(); + ConnectedNavmeshesCount = br.ReadInt32(); + GateNodeIndex = br.ReadInt16(); + GateNodeCount = br.ReadInt16(); + Unk4C = br.ReadInt32(); if (version < 4) { - if (nameRefCount > 16) - throw new InvalidDataException("Name reference count should not exceed 16 in DS3/BB."); - NameReferenceIDs = new List(br.ReadInt32s(nameRefCount)); - for (int i = 0; i < 16 - nameRefCount; i++) + if (ConnectedNavmeshesCount > 16) + throw new InvalidDataException("Connected navmeshes count should not exceed 16 in DS3/BB."); + ConnectedNavmeshes = new List(br.ReadInt32s(ConnectedNavmeshesCount)); + for (int i = 0; i < 16 - ConnectedNavmeshesCount; i++) br.AssertInt32(-1); } else { - int nameRefOffset = br.ReadInt32(); + int unkOffset = br.ReadInt32(); br.AssertInt32(0); br.AssertInt32(0); br.AssertInt32(0); - NameReferenceIDs = new List(br.GetInt32s(nameRefOffset, nameRefCount)); + + if (unkOffset == 0xFF01) + { + IsConnectedNavmeshesInline = true; + ConnectedNavmeshes = new List(br.ReadInt32s(12)); + } + else if (ConnectedNavmeshesCount > 0) + { + ConnectedNavmeshes = new List(br.GetInt32s(unkOffset, ConnectedNavmeshesCount)); + } + else + { + ConnectedNavmeshes = new List(); + } } } - internal void TakeMapNodes(MapNodeSection entries8) + internal void TakeGateNodes(GateNodeSection gateNodes) { - MapNodes = new List(MapNodeCount); - for (int i = 0; i < MapNodeCount; i++) - MapNodes.Add(entries8[MapNodesIndex + i]); - MapNodeCount = -1; + GateNodes = new List(GateNodeCount); + for (int i = 0; i < GateNodeCount; i++) + GateNodes.Add(gateNodes[GateNodeIndex + i]); + GateNodeCount = -1; + } - foreach (MapNode mapNode in MapNodes) - { - if (mapNode.SiblingDistances.Count > MapNodes.Count) - mapNode.SiblingDistances.RemoveRange(MapNodes.Count, mapNode.SiblingDistances.Count - MapNodes.Count); - } + internal void GiveGateNodes(GateNodeSection gateNodesList) + { + // Sometimes when the gate node count is 0 the index is also 0, + // but usually this is accurate. + GateNodeIndex = (short)gateNodesList.Count; + gateNodesList.AddRange(GateNodes); } internal void Write(BinaryWriterEx bw, int version, int index) { - bw.WriteVector3(Position); - bw.WriteSingle(1); - bw.WriteVector3(Rotation); - bw.WriteInt32(0); - bw.WriteVector3(Scale); - bw.WriteInt32(0); + bw.WriteVector4(Position); + bw.WriteVector4(Rotation); + bw.WriteVector4(Scale); bw.WriteInt32(NameID); bw.WriteInt32(ModelID); - bw.WriteInt32(Unk38); - bw.WriteInt32(0); - bw.WriteInt32(VertexCount); - bw.WriteInt32(NameReferenceIDs.Count); - bw.WriteInt16(MapNodesIndex); - bw.WriteInt16((short)MapNodes.Count); - bw.WriteInt32(Unk4C ? 1 : 0); + bw.WriteInt32(FaceDataIndex); + bw.WriteInt32(Unk3C); + bw.WriteInt32(FaceCount); + bw.WriteInt32(IsConnectedNavmeshesInline ? ConnectedNavmeshesCount : ConnectedNavmeshes.Count); + bw.WriteInt16(GateNodeIndex); + bw.WriteInt16((short)GateNodes.Count); + bw.WriteInt32(Unk4C); if (version < 4) { - if (NameReferenceIDs.Count > 16) - throw new InvalidDataException("Name reference count should not exceed 16 in DS3/BB."); - bw.WriteInt32s(NameReferenceIDs); - for (int i = 0; i < 16 - NameReferenceIDs.Count; i++) + if (ConnectedNavmeshes.Count > 16) + throw new InvalidDataException("Connected navmeshes count should not exceed 16 in DS3/BB."); + bw.WriteInt32s(ConnectedNavmeshes); + for (int i = 0; i < 16 - ConnectedNavmeshes.Count; i++) bw.WriteInt32(-1); } else { - bw.ReserveInt32($"NameRefOffset{index}"); - bw.WriteInt32(0); - bw.WriteInt32(0); - bw.WriteInt32(0); + if (IsConnectedNavmeshesInline) + { + bw.WriteInt32(0xFF01); + bw.WriteInt32(0); + bw.WriteInt32(0); + bw.WriteInt32(0); + for (int i = 0; i < 12; i++) + { + bw.WriteInt32(i < ConnectedNavmeshes.Count ? ConnectedNavmeshes[i] : 0); + } + } + else + { + bw.ReserveInt32($"UnkOffset{index}"); + bw.WriteInt32(0); + bw.WriteInt32(0); + bw.WriteInt32(0); + } } } internal void WriteNameRefs(BinaryWriterEx bw, int version, int index) { - if (version >= 4) + if (version >= 4 && !IsConnectedNavmeshesInline) { - bw.FillInt32($"NameRefOffset{index}", (int)bw.Position); - bw.WriteInt32s(NameReferenceIDs); + if (ConnectedNavmeshes.Count > 0) + { + bw.FillInt32($"UnkOffset{index}", (int)bw.Position); + bw.WriteInt32s(ConnectedNavmeshes); + } + else + { + bw.FillInt32($"UnkOffset{index}", 0); + } } } - - internal void GiveMapNodes(MapNodeSection mapNodes) - { - // Sometimes when the map node count is 0 the index is also 0, - // but usually this is accurate. - MapNodesIndex = (short)mapNodes.Count; - mapNodes.AddRange(MapNodes); - } - - /// - /// Returns a string representation of the navmesh. - /// - public override string ToString() - { - return $"{NameID} {Position} {Rotation} [{NameReferenceIDs.Count} References] [{MapNodes.Count} MapNodes]"; - } } /// - /// Unknown. + /// Face Data Section. /// - public class Section1 : Section + public class FaceDataSection : Section { - /// - /// Creates an empty Section1. - /// - public Section1() : base(1) { } + public FaceDataSection() : base(1) { } - internal Section1(BinaryReaderEx br) : base(br, 1, 1) { } + internal FaceDataSection(BinaryReaderEx br) : base(br, 1, 1) { } internal override void ReadEntries(BinaryReaderEx br, int count) { - for (int i = 0; i < count; i++) - Add(new Entry1(br)); + for (int i = 0; i < count; i++) + Add(new FaceData(br)); } internal override void WriteEntries(BinaryWriterEx bw) { - foreach (Entry1 entry in this) + foreach (FaceData entry in this) entry.Write(bw); } } /// - /// Unknown. + /// Face Data. /// - public class Entry1 + public class FaceData { - /// - /// Unknown; always 0 in DS3 and SDT, sometimes 1 in BB. - /// public int Unk00 { get; set; } + public int Unk04 { get; set; } - /// - /// Creates an Entry1 with default values. - /// - public Entry1() { } + public FaceData() { } - internal Entry1(BinaryReaderEx br) + internal FaceData(BinaryReaderEx br) { Unk00 = br.ReadInt32(); - br.AssertInt32(0); + Unk04 = br.ReadInt32(); } internal void Write(BinaryWriterEx bw) { bw.WriteInt32(Unk00); - bw.WriteInt32(0); + bw.WriteInt32(Unk04); } - + /// /// Returns a string representation of the entry. /// public override string ToString() { - return $"{Unk00}"; + return $"Unk00: {Unk00} Unk04: {Unk04}"; } } /// - /// Unknown. + /// Node Bank Section. /// - public class Section2 : Section + public class NodeBankSection : Section { - /// - /// Creates an empty Section2. - /// - public Section2() : base(1) { } + public NodeBankSection() : base(1) { } - internal Section2(BinaryReaderEx br) : base(br, 2, 1) { } + internal NodeBankSection(BinaryReaderEx br) : base(br, 2, 1) { } internal override void ReadEntries(BinaryReaderEx br, int count) { - for (int i = 0; i < count; i++) - Add(new Entry2(br)); + for (int i = 0; i < count; i++) + Add(new NodeBank(br)); } internal override void WriteEntries(BinaryWriterEx bw) { - foreach (Entry2 entry in this) + foreach (NodeBank entry in this) entry.Write(bw); } } /// - /// Unknown. + /// Node Bank. /// - public class Entry2 + public class NodeBank { /// - /// Unknown; seems to just be the index of this entry. - /// - public int Unk00 { get; set; } - - /// - /// References in this entry; maximum of 64. + /// Bank ID. /// - public List References { get; set; } + public int BankID { get; set; } /// - /// Unknown. + /// Face references in this entry; maximum of 64. /// - public int Unk08 { get; set; } + public List Faces { get; set; } + + public int EntityID { get; set; } + public int Unk0C { get; set; } - /// - /// Creates an Entry2 with default values. - /// - public Entry2() + public NodeBank() { - References = new List(); - Unk08 = -1; + Faces = new List(); } - internal Entry2(BinaryReaderEx br) + internal NodeBank(BinaryReaderEx br) { - Unk00 = br.ReadInt32(); - int referenceCount = br.ReadInt32(); - Unk08 = br.ReadInt32(); - br.AssertInt32(0); - if (referenceCount > 64) - throw new InvalidDataException("Entry2 reference count should not exceed 64."); + BankID = br.ReadInt32(); + int faceNum = br.ReadInt32(); + EntityID = br.ReadInt32(); + Unk0C = br.ReadInt32(); + + if (faceNum > 64) + throw new InvalidDataException("NodeBank face count should not exceed 64."); - References = new List(referenceCount); - for (int i = 0; i < referenceCount; i++) - References.Add(new Reference(br)); + Faces = new List(faceNum); + for (int i = 0; i < faceNum; i++) + Faces.Add(new NodeBankFace(br)); - for (int i = 0; i < 64 - referenceCount; i++) + for (int i = 0; i < 64 - faceNum; i++) br.AssertInt64(0); } internal void Write(BinaryWriterEx bw) { - bw.WriteInt32(Unk00); - bw.WriteInt32(References.Count); - bw.WriteInt32(Unk08); - bw.WriteInt32(0); - if (References.Count > 64) - throw new InvalidDataException("Entry2 reference count should not exceed 64."); + bw.WriteInt32(BankID); + bw.WriteInt32(Faces.Count); + bw.WriteInt32(EntityID); + bw.WriteInt32(Unk0C); - foreach (Reference reference in References) - reference.Write(bw); + if (Faces.Count > 64) + throw new InvalidDataException("NodeBank face count should not exceed 64."); - for (int i = 0; i < 64 - References.Count; i++) + foreach (NodeBankFace face in Faces) + face.Write(bw); + + for (int i = 0; i < 64 - Faces.Count; i++) bw.WriteInt64(0); } - + /// /// Returns a string representation of the entry. /// public override string ToString() { - return $"{Unk00} {Unk08} [{References.Count} References]"; + return $"{BankID} {EntityID} [{Faces.Count} Faces]"; } - /// - /// Unknown. - /// - public class Reference - { - /// - /// Unknown. - /// - public int UnkIndex { get; set; } - - /// - /// Unknown. - /// - public int NameID { get; set; } + } - /// - /// Creates a Reference with defalt values. - /// - public Reference() { } + public class NodeBankFace + { + public int FaceIndex { get; set; } + public int CollisionID { get; set; } - internal Reference(BinaryReaderEx br) - { - UnkIndex = br.ReadInt32(); - NameID = br.ReadInt32(); - } + public NodeBankFace() { } - internal void Write(BinaryWriterEx bw) - { - bw.WriteInt32(UnkIndex); - bw.WriteInt32(NameID); - } + internal NodeBankFace(BinaryReaderEx br) + { + FaceIndex = br.ReadInt32(); + CollisionID = br.ReadInt32(); + } - /// - /// Returns a string representation of the reference. - /// - public override string ToString() - { - return $"{UnkIndex} {NameID}"; - } + internal void Write(BinaryWriterEx bw) + { + bw.WriteInt32(FaceIndex); + bw.WriteInt32(CollisionID); + } + + /// + /// Returns a string representation of the reference. + /// + public override string ToString() + { + return $"{FaceIndex} {CollisionID}"; } } - private class Section3 : Section + public class Section3 : Section { public Section3() : base(1) { } @@ -613,27 +707,44 @@ internal Section3(BinaryReaderEx br) : base(br, 3, 1) { } internal override void ReadEntries(BinaryReaderEx br, int count) { - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) Add(new Entry3(br)); } internal override void WriteEntries(BinaryWriterEx bw) { - foreach (Entry3 entry in this) + foreach (Entry3 entry in this) entry.Write(bw); } } - private class Entry3 + public class Entry3 { + public Vector4[] Vectors { get; set; } + + const int _numElements = 8; + + public Entry3() + { + Vectors = new Vector4[_numElements]; + } + internal Entry3(BinaryReaderEx br) { - throw new NotImplementedException("Section3 is empty in all known NVAs."); + // Reminder that this should be empty prior to Elden Ring + // throw new NotImplementedException("Section3 is empty in all known NVAs."); + Vectors = new Vector4[_numElements]; + for (int i = 0; i < _numElements; i++) + Vectors[i] = br.ReadVector4(); } internal void Write(BinaryWriterEx bw) { - throw new NotImplementedException("Section3 is empty in all known NVAs."); + // Reminder that this should be empty prior to Elden Ring + // throw new NotImplementedException("Section3 is empty in all known NVAs."); + + for (int i = 0; i < _numElements; i++) + bw.WriteVector4(Vectors[i]); } } @@ -676,107 +787,109 @@ public class Connector /// The navmesh to be attached. /// public int TargetNameID { get; set; } + public int Unk14 { get; set; } + public int Unk1C { get; set; } /// - /// Points used by this connection. - /// - public List Points { get; set; } - - /// - /// Conditions used by this connection. + /// NavMeshs used by this connection. /// - public List Conditions { get; set; } + public List NavMeshConnections { get; set; } + public List GraphConnections { get; set; } - private int PointCount; - private int ConditionCount; - private int PointsIndex; - private int ConditionsIndex; + private int NavMeshConnectionCount; + private int GraphConnectionCount; + private int NavMeshConnectionIndex; + private int GraphConnectionIndex; /// /// Creates a Connector with default values. /// public Connector() { - Points = new List(); - Conditions = new List(); + NavMeshConnections = new List(); + GraphConnections = new List(); } internal Connector(BinaryReaderEx br) { MainNameID = br.ReadInt32(); TargetNameID = br.ReadInt32(); - PointCount = br.ReadInt32(); - ConditionCount = br.ReadInt32(); - PointsIndex = br.ReadInt32(); - br.AssertInt32(0); - ConditionsIndex = br.ReadInt32(); - br.AssertInt32(0); + NavMeshConnectionCount = br.ReadInt32(); + GraphConnectionCount = br.ReadInt32(); + NavMeshConnectionIndex = br.ReadInt32(); + Unk14 = br.ReadInt32(); + GraphConnectionIndex = br.ReadInt32(); + Unk1C = br.ReadInt32(); } - internal void TakePointsAndConds(ConnectorPointSection points, ConnectorConditionSection conds) + internal void TakeConnections(NavMeshConnectionSection navMeshConns, GraphConnectionSection graphConns) { - Points = new List(PointCount); - for (int i = 0; i < PointCount; i++) - Points.Add(points[PointsIndex + i]); - PointCount = -1; - - Conditions = new List(ConditionCount); - for (int i = 0; i < ConditionCount; i++) - Conditions.Add(conds[ConditionsIndex + i]); - ConditionCount = -1; + NavMeshConnections = new List(NavMeshConnectionCount); + for (int i = 0; i < NavMeshConnectionCount; i++) + NavMeshConnections.Add(navMeshConns[NavMeshConnectionIndex + i]); + NavMeshConnectionCount = -1; + + GraphConnections = new List(GraphConnectionCount); + for (int i = 0; i < GraphConnectionCount; i++) + GraphConnections.Add(graphConns[GraphConnectionIndex + i]); + GraphConnectionCount = -1; } - internal void GivePointsAndConds(ConnectorPointSection points, ConnectorConditionSection conds) + internal void GiveConnections(NavMeshConnectionSection navMeshConns, GraphConnectionSection graphConns) { - PointsIndex = points.Count; - points.AddRange(Points); + NavMeshConnectionIndex = navMeshConns.Count; + navMeshConns.AddRange(NavMeshConnections); - ConditionsIndex = conds.Count; - conds.AddRange(Conditions); + GraphConnectionIndex = graphConns.Count; + graphConns.AddRange(GraphConnections); } internal void Write(BinaryWriterEx bw) { bw.WriteInt32(MainNameID); bw.WriteInt32(TargetNameID); - bw.WriteInt32(Points.Count); - bw.WriteInt32(Conditions.Count); - bw.WriteInt32(PointsIndex); - bw.WriteInt32(0); - bw.WriteInt32(ConditionsIndex); - bw.WriteInt32(0); + bw.WriteInt32(NavMeshConnections.Count); + bw.WriteInt32(GraphConnections.Count); + bw.WriteInt32(NavMeshConnectionIndex); + bw.WriteInt32(Unk14); + bw.WriteInt32(GraphConnectionIndex); + bw.WriteInt32(Unk1C); } - + /// /// Returns a string representation of the connector. /// public override string ToString() { - return $"{MainNameID} -> {TargetNameID} [{Points.Count} Points][{Conditions.Count} Conditions]"; + return $"{MainNameID} -> {TargetNameID} [{NavMeshConnections.Count} Points][{GraphConnections.Count} Conditions]"; } + } /// - /// A list of points used to connect navmeshes. + /// A list of NavMeshes used to connect navmeshes. /// - internal class ConnectorPointSection : Section + internal class NavMeshConnectionSection : Section { /// /// Creates an empty ConnectorPointSection. /// - public ConnectorPointSection() : base(1) { } + public NavMeshConnectionSection() : base(1) { } - internal ConnectorPointSection(BinaryReaderEx br) : base(br, 5, 1) { } + /// + /// Creates an empty NavMeshConnectionSection + /// + internal NavMeshConnectionSection(BinaryReaderEx br) : base(br, 5, 1) { } internal override void ReadEntries(BinaryReaderEx br, int count) { for (int i = 0; i < count; i++) - Add(new ConnectorPoint(br)); + Add(new NavMeshConnection(br)); } internal override void WriteEntries(BinaryWriterEx bw) { - foreach (ConnectorPoint entry in this) + foreach (NavMeshConnection entry in this) entry.Write(bw); } } @@ -784,79 +897,50 @@ internal override void WriteEntries(BinaryWriterEx bw) /// /// A point used to connect two navmeshes. /// - public class ConnectorPoint + public class NavMeshConnection { - /// - /// Unknown. - /// - public int Unk00 { get; set; } - - /// - /// Unknown. - /// - public int Unk04 { get; set; } - - /// - /// Unknown. - /// - public int Unk08 { get; set; } - - /// - /// Unknown. - /// - public int Unk0C { get; set; } + public int FaceIndex { get; set; } + public int EdgeIndex { get; set; } + public int OppositeFaceIndex { get; set; } + public int OppositeEdgeIndex { get; set; } - /// - /// Creates a ConnectorPoint with default values. - /// - public ConnectorPoint() { } + public NavMeshConnection() { } - internal ConnectorPoint(BinaryReaderEx br) + internal NavMeshConnection(BinaryReaderEx br) { - Unk00 = br.ReadInt32(); - Unk04 = br.ReadInt32(); - Unk08 = br.ReadInt32(); - Unk0C = br.ReadInt32(); + FaceIndex = br.ReadInt32(); + EdgeIndex = br.ReadInt32(); + OppositeFaceIndex = br.ReadInt32(); + OppositeEdgeIndex = br.ReadInt32(); } internal void Write(BinaryWriterEx bw) { - bw.WriteInt32(Unk00); - bw.WriteInt32(Unk04); - bw.WriteInt32(Unk08); - bw.WriteInt32(Unk0C); - } - - /// - /// Returns a string representation of the point. - /// - public override string ToString() - { - return $"{Unk00} {Unk04} {Unk08} {Unk0C}"; + bw.WriteInt32(FaceIndex); + bw.WriteInt32(EdgeIndex); + bw.WriteInt32(OppositeFaceIndex); + bw.WriteInt32(OppositeEdgeIndex); } } /// /// A list of unknown conditions used by connectors. /// - internal class ConnectorConditionSection : Section + internal class GraphConnectionSection : Section { - /// - /// Creates an empty ConnectorConditionSection. - /// - public ConnectorConditionSection() : base(1) { } + public GraphConnectionSection() : base(1) { } - internal ConnectorConditionSection(BinaryReaderEx br) : base(br, 6, 1) { } + internal GraphConnectionSection(BinaryReaderEx br) : base(br, 6, 1) { } internal override void ReadEntries(BinaryReaderEx br, int count) { for (int i = 0; i < count; i++) - Add(new ConnectorCondition(br)); + Add(new GraphConnection(br)); } internal override void WriteEntries(BinaryWriterEx bw) { - foreach (ConnectorCondition entry in this) + foreach (GraphConnection entry in this) entry.Write(bw); } } @@ -864,65 +948,53 @@ internal override void WriteEntries(BinaryWriterEx bw) /// /// An unknown condition used by a connector. /// - public class ConnectorCondition + public class GraphConnection { - /// - /// Unknown. - /// - public int Condition1 { get; set; } + public int NodeIndex { get; set; } + public int OppositeNodeIndex { get; set; } - /// - /// Unknown. - /// - public int Condition2 { get; set; } - - /// - /// Creates a ConnectorCondition with default values. - /// - public ConnectorCondition() { } + public GraphConnection() { } - internal ConnectorCondition(BinaryReaderEx br) + internal GraphConnection(BinaryReaderEx br) { - Condition1 = br.ReadInt32(); - Condition2 = br.ReadInt32(); + NodeIndex = br.ReadInt32(); + OppositeNodeIndex = br.ReadInt32(); } internal void Write(BinaryWriterEx bw) { - bw.WriteInt32(Condition1); - bw.WriteInt32(Condition2); + bw.WriteInt32(NodeIndex); + bw.WriteInt32(OppositeNodeIndex); } - + /// /// Returns a string representation of the condition. /// public override string ToString() { - return $"{Condition1} {Condition2}"; + return $"{NodeIndex} {OppositeNodeIndex}"; } } + /// - /// Unknown. + /// Returns a string representation of the condition. /// - public class Section7 : Section + public class LevelConnectorSection : Section { - /// - /// Creates an empty Section7. - /// - public Section7() : base(1) { } + public LevelConnectorSection() : base(1) { } - internal Section7(BinaryReaderEx br) : base(br, 7, 1) { } + internal LevelConnectorSection(BinaryReaderEx br) : base(br, 7, 1) { } internal override void ReadEntries(BinaryReaderEx br, int count) { for (int i = 0; i < count; i++) - Add(new Entry7(br)); + Add(new LevelConnector(br)); } internal override void WriteEntries(BinaryWriterEx bw) { - foreach (Entry7 entry in this) + foreach (LevelConnector entry in this) entry.Write(bw); } } @@ -930,46 +1002,35 @@ internal override void WriteEntries(BinaryWriterEx bw) /// /// Unknown; believed to have something to do with connecting maps. /// - public class Entry7 + public class LevelConnector { - /// - /// Unknown. - /// - public Vector3 Position { get; set; } - - /// - /// Unknown. - /// - public int NameID { get; set; } - - /// - /// Unknown. - /// + public Vector4 Position { get; set; } + public int NavmeshID { get; set; } + public int Unk14 { get; set; } public int Unk18 { get; set; } + public int Unk1C { get; set; } /// - /// Creates an Entry7 with default values. + /// Creates a LevelConnector with default values. /// - public Entry7() { } + public LevelConnector() { } - internal Entry7(BinaryReaderEx br) + internal LevelConnector(BinaryReaderEx br) { - Position = br.ReadVector3(); - br.AssertSingle(1); - NameID = br.ReadInt32(); - br.AssertInt32(0); + Position = br.ReadVector4(); + NavmeshID = br.ReadInt32(); + Unk14 = br.ReadInt32(); Unk18 = br.ReadInt32(); - br.AssertInt32(0); + Unk1C = br.ReadInt32(); } internal void Write(BinaryWriterEx bw) { - bw.WriteVector3(Position); - bw.WriteSingle(1); - bw.WriteInt32(NameID); - bw.WriteInt32(0); + bw.WriteVector4(Position); + bw.WriteInt32(NavmeshID); + bw.WriteInt32(Unk14); bw.WriteInt32(Unk18); - bw.WriteInt32(0); + bw.WriteInt32(Unk1C); } /// @@ -977,26 +1038,23 @@ internal void Write(BinaryWriterEx bw) /// public override string ToString() { - return $"{Position} {NameID} {Unk18}"; + return $"Position: {Position} NavmeshID: {NavmeshID} Unk14: {Unk14} Unk18: {Unk18} Unk1C: {Unk1C}"; } } /// /// Unknown. Version: 1 for BB and DS3, 2 for Sekiro. /// - internal class MapNodeSection : Section + internal class GateNodeSection : Section { - /// - /// Creates an empty Section8 with the given version. - /// - public MapNodeSection(int version) : base(version) { } + public GateNodeSection(int version) : base(version) { } - internal MapNodeSection(BinaryReaderEx br) : base(br, 8, 1, 2) { } + internal GateNodeSection(BinaryReaderEx br) : base(br, 8, 1, 2) { } internal override void ReadEntries(BinaryReaderEx br, int count) { for (int i = 0; i < count; i++) - Add(new MapNode(br, Version)); + Add(new GateNode(br, Version)); } internal override void WriteEntries(BinaryWriterEx bw) @@ -1005,113 +1063,316 @@ internal override void WriteEntries(BinaryWriterEx bw) this[i].Write(bw, Version, i); for (int i = 0; i < Count; i++) - this[i].WriteSubIDs(bw, Version, i); + this[i].WriteCosts(bw, Version, i); } } /// /// Unknown. /// - public class MapNode + public class GateNode { /// /// Unknown. /// public Vector3 Position { get; set; } - /// /// Index to a navmesh. /// - public short Section0Index { get; set; } - + public short ConnectedNavmeshIndex { get; set; } /// /// Unknown. /// - public short MainID { get; set; } - + public short NodeSubID { get; set; } /// /// Unknown. /// - public List SiblingDistances { get; set; } - + public List NeighbourGateNodeCosts { get; set; } + /// /// Unknown; only present in Sekiro. /// public int Unk14 { get; set; } /// - /// Creates an Entry8 with default values. + /// Creates a GateNode with default values. /// - public MapNode() + public GateNode() { - SiblingDistances = new List(); + NeighbourGateNodeCosts = new List(); } - internal MapNode(BinaryReaderEx br, int version) + internal GateNode(BinaryReaderEx br, int version) { Position = br.ReadVector3(); - Section0Index = br.ReadInt16(); - MainID = br.ReadInt16(); + ConnectedNavmeshIndex = br.ReadInt16(); + NodeSubID = br.ReadInt16(); if (version < 2) { - SiblingDistances = new List( - br.ReadUInt16s(16).Select(s => s == 0xFFFF ? -1 : s * 0.01f)); + NeighbourGateNodeCosts = new List(br.ReadInt16s(16)); } else { - int subIDCount = br.ReadInt32(); + int costsCount = br.ReadInt32(); Unk14 = br.ReadInt32(); - int subIDsOffset = br.ReadInt32(); + int costsOffset = br.ReadInt32(); br.AssertInt32(0); - SiblingDistances = new List( - br.GetUInt16s(subIDsOffset, subIDCount).Select(s => s == 0xFFFF ? -1 : s * 0.01f)); + + if (costsCount > 0) + NeighbourGateNodeCosts = new List(br.GetInt16s(costsOffset, costsCount)); + else + NeighbourGateNodeCosts = new List(); } } internal void Write(BinaryWriterEx bw, int version, int index) { bw.WriteVector3(Position); - bw.WriteInt16(Section0Index); - bw.WriteInt16(MainID); + bw.WriteInt16(ConnectedNavmeshIndex); + bw.WriteInt16(NodeSubID); if (version < 2) { - if (SiblingDistances.Count > 16) - throw new InvalidDataException("MapNode distance count must not exceed 16 in DS3/BB."); + if (NeighbourGateNodeCosts.Count > 16) + throw new InvalidDataException("GateNode costs count must not exceed 16 in BB/DS3."); - foreach (float distance in SiblingDistances) - bw.WriteUInt16((ushort)(distance == -1 ? 0xFFFF : Math.Round(distance * 100))); + // Stored as floats in previous nva implementation. Converted with: + // (ushort)(distance == -1 ? 0xFFFF : Math.Round(distance * 100) + foreach (short cost in NeighbourGateNodeCosts) + bw.WriteInt16(cost); - for (int i = 0; i < 16 - SiblingDistances.Count; i++) - bw.WriteUInt16(0xFFFF); + for (int i = 0; i < 16 - NeighbourGateNodeCosts.Count; i++) + bw.WriteInt16(-1); } else { - bw.WriteInt32(SiblingDistances.Count); + bw.WriteInt32(NeighbourGateNodeCosts.Count); bw.WriteInt32(Unk14); - bw.ReserveInt32($"SubIDsOffset{index}"); + bw.ReserveInt32($"CostsOffset{index}"); bw.WriteInt32(0); } } - internal void WriteSubIDs(BinaryWriterEx bw, int version, int index) + internal void WriteCosts(BinaryWriterEx bw, int version, int index) { if (version >= 2) { - bw.FillInt32($"SubIDsOffset{index}", (int)bw.Position); - foreach (float distance in SiblingDistances) - bw.WriteUInt16((ushort)(distance == -1 ? 0xFFFF : Math.Round(distance * 100))); + // This check not in original. Is this needed? + if (NeighbourGateNodeCosts.Count > 0) + { + bw.FillInt32($"CostsOffset{index}", (int)bw.Position); + foreach (short cost in NeighbourGateNodeCosts) + bw.WriteInt16(cost); + } + else + { + bw.FillInt32($"CostsOffset{index}", 0); + } } } - + /// /// Returns a string representation of the entry. /// public override string ToString() { - return $"{Position} {Section0Index} {MainID} [{SiblingDistances.Count} SubIDs]"; + return $"[Position: {Position} ConnectedNavmeshIndex: {ConnectedNavmeshIndex} NeighbourGateNodeCosts.Count: {NeighbourGateNodeCosts.Count} SubIDs Unk14: {Unk14}]"; + } + } + + public class Section9 : Section + { + public Section9() : base(1) { } + + internal Section9(BinaryReaderEx br) : base(br, 9, 1) { } + + internal override void ReadEntries(BinaryReaderEx br, int count) + { + for (int i = 0; i < count; i++) Add(new Entry9(br)); + } + + internal override void WriteEntries(BinaryWriterEx bw) + { + foreach (Entry9 entry in this) entry.Write(bw); + } + } + + public class Entry9 + { + public int Unk00 { get; set; } + public int Unk04 { get; set; } + public int Unk08 { get; set; } + public int Unk0C { get; set; } + + public Entry9() + { + } + + internal Entry9(BinaryReaderEx br) + { + Unk00 = br.ReadInt32(); + Unk04 = br.ReadInt32(); + Unk08 = br.ReadInt32(); + Unk0C = br.ReadInt32(); + } + + internal void Write(BinaryWriterEx bw) + { + bw.WriteInt32(Unk00); + bw.WriteInt32(Unk04); + bw.WriteInt32(Unk08); + bw.WriteInt32(Unk0C); + } + } + + public class Section10 : Section + { + public Section10() : base(1) + { + } + + internal Section10(BinaryReaderEx br) : base(br, 10, 1) + { + } + + internal override void ReadEntries(BinaryReaderEx br, int count) + { + for (int i = 0; i < count; i++) Add(new Entry10(br)); + } + + internal override void WriteEntries(BinaryWriterEx bw) + { + foreach (Entry10 entry in this) entry.Write(bw); + } + } + + public class Entry10 + { + public Vector4 Unk00 { get; set; } + public int Unk10 { get; set; } + public int Unk14 { get; set; } + public int Unk18 { get; set; } + public int Unk1C { get; set; } + + public Entry10() + { + } + + internal Entry10(BinaryReaderEx br) + { + Unk00 = br.ReadVector4(); + Unk10 = br.ReadInt32(); + Unk14 = br.ReadInt32(); + Unk18 = br.ReadInt32(); + Unk1C = br.ReadInt32(); + } + + internal void Write(BinaryWriterEx bw) + { + bw.WriteVector4(Unk00); + bw.WriteInt32(Unk10); + bw.WriteInt32(Unk14); + bw.WriteInt32(Unk18); + bw.WriteInt32(Unk1C); + } + } + + public class Section11 : Section + { + public Section11() : base(1) + { + } + + internal Section11(BinaryReaderEx br) : base(br, 11, 1) + { + } + + internal override void ReadEntries(BinaryReaderEx br, int count) + { + for (int i = 0; i < count; i++) Add(new Entry11(br)); + } + + internal override void WriteEntries(BinaryWriterEx bw) + { + foreach (Entry11 entry in this) entry.Write(bw); + } + } + + public class Entry11 + { + public int Unk00 { get; set; } + public int Unk04 { get; set; } + public int Unk08 { get; set; } + public int Unk0C { get; set; } + + public Entry11() + { + } + + internal Entry11(BinaryReaderEx br) + { + Unk00 = br.ReadInt32(); + Unk04 = br.ReadInt32(); + Unk08 = br.ReadInt32(); + Unk0C = br.ReadInt32(); + } + + internal void Write(BinaryWriterEx bw) + { + bw.WriteInt32(Unk00); + bw.WriteInt32(Unk04); + bw.WriteInt32(Unk08); + bw.WriteInt32(Unk0C); + } + } + + public class Section13 : Section + { + public Section13() : base(1) + { + } + + internal Section13(BinaryReaderEx br) : base(br, 13, 1) + { + } + + internal override void ReadEntries(BinaryReaderEx br, int count) + { + for (int i = 0; i < count; i++) Add(new Entry13(br)); + } + + internal override void WriteEntries(BinaryWriterEx bw) + { + foreach (Entry13 entry in this) entry.Write(bw); + } + } + + public class Entry13 + { + public Vector4[] Vectors { get; set; } + public int[] Ints { get; set; } + + public Entry13() + { + Vectors = new Vector4[6]; + Ints = new int[8]; + } + + internal Entry13(BinaryReaderEx br) + { + Vectors = new Vector4[6]; + for (int i = 0; i < 6; i++) Vectors[i] = br.ReadVector4(); + + Ints = new int[8]; + for (int i = 0; i < 8; i++) Ints[i] = br.ReadInt32(); + } + + internal void Write(BinaryWriterEx bw) + { + for (int i = 0; i < 6; i++) bw.WriteVector4(Vectors[i]); + for (int i = 0; i < 8; i++) bw.WriteInt32(Ints[i]); } } } -} +} \ No newline at end of file From 15b8da6a412ff0263b2d2c0567f8b2810d90a163 Mon Sep 17 00:00:00 2001 From: nordgaren Date: Tue, 10 Mar 2026 03:03:09 -0700 Subject: [PATCH 2/2] Put `ConnectedNavmeshesCount` back to original name and just st default value for when it's inline --- SoulsFormats/Formats/NVA.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SoulsFormats/Formats/NVA.cs b/SoulsFormats/Formats/NVA.cs index 183a34b0..1e8e15e8 100644 --- a/SoulsFormats/Formats/NVA.cs +++ b/SoulsFormats/Formats/NVA.cs @@ -82,7 +82,7 @@ public enum NVAVersion : uint { Version = NVAVersion.EldenRing; Entries11 = new Section11(); - Navmeshes = new NavmeshSection(2); + Navmeshes = new NavmeshSection(4); FaceDatas = new FaceDataSection(); NodeBanks = new NodeBankSection(); Entries3 = new Section3(); @@ -401,6 +401,9 @@ public Navmesh() Scale = Vector4.One; ConnectedNavmeshes = new List(); GateNodes = new List(); + // No clue. Maybe a sentinel value for when + // ConnectedNavmeshes is inline (even though unkOffset seems to also do that?) + ConnectedNavmeshesCount = 1075419545; } internal Navmesh(BinaryReaderEx br, int version)