b8c8ef8395
The aamp editor now acts like the byaml one. Fixed aamp files saving with improper strings. Added toggle for viewport in bfres editor. The setting will apply to the config so you can always disable it for quicker previewing for bfres. More editors for bfres now dock rather than convering the whole screen. Plugins can have icons for the toolstrip that contains the save/update icons. Force PTCL texture injection due to an alignment thing
760 lines
29 KiB
C#
760 lines
29 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Drawing;
|
|
using System.Threading.Tasks;
|
|
using Switch_Toolbox;
|
|
using System.Windows.Forms;
|
|
using Switch_Toolbox.Library;
|
|
using Switch_Toolbox.Library.IO;
|
|
using Switch_Toolbox.Library.Forms;
|
|
using System.ComponentModel;
|
|
using Syroot.NintenTools.NSW.Bntx;
|
|
using Syroot.NintenTools.NSW.Bntx.GFX;
|
|
|
|
namespace FirstPlugin
|
|
{
|
|
public class NUTEXB : STGenericTexture, IFileFormat
|
|
{
|
|
public override TEX_FORMAT[] SupportedFormats
|
|
{
|
|
get
|
|
{
|
|
return new TEX_FORMAT[]
|
|
{
|
|
TEX_FORMAT.BC1_UNORM,
|
|
TEX_FORMAT.BC1_UNORM_SRGB,
|
|
TEX_FORMAT.BC3_UNORM,
|
|
TEX_FORMAT.BC3_UNORM_SRGB,
|
|
TEX_FORMAT.BC6H_UF16,
|
|
TEX_FORMAT.BC7_UNORM,
|
|
TEX_FORMAT.BC7_UNORM_SRGB,
|
|
TEX_FORMAT.R8G8B8A8_UNORM_SRGB,
|
|
TEX_FORMAT.R8G8B8A8_UNORM,
|
|
};
|
|
}
|
|
}
|
|
|
|
public override bool CanEdit { get; set; } = true;
|
|
|
|
public bool CanSave { get; set; }
|
|
public string[] Description { get; set; } = new string[] { "Namco Texture" };
|
|
public string[] Extension { get; set; } = new string[] { "*.nutexb" };
|
|
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(3, "XET", reader.BaseStream.Length - 7);
|
|
}
|
|
}
|
|
|
|
public bool LimitFileSize { get; set; } = true;
|
|
|
|
public enum NUTEXImageFormat : byte
|
|
{
|
|
R8G8B8A8_UNORM = 0x00,
|
|
R8G8B8A8_SRGB = 0x05,
|
|
R32G32B32A32_FLOAT = 0x34,
|
|
B8G8R8A8_UNORM = 0x50,
|
|
B8G8R8A8_SRGB = 0x55,
|
|
BC1_UNORM = 0x80,
|
|
BC1_SRGB = 0x85,
|
|
BC2_UNORM = 0x90,
|
|
BC2_SRGB = 0x95,
|
|
BC3_UNORM = 0xa0,
|
|
BC3_SRGB = 0xa5,
|
|
BC4_UNORM = 0xb0,
|
|
BC4_SNORM = 0xb5,
|
|
BC5_UNORM = 0xc0,
|
|
BC5_SNORM = 0xc5,
|
|
BC6_UFLOAT = 0xd7,
|
|
BC7_UNORM = 0xe0,
|
|
BC7_SRGB = 0xe5,
|
|
};
|
|
|
|
public static uint blk_dims(byte format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case (byte)NUTEXImageFormat.BC1_UNORM:
|
|
case (byte)NUTEXImageFormat.BC1_SRGB:
|
|
case (byte)NUTEXImageFormat.BC2_UNORM:
|
|
case (byte)NUTEXImageFormat.BC2_SRGB:
|
|
case (byte)NUTEXImageFormat.BC3_UNORM:
|
|
case (byte)NUTEXImageFormat.BC3_SRGB:
|
|
case (byte)NUTEXImageFormat.BC4_UNORM:
|
|
case (byte)NUTEXImageFormat.BC4_SNORM:
|
|
case (byte)NUTEXImageFormat.BC5_UNORM:
|
|
case (byte)NUTEXImageFormat.BC5_SNORM:
|
|
case (byte)NUTEXImageFormat.BC6_UFLOAT:
|
|
case (byte)NUTEXImageFormat.BC7_UNORM:
|
|
case (byte)NUTEXImageFormat.BC7_SRGB:
|
|
return 0x44;
|
|
|
|
default: return 0x11;
|
|
}
|
|
}
|
|
|
|
public static uint bpps(byte format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case (byte)NUTEXImageFormat.B8G8R8A8_UNORM:
|
|
case (byte)NUTEXImageFormat.B8G8R8A8_SRGB:
|
|
case (byte)NUTEXImageFormat.R8G8B8A8_UNORM:
|
|
case (byte)NUTEXImageFormat.R8G8B8A8_SRGB:
|
|
return 4;
|
|
|
|
case (byte)NUTEXImageFormat.BC1_UNORM:
|
|
case (byte)NUTEXImageFormat.BC1_SRGB:
|
|
case (byte)NUTEXImageFormat.BC4_UNORM:
|
|
case (byte)NUTEXImageFormat.BC4_SNORM:
|
|
return 8;
|
|
|
|
case (byte)NUTEXImageFormat.R32G32B32A32_FLOAT:
|
|
case (byte)NUTEXImageFormat.BC2_UNORM:
|
|
case (byte)NUTEXImageFormat.BC2_SRGB:
|
|
case (byte)NUTEXImageFormat.BC3_UNORM:
|
|
case (byte)NUTEXImageFormat.BC3_SRGB:
|
|
case (byte)NUTEXImageFormat.BC5_UNORM:
|
|
case (byte)NUTEXImageFormat.BC5_SNORM:
|
|
case (byte)NUTEXImageFormat.BC6_UFLOAT:
|
|
case (byte)NUTEXImageFormat.BC7_UNORM:
|
|
case (byte)NUTEXImageFormat.BC7_SRGB:
|
|
return 16;
|
|
default: return 0x00;
|
|
}
|
|
}
|
|
|
|
public Type[] Types
|
|
{
|
|
get
|
|
{
|
|
List<Type> types = new List<Type>();
|
|
types.Add(typeof(MenuExt));
|
|
return types.ToArray();
|
|
}
|
|
}
|
|
|
|
public override void OnClick(TreeView treeview)
|
|
{
|
|
UpdateEditor();
|
|
}
|
|
|
|
private void UpdateEditor()
|
|
{
|
|
ImageEditorBase editor = (ImageEditorBase)LibraryGUI.Instance.GetActiveContent(typeof(ImageEditorBase));
|
|
if (editor == null)
|
|
{
|
|
editor = new ImageEditorBase();
|
|
editor.Dock = DockStyle.Fill;
|
|
LibraryGUI.Instance.LoadEditor(editor);
|
|
}
|
|
|
|
Properties prop = new Properties();
|
|
prop.Width = Width;
|
|
prop.Height = Height;
|
|
prop.Depth = Depth;
|
|
prop.MipCount = MipCount;
|
|
prop.ArrayCount = ArrayCount;
|
|
prop.ImageSize = (uint)ImageData.Length;
|
|
prop.Format = NutFormat;
|
|
|
|
editor.Text = Text;
|
|
editor.LoadProperties(prop);
|
|
editor.LoadImage(this);
|
|
}
|
|
|
|
private void UseSizeRestrictions(object sender, EventArgs args)
|
|
{
|
|
if (sender is STToolStripItem)
|
|
{
|
|
if (((STToolStripItem)sender).Checked)
|
|
{
|
|
((STToolStripItem)sender).Checked = false;
|
|
}
|
|
else
|
|
{
|
|
((STToolStripItem)sender).Checked = true;
|
|
}
|
|
|
|
LimitFileSize = ((STToolStripItem)sender).Checked;
|
|
}
|
|
}
|
|
|
|
public class Properties
|
|
{
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Height of the image.")]
|
|
[Category("Image Info")]
|
|
public uint Height { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Width of the image.")]
|
|
[Category("Image Info")]
|
|
public uint Width { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Format of the image.")]
|
|
[Category("Image Info")]
|
|
public NUTEXImageFormat Format { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Depth of the image (3D type).")]
|
|
[Category("Image Info")]
|
|
public uint Depth { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Mip map count of the image.")]
|
|
[Category("Image Info")]
|
|
public uint MipCount { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Array count of the image for multiple surfaces.")]
|
|
[Category("Image Info")]
|
|
public uint ArrayCount { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("The image size in bytes.")]
|
|
[Category("Image Info")]
|
|
public uint ImageSize { get; set; }
|
|
}
|
|
|
|
class MenuExt : IFileMenuExtension
|
|
{
|
|
public STToolStripItem[] NewFileMenuExtensions => null;
|
|
public STToolStripItem[] NewFromFileMenuExtensions => null;
|
|
public STToolStripItem[] ToolsMenuExtensions => toolExt;
|
|
public STToolStripItem[] TitleBarExtensions => null;
|
|
public STToolStripItem[] CompressionMenuExtensions => null;
|
|
public STToolStripItem[] ExperimentalMenuExtensions => null;
|
|
public STToolStripItem[] EditMenuExtensions => null;
|
|
public ToolStripButton[] IconButtonMenuExtensions => null;
|
|
|
|
STToolStripItem[] toolExt = new STToolStripItem[1];
|
|
public MenuExt()
|
|
{
|
|
toolExt[0] = new STToolStripItem("Batch Export NUTEXB");
|
|
toolExt[0].Click += Export;
|
|
}
|
|
private void Export(object sender, EventArgs args)
|
|
{
|
|
string formats = FileFilters.NUTEXB;
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
BatchFormatExport form = new BatchFormatExport(Formats);
|
|
if (form.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string extension = form.GetSelectedExtension();
|
|
|
|
OpenFileDialog ofd = new OpenFileDialog();
|
|
ofd.Multiselect = true;
|
|
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
foreach (string file in ofd.FileNames)
|
|
{
|
|
NUTEXB texture = new NUTEXB();
|
|
texture.Read(new FileReader(file));
|
|
|
|
try
|
|
{
|
|
texture.Export(System.IO.Path.GetFullPath(file) + texture.ArcOffset + texture.Text + extension);
|
|
}
|
|
catch
|
|
{
|
|
Console.WriteLine("Something went wrong??");
|
|
}
|
|
|
|
texture = null;
|
|
GC.Collect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
public uint unk;
|
|
public int unk2;
|
|
|
|
public NUTEXImageFormat NutFormat;
|
|
public List<uint[]> mipSizes = new List<uint[]>();
|
|
public int Alignment;
|
|
public byte[] ImageData;
|
|
public string ArcOffset; //Temp for exporting in batch
|
|
|
|
public override string ExportFilter => FileFilters.NUTEXB;
|
|
public override string ReplaceFilter => FileFilters.NUTEXB;
|
|
|
|
private void Replace(object sender, EventArgs args)
|
|
{
|
|
OpenFileDialog ofd = new OpenFileDialog();
|
|
ofd.Filter = FileFilters.NUTEXB;
|
|
|
|
ofd.Multiselect = false;
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
var bntxFile = new BNTX();
|
|
var tex = new TextureData();
|
|
tex.Replace(ofd.FileName, MipCount, Format);
|
|
|
|
//If it's null, the operation is cancelled
|
|
if (tex.Texture == null)
|
|
return;
|
|
|
|
var surfacesNew = tex.GetSurfaces();
|
|
var surfaces = GetSurfaces();
|
|
|
|
if (LimitFileSize)
|
|
{
|
|
if (surfaces[0].mipmaps[0].Length != surfacesNew[0].mipmaps[0].Length)
|
|
throw new Exception($"Image must be the same size! {surfaces[0].mipmaps[0].Length}");
|
|
|
|
if (mipSizes[0].Length != surfacesNew[0].mipmaps.Count)
|
|
throw new Exception($"Mip map count must be the same! {mipSizes[0].Length}");
|
|
|
|
if (Width != tex.Texture.Width || Height != tex.Texture.Height)
|
|
throw new Exception("Image size must be the same!");
|
|
|
|
ImageData = tex.Texture.TextureData[0][0];
|
|
|
|
Width = tex.Texture.Width;
|
|
Height = tex.Texture.Height;
|
|
MipCount = tex.Texture.MipCount;
|
|
}
|
|
else
|
|
{
|
|
ImageData = tex.Texture.TextureData[0][0];
|
|
|
|
Width = tex.Texture.Width;
|
|
Height = tex.Texture.Height;
|
|
MipCount = tex.Texture.MipCount;
|
|
|
|
Format = tex.Format;
|
|
NutFormat = ConvertGenericToNutFormat(tex.Format);
|
|
|
|
mipSizes = GenerateMipSizes();
|
|
}
|
|
|
|
surfacesNew.Clear();
|
|
surfaces.Clear();
|
|
|
|
UpdateEditor();
|
|
}
|
|
}
|
|
|
|
private List<uint[]> GenerateMipSizes(uint SurfaceCount = 1)
|
|
{
|
|
List<uint[]> MipMapSizes = new List<uint[]>();
|
|
|
|
uint bpp = GetBytesPerPixel(Format);
|
|
uint blkWidth = GetBlockWidth(Format);
|
|
uint blkHeight = GetBlockHeight(Format);
|
|
uint blkDepth = GetBlockDepth(Format);
|
|
|
|
uint blockHeight = TegraX1Swizzle.GetBlockHeight(TegraX1Swizzle.DIV_ROUND_UP(Height, blkHeight));
|
|
uint BlockHeightLog2 = (uint)Convert.ToString(blockHeight, 2).Length - 1;
|
|
|
|
uint Pitch = 0;
|
|
uint DataAlignment = 512;
|
|
|
|
int linesPerBlockHeight = (1 << (int)BlockHeightLog2) * 8;
|
|
|
|
uint ArrayCount = (uint)mipSizes.Count;
|
|
|
|
uint ArrayOffset = 0;
|
|
for (int arrayLevel = 0; arrayLevel < ArrayCount; arrayLevel++)
|
|
{
|
|
uint SurfaceSize = 0;
|
|
int blockHeightShift = 0;
|
|
|
|
uint[] MipOffsets = new uint[MipCount];
|
|
|
|
for (int mipLevel = 0; mipLevel < MipCount; mipLevel++)
|
|
{
|
|
uint width = (uint)Math.Max(1, Width >> mipLevel);
|
|
uint height = (uint)Math.Max(1, Height >> mipLevel);
|
|
uint depth = (uint)Math.Max(1, Depth >> mipLevel);
|
|
|
|
uint size = TegraX1Swizzle.DIV_ROUND_UP(width, blkWidth) * TegraX1Swizzle.DIV_ROUND_UP(height, blkHeight) * bpp;
|
|
|
|
if (TegraX1Swizzle.pow2_round_up(TegraX1Swizzle.DIV_ROUND_UP(height, blkWidth)) < linesPerBlockHeight)
|
|
blockHeightShift += 1;
|
|
|
|
uint width__ = TegraX1Swizzle.DIV_ROUND_UP(width, blkWidth);
|
|
uint height__ = TegraX1Swizzle.DIV_ROUND_UP(height, blkHeight);
|
|
|
|
//Calculate the mip size instead
|
|
byte[] AlignedData = new byte[(TegraX1Swizzle.round_up(SurfaceSize, DataAlignment) - SurfaceSize)];
|
|
SurfaceSize += (uint)AlignedData.Length;
|
|
MipOffsets[mipLevel] = (SurfaceSize);
|
|
|
|
//Get the first mip offset and current one and the total image size
|
|
int msize = (int)((MipOffsets[0] + ImageData.Length - MipOffsets[mipLevel]) / ArrayCount);
|
|
|
|
Pitch = TegraX1Swizzle.round_up(width__ * bpp, 64);
|
|
SurfaceSize += Pitch * TegraX1Swizzle.round_up(height__, Math.Max(1, blockHeight >> blockHeightShift) * 8);
|
|
}
|
|
ArrayOffset += (uint)(ImageData.Length / ArrayCount);
|
|
|
|
MipMapSizes.Add(MipOffsets);
|
|
}
|
|
|
|
return MipMapSizes;
|
|
}
|
|
|
|
private void Export(object sender, EventArgs args)
|
|
{
|
|
ExportImage();
|
|
}
|
|
private void Save(object sender, EventArgs args)
|
|
{
|
|
List<IFileFormat> formats = new List<IFileFormat>();
|
|
formats.Add(this);
|
|
|
|
SaveFileDialog sfd = new SaveFileDialog();
|
|
sfd.Filter = Utils.GetAllFilters(formats);
|
|
sfd.FileName = FileName;
|
|
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
STFileSaver.SaveFileFormat(this, sfd.FileName);
|
|
}
|
|
}
|
|
public void Read(FileReader reader)
|
|
{
|
|
ImageKey = "Texture";
|
|
SelectedImageKey = "Texture";
|
|
|
|
long pos = reader.BaseStream.Length;
|
|
string magic = reader.ReadMagic((int)pos - 7, 3);//Check magic first!
|
|
|
|
if (magic != "XET")
|
|
throw new Exception($"Invalid magic! Expected XET but got {magic}");
|
|
|
|
reader.Seek(pos - 112, System.IO.SeekOrigin.Begin); //Subtract size where the name occurs
|
|
byte padding = reader.ReadByte();
|
|
string StrMagic = reader.ReadString(3);
|
|
Text = reader.ReadString(Syroot.BinaryData.BinaryStringFormat.ZeroTerminated);
|
|
|
|
//We cannot check if it's swizzled properly
|
|
//So far if this part is blank, it's for Taiko No Tatsujin "Drum 'n' Fun
|
|
if (StrMagic != "XNT")
|
|
IsSwizzled = false;
|
|
|
|
reader.Seek(pos - 48, System.IO.SeekOrigin.Begin); //Subtract size of header
|
|
uint padding2 = reader.ReadUInt32();
|
|
Width = reader.ReadUInt32();
|
|
Height = reader.ReadUInt32();
|
|
Depth = reader.ReadUInt32(); //3d textures
|
|
NutFormat = reader.ReadEnum<NUTEXImageFormat>(true);
|
|
unk = reader.ReadByte(); //Related to pixel type??
|
|
ushort padding3 = reader.ReadUInt16();
|
|
unk2 = reader.ReadInt32();
|
|
MipCount = reader.ReadUInt32();
|
|
Alignment = reader.ReadInt32();
|
|
ArrayCount = reader.ReadUInt32(); //6 for cubemaps
|
|
int imagesize = reader.ReadInt32();
|
|
Format = ConvertFormat(NutFormat);
|
|
|
|
reader.Seek(imagesize, System.IO.SeekOrigin.Begin); //Get mipmap sizes
|
|
for (int arrayLevel = 0; arrayLevel < ArrayCount; arrayLevel++)
|
|
{
|
|
long mipPos = reader.Position;
|
|
uint[] mips = reader.ReadUInt32s((int)MipCount);
|
|
mipSizes.Add(mips);
|
|
|
|
//Each mip section is 0x40 size for each array
|
|
//Seek to next one
|
|
reader.Seek(mipPos + 0x40, System.IO.SeekOrigin.Begin);
|
|
}
|
|
reader.Seek(0, System.IO.SeekOrigin.Begin);
|
|
ImageData = reader.ReadBytes(imagesize);
|
|
|
|
reader.Dispose();
|
|
reader.Close();
|
|
reader = null;
|
|
}
|
|
|
|
public static NUTEXImageFormat ConvertGenericToNutFormat(TEX_FORMAT nutFormat)
|
|
{
|
|
switch (nutFormat)
|
|
{
|
|
case TEX_FORMAT.B8G8R8A8_UNORM_SRGB: return NUTEXImageFormat.B8G8R8A8_SRGB;
|
|
case TEX_FORMAT.B8G8R8A8_UNORM: return NUTEXImageFormat.B8G8R8A8_UNORM;
|
|
case TEX_FORMAT.BC1_UNORM_SRGB: return NUTEXImageFormat.BC1_SRGB;
|
|
case TEX_FORMAT.BC1_UNORM: return NUTEXImageFormat.BC1_UNORM;
|
|
case TEX_FORMAT.BC2_UNORM: return NUTEXImageFormat.BC2_UNORM;
|
|
case TEX_FORMAT.BC3_UNORM: return NUTEXImageFormat.BC3_UNORM;
|
|
case TEX_FORMAT.BC3_UNORM_SRGB: return NUTEXImageFormat.BC3_SRGB;
|
|
case TEX_FORMAT.BC4_UNORM: return NUTEXImageFormat.BC4_UNORM;
|
|
case TEX_FORMAT.BC4_SNORM: return NUTEXImageFormat.BC4_SNORM;
|
|
case TEX_FORMAT.BC5_UNORM: return NUTEXImageFormat.BC5_UNORM;
|
|
case TEX_FORMAT.BC5_SNORM: return NUTEXImageFormat.BC5_SNORM;
|
|
case TEX_FORMAT.BC6H_UF16: return NUTEXImageFormat.BC6_UFLOAT;
|
|
case TEX_FORMAT.BC7_UNORM: return NUTEXImageFormat.BC7_UNORM;
|
|
case TEX_FORMAT.BC7_UNORM_SRGB: return NUTEXImageFormat.BC7_SRGB;
|
|
case TEX_FORMAT.R32G32B32A32_FLOAT: return NUTEXImageFormat.R32G32B32A32_FLOAT;
|
|
case TEX_FORMAT.R8G8B8A8_UNORM_SRGB: return NUTEXImageFormat.R8G8B8A8_SRGB;
|
|
case TEX_FORMAT.R8G8B8A8_UNORM: return NUTEXImageFormat.R8G8B8A8_UNORM;
|
|
default:
|
|
throw new Exception($"Cannot convert format {nutFormat}");
|
|
}
|
|
}
|
|
|
|
public static TEX_FORMAT ConvertFormat(NUTEXImageFormat nutFormat)
|
|
{
|
|
switch (nutFormat)
|
|
{
|
|
case NUTEXImageFormat.B8G8R8A8_SRGB: return TEX_FORMAT.B8G8R8A8_UNORM_SRGB;
|
|
case NUTEXImageFormat.B8G8R8A8_UNORM: return TEX_FORMAT.B8G8R8A8_UNORM;
|
|
case NUTEXImageFormat.BC1_SRGB: return TEX_FORMAT.BC1_UNORM_SRGB;
|
|
case NUTEXImageFormat.BC1_UNORM: return TEX_FORMAT.BC1_UNORM;
|
|
case NUTEXImageFormat.BC2_UNORM: return TEX_FORMAT.BC2_UNORM;
|
|
case NUTEXImageFormat.BC3_UNORM: return TEX_FORMAT.BC3_UNORM;
|
|
case NUTEXImageFormat.BC3_SRGB: return TEX_FORMAT.BC3_UNORM_SRGB;
|
|
case NUTEXImageFormat.BC4_UNORM: return TEX_FORMAT.BC4_UNORM;
|
|
case NUTEXImageFormat.BC4_SNORM: return TEX_FORMAT.BC4_SNORM;
|
|
case NUTEXImageFormat.BC5_UNORM: return TEX_FORMAT.BC5_UNORM;
|
|
case NUTEXImageFormat.BC5_SNORM: return TEX_FORMAT.BC5_SNORM;
|
|
case NUTEXImageFormat.BC6_UFLOAT: return TEX_FORMAT.BC6H_UF16;
|
|
case NUTEXImageFormat.BC7_UNORM: return TEX_FORMAT.BC7_UNORM;
|
|
case NUTEXImageFormat.BC7_SRGB: return TEX_FORMAT.BC7_UNORM_SRGB;
|
|
case NUTEXImageFormat.R32G32B32A32_FLOAT: return TEX_FORMAT.R32G32B32A32_FLOAT;
|
|
case NUTEXImageFormat.R8G8B8A8_SRGB: return TEX_FORMAT.R8G8B8A8_UNORM_SRGB;
|
|
case NUTEXImageFormat.R8G8B8A8_UNORM: return TEX_FORMAT.R8G8B8A8_UNORM;
|
|
default:
|
|
throw new Exception($"Cannot convert format {nutFormat}");
|
|
}
|
|
}
|
|
|
|
public void Write(FileWriter writer)
|
|
{
|
|
//MipSizes stores mip sizes for multile arrays
|
|
int arrayCount = mipSizes.Count;
|
|
|
|
//Mip sizes for the first array
|
|
int mipCount = mipSizes[0].Length;
|
|
|
|
writer.Write(ImageData); //Write textue block first
|
|
|
|
long headerStart = writer.Position;
|
|
foreach (var mips in mipSizes)
|
|
{
|
|
long MipStart = writer.Position;
|
|
writer.Write(mips); //Write textue block first
|
|
|
|
writer.Seek(MipStart + 0x40, System.IO.SeekOrigin.Begin);
|
|
}
|
|
long stringPos = writer.Position;
|
|
writer.Write((byte)0x20);
|
|
writer.WriteSignature("XNT");
|
|
writer.WriteString(Text);
|
|
writer.Seek(stringPos + 0x40, System.IO.SeekOrigin.Begin);
|
|
writer.Seek(4); //padding
|
|
writer.Write(Width);
|
|
writer.Write(Height);
|
|
writer.Write(Depth);
|
|
writer.Write((byte)NutFormat);
|
|
writer.Write((byte)unk);
|
|
writer.Seek(2); //padding
|
|
writer.Write(unk2);
|
|
writer.Write(mipCount);
|
|
writer.Write(Alignment);
|
|
writer.Write(arrayCount);
|
|
writer.Write(ImageData.Length);
|
|
writer.WriteSignature(" XET");
|
|
writer.Write(131073);
|
|
|
|
writer.Close();
|
|
writer.Dispose();
|
|
}
|
|
|
|
public override void SetImageData(Bitmap bitmap, int ArrayLevel)
|
|
{
|
|
if (!IsSwizzled)
|
|
{
|
|
// MipCount = GenerateMipCount(bitmap.Width, bitmap.Height);
|
|
|
|
// ImageData = GenerateMipsAndCompress(bitmap, Format);
|
|
return;
|
|
}
|
|
|
|
Texture tex = new Texture();
|
|
tex.Height = (uint)bitmap.Height;
|
|
tex.Width = (uint)bitmap.Width;
|
|
tex.Format = TextureData.GenericToBntxSurfaceFormat(Format);
|
|
tex.Name = Text;
|
|
tex.Path = "";
|
|
tex.TextureData = new List<List<byte[]>>();
|
|
|
|
STChannelType[] channels = SetChannelsByFormat(Format);
|
|
tex.sparseBinding = 0; //false
|
|
tex.sparseResidency = 0; //false
|
|
tex.Flags = 0;
|
|
tex.Swizzle = 0;
|
|
tex.textureLayout = 0;
|
|
tex.Regs = new uint[0];
|
|
tex.AccessFlags = 0x20;
|
|
tex.ArrayLength = (uint)ArrayLevel;
|
|
tex.MipCount = MipCount;
|
|
tex.Depth = Depth;
|
|
tex.Dim = Dim.Dim2D;
|
|
tex.TileMode = TileMode.Default;
|
|
tex.textureLayout2 = 0x010007;
|
|
tex.SurfaceDim = SurfaceDim.Dim2D;
|
|
tex.SampleCount = 1;
|
|
tex.Pitch = 32;
|
|
tex.MipOffsets = new long[tex.MipCount];
|
|
|
|
MipCount = GenerateMipCount(bitmap.Width, bitmap.Height);
|
|
|
|
var mipmaps = TextureImporterSettings.SwizzleSurfaceMipMaps(tex,
|
|
GenerateMipsAndCompress(bitmap, Format), MipCount);
|
|
|
|
ImageData = Utils.CombineByteArray(mipmaps.ToArray());
|
|
}
|
|
|
|
public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0)
|
|
{
|
|
if (!IsSwizzled)
|
|
return DDS.GetArrayFaces(this, ImageData,1)[ArrayLevel].mipmaps[0];
|
|
|
|
int target = 1;
|
|
uint bpp = GetBytesPerPixel(Format);
|
|
uint blkWidth = GetBlockWidth(Format);
|
|
uint blkHeight = GetBlockHeight(Format);
|
|
uint blkDepth = GetBlockDepth(Format);
|
|
|
|
uint blockHeight = TegraX1Swizzle.GetBlockHeight(TegraX1Swizzle.DIV_ROUND_UP(Height, blkHeight));
|
|
uint BlockHeightLog2 = (uint)Convert.ToString(blockHeight, 2).Length - 1;
|
|
|
|
uint Pitch = 0;
|
|
uint DataAlignment = 512;
|
|
uint TileMode = 0;
|
|
|
|
int linesPerBlockHeight = (1 << (int)BlockHeightLog2) * 8;
|
|
|
|
uint ArrayCount = (uint)mipSizes.Count;
|
|
|
|
uint ArrayOffset = 0;
|
|
for (int arrayLevel = 0; arrayLevel < ArrayCount; arrayLevel++)
|
|
{
|
|
uint SurfaceSize = 0;
|
|
int blockHeightShift = 0;
|
|
|
|
List<uint> MipOffsets = new List<uint>();
|
|
|
|
for (int mipLevel = 0; mipLevel < mipSizes[arrayLevel].Length; mipLevel++)
|
|
{
|
|
uint width = (uint)Math.Max(1, Width >> mipLevel);
|
|
uint height = (uint)Math.Max(1, Height >> mipLevel);
|
|
uint depth = (uint)Math.Max(1, Depth >> mipLevel);
|
|
|
|
uint size = TegraX1Swizzle.DIV_ROUND_UP(width, blkWidth) * TegraX1Swizzle.DIV_ROUND_UP(height, blkHeight) * bpp;
|
|
|
|
if (TegraX1Swizzle.pow2_round_up(TegraX1Swizzle.DIV_ROUND_UP(height, blkWidth)) < linesPerBlockHeight)
|
|
blockHeightShift += 1;
|
|
|
|
|
|
uint width__ = TegraX1Swizzle.DIV_ROUND_UP(width, blkWidth);
|
|
uint height__ = TegraX1Swizzle.DIV_ROUND_UP(height, blkHeight);
|
|
|
|
//Calculate the mip size instead
|
|
byte[] AlignedData = new byte[(TegraX1Swizzle.round_up(SurfaceSize, DataAlignment) - SurfaceSize)];
|
|
SurfaceSize += (uint)AlignedData.Length;
|
|
MipOffsets.Add(SurfaceSize);
|
|
|
|
//Get the first mip offset and current one and the total image size
|
|
int msize = (int)((MipOffsets[0] + ImageData.Length - MipOffsets[mipLevel]) / ArrayCount);
|
|
|
|
byte[] data_ = Utils.SubArray(ImageData, ArrayOffset + MipOffsets[mipLevel], (uint)msize);
|
|
|
|
try
|
|
{
|
|
Pitch = TegraX1Swizzle.round_up(width__ * bpp, 64);
|
|
SurfaceSize += Pitch * TegraX1Swizzle.round_up(height__, Math.Max(1, blockHeight >> blockHeightShift) * 8);
|
|
|
|
Console.WriteLine($"{width} {height} {blkWidth} {blkHeight} {target} {bpp} {TileMode} {(int)Math.Max(0, BlockHeightLog2 - blockHeightShift)} {data_.Length}");
|
|
byte[] result = TegraX1Swizzle.deswizzle(width, height, depth, blkWidth, blkHeight, blkDepth, target, bpp, TileMode, (int)Math.Max(0, BlockHeightLog2 - blockHeightShift), data_);
|
|
//Create a copy and use that to remove uneeded data
|
|
byte[] result_ = new byte[size];
|
|
Array.Copy(result, 0, result_, 0, size);
|
|
result = null;
|
|
|
|
if (ArrayLevel == arrayLevel && MipLevel == mipLevel)
|
|
return result_;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
System.Windows.Forms.MessageBox.Show($"Failed to swizzle texture {Text}!");
|
|
Console.WriteLine(e);
|
|
|
|
return new byte[0];
|
|
}
|
|
}
|
|
|
|
ArrayOffset += (uint)(ImageData.Length / ArrayCount);
|
|
}
|
|
return new byte[0];
|
|
}
|
|
MenuItem save = new MenuItem("Save");
|
|
MenuItem export = new MenuItem("Export");
|
|
MenuItem replace = new MenuItem("Replace");
|
|
MenuItem useSizeRestrictions = new MenuItem("UseSizeRestrictions");
|
|
|
|
public void Load(System.IO.Stream stream)
|
|
{
|
|
Text = FileName;
|
|
CanSave = true;
|
|
|
|
Read(new FileReader(stream));
|
|
|
|
ImageKey = "Texture";
|
|
SelectedImageKey = "Texture";
|
|
|
|
useSizeRestrictions.Checked = true;
|
|
|
|
ContextMenu = new ContextMenu();
|
|
ContextMenu.MenuItems.Add(save);
|
|
ContextMenu.MenuItems.Add(useSizeRestrictions);
|
|
ContextMenu.MenuItems.Add(export);
|
|
ContextMenu.MenuItems.Add(replace);
|
|
|
|
save.Click += Save;
|
|
replace.Click += Replace;
|
|
export.Click += Export;
|
|
}
|
|
public void Unload()
|
|
{
|
|
|
|
}
|
|
public byte[] Save()
|
|
{
|
|
System.IO.MemoryStream mem = new System.IO.MemoryStream();
|
|
Write(new FileWriter(mem));
|
|
return mem.ToArray();
|
|
}
|
|
}
|
|
} |