Add WIP LM3 .dict/.data support.

LM3 uses an updated format of LM2 Darkmoon. Currently this can now load textures.  Most of them should load fine, however ASTC_6x6 textures keep giving me issues so those may or may not error out.
Models for LM3 is currently not supported due to the various changes but is planned.
This commit is contained in:
KillzXGaming 2019-10-27 10:48:16 -04:00
parent 8939687f6a
commit ee184391fb
9 changed files with 1440 additions and 1 deletions

View file

@ -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);

View file

@ -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<ChunkEntry> ChunkEntries = new List<ChunkEntry>();
public List<ChunkSubEntry> ChunkSubEntries = new List<ChunkSubEntry>();
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<SubDataType>(false); //The type of chunk. 0x8701B5 for example for texture info
tableReader.ReadUInt16();
subEntry.ChunkSize = tableReader.ReadUInt32();
subEntry.ChunkOffset = tableReader.ReadUInt32();
ChunkSubEntries.Add(subEntry);
}
}
}
}

View file

@ -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<Type> types = new List<Type>();
return types.ToArray();
}
}
public override void OnAfterAdded()
{
if (!DrawablesLoaded)
{
ObjectEditor.AddContainer(DrawableContainer);
DrawablesLoaded = true;
}
}
public static bool DebugMode = false;
public List<ChunkDataEntry> chunkEntries = new List<ChunkDataEntry>();
public bool IsCompressed = false;
public LM3_ChunkTable ChunkTable;
public List<FileEntry> fileEntries = new List<FileEntry>();
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<uint, string> HashNames = new Dictionary<uint, string>();
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<ToolStripItem> Items = new List<ToolStripItem>();
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<ToolStripItem> Items = new List<ToolStripItem>();
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;
}
}
}
}

View file

@ -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
}
}

View file

@ -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
{
}
}

View file

@ -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<ToolStripItem> Items = new List<ToolStripItem>();
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<STGenericMaterial> Materials = new List<STGenericMaterial>();
// 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<STGenericTexture>(), new STSkeleton());
}
}
public class LM3_Model : TreeNodeCustom, IContextMenuNode
{
public LM3_DICT DataDictionary;
public LM3_ModelInfo ModelInfo;
public List<LM3_Mesh> Meshes = new List<LM3_Mesh>();
public List<uint> VertexBufferPointers = new List<uint>();
public uint BufferStart;
public uint BufferSize;
private List<RenderableMeshWrapper> RenderedMeshes = new List<RenderableMeshWrapper>();
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<ToolStripItem> Items = new List<ToolStripItem>();
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<STGenericMaterial> Materials = new List<STGenericMaterial>();
// 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<STGenericTexture>(), 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<LM3_Mesh> 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<ToolStripItem> Items = new List<ToolStripItem>();
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<IndexFormat>(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<ulong, FormatInfo> FormatInfos = new Dictionary<ulong, FormatInfo>()
{
{ 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)},
};
}
}

View file

@ -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<byte, TEX_FORMAT> FormatTable = new Dictionary<byte, TEX_FORMAT>()
{
{ 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,
};
}
}
}
}

View file

@ -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<TexturePOWE> TextureList = new List<TexturePOWE>();
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;
}
}
}

View file

@ -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));