using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Toolbox; using System.Windows.Forms; using Toolbox.Library; using Toolbox.Library.IO; using Toolbox.Library.Forms; using System.Drawing; using Syroot.NintenTools.Bfres.GX2; namespace FirstPlugin { public class NUT : TreeNodeFile, IFileFormat, ITextureContainer { public FileType FileType { get; set; } = FileType.Image; public bool CanSave { get; set; } public string[] Description { get; set; } = new string[] { "Namco Universal Texture Container" }; public string[] Extension { get; set; } = new string[] { "*.nut" }; 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)) { if (reader.CheckSignature(4, "NTWU") || reader.CheckSignature(4, "NTP3") || reader.CheckSignature(4, "NTWD")) return true; else return false; } } public bool DisplayIcons => true; public List TextureList { get { List textures = new List(); foreach (STGenericTexture node in Nodes) textures.Add(node); return textures; } set { } } public Type[] Types { get { List types = new List(); return types.ToArray(); } } Header NutHeader; public void Load(System.IO.Stream stream) { Text = FileName; NutHeader = new Header(); NutHeader.Read(new FileReader(stream)); string name = System.IO.Path.GetFileNameWithoutExtension(Text); foreach (var image in NutHeader.Images) { if (NutHeader.Images.Count == 1) image.Text = $"{name}"; else image.Text = $"{name}_{Nodes.Count}"; Nodes.Add(image); } } public void Unload() { } public void Save(System.IO.Stream stream) { var mem = new System.IO.MemoryStream(); NutHeader.Write(new FileWriter(stream)); } public class Header { public string Magic; public ushort ByteOrderMark; public ushort Version; public List Images = new List(); byte[] Reserved; public bool IsNTP3; public void Read(FileReader reader) { reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian; Magic = reader.ReadString(4, Encoding.ASCII); Version = reader.ReadUInt16(); IsNTP3 = Magic == "NTP3"; if (Magic == "NTWD") { reader.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian; IsNTP3 = true; } ushort ImageCount = reader.ReadUInt16(); Reserved = reader.ReadBytes(8); for (int i = 0; i < ImageCount; i++) { NutImage image = new NutImage(); image.Read(reader, this); Images.Add(image); } } public void Write(FileWriter writer) { writer.WriteSignature(Magic); writer.Write(Version); writer.Write((ushort)Images.Count); writer.Write(Reserved); for (int i = 0; i < Images.Count; i++) { Images[i].Write(writer, IsNTP3); } } } public class NutImage : STGenericTexture { public override TEX_FORMAT[] SupportedFormats { get { return new TEX_FORMAT[] { TEX_FORMAT.BC1_UNORM, TEX_FORMAT.BC1_UNORM_SRGB, TEX_FORMAT.BC2_UNORM, TEX_FORMAT.BC2_UNORM_SRGB, TEX_FORMAT.BC3_UNORM, TEX_FORMAT.BC3_UNORM_SRGB, TEX_FORMAT.BC4_UNORM, TEX_FORMAT.BC4_SNORM, TEX_FORMAT.BC5_UNORM, TEX_FORMAT.BC5_SNORM, TEX_FORMAT.B5G5R5A1_UNORM, TEX_FORMAT.B5G6R5_UNORM, TEX_FORMAT.B8G8R8A8_UNORM_SRGB, TEX_FORMAT.B8G8R8A8_UNORM, TEX_FORMAT.R10G10B10A2_UNORM, TEX_FORMAT.R16_UNORM, TEX_FORMAT.B4G4R4A4_UNORM, TEX_FORMAT.B5G5R5A1_UNORM, TEX_FORMAT.R8G8B8A8_UNORM_SRGB, TEX_FORMAT.R8G8B8A8_UNORM, TEX_FORMAT.R8_UNORM, TEX_FORMAT.R8G8_UNORM, TEX_FORMAT.R32G8X24_FLOAT, }; } } public override bool CanEdit { get; set; } = false; public uint TotalTextureSize; public uint PaletteSize; public uint ImageSize; public uint HeaderSize; public ushort PaletteCount; public byte NutFormat; public byte OldFormat; public byte PaletteFormat; public bool IsCubeMap = false; public EXT ExternalData; public GIDX GIDXHeaderData; public NutGX2Surface Gx2HeaderData; public byte[] Data; public byte[] MipData; public uint[] ImageSizes; 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); } Properties prop = new Properties(); prop.Width = Width; prop.Height = Height; prop.Depth = Depth; prop.MipCount = MipCount; prop.ArrayCount = ArrayCount; prop.ImageSize = (uint)ImageSize; prop.Format = Format; editor.Text = Text; editor.LoadProperties(prop); editor.LoadImage(this); } private TEX_FORMAT SetFormat(byte format) { switch (format) { case 0x0: return TEX_FORMAT.BC1_UNORM; case 0x1: return TEX_FORMAT.BC2_UNORM; case 0x2: return TEX_FORMAT.BC3_UNORM; case 8: return TEX_FORMAT.B5G5R5A1_UNORM; case 12: return TEX_FORMAT.R16G16B16A16_UNORM; case 14: return TEX_FORMAT.R8G8B8A8_UNORM; case 16: return TEX_FORMAT.R8G8B8A8_UNORM; case 17: return TEX_FORMAT.R8G8B8A8_UNORM; case 21: return TEX_FORMAT.BC4_UNORM; case 22: return TEX_FORMAT.BC5_UNORM; default: throw new NotImplementedException($"Unsupported Nut Format {format}"); } } public void Read(FileReader reader, Header header) { long pos = reader.Position; Console.WriteLine("pos " + pos); TotalTextureSize = reader.ReadUInt32(); //Including header PaletteSize = reader.ReadUInt32(); //Used in older versions ImageSize = reader.ReadUInt32(); HeaderSize = reader.ReadUInt16(); PaletteCount = reader.ReadUInt16(); //Used in older versions OldFormat = reader.ReadByte(); //Used in older versions MipCount = reader.ReadByte(); PaletteFormat = reader.ReadByte(); //Used in older versions NutFormat = reader.ReadByte(); Format = SetFormat(NutFormat); Width = reader.ReadUInt16(); Height = reader.ReadUInt16(); uint Unknown = reader.ReadUInt32(); //Maybe related to 3D depth size uint Flags = reader.ReadUInt32(); //Determine when to use cube maps uint dataOffset = 0; if (header.IsNTP3) { if (header.Version < 0x0200) { dataOffset = HeaderSize; uint padding2 = reader.ReadUInt32(); } else if (header.Version >= 0x0200) dataOffset = reader.ReadUInt32(); } else dataOffset = reader.ReadUInt32(); uint mipOffset = reader.ReadUInt32(); uint gtxHeaderOffset = reader.ReadUInt32(); uint padding = reader.ReadUInt32(); //Could be an offset to an unused section? uint cubeMapSize1 = 0; uint cubeMapSize2 = 0; if ((Flags & (uint)DDS.DDSCAPS2.CUBEMAP) == (uint)DDS.DDSCAPS2.CUBEMAP) { //Only supporting all six faces if ((Flags & (uint)DDS.DDSCAPS2.CUBEMAP_ALLFACES) == (uint)DDS.DDSCAPS2.CUBEMAP_ALLFACES) { IsCubeMap = true; ArrayCount = 6; } else { throw new NotImplementedException($"Unsupported cubemap face amount for texture."); } } if (IsCubeMap) { cubeMapSize1 = reader.ReadUInt32(); cubeMapSize2 = reader.ReadUInt32(); uint unk = reader.ReadUInt32(); uint unk2 = reader.ReadUInt32(); } ImageSizes = new uint[MipCount]; uint MipBlockSize = 0; if (MipCount == 1) { if (IsCubeMap) ImageSizes[0] = cubeMapSize1; else ImageSizes[0] = ImageSize; } else { if (header.IsNTP3) { ImageSizes = reader.ReadUInt32s((int)MipCount ); } else { ImageSizes[0] = reader.ReadUInt32(); MipBlockSize = reader.ReadUInt32(); reader.ReadUInt32s((int)MipCount - 2); //Padding / Unused } reader.Align(16); } ExternalData = new EXT(); ExternalData.Read(reader, header.IsNTP3); GIDXHeaderData = new GIDX(); GIDXHeaderData.Read(reader, header.IsNTP3); Text = GIDXHeaderData.HashID.ToString(); if (dataOffset != 0) { using (reader.TemporarySeek(dataOffset + pos, System.IO.SeekOrigin.Begin)) { if (header.IsNTP3) Data = reader.ReadBytes((int)ImageSize); else Data = reader.ReadBytes((int)ImageSizes[0]); //Mip maps are seperate for GX2 } } if (mipOffset != 0) { using (reader.TemporarySeek(mipOffset + pos, System.IO.SeekOrigin.Begin)) { MipData = reader.ReadBytes((int)MipBlockSize); } } if (gtxHeaderOffset != 0) { using (reader.TemporarySeek(gtxHeaderOffset + pos, System.IO.SeekOrigin.Begin)) { //Now here is where the GX2 header starts Gx2HeaderData = new NutGX2Surface(); Gx2HeaderData.Read(reader); Gx2HeaderData.data = Data; Gx2HeaderData.mipData = MipData; RedChannel = GX2ChanneToGeneric((GX2CompSel)Gx2HeaderData.compSel[0]); GreenChannel = GX2ChanneToGeneric((GX2CompSel)Gx2HeaderData.compSel[1]); BlueChannel = GX2ChanneToGeneric((GX2CompSel)Gx2HeaderData.compSel[2]); AlphaChannel = GX2ChanneToGeneric((GX2CompSel)Gx2HeaderData.compSel[3]); Format = Bfres.Structs.FTEX.ConvertFromGx2Format((GX2SurfaceFormat)Gx2HeaderData.format); } } //Seek back for next image reader.Seek(pos + HeaderSize, System.IO.SeekOrigin.Begin); } private STChannelType GX2ChanneToGeneric(GX2CompSel comp) { if (comp == GX2CompSel.ChannelR) return STChannelType.Red; else if (comp == GX2CompSel.ChannelG) return STChannelType.Green; else if (comp == GX2CompSel.ChannelB) return STChannelType.Blue; else if (comp == GX2CompSel.ChannelA) return STChannelType.Alpha; else if (comp == GX2CompSel.Always0) return STChannelType.Zero; else return STChannelType.One; } public void Write(FileWriter writer, bool IsNTP3) { } public override void SetImageData(Bitmap bitmap, int ArrayLevel) { throw new NotImplementedException(); } public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0, int DepthLevel = 0) { if (Gx2HeaderData != null) { return GX2.Decode(Gx2HeaderData, ArrayLevel, MipLevel); } else { uint DataOffset = 0; for (byte arrayLevel = 0; arrayLevel < ArrayCount; ++arrayLevel) { for (byte mipLevel = 0; mipLevel < MipCount; ++mipLevel) { if (ArrayLevel == arrayLevel && MipLevel == mipLevel) { return Utils.SubArray(Data, DataOffset, ImageSizes[mipLevel]); } else { DataOffset += ImageSizes[mipLevel]; } } } return null; } } } public class GIDX { public uint HeaderSize; public uint HashID; public uint Padding; public void Read(FileReader reader, bool IsNTP3) { var Magic = reader.ReadSignature(4, "GIDX"); HeaderSize = reader.ReadUInt32(); HashID = reader.ReadUInt32(); Padding = reader.ReadUInt32(); } public void Write(FileWriter writer, bool IsNTP3) { writer.WriteSignature("GIDX"); writer.Write(HeaderSize); writer.Write(HashID); writer.Write(Padding); } } public class EXT { public uint Unknown = 32; public uint HeaderSize = 16; public uint Padding; public void Read(FileReader reader, bool IsNTP3) { var Magic = reader.ReadSignature(3, "eXt"); byte padding = reader.ReadByte(); Unknown = reader.ReadUInt32(); HeaderSize = reader.ReadUInt32(); Padding = reader.ReadUInt32(); } public void Write(FileWriter writer, bool IsNTP3) { writer.WriteSignature("Ext"); writer.Write((byte)0); writer.Write(Unknown); writer.Write(HeaderSize); writer.Write(Padding); } } public class NutGX2Surface : GX2.GX2Surface { public NutGX2Surface() { compSel = new byte[4] { 0,1,2,3,}; } public void Read(FileReader reader) { reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian; dim = reader.ReadUInt32(); width = reader.ReadUInt32(); height = reader.ReadUInt32(); depth = reader.ReadUInt32(); numMips = reader.ReadUInt32(); format = reader.ReadUInt32(); aa = reader.ReadUInt32(); use = reader.ReadUInt32(); imageSize = reader.ReadUInt32(); imagePtr = reader.ReadUInt32(); mipSize = reader.ReadUInt32(); mipPtr = reader.ReadUInt32(); tileMode = reader.ReadUInt32(); swizzle = reader.ReadUInt32(); alignment = reader.ReadUInt32(); pitch = reader.ReadUInt32(); mipOffset = reader.ReadUInt32s(13); firstMip = reader.ReadUInt32(); imageCount = reader.ReadUInt32(); firstSlice = reader.ReadUInt32(); } public void Write(FileWriter writer) { writer.Write(dim); writer.Write(width); writer.Write(height); writer.Write(depth); writer.Write(numMips); writer.Write(format); writer.Write(aa); writer.Write(use); writer.Write(imageSize); writer.Write(imagePtr); writer.Write(mipSize); writer.Write(mipPtr); writer.Write(tileMode); writer.Write(swizzle); writer.Write(alignment); writer.Write(pitch); writer.Write(mipOffset); writer.Write(firstMip); writer.Write(imageCount); writer.Write(firstSlice); } } } }