Add support for Luigi's Mansion 3 Models

This commit is contained in:
KillzXGaming 2019-10-30 17:43:26 -04:00
parent ed961ef10a
commit 544a30639b
11 changed files with 459 additions and 245 deletions

View file

@ -22,7 +22,7 @@ namespace FirstPlugin.LuigisMansion.DarkMoon
public enum IndexFormat : ushort
{
Index_16 = 0x0,
Index_8 = 0x8000,
Index_8 = 0x8000,
}
public enum SubDataType : uint

View file

@ -9,7 +9,7 @@ namespace FirstPlugin.LuigisMansion3
{
public class ChunkEntry
{
public uint Unknown1;
public uint ChunkSize;
public uint ChunkOffset;
public DataType ChunkType;
public uint ChunkSubCount;
@ -26,7 +26,7 @@ namespace FirstPlugin.LuigisMansion3
//Table consists of 2 chunk entry lists that define how the .data reads sections
public class LM3_ChunkTable
{
private const int ChunkInfoIdenfier = 0x2001301;
private const short ChunkInfoIdenfier = 0x1301;
//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
@ -38,7 +38,40 @@ namespace FirstPlugin.LuigisMansion3
{
tableReader.SetByteOrder(false);
//Read to the end of the file as the rest of the table are types, offsets, and an unknown value
//Load the first chunk table
//These point to sections which usually have magic and a hash
//The chunk table afterwards contains the data itself
while (tableReader.ReadUInt16() == ChunkInfoIdenfier)
{
tableReader.ReadUInt16();
ChunkEntry entry = new ChunkEntry();
ChunkEntries.Add(entry);
entry.ChunkSize = tableReader.ReadUInt32(); //Always 8
entry.ChunkOffset = tableReader.ReadUInt32(); //The chunk offset in the file. Relative to the first chunk position in the file
entry.ChunkType = tableReader.ReadEnum<DataType>(false); //The type of chunk. 0x8701B5 for example for texture info
entry.ChunkSubCount = tableReader.ReadByte(); //Uncertain about this. 2 for textures (info + block). Some sections however use large numbers.
tableReader.ReadByte();
tableReader.ReadByte();
tableReader.ReadByte();
//This increases by 2 each chunk info, however the starting value is not 0
//Note the last entry does not have this
entry.Unknown3 = tableReader.ReadUInt32();
Console.WriteLine("ChunkOffset " + entry.ChunkOffset);
}
if (ChunkEntries.Count > 0)
ChunkEntries.LastOrDefault<ChunkEntry>().Unknown3 = 0;
tableReader.Position -= 2; //Seek 4 back as the last entry lacks unkown 4
//Check the chunk types
//This time it includes more chunks (like image blocks)
//Read to the end of the file as the rest of the table are types, offsets, and sizes
while (!tableReader.EndOfStream && tableReader.Position <= tableReader.BaseStream.Length - 12)
{
ChunkSubEntry subEntry = new ChunkSubEntry();

View file

@ -13,7 +13,7 @@ using System.Drawing;
namespace FirstPlugin.LuigisMansion3
{
//Parse info based on https://github.com/TheFearsomeDzeraora/LM3L
public class LM3_DICT : TreeNodeFile, IFileFormat
public class LM3_DICT : TreeNodeFile, IFileFormat, ILeaveOpenOnLoad
{
public FileType FileType { get; set; } = FileType.Archive;
@ -63,7 +63,7 @@ namespace FirstPlugin.LuigisMansion3
}
}
public static bool DebugMode = false;
public static bool DebugMode = true;
public List<ChunkDataEntry> chunkEntries = new List<ChunkDataEntry>();
@ -84,22 +84,28 @@ namespace FirstPlugin.LuigisMansion3
private void LoadHashes()
{
/* foreach (string hashStr in Properties.Resources.LM3_Hashes.Split('\n'))
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);
//This hash txt includes a hash and then the string path after seperated by a comma
//The game does not store actual strings in the exefs so it's impossible to get the original names
//Instead I use a text file with user generated names based on the texture for easier searching
string[] hashes = hashStr.Split(',');
if (hashes.Length != 2) continue;
foreach (string pathStr in hashStr.Split('/'))
{
uint hash2 = Toolbox.Library.Security.Cryptography.Crc32.Compute(pathStr);
if (!HashNames.ContainsKey(hash2))
HashNames.Add(hash2, pathStr);
}
}*/
uint hash = 0;
uint.TryParse(hashes[0], System.Globalization.NumberStyles.HexNumber, null, out hash);
if (!HashNames.ContainsKey(hash))
HashNames.Add(hash, hashes[1]);
}
}
public byte[] GetFileVertexData()
public System.IO.Stream GetFileBufferData()
{
return fileEntries[54].GetData(); //Get the fourth file
}
public System.IO.Stream GetFileVertexData()
{
return fileEntries[60].GetData(); //Get the fourth file
}
@ -145,6 +151,9 @@ namespace FirstPlugin.LuigisMansion3
var FileCount = 120;
TreeNode chunkTexFolder = new TreeNode("Texture");
TreeNode chunkModelFolder = new TreeNode("Model");
long FileTablePos = reader.Position;
for (int i = 0; i < FileCount; i++)
{
@ -180,7 +189,7 @@ namespace FirstPlugin.LuigisMansion3
foreach (var chunk in ChunkTable.ChunkEntries)
{
list1.Nodes.Add($"ChunkType {chunk.ChunkType} ChunkOffset {chunk.ChunkOffset} Unknown1 {chunk.Unknown1} ChunkSubCount {chunk.ChunkSubCount} Unknown3 {chunk.Unknown3}");
list1.Nodes.Add($"ChunkType {chunk.ChunkType.ToString("X")} ChunkOffset {chunk.ChunkOffset} ChunkSize {chunk.ChunkSize} ChunkSubCount {chunk.ChunkSubCount} Unknown3 {chunk.Unknown3}");
}
foreach (var chunk in ChunkTable.ChunkSubEntries)
{
@ -194,22 +203,28 @@ namespace FirstPlugin.LuigisMansion3
//Model data block
//Contains texture hash refs and model headers
byte[] File052Data = fileEntries[52].GetData();
var File052Data = fileEntries[52].GetData();
//Unsure, layout data??
var File053Data = fileEntries[53].GetData();
//Contains model data
byte[] File054Data = fileEntries[54].GetData();
var File054Data = fileEntries[54].GetData();
//Image header block
byte[] File063Data = fileEntries[63].GetData();
//Image header block. Also contains shader data
var File063Data = fileEntries[63].GetData();
//Image data block
byte[] File065Data = fileEntries[65].GetData();
var 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();
ChunkDataEntry currentVertexPointerList = null;
List<uint> TextureHashes = new List<uint>();
int chunkId = 0;
uint modelIndex = 0;
@ -223,7 +238,7 @@ namespace FirstPlugin.LuigisMansion3
chunkEntry.DataFile = File063Data;
//Read the info
using (var textureReader = new FileReader(chunkEntry.FileData))
using (var textureReader = new FileReader(chunkEntry.FileData, true))
{
currentTexture = new TexturePOWE();
currentTexture.ImageKey = "texture";
@ -240,20 +255,21 @@ namespace FirstPlugin.LuigisMansion3
textureFolder.Nodes.Add(currentTexture);
Renderer.TextureList.Add(currentTexture);
TextureHashes.Add(currentTexture.ID2);
ImageHeaderIndex++;
}
break;
case SubDataType.TextureData:
chunkEntry.DataFile = File065Data;
currentTexture.ImageData = chunkEntry.FileData;
currentTexture.ImageData = chunkEntry.FileData.ToBytes();
break;
/* case SubDataType.ModelStart:
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);
currentModel.ModelInfo.Data = chunkEntry.FileData.ToBytes();
modelIndex++;
break;
case SubDataType.MeshBuffers:
@ -263,25 +279,31 @@ namespace FirstPlugin.LuigisMansion3
break;
case SubDataType.VertexStartPointers:
chunkEntry.DataFile = File052Data;
using (var vtxPtrReader = new FileReader(chunkEntry.FileData))
{
while (!vtxPtrReader.EndOfStream)
currentModel.VertexBufferPointers.Add(vtxPtrReader.ReadUInt32());
}
currentVertexPointerList = chunkEntry;
break;
case SubDataType.SubmeshInfo:
chunkEntry.DataFile = File052Data;
int MeshCount = chunkEntry.FileData.Length / 0x28;
int MeshCount = (int)chunkEntry.FileData.Length / 0x40;
using (var vtxPtrReader = new FileReader(currentVertexPointerList.FileData))
using (var meshReader = new FileReader(chunkEntry.FileData))
{
for (uint i = 0; i < MeshCount; i++)
{
meshReader.SeekBegin(i * 0x40);
LM3_Mesh mesh = new LM3_Mesh();
mesh.Read(meshReader);
currentModel.Meshes.Add(mesh);
Console.WriteLine($"mesh.Unknown3 {mesh.Unknown3 }");
var buffer = new LM3_Model.PointerInfo();
buffer.Read(vtxPtrReader, mesh.Unknown3 != 4294967295);
currentModel.VertexBufferPointers.Add(buffer);
}
}
currentModel.ModelInfo.Read(new FileReader(currentModel.ModelInfo.Data), currentModel.Meshes);
modelFolder.Nodes.Add(currentModel);
break;
case SubDataType.ModelTransform:
chunkEntry.DataFile = File052Data;
@ -297,28 +319,42 @@ namespace FirstPlugin.LuigisMansion3
}
break;
case SubDataType.MaterialName:
using (var matReader = new FileReader(chunkEntry.FileData))
chunkEntry.DataFile = File053Data;
/* using (var matReader = new FileReader(chunkEntry.FileData))
{
materialNamesFolder.Nodes.Add(matReader.ReadZeroTerminatedString());
}
break;*/
}*/
break;
case SubDataType.UILayoutMagic:
chunkEntry.DataFile = File053Data;
break;
case SubDataType.UILayout:
chunkEntry.DataFile = File053Data;
break;
default:
chunkEntry.DataFile = File052Data;
break;
}
chunkEntry.Text = $"{chunk.ChunkType.ToString("X")} {chunk.ChunkType} {chunk.ChunkOffset} {chunk.ChunkSize}";
chunkEntry.Text = $"{chunkId} {chunk.ChunkType.ToString("X")} {chunk.ChunkType} {chunk.ChunkOffset} {chunk.ChunkSize}";
chunkFolder.Nodes.Add(chunkEntry);
// if (currentModel != null && currentModel.Meshes?.Count > 0)
// break;
chunkId++;
}
foreach (var model in modelFolder.Nodes)
{
((LM3_Model)currentModel).ModelInfo.Read(new FileReader(
currentModel.ModelInfo.Data), currentModel.Meshes, TextureHashes);
}
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);
}
@ -331,6 +367,7 @@ namespace FirstPlugin.LuigisMansion3
public void Save(System.IO.Stream stream)
{
}
public bool AddFile(ArchiveFileInfo archiveFileInfo)
@ -364,7 +401,7 @@ namespace FirstPlugin.LuigisMansion3
public class ChunkDataEntry : TreeNodeFile, IContextMenuNode
{
public byte[] DataFile;
public System.IO.Stream DataFile;
public LM3_DICT ParentDictionary { get; set; }
public ChunkSubEntry Entry;
@ -374,15 +411,16 @@ namespace FirstPlugin.LuigisMansion3
Entry = entry;
}
public byte[] FileData
public System.IO.Stream FileData
{
get
{
using (var reader = new FileReader(DataFile))
{
reader.SeekBegin(Entry.ChunkOffset);
return reader.ReadBytes((int)Entry.ChunkSize);
}
if (Entry.ChunkSize == 0)
return new System.IO.MemoryStream();
else if (Entry.ChunkOffset + Entry.ChunkSize < DataFile?.Length)
return new SubStream(DataFile, Entry.ChunkOffset, Entry.ChunkSize);
else
return new System.IO.MemoryStream();
}
}
@ -400,9 +438,7 @@ namespace FirstPlugin.LuigisMansion3
sfd.Filter = "Raw Data (*.*)|*.*";
if (sfd.ShowDialog() == DialogResult.OK)
{
System.IO.File.WriteAllBytes(sfd.FileName, FileData);
}
FileData.ExportToFile(sfd.FileName);
}
public override void OnClick(TreeView treeView)
@ -447,12 +483,11 @@ namespace FirstPlugin.LuigisMansion3
private bool IsTextureBinary()
{
byte[] Data = GetData();
if (Data.Length < 4)
var stream = GetData();
if (stream.Length < 4)
return false;
using (var reader = new FileReader(Data))
using (var reader = new FileReader(stream, true))
{
return reader.ReadUInt32() == 0xE977D350;
}
@ -472,9 +507,7 @@ namespace FirstPlugin.LuigisMansion3
sfd.Filter = "Raw Data (*.*)|*.*";
if (sfd.ShowDialog() == DialogResult.OK)
{
System.IO.File.WriteAllBytes(sfd.FileName, GetData());
}
GetData().ExportToFile(sfd.FileName);
}
public override void OnClick(TreeView treeView)
@ -490,19 +523,19 @@ namespace FirstPlugin.LuigisMansion3
editor.LoadData(GetData());
}
public byte[] GetData()
public System.IO.Stream GetData()
{
byte[] Data = new byte[DecompressedSize];
System.IO.Stream Data = new System.IO.MemoryStream();
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))
using (var reader = new FileReader(DataFile, true))
{
if (Offset > reader.BaseStream.Length)
return reader.ReadBytes((int)CompressedSize);
return new System.IO.MemoryStream();
reader.SeekBegin(Offset);
if (ParentDictionary.IsCompressed)
@ -510,16 +543,15 @@ namespace FirstPlugin.LuigisMansion3
ushort Magic = reader.ReadUInt16();
reader.SeekBegin(Offset);
Data = reader.ReadBytes((int)CompressedSize);
if (Magic == 0x9C78 || Magic == 0xDA78)
return STLibraryCompression.ZLIB.Decompress(Data);
return new System.IO.MemoryStream(STLibraryCompression.ZLIB.Decompress(reader.ReadBytes((int)CompressedSize)));
else //Unknown compression
return Data;
}
else
{
return reader.ReadBytes((int)DecompressedSize);
}
else if (DecompressedSize == 0)
return new System.IO.MemoryStream();
else if (Offset + DecompressedSize < reader.BaseStream.Length)
return new SubStream(reader.BaseStream, Offset, DecompressedSize);
}
}

