diff --git a/CodeWalker.Core/GameFiles/FileTypes/AudioSectorsFile.cs b/CodeWalker.Core/GameFiles/FileTypes/AudioSectorsFile.cs new file mode 100644 index 000000000..8dfd7d164 --- /dev/null +++ b/CodeWalker.Core/GameFiles/FileTypes/AudioSectorsFile.cs @@ -0,0 +1,223 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; +using EXP = System.ComponentModel.ExpandableObjectConverter; +using TC = System.ComponentModel.TypeConverterAttribute; + +namespace CodeWalker.GameFiles +{ + public class AudioWorldSectorsFile : GameFile, PackedFile + { + public byte[] RawFileData { get; set; } + + public const int NumSectorsX = 100; + public const int NumSectorsY = 100; + public const int NumSectors = NumSectorsX * NumSectorsY; + + [TC(typeof(EXP))] + public struct AudSector + { + public byte numHighwayNodes { get; set; } + public byte tallestBuilding { get; set; } + public byte numBuildings { get; set; } + public byte numTrees { get; set; } + public bool isWaterSector { get; set; } + + public override string ToString() + { + return numHighwayNodes.ToString() + ": " + tallestBuilding.ToString() + ": " + numBuildings.ToString() + ": " + numTrees.ToString() + ": " + isWaterSector.ToString(); + } + } + + public AudSector[] Sectors { get; set; } = new AudSector[NumSectors]; + public AudioWorldSectorsFile() : base(null, GameFileType.AudioWorldSectors) { } + public AudioWorldSectorsFile(RpfFileEntry entry) : base(entry, GameFileType.AudioWorldSectors) + { + RpfFileEntry = entry; + } + + public void Load(byte[] data, RpfFileEntry entry) + { + RawFileData = data; + if (entry != null) + { + RpfFileEntry = entry; + Name = entry.Name; + } + + using (var ms = new MemoryStream(data, false)) + { + var r = new DataReader(ms); + Read(r, (int)ms.Length); + } + } + + public byte[] Save() + { + using (var s = new MemoryStream()) + { + var w = new DataWriter(s); + Write(w); + return s.ToArray(); + } + } + + private void Read(DataReader r, int dataLength) + { + int expectedBytes = NumSectors * 4; + int toReadBytes = Math.Min(expectedBytes, dataLength); + + for (int i = 0; i < NumSectors; i++) + { + if ((i * 4 + 4) > toReadBytes) + { + Sectors[i] = default; + continue; + } + + byte numHighwayNodes = r.ReadByte(); + byte tallestBuilding = r.ReadByte(); + byte numBuildings = r.ReadByte(); + byte bitfield = r.ReadByte(); + + Sectors[i] = new AudSector + { + numHighwayNodes = numHighwayNodes, + tallestBuilding = tallestBuilding, + numBuildings = numBuildings, + numTrees = (byte)(bitfield >> 1), + isWaterSector = (bitfield & 0x1) != 0 + }; + } + } + + private void Write(DataWriter w) + { + for (int i = 0; i < NumSectors; i++) + { + var s = Sectors[i]; + byte bitfield = (byte)((s.numTrees << 1) | (s.isWaterSector ? 0x1 : 0x0)); + + w.Write(s.numHighwayNodes); + w.Write(s.tallestBuilding); + w.Write(s.numBuildings); + w.Write(bitfield); + } + } + + public void WriteXml(StringBuilder sb, int indent) + { + AudXml.OpenTag(sb, indent, "Sectors"); + + for (int y = 0; y < NumSectorsY; y++) + { + for (int x = 0; x < NumSectorsX; x++) + { + int i = y * NumSectorsX + x; + var s = Sectors[i]; + + AudXml.OpenTag(sb, indent + 1, $"Sector Index=\"{i}\" X=\"{x}\" Y=\"{y}\""); + AudXml.SelfClosingTag(sb, indent + 2, $"HighwayNodes value=\"{s.numHighwayNodes}\""); + AudXml.SelfClosingTag(sb, indent + 2, $"TallestBuilding value=\"{s.tallestBuilding}\""); + AudXml.SelfClosingTag(sb, indent + 2, $"NumBuildings value=\"{s.numBuildings}\""); + AudXml.SelfClosingTag(sb, indent + 2, $"NumTrees value=\"{s.numTrees}\""); + AudXml.SelfClosingTag(sb, indent + 2, $"IsWaterSector value=\"{(s.isWaterSector ? "true" : "false")}\""); + AudXml.CloseTag(sb, indent + 1, "Sector"); + } + } + AudXml.CloseTag(sb, indent, "Sectors"); + } + + public void ReadXml(XmlNode node) + { + var sectorsNode = Xml.GetChild((XmlElement)node, "Sectors"); + if (sectorsNode == null) throw new InvalidDataException("Missing node"); + + var tmp = new AudSector[NumSectors]; + + foreach (XmlNode sectorNode in sectorsNode.ChildNodes) + { + var se = sectorNode as XmlElement; + if (se == null || se.Name != "Sector") + continue; + + int x = -1, y = -1, idx = -1; + + var sx = Xml.GetStringAttribute(se, "X"); + var sy = Xml.GetStringAttribute(se, "Y"); + var sIdx = Xml.GetStringAttribute(se, "Index"); + + if (!string.IsNullOrEmpty(sx)) int.TryParse(sx, out x); + if (!string.IsNullOrEmpty(sy)) int.TryParse(sy, out y); + if (!string.IsNullOrEmpty(sIdx)) int.TryParse(sIdx, out idx); + + if (x < 0 || y < 0) + { + if (idx < 0) + throw new InvalidDataException("Sector missing X/Y and Index"); + y = idx / NumSectorsX; + x = idx - (y * NumSectorsX); + } + + if ((uint)x >= NumSectorsX || (uint)y >= NumSectorsY) + throw new InvalidDataException($"Sector out of range X={x}, Y={y}"); + + int i = y * NumSectorsX + x; + + byte numHighwayNodes = (byte)Xml.GetChildIntAttribute(se, "HighwayNodes", "value"); + byte tallestBuilding = (byte)Xml.GetChildIntAttribute(se, "TallestBuilding", "value"); + byte numBuildings = (byte)Xml.GetChildIntAttribute(se, "NumBuildings", "value"); + byte numTrees = (byte)Xml.GetChildIntAttribute(se, "NumTrees", "value"); + bool isWaterSector = Xml.GetChildBoolAttribute(se, "IsWaterSector", "value"); + + tmp[i] = new AudSector + { + numHighwayNodes = numHighwayNodes, + tallestBuilding = tallestBuilding, + numBuildings = numBuildings, + numTrees = numTrees, + isWaterSector = isWaterSector + }; + } + + Sectors = tmp; + } + } + public class AudXml : MetaXmlBase + { + public static string GetXml(AudioWorldSectorsFile awsf) + { + var sb = new StringBuilder(); + sb.AppendLine(XmlHeader); + + if (awsf != null && awsf.Sectors != null) + { + var name = "AudioWorldSectors"; + OpenTag(sb, 0, name); + + awsf.WriteXml(sb, 1); + + CloseTag(sb, 0, name); + } + + return sb.ToString(); + } + } + public class XmlAud + { + public static AudioWorldSectorsFile GetAudWorldSectors(string xml) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + return GetAudWorldSectors(doc); + } + + public static AudioWorldSectorsFile GetAudWorldSectors(XmlDocument doc) + { + var awsf = new AudioWorldSectorsFile(); + awsf.ReadXml(doc.DocumentElement); + return awsf; + } + } +} \ No newline at end of file diff --git a/CodeWalker.Core/GameFiles/GameFile.cs b/CodeWalker.Core/GameFiles/GameFile.cs index 05d206436..87e762725 100644 --- a/CodeWalker.Core/GameFiles/GameFile.cs +++ b/CodeWalker.Core/GameFiles/GameFile.cs @@ -85,6 +85,7 @@ public enum GameFileType : int Mrf = 29, DistantLights = 30, Ypdb = 31, + AudioWorldSectors = 32, } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index e16c2f689..1677cc161 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -152,6 +152,11 @@ public static string GetXml(RpfFileEntry e, byte[] data, out string filename, st HeightmapFile hmf = RpfFile.GetFile(e, data); return GetXml(hmf, out filename, outputfolder); } + else if (fnl.EndsWith(".dat") && fnl.StartsWith("audioworld")) + { + AudioWorldSectorsFile aws = RpfFile.GetFile(e, data); + return GetXml(aws, out filename, outputfolder); + } else if (fnl.EndsWith(".mrf")) { MrfFile mrf = RpfFile.GetFile(e, data); @@ -337,6 +342,13 @@ public static string GetXml(MrfFile mrf, out string filename, string outputfolde return MrfXml.GetXml(mrf); } + public static string GetXml(AudioWorldSectorsFile aws, out string filename, string outputfolder) + { + var fn = (aws?.Name) ?? ""; + filename = fn + ".xml"; + return AudXml.GetXml(aws); + } + @@ -2304,6 +2316,7 @@ public enum MetaFormat Ypdb = 22, Mrf = 23, Yfd = 24, + AudioWorldSectors = 25, } } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs b/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs index 47fc573ba..226ea36db 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs @@ -63,6 +63,8 @@ public static byte[] GetData(XmlDocument doc, MetaFormat mformat, string fpathin return GetYfdData(doc); case MetaFormat.Mrf: return GetMrfData(doc); + case MetaFormat.AudioWorldSectors: + return GetAudioWorldSectorsData(doc); } return null; } @@ -211,6 +213,13 @@ public static byte[] GetMrfData(XmlDocument doc) return mrf.Save(); } + public static byte[] GetAudioWorldSectorsData(XmlDocument doc) + { + var aws = XmlAud.GetAudWorldSectors(doc); + if (aws.Sectors == null) return null; + return aws.Save(); + } + public static string GetXMLFormatName(MetaFormat mformat) { @@ -237,6 +246,7 @@ public static string GetXMLFormatName(MetaFormat mformat) case MetaFormat.Fxc: return "FXC XML"; case MetaFormat.CacheFile: return "CacheFile XML"; case MetaFormat.Heightmap: return "Heightmap XML"; + case MetaFormat.AudioWorldSectors: return "AudioWorldSectorsInfo XML"; case MetaFormat.Ypdb: return "YPDB XML"; case MetaFormat.Mrf: return "MRF XML"; case MetaFormat.Yfd: return "YFD XML"; @@ -334,6 +344,10 @@ public static MetaFormat GetXMLFormat(string fnamel, out int trimlength) { mformat = MetaFormat.Heightmap; } + if (fnamel.EndsWith(".dat.xml") && fnamel.StartsWith("audioworld")) + { + mformat = MetaFormat.AudioWorldSectors; + } if (fnamel.EndsWith(".ypdb.xml")) { mformat = MetaFormat.Ypdb; diff --git a/CodeWalker/ExploreForm.cs b/CodeWalker/ExploreForm.cs index 593ec4e97..03783afc5 100644 --- a/CodeWalker/ExploreForm.cs +++ b/CodeWalker/ExploreForm.cs @@ -317,6 +317,7 @@ private void InitFileTypes() InitSubFileType(".dat", "cache_y.dat", "Cache File", 6, FileTypeAction.ViewCacheDat, true); InitSubFileType(".dat", "heightmap.dat", "Heightmap", 6, FileTypeAction.ViewHeightmap, true); + InitSubFileType(".dat", "audioworldsectorsinfo.dat", "Audio Sectors", 6, FileTypeAction.ViewAudioWorldSectors, true); InitSubFileType(".dat", "heightmapheistisland.dat", "Heightmap", 6, FileTypeAction.ViewHeightmap, true); InitSubFileType(".dat", "distantlights.dat", "Distant Lights", 6, FileTypeAction.ViewDistantLights); InitSubFileType(".dat", "distantlights_hd.dat", "Distant Lights", 6, FileTypeAction.ViewDistantLights); @@ -1560,6 +1561,7 @@ private bool CanViewFile(MainListItem item) case FileTypeAction.ViewYld: case FileTypeAction.ViewYfd: case FileTypeAction.ViewHeightmap: + case FileTypeAction.ViewAudioWorldSectors: case FileTypeAction.ViewMrf: case FileTypeAction.ViewDistantLights: return true; @@ -1691,6 +1693,9 @@ private void View(MainListItem item) case FileTypeAction.ViewHeightmap: ViewHeightmap(name, path, data, fe); break; + case FileTypeAction.ViewAudioWorldSectors: + ViewAudioWorldSectors(name, path, data, fe); + break; case FileTypeAction.ViewMrf: ViewMrf(name, path, data, fe); break; @@ -1944,6 +1949,14 @@ private void ViewHeightmap(string name, string path, byte[] data, RpfFileEntry e f.Show(); f.LoadMeta(heightmap); } + + private void ViewAudioWorldSectors(string name, string path, byte[] data, RpfFileEntry e) + { + var audiosectors = RpfFile.GetFile(e, data); + MetaForm f = new MetaForm(this); + f.Show(); + f.LoadMeta(audiosectors); + } private void ViewMrf(string name, string path, byte[] data, RpfFileEntry e) { var mrf = RpfFile.GetFile(e, data); @@ -4998,6 +5011,7 @@ public enum FileTypeAction ViewNametable = 25, ViewDistantLights = 26, ViewYpdb = 27, + ViewAudioWorldSectors = 28, } diff --git a/CodeWalker/Forms/MetaForm.cs b/CodeWalker/Forms/MetaForm.cs index 47b182659..57b97a72b 100644 --- a/CodeWalker/Forms/MetaForm.cs +++ b/CodeWalker/Forms/MetaForm.cs @@ -374,6 +374,21 @@ public void LoadMeta(HeightmapFile heightmap) metaFormat = MetaFormat.Heightmap; } } + + public void LoadMeta(AudioWorldSectorsFile audioworldsectors) + { + var fn = ((audioworldsectors?.RpfFileEntry?.Name) ?? "") + ".xml"; + Xml = AudXml.GetXml(audioworldsectors); + FileName = fn; + RawPropertyGrid.SelectedObject = audioworldsectors; + rpfFileEntry = audioworldsectors?.RpfFileEntry; + modified = false; + metaFormat = MetaFormat.XML; + if (audioworldsectors?.RpfFileEntry != null) + { + metaFormat = MetaFormat.AudioWorldSectors; + } + } public void LoadMeta(YpdbFile ypdb) { var fn = ((ypdb?.RpfFileEntry?.Name) ?? "") + ".xml";