/// <summary> /// Gets the description from the enum value, in this case the Texture Code /// </summary> /// <param name="value">The enum value</param> /// <returns>The Texture Code</returns> public static string GetTexDisplayName(this XivTexFormat value) { var field = value.GetType().GetField(value.ToString()); var attribute = (XivTexFormatDescriptionAttribute[])field.GetCustomAttributes(typeof(XivTexFormatDescriptionAttribute), false); return(attribute.Length > 0 ? attribute[0].DisplayName : value.ToString()); }
/// <summary> /// Creates the header for the texture info from the data to be imported. /// </summary> /// <param name="xivTex">Data for the currently displayed texture.</param> /// <param name="newWidth">The width of the DDS texture to be imported.</param> /// <param name="newHeight">The height of the DDS texture to be imported.</param> /// <param name="newMipCount">The number of mipmaps the DDS texture to be imported contains.</param> /// <returns>The created header data.</returns> private static List <byte> MakeTextureInfoHeader(XivTexFormat format, int newWidth, int newHeight, int newMipCount) { var headerData = new List <byte>(); headerData.AddRange(BitConverter.GetBytes((short)0)); headerData.AddRange(BitConverter.GetBytes((short)128)); headerData.AddRange(BitConverter.GetBytes(short.Parse(format.GetTexFormatCode()))); headerData.AddRange(BitConverter.GetBytes((short)0)); headerData.AddRange(BitConverter.GetBytes((short)newWidth)); headerData.AddRange(BitConverter.GetBytes((short)newHeight)); headerData.AddRange(BitConverter.GetBytes((short)1)); headerData.AddRange(BitConverter.GetBytes((short)newMipCount)); headerData.AddRange(BitConverter.GetBytes(0)); headerData.AddRange(BitConverter.GetBytes(1)); headerData.AddRange(BitConverter.GetBytes(2)); int mipLength; switch (format) { case XivTexFormat.DXT1: mipLength = (newWidth * newHeight) / 2; break; case XivTexFormat.DXT5: case XivTexFormat.A8: mipLength = newWidth * newHeight; break; case XivTexFormat.A1R5G5B5: case XivTexFormat.A4R4G4B4: mipLength = (newWidth * newHeight) * 2; break; case XivTexFormat.L8: case XivTexFormat.A8R8G8B8: case XivTexFormat.X8R8G8B8: case XivTexFormat.R32F: case XivTexFormat.G16R16F: case XivTexFormat.G32R32F: case XivTexFormat.A16B16G16R16F: case XivTexFormat.A32B32G32R32F: case XivTexFormat.DXT3: case XivTexFormat.D16: default: mipLength = (newWidth * newHeight) * 4; break; } var combinedLength = 80; for (var i = 0; i < newMipCount; i++) { headerData.AddRange(BitConverter.GetBytes(combinedLength)); combinedLength = combinedLength + mipLength; if (mipLength > 16) { mipLength = mipLength / 4; } else { mipLength = 16; } } var padding = 80 - headerData.Count; headerData.AddRange(new byte[padding]); return(headerData); }
/// <summary> /// Creates texture data ready to be imported into the DATs from an external file. /// If format is not specified, either the incoming file's DDS format is used (DDS files), /// or the existing internal file's DDS format is used. /// </summary> /// <param name="internalPath"></param> /// <param name="externalPath"></param> /// <param name="texFormat"></param> /// <returns></returns> public async Task <byte[]> MakeTexData(string internalPath, string externalPath, XivTexFormat texFormat = XivTexFormat.INVALID) { // Ensure file exists. if (!File.Exists(externalPath)) { throw new IOException($"Could not find file: {externalPath}"); } var root = await XivCache.GetFirstRoot(internalPath); bool isDds = Path.GetExtension(externalPath).ToLower() == ".dds"; var ddsContainer = new DDSContainer(); try { // If no format was specified... if (texFormat == XivTexFormat.INVALID) { if (isDds) { // If we're importing a DDS file, get the format from the incoming DDS file using (var fs = new FileStream(externalPath, FileMode.Open)) { using (var sr = new BinaryReader(fs)) { texFormat = GetDDSTexFormat(sr); } } } else { // Otherwise use the current internal format. var xivt = await _dat.GetType4Data(internalPath, false); texFormat = xivt.TextureFormat; } } // Check if the texture being imported has been imported before CompressionFormat compressionFormat = CompressionFormat.BGRA; switch (texFormat) { case XivTexFormat.DXT1: compressionFormat = CompressionFormat.BC1a; break; case XivTexFormat.DXT5: compressionFormat = CompressionFormat.BC3; break; case XivTexFormat.A8R8G8B8: compressionFormat = CompressionFormat.BGRA; break; default: if (!isDds) { throw new Exception($"Format {texFormat} is not currently supported for BMP import\n\nPlease use the DDS import option instead."); } break; } if (!isDds) { using (var surface = Surface.LoadFromFile(externalPath)) { if (surface == null) { throw new FormatException($"Unsupported texture format"); } surface.FlipVertically(); var maxMipCount = 1; if (root != null) { // For things that have real roots (things that have actual models/aren't UI textures), we always want mipMaps, even if the existing texture only has one. // (Ex. The Default Mat-Add textures) maxMipCount = -1; } using (var compressor = new Compressor()) { // UI/Paintings only have a single mipmap and will crash if more are generated, for everything else generate max levels compressor.Input.SetMipmapGeneration(true, maxMipCount); compressor.Input.SetData(surface); compressor.Compression.Format = compressionFormat; compressor.Compression.SetBGRAPixelFormat(); compressor.Process(out ddsContainer); } } } // If we're not a DDS, write the DDS to file temporarily. var ddsFilePath = externalPath; if (!isDds) { var tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".dds"); ddsContainer.Write(tempFile, DDSFlags.None); ddsFilePath = tempFile; } using (var br = new BinaryReader(File.OpenRead(ddsFilePath))) { br.BaseStream.Seek(12, SeekOrigin.Begin); var newHeight = br.ReadInt32(); var newWidth = br.ReadInt32(); br.ReadBytes(8); var newMipCount = br.ReadInt32(); if (newHeight % 2 != 0 || newWidth % 2 != 0) { throw new Exception("Resolution must be a multiple of 2"); } br.BaseStream.Seek(80, SeekOrigin.Begin); var textureFlags = br.ReadInt32(); var texType = br.ReadInt32(); var uncompressedLength = (int)new FileInfo(ddsFilePath).Length - 128; var newTex = new List <byte>(); if (!internalPath.Contains(".atex")) { var DDSInfo = await DDS.ReadDDS(br, texFormat, newWidth, newHeight, newMipCount); newTex.AddRange(_dat.MakeType4DatHeader(texFormat, DDSInfo.mipPartOffsets, DDSInfo.mipPartCounts, (int)uncompressedLength, newMipCount, newWidth, newHeight)); newTex.AddRange(MakeTextureInfoHeader(texFormat, newWidth, newHeight, newMipCount)); newTex.AddRange(DDSInfo.compressedDDS); return(newTex.ToArray()); } else { br.BaseStream.Seek(128, SeekOrigin.Begin); newTex.AddRange(MakeTextureInfoHeader(texFormat, newWidth, newHeight, newMipCount)); newTex.AddRange(br.ReadBytes((int)uncompressedLength)); var data = await _dat.CreateType2Data(newTex.ToArray()); return(data); } } } finally { ddsContainer.Dispose(); } }
/// <summary> /// Reads and parses data from the DDS file to be imported. /// </summary> /// <param name="br">The currently active BinaryReader.</param> /// <param name="xivTex">The Texture data.</param> /// <param name="newWidth">The width of the DDS texture to be imported.</param> /// <param name="newHeight">The height of the DDS texture to be imported.</param> /// <param name="newMipCount">The number of mipmaps the DDS texture to be imported contains.</param> /// <returns>A tuple containing the compressed DDS data, a list of offsets to the mipmap parts, a list with the number of parts per mipmap.</returns> public static async Task <(List <byte> compressedDDS, List <short> mipPartOffsets, List <short> mipPartCounts)> ReadDDS(BinaryReader br, XivTexFormat format, int newWidth, int newHeight, int newMipCount) { var compressedDDS = new List <byte>(); var mipPartOffsets = new List <short>(); var mipPartCount = new List <short>(); int mipLength; switch (format) { case XivTexFormat.DXT1: mipLength = (newWidth * newHeight) / 2; break; case XivTexFormat.DXT5: case XivTexFormat.A8: mipLength = newWidth * newHeight; break; case XivTexFormat.A1R5G5B5: case XivTexFormat.A4R4G4B4: mipLength = (newWidth * newHeight) * 2; break; case XivTexFormat.L8: case XivTexFormat.A8R8G8B8: case XivTexFormat.X8R8G8B8: case XivTexFormat.R32F: case XivTexFormat.G16R16F: case XivTexFormat.G32R32F: case XivTexFormat.A16B16G16R16F: case XivTexFormat.A32B32G32R32F: case XivTexFormat.DXT3: case XivTexFormat.D16: default: mipLength = (newWidth * newHeight) * 4; break; } br.BaseStream.Seek(128, SeekOrigin.Begin); for (var i = 0; i < newMipCount; i++) { var mipParts = (int)Math.Ceiling(mipLength / 16000f); mipPartCount.Add((short)mipParts); if (mipParts > 1) { for (var j = 0; j < mipParts; j++) { int uncompLength; var comp = true; if (j == mipParts - 1) { uncompLength = mipLength % 16000; } else { uncompLength = 16000; } var uncompBytes = br.ReadBytes(uncompLength); var compressed = await IOUtil.Compressor(uncompBytes); if (compressed.Length > uncompLength) { compressed = uncompBytes; comp = false; } compressedDDS.AddRange(BitConverter.GetBytes(16)); compressedDDS.AddRange(BitConverter.GetBytes(0)); compressedDDS.AddRange(!comp ? BitConverter.GetBytes(32000) : BitConverter.GetBytes(compressed.Length)); compressedDDS.AddRange(BitConverter.GetBytes(uncompLength)); compressedDDS.AddRange(compressed); var padding = 128 - (compressed.Length % 128); compressedDDS.AddRange(new byte[padding]); mipPartOffsets.Add((short)(compressed.Length + padding + 16)); } } else { int uncompLength; var comp = true; if (mipLength != 16000) { uncompLength = mipLength % 16000; } else { uncompLength = 16000; } var uncompBytes = br.ReadBytes(uncompLength); var compressed = await IOUtil.Compressor(uncompBytes); if (compressed.Length > uncompLength) { compressed = uncompBytes; comp = false; } compressedDDS.AddRange(BitConverter.GetBytes(16)); compressedDDS.AddRange(BitConverter.GetBytes(0)); compressedDDS.AddRange(!comp ? BitConverter.GetBytes(32000) : BitConverter.GetBytes(compressed.Length)); compressedDDS.AddRange(BitConverter.GetBytes(uncompLength)); compressedDDS.AddRange(compressed); var padding = 128 - (compressed.Length % 128); compressedDDS.AddRange(new byte[padding]); mipPartOffsets.Add((short)(compressed.Length + padding + 16)); } if (mipLength > 32) { mipLength = mipLength / 4; } else { mipLength = 8; } } return(compressedDDS, mipPartOffsets, mipPartCount); }