Exemple #1
0
        /// <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());
        }
Exemple #2
0
        /// <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);
        }
Exemple #3
0
        /// <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();
            }
        }
Exemple #4
0
        /// <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);
        }