View file

@ -8,7 +8,9 @@ namespace FirstPlugin.LuigisMansion3
{
public enum DataType : uint
{
Texture = 0x8701B500,
Texture = 0xBA41B500,
Model = 0x8101B000,
Unknown = 0x08C17000,
}
public enum VertexDataFormat
@ -22,7 +24,9 @@ namespace FirstPlugin.LuigisMansion3
public enum IndexFormat : ushort
{
Index_16 = 0x0,
Index_8 = 0x8000,
Index_32 = 0x1,
Index_32_ = 0x2,
Index_8 = 0x8000,
}
public enum SubDataType2 : uint

View file

@ -66,7 +66,7 @@ namespace FirstPlugin.LuigisMansion3
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 List<PointerInfo> VertexBufferPointers = new List<PointerInfo>();
public uint BufferStart;
public uint BufferSize;
@ -87,8 +87,12 @@ namespace FirstPlugin.LuigisMansion3
}
}
private bool loaded = false;
public override void OnClick(TreeView treeView)
{
if (!loaded)
UpdateVertexData();
if (Runtime.UseOpenGL)
{
if (viewport == null)
@ -97,13 +101,36 @@ namespace FirstPlugin.LuigisMansion3
viewport.Dock = DockStyle.Fill;
}
viewport.ReloadDrawables(DataDictionary.DrawableContainer);
LibraryGUI.LoadEditor(viewport);
viewport.SuppressUpdating = true;
foreach (var mesh in DataDictionary.Renderer.Meshes)
mesh.Checked = false;
foreach (TreeNode mesh in Nodes)
mesh.Checked = true;
viewport.SuppressUpdating = false;
Viewport editor = (Viewport)LibraryGUI.GetActiveContent(typeof(Viewport));
if (editor == null)
{
editor = viewport;
LibraryGUI.LoadEditor(viewport);
}
viewport.ReloadDrawables(DataDictionary.DrawableContainer);
viewport.Text = Text;
}
}
private void UpdateVertexData()
{
ReadVertexBuffers();
DataDictionary.Renderer.UpdateVertexData();
loaded = true;
}
public ToolStripItem[] GetContextMenuItems()
{
List<ToolStripItem> Items = new List<ToolStripItem>();
@ -143,13 +170,35 @@ namespace FirstPlugin.LuigisMansion3
DataDictionary = dict;
}
public class PointerInfo
{
//Note if a pointer is not used, it will be 0xFFFFF
public uint WeightTablePointer;
public uint VertexBufferPointer;
public uint IndexBufferPointer;
public uint IndexBufferPointer2;
public void Read(FileReader reader, bool hasWeightTable = true)
{
if (hasWeightTable && !reader.EndOfStream)
WeightTablePointer = reader.ReadUInt32();
if (!reader.EndOfStream)
VertexBufferPointer = reader.ReadUInt32();
if (!reader.EndOfStream)
IndexBufferPointer = reader.ReadUInt32();
if (!reader.EndOfStream)
IndexBufferPointer2 = reader.ReadUInt32();
}
}
public void OnPropertyChanged() { }
public void ReadVertexBuffers()
{
Nodes.Clear();
using (var reader = new FileReader(DataDictionary.GetFileVertexData()))
using (var reader = new FileReader(DataDictionary.GetFileBufferData()))
{
for (int i = 0; i < Meshes.Count; i++)
{
@ -157,7 +206,11 @@ namespace FirstPlugin.LuigisMansion3
RenderableMeshWrapper genericObj = new RenderableMeshWrapper();
genericObj.Mesh = mesh;
genericObj.Text = $"Mesh {i}";
genericObj.Checked = true;
genericObj.Text = $"Mesh {i} {mesh.HashID.ToString("X")}";
if (LM3_DICT.HashNames.ContainsKey(mesh.HashID))
genericObj.Text = LM3_DICT.HashNames[mesh.HashID];
genericObj.SetMaterial(mesh.Material);
RenderedMeshes.Add(genericObj);
@ -167,127 +220,141 @@ namespace FirstPlugin.LuigisMansion3
STGenericPolygonGroup polyGroup = new STGenericPolygonGroup();
genericObj.PolygonGroups.Add(polyGroup);
using (reader.TemporarySeek(BufferStart + VertexBufferPointers[i], System.IO.SeekOrigin.Begin))
uint vertexBufferPointer = VertexBufferPointers[i].VertexBufferPointer;
using (reader.TemporarySeek(BufferStart + vertexBufferPointer, System.IO.SeekOrigin.Begin))
{
var bufferNodeDebug = new DebugVisualBytes(reader.ReadBytes((int)80 * mesh.VertexCount));
var bufferNodeDebug = new DebugVisualBytes(reader.ReadBytes((int)80 * (int)mesh.VertexCount));
bufferNodeDebug.Text = $"Buffer {mesh.DataFormat.ToString("x")}";
genericObj.Nodes.Add(bufferNodeDebug);
}
LM3_Mesh.FormatInfo formatInfo;
if (!LM3_Mesh.FormatInfos.ContainsKey(mesh.DataFormat))
{
Console.WriteLine($"Unsupported data format! " + mesh.DataFormat.ToString("x"));
continue;
formatInfo = new LM3_Mesh.FormatInfo(VertexDataFormat.Float32_32_32, 0x30);
// continue;
}
else
formatInfo = LM3_Mesh.FormatInfos[mesh.DataFormat];
if (formatInfo.BufferLength > 0)
{
var formatInfo = LM3_Mesh.FormatInfos[mesh.DataFormat];
if (formatInfo.BufferLength > 0)
Console.WriteLine($"BufferStart {BufferStart} IndexStartOffset {mesh.IndexStartOffset}");
reader.BaseStream.Position = BufferStart + mesh.IndexStartOffset;
switch (mesh.IndexFormat)
{
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));
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;
case IndexFormat.Index_32:
for (int f = 0; f < mesh.IndexCount; f++)
polyGroup.faces.Add((int)reader.ReadUInt32());
break;
}
Console.WriteLine($"Mesh {genericObj.Text} Format {formatInfo.Format} BufferLength {formatInfo.BufferLength}");
Console.WriteLine($"BufferStart {BufferStart} VertexBufferPointers {vertexBufferPointer}");
uint bufferOffet = BufferStart + vertexBufferPointer;
/* 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 + vertexBufferPointer + 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);
reader.ReadSingle();
vert.nrm = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()).Normalized();
vert.uv0 = new Vector2(reader.ReadSingle(), reader.ReadSingle());
vert.uv1 = new Vector2(reader.ReadSingle(), reader.ReadSingle());
var val = reader.ReadSingle();
// 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();
}
}
@ -325,61 +392,34 @@ namespace FirstPlugin.LuigisMansion3
{
public byte[] Data;
public void Read(FileReader reader, List<LM3_Mesh> Meshes)
public void Read(FileReader reader, List<LM3_Mesh> Meshes, List<uint> Hashes)
{
// 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)
List<uint> ModelTexHashes = new List<uint>();
//Read entire section till i find a matching texture hash
while (!reader.EndOfStream && reader.Position < reader.BaseStream.Length - 4)
{
reader.Position = pos++;
uint HashIDCheck = reader.ReadUInt32();
for (int i = 0; i < Meshes.Count; i++)
{
if (Meshes[i].HashID == HashIDCheck)
{
uint TextureHashID = reader.ReadUInt32();
if (Hashes.Contains(HashIDCheck) && !ModelTexHashes.Contains(HashIDCheck))
ModelTexHashes.Add(HashIDCheck);
}
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 (ModelTexHashes.Count > i)
{
if (Meshes[m].HashID == MeshHashID)
uint TextureHashID = ModelTexHashes[i];
Meshes[i].Material = new LM3_Material();
var texUnit = 1;
Meshes[i].Material.TextureMaps.Add(new STGenericMatTexture()
{
}
};
if (i != Meshes.Count - 1)
reader.Seek(4); //padding on all but last entry
}*/
textureUnit = texUnit++,
Type = STGenericMatTexture.TextureType.Diffuse,
Name = TextureHashID.ToString("x"),
});
}
}
}
}
@ -487,9 +527,10 @@ namespace FirstPlugin.LuigisMansion3
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 Unknown3 { get; private set; } //Possibly contributes to rigged meshes. 0xFFFF if static (no weight table / pointer)
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 uint Unknown5 { get; private set; }
public uint VertexCount { get; private set; }
public ushort Unknown7 { get; private set; } //Always 256?
public uint HashID { get; private set; }
@ -501,19 +542,25 @@ namespace FirstPlugin.LuigisMansion3
{
Material = new LM3_Material();
HashID = reader.ReadUInt32();
IndexStartOffset = reader.ReadUInt32();
IndexCount = reader.ReadUInt16();
IndexFormat = reader.ReadEnum<IndexFormat>(false);
IndexFormat = IndexFormat.Index_16;
if (IndexFormat != IndexFormat.Index_16)
IndexFormat = IndexFormat.Index_8;
VertexCount = reader.ReadUInt32();
reader.ReadUInt32(); //unknown
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();
Unknown3 = reader.ReadUInt32(); //Sometimes 0xFFFF.
reader.ReadUInt16();
Unknown4 = reader.ReadUInt32();
VertexCount = reader.ReadUInt16();
Unknown7 = reader.ReadUInt16(); //0x100
HashID = reader.ReadUInt32(); //0x100
Unknown5 = reader.ReadUInt32();
Unknown7 = reader.ReadUInt16();
reader.ReadUInt32(); //unknown
reader.ReadUInt32(); //unknown
}
public class FormatInfo
@ -532,6 +579,25 @@ namespace FirstPlugin.LuigisMansion3
//These may not be very accurate, i need to look more into these
public static Dictionary<ulong, FormatInfo> FormatInfos = new Dictionary<ulong, FormatInfo>()
{
{ 0xcb418c82ba25920e, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0x4c083342551178ce, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0xa2fdc74f42ce4fdb, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0xcb092e9f8322ba, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0x2031dd4da78347d9, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0x210ed90e5465129a, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0xa86b2280a1500a0c, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0xc5f54a808b32320c, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0x568f92478fa0a2d3, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0xc344835dd398dde9, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0x8d45618da4768c19, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0xd5b9166924e124f5, new FormatInfo(VertexDataFormat.Float32_32_32, 0x30)},
{ 0x5f5227f782c08883, new FormatInfo(VertexDataFormat.Float32_32_32, 0x0C)},
{ 0x6350379972D28D0D, new FormatInfo(VertexDataFormat.Float16, 0x46)},
{ 0xDC0291B311E26127, new FormatInfo(VertexDataFormat.Float16, 0x16)},
{ 0x93359708679BEB7C, new FormatInfo(VertexDataFormat.Float16, 0x16)},

View file

@ -131,6 +131,7 @@
this.Name,
this.Format});
this.listViewCustom1.Dock = System.Windows.Forms.DockStyle.Left;
this.listViewCustom1.HideSelection = false;
this.listViewCustom1.Location = new System.Drawing.Point(0, 25);
this.listViewCustom1.Name = "listViewCustom1";
this.listViewCustom1.OwnerDraw = true;

