Add support for DKCTF Wii U and Switch files.

Supports loading rigged models and viewing textures for both the Wii U and Switch versions of the game.
Keep in mind the Switch version lacks LZSS 3 byte compression and will be missing vertex data for certain models.
This commit is contained in:
KillzXGaming 2022-10-26 19:33:56 -04:00
parent 5f3cde8d57
commit 2cc2269bab
23 changed files with 12339 additions and 225 deletions

View file

@ -0,0 +1,175 @@
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 Toolbox.Library.Rendering;
using OpenTK;
using CafeLibrary.M2;
using System.IO;
namespace DKCTF
{
public class CCharacter : TreeNodeFile
{
public FileType FileType { get; set; } = FileType.Model;
public bool CanSave { get; set; }
public string[] Description { get; set; } = new string[] { "Character Model" };
public string[] Extension { get; set; } = new string[] { "*.char" };
public string FileName { get; set; }
public string FilePath { get; set; }
public IFileInfo IFileInfo { get; set; }
public bool Identify(System.IO.Stream stream)
{
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
{
bool IsForm = reader.CheckSignature(4, "RFRM");
bool FormType = reader.CheckSignature(4, "CHAR", 20);
return IsForm && FormType;
}
}
public Type[] Types
{
get
{
List<Type> types = new List<Type>();
return types.ToArray();
}
}
//Check for the viewport in the object editor
//This is attached to it to load multiple file formats within the object editor to the viewer
Viewport viewport
{
get
{
var editor = LibraryGUI.GetObjectEditor();
return editor.GetViewport();
}
set
{
var editor = LibraryGUI.GetObjectEditor();
editor.LoadViewport(value);
}
}
bool DrawablesLoaded = false;
public override void OnClick(TreeView treeView)
{
//Make sure opengl is enabled
if (Runtime.UseOpenGL)
{
//Open the viewport
if (viewport == null)
{
viewport = new Viewport(ObjectEditor.GetDrawableContainers());
viewport.Dock = DockStyle.Fill;
}
//Make sure to load the drawables only once so set it to true!
if (!DrawablesLoaded)
{
ObjectEditor.AddContainer(DrawableContainer);
DrawablesLoaded = true;
}
//Reload which drawable to display
viewport.ReloadDrawables(DrawableContainer);
LibraryGUI.LoadEditor(viewport);
viewport.Text = Text;
}
}
public IEnumerable<STGenericObject> ExportableMeshes => Model.Objects;
public IEnumerable<STGenericMaterial> ExportableMaterials => Model.Materials;
public IEnumerable<STGenericTexture> ExportableTextures => TextureList;
public STSkeleton ExportableSkeleton => Model.GenericSkeleton;
public GenericModelRenderer Renderer;
public DrawableContainer DrawableContainer = new DrawableContainer();
public List<STGenericTexture> TextureList = new List<STGenericTexture>();
public CHAR CharData { get; set; }
public STGenericModel Model;
TreeNode modelFolder = new TreeNode("Models");
TreeNode skeletonFolder = new STTextureFolder("Skeleton");
public void Load(System.IO.Stream stream)
{
Text = FileName;
Renderer = new GenericModelRenderer();
CharData = new CHAR(stream);
Nodes.Add(modelFolder);
Nodes.Add(skeletonFolder);
Model = ToGeneric();
DrawableContainer = new DrawableContainer();
DrawableContainer.Name = FileName;
DrawableContainer.Drawables.Add(Renderer);
DrawableContainer.Drawables.Add(Model.GenericSkeleton);
}
public void LoadModels(PAK pakFile)
{
var skeleton = pakFile.SkeletonFiles[CharData.SkeletonFileID.ToString()];
var skel = new SKEL(new MemoryStream(skeleton.FileData));
skeletonFolder.Nodes.Clear();
foreach (var bone in skel.JointNames)
{
TreeNode boneNode = new TreeNode(bone);
skeletonFolder.Nodes.Add(boneNode);
}
foreach (var model in CharData.Models)
{
var mod = pakFile.ModelFiles[model.FileID.ToString()].OpenFile() as CModel;
mod.LoadTextures(pakFile.TextureFiles);
modelFolder.Nodes.Add(mod);
}
}
public void Unload()
{
}
public void Save(System.IO.Stream stream)
{
}
public STGenericModel ToGeneric()
{
var model = new STGenericModel();
model.GenericSkeleton = new STSkeleton();
List<GenericRenderedObject> meshes = new List<GenericRenderedObject>();
List<STGenericMaterial> materials = new List<STGenericMaterial>();
model.Objects = meshes;
model.Name = this.FileName;
return model;
}
}
}

View file

@ -0,0 +1,300 @@
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 Toolbox.Library.Rendering;
using OpenTK;
using CafeLibrary.M2;
using System.IO;
namespace DKCTF
{
public class CModel : TreeNodeFile, IFileFormat, IExportableModel
{
public FileType FileType { get; set; } = FileType.Model;
public bool CanSave { get; set; }
public string[] Description { get; set; } = new string[] { "CMDL" };
public string[] Extension { get; set; } = new string[] { "*.cmdl", "*.smdl", "*.wmdl" };
public string FileName { get; set; }
public string FilePath { get; set; }
public IFileInfo IFileInfo { get; set; }
public bool Identify(System.IO.Stream stream)
{
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
{
bool IsForm = reader.CheckSignature(4, "RFRM");
bool FormType = reader.CheckSignature(4, "CMDL", 20) ||
reader.CheckSignature(4, "WMDL", 20) ||
reader.CheckSignature(4, "SMDL", 20);
return IsForm && FormType;
}
}
public Type[] Types
{
get
{
List<Type> types = new List<Type>();
return types.ToArray();
}
}
//Check for the viewport in the object editor
//This is attached to it to load multiple file formats within the object editor to the viewer
Viewport viewport
{
get
{
var editor = LibraryGUI.GetObjectEditor();
return editor.GetViewport();
}
set
{
var editor = LibraryGUI.GetObjectEditor();
editor.LoadViewport(value);
}
}
bool DrawablesLoaded = false;
public override void OnClick(TreeView treeView)
{
//Make sure opengl is enabled
if (Runtime.UseOpenGL)
{
//Open the viewport
if (viewport == null)
{
viewport = new Viewport(ObjectEditor.GetDrawableContainers());
viewport.Dock = DockStyle.Fill;
}
//Make sure to load the drawables only once so set it to true!
if (!DrawablesLoaded)
{
ObjectEditor.AddContainer(DrawableContainer);
DrawablesLoaded = true;
}
//Reload which drawable to display
viewport.ReloadDrawables(DrawableContainer);
LibraryGUI.LoadEditor(viewport);
viewport.Text = Text;
}
}
public IEnumerable<STGenericObject> ExportableMeshes => Model.Objects;
public IEnumerable<STGenericMaterial> ExportableMaterials => Model.Materials;
public IEnumerable<STGenericTexture> ExportableTextures => Renderer.Textures;
public STSkeleton ExportableSkeleton => Model.GenericSkeleton;
public GenericModelRenderer Renderer;
public DrawableContainer DrawableContainer = new DrawableContainer();
public List<STGenericTexture> TextureList = new List<STGenericTexture>();
public CMDL ModelData { get; set; }
public STGenericModel Model;
public SKEL SkeletonData;
public void LoadSkeleton(SKEL skeleton)
{
SkeletonData = skeleton;
Model = ToGeneric();
Model.GenericSkeleton = SkeletonData.ToGenericSkeleton();
DrawableContainer.Drawables.Add(Model.GenericSkeleton);
foreach (var bone in Model.GenericSkeleton.bones)
{
if (bone.Parent == null)
skeletonFolder.Nodes.Add(bone);
}
Renderer.Skeleton = Model.GenericSkeleton;
//Reload meshes with updated bone IDs
Renderer.Meshes.Clear();
foreach (GenericRenderedObject mesh in Model.Objects)
Renderer.Meshes.Add(mesh);
}
public void LoadTextures(Dictionary<string, FileEntry> Textures)
{
texFolder.Nodes.Clear();
foreach (var mat in ModelData.Materials)
{
foreach (var texMap in mat.Textures)
{
string guid = texMap.Value.FileID.ToString();
if (texFolder.Nodes.ContainsKey(guid))
continue;
if (Textures[guid].FileFormat == null)
Textures[guid].OpenFile();
var tex = Textures[guid].FileFormat as CTexture;
TreeNode t = new TreeNode(guid);
t.ImageKey = "texture";
t.SelectedImageKey = "texture";
t.Tag = tex;
texFolder.Nodes.Add(t);
if (texMap.Key == "DIFT")
{
tex.Text = guid;
if (tex.RenderableTex == null)
tex.LoadOpenGLTexture();
Renderer.Textures.Add(tex);
}
}
}
}
TreeNode texFolder = new STTextureFolder("Textures");
TreeNode skeletonFolder = new STTextureFolder("Skeleton");
public void Load(System.IO.Stream stream)
{
Text = FileName;
Renderer = new GenericModelRenderer();
ModelData = new CMDL(stream);
TreeNode meshFolder = new TreeNode("Meshes");
Nodes.Add(meshFolder);
Nodes.Add(texFolder);
Nodes.Add(skeletonFolder);
Model = ToGeneric();
foreach (GenericRenderedObject mesh in Model.Objects)
{
Renderer.Meshes.Add(mesh);
meshFolder.Nodes.Add(mesh);
}
foreach (var tex in TextureList)
{
Renderer.Textures.Add(tex);
texFolder.Nodes.Add(tex);
}
Renderer.Skeleton = Model.GenericSkeleton;
DrawableContainer = new DrawableContainer();
DrawableContainer.Name = FileName;
DrawableContainer.Drawables.Add(Renderer);
}
public void Unload()
{
}
public void Save(System.IO.Stream stream)
{
}
public STGenericModel ToGeneric()
{
var model = new STGenericModel();
model.Name = this.FileName;
model.GenericSkeleton = new STSkeleton();
if (SkeletonData != null)
model.GenericSkeleton = SkeletonData.ToGenericSkeleton();
List<GenericRenderedObject> meshes = new List<GenericRenderedObject>();
List<STGenericMaterial> materials = new List<STGenericMaterial>();
foreach (var mat in ModelData.Materials)
{
STGenericMaterial genericMaterial = new STGenericMaterial();
genericMaterial.Text = mat.Name;
materials.Add(genericMaterial);
foreach (var tex in mat.Textures)
{
var type = STGenericMatTexture.TextureType.Unknown;
if (tex.Key == "DIFT") type = STGenericMatTexture.TextureType.Diffuse;
if (tex.Key == "NMAP") type = STGenericMatTexture.TextureType.Normal;
if (tex.Key == "SPCT") type = STGenericMatTexture.TextureType.Specular;
genericMaterial.TextureMaps.Add(new STGenericMatTexture()
{
Type = type,
Name = tex.Value.FileID.ToString(),
WrapModeS = STTextureWrapMode.Repeat,
WrapModeT = STTextureWrapMode.Repeat,
});
}
}
foreach (var mesh in ModelData.Meshes)
{
var mat = materials[mesh.Header.MaterialIndex];
GenericRenderedObject genericMesh = new GenericRenderedObject();
genericMesh.Text = "Mesh_" + mat.Name;
meshes.Add(genericMesh);
foreach (var vert in mesh.Vertices)
{
Vertex v = new Vertex();
v.pos = vert.Position;
v.nrm = vert.Normal;
v.uv0 = vert.TexCoord0;
v.uv1 = vert.TexCoord1;
v.uv2 = vert.TexCoord2;
v.tan = vert.Tangent;
v.col = vert.Color;
if (SkeletonData != null)
{
for (int j = 0; j < 4; j++)
{
var weight = vert.BoneWeights[j];
var boneIndex = (int)vert.BoneIndices[j];
if (weight == 0)
continue;
v.boneWeights.Add(weight);
v.boneIds.Add(SkeletonData.SkinnedBonesRemap[boneIndex]);;
}
}
genericMesh.vertices.Add(v);
}
var poly = new STGenericPolygonGroup();
poly.Material = mat;
poly.MaterialIndex = mesh.Header.MaterialIndex;
genericMesh.PolygonGroups.Add(poly);
foreach (var id in mesh.Indices)
poly.faces.Add((int)id);
}
model.Materials = materials;
model.Objects = meshes;
model.Name = this.FileName;
return model;
}
}
}

