mirror of
https://github.com/KillzXGaming/Switch-Toolbox
synced 2024-11-26 22:40:27 +00:00
0c126e4155
Rewrote the compression handling from scatch. It's way easier and cleaner to add new formats code wise as it's handled like file formats. Added wip TVOL support (Touhou Azure Reflections) Added XCI support. Note I plan to improve NSP, XCI, NCA, etc later for exefs exporting. The compression rework now compresses via streams, so files get decompressed properly within archives as streams. Added hyrule warriors bin.gz compression along with archive rebuilding. Note i do not have texture rebuilding done just yet.
849 lines
32 KiB
C#
849 lines
32 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Toolbox.Library.IO;
|
|
using Toolbox.Library;
|
|
using System.Windows.Forms;
|
|
using OpenTK.Graphics.OpenGL;
|
|
using Toolbox.Library.Forms;
|
|
using Bfres.Structs;
|
|
using System.IO;
|
|
using System.Linq;
|
|
|
|
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, IContextMenuNode
|
|
{
|
|
public FileType FileType { get; set; } = FileType.Image;
|
|
|
|
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 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 override UserControl GetEditor()
|
|
{
|
|
STPropertyGrid editor = new STPropertyGrid();
|
|
editor.Text = Text;
|
|
editor.Dock = DockStyle.Fill;
|
|
return editor;
|
|
}
|
|
|
|
public override void OnAfterAdded()
|
|
{
|
|
if (textures.Count > 0 && this.TreeView != null)
|
|
this.TreeView.SelectedNode = textures[0];
|
|
}
|
|
|
|
public override void FillEditor(UserControl control)
|
|
{
|
|
((STPropertyGrid)control).LoadProperty(header);
|
|
}
|
|
|
|
public void Load(System.IO.Stream stream)
|
|
{
|
|
CanSave = true;
|
|
Text = FileName;
|
|
|
|
ReadGx2(new FileReader(stream));
|
|
}
|
|
|
|
public ToolStripItem[] GetContextMenuItems()
|
|
{
|
|
return new ToolStripItem[]
|
|
{
|
|
new ToolStripMenuItem("Save", null, Save, Keys.Control | Keys.S),
|
|
new ToolStripMenuItem("Export All", null, ExportAllAction, Keys.Control | Keys.E),
|
|
};
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
protected void ExportAllAction(object sender, EventArgs e)
|
|
{
|
|
if (Nodes.Count <= 0)
|
|
return;
|
|
|
|
string formats = FileFilters.GTX;
|
|
|
|
string[] forms = formats.Split('|');
|
|
|
|
List<string> Formats = new List<string>();
|
|
|
|
for (int i = 0; i < forms.Length; i++)
|
|
{
|
|
if (i > 1 || i == (forms.Length - 1)) //Skip lines with all extensions
|
|
{
|
|
if (!forms[i].StartsWith("*"))
|
|
Formats.Add(forms[i]);
|
|
}
|
|
}
|
|
|
|
FolderSelectDialog sfd = new FolderSelectDialog();
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string folderPath = sfd.SelectedPath;
|
|
|
|
BatchFormatExport form = new BatchFormatExport(Formats);
|
|
if (form.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string extension = form.GetSelectedExtension();
|
|
extension.Replace(" ", string.Empty);
|
|
|
|
foreach (STGenericTexture node in Nodes)
|
|
{
|
|
((STGenericTexture)node).Export($"{folderPath}\\{node.Text}{extension}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Unload()
|
|
{
|
|
|
|
}
|
|
|
|
public void Save(System.IO.Stream stream)
|
|
{
|
|
using (FileWriter writer = new FileWriter(stream, true))
|
|
{
|
|
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}");
|
|
|
|
int imageInfoIndex = -1;
|
|
int imageBlockIndex = -1;
|
|
int imageMipBlockIndex = -1;
|
|
|
|
writer.Seek(header.HeaderSize, System.IO.SeekOrigin.Begin);
|
|
foreach (var block in blocks)
|
|
{
|
|
if ((uint)block.BlockType == surfBlockType)
|
|
{
|
|
imageInfoIndex++;
|
|
imageBlockIndex++;
|
|
imageMipBlockIndex++;
|
|
|
|
block.data = textures[imageInfoIndex].surface.Write();
|
|
block.Write(writer);
|
|
|
|
}
|
|
else if ((uint)block.BlockType == dataBlockType)
|
|
{
|
|
var tex = textures[imageBlockIndex];
|
|
|
|
var pos = writer.Position;
|
|
uint Alignment = tex.surface.alignment;
|
|
//Create alignment block first
|
|
uint dataAlignment = GetAlignBlockSize((uint)pos + 32, Alignment);
|
|
GTXDataBlock dataAlignBlock = new GTXDataBlock(BlockType.AlignData, dataAlignment, 0, 0);
|
|
dataAlignBlock.Write(writer);
|
|
|
|
block.data = tex.surface.data;
|
|
block.Write(writer);
|
|
}
|
|
else if ((uint)block.BlockType == mipBlockType)
|
|
{
|
|
var tex = textures[imageMipBlockIndex];
|
|
|
|
var pos = writer.Position;
|
|
uint Alignment = tex.surface.alignment;
|
|
//Create alignment block first
|
|
uint dataAlignment = GetAlignBlockSize((uint)pos + 32, Alignment);
|
|
GTXDataBlock dataAlignBlock = new GTXDataBlock(BlockType.AlignData, dataAlignment, 0, 0);
|
|
dataAlignBlock.Write(writer);
|
|
|
|
if (tex.surface.mipData == null || tex.surface.mipData.Length <= 0)
|
|
throw new Exception("Invalid mip data!");
|
|
|
|
block.data = tex.surface.mipData;
|
|
block.Write(writer);
|
|
}
|
|
else if (block.BlockType != BlockType.AlignData)
|
|
{
|
|
block.Write(writer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static uint GetAlignBlockSize(uint DataOffset, uint Alignment)
|
|
{
|
|
uint alignSize = RoundUp(DataOffset, Alignment) - DataOffset - 32;
|
|
|
|
uint z = 1;
|
|
while (alignSize < 0)
|
|
alignSize = RoundUp(DataOffset + (Alignment * z), Alignment) - DataOffset - 32;
|
|
z += 1;
|
|
|
|
return alignSize;
|
|
}
|
|
|
|
private static uint RoundUp(uint X, uint Y) { return((X - 1) | (Y - 1)) + 1; }
|
|
|
|
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;
|
|
uint vertexShaderHeader = 0x03;
|
|
uint vertexShaderProgram = 0x05;
|
|
uint pixelShaderHeader = 0x06;
|
|
uint pixelShaderProgram = 0x07;
|
|
uint geometryShaderHeader = 0x08;
|
|
uint geometryShaderProgram = 0x09;
|
|
uint userDataBlock = 0x10;
|
|
|
|
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)
|
|
{
|
|
Console.WriteLine("BLOCK POS " + reader.Position + " " + reader.BaseStream.Length);
|
|
GTXDataBlock block = new GTXDataBlock();
|
|
block.Read(reader);
|
|
blocks.Add(block);
|
|
|
|
bool BlockIsEmpty = block.BlockType == BlockType.AlignData ||
|
|
block.BlockType == BlockType.EndOfFile;
|
|
|
|
//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.depth;
|
|
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);
|
|
}
|
|
else if ((uint)block.BlockType == vertexShaderHeader)
|
|
Nodes.Add(new BlockDisplay(block.data) { Text = "Vertex Shader Header" });
|
|
else if ((uint)block.BlockType == vertexShaderProgram)
|
|
Nodes.Add(new BlockDisplay(block.data) { Text = "Vertex Shader Program" });
|
|
else if ((uint)block.BlockType == pixelShaderHeader)
|
|
Nodes.Add(new BlockDisplay(block.data) { Text = "Pixel Shader Header" });
|
|
else if ((uint)block.BlockType == pixelShaderProgram)
|
|
Nodes.Add(new BlockDisplay(block.data) { Text = "Pixel Shader Program" });
|
|
else if ((uint)block.BlockType == geometryShaderHeader)
|
|
Nodes.Add(new BlockDisplay(block.data) { Text = "Geometry Shader Header" });
|
|
else if ((uint)block.BlockType == geometryShaderProgram)
|
|
Nodes.Add(new BlockDisplay(block.data) { Text = "Geometry Shader Program" });
|
|
else if (!BlockIsEmpty)
|
|
Nodes.Add(new BlockDisplay(block.data) { Text = $"Block Type {block.BlockType.ToString("X")}" });
|
|
}
|
|
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)
|
|
{
|
|
if (node is TextureData)
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class BlockDisplay : TreeNodeCustom
|
|
{
|
|
public byte[] BlockData;
|
|
|
|
public BlockDisplay(byte[] data)
|
|
{
|
|
BlockData = data;
|
|
}
|
|
|
|
public override void OnClick(TreeView treeview)
|
|
{
|
|
HexEditor editor = (HexEditor)LibraryGUI.GetActiveContent(typeof(HexEditor));
|
|
if (editor == null)
|
|
{
|
|
editor = new HexEditor();
|
|
LibraryGUI.LoadEditor(editor);
|
|
}
|
|
editor.Text = Text;
|
|
editor.Dock = DockStyle.Fill;
|
|
editor.LoadData(BlockData);
|
|
}
|
|
}
|
|
|
|
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 GTXDataBlock() { }
|
|
|
|
public GTXDataBlock(BlockType blockType, uint dataSize, uint identifier, uint index)
|
|
{
|
|
HeaderSize = 32;
|
|
MajorVersion = 1;
|
|
MinorVersion = 0;
|
|
BlockType = blockType;
|
|
DataSize = dataSize;
|
|
Identifier = identifier;
|
|
Index = index;
|
|
data = new byte[dataSize];
|
|
}
|
|
|
|
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(data.Length);
|
|
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.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; } = true;
|
|
|
|
public SurfaceInfoParse surface;
|
|
|
|
public TextureData()
|
|
{
|
|
ImageKey = "Texture";
|
|
SelectedImageKey = "Texture";
|
|
|
|
CanDelete = false;
|
|
CanReplace = true;
|
|
CanRename = false;
|
|
}
|
|
|
|
public override string ExportFilter => FileFilters.GTX;
|
|
public override string ReplaceFilter => FileFilters.GTX;
|
|
|
|
private void ApplySurface(GX2.GX2Surface NewSurface)
|
|
{
|
|
surface.aa = NewSurface.aa;
|
|
surface.alignment = NewSurface.alignment;
|
|
surface.bpp = NewSurface.bpp;
|
|
surface.compSel = NewSurface.compSel;
|
|
surface.data = NewSurface.data;
|
|
surface.depth = NewSurface.depth;
|
|
surface.dim = NewSurface.dim;
|
|
surface.firstMip = NewSurface.firstMip;
|
|
surface.firstSlice = NewSurface.firstSlice;
|
|
surface.format = NewSurface.format;
|
|
surface.height = NewSurface.height;
|
|
surface.imageCount = NewSurface.imageCount;
|
|
surface.imageSize = NewSurface.imageSize;
|
|
surface.mipData = NewSurface.mipData;
|
|
surface.mipSize = NewSurface.mipSize;
|
|
surface.mipOffset = NewSurface.mipOffset;
|
|
surface.numArray = NewSurface.numArray;
|
|
surface.numMips = NewSurface.numMips;
|
|
surface.pitch = NewSurface.pitch;
|
|
surface.resourceFlags = NewSurface.resourceFlags;
|
|
surface.swizzle = NewSurface.swizzle;
|
|
surface.tileMode = NewSurface.tileMode;
|
|
surface.use = NewSurface.use;
|
|
surface.width = NewSurface.width;
|
|
surface.texRegs = NewSurface.texRegs;
|
|
|
|
SetChannelComponents();
|
|
}
|
|
|
|
private STChannelType SetChannel(byte compSel)
|
|
{
|
|
if (compSel == 0) return STChannelType.Red;
|
|
else if (compSel == 1) return STChannelType.Green;
|
|
else if (compSel == 2) return STChannelType.Blue;
|
|
else if (compSel == 3) return STChannelType.Alpha;
|
|
else if (compSel == 4) return STChannelType.Zero;
|
|
else return STChannelType.One;
|
|
}
|
|
|
|
private void SetChannelComponents()
|
|
{
|
|
surface.compSel = new byte[4] { 0, 1, 2, 3 };
|
|
}
|
|
|
|
public override void SetImageData(Bitmap bitmap, int ArrayLevel)
|
|
{
|
|
if (bitmap == null)
|
|
return; //Image is likely disposed and not needed to be applied
|
|
|
|
RedChannel = SetChannel(surface.compSel[0]);
|
|
GreenChannel = SetChannel(surface.compSel[1]);
|
|
BlueChannel = SetChannel(surface.compSel[2]);
|
|
AlphaChannel = SetChannel(surface.compSel[3]);
|
|
|
|
surface.format = (uint)FTEX.ConvertToGx2Format(Format);
|
|
surface.width = (uint)bitmap.Width;
|
|
surface.height = (uint)bitmap.Height;
|
|
|
|
if (MipCount != 1)
|
|
{
|
|
MipCount = GenerateMipCount(bitmap.Width, bitmap.Height);
|
|
if (MipCount == 0)
|
|
MipCount = 1;
|
|
}
|
|
|
|
surface.numMips = MipCount;
|
|
surface.mipOffset = new uint[MipCount];
|
|
|
|
//Create image block from bitmap first
|
|
var data = GenerateMipsAndCompress(bitmap, MipCount, Format);
|
|
|
|
//Swizzle and create surface
|
|
var NewSurface = GX2.CreateGx2Texture(data, Text,
|
|
(uint)surface.tileMode,
|
|
(uint)surface.aa,
|
|
(uint)surface.width,
|
|
(uint)surface.height,
|
|
(uint)surface.depth,
|
|
(uint)surface.format,
|
|
(uint)0,
|
|
(uint)surface.dim,
|
|
(uint)surface.numMips
|
|
);
|
|
|
|
ApplySurface(NewSurface);
|
|
IsEdited = true;
|
|
LoadOpenGLTexture();
|
|
LibraryGUI.UpdateViewport();
|
|
}
|
|
|
|
public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0)
|
|
{
|
|
RedChannel = SetChannel(surface.compSel[0]);
|
|
GreenChannel = SetChannel(surface.compSel[1]);
|
|
BlueChannel = SetChannel(surface.compSel[2]);
|
|
AlphaChannel = SetChannel(surface.compSel[3]);
|
|
|
|
|
|
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);
|
|
|
|
return GX2.Decode(surface, ArrayLevel, MipLevel);
|
|
}
|
|
private void Remove(object sender, EventArgs args) {
|
|
((GTXFile)Parent).Nodes.Remove(this);
|
|
}
|
|
|
|
public override void Export(string FileName) {
|
|
Export(FileName);
|
|
}
|
|
|
|
public override void Replace(string FileName)
|
|
{
|
|
FTEX ftex = new FTEX();
|
|
ftex.ReplaceTexture(FileName, Format);
|
|
if (ftex.texture != null)
|
|
{
|
|
surface.swizzle = ftex.texture.Swizzle;
|
|
surface.tileMode = (uint)ftex.texture.TileMode;
|
|
surface.format = (uint)ftex.texture.Format;
|
|
surface.aa = (uint)ftex.texture.AAMode;
|
|
surface.use = (uint)ftex.texture.Use;
|
|
surface.alignment = (uint)ftex.texture.Alignment;
|
|
surface.dim = (uint)ftex.texture.Dim;
|
|
surface.width = (uint)ftex.texture.Width;
|
|
surface.height = (uint)ftex.texture.Height;
|
|
surface.depth = (uint)ftex.texture.Depth;
|
|
surface.numMips = (uint)ftex.texture.MipCount;
|
|
surface.imageSize = (uint)ftex.texture.Data.Length;
|
|
surface.mipSize = (uint)ftex.texture.MipData.Length;
|
|
surface.data = ftex.texture.Data;
|
|
surface.mipData = ftex.texture.MipData;
|
|
surface.mipOffset = ftex.texture.MipOffsets;
|
|
surface.firstMip = ftex.texture.ViewMipFirst;
|
|
surface.firstSlice = 0;
|
|
surface.numSlices = ftex.texture.ArrayLength;
|
|
surface.imageCount = ftex.texture.MipCount;
|
|
surface.pitch = ftex.texture.Pitch;
|
|
surface.texRegs = GX2.CreateRegisters(surface);
|
|
|
|
SetChannelComponents();
|
|
|
|
Format = FTEX.ConvertFromGx2Format((Syroot.NintenTools.Bfres.GX2.GX2SurfaceFormat)surface.format);
|
|
Width = surface.width;
|
|
Height = surface.height;
|
|
MipCount = surface.numMips;
|
|
ArrayCount = surface.depth;
|
|
|
|
ImageEditorBase editor = (ImageEditorBase)LibraryGUI.GetActiveContent(typeof(ImageEditorBase));
|
|
|
|
if (editor != null)
|
|
UpdateEditor();
|
|
}
|
|
}
|
|
public override void OnClick(TreeView treeView)
|
|
{
|
|
UpdateEditor();
|
|
}
|
|
|
|
public void UpdateEditor()
|
|
{
|
|
ImageEditorBase editor = (ImageEditorBase)LibraryGUI.GetActiveContent(typeof(ImageEditorBase));
|
|
if (editor == null)
|
|
{
|
|
editor = new ImageEditorBase();
|
|
editor.Dock = DockStyle.Fill;
|
|
|
|
LibraryGUI.LoadEditor(editor);
|
|
}
|
|
editor.Text = Text;
|
|
var tex = FTEX.FromGx2Surface(surface, Text);
|
|
tex.MipCount = MipCount;
|
|
editor.LoadProperties(tex);
|
|
editor.LoadImage(this);
|
|
}
|
|
}
|
|
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 byte[] Write()
|
|
{
|
|
MemoryStream mem = new MemoryStream();
|
|
|
|
FileWriter writer = new FileWriter(mem);
|
|
writer.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian;
|
|
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);
|
|
|
|
for (int i = 0; i < 13; i++)
|
|
{
|
|
if (mipOffset.Length > i)
|
|
writer.Write(mipOffset[i]);
|
|
else
|
|
writer.Write(0);
|
|
}
|
|
|
|
writer.Write(firstMip);
|
|
writer.Write(imageCount);
|
|
writer.Write(firstSlice);
|
|
writer.Write(numSlices);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (compSel != null && compSel.Length > i)
|
|
writer.Write(compSel[i]);
|
|
else
|
|
writer.Write((byte)0);
|
|
}
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
if (texRegs != null && texRegs.Length > i)
|
|
writer.Write(texRegs[i]);
|
|
else
|
|
writer.Write(0);
|
|
}
|
|
|
|
return mem.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|