diff --git a/File_Format_Library/FileFormats/Archives/BEA.cs b/File_Format_Library/FileFormats/Archives/BEA.cs index a081701c..6095e9db 100644 --- a/File_Format_Library/FileFormats/Archives/BEA.cs +++ b/File_Format_Library/FileFormats/Archives/BEA.cs @@ -179,6 +179,12 @@ namespace FirstPlugin asset.FileName = node.FileName; asset.FileData = node.CompressedData; asset.UncompressedSize = node.FileData.Length; + asset.FileID1 = node.FileID1; + asset.FileID2 = node.FileID2; + asset.FileHash = node.FileHash; + asset.Unknown3 = node.Unknown3; + asset.FileType = node.FileType; + beaFile.FileList.Add(asset.FileName, asset); beaFile.FileDictionary.Add(asset.FileName); } @@ -204,6 +210,9 @@ namespace FirstPlugin public class FileEntry : ArchiveFileInfo { BEA ArchiveFile; + + + public FileEntry(BEA bea) { ArchiveFile = bea; @@ -233,6 +242,11 @@ namespace FirstPlugin public ushort unk1; public ushort unk2; public bool IsCompressed; + public ulong FileID1; + public ulong FileID2; + public uint FileHash; + public string FileType = ""; + public uint Unknown3; public override byte[] FileData { @@ -248,6 +262,8 @@ namespace FirstPlugin unk1 = 2; unk2 = 12; FileData = data; + FileID1 = (ulong)new Random().Next(0, int.MaxValue); + FileID2 = (ulong)new Random().Next(0, int.MaxValue); } public override Dictionary ExtensionImageKeyLookup @@ -309,6 +325,12 @@ namespace FirstPlugin fileEntry.unk2 = asset.unk2; fileEntry.IsCompressed = asset.IsCompressed; fileEntry.CompressedData = asset.FileData; + fileEntry.Unknown3 = asset.Unknown3; + fileEntry.FileID1 = asset.FileID1; + fileEntry.FileID2 = asset.FileID2; + fileEntry.FileHash = asset.FileHash; + fileEntry.FileType = asset.FileType; + return fileEntry; } } diff --git a/File_Format_Library/FileFormats/Archives/BEA/ASST.cs b/File_Format_Library/FileFormats/Archives/BEA/ASST.cs new file mode 100644 index 00000000..f0743b67 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/BEA/ASST.cs @@ -0,0 +1,96 @@ +using System; +using Syroot.BinaryData; +using System.IO; +using System.Text; + +namespace BezelEngineArchive_Lib +{ + public class ASST : IFileData //File asset + { + private const string _signature = "ASST"; + + public ushort unk = 2; + public ushort unk2 = 12; + public string FileName; + public long UncompressedSize; + public bool IsCompressed = true; + + public ulong FileID1; + public ulong FileID2; + + public uint FileHash; + public uint Unknown3; + + public byte[] FileData; + + public uint FileSize; + public long FileOffset; + + public string FileType = ""; + + public BezelEngineArchive ParentArchive; + + void IFileData.Load(FileLoader loader) + { + loader.CheckSignature(_signature); + loader.LoadBlockHeader(); + unk = loader.ReadUInt16(); + unk2 = loader.ReadUInt16(); + FileSize = loader.ReadUInt32(); + UncompressedSize = loader.ReadUInt32(); + + if (loader.Archive.VersionMajor2 >= 5) + { + FileType = Encoding.ASCII.GetString(loader.ReadBytes(8)); + } + + Unknown3 = loader.ReadUInt32(); + + if (loader.Archive.VersionMajor2 >= 6) + { + FileID1 = loader.ReadUInt64(); + FileID2 = loader.ReadUInt64(); + } + + FileOffset = loader.ReadInt64(); + FileName = loader.LoadString(); + + using (loader.TemporarySeek(FileOffset, SeekOrigin.Begin)) { + FileData = loader.ReadBytes((int)FileSize); + } + + if (UncompressedSize == FileSize) + IsCompressed = false; + } + void IFileData.Save(FileSaver saver) + { + saver.WriteSignature(_signature); + saver.SaveBlockHeader(); + saver.Write(unk); + saver.Write(unk2); + saver.Write((uint)FileData.Length); + saver.Write((uint)UncompressedSize); + + if (saver.Archive.VersionMajor2 >= 5) + { + if (FileType.Length > 8) + throw new Exception($"Invalid file type length! Must be equal or less than 12 bytes! {FileType}!"); + + saver.Write(Encoding.ASCII.GetBytes(FileType)); + saver.Write(new byte[8 - FileType.Length]); + } + + saver.Write(Unknown3); + + if (saver.Archive.VersionMajor2 >= 6) + { + saver.Write(FileID1); + saver.Write(FileID2); + } + + saver.SaveBlock(FileData, (uint)saver.Archive.RawAlignment, () => saver.Write(FileData)); + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "Asst File Name Offset"); + saver.SaveString(FileName); + } + } +} diff --git a/File_Format_Library/FileFormats/Archives/BEA/BevelEngineArchive.cs b/File_Format_Library/FileFormats/Archives/BEA/BevelEngineArchive.cs new file mode 100644 index 00000000..71f0c293 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/BEA/BevelEngineArchive.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using Syroot.BinaryData; +using System.IO; + +namespace BezelEngineArchive_Lib +{ + public class BezelEngineArchive : IFileData + { + private const string _signature = "SCNE"; + + public BezelEngineArchive(Stream stream, bool leaveOpen = false) + { + using (FileLoader loader = new FileLoader(this, stream, leaveOpen)) + { + loader.Execute(); + } + } + + public BezelEngineArchive(string fileName) + { + using (FileLoader loader = new FileLoader(this, fileName)) + { + loader.Execute(); + } + } + + public ushort ByteOrder { get; private set; } + public uint VersionMajor { get; set; } + public uint VersionMajor2 { get; set; } + public uint VersionMinor { get; set; } + public uint VersionMinor2 { get; set; } + public uint Alignment { get; set; } + public uint TargetAddressSize { get; set; } + public string Name { get; set; } + public string CompressionName { get; set; } + + public ResDict FileDictionary { get; set; } //Todo, Need to setup ResDict to grab indexes quicker + public Dictionary FileList { get; set; } //Use a dictionary for now so i can look up files quickly + + /// s + /// Gets or sets the alignment to use for raw data blocks in the file. + /// + public int RawAlignment { get; set; } + + public List ReferenceList = new List(); + + public void Save(Stream stream, bool leaveOpen = false) + { + using (FileSaver saver = new FileSaver(this, stream, leaveOpen)) + { + saver.Execute(); + } + } + + public void Save(string FileName) + { + using (FileSaver saver = new FileSaver(this, FileName)) + { + saver.Execute(); + } + } + + internal uint SaveVersion() + { + return VersionMajor << 24 | VersionMajor2 << 16 | VersionMinor << 8 | VersionMinor2; + } + + private void SetVersionInfo(uint Version) + { + VersionMajor = Version >> 24; + VersionMajor2 = Version >> 16 & 0xFF; + VersionMinor = Version >> 8 & 0xFF; + VersionMinor2 = Version & 0xFF; + } + + internal Stream _stream; + + void IFileData.Load(FileLoader loader) + { + _stream = loader.BaseStream; + + loader.CheckSignature(_signature); + uint padding = loader.ReadUInt32(); + uint Version = loader.ReadUInt32(); + SetVersionInfo(Version); + ByteOrder = loader.ReadUInt16(); + Alignment = (uint)loader.ReadByte(); + TargetAddressSize = (uint)loader.ReadByte(); + uint Padding = loader.ReadUInt32(); //Usually name offset for file with other switch formats + ushort Padding2 = loader.ReadUInt16(); + ushort BlockOffset = loader.ReadUInt16(); //Goes to ASST section which seems to have block headers + uint RelocationTableOffset = loader.ReadUInt32(); + uint DataOffset = loader.ReadUInt32(); //data or end of file offset + var FileCount = loader.ReadUInt32(); + var RefCount = loader.ReadUInt32(); + + ulong FileInfoOffset = 0; + + if (VersionMajor2 >= 5) + { + var AssetOffset = loader.ReadUInt64(); //asset offset + FileInfoOffset = loader.ReadUInt64(); + FileDictionary = loader.LoadDict(); + Name = loader.LoadString(); + CompressionName = loader.LoadString(); + ulong RefOffset = loader.ReadUInt64(); + + ReferenceList = loader.LoadCustom(() => + { + List list = new List(); + for (int i = 0; i < (int)RefCount; i++) + list.Add(loader.LoadString()); + return list; + + }, (long)RefOffset); + } + else + { + FileInfoOffset = loader.ReadUInt64(); + FileDictionary = loader.LoadDict(); + ulong unk = loader.ReadUInt64(); + Name = loader.LoadString(); + } + + FileList = loader.LoadCustom(() => + { + Dictionary asstList = new Dictionary(); + for (int i = 0; i < (int)FileCount; i++) + { + var asset = loader.Load(); + asstList.Add(asset.FileName, asset); + } + return asstList; + }, (long)FileInfoOffset); + } + void IFileData.Save(FileSaver saver) + { + RawAlignment = (1 << (int)Alignment); + + saver.WriteSignature(_signature); + saver.Write(0); + saver.Write(SaveVersion()); + saver.Write(ByteOrder); + saver.Write((byte)Alignment); + saver.Write((byte)TargetAddressSize); + saver.Write(0); + saver.Write((ushort)0); + saver.SaveFirstBlock(); + saver.SaveRelocationTablePointer(); + saver.SaveFileSize(); + saver.Write((uint)FileList.Count); + saver.Write(ReferenceList == null ? 0u : (uint)ReferenceList.Count); + + if (VersionMajor2 >= 5) + { + saver.SaveAssetBlock(); + + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "Asst Offset Array"); + saver.SaveFileAsstPointer(); + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "DIC"); + saver.SaveFileDictionaryPointer(); + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "FileName"); + saver.SaveString(Name); + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "CompressionName"); + saver.SaveString(CompressionName); + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "Ref Offset"); + saver.SaveAssetRefPointer(); + } + else + { + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "Asst Offset Array"); + saver.SaveFileAsstPointer(); + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "DIC"); + saver.SaveFileDictionaryPointer(); + saver.Write(0L); + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "FileName"); + saver.SaveString(Name); + } + } + } +} diff --git a/File_Format_Library/FileFormats/Archives/BEA/DIC/ResDict.cs b/File_Format_Library/FileFormats/Archives/BEA/DIC/ResDict.cs new file mode 100644 index 00000000..f6351aca --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/BEA/DIC/ResDict.cs @@ -0,0 +1,428 @@ +using System; +using System.Numerics; +using System.Linq; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace BezelEngineArchive_Lib +{ + /// + /// Represents the non-generic base of a dictionary which can quickly look up instances via + /// key or index. + /// + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(TypeProxy))] + public class ResDict : IEnumerable, IFileData + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private IList _nodes; // Includes root node. + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class. + /// + public ResDict() + { + // Create root node. + _nodes = new List { new Node() }; + } + + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + /// + /// Gets the number of instances stored. + /// + public int Count + { + get { return _nodes.Count - 1; } + } + + // ---- OPERATORS ---------------------------------------------------------------------------------------------- + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + /// + /// Adds the given to insert in the dictionary. + /// + /// Duplicated instances + /// already exists. + public void Add(string key) + { + if (!ContainsKey((key))) + _nodes.Add(new Node(key)); + else + throw new Exception($"key {key} already exists in the dictionary!"); + } + + /// + /// Removes the given from the dictionary. + /// + /// Duplicated instances + /// already exists. + public void Remove(string key) + { + _nodes.Remove(_nodes.Where(n => n.Key == key).FirstOrDefault()); + } + + /// + /// Determines whether the given is in the dictionary. + /// + /// true if was found in the dictionary; otherwise false. + /// + public bool ContainsKey(string key) + { + return _nodes.Any(p => p.Key == key); + } + + /// + /// Returns the key given is within range of the dictionary. + /// + public string GetKey(int index) + { + if (index < _nodes.Count || index > 0) + return _nodes[index + 1].Key; + else + throw new Exception($"Index {index} is out of range!"); + } + + /// + /// Removes all elements from the dictionary. + /// + public void Clear() + { + // Create new collection with root node. + _nodes.Clear(); + _nodes.Add(new Node()); + } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + void IFileData.Load(FileLoader loader) + { + // Read the header. + uint signature = loader.ReadUInt32(); //Always 0x00000000 + int numNodes = loader.ReadInt32(); // Excludes root node. + + int i = 0; + // Read the nodes including the root node. + List nodes = new List(); + for (; numNodes >= 0; numNodes--) + { + nodes.Add(ReadNode(loader)); + i++; + } + _nodes = nodes; + } + + void IFileData.Save(FileSaver saver) + { + // Update the Patricia trie values in the nodes. + UpdateNodes(); + + // Write header. + saver.WriteSignature("_DIC"); + saver.Write(Count); + + // Write nodes. + int index = -1; // Start at -1 due to root node. + int curNode = 0; + foreach (Node node in _nodes) + { + saver.Write(node.Reference); + saver.Write(node.IdxLeft); + saver.Write(node.IdxRight); + + if (curNode == 0) + { + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "DIC " + node.Key); // <------------ Entry Set + saver.SaveString(""); + } + else + { + saver.SaveRelocateEntryToSection(saver.Position, 1, 1, 0, 1, "DIC " + node.Key); // <------------ Entry Set + saver.SaveString(node.Key); + } + curNode++; + } + } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + IEnumerator GetEnumerator() + { + foreach (Node node in Nodes) + { + yield return node.Key; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Returns only the publically visible nodes, excluding the root node. + /// + protected IEnumerable Nodes + { + get + { + for (int i = 1; i < _nodes.Count; i++) + { + yield return _nodes[i]; + } + } + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + static string ToBinaryString(string text, Encoding encoding) + { + return string.Join("", encoding.GetBytes(text).Select(n => Convert.ToString(n, 2).PadLeft(8, '0'))); + } + + static int _bit(BigInteger n, int b) + { + BigInteger test = (n >> (int)(b & 0xffffffff)) & 1; + return (int)test; + } + + static int first_1bit(BigInteger n) + { + int bitlength1 = BitLength(n); + for (int i = 0; i < bitlength1; i++) + { + if (((n >> i) & 1) == 1) + { + return i; + } + } + + throw new Exception("Operation Failed"); + } + + static int bit_mismatch(BigInteger int1, BigInteger int2) + { + int bitlength1 = BitLength(int1); + int bitlength2 = BitLength(int2); + + for (int i = 0; i < Math.Max(bitlength1, bitlength2); i++) + { + if (((int1 >> i) & 1) != ((int2 >> i) & 1)) + return i; + } + return -1; + } + + + static int BitLength(BigInteger bits) + { + int bitLength = 0; + while (bits / 2 != 0) + { + bits /= 2; + bitLength++; + } + bitLength += 1; + return bitLength; + } + + class Tree + { + public Node root; + + public Dictionary> entries; + + public Tree() + { + entries = new Dictionary>(); + + root = new Node(0, -1, root); + root.Parent = root; + + insertEntry(0, root); + } + + int GetCompactBitIdx() + { + return -1; + } + + public void insertEntry(BigInteger data, Node node) + { + entries[data] = (Tuple.Create(entries.Count, node)); + } + + Node Search(BigInteger data, bool prev) + { + if (root.Child[0] == root) + return root; + + Node node = root.Child[0]; + Node prevNode = node; + while (true) + { + prevNode = node; + node = node.Child[_bit(data, node.bitInx)]; + if (node.bitInx <= prevNode.bitInx) + break; + } + if (prev) + return prevNode; + else + return node; + } + + public void Insert(string Name) + { + string bits = ToBinaryString(Name, Encoding.UTF8); + BigInteger data = bits.Aggregate(new BigInteger(), (b, c) => b * 2 + c - '0'); + Node current = Search(data, true); + int bitIdx = bit_mismatch(current.Data, data); + + while (bitIdx < current.Parent.bitInx) + current = current.Parent; + + if (bitIdx < current.bitInx) + { + Node newNode = new Node(data, bitIdx, current.Parent); + newNode.Child[_bit(data, bitIdx) ^ 1] = current; + current.Parent.Child[_bit(data, current.Parent.bitInx)] = newNode; + current.Parent = newNode; + + insertEntry(data, newNode); + } + else if (bitIdx > current.bitInx) + { + Node newNode = new Node(data, bitIdx, current); + if (_bit(current.Data, bitIdx) == (_bit(data, bitIdx) ^ 1)) + newNode.Child[_bit(data, bitIdx) ^ 1] = current; + else + newNode.Child[_bit(data, bitIdx) ^ 1] = root; + + + current.Child[_bit(data, current.bitInx)] = newNode; + insertEntry(data, newNode); + } + else + { + + int NewBitIdx = first_1bit(data); + + if (current.Child[_bit(data, bitIdx)] != root) + NewBitIdx = bit_mismatch(current.Child[_bit(data, bitIdx)].Data, data); + Node newNode = new Node(data, NewBitIdx, current); + + newNode.Child[_bit(data, NewBitIdx) ^ 1] = current.Child[_bit(data, bitIdx)]; + current.Child[_bit(data, bitIdx)] = newNode; + insertEntry(data, newNode); + } + } + } + + + private void UpdateNodes() + { + Tree tree = new Tree(); + + // Create a new root node with empty key so the length can be retrieved throughout the process. + _nodes[0] = new Node() { Key = String.Empty, bitInx = -1, Parent = _nodes[0] }; + + // Update the data-referencing nodes. + for (ushort i = 1; i < _nodes.Count; i++) + tree.Insert(_nodes[i].Key); + + int CurEntry = 0; + foreach (var entry in tree.entries.Values) + { + Node node = entry.Item2; + + node.Reference = (uint)(node.GetCompactBitIdx() & 0xffffffff); + node.IdxLeft = (ushort)tree.entries[node.Child[0].Data].Item1; + node.IdxRight = (ushort)tree.entries[node.Child[1].Data].Item1; + node.Key = node.GetName(); + _nodes[CurEntry] = node; + + CurEntry++; + } + + // Remove the dummy empty key in the root again. + _nodes[0].Key = null; + } + + private Node ReadNode(FileLoader loader) + { + return new Node() + { + Reference = loader.ReadUInt32(), + IdxLeft = loader.ReadUInt16(), + IdxRight = loader.ReadUInt16(), + Key = loader.LoadString(), + }; + } + + // ---- CLASSES ------------------------------------------------------------------------------------------------ + + /// + /// Represents a node forming the Patricia trie of the dictionary. + /// + [DebuggerDisplay(nameof(Node) + " {" + nameof(Key) + "}")] + protected class Node + { + internal const int SizeInBytes = 16; + + internal List Child = new List(); + internal Node Parent; + internal int bitInx; + internal BigInteger Data; + internal uint Reference; + internal ushort IdxLeft; + internal ushort IdxRight; + internal string Key; + + internal Node() + { + Child.Add(this); + Child.Add(this); + Reference = UInt32.MaxValue; + } + internal string GetName() + { + BigInteger data = BitLength(Data) + 7 / 8; + byte[] stringBytes = Data.ToByteArray(); + Array.Reverse(stringBytes, 0, stringBytes.Length); //Convert to big endian + return Encoding.UTF8.GetString(stringBytes); //Decode byte[] to string + } + internal int GetCompactBitIdx() + { + int byteIndx = bitInx / 8; + return (byteIndx << 3) | bitInx - 8 * byteIndx; + } + internal Node(BigInteger data, int bitidx, Node parent) : this() + { + Data = data; + bitInx = bitidx; + Parent = parent; + } + internal Node(string key) : this() + { + Key = key; + } + } + + private class TypeProxy + { + private ResDict _dict; + + internal TypeProxy(ResDict dict) + { + _dict = dict; + } + } + } +} diff --git a/File_Format_Library/FileFormats/Archives/BEA/IO/BinaryDataReader.cs b/File_Format_Library/FileFormats/Archives/BEA/IO/BinaryDataReader.cs new file mode 100644 index 00000000..4273ee7f --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/BEA/IO/BinaryDataReader.cs @@ -0,0 +1,147 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Syroot.BinaryData; + +namespace BezelEngineArchive_Lib +{ + //Thanks to Syroot for the IO and methods + public class FileLoader : BinaryDataReader + { + private IDictionary _dataMap; + + public BezelEngineArchive Archive; + + public FileLoader(BezelEngineArchive bea, Stream stream, bool leaveOpen = true) + : base(stream, Encoding.ASCII, leaveOpen) + { + ByteOrder = ByteOrder.LittleEndian; + Archive = bea; + _dataMap = new Dictionary(); + } + + internal FileLoader(BezelEngineArchive bea, string fileName) + : this(bea, new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + } + + internal T LoadCustom(Func callback, long? offset = null) + { + offset = offset ?? ReadOffset(); + if (offset == 0) return default(T); + + using (this.TemporarySeek(offset.Value, SeekOrigin.Begin)) + { + return callback.Invoke(); + } + } + + internal string LoadString(Encoding encoding = null) + { + long offset = ReadInt64(); + if (offset == 0) return null; + + encoding = encoding ?? Encoding; + using (this.TemporarySeek(offset, SeekOrigin.Begin)) + { + ushort count = ReadUInt16(); + return this.ReadString(count, encoding: encoding); + } + } + internal void LoadBlockHeader() + { + uint offset = ReadUInt32(); + ulong size = ReadUInt64(); + } + + internal void Execute() + { + Seek(0, SeekOrigin.Begin); + ((IFileData)Archive).Load(this); + } + + internal long ReadOffset() + { + long offset = ReadInt64(); + return offset == 0 ? 0 : offset; + } + + internal T Load() + where T : IFileData, new() + { + long offset = ReadOffset(); + if (offset == 0) return default(T); + + // Seek to the instance data and load it. + using (this.TemporarySeek(offset, SeekOrigin.Begin)) + { + return ReadData(); + } + } + + internal IList LoadList(int count, long? offset = null) + where T : IFileData, new() + { + List list = new List(count); + offset = offset ?? ReadOffset(); + if (offset == 0 || count == 0) return list; + + // Seek to the list start and read it. + using (this.TemporarySeek(offset.Value, SeekOrigin.Begin)) + { + for (; count > 0; count--) + { + list.Add(ReadData()); + } + return list; + } + } + + internal ResDict LoadDict() + { + long offset = ReadInt64(); + if (offset == 0) return new ResDict(); + + using (this.TemporarySeek(offset, SeekOrigin.Begin)) + { + ResDict dict = new ResDict(); + ((IFileData)dict).Load(this); + return dict; + } + } + + internal void CheckSignature(string validSignature) + { + // Read the actual signature and compare it. + string signature = this.ReadString(sizeof(uint), Encoding.ASCII); + if (signature != validSignature) + { + throw new Exception($"Invalid signature, expected '{validSignature}' but got '{signature}'."); + } + } + + private T ReadData() + where T : IFileData, new() + { + uint offset = (uint)Position; + + // Same data can be referenced multiple times. Load it in any case to move in the stream, needed for lists. + T instance = new T(); + instance.Load(this); + + // If possible, return an instance already representing the data. + if (_dataMap.TryGetValue(offset, out IFileData existingInstance)) + { + return (T)existingInstance; + } + else + { + _dataMap.Add(offset, instance); + return instance; + } + } + } +} diff --git a/File_Format_Library/FileFormats/Archives/BEA/IO/BinaryDataWriter.cs b/File_Format_Library/FileFormats/Archives/BEA/IO/BinaryDataWriter.cs new file mode 100644 index 00000000..94840ae4 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/BEA/IO/BinaryDataWriter.cs @@ -0,0 +1,455 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Syroot.BinaryData; + +namespace BezelEngineArchive_Lib +{ + public class FileSaver : BinaryDataWriter + { + private IDictionary _savedStrings; + private List _savedBlockPositions; + private IDictionary _savedBlocks; + private List _savedSection1Entries; + private List _savedRelocatedSections; + private List _savedItems; + + internal BezelEngineArchive Archive; + private long _ofsFileSize; + private long _ofsRelocationTable; + private long _ofsFirstBlock; + private long _ofsAsstArray; + private long _ofsAsstRefArray; + private long _ofsFileDictionary; + private long _ofsEndOfBlock; + private uint Section1Size; + private uint beaSize; //Excludes data blocks + private long _ofsAsstStart; + + internal FileSaver(BezelEngineArchive bea, Stream stream, bool leaveOpen = true) + : base(stream, Encoding.ASCII, leaveOpen) + { + ByteOrder = ByteOrder.LittleEndian; + Archive = bea; + } + + internal FileSaver(BezelEngineArchive bea, string fileName) + : this(bea, new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Read), true) + { + } + + internal void Execute() + { + _savedBlockPositions = new List(); + _savedBlocks = new Dictionary(); + _savedSection1Entries = new List(); + _savedStrings = new Dictionary(); + _savedRelocatedSections = new List(); + + ((IFileData)Archive).Save(this); + + //Ref list for version 5 and up + long RefOffset = Position; //Offset to blocks + + if (Archive.ReferenceList != null) + { + SaveRelocateEntryToSection(Position, (uint)Archive.ReferenceList.Count, 1, 0, 1, "Ref String List "); + foreach (var asstRef in Archive.ReferenceList) + SaveString(asstRef); + } + + //Set enough space to add offsets later + long OffsetArrayASST = Position; //Offset to blocks + + List _ofsAsstOffsets = new List(); + foreach (ASST asst in Archive.FileList.Values) + { + SaveRelocateEntryToSection(Position, 1, 1, 0, 1, "Asst Offset "); + _ofsAsstOffsets.Add(Position); + Write(0L); + } + //Now padding. 40 per file + Seek(40 * Archive.FileList.Count, SeekOrigin.Current); + + //Now save dictionary. + long DictionaryOffset = Position; //Offset to blocks + ((IFileData)Archive.FileDictionary).Save(this); + + long BlockOffset = Position; //Offset to blocks + for (int i = 0; i < Archive.FileList.Count; i++) + { + long AsstOffset = Position; + using (this.TemporarySeek(_ofsAsstOffsets[i], SeekOrigin.Begin)) + { + Write(AsstOffset); + } + string FileName = Archive.FileDictionary.GetKey(i); + ((IFileData)Archive.FileList[FileName]).Save(this); + } + + WriteStringPool(); + + + SetupRelocationTable(); + WriteRelocationTable(); + + WriteBlocks(); + + for (int i = 0; i < _savedBlockPositions.Count; i++) + { + Position = _savedBlockPositions[i]; + + if (i == _savedBlockPositions.Count - 1) + { + Write(0); + Write(_ofsEndOfBlock - _savedBlockPositions[i]); //Size of string table to relocation table + } + else + { + uint blockSize = (uint)(_savedBlockPositions[i + 1] - _savedBlockPositions[i]); + WriteHeaderBlock(blockSize, blockSize); + } + } + + Position = _ofsAsstArray; + Write((ulong)OffsetArrayASST); + + if (_ofsAsstStart != 0) + { + Position = _ofsAsstStart; + Write((ulong)BlockOffset); + } + + Position = _ofsFirstBlock; + Write((ushort)BlockOffset); + + Position = _ofsFileDictionary; + Write((ulong)DictionaryOffset); + + if (_ofsAsstRefArray != 0) + { + Position = _ofsAsstRefArray; + Write((ulong)RefOffset); + } + + Position = _ofsFileSize; + Write(beaSize); + Flush(); + } + internal void SaveFirstBlock() + { + _ofsFirstBlock = Position; + Write((ushort)0); + } + + internal void SaveAssetBlock() + { + _ofsAsstStart = Position; + Write((ulong)0); + } + internal void SaveFileAsstPointer() + { + _ofsAsstArray = Position; + Write(0L); + } + + + internal void SaveAssetRefPointer() + { + _ofsAsstRefArray = Position; + Write(0L); + } + internal void SaveFileDictionaryPointer() + { + _ofsFileDictionary = Position; + Write(0L); + } + internal void SaveRelocationTablePointer() + { + _ofsRelocationTable = Position; + Write(0); + } + internal void SaveFileSize() + { + _ofsFileSize = Position; + Write(0); + } + internal void WriteSize(uint Offset, uint value) + { + using (this.TemporarySeek(Offset, SeekOrigin.Begin)) + { + Write(value); + } + } + + internal uint SaveSizePtr() + { + Write(0); + return (uint)Position; + } + internal void SaveBlockHeader() + { + _savedBlockPositions.Add(Position); + Write(0); + Write(0L); + } + internal void SaveBlock(object data, uint alignment, Action callback) + { + if (data == null) + { + Write(0L); + return; + } + if (_savedBlocks.TryGetValue(data, out BlockEntry entry)) + { + entry.Offsets.Add((uint)Position); + } + else + { + _savedBlocks.Add(data, new BlockEntry((uint)Position, alignment, callback)); + } + Write(UInt64.MaxValue); + } + internal void SaveString(string str, Encoding encoding = null) + { + if (str == null) + { + Write(0L); + return; + } + if (_savedStrings.TryGetValue(str, out StringEntry entry)) + { + entry.Offsets.Add((uint)Position); + } + else + { + _savedStrings.Add(str, new StringEntry((uint)Position, encoding)); + } + Write(UInt64.MaxValue); + } + internal void WriteSignature(string value) + { + this.Write(Encoding.ASCII.GetBytes(value)); + } + + //This only should use 1 section to relocate data + internal void SaveRelocateEntryToSection(long pos, uint OffsetCount, uint StructCount, uint PaddingCount, int SectionNumber, string Hint) + { + if (SectionNumber == 1) + _savedSection1Entries.Add(new RelocationEntry((uint)pos, OffsetCount, StructCount, PaddingCount, Hint)); + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private void WriteHeaderBlock(uint Size, long Offset) + { + Write(Size); + Write(Offset); + } + private void SetupRelocationTable() + { + this.Align(Archive.RawAlignment); + RelocationSection FileMainSect; + + long RelocationTableOffset = Position; + + int EntryIndex = 0; + uint EntryPos = 0; + + foreach (RelocationEntry entry in _savedSection1Entries) + { + Console.WriteLine("Pos = " + entry.Position + " " + entry.StructCount + " " + entry.OffsetCount + " " + entry.PadingCount + " " + entry.Hint); + } + + _savedSection1Entries = _savedSection1Entries.OrderBy(o => o.Position).ToList(); + FileMainSect = new RelocationSection(EntryPos, EntryIndex, Section1Size, _savedSection1Entries); + + _savedRelocatedSections.Add(FileMainSect); + + } + private void WriteRelocationTable() + { + uint relocationTableOffset = (uint)Position; + WriteSignature("_RLT"); + _ofsEndOfBlock = (uint)Position; + Write(relocationTableOffset); + Write(_savedRelocatedSections.Count); + Write(0); //padding + + foreach (RelocationSection section in _savedRelocatedSections) + { + Write(0L); //padding + Write(section.Position); + Write(section.Size); + Write(section.EntryIndex); + Write(section.Entries.Count); + } + + foreach (RelocationSection section in _savedRelocatedSections) + { + foreach (RelocationEntry entry in section.Entries) + { + Write(entry.Position); + Write((ushort)entry.StructCount); + Write((byte)entry.OffsetCount); + Write((byte)entry.PadingCount); + } + } + + beaSize = (uint)Position; + + using (this.TemporarySeek(_ofsRelocationTable, SeekOrigin.Begin)) + { + Write(relocationTableOffset); + } + } + private void WriteBlocks() + { + foreach (KeyValuePair entry in _savedBlocks) + { + // Align and satisfy offsets. + Console.WriteLine(entry.Value.Alignment); + Console.WriteLine(Position); + if (entry.Value.Alignment != 0) this.Align((int)entry.Value.Alignment); + Console.WriteLine(Position); + + using (this.TemporarySeek()) + { + SatisfyOffsets(entry.Value.Offsets, (uint)Position); + } + + // Write the data. + entry.Value.Callback.Invoke(); + } + } + private void WriteStringPool() + { + WriteSignature("_STR"); + SaveBlockHeader(); + Write(_savedStrings.Count - 1); + + foreach (KeyValuePair entry in _savedStrings) + { + using (this.TemporarySeek()) + { + SatisfyOffsets(entry.Value.Offsets, (uint)Position); + } + // Write the name. + Write(entry.Key, BinaryStringFormat.WordLengthPrefix, entry.Value.Encoding ?? Encoding); + Align(2); + } + Section1Size = (uint)Position; + } + + private void SatisfyOffsets(IEnumerable offsets, uint target) + { + foreach (uint offset in offsets) + { + Position = offset; + Write((long)(target)); + } + } + + private bool TryGetItemEntry(object data, ItemEntryType type, out ItemEntry entry) + { + foreach (ItemEntry savedItem in _savedItems) + { + if (savedItem.Data.Equals(data) && savedItem.Type == type) + { + entry = savedItem; + return true; + } + } + entry = null; + return false; + } + + private class StringEntry + { + internal List Offsets; + internal Encoding Encoding; + + internal StringEntry(uint offset, Encoding encoding = null) + { + Offsets = new List(new uint[] { offset }); + Encoding = encoding; + } + } + private class BlockEntry + { + internal List Offsets; + internal uint Alignment; + internal Action Callback; + + internal BlockEntry(uint offset, uint alignment, Action callback) + { + Offsets = new List { offset }; + Alignment = alignment; + Callback = callback; + } + } + private class RelocationSection + { + internal List Entries; + internal int EntryIndex; + internal uint Size; + internal uint Position; + + internal RelocationSection(uint position, int entryIndex, uint size, List entries) + { + Position = position; + EntryIndex = entryIndex; + Size = size; + Entries = entries; + } + } + private enum ItemEntryType + { + List, Dict, FileData, Custom + } + private class ItemEntry + { + internal object Data; + internal ItemEntryType Type; + internal List Offsets; + internal uint? Target; + internal Action Callback; + internal int Index; + + internal ItemEntry(object data, ItemEntryType type, uint? offset = null, uint? target = null, + Action callback = null, int index = -1) + { + Data = data; + Type = type; + Offsets = new List(); + if (offset.HasValue) // Might be null for enumerable entries to resolve references to them later. + { + Offsets.Add(offset.Value); + } + Callback = callback; + Target = target; + Index = index; + } + } + private class RelocationEntry + { + internal uint Position; + internal uint PadingCount; + internal uint StructCount; + internal uint OffsetCount; + internal string Hint; + + internal RelocationEntry(uint position, uint offsetCount, uint structCount, uint padingCount, string hint) + { + Position = position; + StructCount = structCount; + OffsetCount = offsetCount; + PadingCount = padingCount; + Hint = hint; + } + } + } +} diff --git a/File_Format_Library/FileFormats/Archives/BEA/IO/IResData.cs b/File_Format_Library/FileFormats/Archives/BEA/IO/IResData.cs new file mode 100644 index 00000000..817d4d38 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/BEA/IO/IResData.cs @@ -0,0 +1,22 @@ +namespace BezelEngineArchive_Lib +{ + /// + /// Represents the common interface for data instances. + /// + public interface IFileData + { + // ---- METHODS ------------------------------------------------------------------------------------------------ + + /// + /// Loads raw data from the data stream into instances. + /// + /// The to load data with. + void Load(FileLoader loader); + + /// + /// Saves header data of the instance and queues referenced data in the given . + /// + /// The to save headers and queue data with. + void Save(FileSaver saver); + } +} diff --git a/File_Format_Library/FileFormats/Archives/BEA/IO/SubStreamBea.cs b/File_Format_Library/FileFormats/Archives/BEA/IO/SubStreamBea.cs new file mode 100644 index 00000000..f23908b3 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/BEA/IO/SubStreamBea.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace BezelEngineArchive_Lib +{ + public class SubStreamBea : Stream + { + Stream baseStream; + readonly long length; + readonly long baseOffset; + public SubStreamBea(Stream baseStream, long offset, long length) + { + if (baseStream == null) throw new ArgumentNullException("baseStream"); + if (!baseStream.CanRead) throw new ArgumentException("baseStream.CanRead is false"); + if (!baseStream.CanSeek) throw new ArgumentException("baseStream.CanSeek is false"); + if (offset < 0) throw new ArgumentOutOfRangeException("offset"); + if (offset + length > baseStream.Length) throw new ArgumentOutOfRangeException("length"); + + this.baseStream = baseStream; + this.length = length; + baseOffset = offset; + } + public override int Read(byte[] buffer, int offset, int count) + { + baseStream.Position = baseOffset + offset + Position; + int read = baseStream.Read(buffer, offset, (int)Math.Min(count, length - Position)); + Position += read; + return read; + } + public override long Length => length; + public override bool CanRead => true; + public override bool CanWrite => false; + public override bool CanSeek => true; + public override long Position { get; set; } + public override void Flush() => baseStream.Flush(); + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: return Position = offset; + case SeekOrigin.Current: return Position += offset; + case SeekOrigin.End: return Position = length + offset; + } + throw new ArgumentException("origin is invalid"); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL.cs b/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL.cs index 0ab34120..e530715b 100644 --- a/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL.cs +++ b/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL.cs @@ -773,7 +773,7 @@ namespace Bfres.Structs if (AttributeMatcher.ContainsKey(obj.ObjectName)) shape.vertexAttributes = csvsettings.CreateNewAttributes(AttributeMatcher[obj.ObjectName]); else - shape.vertexAttributes = csvsettings.CreateNewAttributes(); + shape.vertexAttributes = csvsettings.CreateNewAttributes(GetMaterial(shape.MaterialIndex)); shape.BoneIndex = 0; shape.Text = obj.ObjectName; @@ -1304,12 +1304,6 @@ namespace Bfres.Structs shape.VertexBufferIndex = shapes.Count; shape.vertices = obj.vertices; - - if (AttributeMatcher.ContainsKey(obj.ObjectName)) - shape.vertexAttributes = settings.CreateNewAttributes(AttributeMatcher[obj.ObjectName]); - else - shape.vertexAttributes = settings.CreateNewAttributes(); - shape.BoneIndex = obj.BoneIndex; if (obj.MaterialIndex + MatStartIndex < materials.Count && obj.MaterialIndex > 0) @@ -1317,6 +1311,11 @@ namespace Bfres.Structs else shape.MaterialIndex = 0; + if (AttributeMatcher.ContainsKey(obj.ObjectName)) + shape.vertexAttributes = settings.CreateNewAttributes(AttributeMatcher[obj.ObjectName]); + else + shape.vertexAttributes = settings.CreateNewAttributes(GetMaterial(shape.MaterialIndex)); + shape.lodMeshes = obj.lodMeshes; shape.CreateBoneList(obj, this, ForceSkinInfluence, ForceSkinInfluenceMax); shape.CreateIndexList(obj, this, ForceSkinInfluence, ForceSkinInfluenceMax); diff --git a/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL/FSHP.cs b/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL/FSHP.cs index 60a3ee58..eccb6e16 100644 --- a/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL/FSHP.cs +++ b/File_Format_Library/FileFormats/BFRES/Bfres Structs/SubFiles/FMDL/FSHP.cs @@ -1400,16 +1400,17 @@ namespace Bfres.Structs max = CalculateBBMax(vertices); } - //Get the largest values in local space to create the largest bounding box - foreach (var bounding in aabb) + var c = (min + max) / 2.0f; + var e = (max - min) / 2.0f; + + float sphereRadius = (float)(c.Length + e.Length); + + return new BoundingBox() { - min.X = Math.Min(min.X, bounding.Min.X); - min.Y = Math.Min(min.Y, bounding.Min.Y); - min.Z = Math.Min(min.Z, bounding.Min.Z); - max.X = Math.Max(max.X, bounding.Max.X); - max.Y = Math.Max(max.Y, bounding.Max.Y); - max.Z = Math.Max(max.Z, bounding.Max.Z); - } + Radius = sphereRadius, + Center = new Vector3(c.X, c.Y, c.Z), + Extend = new Vector3(e.X, e.Y, e.Z), + }; } else { @@ -1756,6 +1757,22 @@ namespace Bfres.Structs vert.Format = att.Format; atrib.Add(vert); } + if (att.Name == "_g3d_02_u0_u1") + { + VertexBufferHelperAttrib vert = new VertexBufferHelperAttrib(); + vert.Name = att.Name; + vert.Data = uv0.ToArray(); + vert.Format = att.Format; + atrib.Add(vert); + } + if (att.Name == "_g3d_02_u2_u3") + { + VertexBufferHelperAttrib vert = new VertexBufferHelperAttrib(); + vert.Name = att.Name; + vert.Data = uv2.ToArray(); + vert.Format = att.Format; + atrib.Add(vert); + } if (att.Name == "_b0") { VertexBufferHelperAttrib vert = new VertexBufferHelperAttrib(); @@ -1780,6 +1797,31 @@ namespace Bfres.Structs vert.Format = att.Format; atrib.Add(vert); } + if (att.Name == "_c1") + { + VertexBufferHelperAttrib vert = new VertexBufferHelperAttrib(); + vert.Name = att.Name; + vert.Data = colors1.ToArray(); + vert.Format = att.Format; + atrib.Add(vert); + } + if (att.Name == "_c2") + { + VertexBufferHelperAttrib vert = new VertexBufferHelperAttrib(); + vert.Name = att.Name; + vert.Data = colors2.ToArray(); + vert.Format = att.Format; + atrib.Add(vert); + } + + if (att.Name == "_c3") + { + VertexBufferHelperAttrib vert = new VertexBufferHelperAttrib(); + vert.Name = att.Name; + vert.Data = colors3.ToArray(); + vert.Format = att.Format; + atrib.Add(vert); + } // Set _w and _i for (int i = 0; i < weights.Count; i++) @@ -1830,6 +1872,9 @@ namespace Bfres.Structs internal List> weights = new List>(); internal List> boneInd = new List>(); internal List colors = new List(); + internal List colors1 = new List(); + internal List colors2 = new List(); + internal List colors3 = new List(); public string GetBoneNameFromIndex(FMDL mdl, int index) { @@ -2059,6 +2104,9 @@ namespace Bfres.Structs colors.Clear(); weights.Clear(); boneInd.Clear(); + colors1.Clear(); + colors2.Clear(); + colors3.Clear(); // Create arrays to be able to fit the needed skin count int listCount = (int)Math.Ceiling(VertexSkinCount / 4.0); @@ -2086,12 +2134,15 @@ namespace Bfres.Structs verts.Add(new Syroot.Maths.Vector4F(vtx.pos.X, vtx.pos.Y, vtx.pos.Z, 1.0f)); norms.Add(new Syroot.Maths.Vector4F(vtx.nrm.X, vtx.nrm.Y, vtx.nrm.Z, 0)); - uv0.Add(new Syroot.Maths.Vector4F(vtx.uv0.X, vtx.uv0.Y, 0, 0)); + uv0.Add(new Syroot.Maths.Vector4F(vtx.uv0.X, vtx.uv0.Y, vtx.uv1.X, vtx.uv1.Y)); uv1.Add(new Syroot.Maths.Vector4F(vtx.uv1.X, vtx.uv1.Y, 0, 0)); - uv2.Add(new Syroot.Maths.Vector4F(vtx.uv2.X, vtx.uv2.Y, 0, 0)); + uv2.Add(new Syroot.Maths.Vector4F(vtx.uv2.X, vtx.uv2.Y, vtx.uv3.X, vtx.uv3.Y)); tans.Add(new Syroot.Maths.Vector4F(vtx.tan.X, vtx.tan.Y, vtx.tan.Z, vtx.tan.W)); bitans.Add(new Syroot.Maths.Vector4F(vtx.bitan.X, vtx.bitan.Y, vtx.bitan.Z, vtx.bitan.W)); colors.Add(new Syroot.Maths.Vector4F(vtx.col.X, vtx.col.Y, vtx.col.Z, vtx.col.W)); + colors1.Add(new Syroot.Maths.Vector4F(vtx.col2.X, vtx.col2.Y, vtx.col2.Z, vtx.col2.W)); + colors2.Add(new Syroot.Maths.Vector4F(vtx.col3.X, vtx.col3.Y, vtx.col3.Z, vtx.col3.W)); + colors3.Add(new Syroot.Maths.Vector4F(vtx.col4.X, vtx.col4.Y, vtx.col4.Z, vtx.col4.W)); // Init arrays based on skincount float[] weightsA = new float[TargetVertexSkinCount]; diff --git a/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs b/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs index be963d81..79b2cb40 100644 --- a/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs +++ b/File_Format_Library/FileFormats/BFRES/BfresSwitch.cs @@ -263,6 +263,9 @@ namespace FirstPlugin Syroot.Maths.Vector4F[] vec4uv1 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4uv2 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4c0 = new Syroot.Maths.Vector4F[0]; + Syroot.Maths.Vector4F[] vec4c1 = new Syroot.Maths.Vector4F[0]; + Syroot.Maths.Vector4F[] vec4c2 = new Syroot.Maths.Vector4F[0]; + Syroot.Maths.Vector4F[] vec4c3 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4t0 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4b0 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4w0 = new Syroot.Maths.Vector4F[0]; @@ -270,6 +273,9 @@ namespace FirstPlugin Syroot.Maths.Vector4F[] vec4i0 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4i1 = new Syroot.Maths.Vector4F[0]; + Syroot.Maths.Vector4F[] vec4uv01 = new Syroot.Maths.Vector4F[0]; + Syroot.Maths.Vector4F[] vec4uv23 = new Syroot.Maths.Vector4F[0]; + //For shape morphing Syroot.Maths.Vector4F[] vec4Positions1 = new Syroot.Maths.Vector4F[0]; Syroot.Maths.Vector4F[] vec4Positions2 = new Syroot.Maths.Vector4F[0]; @@ -292,6 +298,12 @@ namespace FirstPlugin vec4uv2 = AttributeData(att, helper, "_u2"); if (att.Name == "_c0") vec4c0 = AttributeData(att, helper, "_c0"); + if (att.Name == "_c1") + vec4c1 = AttributeData(att, helper, "_c1"); + if (att.Name == "_c2") + vec4c2 = AttributeData(att, helper, "_c2"); + if (att.Name == "_c3") + vec4c3 = AttributeData(att, helper, "_c3"); if (att.Name == "_t0") vec4t0 = AttributeData(att, helper, "_t0"); if (att.Name == "_b0") @@ -300,10 +312,10 @@ namespace FirstPlugin vec4w0 = AttributeData(att, helper, "_w0"); if (att.Name == "_i0") vec4i0 = AttributeData(att, helper, "_i0"); - if (att.Name == "_w1") - vec4w1 = AttributeData(att, helper, "_w1"); - if (att.Name == "_i1") - vec4i1 = AttributeData(att, helper, "_i1"); + if (att.Name == "_g3d_02_u0_u1") + vec4uv01 = AttributeData(att, helper, "_g3d_02_u0_u1"); + if (att.Name == "_g3d_02_u2_u3") + vec4uv23 = AttributeData(att, helper, "_g3d_02_u2_u3"); if (att.Name == "_p1") vec4Positions1 = AttributeData(att, helper, "_p1"); @@ -330,6 +342,17 @@ namespace FirstPlugin if (vec4uv2.Length > 0) v.uv2 = new Vector2(vec4uv2[i].X, vec4uv2[i].Y); + if (vec4uv01.Length > 0) + { + v.uv0 = new Vector2(vec4uv01[i].X, vec4uv01[i].Y); + v.uv1 = new Vector2(vec4uv01[i].Z, vec4uv01[i].W); + } + if (vec4uv23.Length > 0) + { + v.uv2 = new Vector2(vec4uv23[i].X, vec4uv23[i].Y); + v.uv3 = new Vector2(vec4uv23[i].Z, vec4uv23[i].W); + } + if (vec4w0.Length > 0) { if (fshp.VertexSkinCount > 0) @@ -374,12 +397,19 @@ namespace FirstPlugin if (fshp.VertexSkinCount > 7) v.boneIds.Add((int)vec4i1[i].W); } + if (vec4t0.Length > 0) v.tan = new Vector4(vec4t0[i].X, vec4t0[i].Y, vec4t0[i].Z, vec4t0[i].W); if (vec4b0.Length > 0) v.bitan = new Vector4(vec4b0[i].X, vec4b0[i].Y, vec4b0[i].Z, vec4b0[i].W); if (vec4c0.Length > 0) v.col = new Vector4(vec4c0[i].X, vec4c0[i].Y, vec4c0[i].Z, vec4c0[i].W); + if (vec4c1.Length > 0) + v.col2 = new Vector4(vec4c1[i].X, vec4c1[i].Y, vec4c1[i].Z, vec4c1[i].W); + if (vec4c2.Length > 0) + v.col3 = new Vector4(vec4c2[i].X, vec4c2[i].Y, vec4c2[i].Z, vec4c2[i].W); + if (vec4c3.Length > 0) + v.col4 = new Vector4(vec4c3[i].X, vec4c3[i].Y, vec4c3[i].Z, vec4c3[i].W); if (fshp.VertexSkinCount == 1) { @@ -778,7 +808,6 @@ namespace FirstPlugin m.HasNormalMap = true; texture.Type = MatTexture.TextureType.Normal; } - else if (texture.SamplerName == "_e0") { m.HasEmissionMap = true; @@ -796,7 +825,7 @@ namespace FirstPlugin } else if (texture.SamplerName == "_gn0") //Damage { - + } // EOW Samplers else if (useSampler == "_albedo0") @@ -1063,7 +1092,6 @@ namespace FirstPlugin m.HasEmissionMap = true; texture.Type = MatTexture.TextureType.Emission; } - else if (texture.SamplerName == "_s0" || useSampler == "_s0") { m.HasSpecularMap = true; @@ -1084,14 +1112,11 @@ namespace FirstPlugin m.HasLightMap = true; texture.Type = MatTexture.TextureType.Light; } - else if (texture.SamplerName == "bake0") { m.HasShadowMap = true; texture.Type = MatTexture.TextureType.Shadow; - } - - // EOW Frag Samplers + } // EOW Frag Samplers else if (useSampler == "Albedo0") { diff --git a/File_Format_Library/File_Format_Library.csproj b/File_Format_Library/File_Format_Library.csproj index 7db508d7..fa2096be 100644 --- a/File_Format_Library/File_Format_Library.csproj +++ b/File_Format_Library/File_Format_Library.csproj @@ -50,10 +50,6 @@ False ..\Toolbox\Lib\BcresLibrary.dll - - ..\Toolbox\Lib\BezelEngineArchive_Lib.dll - False - False ..\Toolbox\Lib\BfshaLibrary.dll @@ -229,6 +225,13 @@ + + + + + + + diff --git a/File_Format_Library/GUI/BFRES/BfresModelImportSettings.Designer.cs b/File_Format_Library/GUI/BFRES/BfresModelImportSettings.Designer.cs index 97e333d1..ad347f0a 100644 --- a/File_Format_Library/GUI/BFRES/BfresModelImportSettings.Designer.cs +++ b/File_Format_Library/GUI/BFRES/BfresModelImportSettings.Designer.cs @@ -105,6 +105,7 @@ this.stCheckBox1 = new Toolbox.Library.Forms.STCheckBox(); this.chkMapOriginalMaterials = new Toolbox.Library.Forms.STCheckBox(); this.ogSkinCountChkBox = new Toolbox.Library.Forms.STCheckBox(); + this.combineUVs = new Toolbox.Library.Forms.STCheckBox(); this.contentContainer.SuspendLayout(); this.panel1.SuspendLayout(); this.panel2.SuspendLayout(); @@ -460,6 +461,7 @@ // // panel8 // + this.panel8.Controls.Add(this.combineUVs); this.panel8.Controls.Add(this.stLabel4); this.panel8.Controls.Add(this.lodCountUD); this.panel8.Controls.Add(this.chkCreateDummyLODs); @@ -603,7 +605,6 @@ this.chkBoxRotNegative90Y.Text = "Rotate -90 degrees"; this.chkBoxRotNegative90Y.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; this.chkBoxRotNegative90Y.UseVisualStyleBackColor = true; - this.chkBoxRotNegative90Y.CheckedChanged += new System.EventHandler(this.chkBoxSettings_CheckedChanged); // // textBoxMaterialPath // @@ -836,7 +837,7 @@ this.tabPageAdvanced.Location = new System.Drawing.Point(4, 25); this.tabPageAdvanced.Name = "tabPageAdvanced"; this.tabPageAdvanced.Padding = new System.Windows.Forms.Padding(3); - this.tabPageAdvanced.Size = new System.Drawing.Size(530, 347); + this.tabPageAdvanced.Size = new System.Drawing.Size(192, 71); this.tabPageAdvanced.TabIndex = 0; this.tabPageAdvanced.Text = "Advanced Settings"; // @@ -853,7 +854,7 @@ this.stPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.stPanel1.Location = new System.Drawing.Point(3, 3); this.stPanel1.Name = "stPanel1"; - this.stPanel1.Size = new System.Drawing.Size(524, 341); + this.stPanel1.Size = new System.Drawing.Size(186, 65); this.stPanel1.TabIndex = 17; // // tabPage1 @@ -870,7 +871,7 @@ this.tabPage1.Location = new System.Drawing.Point(4, 25); this.tabPage1.Name = "tabPage1"; this.tabPage1.Padding = new System.Windows.Forms.Padding(3); - this.tabPage1.Size = new System.Drawing.Size(530, 347); + this.tabPage1.Size = new System.Drawing.Size(192, 71); this.tabPage1.TabIndex = 2; this.tabPage1.Text = "Inject Mode"; this.tabPage1.UseVisualStyleBackColor = true; @@ -981,6 +982,16 @@ this.ogSkinCountChkBox.Text = "Keep Original Skin Count (can help crashes)"; this.ogSkinCountChkBox.UseVisualStyleBackColor = true; // + // combineUVs + // + this.combineUVs.AutoSize = true; + this.combineUVs.Location = new System.Drawing.Point(414, 88); + this.combineUVs.Name = "combineUVs"; + this.combineUVs.Size = new System.Drawing.Size(90, 17); + this.combineUVs.TabIndex = 40; + this.combineUVs.Text = "Combine UVs"; + this.combineUVs.UseVisualStyleBackColor = true; + // // BfresModelImportSettings // this.ClientSize = new System.Drawing.Size(547, 412); @@ -1097,5 +1108,6 @@ private Toolbox.Library.Forms.STCheckBox chkCreateDummyLODs; private Toolbox.Library.Forms.STLabel stLabel4; private Toolbox.Library.Forms.NumericUpDownUint lodCountUD; + private Toolbox.Library.Forms.STCheckBox combineUVs; } } diff --git a/File_Format_Library/GUI/BFRES/BfresModelImportSettings.cs b/File_Format_Library/GUI/BFRES/BfresModelImportSettings.cs index 59c88052..8a00d9e4 100644 --- a/File_Format_Library/GUI/BFRES/BfresModelImportSettings.cs +++ b/File_Format_Library/GUI/BFRES/BfresModelImportSettings.cs @@ -7,6 +7,8 @@ using Toolbox.Library.Forms; using Toolbox.Library.Rendering; using Bfres.Structs; using System.Linq; +using Syroot.NintenTools.NSW.Bfres; +using static OpenTK.Graphics.OpenGL.GL; namespace FirstPlugin { @@ -65,6 +67,8 @@ namespace FirstPlugin public bool ResetUVParams; public bool ResetColorParams; + public bool CombineUVs => combineUVs.Checked; + public bool LimitSkinCount => ogSkinCountChkBox.Checked; public bool MapOriginalMaterials { @@ -181,13 +185,18 @@ namespace FirstPlugin Attributes[i].Format = (AttribFormat)comboBoxFormatWeights.SelectedItem; if (Attributes[i].Name == "_i0") Attributes[i].Format = (AttribFormat)comboBoxFormatIndices.SelectedItem; + + if (CombineUVs && Attributes[i].Name == "_u0") + Attributes[i].Format = AttribFormat.Format_16_16_16_16_Single; + + } } return Attributes; } - public List CreateNewAttributes() + public List CreateNewAttributes(FMAT material = null) { Dictionary attribute = new Dictionary(); @@ -230,20 +239,35 @@ namespace FirstPlugin FSHP.VertexAttribute att = new FSHP.VertexAttribute(); att.Name = "_u0"; att.Format = (AttribFormat)comboBoxFormatUvs.SelectedItem; + + if (material.shaderassign.attributes.ContainsValue("_g3d_02_u0_u1")) + { + att.Format = AttribFormat.Format_16_16_16_16_Single; + att.Name = "_g3d_02_u0_u1"; + } + attribute.Add(att.Name, att); } - if (EnableUV1 && EnableUV0) + if (EnableUV1 && EnableUV0 && !attribute.ContainsKey("_g3d_02_u0_u1")) { FSHP.VertexAttribute att = new FSHP.VertexAttribute(); att.Name = "_u1"; att.Format = (AttribFormat)comboBoxFormatUvs.SelectedItem; attribute.Add(att.Name, att); } + if (EnableUV2 && EnableUV0) { FSHP.VertexAttribute att = new FSHP.VertexAttribute(); att.Name = "_u2"; att.Format = (AttribFormat)comboBoxFormatUvs.SelectedItem; + + if (material.shaderassign.attributes.ContainsValue("_g3d_02_u2_u3")) + { + att.Format = AttribFormat.Format_16_16_16_16_Single; + att.Name = "_g3d_02_u2_u3"; + } + attribute.Add(att.Name, att); } if (EnableTangents) @@ -275,6 +299,25 @@ namespace FirstPlugin attribute.Add(att.Name, att); } + if (material.shaderassign.attributes.ContainsValue("_c0") && !EnableVertexColors) + { + FSHP.VertexAttribute att = new FSHP.VertexAttribute(); + att.Name = "_c0"; + att.Format = (AttribFormat)comboBoxFormatVertexColors.SelectedItem; + attribute.Add(att.Name, att); + } + + for (int i = 1; i < 6; i++) + { + if (material.shaderassign.attributes.ContainsValue($"_c{i}")) + { + FSHP.VertexAttribute att = new FSHP.VertexAttribute(); + att.Name = $"_c{i}"; + att.Format = (AttribFormat)comboBoxFormatVertexColors.SelectedItem; + attribute.Add(att.Name, att); + } + } + switch ((GamePreset)gamePresetCB.SelectedItem) { //Use single buffer @@ -492,12 +535,10 @@ namespace FirstPlugin int assimpIndex = assimpMeshListView.SelectedIndices[0]; - objectNameTB.BackColor = System.Drawing.Color.DarkRed; - foreach (ListViewItem item in originalMeshListView.Items) - { - if (objectNameTB.Text == item.Text) - objectNameTB.BackColor = System.Drawing.Color.Green; - } + if (objectNameTB.Text == originalMeshListView.Items[assimpIndex].Text) + objectNameTB.BackColor = System.Drawing.Color.Green; + else + objectNameTB.BackColor = System.Drawing.Color.DarkRed; NewMeshlist[assimpIndex].ObjectName = objectNameTB.Text; } diff --git a/Switch_Toolbox_Library/FileFormats/DAE/DAE.cs b/Switch_Toolbox_Library/FileFormats/DAE/DAE.cs index 00ad619b..b040eb87 100644 --- a/Switch_Toolbox_Library/FileFormats/DAE/DAE.cs +++ b/Switch_Toolbox_Library/FileFormats/DAE/DAE.cs @@ -15,7 +15,7 @@ using Toolbox.Library.IO; namespace Toolbox.Library { - public class DAE + public class DAE { public class ExportSettings { @@ -55,11 +55,12 @@ namespace Toolbox.Library new List(), new List()); } - public static void Export(string FileName, ExportSettings settings, STGenericModel model, List Textures, STSkeleton skeleton = null, List NodeArray = null) { - Export(FileName, settings, model.Objects.ToList(), model.Materials.ToList(), Textures, skeleton, NodeArray); + public static void Export(string FileName, ExportSettings settings, STGenericModel model, List Textures, STSkeleton skeleton = null, List NodeArray = null) + { + Export(FileName, settings, model.Objects.ToList(), model.Materials.ToList(), Textures, skeleton, NodeArray); } - public static void Export(string FileName, ExportSettings settings, + public static void Export(string FileName, ExportSettings settings, List Meshes, List Materials, List Textures, STSkeleton skeleton = null, List NodeArray = null) { @@ -102,7 +103,8 @@ namespace Toolbox.Library if (!textureNames.Contains(Textures[i].Text)) textureNames.Add(Textures[i].Text); - if (settings.ExportTextures) { + if (settings.ExportTextures) + { progressBar.Task = $"Exporting Texture {Textures[i].Text}"; progressBar.Value = ((i * 100) / Textures.Count); @@ -137,7 +139,8 @@ namespace Toolbox.Library GC.Collect(); } } - catch (Exception ex) { + catch (Exception ex) + { failedTextureExport.Add(Textures[i].Text); } } @@ -189,7 +192,7 @@ namespace Toolbox.Library texMap.WrapModeT = SamplerWrapMode.WRAP; else if (tex.WrapModeT == STTextureWrapMode.Mirror) texMap.WrapModeT = SamplerWrapMode.MIRROR; - else if(tex.WrapModeT == STTextureWrapMode.Clamp) + else if (tex.WrapModeT == STTextureWrapMode.Clamp) texMap.WrapModeT = SamplerWrapMode.CLAMP; @@ -210,7 +213,8 @@ namespace Toolbox.Library else writer.WriteLibraryImages(); - if (skeleton != null) { + if (skeleton != null) + { //Search for bones with rigging first List riggedBones = new List(); if (settings.OnlyExportRiggedBones) @@ -223,7 +227,8 @@ namespace Toolbox.Library for (int j = 0; j < vertex.boneIds.Count; j++) { int id = -1; - if (NodeArray != null && NodeArray.Count > vertex.boneIds[j]) { + if (NodeArray != null && NodeArray.Count > vertex.boneIds[j]) + { id = NodeArray[vertex.boneIds[j]]; } else @@ -288,7 +293,7 @@ namespace Toolbox.Library if (mesh.MaterialIndex != -1 && Materials.Count > mesh.MaterialIndex) { writer.CurrentMaterial = Materials[mesh.MaterialIndex].Text; - Console.WriteLine($"MaterialIndex {mesh.MaterialIndex } {Materials[mesh.MaterialIndex].Text}"); + Console.WriteLine($"MaterialIndex {mesh.MaterialIndex} {Materials[mesh.MaterialIndex].Text}"); } @@ -312,18 +317,21 @@ namespace Toolbox.Library transform = diffuse.Transform; var vertexA = mesh.vertices[faces[v]]; - var vertexB = mesh.vertices[faces[v+1]]; - var vertexC = mesh.vertices[faces[v+2]]; + var vertexB = mesh.vertices[faces[v + 1]]; + var vertexC = mesh.vertices[faces[v + 2]]; - if (!transformedVertices.Contains(vertexA)) { + if (!transformedVertices.Contains(vertexA)) + { vertexA.uv0 = (vertexA.uv0 * transform.Scale) + transform.Translate; transformedVertices.Add(vertexA); } - if (!transformedVertices.Contains(vertexB)) { + if (!transformedVertices.Contains(vertexB)) + { vertexB.uv0 = (vertexB.uv0 * transform.Scale) + transform.Translate; transformedVertices.Add(vertexB); } - if (!transformedVertices.Contains(vertexC)) { + if (!transformedVertices.Contains(vertexC)) + { vertexC.uv0 = (vertexC.uv0 * transform.Scale) + transform.Translate; transformedVertices.Add(vertexC); } @@ -340,15 +348,20 @@ namespace Toolbox.Library List UV3 = new List(); List Color = new List(); List Color2 = new List(); + List Color3 = new List(); + List Color4 = new List(); List BoneIndices = new List(); List BoneWeights = new List(); bool HasNormals = false; bool HasColors = false; bool HasColors2 = false; + bool HasColors3 = false; + bool HasColors4 = false; bool HasUV0 = false; bool HasUV1 = false; bool HasUV2 = false; + bool HasUV3 = false; bool HasBoneIds = false; foreach (var vertex in mesh.vertices) @@ -356,9 +369,13 @@ namespace Toolbox.Library if (vertex.nrm != Vector3.Zero) HasNormals = true; if (vertex.col != Vector4.One && settings.UseVertexColors) HasColors = true; if (vertex.col2 != Vector4.One && settings.UseVertexColors) HasColors2 = true; + if (vertex.col3 != Vector4.One && settings.UseVertexColors) HasColors3 = true; + if (vertex.col4 != Vector4.One && settings.UseVertexColors) HasColors4 = true; + if (vertex.uv0 != Vector2.Zero) HasUV0 = true; if (vertex.uv1 != Vector2.Zero) HasUV1 = true; if (vertex.uv2 != Vector2.Zero) HasUV2 = true; + if (vertex.uv3 != Vector2.Zero) HasUV3 = true; if (vertex.boneIds.Count > 0) HasBoneIds = true; Position.Add(vertex.pos.X); Position.Add(vertex.pos.Y); Position.Add(vertex.pos.Z); @@ -369,16 +386,20 @@ namespace Toolbox.Library UV0.Add(vertex.uv0.X); UV0.Add(1 - vertex.uv0.Y); UV1.Add(vertex.uv1.X); UV1.Add(1 - vertex.uv1.Y); UV2.Add(vertex.uv2.X); UV2.Add(1 - vertex.uv2.Y); + UV3.Add(vertex.uv3.X); UV3.Add(1 - vertex.uv3.Y); } else { UV0.Add(vertex.uv0.X); UV0.Add(vertex.uv0.Y); UV1.Add(vertex.uv1.X); UV1.Add(vertex.uv1.Y); UV2.Add(vertex.uv2.X); UV2.Add(vertex.uv2.Y); + UV3.Add(vertex.uv3.X); UV3.Add(vertex.uv3.Y); } Color.AddRange(new float[] { vertex.col.X, vertex.col.Y, vertex.col.Z, vertex.col.W }); Color2.AddRange(new float[] { vertex.col2.X, vertex.col2.Y, vertex.col2.Z, vertex.col2.W }); + Color3.AddRange(new float[] { vertex.col3.X, vertex.col3.Y, vertex.col3.Z, vertex.col3.W }); + Color4.AddRange(new float[] { vertex.col4.X, vertex.col4.Y, vertex.col4.Z, vertex.col4.W }); List bIndices = new List(); List bWeights = new List(); @@ -446,7 +467,7 @@ namespace Toolbox.Library foreach (var group in mesh.PolygonGroups) { TriangleList triangleList = new TriangleList(); - + triangleLists.Add(triangleList); STGenericMaterial material = new STGenericMaterial(); @@ -484,9 +505,12 @@ namespace Toolbox.Library if (HasColors) writer.WriteGeometrySource(mesh.Text, SemanticType.COLOR, Color.ToArray(), triangleLists.ToArray(), 0); - if (HasColors2) writer.WriteGeometrySource(mesh.Text, SemanticType.COLOR, Color2.ToArray(), triangleLists.ToArray(), 1); + if (HasColors3) + writer.WriteGeometrySource(mesh.Text, SemanticType.COLOR, Color3.ToArray(), triangleLists.ToArray(), 2); + if (HasColors4) + writer.WriteGeometrySource(mesh.Text, SemanticType.COLOR, Color4.ToArray(), triangleLists.ToArray(), 3); if (HasUV0) writer.WriteGeometrySource(mesh.Text, SemanticType.TEXCOORD, UV0.ToArray(), triangleLists.ToArray(), 0); @@ -497,6 +521,9 @@ namespace Toolbox.Library if (HasUV2) writer.WriteGeometrySource(mesh.Text, SemanticType.TEXCOORD, UV2.ToArray(), triangleLists.ToArray(), 2); + if (HasUV3) + writer.WriteGeometrySource(mesh.Text, SemanticType.TEXCOORD, UV3.ToArray(), triangleLists.ToArray(), 3); + if (HasBoneIds) writer.AttachGeometryController(BoneIndices, BoneWeights); @@ -508,7 +535,7 @@ namespace Toolbox.Library progressBar?.Close(); if (!settings.SuppressConfirmDialog) - System.Windows.Forms.MessageBox.Show($"Exported {FileName} successfully!"); + System.Windows.Forms.MessageBox.Show($"Exported {FileName} Successfully!"); } @@ -530,7 +557,7 @@ namespace Toolbox.Library COLLADA collada = COLLADA.Load(FileName); - + //Check axis up if (collada.asset != null) { @@ -593,7 +620,7 @@ namespace Toolbox.Library private void LoadNodes(library_nodes nodes) { - + } private void LoadMaterials(library_materials materials) diff --git a/Switch_Toolbox_Library/Rendering/RenderLib.cs b/Switch_Toolbox_Library/Rendering/RenderLib.cs index 11e6e2b6..6991b7ed 100644 --- a/Switch_Toolbox_Library/Rendering/RenderLib.cs +++ b/Switch_Toolbox_Library/Rendering/RenderLib.cs @@ -24,6 +24,8 @@ namespace Toolbox.Library.Rendering public Vector3 nrm = new Vector3(0); public Vector4 col = new Vector4(1); public Vector4 col2 = new Vector4(1); + public Vector4 col3 = new Vector4(1); + public Vector4 col4 = new Vector4(1); public Vector2 uv0 = new Vector2(0); public Vector2 uv1 = new Vector2(0);