<commit_before>﻿using System;
using System.IO;
using System.Text;
using OpenSage.Data.Utilities.Extensions;
using OpenSage.Utilities;
using Veldrid;

namespace OpenSage.Data.Dds
{
    public sealed class DdsFile
    {
        public DdsHeader Header { get; private set; }
        public DdsTextureDimension Dimension { get; private set; }
        public DdsImageFormat ImageFormat { get; private set; }
        public uint ArraySize { get; private set; }
        public uint MipMapCount { get; private set; }
        public TextureMipMapData[] MipMaps { get; private set; }

        public PixelFormat PixelFormat
        {
            get
            {
                switch (ImageFormat)
                {
                    // TODO_VELDRID: Should this be Rgba or Rgb?
                    case DdsImageFormat.Bc1:
                        return PixelFormat.BC1_Rgba_UNorm;

                    case DdsImageFormat.Bc2:
                        return PixelFormat.BC2_UNorm;

                    case DdsImageFormat.Bc3:
                        return PixelFormat.BC3_UNorm;

                    case DdsImageFormat.Rg8SNorm:
                        return PixelFormat.R8_G8_SNorm;

                    case DdsImageFormat.Rgba8:
                        return PixelFormat.R8_G8_B8_A8_UNorm;

                    default:
                        throw new NotSupportedException();
                }
            }
        }

        public static bool IsDdsFile(FileSystemEntry entry)
        {
            using (var stream = entry.Open())
            using (var reader = new BinaryReader(stream, Encoding.ASCII, true))
            {
                var magic = reader.ReadFourCc();
                return magic == "DDS ";
            }
        }

        public static DdsFile FromFileSystemEntry(FileSystemEntry entry)
        {
            using (var stream = entry.Open())
            {
                return FromStream(stream);
            }
        }

        public static DdsFile FromStream(Stream stream)
        {
            using (var reader = new BinaryReader(stream, Encoding.ASCII, true))
            {
                var magic = reader.ReadFourCc();
                if (magic != "DDS ")
                {
                    throw new InvalidDataException();
                }

                var header = DdsHeader.Parse(reader);

                DdsImageFormat imageFormat;
                if (header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.FourCc))
                {
                    imageFormat = GetImageFormat(header.PixelFormat.FourCc);
                }
                else if (header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.BumpDuDv))
                {
                    imageFormat = DdsImageFormat.Rg8SNorm;
                }
                else if (header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.Rgb))
                {
                    imageFormat = DdsImageFormat.Rgba8;
                }
                else
                {
                    throw new InvalidDataException();
                }

                var dimension = DdsTextureDimension.Texture2D;
                var arraySize = 1u;
                if (header.Flags.HasFlag(DdsHeaderFlags.Depth) && header.Caps2.HasFlag(DdsCaps2.Volume))
                {
                    dimension = DdsTextureDimension.Texture3D;
                }
                else if (header.Caps2.HasFlag(DdsCaps2.CubeMap))
                {
                    dimension = DdsTextureDimension.TextureCube;
                    if (!header.Caps2.HasFlag(DdsCaps2.AllCubeMapFaces))
                    {
                        throw new InvalidDataException();
                    }
                    arraySize = 6;
                }

                var isCompressed = imageFormat == DdsImageFormat.Bc1
                    || imageFormat == DdsImageFormat.Bc2
                    || imageFormat == DdsImageFormat.Bc3;

                var mipMapCount = header.MipMapCount;
                if (mipMapCount == 0)
                {
                    mipMapCount = 1;
                }

                var mipMaps = new TextureMipMapData[mipMapCount * arraySize];

                for (var arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
                {
                    var width = header.Width;
                    var height = header.Height;
                    var depth = Math.Max(header.Depth, 1);
                    for (var i = 0; i < mipMapCount; i++)
                    {
                        var surfaceInfo = GetSurfaceInfo(width, height, imageFormat, header);

                        var numSurfaceBytes = surfaceInfo.NumBytes * depth;
                        var mipMapData = reader.ReadBytes((int) numSurfaceBytes);

                        // Set alpha bytes for 32-bit rgb images that don't include alpha data.
                        if (imageFormat == DdsImageFormat.Rgba8 && !header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels))
                        {
                            for (var j = 0; j < mipMapData.Length; j += 4)
                            {
                                mipMapData[j + 3] = 255;
                            }
                        }

                        mipMaps[(arrayIndex * mipMapCount) + i] = new TextureMipMapData(
                            mipMapData,
                            surfaceInfo.RowBytes,
                            surfaceInfo.NumBytes,
                            width,
                            height);

                        width >>= 1;
                        height >>= 1;
                        depth >>= 1;

                        if (isCompressed)
                        {
                            // Align width and height to multiple of 4.
                            width = (width + 3) / 4 * 4;
                            height = (height + 3) / 4 * 4;
                        }
                        else
                        {
                            width = Math.Max(width, 1);
                            height = Math.Max(height, 1);
                        }

                        depth = Math.Max(depth, 1);
                    }
                }

