diff --git a/File_Format_Library/FileFormats/Archives/LM2/LM2_DICT.cs b/File_Format_Library/FileFormats/Archives/LM2/LM2_DICT.cs index 65a69562..4b139b89 100644 --- a/File_Format_Library/FileFormats/Archives/LM2/LM2_DICT.cs +++ b/File_Format_Library/FileFormats/Archives/LM2/LM2_DICT.cs @@ -95,7 +95,6 @@ namespace FirstPlugin.LuigisMansion.DarkMoon reader.ReadByte(); //Padding uint FileCount = reader.ReadUInt32(); uint LargestCompressedFile = reader.ReadUInt32(); - reader.SeekBegin(0x2C); byte[] Unknowns = reader.ReadBytes((int)FileCount); diff --git a/File_Format_Library/FileFormats/Archives/LM3/LM3_ChunkTable.cs b/File_Format_Library/FileFormats/Archives/LM3/LM3_ChunkTable.cs new file mode 100644 index 00000000..94e72764 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/LM3/LM3_ChunkTable.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Toolbox.Library.IO; + +namespace FirstPlugin.LuigisMansion3 +{ + public class ChunkEntry + { + public uint Unknown1; + public uint ChunkOffset; + public DataType ChunkType; + public uint ChunkSubCount; + public uint Unknown3; + } + + public class ChunkSubEntry + { + public SubDataType ChunkType; + public uint ChunkSize; + public uint ChunkOffset; + } + + //Table consists of 2 chunk entry lists that define how the .data reads sections + public class LM3_ChunkTable + { + private const int ChunkInfoIdenfier = 0x2001301; + + //I am uncertain how these chunk lists work. There is first a list with an identifier and one extra unknown + //The second list can contain the same entries as the other list, however it may include more chunks + //Example, the first list may have image headers, while the second include both image headers and image blocks + public List ChunkEntries = new List(); + public List ChunkSubEntries = new List(); + + public void Read(FileReader tableReader) + { + tableReader.SetByteOrder(false); + + //Read to the end of the file as the rest of the table are types, offsets, and an unknown value + while (!tableReader.EndOfStream && tableReader.Position <= tableReader.BaseStream.Length - 12) + { + ChunkSubEntry subEntry = new ChunkSubEntry(); + subEntry.ChunkType = tableReader.ReadEnum(false); //The type of chunk. 0x8701B5 for example for texture info + tableReader.ReadUInt16(); + subEntry.ChunkSize = tableReader.ReadUInt32(); + subEntry.ChunkOffset = tableReader.ReadUInt32(); + ChunkSubEntries.Add(subEntry); + } + } + } +} diff --git a/File_Format_Library/FileFormats/Archives/LM3/LM3_DICT.cs b/File_Format_Library/FileFormats/Archives/LM3/LM3_DICT.cs new file mode 100644 index 00000000..de878239 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/LM3/LM3_DICT.cs @@ -0,0 +1,530 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Toolbox; +using System.Windows.Forms; +using Toolbox.Library; +using Toolbox.Library.IO; +using Toolbox.Library.Forms; +using System.Drawing; + +namespace FirstPlugin.LuigisMansion3 +{ + //Parse info based on https://github.com/TheFearsomeDzeraora/LM3L + public class LM3_DICT : TreeNodeFile, IFileFormat + { + public FileType FileType { get; set; } = FileType.Archive; + + public bool CanSave { get; set; } + public string[] Description { get; set; } = new string[] { "Luigi's Mansion 3 Dictionary" }; + public string[] Extension { get; set; } = new string[] { "*.dict" }; + public string FileName { get; set; } + public string FilePath { get; set; } + public IFileInfo IFileInfo { get; set; } + + public bool CanAddFiles { get; set; } + public bool CanRenameFiles { get; set; } + public bool CanReplaceFiles { get; set; } + public bool CanDeleteFiles { get; set; } + + public bool Identify(System.IO.Stream stream) + { + using (var reader = new Toolbox.Library.IO.FileReader(stream, true)) + { + reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian; + if (reader.ReadUInt32() == 0x5824F3A9) + { + //This value seems consistant enough to tell apart from LM3 + reader.SeekBegin(12); + return reader.ReadUInt32() == 0x78340300; + } + + return false; + } + } + + public Type[] Types + { + get + { + List types = new List(); + return types.ToArray(); + } + } + + public override void OnAfterAdded() + { + if (!DrawablesLoaded) + { + ObjectEditor.AddContainer(DrawableContainer); + DrawablesLoaded = true; + } + } + + public static bool DebugMode = false; + + public List chunkEntries = new List(); + + public bool IsCompressed = false; + + public LM3_ChunkTable ChunkTable; + public List fileEntries = new List(); + + public LM3_Renderer Renderer; + public DrawableContainer DrawableContainer = new DrawableContainer(); + + STTextureFolder textureFolder = new STTextureFolder("Textures"); + LM3_ModelFolder modelFolder; + TreeNode materialNamesFolder = new TreeNode("Material Names"); + TreeNode chunkFolder = new TreeNode("Chunks"); + + public static Dictionary HashNames = new Dictionary(); + + private void LoadHashes() + { + /* foreach (string hashStr in Properties.Resources.LM3_Hashes.Split('\n')) + { + uint hash = Toolbox.Library.Security.Cryptography.Crc32.Compute(hashStr); + if (!HashNames.ContainsKey(hash)) + HashNames.Add(hash, hashStr); + + foreach (string pathStr in hashStr.Split('/')) + { + uint hash2 = Toolbox.Library.Security.Cryptography.Crc32.Compute(pathStr); + if (!HashNames.ContainsKey(hash2)) + HashNames.Add(hash2, pathStr); + } + }*/ + } + + public byte[] GetFileVertexData() + { + return fileEntries[60].GetData(); //Get the fourth file + } + + public bool DrawablesLoaded = false; + public void Load(System.IO.Stream stream) + { + LoadHashes(); + modelFolder = new LM3_ModelFolder(this); + DrawableContainer.Name = FileName; + Renderer = new LM3_Renderer(); + DrawableContainer.Drawables.Add(Renderer); + + Text = FileName; + + using (var reader = new FileReader(stream)) + { + reader.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian; + uint Identifier = reader.ReadUInt32(); + ushort Unknown = reader.ReadUInt16(); //Could also be 2 bytes, not sure. Always 0x0401 + IsCompressed = reader.ReadByte() == 1; + reader.ReadByte(); //Padding + uint unk = reader.ReadUInt32(); + uint unk2 = reader.ReadUInt32(); + + //Start of the chunk info. A fixed list of chunk information + + TreeNode chunkNodes = new TreeNode("Chunks Debug"); + + for (int i = 0; i < 52; i++) + { + ChunkInfo chunk = new ChunkInfo(); + chunk.Read(reader); + chunkNodes.Nodes.Add(chunk); + } + + TreeNode tableNodes = new TreeNode("File Section Entries"); + + if (DebugMode) + Nodes.Add(chunkNodes); + + Nodes.Add(tableNodes); + + var FileCount = 120; + + long FileTablePos = reader.Position; + for (int i = 0; i < FileCount; i++) + { + var file = new FileEntry(this); + file.Read(reader); + fileEntries.Add(file); + + if (file.DecompressedSize > 0) + { + file.Text = $"entry {i}"; + tableNodes.Nodes.Add(file); + } + + //The first file stores a chunk layout + //The second one seems to be a duplicate? + if (i == 0) + { + using (var tableReader = new FileReader(file.GetData())) + { + ChunkTable = new LM3_ChunkTable(); + ChunkTable.Read(tableReader); + + if (DebugMode) + { + TreeNode debugFolder = new TreeNode("DEBUG TABLE INFO"); + Nodes.Add(debugFolder); + + TreeNode list1 = new TreeNode("Entry List 1"); + TreeNode list2 = new TreeNode("Entry List 2 "); + debugFolder.Nodes.Add(list1); + debugFolder.Nodes.Add(list2); + debugFolder.Nodes.Add(chunkFolder); + + foreach (var chunk in ChunkTable.ChunkEntries) + { + list1.Nodes.Add($"ChunkType {chunk.ChunkType} ChunkOffset {chunk.ChunkOffset} Unknown1 {chunk.Unknown1} ChunkSubCount {chunk.ChunkSubCount} Unknown3 {chunk.Unknown3}"); + } + foreach (var chunk in ChunkTable.ChunkSubEntries) + { + list2.Nodes.Add($"ChunkType 0x{chunk.ChunkType.ToString("X")} Size {chunk.ChunkSize} Offset {chunk.ChunkOffset}"); + } + } + } + } + } + + + //Model data block + //Contains texture hash refs and model headers + byte[] File052Data = fileEntries[52].GetData(); + + //Contains model data + byte[] File054Data = fileEntries[54].GetData(); + + //Image header block + byte[] File063Data = fileEntries[63].GetData(); + + //Image data block + byte[] File065Data = fileEntries[65].GetData(); + + //Set an instance of our current data + //Chunks are in order, so you build off of when an instance gets loaded + LM3_Model currentModel = new LM3_Model(this); + + TexturePOWE currentTexture = new TexturePOWE(); + + int chunkId = 0; + uint modelIndex = 0; + uint ImageHeaderIndex = 0; + foreach (var chunk in ChunkTable.ChunkSubEntries) + { + var chunkEntry = new ChunkDataEntry(this, chunk); + switch (chunk.ChunkType) + { + case SubDataType.TextureHeader: + chunkEntry.DataFile = File063Data; + + //Read the info + using (var textureReader = new FileReader(chunkEntry.FileData)) + { + currentTexture = new TexturePOWE(); + currentTexture.ImageKey = "texture"; + currentTexture.SelectedImageKey = currentTexture.ImageKey; + currentTexture.Index = ImageHeaderIndex; + currentTexture.Read(textureReader); + if (DebugMode) + currentTexture.Text = $"Texture {ImageHeaderIndex} {currentTexture.TexFormat.ToString("X")} {currentTexture.Unknown.ToString("X")}"; + else + currentTexture.Text = $"Texture {currentTexture.ID2.ToString("X")}"; + + if (HashNames.ContainsKey(currentTexture.ID2)) + currentTexture.Text = HashNames[currentTexture.ID2]; + textureFolder.Nodes.Add(currentTexture); + Renderer.TextureList.Add(currentTexture); + + ImageHeaderIndex++; + } + break; + case SubDataType.TextureData: + chunkEntry.DataFile = File065Data; + currentTexture.ImageData = chunkEntry.FileData; + break; + /* case SubDataType.ModelStart: + chunkEntry.DataFile = File052Data; + currentModel = new LM3_Model(this); + currentModel.ModelInfo = new LM3_ModelInfo(); + currentModel.Text = $"Model {modelIndex}"; + currentModel.ModelInfo.Data = chunkEntry.FileData; + modelFolder.Nodes.Add(currentModel); + modelIndex++; + break; + case SubDataType.MeshBuffers: + chunkEntry.DataFile = File054Data; + currentModel.BufferStart = chunkEntry.Entry.ChunkOffset; + currentModel.BufferSize = chunkEntry.Entry.ChunkSize; + break; + case SubDataType.VertexStartPointers: + chunkEntry.DataFile = File052Data; + using (var vtxPtrReader = new FileReader(chunkEntry.FileData)) + { + while (!vtxPtrReader.EndOfStream) + currentModel.VertexBufferPointers.Add(vtxPtrReader.ReadUInt32()); + } + break; + case SubDataType.SubmeshInfo: + chunkEntry.DataFile = File052Data; + int MeshCount = chunkEntry.FileData.Length / 0x28; + using (var meshReader = new FileReader(chunkEntry.FileData)) + { + for (uint i = 0; i < MeshCount; i++) + { + LM3_Mesh mesh = new LM3_Mesh(); + mesh.Read(meshReader); + currentModel.Meshes.Add(mesh); + } + } + currentModel.ModelInfo.Read(new FileReader(currentModel.ModelInfo.Data), currentModel.Meshes); + break; + case SubDataType.ModelTransform: + chunkEntry.DataFile = File052Data; + using (var transformReader = new FileReader(chunkEntry.FileData)) + { + //This is possibly very wrong + //The data isn't always per mesh, but sometimes is + if (transformReader.BaseStream.Length / 0x40 == currentModel.Meshes.Count) + { + for (int i = 0; i < currentModel.Meshes.Count; i++) + currentModel.Meshes[i].Transform = transformReader.ReadMatrix4(); + } + } + break; + case SubDataType.MaterialName: + using (var matReader = new FileReader(chunkEntry.FileData)) + { + materialNamesFolder.Nodes.Add(matReader.ReadZeroTerminatedString()); + } + break;*/ + default: + chunkEntry.DataFile = File052Data; + break; + } + + chunkEntry.Text = $"{chunk.ChunkType.ToString("X")} {chunk.ChunkType} {chunk.ChunkOffset} {chunk.ChunkSize}"; + chunkFolder.Nodes.Add(chunkEntry); + } + + if (textureFolder.Nodes.Count > 0) + Nodes.Add(textureFolder); + + foreach (LM3_Model model in modelFolder.Nodes) + { + model.ReadVertexBuffers(); + } + + if (modelFolder.Nodes.Count > 0) + Nodes.Add(modelFolder); + } + } + + public void Unload() + { + + } + + public void Save(System.IO.Stream stream) + { + } + + public bool AddFile(ArchiveFileInfo archiveFileInfo) + { + return false; + } + + public bool DeleteFile(ArchiveFileInfo archiveFileInfo) + { + return false; + } + + public class ChunkInfo : TreeNodeCustom + { + public string Type; + + public void Read(FileReader reader) + { + uint Unknown1 = reader.ReadUInt32(); + ushort Unknown2 = reader.ReadUInt16(); + ushort Unknown3 = reader.ReadUInt16(); + uint Unknown4 = reader.ReadUInt32(); + Type = reader.ReadString(3); + byte Unknown5 = reader.ReadByte(); + uint Unknown6 = reader.ReadUInt32(); + uint Unknown7 = reader.ReadUInt32(); + + Text = $" Type: [{Type}] [{Unknown1} {Unknown2} {Unknown3} {Unknown4} {Unknown5} {Unknown6}] "; + } + } + + public class ChunkDataEntry : TreeNodeFile, IContextMenuNode + { + public byte[] DataFile; + public LM3_DICT ParentDictionary { get; set; } + public ChunkSubEntry Entry; + + public ChunkDataEntry(LM3_DICT dict, ChunkSubEntry entry) + { + ParentDictionary = dict; + Entry = entry; + } + + public byte[] FileData + { + get + { + using (var reader = new FileReader(DataFile)) + { + reader.SeekBegin(Entry.ChunkOffset); + return reader.ReadBytes((int)Entry.ChunkSize); + } + } + } + + public ToolStripItem[] GetContextMenuItems() + { + List Items = new List(); + Items.Add(new STToolStipMenuItem("Export Raw Data", null, Export, Keys.Control | Keys.E)); + return Items.ToArray(); + } + + private void Export(object sender, EventArgs args) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.FileName = Text; + sfd.Filter = "Raw Data (*.*)|*.*"; + + if (sfd.ShowDialog() == DialogResult.OK) + { + System.IO.File.WriteAllBytes(sfd.FileName, FileData); + } + } + + public override void OnClick(TreeView treeView) + { + HexEditor editor = (HexEditor)LibraryGUI.GetActiveContent(typeof(HexEditor)); + if (editor == null) + { + editor = new HexEditor(); + LibraryGUI.LoadEditor(editor); + } + editor.Text = Text; + editor.Dock = DockStyle.Fill; + editor.LoadData(FileData); + } + } + + public class FileEntry : TreeNodeFile, IContextMenuNode + { + public LM3_DICT ParentDictionary { get; set; } + + public uint Offset; + public uint DecompressedSize; + public uint CompressedSize; + public ushort Unknown1; + public byte Unknown2; + public byte Unknown3; //Possibly the effect? 0 for image block, 1 for info + + public FileEntry(LM3_DICT dict) + { + ParentDictionary = dict; + } + + public void Read(FileReader reader) + { + Offset = reader.ReadUInt32(); + DecompressedSize = reader.ReadUInt32(); + CompressedSize = reader.ReadUInt32(); + Unknown1 = reader.ReadUInt16(); + Unknown2 = reader.ReadByte(); + Unknown3 = reader.ReadByte(); + } + + private bool IsTextureBinary() + { + byte[] Data = GetData(); + + if (Data.Length < 4) + return false; + + using (var reader = new FileReader(Data)) + { + return reader.ReadUInt32() == 0xE977D350; + } + } + + public ToolStripItem[] GetContextMenuItems() + { + List Items = new List(); + Items.Add(new STToolStipMenuItem("Export Raw Data", null, Export, Keys.Control | Keys.E)); + return Items.ToArray(); + } + + private void Export(object sender, EventArgs args) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.FileName = Text; + sfd.Filter = "Raw Data (*.*)|*.*"; + + if (sfd.ShowDialog() == DialogResult.OK) + { + System.IO.File.WriteAllBytes(sfd.FileName, GetData()); + } + } + + public override void OnClick(TreeView treeView) + { + HexEditor editor = (HexEditor)LibraryGUI.GetActiveContent(typeof(HexEditor)); + if (editor == null) + { + editor = new HexEditor(); + LibraryGUI.LoadEditor(editor); + } + editor.Text = Text; + editor.Dock = DockStyle.Fill; + editor.LoadData(GetData()); + } + + public byte[] GetData() + { + byte[] Data = new byte[DecompressedSize]; + + string FolderPath = System.IO.Path.GetDirectoryName(ParentDictionary.FilePath); + string DataFile = System.IO.Path.Combine(FolderPath, $"{ParentDictionary.FileName.Replace(".dict", ".data")}"); + + if (System.IO.File.Exists(DataFile)) + { + using (var reader = new FileReader(DataFile)) + { + if (Offset > reader.BaseStream.Length) + return reader.ReadBytes((int)CompressedSize); + + reader.SeekBegin(Offset); + if (ParentDictionary.IsCompressed) + { + ushort Magic = reader.ReadUInt16(); + reader.SeekBegin(Offset); + + Data = reader.ReadBytes((int)CompressedSize); + if (Magic == 0x9C78 || Magic == 0xDA78) + return STLibraryCompression.ZLIB.Decompress(Data); + else //Unknown compression + return Data; + } + else + { + return reader.ReadBytes((int)DecompressedSize); + } + } + } + + return Data; + } + } + } +} diff --git a/File_Format_Library/FileFormats/Archives/LM3/LM3_Enums.cs b/File_Format_Library/FileFormats/Archives/LM3/LM3_Enums.cs new file mode 100644 index 00000000..d6cb13cc --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/LM3/LM3_Enums.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FirstPlugin.LuigisMansion3 +{ + public enum DataType : uint + { + Texture = 0x8701B500, + } + + public enum VertexDataFormat + { + Float16, + Float32, + Float32_32, + Float32_32_32, + } + + public enum IndexFormat : ushort + { + Index_16 = 0x0, + Index_8 = 0x8000, + } + + public enum SubDataType2 : uint + { + TextureHeader = 0x38C1B501, + TextureData = 0x5241B502, + + ModelStart = 0x1201B006, + SubmeshInfo = 0x1201B003, //Or polygon groups? + VertexStartPointers = 0x1201B004, + ModelTransform = 0x1301B001, //Matrix4x4. 0x40 in size + MeshBuffers = 0x1301B005, //vertex and index buffer + MaterialName = 0x1201B333, + MeshIndexTable = 0x1201B007, + MessageData = 0x12027020, + ShaderData = 0x1401B400, + UILayoutMagic = 0x92027000, + UILayoutHeader = 0x12027001, + UILayoutData = 0x12027002, //Without header + UILayout = 0x02027003, //All parts combined + } + + public enum SubDataType : ushort + { + TextureHeader = 0xB501, + TextureData = 0xB502, + ModelStart = 0xB006, + SubmeshInfo = 0xB003, //Or polygon groups? + VertexStartPointers = 0xB004, + ModelTransform = 0xB001, //Matrix4x4. 0x40 in size + MeshBuffers = 0xB005, //vertex and index buffer + MaterialName = 0xB333, + MeshIndexTable = 0xB007, + MessageData = 0x7020, + ShaderData = 0xB400, + UILayoutMagic = 0x7000, + UILayoutHeader = 0x7001, + UILayoutData = 0x7002, //Without header + UILayout = 0x7003, //All parts combined + } +} diff --git a/File_Format_Library/FileFormats/Archives/LM3/LM3_Material.cs b/File_Format_Library/FileFormats/Archives/LM3/LM3_Material.cs new file mode 100644 index 00000000..fe4de095 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/LM3/LM3_Material.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Toolbox.Library; + +namespace FirstPlugin.LuigisMansion3 +{ + public class LM3_Material : STGenericMaterial + { + + } +} diff --git a/File_Format_Library/FileFormats/Archives/LM3/LM3_Model.cs b/File_Format_Library/FileFormats/Archives/LM3/LM3_Model.cs new file mode 100644 index 00000000..cb845d09 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/LM3/LM3_Model.cs @@ -0,0 +1,556 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Toolbox.Library; +using Toolbox.Library.IO; +using Toolbox.Library.Rendering; +using Toolbox.Library.Forms; +using OpenTK; + +namespace FirstPlugin.LuigisMansion3 +{ + public class LM3_ModelFolder : TreeNodeCustom, IContextMenuNode + { + public LM3_DICT DataDictionary; + + public LM3_ModelFolder(LM3_DICT dict) + { + DataDictionary = dict; + Text = "Models"; + } + + public ToolStripItem[] GetContextMenuItems() + { + List Items = new List(); + Items.Add(new ToolStripMenuItem("Export All", null, ExportModelAction, Keys.Control | Keys.E)); + return Items.ToArray(); + } + + private void ExportModelAction(object sender, EventArgs args) + { + ExportModel(); + } + + private void ExportModel() + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.Filter = "Supported Formats|*.dae;"; + if (sfd.ShowDialog() == DialogResult.OK) + { + ExportModel(sfd.FileName); + } + } + + private void ExportModel(string FileName) + { + AssimpSaver assimp = new AssimpSaver(); + ExportModelSettings settings = new ExportModelSettings(); + + List Materials = new List(); + // foreach (var msh in DataDictionary.Renderer.Meshes) + // Materials.Add(msh.GetMaterial()); + + var model = new STGenericModel(); + model.Materials = Materials; + model.Objects = DataDictionary.Renderer.Meshes; + + assimp.SaveFromModel(model, FileName, new List(), new STSkeleton()); + } + } + + public class LM3_Model : TreeNodeCustom, IContextMenuNode + { + public LM3_DICT DataDictionary; + public LM3_ModelInfo ModelInfo; + public List Meshes = new List(); + public List VertexBufferPointers = new List(); + + public uint BufferStart; + public uint BufferSize; + + private List RenderedMeshes = new List(); + + Viewport viewport + { + get + { + var editor = LibraryGUI.GetObjectEditor(); + return editor.GetViewport(); + } + set + { + var editor = LibraryGUI.GetObjectEditor(); + editor.LoadViewport(value); + } + } + + public override void OnClick(TreeView treeView) + { + if (Runtime.UseOpenGL) + { + if (viewport == null) + { + viewport = new Viewport(ObjectEditor.GetDrawableContainers()); + viewport.Dock = DockStyle.Fill; + } + + viewport.ReloadDrawables(DataDictionary.DrawableContainer); + LibraryGUI.LoadEditor(viewport); + + viewport.Text = Text; + } + } + + public ToolStripItem[] GetContextMenuItems() + { + List Items = new List(); + Items.Add(new ToolStripMenuItem("Export", null, ExportModelAction, Keys.Control | Keys.E)); + return Items.ToArray(); + } + + private void ExportModelAction(object sender, EventArgs args) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.Filter = "Supported Formats|*.dae;"; + if (sfd.ShowDialog() == DialogResult.OK) + { + ExportModel(sfd.FileName); + } + } + + private void ExportModel(string FileName) + { + AssimpSaver assimp = new AssimpSaver(); + ExportModelSettings settings = new ExportModelSettings(); + + List Materials = new List(); + // foreach (var msh in DataDictionary.Renderer.Meshes) + // Materials.Add(msh.GetMaterial()); + + var model = new STGenericModel(); + model.Materials = Materials; + model.Objects = RenderedMeshes; + + assimp.SaveFromModel(model, FileName, new List(), new STSkeleton()); + } + + + public LM3_Model(LM3_DICT dict) + { + DataDictionary = dict; + } + + public void OnPropertyChanged() { } + + public void ReadVertexBuffers() + { + Nodes.Clear(); + + using (var reader = new FileReader(DataDictionary.GetFileVertexData())) + { + for (int i = 0; i < Meshes.Count; i++) + { + LM3_Mesh mesh = Meshes[i]; + + RenderableMeshWrapper genericObj = new RenderableMeshWrapper(); + genericObj.Mesh = mesh; + genericObj.Text = $"Mesh {i}"; + genericObj.SetMaterial(mesh.Material); + RenderedMeshes.Add(genericObj); + + Nodes.Add(genericObj); + DataDictionary.Renderer.Meshes.Add(genericObj); + + STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); + genericObj.PolygonGroups.Add(polyGroup); + + using (reader.TemporarySeek(BufferStart + VertexBufferPointers[i], System.IO.SeekOrigin.Begin)) + { + var bufferNodeDebug = new DebugVisualBytes(reader.ReadBytes((int)80 * mesh.VertexCount)); + bufferNodeDebug.Text = $"Buffer {mesh.DataFormat.ToString("x")}"; + genericObj.Nodes.Add(bufferNodeDebug); + } + + if (!LM3_Mesh.FormatInfos.ContainsKey(mesh.DataFormat)) + { + Console.WriteLine($"Unsupported data format! " + mesh.DataFormat.ToString("x")); + continue; + } + else + { + var formatInfo = LM3_Mesh.FormatInfos[mesh.DataFormat]; + if (formatInfo.BufferLength > 0) + { + reader.BaseStream.Position = BufferStart + mesh.IndexStartOffset; + switch (mesh.IndexFormat) + { + case IndexFormat.Index_8: + for (int f = 0; f < mesh.IndexCount; f++) + polyGroup.faces.Add(reader.ReadByte()); + break; + case IndexFormat.Index_16: + for (int f = 0; f < mesh.IndexCount; f++) + polyGroup.faces.Add(reader.ReadUInt16()); + break; + } + + Console.WriteLine($"Mesh {genericObj.Text} Format {formatInfo.Format} BufferLength {formatInfo.BufferLength}"); + + uint bufferOffet = BufferStart + VertexBufferPointers[i]; + /* for (int v = 0; v < mesh.VertexCount; v++) + { + reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); + + }*/ + + switch (formatInfo.Format) + { + case VertexDataFormat.Float16: + for (int v = 0; v < mesh.VertexCount; v++) + { + reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); + + Vertex vert = new Vertex(); + genericObj.vertices.Add(vert); + vert.pos = new Vector3( + UShortToFloatDecode(reader.ReadInt16()), + UShortToFloatDecode(reader.ReadInt16()), + UShortToFloatDecode(reader.ReadInt16())); + + Vector4 nrm = Read_8_8_8_8_Snorm(reader); + vert.nrm = nrm.Xyz.Normalized(); + + vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); + vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); + + if (formatInfo.BufferLength == 22) + { + Console.WriteLine("unk 1 " + reader.ReadUInt16()); + Console.WriteLine("unk 2 " + reader.ReadUInt16()); + Console.WriteLine("unk 3 " + reader.ReadUInt16()); + Console.WriteLine("unk 4 " + reader.ReadUInt16()); + } + } + break; + case VertexDataFormat.Float32: + for (int v = 0; v < mesh.VertexCount; v++) + { + reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); + + Vertex vert = new Vertex(); + genericObj.vertices.Add(vert); + + vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); + } + break; + case VertexDataFormat.Float32_32: + reader.BaseStream.Position = BufferStart + VertexBufferPointers[i] + 0x08; + for (int v = 0; v < mesh.VertexCount; v++) + { + reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); + + Vertex vert = new Vertex(); + genericObj.vertices.Add(vert); + + vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); + vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); + vert.uv1 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); + vert.col = Read_8_8_8_8_Unorm(reader); + } + break; + case VertexDataFormat.Float32_32_32: + for (int v = 0; v < mesh.VertexCount; v++) + { + reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); + + Vertex vert = new Vertex(); + genericObj.vertices.Add(vert); + + vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); + Vector4 nrm = Read_8_8_8_8_Snorm(reader); + vert.nrm = nrm.Xyz.Normalized(); + vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); + vert.uv1 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); + + if (formatInfo.BufferLength >= 0x1C) + vert.col = Read_8_8_8_8_Unorm(reader); + } + break; + } + + genericObj.TransformPosition(new Vector3(0), new Vector3(-90, 0, 0), new Vector3(1)); + } + } + + genericObj.RemoveDuplicateVertices(); + } + } + } + + public static Vector4 Read_8_8_8_8_Snorm(FileReader reader) + { + return new Vector4(reader.ReadSByte() / 255f, reader.ReadSByte() / 255f, reader.ReadSByte() / 255f, reader.ReadSByte() / 255f); + } + + public static Vector4 Read_8_8_8_8_Unorm(FileReader reader) + { + return new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f); + } + + public static Vector3 Read_8_8_8_Unorm(FileReader reader) + { + return new Vector3(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f ); + } + + public static Vector2 NormalizeUvCoordsToFloat(ushort U, ushort V) + { + return new Vector2( U / 1024f, V / 1024f); + } + + public static float UShortToFloatDecode(short input) + { + float fraction = (float)BitConverter.GetBytes(input)[0] / (float)256; + sbyte integer = (sbyte)BitConverter.GetBytes(input)[1]; + return integer + fraction; + } + } + + public class LM3_ModelInfo + { + public byte[] Data; + + public void Read(FileReader reader, List Meshes) + { + // This is very dumb. Just look and try to find the mesh hash and get the texture after + int pos = 0; + //todo LM3 doesn't work right with this + /* while (!reader.EndOfStream && reader.Position < reader.BaseStream.Length - 5) + { + reader.Position = pos++; + uint HashIDCheck = reader.ReadUInt32(); + for (int i = 0; i < Meshes.Count; i++) + { + if (Meshes[i].HashID == HashIDCheck) + { + uint TextureHashID = reader.ReadUInt32(); + + Meshes[i].Material = new LM3_Material(); + var texUnit = 1; + Meshes[i].Material.TextureMaps.Add(new STGenericMatTexture() + { + textureUnit = texUnit++, + Type = STGenericMatTexture.TextureType.Diffuse, + Name = TextureHashID.ToString("x"), + }); + } + } + }*/ + + /* + for (int i = 0; i < Meshes.Count; i++) + { + //This section keeps varing so just search for mesh hash id and get texture hash after it + + + uint Unknown = reader.ReadUInt32(); //A81E313F + reader.Seek(40); + + //Not sure what this is. Not a transform as the UVs seem fine as is + float[] Unknown2 = reader.ReadSingles(5); //0.5, 1, 0.5,0.5, 1 + reader.Seek(4); //Padding + uint MeshHashID = reader.ReadUInt32(); + uint TextureHashID = reader.ReadUInt32(); + uint UnknownHashID = reader.ReadUInt32(); //Material hash?? + + //Go through each mesh and find a matching hash + for (int m = 0; m < Meshes.Count; m++) + { + if (Meshes[m].HashID == MeshHashID) + { + + } + }; + + if (i != Meshes.Count - 1) + reader.Seek(4); //padding on all but last entry + }*/ + } + } + + public class RenderableMeshWrapper : GenericRenderedObject + { + public LM3_Mesh Mesh { get; set; } + + LM3_Material material; + + public override STGenericMaterial GetMaterial() + { + return material; + } + + public void SetMaterial(LM3_Material mat) + { + material = mat; + } + + public override void OnClick(TreeView treeView) + { + STPropertyGrid editor = (STPropertyGrid)LibraryGUI.GetActiveContent(typeof(STPropertyGrid)); + if (editor == null) + { + editor = new STPropertyGrid(); + LibraryGUI.LoadEditor(editor); + } + editor.Text = Text; + editor.Dock = DockStyle.Fill; + editor.LoadProperty(Mesh, OnPropertyChanged); + } + + public void OnPropertyChanged() { } + } + + + public class DebugVisualBytes : TreeNodeFile, IContextMenuNode + { + public byte[] Data; + + public DebugVisualBytes(byte[] bytes) + { + Data = bytes; + } + + public override void OnClick(TreeView treeView) + { + HexEditor editor = (HexEditor)LibraryGUI.GetActiveContent(typeof(HexEditor)); + if (editor == null) + { + editor = new HexEditor(); + LibraryGUI.LoadEditor(editor); + } + editor.Text = Text; + editor.Dock = DockStyle.Fill; + editor.LoadData(Data); + } + + public ToolStripItem[] GetContextMenuItems() + { + List Items = new List(); + Items.Add(new STToolStipMenuItem("Export Raw Data", null, Export, Keys.Control | Keys.E)); + return Items.ToArray(); + } + + private void Export(object sender, EventArgs args) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.FileName = Text; + sfd.Filter = "Raw Data (*.*)|*.*"; + + if (sfd.ShowDialog() == DialogResult.OK) + { + System.IO.File.WriteAllBytes(sfd.FileName, Data); + } + } + } + + public class LM3_IndexList + { + public short[] UnknownIndices { get; set; } + + public uint Unknown { get; set; } + + public short[] UnknownIndices2 { get; set; } + + public uint[] Unknown2 { get; set; } + + public void Read(FileReader reader) + { + UnknownIndices = reader.ReadInt16s(4); + Unknown = reader.ReadUInt32(); + UnknownIndices2 = reader.ReadInt16s(8); + Unknown2 = reader.ReadUInt32s(6); //Increases by 32 each entry + } + } + + public class LM3_Mesh + { + public uint IndexStartOffset { get; private set; } //relative to buffer start + public ushort IndexCount { get; private set; } //divide by 3 to get face count + public IndexFormat IndexFormat { get; private set; } //0x0 - ushort, 0x8000 - byte + + public ushort BufferPtrOffset { get; private set; } + public ushort Unknown { get; private set; } + public ulong DataFormat { get; private set; } + public uint Unknown2 { get; private set; } + public uint Unknown3 { get; private set; } + public uint Unknown4 { get; private set; } //Increases after each mesh. Always 0 for the first mesh (some sort of offset)? + public ushort VertexCount { get; private set; } + public ushort Unknown7 { get; private set; } //Always 256? + public uint HashID { get; private set; } + + public LM3_Material Material { get; set; } + + public Matrix4 Transform { get; set; } = Matrix4.Identity; + + public void Read(FileReader reader) + { + Material = new LM3_Material(); + + IndexStartOffset = reader.ReadUInt32(); + IndexCount = reader.ReadUInt16(); + IndexFormat = reader.ReadEnum(false); + IndexFormat = IndexFormat.Index_16; + BufferPtrOffset = reader.ReadUInt16(); //I believe this might be for the buffer pointers. It shifts by 4 for each mesh + Unknown = reader.ReadUInt16(); + DataFormat = reader.ReadUInt64(); + Unknown2 = reader.ReadUInt32(); + Unknown3 = reader.ReadUInt32(); + Unknown4 = reader.ReadUInt32(); + VertexCount = reader.ReadUInt16(); + Unknown7 = reader.ReadUInt16(); //0x100 + HashID = reader.ReadUInt32(); //0x100 + } + + public class FormatInfo + { + public VertexDataFormat Format { get; set; } + public uint BufferLength { get; set; } + + public FormatInfo(VertexDataFormat format, uint length) + { + Format = format; + BufferLength = length; + } + } + + //Formats are based on https://github.com/TheFearsomeDzeraora/LM3L/blob/master/ModelThingy.cs#L639 + //These may not be very accurate, i need to look more into these + public static Dictionary FormatInfos = new Dictionary() + { + { 0x6350379972D28D0D, new FormatInfo(VertexDataFormat.Float16, 0x46)}, + { 0xDC0291B311E26127, new FormatInfo(VertexDataFormat.Float16, 0x16)}, + { 0x93359708679BEB7C, new FormatInfo(VertexDataFormat.Float16, 0x16)}, + { 0x1A833CEEC88C1762, new FormatInfo(VertexDataFormat.Float16, 0x46)}, + { 0xD81AC10B8980687F, new FormatInfo(VertexDataFormat.Float16, 0x16)}, + { 0x2AA2C56A0FFA5BDE, new FormatInfo(VertexDataFormat.Float16, 0x1A)}, + { 0x5D6C62BAB3F4492E, new FormatInfo(VertexDataFormat.Float16, 0x16)}, + { 0x3CC7AB6B4821B2DF, new FormatInfo(VertexDataFormat.Float32, 0x14)}, + { 0x408E2B1F5576A693, new FormatInfo(VertexDataFormat.Float32_32_32, 0x10)}, + { 0x0B663399DF24890D, new FormatInfo(VertexDataFormat.Float32_32_32, 0x18)}, + { 0x7EB9853DF4F13EB1, new FormatInfo(VertexDataFormat.Float32_32_32, 0x18)}, + { 0x314A20AEFADABB22, new FormatInfo(VertexDataFormat.Float32_32_32, 0x18)}, + { 0x0F3F68A287C2B716, new FormatInfo(VertexDataFormat.Float32_32_32, 0x18)}, + { 0x27F993771090E6EB, new FormatInfo(VertexDataFormat.Float32_32_32, 0x1C)}, + { 0x4E315C83A856FBF7, new FormatInfo(VertexDataFormat.Float32_32_32, 0x1C)}, + { 0xBD15F722F07FC596, new FormatInfo(VertexDataFormat.Float32_32_32, 0x1C)}, + { 0xFBACD243DDCC31B7, new FormatInfo(VertexDataFormat.Float32_32_32, 0x1C)}, + { 0x8A4CC565333626D9, new FormatInfo(VertexDataFormat.Float32_32, 0x18)}, + { 0x8B8CE58EAA846002, new FormatInfo(VertexDataFormat.Float32_32_32, 0x14)}, + }; + } +} diff --git a/File_Format_Library/FileFormats/Archives/LM3/TexturePOWE.cs b/File_Format_Library/FileFormats/Archives/LM3/TexturePOWE.cs new file mode 100644 index 00000000..a2fb5299 --- /dev/null +++ b/File_Format_Library/FileFormats/Archives/LM3/TexturePOWE.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Drawing; +using System.ComponentModel; +using Toolbox.Library; +using Toolbox.Library.IO; +using Toolbox.Library.Forms; +using System.Windows.Forms; + +namespace FirstPlugin.LuigisMansion3 +{ + public class TexturePOWE : STGenericTexture + { + public static readonly uint Identifier = 0xE977D350; + + public uint Index { get; set; } + + public uint ID { get; set; } + public uint ID2 { get; set; } + + public byte[] ImageData { get; set; } + + private POWEProperties properties; + + public class POWEProperties + { + [Browsable(false)] + public uint ID { get; set; } + + public string HashID + { + get + { + return ID.ToString("x"); + } + } + + [ReadOnly(true)] + public uint Width { get; set; } + [ReadOnly(true)] + public uint Height { get; set; } + [ReadOnly(true)] + public byte NumMips { get; set; } + [ReadOnly(true)] + public TEX_FORMAT Format { get; set; } + + } + + public Dictionary FormatTable = new Dictionary() + { + { 0x00, TEX_FORMAT.R8G8B8A8_UNORM }, + { 0x01, TEX_FORMAT.R8G8B8A8_UNORM_SRGB }, + { 0x11, TEX_FORMAT.BC1_UNORM }, + { 0x12, TEX_FORMAT.BC1_UNORM_SRGB }, + { 0x13, TEX_FORMAT.BC2_UNORM }, + { 0x14, TEX_FORMAT.BC3_UNORM }, + { 0x15, TEX_FORMAT.BC4_UNORM }, + { 0x16, TEX_FORMAT.BC5_SNORM }, + { 0x17, TEX_FORMAT.BC6H_UF16 }, + { 0x18, TEX_FORMAT.BC7_UNORM }, + { 0x19, TEX_FORMAT.ASTC_4x4_UNORM }, + { 0x1A, TEX_FORMAT.ASTC_5x4_UNORM }, + { 0x1B, TEX_FORMAT.ASTC_5x5_UNORM }, + { 0x1C, TEX_FORMAT.ASTC_6x5_UNORM }, + { 0x1D, TEX_FORMAT.ASTC_6x6_UNORM }, + { 0x1E, TEX_FORMAT.ASTC_8x5_UNORM }, + { 0x1F, TEX_FORMAT.ASTC_8x6_UNORM }, + { 0x20, TEX_FORMAT.ASTC_8x8_UNORM }, + }; + + public byte TexFormat; + public uint Unknown; + + public void Read(FileReader reader) + { + //Magic and ID not pointed to for sub entries so just skip them for now + // uint magic = reader.ReadUInt32(); + // if (magic != Identifier) + // throw new Exception($"Invalid texture header magic! Expected {Identifier.ToString("x")}. Got {Identifier.ToString("x")}"); + // ID = reader.ReadUInt32(); + + ID2 = reader.ReadUInt32(); + Width = reader.ReadUInt16(); + Height = reader.ReadUInt16(); + var numMips = reader.ReadByte(); + var unk = reader.ReadByte(); //padding? + var numArray = reader.ReadByte(); + var unk2 = reader.ReadByte(); + TexFormat = reader.ReadByte(); + var unk3 = reader.ReadByte(); + Unknown = reader.ReadUInt16(); + + if (FormatTable.ContainsKey(TexFormat)) + Format = FormatTable[TexFormat]; + else + { + Format = TEX_FORMAT.ASTC_8x8_UNORM; + Console.WriteLine("Unknown Format!" + TexFormat.ToString("X")); + } + + MipCount = 1; + ArrayCount = numArray; + + properties = new POWEProperties(); + properties.ID = ID2; + properties.Width = Width; + properties.Height = Height; + properties.NumMips = numMips; + properties.Format = Format; + } + + + + public override void OnClick(TreeView treeview) + { + ImageEditorBase editor = (ImageEditorBase)LibraryGUI.GetActiveContent(typeof(ImageEditorBase)); + if (editor == null) + { + editor = new ImageEditorBase(); + editor.Dock = DockStyle.Fill; + + LibraryGUI.LoadEditor(editor); + } + editor.Text = Text; + editor.LoadProperties(properties); + editor.LoadImage(this); + } + + public override bool CanEdit { get; set; } = false; + + public override void SetImageData(Bitmap bitmap, int ArrayLevel) + { + throw new NotImplementedException(); + } + + public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0) + { + uint blkHeight = STGenericTexture.GetBlockHeight(Format); + uint blkDepth = STGenericTexture.GetBlockDepth(Format); + uint blockHeight = TegraX1Swizzle.GetBlockHeight(TegraX1Swizzle.DIV_ROUND_UP(Height, blkHeight)); + uint BlockHeightLog2 = (uint)Convert.ToString(blockHeight, 2).Length; + + if (Format == TEX_FORMAT.ASTC_6x6_UNORM) + BlockHeightLog2 -= 1; + + Console.WriteLine("blkHeight " + blkHeight); + Console.WriteLine("blockHeight " + blockHeight); + Console.WriteLine("BlockHeightLog2 " + BlockHeightLog2); + + return TegraX1Swizzle.GetImageData(this, ImageData, ArrayLevel, MipLevel, BlockHeightLog2, 1); + } + + public override TEX_FORMAT[] SupportedFormats + { + get + { + return new TEX_FORMAT[] + { + TEX_FORMAT.B5G6R5_UNORM, + TEX_FORMAT.R8G8_UNORM, + TEX_FORMAT.B5G5R5A1_UNORM, + TEX_FORMAT.B4G4R4A4_UNORM, + TEX_FORMAT.LA8, + TEX_FORMAT.HIL08, + TEX_FORMAT.L8, + TEX_FORMAT.A8_UNORM, + TEX_FORMAT.LA4, + TEX_FORMAT.A4, + TEX_FORMAT.ETC1_UNORM, + TEX_FORMAT.ETC1_A4, + }; + } + } + } +} diff --git a/File_Format_Library/GL/LM3_Renderer.cs b/File_Format_Library/GL/LM3_Renderer.cs new file mode 100644 index 00000000..5fc155f2 --- /dev/null +++ b/File_Format_Library/GL/LM3_Renderer.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Toolbox.Library.Rendering; +using GL_EditorFramework.GL_Core; +using GL_EditorFramework.Interfaces; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using Toolbox.Library; + +namespace FirstPlugin.LuigisMansion3 +{ + public class LM3_Renderer : GenericModelRenderer + { + public List TextureList = new List(); + + public override void OnRender(GLControl control) + { + + } + + public override int BindTexture(STGenericMatTexture tex, ShaderProgram shader) + { + GL.ActiveTexture(TextureUnit.Texture0 + tex.textureUnit + 1); + GL.BindTexture(TextureTarget.Texture2D, RenderTools.defaultTex.RenderableTex.TexID); + + string activeTex = tex.Name; + + foreach (var texture in TextureList) + { + if (texture.ID2.ToString("x") == tex.Name) + { + BindGLTexture(tex, shader, texture); + return tex.textureUnit + 1; + } + } + + return tex.textureUnit + 1; + } + } +} diff --git a/File_Format_Library/Main.cs b/File_Format_Library/Main.cs index 0402d5d1..53653f05 100644 --- a/File_Format_Library/Main.cs +++ b/File_Format_Library/Main.cs @@ -8,6 +8,7 @@ using Toolbox.Library.Forms; using Toolbox.Library.IO; using FirstPlugin.Forms; using FirstPlugin.LuigisMansion.DarkMoon; +using FirstPlugin.LuigisMansion3; namespace FirstPlugin { @@ -352,6 +353,7 @@ namespace FirstPlugin Formats.Add(typeof(NCA)); Formats.Add(typeof(RARC)); Formats.Add(typeof(ME01)); + Formats.Add(typeof(LM3_DICT)); Formats.Add(typeof(LM2_DICT)); Formats.Add(typeof(GMX)); Formats.Add(typeof(BMD));