View file

@ -200,6 +200,39 @@ namespace FirstPlugin.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to D:\Project\NeoPlum\Out\Dist\NXROM\NeoPlumNX64_dist.nss
///nnSdk.nss
///c:/NXSDK/V7_3_2/NintendoSDK/Libraries/NX-NXFP2-a64/Release/multimedia.nss
///__nnDetailNintendoSdkRuntimeObjectFile
///__nnDetailNintendoSdkRuntimeObjectFileRefer
///__nnmusl_fini_dso
///__nnmusl_init_dso
///__rel_dyn_end
///__rel_dyn_start
///__rel_plt_end
///__rel_plt_start
///nndetailRoGetRoDataEnd
///nndetailRoGetRoDataStart
///nnMain
///nninitStartup
///_ZdlPv
///_ZNSt3__112__next_primeEm
///strtoul
///strtod
///vsnprintf
///vswprintf
///_ZdaPv
///_Znwm
///_Znam
///__cxa_guard_ac [rest of string was truncated]&quot;;.
/// </summary>
internal static string LM3_Hashes {
get {
return ResourceManager.GetString("LM3_Hashes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View file

@ -199,4 +199,7 @@
<data name="MissingTexture" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\MissingTexture.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="LM3_Hashes" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Hashes\LM3_Hashes.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
</root>

View file

@ -0,0 +1,10 @@
-- Names here are user generated --
-- Hashes are in hex format and names go after seperated by a comma --
-- Names can be paths for multiple folders --
9d6bf95e, Body_Diffuse
9deaa4e5, Body_SSS
9e5737eb, Body_Normal
852f0ba7, Cloth_Diffuse
85adb72e, Cloth_SSS
861a4a34, Cloth_Normal

View file

@ -121,13 +121,22 @@ namespace Toolbox.Library.Forms
Color uvColor = Color.LightGreen;
Color gridColor = Color.Black;
List<int> f = genericObject.lodMeshes[0].getDisplayFace();
for (int v = 0; v < genericObject.lodMeshes[0].displayFaceSize; v += 3)
List<int> f = new List<int>();
int displayFaceSize = 0;
if (genericObject.lodMeshes.Count > 0)
{
if (genericObject.lodMeshes[0].displayFaceSize < 3 ||
f = genericObject.lodMeshes[0].getDisplayFace();
displayFaceSize = genericObject.lodMeshes[0].displayFaceSize;
}
if (genericObject.PolygonGroups.Count > 0)
{
f = genericObject.PolygonGroups[0].GetDisplayFace();
displayFaceSize = genericObject.PolygonGroups[0].displayFaceSize;
}
for (int v = 0; v < displayFaceSize; v += 3)
{
if (displayFaceSize < 3 ||
genericObject.vertices.Count < 3)
return;
@ -135,6 +144,9 @@ namespace Toolbox.Library.Forms
Vector2 v2 = new Vector2(0);
Vector2 v3 = new Vector2(0);
if (f.Count < v + 2)
continue;
if (UvChannelIndex == 0)
{
v1 = genericObject.vertices[f[v]].uv0;
@ -563,6 +575,22 @@ namespace Toolbox.Library.Forms
}
}
}
if (container.Drawables[i] is Rendering.GenericModelRenderer && container.Drawables[i].Visible)
{
for (int m = 0; m < ((Rendering.GenericModelRenderer)container.Drawables[i]).Meshes.Count; m++)
{
var mesh = ((Rendering.GenericModelRenderer)container.Drawables[i]).Meshes[m];
if (mesh.GetMaterial() != null)
{
Objects.Add(mesh);
var mat = mesh.GetMaterial();
if (!Materials.Contains(mat))
{
Materials.Add(mat);
}
}
}
}
}
Reset();

View file

@ -30,6 +30,8 @@ namespace Toolbox.Library
}
}
public bool SuppressUpdating = false;
public List<DrawableContainer> DrawableContainers;
public EditorScene scene = new EditorScene();
@ -285,6 +287,8 @@ namespace Toolbox.Library
public void UpdateViewport()
{
if (SuppressUpdating) return;
if (GL_ControlModern != null)
GL_ControlModern.Refresh();
if (GL_ControlLegacy != null)