545 lines
19 KiB
C#
545 lines
19 KiB
C#
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<STGenericTexture> TextureList
|
|
{
|
|
get
|
|
{
|
|
List<STGenericTexture> textures = new List<STGenericTexture>();
|
|
foreach (STGenericTexture node in Nodes)
|
|
textures.Add(node);
|
|
|
|
return textures;
|
|
}
|
|
set { }
|
|
}
|
|
|
|
public Type[] Types
|
|
{
|
|
get
|
|
{
|
|
List<Type> types = new List<Type>();
|
|
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<NutImage> Images = new List<NutImage>();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|