View file

@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Toolbox;
using System.Windows.Forms;
using Toolbox.Library;
using Toolbox.Library.Forms;
using Toolbox.Library.IO;
using System.Runtime.InteropServices;
namespace DKCTF
{
public class CTexture : STGenericTexture, IFileFormat, IContextMenuNode
{
public FileType FileType { get; set; } = FileType.Image;
public bool CanSave { get; set; }
public string[] Description { get; set; } = new string[] { "TXTR" };
public string[] Extension { get; set; } = new string[] { "*.txtr" };
public string FileName { get; set; }
public string FilePath { get; set; }
public IFileInfo IFileInfo { get; set; }
public bool Identify(System.IO.Stream stream)
{
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
{
bool IsForm = reader.CheckSignature(4, "RFRM");
bool FormType = reader.CheckSignature(4, "TXTR", 20);
return IsForm && FormType;
}
}
public Type[] Types
{
get
{
List<Type> types = new List<Type>();
return types.ToArray();
}
}
public override bool CanEdit { get; set; } = false;
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,
};
}
}
TXTR Header;
public override void OnClick(TreeView treeView)
{
UpdateEditor();
}
private void UpdateEditor()
{
ImageEditorBase editor = (ImageEditorBase)LibraryGUI.GetActiveContent(typeof(ImageEditorBase));
if (editor == null)
{
editor = new ImageEditorBase();
editor.Dock = DockStyle.Fill;
LibraryGUI.LoadEditor(editor);
}
editor.LoadProperties(GenericProperties);
editor.LoadImage(this);
}
public ToolStripItem[] GetContextMenuItems()
{
List<ToolStripItem> Items = new List<ToolStripItem>();
Items.Add(new ToolStripMenuItem("Save", null, SaveAction, Keys.Control | Keys.S));
Items.AddRange(base.GetContextMenuItems());
return Items.ToArray();
}
private void SaveAction(object sender, EventArgs args)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = Utils.GetAllFilters(this);
sfd.FileName = FileName;
if (sfd.ShowDialog() == DialogResult.OK)
{
STFileSaver.SaveFileFormat(this, sfd.FileName);
}
}
public void FillEditor(UserControl control)
{
Properties prop = new Properties();
prop.Width = Width;
prop.Height = Height;
prop.Depth = Depth;
prop.MipCount = MipCount;
prop.ArrayCount = ArrayCount;
prop.ImageSize = (uint)Header.BufferData.Length;
prop.Format = Format;
((ImageEditorBase)control).LoadImage(this);
((ImageEditorBase)control).LoadProperties(prop);
}
public void Load(System.IO.Stream stream)
{
PlatformSwizzle = PlatformSwizzle.Platform_Switch;
CanSave = true;
CanReplace = true;
Text = FileName;
this.ImageKey = "texture";
this.SelectedImageKey = "texture";
Header = new TXTR(stream);
if (Header.IsSwitch)
PlatformSwizzle = PlatformSwizzle.Platform_WiiU;
Width = Header.TextureHeader.Width;
Height = Header.TextureHeader.Height;
MipCount = (uint)Header.MipSizes.Length;
Format = FormatList[Header.TextureHeader.Format];
}
public void Save(System.IO.Stream stream)
{
using (var writer = new FileWriter(stream, true))
{
}
}
public override void Replace(string FileName)
{
}
public override void SetImageData(System.Drawing.Bitmap bitmap, int ArrayLevel)
{
}
public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0, int DepthLevel = 0)
{
if (Header.IsSwitch)
return TegraX1Swizzle.GetImageData(this, Header.BufferData, ArrayLevel, MipLevel, DepthLevel);
else
{
var tex = this.Header.TextureHeader;
uint bpp = GetBytesPerPixel(Format);
GX2.GX2Surface surf = new GX2.GX2Surface();
surf.bpp = bpp;
surf.height = tex.Height;
surf.width = tex.Width;
surf.aa = (uint)GX2.GX2AAMode.GX2_AA_MODE_1X;
surf.alignment = 8192;
surf.imageSize = (uint)Header.BufferData.Length;
if (Header.Meta != null)
surf.alignment = Header.Meta.BaseAlignment;
surf.depth = 1;
surf.dim = (uint)GX2.GX2SurfaceDimension.DIM_2D;
surf.format = (uint)Bfres.Structs.FTEX.ConvertToGx2Format(Format);
surf.use = (uint)GX2.GX2SurfaceUse.USE_TEXTURE;
surf.data = Header.BufferData;
surf.numMips = 1;
surf.mipOffset = new uint[0];
surf.mipData = Header.BufferData;
surf.tileMode = (int)GX2.GX2TileMode.MODE_2D_TILED_THIN1;
var surfOut = GX2.getSurfaceInfo((GX2.GX2SurfaceFormat)Format, surf.width, surf.height, 1, 1, surf.tileMode, 0, 0);
surf.pitch = surfOut.pitch;
var swizzlePattern = 0xd0000 | tex.Swizzle << 8;
surf.swizzle = swizzlePattern;
Console.WriteLine("");
Console.WriteLine("// ----- GX2Surface Info ----- ");
Console.WriteLine(" dim = " + surf.dim);
Console.WriteLine(" width = " + surf.width);
Console.WriteLine(" height = " + surf.height);
Console.WriteLine(" depth = " + surf.depth);
Console.WriteLine(" numMips = " + surf.numMips);
Console.WriteLine(" format = " + surf.format);
Console.WriteLine(" aa = " + surf.aa);
Console.WriteLine(" use = " + surf.use);
Console.WriteLine(" imageSize = " + surf.imageSize);
Console.WriteLine(" mipSize = " + surf.mipSize);
Console.WriteLine(" tileMode = " + surf.tileMode);
Console.WriteLine(" swizzle = " + surf.swizzle);
Console.WriteLine(" alignment = " + surf.alignment);
Console.WriteLine(" pitch = " + surf.pitch);
Console.WriteLine(" bits per pixel = " + (surf.bpp << 3));
Console.WriteLine(" bytes per pixel = " + surf.bpp);
Console.WriteLine(" data size = " + surf.data.Length);
Console.WriteLine(" mip size = " + surf.mipData.Length);
Console.WriteLine(" realSize = " + surf.imageSize);
return GX2.Decode(surf, ArrayLevel, MipLevel);
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class STextureHeader
{
public uint Type;
public uint Format;
public uint Width;
public uint Height;
public uint Depth;
public uint TileMode;
public uint Swizzle;
}
Dictionary<uint, TEX_FORMAT> FormatList = new Dictionary<uint, TEX_FORMAT>()
{
{ 12, TEX_FORMAT.R8G8B8A8_UNORM },
{ 13, TEX_FORMAT.R8G8B8A8_UNORM_SRGB },
{ 20, TEX_FORMAT.BC1_UNORM },
{ 21, TEX_FORMAT.BC1_UNORM_SRGB },
{ 22, TEX_FORMAT.BC2_UNORM },
{ 23, TEX_FORMAT.BC2_UNORM_SRGB },
{ 24, TEX_FORMAT.BC3_UNORM },
{ 25, TEX_FORMAT.BC3_UNORM_SRGB },
{ 26, TEX_FORMAT.BC4_UNORM },
{ 27, TEX_FORMAT.BC4_SNORM },
{ 28, TEX_FORMAT.BC5_UNORM },
{ 29, TEX_FORMAT.BC5_SNORM },
{ 30, TEX_FORMAT.R11G11B10_FLOAT },
{ 31, TEX_FORMAT.R32_FLOAT },
{ 32, TEX_FORMAT.R16G16_FLOAT },
{ 33, TEX_FORMAT.R8G8_UNORM },
{ 54, TEX_FORMAT.ASTC_5x4_UNORM },
{ 55, TEX_FORMAT.ASTC_5x5_UNORM },
{ 56, TEX_FORMAT.ASTC_6x5_UNORM },
{ 57, TEX_FORMAT.ASTC_6x6_UNORM },
{ 58, TEX_FORMAT.ASTC_8x5_UNORM },
};
}
}

View file

