using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Linq; using System.Windows.Forms; using Toolbox.Library; using Toolbox.Library.IO; using Toolbox.Library.Forms; using System.ComponentModel; using Bfres.Structs; namespace FirstPlugin { public class BFLIM : STGenericTexture, IEditor, IFileFormat { public FileType FileType { get; set; } = FileType.Image; 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.R8G8B8A8_UNORM_SRGB, TEX_FORMAT.R8G8B8A8_UNORM, TEX_FORMAT.A8_UNORM, TEX_FORMAT.R8G8_UNORM, TEX_FORMAT.R8G8_SNORM, TEX_FORMAT.B5G6R5_UNORM, TEX_FORMAT.R10G10B10A2_UNORM, TEX_FORMAT.B4G4R4A4_UNORM, }; } } public override bool CanEdit { get; set; } = true; public bool CanSave { get; set; } public string[] Description { get; set; } = new string[] { "Layout Image" }; public string[] Extension { get; set; } = new string[] { "*.bflim" }; 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, "FLIM", reader.BaseStream.Length - 0x28); } } public Type[] Types { get { List types = new List(); types.Add(typeof(MenuExt)); return types.ToArray(); } } private ImageEditorBase form; public ImageEditorBase OpenForm() { form = new ImageEditorBase(); return form; } public void UpdateForm() { UpdateForm(form); } public void FillEditor(UserControl control) { form = (ImageEditorBase)control; UpdateForm((ImageEditorBase)control); } private void UpdateForm(ImageEditorBase form) { if (image != null) { 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 = Format; prop.TileMode = image.TileMode; prop.Swizzle = image.Swizzle; form.Text = Text; form.Dock = DockStyle.Fill; form.ResetMenus(); form.AddFileContextEvent("Save", Save); form.AddFileContextEvent("Replace", Replace); form.LoadProperties(prop); form.LoadImage(this); Console.WriteLine("UpdateForm LoadImage"); } } class MenuExt : IFileMenuExtension { public STToolStripItem[] NewFileMenuExtensions => null; public STToolStripItem[] NewFromFileMenuExtensions => newFileExt; 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[] newFileExt = new STToolStripItem[1]; STToolStripItem[] toolExt = new STToolStripItem[1]; public MenuExt() { toolExt[0] = new STToolStripItem("Textures"); toolExt[0].DropDownItems.Add(new STToolStripItem("Batch Export (Wii U Textures)", Export)); newFileExt[0] = new STToolStripItem("BFLIM From Image", CreateNew); } private void Export(object sender, EventArgs args) { string formats = FileFilters.GTX; string[] forms = formats.Split('|'); List Formats = new List(); 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; ofd.Filter = Utils.GetAllFilters(new Type[] { typeof(BFLIM), typeof(BXFNT), typeof(BFRES), typeof(PTCL), typeof(SARC) }); if (ofd.ShowDialog() == DialogResult.OK) { FolderSelectDialog folderDialog = new FolderSelectDialog(); if (folderDialog.ShowDialog() == DialogResult.OK) { foreach (string file in ofd.FileNames) { var FileFormat = STFileLoader.OpenFileFormat(file, new Type[] { typeof(BFLIM), typeof(PTCL), typeof(BFRES), typeof(BXFNT), typeof(SARC) }); if (FileFormat == null) continue; if (FileFormat is SARC) { string ArchiveFilePath = Path.Combine(folderDialog.SelectedPath, Path.GetFileNameWithoutExtension(file)); ArchiveFilePath = Path.GetDirectoryName(ArchiveFilePath); if (!Directory.Exists(ArchiveFilePath)) Directory.CreateDirectory(ArchiveFilePath); SearchBinary(FileFormat, ArchiveFilePath, Extension); } else SearchBinary(FileFormat, folderDialog.SelectedPath, Extension); } } } } } private void SearchBinary(IFileFormat FileFormat, string Folder, string Extension) { if (FileFormat is SARC) { string ArchiveFilePath = Path.Combine(Folder, Path.GetFileNameWithoutExtension(FileFormat.FileName)); if (!Directory.Exists(ArchiveFilePath)) Directory.CreateDirectory(ArchiveFilePath); foreach (var file in ((SARC)FileFormat).Files) { var archiveFile = STFileLoader.OpenFileFormat(file.FileName, new Type[] { typeof(BFLIM), typeof(BXFNT), typeof(PTCL), typeof(BFRES), typeof(SARC) }, file.FileData); if (archiveFile == null) continue; SearchBinary(archiveFile, ArchiveFilePath, Extension); } } if (FileFormat is BXFNT) { foreach (STGenericTexture texture in ((BXFNT)FileFormat).bffnt.FontSection.TextureGlyph.Textures) texture.Export(Path.Combine(Folder, $"{texture.Text}{Extension}")); } if (FileFormat is BFRES) { var FtexContainer = ((BFRES)FileFormat).GetFTEXContainer; if (FtexContainer != null) { foreach (var texture in FtexContainer.ResourceNodes.Values) ((FTEX)texture).Export(Path.Combine(Folder, $"{texture.Text}{Extension}")); } } if (FileFormat is PTCL) { if (((PTCL)FileFormat).headerU != null) { foreach (STGenericTexture texture in ((PTCL)FileFormat).headerU.Textures) texture.Export(Path.Combine(Folder, $"{texture.Text}{Extension}")); } } if (FileFormat is BFLIM) { ((BFLIM)FileFormat).Export(Path.Combine(Folder, $"{FileFormat.FileName}{Extension}")); } FileFormat.Unload(); } public void CreateNew(object sender, EventArgs args) { BFLIM bflim = CreateNewFromImage(); if (bflim == null) return; var form = new GenericEditorForm(false, bflim.OpenForm()); LibraryGUI.CreateMdiWindow(form); bflim.UpdateForm(); } } public static BFLIM CreateNewFromImage() { BFLIM bflim = new BFLIM(); bflim.CanSave = true; bflim.IFileInfo = new IFileInfo(); bflim.header = new Header(); OpenFileDialog ofd = new OpenFileDialog(); ofd.Multiselect = false; ofd.Filter = FileFilters.GTX; if (ofd.ShowDialog() != DialogResult.OK) return null; FTEX ftex = new FTEX(); ftex.ReplaceTexture(ofd.FileName, TEX_FORMAT.BC3_UNORM_SRGB, 1, 0, bflim.SupportedFormats, true, false, true, false); if (ftex.texture != null) { bflim.Text = $"{Path.GetFileNameWithoutExtension(ofd.FileName)}.bflim"; bflim.image = new Image(); bflim.image.Swizzle = (byte)ftex.texture.Swizzle; bflim.image.BflimFormat = FormatsWiiU.FirstOrDefault(x => x.Value == ftex.Format).Key; if (ftex.UseBc4Alpha) bflim.image.BflimFormat = 16; bflim.image.Height = (ushort)ftex.texture.Height; bflim.image.Width = (ushort)ftex.texture.Width; bflim.Format = FormatsWiiU[bflim.image.BflimFormat]; bflim.Width = bflim.image.Width; bflim.Height = bflim.image.Height; bflim.ImageData = ftex.texture.Data; bflim.LoadComponents(bflim.Format, ftex.UseBc4Alpha); } return bflim; } public override string ExportFilter => FileFilters.GTX; public override string ReplaceFilter => FileFilters.GTX; private void Replace(object sender, EventArgs args) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = ReplaceFilter; ofd.FileName = Text; ofd.Multiselect = false; if (ofd.ShowDialog() == DialogResult.OK) { Replace(ofd.FileName); } } public override void Replace(string FileName) { uint swizzle = (image.Swizzle >> 8) & 7; FTEX ftex = new FTEX(); ftex.ReplaceTexture(FileName, Format, 1, swizzle, SupportedFormats, true, true, true, false); if (ftex.texture != null) { image.Swizzle = ftex.texture.Swizzle; image.BflimFormat = FormatsWiiU.FirstOrDefault(x => x.Value == ftex.Format).Key; image.Height = (ushort)ftex.texture.Height; image.Width = (ushort)ftex.texture.Width; if (ftex.UseBc4Alpha) image.BflimFormat = 16; Format = FormatsWiiU[image.BflimFormat]; Width = image.Width; Height = image.Height; ImageData = ftex.texture.Data; LoadComponents(Format, ftex.UseBc4Alpha); UpdateForm(); } } private void Save(object sender, EventArgs args) { List formats = new List(); 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 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 TEX_FORMAT 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; } [Browsable(true)] [ReadOnly(true)] [Description("The image tilemode.")] [Category("Image Info")] public GX2.GX2TileMode TileMode { get; set; } [Browsable(true)] [ReadOnly(true)] [Category("Image Info")] public uint Swizzle { get; set; } } Header header; Image image; byte[] ImageData; public void Load(System.IO.Stream stream) { CanSave = true; Text = FileName; using (var reader = new FileReader(stream)) { uint FileSize = (uint)reader.BaseStream.Length; reader.Seek(FileSize - 0x28, SeekOrigin.Begin); header = new Header(); header.Read(reader); bool Is3DS = reader.ByteOrder == Syroot.BinaryData.ByteOrder.LittleEndian; reader.Seek(header.HeaderSize + FileSize - 0x28, SeekOrigin.Begin); image = new Image(Is3DS); image.Read(reader); bool isBc4Alpha = image.BflimFormat == 16; if (Is3DS) Format = Formats3DS[image.BflimFormat]; else Format = FormatsWiiU[image.BflimFormat]; Width = image.Width; Height = image.Height; LoadComponents(Format, isBc4Alpha); uint ImageSize = reader.ReadUInt32(); reader.Position = 0; ImageData = reader.ReadBytes((int)ImageSize); if (!PluginRuntime.bflimTextures.ContainsKey(Text)) PluginRuntime.bflimTextures.Add(Text, this); } } private void LoadComponents(TEX_FORMAT Format, bool isBc4Alpha) { switch (Format) { case TEX_FORMAT.BC5_SNORM: case TEX_FORMAT.BC5_UNORM: RedChannel = STChannelType.Red; GreenChannel = STChannelType.Red; BlueChannel = STChannelType.Red; AlphaChannel = STChannelType.Green; break; case TEX_FORMAT.BC4_SNORM: case TEX_FORMAT.BC4_UNORM: RedChannel = STChannelType.Red; GreenChannel = STChannelType.Red; BlueChannel = STChannelType.Red; AlphaChannel = STChannelType.One; if (isBc4Alpha) { RedChannel = STChannelType.One; GreenChannel = STChannelType.One; BlueChannel = STChannelType.One; AlphaChannel = STChannelType.Red; } break; } } public static Dictionary Formats3DS = new Dictionary() { [0] = TEX_FORMAT.L8, [1] = TEX_FORMAT.A8_UNORM, [2] = TEX_FORMAT.LA4, [3] = TEX_FORMAT.LA8, [4] = TEX_FORMAT.HIL08, [5] = TEX_FORMAT.B5G6R5_UNORM, [6] = TEX_FORMAT.B8G8R8A8_UNORM, [7] = TEX_FORMAT.B5G5R5A1_UNORM, [8] = TEX_FORMAT.B4G4R4A4_UNORM, [9] = TEX_FORMAT.R8G8B8A8_UNORM, [10] = TEX_FORMAT.ETC1_UNORM, [11] = TEX_FORMAT.ETC1_A4, [12] = TEX_FORMAT.BC1_UNORM, [13] = TEX_FORMAT.BC2_UNORM, [14] = TEX_FORMAT.BC3_UNORM, [15] = TEX_FORMAT.BC4_UNORM, //BC4L_UNORM [16] = TEX_FORMAT.BC4_UNORM, //BC4A_UNORM [17] = TEX_FORMAT.BC5_UNORM, [18] = TEX_FORMAT.L4, [19] = TEX_FORMAT.A4, }; public static Dictionary FormatsWiiU = new Dictionary() { [0] = TEX_FORMAT.L8, [1] = TEX_FORMAT.A8_UNORM, [2] = TEX_FORMAT.LA4, [3] = TEX_FORMAT.LA8, [4] = TEX_FORMAT.R8G8_UNORM, //HILO8 [5] = TEX_FORMAT.B5G6R5_UNORM, [6] = TEX_FORMAT.B8G8R8A8_UNORM, [7] = TEX_FORMAT.B5G5R5A1_UNORM, [8] = TEX_FORMAT.B4G4R4A4_UNORM, [9] = TEX_FORMAT.R8G8B8A8_UNORM, [10] = TEX_FORMAT.ETC1_UNORM, [11] = TEX_FORMAT.ETC1_A4, [12] = TEX_FORMAT.BC1_UNORM, [13] = TEX_FORMAT.BC2_UNORM, [14] = TEX_FORMAT.BC3_UNORM, [15] = TEX_FORMAT.BC4_UNORM, //BC4L_UNORM [16] = TEX_FORMAT.BC4_UNORM, //BC4A_UNORM [17] = TEX_FORMAT.BC5_UNORM, [18] = TEX_FORMAT.L4, [19] = TEX_FORMAT.A4, [20] = TEX_FORMAT.R8G8B8A8_UNORM_SRGB, [21] = TEX_FORMAT.BC1_UNORM_SRGB, [22] = TEX_FORMAT.BC2_UNORM_SRGB, [23] = TEX_FORMAT.BC3_UNORM_SRGB, [24] = TEX_FORMAT.R10G10B10A2_UNORM, [25] = TEX_FORMAT.R5G5B5_UNORM, }; public override void SetImageData(System.Drawing.Bitmap bitmap, int ArrayLevel) { if (bitmap == null || image == null) return; //Image is likely disposed and not needed to be applied MipCount = 1; var Gx2Format = FTEX.ConvertToGx2Format(Format); uint swizzle = (image.Swizzle >> 8) & 7; try { //Create image block from bitmap first var data = GenerateMipsAndCompress(bitmap, MipCount, Format); //Swizzle and create surface var surface = GX2.CreateGx2Texture(data, Text, (uint)image.TileMode, (uint)0, (uint)image.Width, (uint)image.Height, (uint)1, (uint)Gx2Format, (uint)swizzle, (uint)1, (uint)MipCount ); image.Swizzle = surface.swizzle; image.BflimFormat = FormatsWiiU.FirstOrDefault(x => x.Value == Format).Key; image.Height = (ushort)surface.height; image.Width = (ushort)surface.width; Width = image.Width; Height = image.Height; ImageData = surface.data; IsEdited = true; LoadOpenGLTexture(); LibraryGUI.UpdateViewport(); } catch (Exception ex) { STErrorDialog.Show("Failed to swizzle and compress image " + Text, "Error", ex.ToString()); } } public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0, int DepthLevel = 0) { if (image.Is3DS) { PlatformSwizzle = PlatformSwizzle.Platform_3DS; return ImageData; } else { uint bpp = GetBytesPerPixel(Format); GX2.GX2Surface surf = new GX2.GX2Surface(); surf.bpp = bpp; surf.height = image.Height; surf.width = image.Width; surf.aa = (uint)GX2.GX2AAMode.GX2_AA_MODE_1X; surf.alignment = image.Alignment; surf.depth = 1; surf.dim = (uint)GX2.GX2SurfaceDimension.DIM_2D; surf.format = (uint)FTEX.ConvertToGx2Format(Format); surf.use = (uint)GX2.GX2SurfaceUse.USE_COLOR_BUFFER; surf.pitch = 0; surf.data = ImageData; surf.numMips = 1; surf.mipOffset = new uint[0]; surf.mipData = ImageData; surf.tileMode = (uint)image.TileMode; surf.swizzle = image.Swizzle; surf.numArray = 1; return GX2.Decode(surf, ArrayLevel, MipLevel); } } public void Unload() { } public void Save(System.IO.Stream stream) { using (var writer = new FileWriter(stream, true)) { writer.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian; writer.CheckByteOrderMark(header.ByteOrderMark); writer.Write(ImageData); long headerPos = writer.Position; header.Write(writer); image.Write(writer); writer.Write(ImageData.Length); writer.Seek(headerPos + 0x0C, SeekOrigin.Begin); writer.Write((uint)writer.BaseStream.Length); } } public class Header { public ushort ByteOrderMark; public ushort HeaderSize; public uint Version; public ushort blockount; public ushort padding; public Header() { ByteOrderMark = 65279; HeaderSize = 20; blockount = 1; Version = 33685504; } public void Read(FileReader reader) { reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian; reader.ReadSignature(4, "FLIM"); ByteOrderMark = reader.ReadUInt16(); reader.CheckByteOrderMark(ByteOrderMark); HeaderSize = reader.ReadUInt16(); Version = reader.ReadUInt32(); uint fileSize = reader.ReadUInt32(); blockount = reader.ReadUInt16(); padding = reader.ReadUInt16(); } public void Write(FileWriter writer) { writer.WriteSignature("FLIM"); if (writer.ByteOrder != Syroot.BinaryData.ByteOrder.LittleEndian) writer.Write((ushort)0xFFFE); else writer.Write((ushort)0xFEFF); writer.Write(HeaderSize); writer.Write(Version); writer.Write(uint.MaxValue); writer.Write(blockount); writer.Write(padding); } } public class Image { public uint Size; public ushort Width; public ushort Height; public ushort Alignment; public byte BflimFormat; public byte Flags; public bool Is3DS = false; public Image(bool is3DS) { Is3DS = is3DS; } public Image() { Alignment = 8192; Flags = 0xC4; Size = 16; } public GX2.GX2TileMode TileMode { get { return (GX2.GX2TileMode) ((int)Flags & 31); } set { Flags = (byte)((int)Flags & 224 | (int)(byte)value & 31); } } public uint Swizzle { get { return (uint)(((int)((uint)Flags >> 5) & 7) << 8); } set { Flags = (byte)((int)Flags & 31 | (int)(byte)(value >> 8) << 5); } } public void Read(FileReader reader) { reader.ReadSignature(4, "imag"); Size = reader.ReadUInt32(); Width = reader.ReadUInt16(); Height = reader.ReadUInt16(); Alignment = reader.ReadUInt16(); BflimFormat = reader.ReadByte(); Flags = reader.ReadByte(); } public void Write(FileWriter writer) { writer.WriteSignature("imag"); writer.Write(Size); writer.Write(Width); writer.Write(Height); writer.Write(Alignment); writer.Write(BflimFormat); writer.Write(Flags); } } } }