1
0
mirror of synced 2024-12-13 08:11:07 +01:00
Switch-Toolbox/Switch_FileFormatsMain/FileFormats/Texture/GTXFile.cs

563 lines
21 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Threading.Tasks;
using Switch_Toolbox.Library.IO;
using Switch_Toolbox.Library;
using System.Windows.Forms;
using OpenTK.Graphics.OpenGL;
using Switch_Toolbox.Library.Forms;
using Bfres.Structs;
namespace FirstPlugin
{
public enum BlockType : uint
{
Invalid = 0x00,
EndOfFile = 0x01,
AlignData = 0x02,
VertexShaderHeader = 0x03,
VertexShaderProgram = 0x05,
PixelShaderHeader = 0x06,
PixelShaderProgram = 0x07,
GeometryShaderHeader = 0x08,
GeometryShaderProgram = 0x09,
GeometryShaderProgram2 = 0x10,
ImageInfo = 0x11,
ImageData = 0x12,
MipData = 0x13,
ComputeShaderHeader = 0x14,
ComputeShader = 0x15,
UserBlock = 0x16,
}
public class GTXFile : TreeNodeFile, IFileFormat
{
public bool CanSave { get; set; }
public string[] Description { get; set; } = new string[] { "GTX" };
public string[] Extension { get; set; } = new string[] { "*.gtx" };
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 Switch_Toolbox.Library.IO.FileReader(stream, true))
{
return reader.CheckSignature(4, "Gfx2");
}
}
public Type[] Types
{
get
{
List<Type> types = new List<Type>();
return types.ToArray();
}
}
private GTXHeader header;
public List<byte[]> data = new List<byte[]>();
public List<byte[]> mipMaps = new List<byte[]>();
public List<TextureData> textures = new List<TextureData>();
public List<GTXDataBlock> blocks = new List<GTXDataBlock>();
public void Load(System.IO.Stream stream)
{
CanSave = true;
Text = FileName;
ReadGx2(new FileReader(stream));
ContextMenu = new ContextMenu();
MenuItem save = new MenuItem("Save");
ContextMenu.MenuItems.Add(save);
save.Click += Save;
}
private void Save(object sender, EventArgs args)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.DefaultExt = "gtx";
sfd.Filter = "Supported Formats|*.gtx;";
sfd.FileName = FileName;
if (sfd.ShowDialog() == DialogResult.OK)
{
STFileSaver.SaveFileFormat(this, sfd.FileName);
}
}
public void Unload()
{
}
public byte[] Save()
{
System.IO.MemoryStream mem = new System.IO.MemoryStream();
using (FileWriter writer = new FileWriter(mem))
{
writer.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian;
header.Write(writer);
uint surfBlockType;
uint dataBlockType;
uint mipBlockType;
if (header.MajorVersion == 6 && header.MinorVersion == 0)
{
surfBlockType = 0x0A;
dataBlockType = 0x0B;
mipBlockType = 0x0C;
}
else if (header.MajorVersion == 6 || header.MajorVersion == 7)
{
surfBlockType = 0x0B;
dataBlockType = 0x0C;
mipBlockType = 0x0D;
}
else
throw new Exception($"Unsupported GTX version {header.MajorVersion}");
writer.Seek(header.HeaderSize, System.IO.SeekOrigin.Begin);
foreach (var tex in textures)
{
tex.surface.Write(writer);
}
foreach (var block in blocks)
{
if ((uint)block.BlockType == surfBlockType)
{
block.Write(writer);
}
else
{
block.Write(writer);
}
}
}
return mem.ToArray();
}
private void ReadGx2(FileReader reader)
{
reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian;
header = new GTXHeader();
header.Read(reader);
Console.WriteLine("header size " + header.HeaderSize);
uint surfBlockType;
uint dataBlockType;
uint mipBlockType;
if (header.MajorVersion == 6 && header.MinorVersion == 0)
{
surfBlockType = 0x0A;
dataBlockType = 0x0B;
mipBlockType = 0x0C;
}
else if (header.MajorVersion == 6 || header.MajorVersion == 7)
{
surfBlockType = 0x0B;
dataBlockType = 0x0C;
mipBlockType = 0x0D;
}
else
throw new Exception($"Unsupported GTX version {header.MajorVersion}");
if (header.GpuVersion != 2)
throw new Exception($"Unsupported GPU version {header.GpuVersion}");
reader.Position = header.HeaderSize;
bool blockB = false;
bool blockC = false;
uint ImageInfo = 0;
uint images = 0;
while (reader.Position < reader.BaseStream.Length)
{
GTXDataBlock block = new GTXDataBlock();
block.Read(reader);
blocks.Add(block);
//Here we use "if" instead of "case" statements as types vary between versions
if ((uint)block.BlockType == surfBlockType)
{
ImageInfo += 1;
blockB = true;
var surface = new SurfaceInfoParse();
surface.Read(new FileReader(block.data));
if (surface.tileMode == 0 || surface.tileMode > 16)
throw new Exception($"Invalid tileMode {surface.tileMode}!");
if (surface.numMips > 14)
throw new Exception($"Invalid number of mip maps {surface.numMips}!");
TextureData textureData = new TextureData();
textureData.surface = surface;
textureData.MipCount = surface.numMips;
textureData.ArrayCount = surface.numArray;
textureData.Text = "Texture" + ImageInfo;
Nodes.Add(textureData);
textures.Add(textureData);
}
else if ((uint)block.BlockType == dataBlockType)
{
images += 1;
blockC = true;
data.Add(block.data);
}
else if ((uint)block.BlockType == mipBlockType)
{
mipMaps.Add(block.data);
}
}
if (textures.Count != data.Count)
throw new Exception($"Bad size! {textures.Count} {data.Count}");
int curTex = 0;
int curMip = 0;
foreach (var node in Nodes)
{
TextureData tex = (TextureData)node;
tex.surface.data = data[curTex];
tex.surface.bpp = GX2.surfaceGetBitsPerPixel(tex.surface.format) >> 3;
tex.Format = FTEX.ConvertFromGx2Format((Syroot.NintenTools.Bfres.GX2.GX2SurfaceFormat)tex.surface.format);
tex.Width = tex.surface.width;
tex.Height = tex.surface.height;
if (tex.surface.numMips > 1)
tex.surface.mipData = mipMaps[curMip++];
else
tex.surface.mipData = new byte[0];
if (tex.surface.mipData == null)
tex.surface.numMips = 1;
curTex++;
}
reader.Close();
reader.Dispose();
}
public class GTXHeader
{
readonly string Magic = "Gfx2";
public uint HeaderSize;
public uint MajorVersion;
public uint MinorVersion;
public uint GpuVersion;
public uint AlignMode;
public void Read(FileReader reader)
{
string Signature = reader.ReadString(4, Encoding.ASCII);
if (Signature != Magic)
throw new Exception($"Invalid signature {Signature}! Expected Gfx2.");
HeaderSize = reader.ReadUInt32();
MajorVersion = reader.ReadUInt32();
MinorVersion = reader.ReadUInt32();
GpuVersion = reader.ReadUInt32(); //Ignored in 6.0
AlignMode = reader.ReadUInt32();
}
public void Write(FileWriter writer)
{
writer.WriteSignature(Magic);
writer.Write(HeaderSize);
writer.Write(MajorVersion);
writer.Write(MinorVersion);
writer.Write(GpuVersion);
writer.Write(AlignMode);
}
}
public class GTXDataBlock
{
readonly string Magic = "BLK{";
public uint HeaderSize;
public uint MajorVersion;
public uint MinorVersion;
public BlockType BlockType;
public uint Identifier;
public uint index;
public uint DataSize;
public byte[] data;
public void Read(FileReader reader)
{
long blockStart = reader.Position;
string Signature = reader.ReadString(4, Encoding.ASCII);
if (Signature != Magic)
throw new Exception($"Invalid signature {Signature}! Expected BLK.");
HeaderSize = reader.ReadUInt32();
MajorVersion = reader.ReadUInt32(); //Must be 0x01 for 6.x
MinorVersion = reader.ReadUInt32(); //Must be 0x00 for 6.x
BlockType = reader.ReadEnum<BlockType>(false);
DataSize = reader.ReadUInt32();
Identifier = reader.ReadUInt32();
index = reader.ReadUInt32();
reader.Seek(blockStart + HeaderSize, System.IO.SeekOrigin.Begin);
data = reader.ReadBytes((int)DataSize);
}
public void Write(FileWriter writer)
{
long blockStart = writer.Position;
writer.WriteSignature(Magic);
writer.Write(HeaderSize);
writer.Write(MajorVersion);
writer.Write(MinorVersion);
writer.Write(BlockType, false);
writer.Write(DataSize);
writer.Write(Identifier);
writer.Write(index);
writer.Seek(blockStart + HeaderSize, System.IO.SeekOrigin.Begin);
writer.Write(data);
}
}
public class TextureData : 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.B5_G5_R5_A1_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 SurfaceInfoParse surface;
public TextureData()
{
ImageKey = "Texture";
SelectedImageKey = "Texture";
ContextMenu = new ContextMenu();
MenuItem export = new MenuItem("Export");
ContextMenu.MenuItems.Add(export);
export.Click += Export;
MenuItem replace = new MenuItem("Replace");
ContextMenu.MenuItems.Add(replace);
replace.Click += Replace;
MenuItem remove = new MenuItem("Remove");
ContextMenu.MenuItems.Add(remove);
remove.Click += Remove;
}
public override void SetImageData(Bitmap bitmap, int ArrayLevel)
{
throw new NotImplementedException("Cannot set image data! Operation not implemented!");
}
public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0)
{
Console.WriteLine("");
Console.WriteLine("// ----- GX2Surface Info ----- ");
Console.WriteLine(" dim = " + surface.dim);
Console.WriteLine(" width = " + surface.width);
Console.WriteLine(" height = " + surface.height);
Console.WriteLine(" depth = " + surface.depth);
Console.WriteLine(" numMips = " + surface.numMips);
Console.WriteLine(" format = " + surface.format);
Console.WriteLine(" aa = " + surface.aa);
Console.WriteLine(" use = " + surface.use);
Console.WriteLine(" imageSize = " + surface.imageSize);
Console.WriteLine(" mipSize = " + surface.mipSize);
Console.WriteLine(" tileMode = " + surface.tileMode);
Console.WriteLine(" swizzle = " + surface.swizzle);
Console.WriteLine(" alignment = " + surface.alignment);
Console.WriteLine(" pitch = " + surface.pitch);
Console.WriteLine(" bits per pixel = " + (surface.bpp << 3));
Console.WriteLine(" bytes per pixel = " + surface.bpp);
Console.WriteLine(" data size = " + surface.data.Length);
Console.WriteLine(" mip size = " + surface.mipData.Length);
Console.WriteLine(" realSize = " + surface.imageSize);
var surfaces = GX2.Decode(surface);
return surfaces[ArrayLevel][MipLevel];
}
private void Remove(object sender, EventArgs args)
{
((GTXFile)Parent).Nodes.Remove(this);
}
private void Export(object sender, EventArgs args)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.FileName = Text;
sfd.DefaultExt = "bftex";
sfd.Filter = "Supported Formats|*.bftex;*.dds; *.png;*.tga;*.jpg;*.tiff|" +
"Binary Texture |*.bftex|" +
"Microsoft DDS |*.dds|" +
"Portable Network Graphics |*.png|" +
"Joint Photographic Experts Group |*.jpg|" +
"Bitmap Image |*.bmp|" +
"Tagged Image File Format |*.tiff|" +
"All files(*.*)|*.*";
if (sfd.ShowDialog() == DialogResult.OK)
{
Export(sfd.FileName);
}
}
public void Export(string FileName, bool ExportSurfaceLevel = false,
bool ExportMipMapLevel = false, int SurfaceLevel = 0, int MipLevel = 0)
{
string ext = System.IO.Path.GetExtension(FileName);
ext = ext.ToLower();
switch (ext)
{
case ".dds":
SaveDDS(FileName);
break;
default:
SaveBitMap(FileName);
break;
}
}
private void Replace(object sender, EventArgs args)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Supported Formats|*.dds; *.png;*.tga;*.jpg;*.tiff|" +
"Microsoft DDS |*.dds|" +
"Portable Network Graphics |*.png|" +
"Joint Photographic Experts Group |*.jpg|" +
"Bitmap Image |*.bmp|" +
"Tagged Image File Format |*.tiff|" +
"All files(*.*)|*.*";
ofd.Multiselect = false;
if (ofd.ShowDialog() == DialogResult.OK)
{
Replace(ofd.FileName);
}
}
public void Replace(string FileName)
{
}
public override void OnClick(TreeView treeView)
{
UpdateEditor();
}
public void UpdateEditor()
{
ImageEditorBase editor = (ImageEditorBase)LibraryGUI.Instance.GetActiveContent(typeof(ImageEditorBase));
if (editor == null)
{
editor = new ImageEditorBase();
editor.Dock = DockStyle.Fill;
LibraryGUI.Instance.LoadEditor(editor);
}
editor.Text = Text;
UserDataEditor userDataEditor = new UserDataEditor();
userDataEditor.Name = "User Data";
editor.LoadImage(this);
editor.AddCustomControl(userDataEditor, typeof(UserDataEditor));
}
}
public class SurfaceInfoParse : GX2.GX2Surface
{
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();
numSlices = reader.ReadUInt32();
compSel = reader.ReadBytes(4);
texRegs = reader.ReadUInt32s(5);
}
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);
writer.Write(numSlices);
writer.Write(compSel);
writer.Write(texRegs);
}
}
}
}