@ -1,105 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Toolbox.Library.IO;
namespace DKCTF
{
//Documentation from https://github.com/Kinnay/Nintendo-File-Formats/wiki/DKCTF-Types#cformdescriptor
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CFormDescriptor
{
public Magic Magic = "RFRM";
public ulong DataSize;
public ulong Unknown;
public Magic FormType;
public uint VersionA;
public uint VersionB;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CChunkDescriptor
{
public Magic ChunkType;
public long DataSize;
public uint Unknown;
public long DataOffset;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CMayaSpline
{
public Magic ChunkType;
public ulong DataSize;
public uint Unknown;
public ulong DataOffset;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CMayaSplineKnot
{
public float Time;
public float Value;
public ETangentType TangentType1;
public ETangentType TangentType2;
public float FieldC;
public float Field10;
public float Field14;
public float Field18;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CObjectTag
{
public Magic Type;
public CGuid Objectid;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CObjectId
{
public CGuid Guid;
public override string ToString()
{
return Guid.ToString();
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CGuid
{
public uint Part1;
public ushort Part2;
public ushort Part3;
public ulong Part4;
public override string ToString()
{
return $"{Part1}|{Part2}|{Part3}|{Part4}";
}
}
public enum ETangentType
{
Linear,
Flat,
Smooth,
Step,
Clamped,
Fixed,
}
public enum EInfinityType
{
Constant,
Linear,
Cycle,
CycleRelative,
Oscillate,
}
}

View file

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Toolbox.Library.IO;
using OpenTK;
namespace DKCTF
{
/// <summary>
/// Represents a helper for loading vertex and index buffer information.
/// </summary>
internal class BufferHelper
{
/// <summary>
/// Gets an index list from the provided buffer and formatting info.
/// </summary>
public static uint[] LoadIndexBuffer(byte[] buffer, CMDL.IndexFormat format, bool isLittleEndian)
{
var stride = GetIndexStride(format);
uint[] indices = new uint[buffer.Length / stride];
using (var reader = new FileReader(buffer))
{
reader.SetByteOrder(!isLittleEndian); //switch is little endianness
if (format == CMDL.IndexFormat.Uint16)
{
for (int i = 0; i < indices.Length; i++)
indices[i] = reader.ReadUInt16();
}
else
{
for (int i = 0; i < indices.Length; i++)
indices[i] = reader.ReadUInt32();
}
}
return indices;
}
/// <summary>
/// Gets a vertex list from the provided buffer and descriptor info.
/// </summary>
public static List<CMDL.CVertex> LoadVertexBuffer(byte[] buffer, CMDL.VertexBuffer vertexBuffer, bool isLittleEndian)
{
List<CMDL.CVertex> vertices = new List<CMDL.CVertex>();
using (var reader = new FileReader(buffer))
{
reader.SetByteOrder(!isLittleEndian); //switch is little endianness
for (int i = 0; i < vertexBuffer.VertexCount; i++) {
CMDL.CVertex vertex = new CMDL.CVertex();
vertices.Add(vertex);
foreach (var comp in vertexBuffer.Components) {
reader.SeekBegin(comp.Offset + i * comp.Stride);
switch (comp.Type)
{
case CMDL.EVertexComponent.in_position:
vertex.Position = ReadData(reader, comp.Format).Xyz;
break;
case CMDL.EVertexComponent.in_normal:
vertex.Normal = ReadData(reader, comp.Format).Xyz;
break;
case CMDL.EVertexComponent.in_texCoord0:
vertex.TexCoord0 = ReadData(reader, comp.Format).Xy;
break;
case CMDL.EVertexComponent.in_texCoord1:
vertex.TexCoord1 = ReadData(reader, comp.Format).Xy;
break;
case CMDL.EVertexComponent.in_texCoord2:
vertex.TexCoord2 = ReadData(reader, comp.Format).Xy;
break;
case CMDL.EVertexComponent.in_boneWeights:
vertex.BoneWeights = ReadData(reader, comp.Format);
break;
case CMDL.EVertexComponent.in_boneIndices:
vertex.BoneIndices = ReadData(reader, comp.Format);
break;
case CMDL.EVertexComponent.in_color:
vertex.Color = ReadData(reader, comp.Format);
break;
case CMDL.EVertexComponent.in_tangent0:
vertex.Tangent = ReadData(reader, comp.Format);
break;
}
}
}
}
return vertices;
}
static Vector4 ReadData(FileReader reader, CMDL.VertexFormat format)
{
switch (format)
{
case CMDL.VertexFormat.Format_16_16_HalfSingle: return new Vector4(reader.ReadHalfSingle(), reader.ReadHalfSingle(), 0, 0);
case CMDL.VertexFormat.Format_32_32_32_Single: return new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), 0);
case CMDL.VertexFormat.Format_16_16_16_HalfSingle: return new Vector4(
reader.ReadHalfSingle(),reader.ReadHalfSingle(),
reader.ReadHalfSingle(), reader.ReadHalfSingle());
case CMDL.VertexFormat.Format_8_8_8_8_Uint:
return new Vector4(
reader.ReadByte(), reader.ReadByte(),
reader.ReadByte(), reader.ReadByte());
}
return new Vector4();
}
private static int GetIndexStride(CMDL.IndexFormat format)
{
if (format == CMDL.IndexFormat.Uint32) return 4;
else return 2;
}
}
}

View file

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Toolbox.Library.IO;
namespace DKCTF
{
/// <summary>
/// Represents a file format for storing character/actor data including skeleton, model and animation information.
/// </summary>
public class CHAR : FileForm
{
public string Name { get; set; }
public CObjectId SkeletonFileID;
public List<CCharacterModelSet> Models = new List<CCharacterModelSet>();
public List<CAnimationInfo> Animations = new List<CAnimationInfo>();
public bool IsSwitch;
public CHAR() { }
public CHAR(System.IO.Stream stream) : base(stream)
{
}
public override void Read(FileReader reader)
{
reader.ReadStruct<CAssetHeader>();
reader.ReadStruct<SInfo>();
//Dumb hack atm
bool IsSwitch = false;
using (reader.TemporarySeek(reader.Position, System.IO.SeekOrigin.Begin))
{
uint len = reader.ReadUInt32();
if (len < 30)
IsSwitch = true;
}
Name = IOFileExtension.ReadFixedString(reader, IsSwitch);
SkeletonFileID = reader.ReadStruct<CObjectId>();
uint numModels = reader.ReadUInt32();
for (int i = 0; i < numModels; i++)
{
Models.Add(new CCharacterModelSet()
{
Name = IOFileExtension.ReadFixedString(reader, IsSwitch),
FileID = reader.ReadStruct<CObjectId>(),
BoundingBox = reader.ReadStruct<CAABox>(),
});
}
uint numAnimations = reader.ReadUInt32();
for (int i = 0; i < numAnimations; i++)
{
Animations.Add(new CAnimationInfo()
{
Name = IOFileExtension.ReadFixedString(reader, IsSwitch),
FileID = reader.ReadStruct<CObjectId>(),
field_1c = reader.ReadUInt32(),
field_20 = reader.ReadUInt32(),
field_24 = reader.ReadUInt16(),
field_26 = reader.ReadUInt16(),
field_28 = reader.ReadBoolean(),
BoundingBox = reader.ReadStruct<CAABox>(),
});
}
Console.WriteLine();
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SInfo
{
public byte field_0;
public byte field_1;
public byte field_2;
public byte field_3;
public byte field_4;
public byte field_5;
public byte field_6;
public byte field_7;
public byte field_8;
public byte field_9;
public byte field_A;
public byte field_B;
public byte field_C;
public byte field_D;
public byte field_E;
public byte field_F;
public ushort flags1;
public byte flags2;
}
public class CCharacterModelSet
{
public string Name;
public CObjectId FileID;
public CAABox BoundingBox;
public override string ToString()
{
return Name;
}
}
public class CAnimationInfo
{
public string Name;
public CObjectId FileID;
public uint field_1c;
public uint field_20;
public ushort field_24;
public ushort field_26;
public bool field_28;
public CAABox BoundingBox;
public override string ToString()
{
return Name;
}
}
}
}

View file

@ -0,0 +1,549 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Toolbox.Library.IO;
using OpenTK;
namespace DKCTF
{
/// <summary>
/// Represents a model file format for loading mesh and material data.
/// </summary>
public class CMDL : FileForm
{
/// <summary>
/// The meshes of the model used to display the model.
/// </summary>
public List<CMesh> Meshes = new List<CMesh>();
/// <summary>
/// The materials of the model for rendering the mesh.
/// </summary>
public List<CMaterial> Materials = new List<CMaterial>();
/// <summary>
/// The vertex buffer list to read the buffer attributes.
/// </summary>
List<VertexBuffer> VertexBuffers = new List<VertexBuffer>();
/// <summary>
/// The index buffer list to read the index buffer data.
/// </summary>
List<CGraphicsIndexBufferToken> IndexBuffer = new List<CGraphicsIndexBufferToken>();
/// <summary>
/// Determines which variant of the file to parse. Switch reads strings and materials differently.
/// </summary>
bool IsSwitch => this.FileHeader.FormType == "SMDL" && this.FileHeader.VersionA >= 0x3A ||
this.FileHeader.FormType == "CMDL" && this.FileHeader.VersionA >= 0x35;
/// <summary>
/// The meta data header for parsing gpu buffers and decompressing.
/// </summary>
SMetaData Meta;
public CMDL() { }
public CMDL(System.IO.Stream stream) : base(stream)
{
}
public override void ReadMetaData(FileReader reader)
{
Meta = new SMetaData();
Meta.Unknown = reader.ReadUInt32();
Meta.GPUOffset = reader.ReadUInt32();
Meta.ReadBufferInfo = IOFileExtension.ReadList<SReadBufferInfo>(reader);
Meta.VertexBuffers = IOFileExtension.ReadList<SBufferInfo>(reader);
Meta.IndexBuffers = IOFileExtension.ReadList<SBufferInfo>(reader);
Console.WriteLine();
}
public override void WriteMetaData(FileWriter writer)
{
writer.Write(Meta.GPUOffset);
writer.Write(Meta.Unknown);
IOFileExtension.WriteList(writer, Meta.ReadBufferInfo);
IOFileExtension.WriteList(writer, Meta.VertexBuffers);
IOFileExtension.WriteList(writer, Meta.IndexBuffers);
}
public override void ReadChunk(FileReader reader, CChunkDescriptor chunk)
{
switch (chunk.ChunkType)
{
case "CMDL":
break;
case "WMDL":
break;
case "SMDL":
reader.ReadUInt32(); //unk
break;
case "HEAD":
reader.ReadStruct<SModelHeader>();
break;
case "MTRL":
if (IsSwitch)
ReadMaterials(reader);
else
ReadMaterialsU(reader);
break;
case "MESH":
ReadMesh(reader);
break;
case "VBUF":
ReadVertexBuffer(reader);
break;
case "IBUF":
ReadIndexBuffer(reader);
break;
case "GPU ":
long startPos = reader.Position;
for (int i = 0; i < Meta.IndexBuffers.Count; i++)
{
var buffer = Meta.IndexBuffers[i];
//First buffer or specific buffer
var info = Meta.ReadBufferInfo[(int)buffer.ReadBufferIndex];
//Seek into the buffer region
reader.SeekBegin(info.Offset + buffer.Offset);
//Decompress
var data = IOFileExtension.DecompressedBuffer(reader, buffer.CompressedSize, buffer.DecompressedSize, IsSwitch);
// if (buffer.DecompressedSize != data.Length)
// throw new Exception();
//All indices
var indices = BufferHelper.LoadIndexBuffer(data, this.IndexBuffer[i].IndexType, IsSwitch);
//Read
foreach (var mesh in Meshes)
{
if (mesh.Header.IndexBufferIndex == i)
{
//Select indices to use
mesh.Indices = indices.Skip((int)mesh.Header.IndexStart).Take((int)mesh.Header.IndexCount).ToArray();
}
}
}
for (int i = 0; i < Meta.VertexBuffers.Count; i++)
{
var vertexInfo = VertexBuffers[i];
var buffer = Meta.VertexBuffers[i];
//First buffer or specific buffer
var info = Meta.ReadBufferInfo[(int)buffer.ReadBufferIndex];
//Seek into the buffer region
reader.SeekBegin(info.Offset + buffer.Offset);
using (reader.TemporarySeek(reader.Position, System.IO.SeekOrigin.Begin))
{
reader.SetByteOrder(false);
var type = reader.ReadUInt32();
reader.SetByteOrder(true);
if (type != 13)
{
byte[] b = reader.ReadBytes((int)buffer.CompressedSize - 4);
System.IO.File.WriteAllBytes($"{Toolbox.Library.Runtime.ExecutableDir}\\VBuffer_{type}_{i}_{buffer.DecompressedSize}.bin", b);
}
}
//Decompress
var data = IOFileExtension.DecompressedBuffer(reader, buffer.CompressedSize, buffer.DecompressedSize, IsSwitch);
if (buffer.DecompressedSize != data.Length)
throw new Exception();
var vertices = BufferHelper.LoadVertexBuffer(data, vertexInfo,IsSwitch);
//Read
foreach (var mesh in Meshes)
{
if (mesh.Header.VertexBufferIndex == i)
{
//Only use the vertices referenced in the indices
//Some meshes use the same big buffer and can add too many unecessary vertices
mesh.SetupVertices(vertices);
}
}
startPos += buffer.CompressedSize;
}
break;
}
}
private void ReadMaterialsU(FileReader reader)
{
uint numMaterials = reader.ReadUInt32();
for (int i = 0; i < numMaterials; i++)
{
CMaterial material = new CMaterial();
Materials.Add(material);
material.Name = reader.ReadZeroTerminatedString();
material.ID = reader.ReadStruct<CObjectId>();
material.Type = reader.ReadStruct<Magic>();
material.Flags = reader.ReadUInt32();
uint numData = reader.ReadUInt32();
//Actual data type data
for (int j = 0; j < numData; j++)
{
var dtype = reader.ReadStruct<Magic>();
uint dformat = reader.ReadUInt32();
Console.WriteLine($"dtype {dtype} {dformat}");
switch (dformat)
{
case 0: //Texture
material.Textures.Add(dtype, reader.ReadStruct<CMaterialTextureTokenData>());
break;
case 1: //Color
material.Colors.Add(dtype, reader.ReadStruct<Color4f>());
break;
case 2: //Scaler
material.Scalars.Add(dtype, reader.ReadSingle());
break;
case 3: //int
material.Int.Add(dtype, reader.ReadInt32());
break;
case 4: //CLayeredTextureData
{
reader.ReadUInt32();
reader.ReadSingles(4); //color
reader.ReadSingles(4); //color
reader.ReadSingles(4); //color
reader.ReadByte(); //Flags
var texture1 = reader.ReadStruct<CObjectId>();
if (!texture1.IsZero())
reader.ReadStruct<STextureUsageInfo>();
var texture2 = reader.ReadStruct<CObjectId>();
if (!texture2.IsZero())
reader.ReadStruct<STextureUsageInfo>();
var texture3 = reader.ReadStruct<CObjectId>();
if (!texture3.IsZero())
reader.ReadStruct<STextureUsageInfo>();
}
break;
case 5: //int4
material.Int4.Add(dtype, reader.ReadInt32s(4));
break;
default:
throw new Exception($"Unsupported material type {dformat}!");
}
}
}
}
private void ReadMaterials(FileReader reader)
{
uint numMaterials = reader.ReadUInt32();
for (int i = 0; i < numMaterials; i++)
{
CMaterial material = new CMaterial();
Materials.Add(material);
uint size = reader.ReadUInt32();
material.Name = reader.ReadString((int)size, true);
material.ID = reader.ReadStruct<CObjectId>();
material.Type = reader.ReadStruct<Magic>();
material.Flags = reader.ReadUInt32();
uint numData = reader.ReadUInt32();
//A list of data types
for (int j = 0; j < numData; j++)
{
var dtype = reader.ReadStruct<Magic>();
var dformat = reader.ReadStruct<Magic>();
}
//Actual data type data
for (int j = 0; j < numData; j++)
{
var dtype = reader.ReadStruct<Magic>();
var dformat = reader.ReadStruct<Magic>();
Console.WriteLine($"dtype {dtype} {dformat}");
switch (dformat)
{
case "TXTR": //Texture
material.Textures.Add(dtype, reader.ReadStruct<CMaterialTextureTokenData>());
break;
case "COLR": //Color
material.Colors.Add(dtype, reader.ReadStruct<Color4f>());
break;
case "SCLR": //Scaler
material.Scalars.Add(dtype, reader.ReadSingle());
break;
case "INT ": //int
material.Int.Add(dtype, reader.ReadInt32());
break;
case "INT4": //int4
material.Int4.Add(dtype, reader.ReadInt32s(4));
break;
case "CPLX": //CLayeredTextureData
{
reader.ReadUInt32();
reader.ReadSingles(4); //color
reader.ReadSingles(4); //color
reader.ReadSingles(4); //color
reader.ReadByte(); //Flags
var texture1 = reader.ReadStruct<CObjectId>();
if (!texture1.IsZero())
reader.ReadStruct<STextureUsageInfo>();
var texture2 = reader.ReadStruct<CObjectId>();
if (!texture2.IsZero())
reader.ReadStruct<STextureUsageInfo>();
var texture3 = reader.ReadStruct<CObjectId>();
if (!texture3.IsZero())
reader.ReadStruct<STextureUsageInfo>();
}
break;
default:
throw new Exception($"Unsupported material type {dformat}!");
}
}
}
}
private void ReadMesh(FileReader reader)
{
uint numMeshes = reader.ReadUInt32();
for (int i = 0; i < numMeshes; i++)
Meshes.Add(new CMesh()
{
Header = reader.ReadStruct<CRenderMesh>(),
});
}
private void ReadVertexBuffer(FileReader reader)
{
uint numBuffers = reader.ReadUInt32();
for (int i = 0; i < numBuffers; i++)
{
VertexBuffer vertexBuffer = new VertexBuffer();
vertexBuffer.VertexCount = reader.ReadUInt32();
uint numAttributes = reader.ReadUInt32();
for (int j = 0; j < numAttributes; j++)
vertexBuffer.Components.Add(reader.ReadStruct<SVertexDataComponent>());
VertexBuffers.Add(vertexBuffer);
}
}
private void ReadIndexBuffer(FileReader reader)
{
uint numBuffers = reader.ReadUInt32();
for (int i = 0; i < numBuffers; i++)
IndexBuffer.Add(reader.ReadStruct<CGraphicsIndexBufferToken>());
}
public class VertexBuffer
{
public List<SVertexDataComponent> Components = new List<SVertexDataComponent>();
public uint VertexCount;
}
public class CVertex
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TexCoord0;
public Vector2 TexCoord1;
public Vector2 TexCoord2;
public Vector4 BoneWeights = new Vector4(1, 0, 0, 0);
public Vector4 BoneIndices = new Vector4(0);
public Vector4 Color = Vector4.One;
public Vector4 Tangent;
}
public class CMaterial
{
public string Name { get; set; }
public string Type { get; set; }
public CObjectId ID { get; set; }
public uint Flags { get; set; }
public Dictionary<string, CMaterialTextureTokenData> Textures = new Dictionary<string, CMaterialTextureTokenData>();
public Dictionary<string, float> Scalars = new Dictionary<string, float>();
public Dictionary<string, int> Int = new Dictionary<string, int>();
public Dictionary<string, int[]> Int4 = new Dictionary<string, int[]>();
public Dictionary<string, Color4f> Colors = new Dictionary<string, Color4f>();
}
public class CMesh
{
public CRenderMesh Header;
public List<CVertex> Vertices = new List<CVertex>();
public uint[] Indices;
public void SetupVertices(List<CVertex> vertices)
{
//Here we optmize the vertices to only use the vertices used by the mesh rather than use one giant list
List<CVertex> vertexList = new List<CVertex>();
List<uint> remappedIndices = new List<uint>();
for (int i = 0; i < Indices.Length; i++)
{
remappedIndices.Add((uint)vertexList.Count);
vertexList.Add(vertices[(int)Indices[i]]);
}
this.Vertices = vertexList;
this.Indices = remappedIndices.ToArray();
}
}
public class SSkinnedModelHeader : CChunkDescriptor
{
public uint Unknown;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CRenderMesh
{
public PrimtiiveType PrimtiiveMode;
public ushort MaterialIndex;
public byte VertexBufferIndex;
public byte IndexBufferIndex;
public uint IndexStart;
public uint IndexCount;
public ushort field_10;
public byte field_12;
public byte field_13;
public byte field_14;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CMaterialTextureTokenData
{
public CObjectId FileID;
public STextureUsageInfo UsageInfo;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class STextureUsageInfo
{
public uint Flags;
public uint TextureFilter;
public uint TextureWrapX;
public uint TextureWrapY;
public uint TextureWrapZ;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SModelHeader
{
public uint NumOpaqueMeshes;
public uint Num1PassTranslucentMeshes;
public uint Num2PassTranslucentMeshes;
public uint Num1BitMeshes;
public uint NumAdditiveMeshes;
public CAABox BoundingBox;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CGraphicsIndexBufferToken
{
public IndexFormat IndexType;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SVertexDataComponent
{
public uint field_0;
public uint Offset;
public uint Stride;
public VertexFormat Format;
public EVertexComponent Type;
}
public enum IndexFormat
{
Uint16 = 1,
Uint32 = 2,
}
public enum PrimtiiveType
{
Triangles = 3,
}
public enum VertexFormat
{
Byte = 0,
Format_16_16_HalfSingle = 20,
Format_8_8_8_8_Uint = 22,
Format_16_16_16_HalfSingle = 34,
Format_32_32_32_Single = 37,
}
public enum EVertexComponent
{
in_position,
in_normal,
in_tangent0,
in_tangent1,
in_texCoord0,
in_texCoord1,
in_texCoord2,
in_texCoord3,
in_color,
in_boneIndices,
in_boneWeights,
in_bakedLightingCoord,
in_bakedLightingTangent,
in_vertInstanceColor,
//3x4 matrices
in_vertTransform0,
in_vertTransform1,
in_vertTransform2,
//3x4 matrices for instancing
in_vertTransformIT0,
in_vertTransformIT1,
in_vertTransformIT2,
in_lastPosition,
in_currentPosition,
}
//Meta data from PAK archive
public class SMetaData
{
public uint Unknown;
public uint GPUOffset;
public List<SReadBufferInfo> ReadBufferInfo = new List<SReadBufferInfo>();
public List<SBufferInfo> VertexBuffers = new List<SBufferInfo>();
public List<SBufferInfo> IndexBuffers = new List<SBufferInfo>();
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SReadBufferInfo
{
public uint Size;
public uint Offset;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SBufferInfo
{
public uint ReadBufferIndex;
public uint Offset;
public uint CompressedSize;
public uint DecompressedSize;
}
}
}

View file

@ -0,0 +1,316 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using Toolbox.Library.IO;
namespace DKCTF
{
/// <summary>
/// Represents the base of a file format.
/// </summary>
public class FileForm
{
/// <summary>
/// The form header containing the type and version of the file.
/// </summary>
public CFormDescriptor FileHeader;
public FileForm() { }
public FileForm(Stream stream, bool leaveOpen = false)
{
using (var reader = new FileReader(stream, leaveOpen))
{
reader.SetByteOrder(true);
FileHeader = reader.ReadStruct<CFormDescriptor>();
Read(reader);
AfterLoad();
}
}
/// <summary>
/// Reads the file by looping through chunks..
/// </summary>
public virtual void Read(FileReader reader)
{
var endPos = ReadMetaFooter(reader);
while (reader.BaseStream.Position < endPos)
{
var chunk = reader.ReadStruct<CChunkDescriptor>();
var pos = reader.Position;
reader.SeekBegin(pos + chunk.DataOffset);
ReadChunk(reader, chunk);
reader.SeekBegin(pos + chunk.DataSize);
}
}
/// <summary>
/// Reads a specified chunk.
/// </summary>
public virtual void ReadChunk(FileReader reader, CChunkDescriptor chunk)
{
}
/// <summary>
/// Reads meta data information within the pak archive.
/// </summary>
public virtual void ReadMetaData(FileReader reader)
{
}
/// <summary>
/// Writes meta data information within the pak archive.
/// </summary>
public virtual void WriteMetaData(FileWriter writer)
{
}
/// <summary>
/// Executes after the file has been fully read.
/// </summary>
public virtual void AfterLoad()
{
}
/// <summary>
/// Reads the tool created footer containing meta data used for decompressing buffer data.
/// </summary>
public long ReadMetaFooter(FileReader reader)
{
using (reader.TemporarySeek(reader.BaseStream.Length - 12, SeekOrigin.Begin))
{
if (reader.ReadString(4, Encoding.ASCII) != "META")
return reader.BaseStream.Length;
}
using (reader.TemporarySeek(reader.BaseStream.Length - 12, SeekOrigin.Begin))
{
reader.ReadSignature(4, "META");
reader.ReadString(4); //type of file
uint size = reader.ReadUInt32(); //size of meta data
//Seek back to meta data
reader.SeekBegin(reader.Position - size);
//Read meta data
ReadMetaData(reader);
return reader.BaseStream.Length - size;
}
}
/// <summary>
/// Writes a footer to a file for accessing meta data information outside a .pak archive.
/// </summary>
public static byte[] WriteMetaFooter(FileReader reader, uint metaOffset, string type)
{
//Magic + meta offset first
var mem = new MemoryStream();
using (var writer = new FileWriter(mem))
{
writer.SetByteOrder(true);
reader.SeekBegin(metaOffset);
var file = GetFileForm(type);
file.ReadMetaData(reader);
file.WriteMetaData(writer);
//Write footer header last
writer.WriteSignature("META");
writer.WriteSignature(type);
writer.Write((uint)(writer.BaseStream.Length + 4)); //size
}
return mem.ToArray();
}
//Creates file instances for read/writing meta entries from pak archives
static FileForm GetFileForm(string type)
{
switch (type)
{
case "CMDL": return new CMDL();
case "SMDL": return new CMDL();
case "WMDL": return new CMDL();
case "TXTR": return new TXTR();
}
return new FileForm();
}
}
//Documentation from https://github.com/Kinnay/Nintendo-File-Formats/wiki/DKCTF-Types#cformdescriptor
/// <summary>
/// Represents the header of a file format.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CFormDescriptor
{
public Magic Magic = "RFRM";
public ulong DataSize;
public ulong Unknown;
public Magic FormType; //File type identifier
public uint VersionA;
public uint VersionB;
}
/// <summary>
/// Represents the header of a chunk of a file form.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CChunkDescriptor
{
public Magic ChunkType;
public long DataSize;
public uint Unknown;
public long DataOffset;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CMayaSpline
{
public Magic ChunkType;
public ulong DataSize;
public uint Unknown;
public ulong DataOffset;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CMayaSplineKnot
{
public float Time;
public float Value;
public ETangentType TangentType1;
public ETangentType TangentType2;
public float FieldC;
public float Field10;
public float Field14;
public float Field18;
}
/// <summary>
/// Tag data for an object providing the type and id.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CObjectTag
{
public Magic Type;
public CObjectId Objectid;
}
/// <summary>
/// A header for asset data providing a type and version number.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CAssetHeader
{
public ushort TypeID;
public ushort Version;
}
/// <summary>
/// Stores a unique ID for a given object to identify it.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CObjectId
{
public CGuid Guid;
public override string ToString()
{
return Guid.ToString();
}
public bool IsZero()
{
return Guid.Part1 == 0 &&
Guid.Part2 == 0 &&
Guid.Part3 == 0 &&
Guid.Part4 == 0;
}
}
/// <summary>
/// An axis aligned bounding box with a min and max position value.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CAABox
{
public Vector3f Min;
public Vector3f Max;
}
/// <summary>
/// A vector with X/Y/Z axis values.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Vector3f
{
public float X;
public float Y;
public float Z;
}
/// <summary>
/// A color struct of RGBA values.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class Color4f
{
public float R;
public float G;
public float B;
public float A;
}
/// <summary>
/// A 128 bit guid for identifying objects.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CGuid
{
public uint Part1;
public ushort Part2;
public ushort Part3;
public ulong Part4;
public Guid ToGUID()
{
var bytes = BitConverter.GetBytes(Part4).Reverse().ToArray();
return new Guid(Part1, Part2, Part3, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]);
}
public override string ToString() //Represented based on output guids in demo files
{
return ToGUID().ToString();
}
}
public enum ETangentType
{
Linear,
Flat,
Smooth,
Step,
Clamped,
Fixed,
}
public enum EInfinityType
{
Constant,
Linear,
Cycle,
CycleRelative,
Oscillate,
}
}

View file

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Toolbox.Library.IO;
using System.Runtime.InteropServices;
namespace DKCTF
{
internal class IOFileExtension
{
public static void WriteList<T>(FileWriter writer, List<T> list)
{
writer.Write(list.Count);
for (int i = 0; i < list.Count; i++)
writer.WriteStruct(list[i]);
}
public static string ReadFixedString(FileReader reader, bool isSwitch = true)
{
if (!isSwitch)
return reader.ReadZeroTerminatedString();
uint len = reader.ReadUInt32();
return reader.ReadString((int)len, true);
}
public static List<T> ReadList<T>(FileReader reader)
{
List<T> list = new List<T>();
uint count = reader.ReadUInt32();
for (int i = 0; i < count; i++)
list.Add(reader.ReadStruct<T>());
return list;
}
public static CObjectId ReadID(FileReader reader)
{
return new CObjectId()
{
Guid = new CGuid()
{
Part1 = reader.ReadUInt32(),
Part2 = reader.ReadUInt16(),
Part3 = reader.ReadUInt16(),
Part4 = reader.ReadUInt64(),
},
};
}
///
public static byte[] DecompressedBuffer(FileReader reader, uint compSize, uint decompSize, bool isSwitch = true)
{
reader.SetByteOrder(false);
CompressionType type = (CompressionType)reader.ReadUInt32();
reader.SetByteOrder(true);
Console.WriteLine($"type {type}");
// File.WriteAllBytes($"Buffer{type}.bin", reader.ReadBytes((int)(compSize - 4)));
var data = reader.ReadBytes((int)compSize - 4);
switch (type)
{
case CompressionType.None:
return reader.ReadBytes((int)decompSize);
//LZSS with byte, short, and uint types
case CompressionType.LZSS_8: return DecompressLZSS(data, 1, decompSize);
case CompressionType.LZSS_16: return DecompressLZSS(data, 2, decompSize);
case CompressionType.LZSS_32: return DecompressLZSS(data, 3, decompSize);
case CompressionType.ZLib:
return STLibraryCompression.ZLIB.Decompress(data);
default:
return new byte[decompSize];
}
}
public enum CompressionType //8 = byte, 16 = short, 32 = uint32
{
None,
LZSS_8 = 0x1,
LZSS_16 = 0x2,
LZSS_32 = 0x3,
ArithmeticStream_LZSS_8 = 0x4,
ArithmeticStream_LZSS_16 = 0x5,
ArithmeticStream_LZSS_32 = 0x6,
LZSS_8_3Byte = 0x7,
LZSS_16_3Byte = 0x8,
LZSS_32_3Byte = 0x9,
ArithmeticStream_LZSS_8_3Byte = 0xA,
ArithmeticStream_LZSS_16_3Byte = 0xB,
ArithmeticStream_LZSS_32_3Byte = 0xC,
ZLib = 0xD,
}
public static byte[] DecompressLZSS(byte[] input, int mode, uint decompressedLength)
{
byte[] decomp = new byte[decompressedLength];
int src = 0;
int dst = 0;
// Otherwise, start preparing for decompression.
byte header_byte = 0;
byte group = 0;
while (src < input.Length && dst < decompressedLength)
{
// group will start at 8 and decrease by 1 with each data chunk read.
// When group reaches 0, we read a new header byte and reset it to 8.
if (group == 0)
{
header_byte = input[src++];
group = 8;
}
// header_byte will be shifted left one bit for every data group read, so 0x80 always corresponds to the current data group.
// If 0x80 is set, then we read back from the decompressed buffer.
if ((header_byte & 0x80) != 0)
{
byte[] bytes = new byte[] { input[src++], input[src++] };
uint count = 0, length = 0;
switch (mode)
{
case 1: //byte
count = (uint)((bytes[0] >> 4) + 3);
length = (uint)(((bytes[0] & 0xF) << 0x8) | bytes[1]);
break;
case 2: //short
count = (uint)((bytes[0] >> 4) + 2);
length = (uint)((((bytes[0] & 0xF) << 0x8) | bytes[1]) << 1);
break;
case 3: //uint
count = (uint)((bytes[0] >> 4) + 1);
length = (uint)((((bytes[0] & 0xF) << 0x8) | bytes[1]) << 2);
break;
}
// With the count and length calculated, we'll set a pointer to where we want to read back data from:
int seek = (dst - (int)length);
// count refers to how many byte groups to read back; the size of one byte group varies depending on mode
for (uint yb = 0; yb < count; yb++)
{
switch (mode)
{
case 1: //byte
decomp[dst++] = decomp[(int)seek++];
break;
case 2: //short
for (uint b = 0; b < 2; b++)
decomp[dst++] = decomp[(int)seek++];
break;
case 3: //uint
for (uint b = 0; b < 4; b++)
decomp[dst++] = decomp[(int)seek++];
break;
}
}
}
else
{
// If 0x80 is not set, then we read one byte group directly from the compressed buffer.
switch (mode)
{
case 1: //byte
decomp[dst++] = input[src++];
break;
case 2: //short
for (uint b = 0; b < 2; b++)
decomp[dst++] = input[src++];
break;
case 3: //uint
for (uint b = 0; b < 4; b++)
decomp[dst++] = input[src++];
break;
}
}
header_byte <<= 1;
group--;
}
return decomp;
}
public static byte[] DecompressArithmeticStream(byte[] input, int mode, uint decompressedLength)
{
//TODO
return input;
}
public static byte[] DecompressLZSS3Bytes(byte[] input, int mode, uint decompressedLength)
{
byte[] decomp = new byte[decompressedLength];
//TODO
return decomp;
}
}
}

View file

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Toolbox.Library.IO;
namespace DKCTF
{
/// <summary>
/// Represents an package file format for storing file data.
/// </summary>
public class PACK : FileForm
{
public CFormDescriptor TocHeader;
public List<DirectoryAssetEntry> Assets = new List<DirectoryAssetEntry>();
public List<CNameTagEntry> NameTagEntries = new List<CNameTagEntry>();
public Dictionary<string, uint> MetaOffsets = new Dictionary<string, uint>();
public long MetaDataOffset;
public PACK() { }
public PACK(System.IO.Stream stream) : base(stream, true)
{
}
public override void Read(FileReader reader)
{
TocHeader = reader.ReadStruct<CFormDescriptor>();
long p = reader.Position;
while (reader.BaseStream.Position < p + (long)TocHeader.DataSize)
{
var chunk = reader.ReadStruct<CChunkDescriptor>();
var pos = reader.Position;
reader.SeekBegin(pos + chunk.DataOffset);
ReadChunk(reader, chunk);
reader.SeekBegin(pos + chunk.DataSize);
}
}
public override void ReadChunk(FileReader reader, CChunkDescriptor chunk)
{
switch (chunk.ChunkType)
{
case "TOCC":
TocHeader = reader.ReadStruct<CFormDescriptor>();
break;
case "ADIR":
ReadAssetDirectoryChunk(reader);
break;
case "META":
ReadMetaChunk(reader);
break;
case "STRG":
ReadFileNameChunk(reader);
break;
}
}
private void ReadAssetDirectoryChunk(FileReader reader)
{
uint numEntries = reader.ReadUInt32();
for (int i = 0; i < numEntries; i++)
{
DirectoryAssetEntry entry = new DirectoryAssetEntry();
entry.Read(reader);
Assets.Add(entry);
}
}
private void ReadMetaChunk(FileReader reader)
{
MetaDataOffset = reader.Position + 4;
uint numEntries = reader.ReadUInt32();
for (int i = 0; i < numEntries; i++)
{
var id = IOFileExtension.ReadID(reader);
uint offset = reader.ReadUInt32();
MetaOffsets.Add(id.ToString(), offset);
}
}
private void ReadFileNameChunk(FileReader reader)
{
uint numEntries = reader.ReadUInt32();
for (int i = 0; i < numEntries; i++)
{
string type = reader.ReadString(4, Encoding.ASCII);
var id = IOFileExtension.ReadID(reader);
string name = reader.ReadZeroTerminatedString();
// reader.Align(4);
NameTagEntries.Add(new CNameTagEntry()
{
Name = name,
FileID = new CObjectTag()
{
Type = type,
Objectid = id,
},
});
}
}
public class DirectoryAssetEntry
{
public string Type;
public CObjectId FileID;
public long Offset;
public long Size;
public void Read(FileReader reader)
{
Type = reader.ReadString(4, Encoding.ASCII);
FileID = IOFileExtension.ReadID(reader);
Offset = reader.ReadInt64();
Size = reader.ReadInt64();
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class MetaOffsetEntry
{
public CObjectId FileID;
public uint FileOffset;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class PakHeader
{
CFormDescriptor PackForm;
CFormDescriptor TocForm;
}
public class CNameTagEntry
{
public CObjectTag FileID;
public string Name;
}
}
}

View file

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Toolbox.Library.IO;
using OpenTK;
using Toolbox.Library;
namespace DKCTF
{
/// <summary>
/// Represents a skeletal file format.
/// </summary>
public class SKEL : FileForm
{
public List<string> JointNames = new List<string>();
public List<string> JointNamesVis = new List<string>();
public List<JointSet> JointSets = new List<JointSet>();
public List<BoneCoord> JointCoords = new List<BoneCoord>();
public byte[] SkinnedBonesRemap = new byte[0];
public byte[] BoneParentingIndices = new byte[0];
CAnimationAttrData AnimationAttributes = null;
CSkelMap SkelMap = null;
public SKEL() { }
public SKEL(System.IO.Stream stream) : base(stream)
{
}
public override void Read(FileReader reader)
{
reader.ReadStruct<CAssetHeader>(); //version 0x9e22
//CSkelLayout
//CJointNameArray
JointNames = ReadCJointNameArray(reader);
ushort numTotalJoints = reader.ReadUInt16(); //Includes vis and attribute nodes
ushort numJoints = reader.ReadUInt16(); //Normal joint list with coordinates associated
ushort numSkinnedJoints = reader.ReadUInt16();
ushort numJointSets = reader.ReadUInt16();
ushort numUnkE = reader.ReadUInt16();
bool hasSkeletonMap = reader.ReadBoolean();
//CSkelMap
if (hasSkeletonMap)
{
SkelMap = new CSkelMap();
ushort numRemap = reader.ReadUInt16();
byte numUnk1 = reader.ReadByte();
byte numUnk2 = reader.ReadByte();
SkelMap.JointIndices = reader.ReadBytes(numRemap);
SkelMap.Unk2 = reader.ReadUInt16s(numRemap * 2);
SkelMap.Unk3 = reader.ReadUInt32s(numUnk1);
SkelMap.Unk4 = reader.ReadInt16s(numUnk2);
SkelMap.Flag = reader.ReadUInt32();
}
bool hasAnimationAttributes = reader.ReadBoolean();
//CAnimationAttrData
if (hasAnimationAttributes)
{
bool hasVisibilityNameGroup = reader.ReadBoolean();
if (hasVisibilityNameGroup)
{
JointNamesVis = ReadCJointNameArray(reader);
}
uint stateParam1 = reader.ReadUInt32();
uint stateParam2 = reader.ReadUInt32();
bool hasAnimationAttributeData = reader.ReadBoolean();
if (hasAnimationAttributeData)
{
AnimationAttributes = new CAnimationAttrData();
AnimationAttributes.Joints = ReadCJointNameArray(reader);
uint numAttributes = reader.ReadUInt32();
for (int i = 0; i < numAttributes; i++)
{
CAnimAttrInfo att = new CAnimAttrInfo();
att.Flag = reader.ReadUInt32();
if (att.Flag == 1)
{
att.Value1 = reader.ReadSingle();
att.Value2 = reader.ReadSingle();
}
AnimationAttributes.Attributes.Add(att);
}
}
}
BoneParentingIndices = reader.ReadBytes((int)numJoints);
SkinnedBonesRemap = reader.ReadBytes((int)numSkinnedJoints);
byte[] unkA = reader.ReadBytes((int)numTotalJoints);
byte[] unkE = reader.ReadBytes((int)numUnkE);
uint[] unkD = reader.ReadUInt32s((int)numJointSets);
for (int i = 0; i < numJoints; i++)
{
var rot = reader.ReadQuaternion();
JointCoords.Add(new BoneCoord()
{
Rotation = new Quaternion(rot.Y, rot.Z, rot.W, rot.X),
Scale = reader.ReadVec3(),
Position = reader.ReadVec3(),
});
}
for (int i = 0; i < numJointSets; i++)
{
JointSet jointSet = new JointSet();
jointSet.unk1 = reader.ReadUInt32();
uint jointSetCount = reader.ReadUInt32();
jointSet.unk2 = reader.ReadUInt32s(8);
jointSet.JointIndices = reader.ReadBytes((int)jointSetCount);
JointSets.Add(jointSet);
}
Console.WriteLine();
}
public STSkeleton ToGenericSkeleton()
{
STSkeleton skeleton = new STSkeleton();
for (int i = 0; i < this.JointCoords.Count; i++)
{
var remap = SkelMap == null ? i : SkelMap.JointIndices[i];
var parentID = this.BoneParentingIndices[i];
var name = this.JointNames[i];
var coord = this.JointCoords[i];
skeleton.bones.Add(new STBone(skeleton)
{
RotationType = STBone.BoneRotationType.Quaternion,
Text = name,
Position = coord.Position,
Rotation = coord.Rotation,
Scale = coord.Scale,
parentIndex = parentID == 255 ? -1 : parentID,
});
}
skeleton.reset();
skeleton.update();
return skeleton;
}
public Matrix4 CalculateLocalMatrix(BoneCoord coord, int id)
{
if (BoneParentingIndices[id] == 255)
return CreateWorldMatrix(coord);
var parentMatrix = CreateWorldMatrix(JointCoords[BoneParentingIndices[id]]);
return parentMatrix.Inverted();
}
public Matrix4 CreateWorldMatrix(BoneCoord coord)
{
return Matrix4.CreateScale(coord.Scale) *
Matrix4.CreateFromQuaternion(coord.Rotation) *
Matrix4.CreateTranslation(coord.Position);
}
private List<string> ReadCJointNameArray(FileReader reader)
{
List<string> joints = new List<string>();
uint field_0 = reader.ReadUInt32();
uint count = reader.ReadUInt32();
for (int i = 0; i < count; i++)
{
joints.Add(IOFileExtension.ReadFixedString(reader, true));
}
uint unk2 = reader.ReadUInt32();
return joints;
}
public class BoneCoord
{
public Vector3 Position;
public Vector3 Scale;
public Quaternion Rotation;
}
public class CSkelMap
{
public byte[] JointIndices;
public ushort[] Unk2;
public uint[] Unk3;
public short[] Unk4;
public uint Flag;
}
public class CAnimationAttrData
{
public List<string> Joints = new List<string>();
public List<CAnimAttrInfo> Attributes = new List<CAnimAttrInfo>();
}
public class CAnimAttrInfo
{
public uint Flag;
public float Value1;
public float Value2;
}
public class JointSet
{
public uint unk1;
public uint[] unk2;
public byte[] JointIndices;
}
}
}

View file

@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Toolbox.Library.IO;
namespace DKCTF
{
/// <summary>
/// Represents a texture file format.
/// </summary>
internal class TXTR : FileForm
{
public STextureHeader TextureHeader;
public SMetaData Meta;
public byte[] BufferData;
public uint[] MipSizes = new uint[0];
public uint TextureSize { get; set; }
public uint Unknown { get; set; }
public bool IsSwitch => this.FileHeader.VersionA >= 0x0F;
public TXTR() { }
public TXTR(System.IO.Stream stream) : base(stream)
{
}
public byte[] CreateUncompressedFile(byte[] fileData)
{
var mem = new MemoryStream();
using (var writer = new FileWriter(mem))
using (var reader = new FileReader(fileData))
{
writer.SetByteOrder(true);
reader.SetByteOrder(true);
FileHeader = reader.ReadStruct<CFormDescriptor>();
ReadMetaFooter(reader);
reader.Position = 0;
byte[] textureInfo = reader.ReadBytes((int)Meta.GPUOffset + 24);
long pos = reader.BaseStream.Position - 24;
var buffer = Meta.BufferInfo[0];
reader.Seek(buffer.Offset);
byte[] BufferData = IOFileExtension.DecompressedBuffer(reader, buffer.CompressedSize, buffer.DecompressedSize, IsSwitch);
writer.Write(textureInfo);
writer.Write(BufferData);
using (writer.TemporarySeek(pos + 4, SeekOrigin.Begin))
{
writer.Write((long)BufferData.Length);
}
return mem.ToArray();
}
}
public override void ReadChunk(FileReader reader, CChunkDescriptor chunk)
{
switch (chunk.ChunkType)
{
case "HEAD":
TextureHeader = reader.ReadStruct<STextureHeader>();
uint numMips = reader.ReadUInt32();
MipSizes = reader.ReadUInt32s((int)numMips);
TextureSize = reader.ReadUInt32();
Unknown = reader.ReadUInt32();
break;
case "GPU ":
if (Meta != null)
{
var buffer = Meta.BufferInfo[0];
reader.Seek(buffer.Offset);
BufferData = IOFileExtension.DecompressedBuffer(reader, buffer.CompressedSize, buffer.DecompressedSize, IsSwitch);
}
else
{
BufferData = reader.ReadBytes((int)chunk.DataSize);
}
break;
}
}
public override void ReadMetaData(FileReader reader)
{
Meta = new SMetaData();
Meta.Unknown = reader.ReadUInt32();
Meta.AllocCategory = reader.ReadUInt32();
Meta.GPUOffset = reader.ReadUInt32();
Meta.BaseAlignment = reader.ReadUInt32();
Meta.GPUDataStart = reader.ReadUInt32();
Meta.GPUDataSize = reader.ReadUInt32();
Meta.BufferInfo = IOFileExtension.ReadList<SCompressedBufferInfo>(reader);
}
public override void WriteMetaData(FileWriter writer)
{
writer.Write(Meta.Unknown);
writer.Write(Meta.AllocCategory);
writer.Write(Meta.GPUOffset);
writer.Write(Meta.BaseAlignment);
writer.Write(Meta.GPUDataStart);
writer.Write(Meta.GPUDataSize);
IOFileExtension.WriteList(writer, Meta.BufferInfo);
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class STextureHeader
{
public uint Type;
public uint Format;
public uint Width;
public uint Height;
public uint Depth;
public uint TileMode;
public uint Swizzle;
}
//Meta data from PAK archive
public class SMetaData
{
public uint Unknown; //4
public uint AllocCategory;
public uint GPUOffset;
public uint BaseAlignment;
public uint GPUDataStart;
public uint GPUDataSize;
public List<SCompressedBufferInfo> BufferInfo = new List<SCompressedBufferInfo>();
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SCompressedBufferInfo
{
public uint DecompressedSize;
public uint CompressedSize;
public uint Offset;
}
}
}

View file

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Toolbox.Library; using Toolbox.Library;
using Toolbox.Library.IO; using Toolbox.Library.IO;
using System.IO;
namespace DKCTF namespace DKCTF
{ {
@ -50,101 +51,98 @@ namespace DKCTF
public void ClearFiles() { files.Clear(); } public void ClearFiles() { files.Clear(); }
private PakHeader Header; //For file searching
private List<DirectoryAssetEntry> Directories = new List<DirectoryAssetEntry>(); public Dictionary<string, FileEntry> ModelFiles = new Dictionary<string, FileEntry>();
private List<MetaOffsetEntry> MetaOffsets = new List<MetaOffsetEntry>(); public Dictionary<string, FileEntry> SkeletonFiles = new Dictionary<string, FileEntry>();
private List<CNameTagEntry> FileNameEntries = new List<CNameTagEntry>(); public Dictionary<string, FileEntry> TextureFiles = new Dictionary<string, FileEntry>();
public Dictionary<string, CHAR> CharFiles = new Dictionary<string, CHAR>();
public Dictionary<string, FileEntry> AnimFiles = new Dictionary<string, FileEntry>();
public void Load(System.IO.Stream stream) public void Load(System.IO.Stream stream)
{ {
Directories.Clear(); PACK pack = new PACK(stream);
MetaOffsets.Clear();
FileNameEntries.Clear();
using (var reader = new FileReader(stream, true)) for (int i = 0; i < pack.Assets.Count; i++)
{ {
reader.SetByteOrder(true); string ext = pack.Assets[i].Type.ToLower();
Header = reader.ReadStruct<PakHeader>();
var ADIRChunk = reader.ReadStruct<CChunkDescriptor>();
ReadAssetDirectoryChunk(reader, ADIRChunk);
var METAChunk = reader.ReadStruct<CChunkDescriptor>();
ReadMetaChunk(reader, METAChunk);
var STRGChunk = reader.ReadStruct<CChunkDescriptor>();
ReadFileNameChunk(reader, STRGChunk);
}
for (int i = 0; i < Directories.Count; i++) FileEntry file = new FileEntry();
{ file.ParentArchive = this;
files.Add(new FileEntry() file.ArchiveStream = stream;
file.AssetEntry = pack.Assets[i];
string dir = pack.Assets[i].Type;
if (DirectoryLabels.ContainsKey(dir))
dir = DirectoryLabels[dir];
file.FileName = $"{dir}/{pack.Assets[i].FileID}.{ext}";
file.SubData = new SubStream(stream, pack.Assets[i].Offset, pack.Assets[i].Size);
if (pack.MetaOffsets.ContainsKey(pack.Assets[i].FileID.ToString()))
file.MetaPointer = pack.MetaDataOffset + pack.MetaOffsets[pack.Assets[i].FileID.ToString()];
files.Add(file);
switch (file.AssetEntry.Type)
{ {
FileData = new byte[0], case "SMDL": ModelFiles.Add(file.AssetEntry.FileID.ToString(), file); break;
FileDataStream = Directories[i].Data, case "TXTR": TextureFiles.Add(file.AssetEntry.FileID.ToString(), file); break;
FileName = Directories[i].Type, case "SKEL": SkeletonFiles.Add(file.AssetEntry.FileID.ToString(), file); break;
}); case "ANIM": AnimFiles.Add(file.AssetEntry.FileID.ToString(), file); break;
case "CHAR":
/* var file = STFileLoader.OpenFileFormat(subStream, Directories[i].ToString()); var c = new CHAR(new MemoryStream(file.FileData));
if (file != null && file is TreeNodeFile) file.FileName = $"Characters/{c.Name}/{c.Name}.char";
{ CharFiles.Add(file.AssetEntry.FileID.ToString(), c);
Nodes.Add((TreeNodeFile)file); break;
} }
else
{
}*/
} }
}
private void ReadAssetDirectoryChunk(FileReader reader, CChunkDescriptor chunk) foreach (var c in CharFiles)
{
if (chunk.ChunkType != "ADIR")
throw new Exception("Unexpected type! Expected ADIR, got " + chunk.ChunkType);
long pos = reader.Position;
reader.SeekBegin(pos + chunk.DataOffset);
uint numEntries = reader.ReadUInt32();
for (int i = 0; i < numEntries; i++)
{ {
DirectoryAssetEntry entry = new DirectoryAssetEntry(); SkeletonFiles[c.Value.SkeletonFileID.ToString()].FileName = $"Characters/{c.Value.Name}/Models/{c.Value.SkeletonFileID}.skel";
entry.Read(reader);
Directories.Add(entry); foreach (var m in c.Value.Models)
{
if (ModelFiles.ContainsKey(m.FileID.ToString()))
ModelFiles[m.FileID.ToString()].FileName = $"Characters/{c.Value.Name}/Models/{m.Name}.smdl";
}
foreach (var m in c.Value.Animations)
{
if (AnimFiles.ContainsKey(m.FileID.ToString()))
AnimFiles[m.FileID.ToString()].FileName = $"Characters/{c.Value.Name}/Animations/{m.Name}.anim";
}
} }
foreach (var file in files)
reader.SeekBegin(pos + chunk.DataSize);
}
private void ReadMetaChunk(FileReader reader, CChunkDescriptor chunk)
{
if (chunk.ChunkType != "META")
throw new Exception("Unexpected type! Expected META, got " + chunk.ChunkType);
long pos = reader.Position;
reader.SeekBegin(pos + chunk.DataOffset);
uint numEntries = reader.ReadUInt32();
for (int i = 0; i < numEntries; i++)
{ {
MetaOffsetEntry entry = reader.ReadStruct< MetaOffsetEntry>(); if (PakFileList.GuiToFilePath.ContainsKey(file.AssetEntry.FileID.ToString()))
MetaOffsets.Add(entry); {
file.FileName = "_LabeledFiles/" + PakFileList.GuiToFilePath[file.AssetEntry.FileID.ToString()];
//Organize the data type folders for easier access.
if (file.AssetEntry.Type == "SMDL") file.FileName = file.FileName.Replace("exportData", "models");
if (file.AssetEntry.Type == "CMDL") file.FileName = file.FileName.Replace("exportData", "models");
if (file.AssetEntry.Type == "TXTR") file.FileName = file.FileName.Replace("exportData", "textures");
if (file.AssetEntry.Type == "ANIM") file.FileName = file.FileName.Replace("exportData", "animations");
}
} }
files = files.OrderBy(x => x.FileName).ToList();
reader.SeekBegin(pos + chunk.DataSize);
} }
private void ReadFileNameChunk(FileReader reader, CChunkDescriptor chunk) Dictionary<string, string> DirectoryLabels = new Dictionary<string, string>()
{ {
if (chunk.ChunkType != "STRG") { "CHAR", "Characters" },
throw new Exception("Unexpected type! Expected STRG, got " + chunk.ChunkType); { "CMDL", "Static Models" },
{ "SMDL", "Skinned Models" },
{ "TXTR", "Textures" },
{ "MTRL", "Shaders" },
{ "CSMP", "AudioSample" },
{ "CAUD", "AudioData" },
{ "GENP", "Gpsys" },
{ "ANIM", "Animations" },
{ "XFRM", "Xfpsys" },
{ "WMDL", "World Models" },
{ "DCLN", "Collision Models" },
{ "CLSN", "Collision Static Models" },
};
long pos = reader.Position;
reader.SeekBegin(pos + chunk.DataOffset);
uint numEntries = reader.ReadUInt32();
for (int i = 0; i < numEntries; i++)
{
// CNameTagEntry entry = reader.ReadStruct<CNameTagEntry>();
// FileNameEntries.Add(entry);
}
reader.SeekBegin(pos + chunk.DataSize);
}
public void Unload() public void Unload()
{ {
@ -168,53 +166,80 @@ namespace DKCTF
public class FileEntry : ArchiveFileInfo public class FileEntry : ArchiveFileInfo
{ {
public PACK.DirectoryAssetEntry AssetEntry;
} public PAK ParentArchive;
public class DirectoryAssetEntry public long MetaPointer;
{
public string Type;
public CObjectId FileID;
public long Offset; public Stream SubData;
public long Size;
public SubStream Data; public Stream ArchiveStream;
public void Read(FileReader reader) public override byte[] FileData
{ {
Type = reader.ReadString(4, Encoding.ASCII); get
FileID = reader.ReadStruct<CObjectId>(); {
Offset = reader.ReadInt64(); List<byte[]> Data = new List<byte[]>();
Size = reader.ReadInt64();
Data = new SubStream(reader.BaseStream, Offset,Size); using (var reader = new FileReader(SubData, true))
{
Data.Add(reader.ReadBytes((int)reader.BaseStream.Length));
if (MetaPointer != null)
{
using (var r = new FileReader(ArchiveStream, true)) {
r.SetByteOrder(true);
Data.Add(FileForm.WriteMetaFooter(r, (uint)MetaPointer, AssetEntry.Type));
}
}
}
if (AssetEntry.Type == "TXTR")
{
var txt = new TXTR();
return txt.CreateUncompressedFile(Utils.CombineByteArray(Data.ToArray()));
}
return Utils.CombineByteArray(Data.ToArray());
}
} }
public override string ToString() public override IFileFormat OpenFile()
{ {
return $"{FileID.Guid.Part4.ToString()}.{Type}"; var pak = this.ParentArchive;
var file = base.OpenFile();
if (file is CModel)
{
((CModel)file).LoadTextures(pak.TextureFiles);
FileEntry GetSkeleton()
{
foreach (var c in pak.CharFiles)
{
foreach (var m in c.Value.Models)
{
if (AssetEntry.FileID.ToString() == m.FileID.ToString())
return pak.SkeletonFiles[c.Value.SkeletonFileID.ToString()];
}
}
return null;
}
var skelFile = GetSkeleton();
if (skelFile != null)
{
var skel = new SKEL(new MemoryStream(skelFile.FileData));
((CModel)file).LoadSkeleton(skel);
}
}
if (file is CCharacter)
((CCharacter)file).LoadModels(pak);
this.FileFormat = file;
return file;
} }
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class MetaOffsetEntry
{
public CObjectTag ObjectTag;
public uint FileOffset;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class PakHeader
{
CFormDescriptor PackForm;
CFormDescriptor TocForm;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class CNameTagEntry
{
public CObjectTag ObjectTag;
public string Name;
}
} }

View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Toolbox.Library;
namespace DKCTF
{
internal class PakFileList
{
static Dictionary<string, string> _filePaths = new Dictionary<string, string>();
public static Dictionary<string, string> GuiToFilePath
{
get
{
if (_filePaths.Count == 0)
Load();
return _filePaths;
}
}
static void Load()
{
string path = Path.Combine(Runtime.ExecutableDir, "Lib", "PakFileIDs", "PakContents.txt");
using (var reader = new StreamReader(path))
{
reader.ReadLine(); //headers
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var items = line.Split('\t');
if (items.Length != 4)
continue;
var id = items[2].Trim();
if (!_filePaths.ContainsKey(id))
_filePaths.Add(id, items[3].Trim());
}
}
}
}
}

View file

@ -26,7 +26,6 @@ namespace CafeLibrary.M2
{ {
attGroups = new Dictionary<uint, Shader.AttributeGroup>(); attGroups = new Dictionary<uint, Shader.AttributeGroup>();
foreach (var file in Directory.GetFiles(Path.Combine(Runtime.ExecutableDir, "Lib", "MTVertexFormats"))) foreach (var file in Directory.GetFiles(Path.Combine(Runtime.ExecutableDir, "Lib", "MTVertexFormats")))
{ {
LoadPresets(file); LoadPresets(file);

View file

@ -118,7 +118,7 @@ namespace CafeLibrary.M2
private Texture FindTextureParam(uint[] parameters, int id) private Texture FindTextureParam(uint[] parameters, int id)
{ {
var index = parameters[id - 1] - 1; var index = parameters[id - 1] - 1;
if (index < Textures.Count) if (index < Textures.Count) //Index before texture hash. Sometimes not always the case?
return Textures[(int)index]; return Textures[(int)index];
return null; return null;
} }

View file

@ -168,7 +168,7 @@ namespace FirstPlugin.LuigisMansion3
fileEntries.Add(file); fileEntries.Add(file);
if (file.DecompressedSize > 0) if (file.DecompressedSize > 0)
{ {
file.Text = $"entry {i}"; file.Text = $"entry {i}";
if (i < 52) if (i < 52)

View file

@ -245,6 +245,17 @@
<Compile Include="FileFormats\BSMAT\NoFormattingConverter.cs" /> <Compile Include="FileFormats\BSMAT\NoFormattingConverter.cs" />
<Compile Include="FileFormats\Byaml\XmlByamlConverter.cs" /> <Compile Include="FileFormats\Byaml\XmlByamlConverter.cs" />
<Compile Include="FileFormats\Byaml\YamlByamlConverter.cs" /> <Compile Include="FileFormats\Byaml\YamlByamlConverter.cs" />
<Compile Include="FileFormats\DKCTF\CCharacter.cs" />
<Compile Include="FileFormats\DKCTF\CModel.cs" />
<Compile Include="FileFormats\DKCTF\FileData\BufferHelper.cs" />
<Compile Include="FileFormats\DKCTF\FileData\CMDL.cs" />
<Compile Include="FileFormats\DKCTF\CTexture.cs" />
<Compile Include="FileFormats\DKCTF\FileData\PACK.cs" />
<Compile Include="FileFormats\DKCTF\FileData\CHAR.cs" />
<Compile Include="FileFormats\DKCTF\FileData\SKEL.cs" />
<Compile Include="FileFormats\DKCTF\FileData\TXTR.cs" />
<Compile Include="FileFormats\DKCTF\FileData\IOFileExtension.cs" />
<Compile Include="FileFormats\DKCTF\PakFileList.cs" />
<Compile Include="FileFormats\Font\BXFNT\CMAP.cs" /> <Compile Include="FileFormats\Font\BXFNT\CMAP.cs" />
<Compile Include="FileFormats\Font\BXFNT\FINF.cs" /> <Compile Include="FileFormats\Font\BXFNT\FINF.cs" />
<Compile Include="FileFormats\Font\BXFNT\FontKerningTable.cs" /> <Compile Include="FileFormats\Font\BXFNT\FontKerningTable.cs" />
@ -350,7 +361,7 @@
<Compile Include="FileFormats\Archives\VIBS.cs" /> <Compile Include="FileFormats\Archives\VIBS.cs" />
<Compile Include="FileFormats\Audio\Archives\AudioCommon.cs" /> <Compile Include="FileFormats\Audio\Archives\AudioCommon.cs" />
<Compile Include="FileFormats\Collision\KclMonoscript.cs" /> <Compile Include="FileFormats\Collision\KclMonoscript.cs" />
<Compile Include="FileFormats\DKCTF\Common.cs" /> <Compile Include="FileFormats\DKCTF\FileData\Common.cs" />
<Compile Include="FileFormats\DKCTF\MSBT.cs" /> <Compile Include="FileFormats\DKCTF\MSBT.cs" />
<Compile Include="FileFormats\DKCTF\PAK.cs" /> <Compile Include="FileFormats\DKCTF\PAK.cs" />
<Compile Include="FileFormats\Effects\ESET.cs" /> <Compile Include="FileFormats\Effects\ESET.cs" />

View file

@ -343,6 +343,8 @@ namespace FirstPlugin
Formats.Add(typeof(BFRES)); Formats.Add(typeof(BFRES));
Formats.Add(typeof(MT_TEX)); Formats.Add(typeof(MT_TEX));
Formats.Add(typeof(MT_Model)); Formats.Add(typeof(MT_Model));
Formats.Add(typeof(DKCTF.CModel));
Formats.Add(typeof(DKCTF.CTexture));
Formats.Add(typeof(BCSV)); Formats.Add(typeof(BCSV));
Formats.Add(typeof(TVOL)); Formats.Add(typeof(TVOL));
Formats.Add(typeof(BTI)); Formats.Add(typeof(BTI));

View file

@ -48,8 +48,15 @@ namespace Toolbox.Library
BatchFormatExport form = new BatchFormatExport(Formats); BatchFormatExport form = new BatchFormatExport(Formats);
if (form.ShowDialog() == DialogResult.OK) if (form.ShowDialog() == DialogResult.OK)
{ {
foreach (STGenericTexture tex in Nodes) foreach (TreeNode node in Nodes)
{ {
STGenericTexture tex = null;
if (node is STGenericTexture) tex = (STGenericTexture)node;
if (node.Tag is STGenericTexture) tex = (STGenericTexture)node.Tag;
if (tex == null)
continue;
if (form.Index == 0) if (form.Index == 0)
tex.SaveDDS(folderPath + '\\' + tex.Text + ".dds"); tex.SaveDDS(folderPath + '\\' + tex.Text + ".dds");
else if (form.Index == 1) else if (form.Index == 1)

View file

@ -13,6 +13,11 @@ namespace Toolbox.Library.IO
int value; int value;
public static implicit operator string(Magic magic) => Encoding.ASCII.GetString(BitConverter.GetBytes(magic.value)); public static implicit operator string(Magic magic) => Encoding.ASCII.GetString(BitConverter.GetBytes(magic.value));
public static implicit operator Magic(string s) => new Magic { value = BitConverter.ToInt32(Encoding.ASCII.GetBytes(s), 0) }; public static implicit operator Magic(string s) => new Magic { value = BitConverter.ToInt32(Encoding.ASCII.GetBytes(s), 0) };
public override string ToString()
{
return Encoding.ASCII.GetString(BitConverter.GetBytes(value));
}
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]

File diff suppressed because it is too large Load diff

View file

@ -558,6 +558,9 @@
<Content Include="Lib\OpenTK.GLControl.dll"> <Content Include="Lib\OpenTK.GLControl.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Lib\PakFileIDs\PakContents.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Lib\Plugins\Blank.txt"> <Content Include="Lib\Plugins\Blank.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>