using System; using System.Collections.Generic; using System.Linq; using System.IO; using System.Drawing; using System.Threading.Tasks; using Switch_Toolbox.Library; using Switch_Toolbox.Library.IO; using Syroot.NintenTools.Bfres.GX2; namespace FirstPlugin { public class GTXImporterSettings { public string TexName; public uint TexWidth; public uint TexHeight; public uint MipCount; public uint Depth = 1; public uint arrayLength = 1; public List DataBlockOutput = new List(); public List DecompressedData = new List(); public GTX.GX2SurfaceFormat Format; public bool GenerateMipmaps; public bool IsSRGB; public uint tileMode = 4; public uint swizzle = 4; public GX2CompSel[] compSel = new GX2CompSel[4]; public GX2SurfaceDim SurfaceDim = GX2SurfaceDim.Dim2D; public GX2AAMode AAMode = GX2AAMode.Mode1X; private GTX.GX2SurfaceFormat LoadDDSFormat(uint fourCC, DDS dds = null, bool IsSRGB = false) { bool IsDX10 = false; switch (fourCC) { case DDS.FOURCC_DXT1: if (IsSRGB) return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC1_SRGB; else return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC1_UNORM; case DDS.FOURCC_DXT3: if (IsSRGB) return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC2_SRGB; else return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC2_UNORM; case DDS.FOURCC_DXT5: if (IsSRGB) return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC3_SRGB; else return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC3_UNORM; case DDS.FOURCC_BC4U: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC4_UNORM; case DDS.FOURCC_ATI1: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC4_UNORM; case DDS.FOURCC_ATI2: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC5_UNORM; case DDS.FOURCC_BC5U: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC4_UNORM; case DDS.FOURCC_DX10: IsDX10 = true; break; default: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM; } if (IsDX10) { switch (dds.DX10header.DXGI_Format) { case DDS.DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC4_UNORM; case DDS.DXGI_FORMAT.DXGI_FORMAT_BC4_SNORM: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC4_SNORM; case DDS.DXGI_FORMAT.DXGI_FORMAT_BC4_TYPELESS: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC4_UNORM; case DDS.DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC5_UNORM; case DDS.DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC5_SNORM; case DDS.DXGI_FORMAT.DXGI_FORMAT_BC5_TYPELESS: return GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC5_UNORM; default: throw new Exception($"Format {dds.DX10header.DXGI_Format} not supported!"); } } throw new Exception($"This shouldn't happen :("); } public void LoadDDS(string FileName, byte[] FileData = null) { TexName = Path.GetFileNameWithoutExtension(FileName); DDS dds = new DDS(); if (FileData != null) dds.Load(new FileReader(new MemoryStream(FileData))); else dds.Load(new FileReader(FileName)); MipCount = dds.header.mipmapCount; TexWidth = dds.header.width; TexHeight = dds.header.height; arrayLength = 1; if (dds.header.caps2 == (uint)DDS.DDSCAPS2.CUBEMAP_ALLFACES) { arrayLength = 6; } DataBlockOutput.Add(dds.bdata); Format = LoadDDSFormat(dds.header.ddspf.fourCC, dds, IsSRGB); GTX.GX2Surface tex = CreateGx2Texture(DataBlockOutput[0]); } public void LoadBitMap(string FileName) { DecompressedData.Clear(); TexName = Path.GetFileNameWithoutExtension(FileName); Format = GTX.GX2SurfaceFormat.GX2_SURFACE_FORMAT_T_BC1_SRGB; GenerateMipmaps = true; Bitmap Image = new Bitmap(FileName); Image = TextureData.SwapBlueRedChannels(Image); TexWidth = (uint)Image.Width; TexHeight = (uint)Image.Height; MipCount = (uint)GetTotalMipCount(); DecompressedData.Add(BitmapExtension.ImageToByte(Image)); Image.Dispose(); if (DecompressedData.Count == 0) { throw new Exception("Failed to load " + Format); } } public int GetTotalMipCount() { int MipmapNum = 0; uint num = Math.Max(TexHeight, TexWidth); int width = (int)TexWidth; int height = (int)TexHeight; while (true) { num >>= 1; width = width / 2; height = height / 2; if (width <= 0 || height <= 0) break; if (num > 0) ++MipmapNum; else break; } return MipmapNum; } public byte[] GenerateMips(int SurfaceLevel = 0) { Bitmap Image = BitmapExtension.GetBitmap(DecompressedData[SurfaceLevel], (int)TexWidth, (int)TexHeight); List mipmaps = new List(); mipmaps.Add(FTEX.CompressBlock(DecompressedData[SurfaceLevel], (int)TexWidth, (int)TexHeight, Format)); //while (Image.Width / 2 > 0 && Image.Height / 2 > 0) // for (int mipLevel = 0; mipLevel < MipCount; mipLevel++) for (int mipLevel = 0; mipLevel < MipCount; mipLevel++) { Image = BitmapExtension.Resize(Image, Image.Width / 2, Image.Height / 2); mipmaps.Add(FTEX.CompressBlock(BitmapExtension.ImageToByte(Image), Image.Width, Image.Height, Format)); } Image.Dispose(); return Utils.CombineByteArray(mipmaps.ToArray()); } public void Compress() { DataBlockOutput.Clear(); foreach (var surface in DecompressedData) { DataBlockOutput.Add(FTEX.CompressBlock(surface, (int)TexWidth, (int)TexHeight, Format)); } } public GTX.GX2Surface CreateGx2Texture(byte[] imageData) { var surfOut = GTX.getSurfaceInfo(Format, TexWidth, TexHeight, 1, 1, tileMode, 0, 0); uint imageSize = (uint)surfOut.surfSize; uint alignment = surfOut.baseAlign; uint pitch = surfOut.pitch; uint mipSize = 0; uint dataSize = (uint)imageData.Length; uint bpp = GTX.surfaceGetBitsPerPixel((uint)Format) >> 3; if (dataSize <= 0) throw new Exception($"Image is empty!!"); if (surfOut.depth != 1) throw new Exception($"Unsupported Depth {surfOut.depth}!"); uint s = 0; switch (tileMode) { case 1: case 2: case 3: case 16: s = 0; break; default: s = 0xd0000 | swizzle << 8; break; } uint blkWidth, blkHeight; if (GTX.IsFormatBCN(Format)) { blkWidth = 4; blkHeight = 4; } else { blkWidth = 1; blkHeight = 1; } List mipOffsets = new List(); List Swizzled = new List(); for (int mipLevel = 0; mipLevel < MipCount; mipLevel++) { var result = TextureHelper.GetCurrentMipSize(TexWidth, TexHeight, blkWidth, blkHeight, bpp, mipLevel); uint offset = result.Item1; uint size = result.Item2; Console.WriteLine("Swizzle Size " + size); Console.WriteLine("Swizzle offset " + offset); Console.WriteLine("bpp " + bpp); Console.WriteLine("TexWidth " + TexWidth); Console.WriteLine("TexHeight " + TexHeight); Console.WriteLine("blkWidth " + blkWidth); Console.WriteLine("blkHeight " + blkHeight); Console.WriteLine("mipLevel " + mipLevel); byte[] data_ = new byte[size]; Array.Copy(imageData, offset, data_,0, size); uint width_ = Math.Max(1, TexWidth >> mipLevel); uint height_ = Math.Max(1, TexHeight >> mipLevel); if (mipLevel != 0) { surfOut = GTX.getSurfaceInfo(Format, TexWidth, TexHeight, 1, 1, tileMode, 0, mipLevel); if (mipLevel == 1) mipOffsets.Add(imageSize); else mipOffsets.Add(mipSize); } data_ = Utils.CombineByteArray(data_, new byte[surfOut.surfSize - size]); byte[] dataAlignBytes = new byte[RoundUp(mipSize, surfOut.baseAlign) - mipSize]; if (mipLevel != 0) mipSize += (uint)(surfOut.surfSize + dataAlignBytes.Length); byte[] SwizzledData = GTX.swizzle(width_, height_, surfOut.height, (uint)Format, surfOut.tileMode, s, surfOut.pitch, surfOut.bpp, data_); Swizzled.Add(dataAlignBytes.Concat(SwizzledData).ToArray()); } compSel[0] = GX2CompSel.ChannelR; compSel[1] = GX2CompSel.ChannelG; compSel[2] = GX2CompSel.ChannelB; compSel[3] = GX2CompSel.ChannelA; GTX.GX2Surface surf = new GTX.GX2Surface(); surf.depth = Depth; surf.width = TexWidth; surf.height = TexHeight; surf.depth = 1; surf.use = 1; surf.dim = (uint)SurfaceDim; surf.tileMode = tileMode; surf.swizzle = s; surf.resourceFlags = 0; surf.pitch = pitch; surf.bpp = bpp; surf.format = (uint)Format; surf.numMips = MipCount; surf.aa = (uint)AAMode; surf.mipOffset = mipOffsets.ToArray(); surf.numMips = (uint)Swizzled.Count; surf.alignment = alignment; surf.mipSize = mipSize; surf.imageSize = imageSize; surf.data = Swizzled[0]; List mips = new List(); for (int mipLevel = 1; mipLevel < Swizzled.Count; mipLevel++) { mips.Add(Swizzled[mipLevel]); Console.WriteLine(Swizzled[mipLevel].Length); } surf.mipData = Utils.CombineByteArray(mips.ToArray()); mips.Clear(); Console.WriteLine(""); Console.WriteLine("// ----- GX2Surface Info ----- "); Console.WriteLine(" dim = 1"); Console.WriteLine(" width = " + surf.width); Console.WriteLine(" height = " + surf.height); Console.WriteLine(" depth = 1"); Console.WriteLine(" numMips = " + surf.numMips); Console.WriteLine(" format = " + surf.format); Console.WriteLine(" aa = 0"); Console.WriteLine(" use = 1"); Console.WriteLine(" imageSize = " + surf.imageSize); Console.WriteLine(" mipSize = " + surf.mipSize); Console.WriteLine(" tileMode = " + surf.tileMode); Console.WriteLine(" swizzle = " + surf.swizzle); Console.WriteLine(" alignment = " + surf.alignment); Console.WriteLine(" pitch = " + surf.pitch); Console.WriteLine(""); Console.WriteLine(" GX2 Component Selector:"); Console.WriteLine(" Red Channel: " + compSel[0]); Console.WriteLine(" Green Channel: " + compSel[1]); Console.WriteLine(" Blue Channel: " + compSel[2]); Console.WriteLine(" Alpha Channel: " + compSel[3]); Console.WriteLine(""); Console.WriteLine(" bits per pixel = " + (surf.bpp << 3)); Console.WriteLine(" bytes per pixel = " + surf.bpp); Console.WriteLine(" realSize = " + imageData.Length); return surf; } private static Tuple GetCurrentMipSize(uint width, uint height, uint bpp, int CurLevel, bool IsCompressed) { uint offset = 0; uint size = 0; for (int mipLevel = 0; mipLevel < CurLevel; mipLevel++) { int level = mipLevel + 1; if (IsCompressed) offset += ((Math.Max(1, width >> level) + 3) >> 2) * ((Math.Max(1, height >> level) + 3) >> 2) * bpp; else offset += Math.Max(1, width >> level) * Math.Max(1, height >> level) * bpp; } if (IsCompressed) size = ((Math.Max(1, width >> CurLevel) + 3) >> 2) * ((Math.Max(1, height >> CurLevel) + 3) >> 2) * bpp; else size = Math.Max(1, width >> CurLevel) * Math.Max(1, height >> CurLevel) * bpp; return Tuple.Create(offset, size); } private 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 int RoundUp(int X, int Y) { return ((X - 1) | (Y - 1)) + 1; } private uint RoundUp(uint X, uint Y) { return ((X - 1) | (Y - 1)) + 1; } } }