                return new DdsFile
                {
                    Header = header,
                    Dimension = dimension,
                    ImageFormat = imageFormat,
                    ArraySize = arraySize,
                    MipMapCount = mipMapCount,
                    MipMaps = mipMaps<commit_msg>Fix crash opening compressed DDS with non-multiple-of-four size<commit_after>﻿using System;
using System.IO;
using System.Text;
using OpenSage.Data.Utilities.Extensions;
using OpenSage.Utilities;
using Veldrid;

namespace OpenSage.Data.Dds
{
    public sealed class DdsFile
    {
        public DdsHeader Header { get; private set; }
        public DdsTextureDimension Dimension { get; private set; }
        public DdsImageFormat ImageFormat { get; private set; }
        public uint ArraySize { get; private set; }
        public uint MipMapCount { get; private set; }
        public TextureMipMapData[] MipMaps { get; private set; }

        public PixelFormat PixelFormat
        {
            get
            {
                switch (ImageFormat)
                {
                    // TODO_VELDRID: Should this be Rgba or Rgb?
                    case DdsImageFormat.Bc1:
                        return PixelFormat.BC1_Rgba_UNorm;

                    case DdsImageFormat.Bc2:
                        return PixelFormat.BC2_UNorm;

                    case DdsImageFormat.Bc3:
                        return PixelFormat.BC3_UNorm;

                    case DdsImageFormat.Rg8SNorm:
                        return PixelFormat.R8_G8_SNorm;

                    case DdsImageFormat.Rgba8:
                        return PixelFormat.R8_G8_B8_A8_UNorm;

                    default:
                        throw new NotSupportedException();
                }
            }
        }

        public static bool IsDdsFile(FileSystemEntry entry)
        {
            using (var stream = entry.Open())
            using (var reader = new BinaryReader(stream, Encoding.ASCII, true))
            {
                var magic = reader.ReadFourCc();
                return magic == "DDS ";
            }
        }

        public static DdsFile FromFileSystemEntry(FileSystemEntry entry)
        {
            using (var stream = entry.Open())
            {
                return FromStream(stream);
            }
        }

        public static DdsFile FromStream(Stream stream)
        {
            using (var reader = new BinaryReader(stream, Encoding.ASCII, true))
            {
                var magic = reader.ReadFourCc();
                if (magic != "DDS ")
                {
                    throw new InvalidDataException();
                }

                var header = DdsHeader.Parse(reader);

                DdsImageFormat imageFormat;
                if (header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.FourCc))
                {
                    imageFormat = GetImageFormat(header.PixelFormat.FourCc);
                }
                else if (header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.BumpDuDv))
                {
                    imageFormat = DdsImageFormat.Rg8SNorm;
                }
                else if (header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.Rgb))
                {
                    imageFormat = DdsImageFormat.Rgba8;
                }
                else
                {
                    throw new InvalidDataException();
                }

                var dimension = DdsTextureDimension.Texture2D;
                var arraySize = 1u;
                if (header.Flags.HasFlag(DdsHeaderFlags.Depth) && header.Caps2.HasFlag(DdsCaps2.Volume))
                {
                    dimension = DdsTextureDimension.Texture3D;
                }
                else if (header.Caps2.HasFlag(DdsCaps2.CubeMap))
                {
                    dimension = DdsTextureDimension.TextureCube;
                    if (!header.Caps2.HasFlag(DdsCaps2.AllCubeMapFaces))
                    {
                        throw new InvalidDataException();
                    }
                    arraySize = 6;
                }

                var isCompressed = imageFormat == DdsImageFormat.Bc1
                    || imageFormat == DdsImageFormat.Bc2
                    || imageFormat == DdsImageFormat.Bc3;

                var mipMapCount = header.MipMapCount;
                if (mipMapCount == 0)
                {
                    mipMapCount = 1;
                }

                var mipMaps = new TextureMipMapData[mipMapCount * arraySize];

                for (var arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
                {
                    var width = header.Width;
                    var height = header.Height;

                    if (isCompressed)
                    {
                        // Ensure width and height are multiple of 4.
                        width = (width + 3) / 4 * 4;
                        height = (height + 3) / 4 * 4;
                    }

                    var depth = Math.Max(header.Depth, 1);

                    for (var i = 0; i < mipMapCount; i++)
                    {
                        var surfaceInfo = GetSurfaceInfo(width, height, imageFormat, header);

                        var numSurfaceBytes = surfaceInfo.NumBytes * depth;
                        var mipMapData = reader.ReadBytes((int) numSurfaceBytes);
                        if (mipMapData.Length != numSurfaceBytes)
                        {
                            throw new InvalidDataException();
                        }

                        // Set alpha bytes for 32-bit rgb images that don't include alpha data.
                        if (imageFormat == DdsImageFormat.Rgba8 && !header.PixelFormat.Flags.HasFlag(DdsPixelFormatFlags.AlphaPixels))
                        {
                            for (var j = 0; j < mipMapData.Length; j += 4)
                            {
                                mipMapData[j + 3] = 255;
                            }
                        }

                        mipMaps[(arrayIndex * mipMapCount) + i] = new TextureMipMapData(
                            mipMapData,
                            surfaceInfo.RowBytes,
                            surfaceInfo.NumBytes,
                            width,
                            height);

                        width >>= 1;
                        height >>= 1;
                        depth >>= 1;

                        if (isCompressed)
                        {
                            // Round width and height down to multiple of 4.
                            width -= width % 4;
                            height -= height % 4;

                            width = Math.Max(width, 4);
                            height = Math.Max(height, 4);
                        }
                        else
                        {
                            width = Math.Max(width, 1);
                            height = Math.Max(height, 1);
                        }

                        depth = Math.Max(depth, 1);
                    }
                }

                return new DdsFile
                {
                    Header = header,
                    Dimension = dimension,
                    ImageFormat = imageFormat,
                    ArraySize = arraySize,
                    MipMapCount = mipMapCount,
                    MipMaps = mipMaps
{'fraction_non_alphanumeric': 0.07005484460694698, 'fraction_numerical': 0.008555758683729433, 'mean_word_length': 1.0153256704980842, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3296246', 'n_tokens_mistral': 3538, 'n_tokens_neox': 3195, 'n_words': 711}