public static void DDSToTexture(GameDatabase.TextureInfo texture, bool inPlace, Vector2 size, string cache = null, bool mipmaps = false) { /** * Kopernicus Planetary System Modifier * ==================================== * Created by: BryceSchroeder and Teknoman117 (aka. Nathaniel R. Lewis) * Maintained by: Thomas P., NathanKell and KillAshley * Additional Content by: Gravitasi, aftokino, KCreator, Padishar, Kragrathea, OvenProofMars, zengei, MrHappyFace * ------------------------------------------------------------- * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * This library is intended to be used as a plugin for Kerbal Space Program * which is copyright 2011-2015 Squad. Your usage of Kerbal Space Program * itself is governed by the terms of its EULA, not the license above. * * https://kerbalspaceprogram.com */ // Borrowed from stock KSP 1.0 DDS loader (hi Mike!) // Also borrowed the extra bits from Sarbian. byte[] buffer = System.IO.File.ReadAllBytes(texture.file.fullPath); System.IO.BinaryReader binaryReader = new System.IO.BinaryReader(new System.IO.MemoryStream(buffer)); uint num = binaryReader.ReadUInt32(); if (num == DDSHeaders.DDSValues.uintMagic) { DDSHeaders.DDSHeader dDSHeader = new DDSHeaders.DDSHeader(binaryReader); if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { new DDSHeaders.DDSHeaderDX10(binaryReader); } bool alpha = (dDSHeader.dwFlags & 0x00000002) != 0; bool fourcc = (dDSHeader.dwFlags & 0x00000004) != 0; bool rgb = (dDSHeader.dwFlags & 0x00000040) != 0; bool alphapixel = (dDSHeader.dwFlags & 0x00000001) != 0; bool luminance = (dDSHeader.dwFlags & 0x00020000) != 0; bool rgb888 = dDSHeader.ddspf.dwRBitMask == 0x000000ff && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x00ff0000; //bool bgr888 = dDSHeader.ddspf.dwRBitMask == 0x00ff0000 && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x000000ff; bool rgb565 = dDSHeader.ddspf.dwRBitMask == 0x0000F800 && dDSHeader.ddspf.dwGBitMask == 0x000007E0 && dDSHeader.ddspf.dwBBitMask == 0x0000001F; bool argb4444 = dDSHeader.ddspf.dwABitMask == 0x0000f000 && dDSHeader.ddspf.dwRBitMask == 0x00000f00 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x0000000f; bool rbga4444 = dDSHeader.ddspf.dwABitMask == 0x0000000f && dDSHeader.ddspf.dwRBitMask == 0x0000f000 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x00000f00; bool mipmap = (dDSHeader.dwCaps & DDSHeaders.DDSPixelFormatCaps.MIPMAP) != (DDSHeaders.DDSPixelFormatCaps) 0u; bool isNormalMap = ((dDSHeader.ddspf.dwFlags & 524288u) != 0u || (dDSHeader.ddspf.dwFlags & 2147483648u) != 0u); Vector2 newSize = size == default(Vector2) ? new Vector2(dDSHeader.dwWidth, dDSHeader.dwHeight) : size; if (fourcc) { if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT1) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT1, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force DXT1 byte[] file = ImageConversion.EncodeToPNG(tmpTex);//tmpTex.EncodeToJPG(); if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameObject.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.RGB24, mipmap); texture.texture.Compress(false); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameObject.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT1, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT3) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, (TextureFormat)11, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force DXT5 byte[] file = ImageConversion.EncodeToPNG(tmpTex); if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameObject.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, (TextureFormat)11, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameObject.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, (TextureFormat)11, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT5) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT5, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force DXT5 byte[] file = ImageConversion.EncodeToPNG(tmpTex); if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameObject.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.ARGB32, mipmap); texture.texture.Compress(false); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameObject.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT5, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT2) { Debug.Log("DXT2 not supported"); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT4) { Debug.Log("DXT4 not supported: "); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { Debug.Log("DX10 dds not supported: "); } else { fourcc = false; } } if (!fourcc) { TextureFormat textureFormat = TextureFormat.ARGB32; bool ok = true; if (rgb && (rgb888 /*|| bgr888*/)) { // RGB or RGBA format textureFormat = alphapixel ? TextureFormat.RGBA32 : TextureFormat.RGB24; } else if (rgb && rgb565) { // Nvidia texconv B5G6R5_UNORM textureFormat = TextureFormat.RGB565; } else if (rgb && alphapixel && argb4444) { // Nvidia texconv B4G4R4A4_UNORM textureFormat = TextureFormat.ARGB4444; } else if (rgb && alphapixel && rbga4444) { textureFormat = TextureFormat.RGBA4444; } else if (!rgb && alpha != luminance) { // A8 format or Luminance 8 textureFormat = TextureFormat.Alpha8; } else { ok = false; Debug.Log("Only DXT1, DXT5, A8, RGB24, RGBA32, RGB565, ARGB4444 and RGBA4444 are supported"); } if (ok) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, textureFormat, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force alpha-less byte[] file; if (alphapixel) { file = ImageConversion.EncodeToPNG(tmpTex); } else { file = ImageConversion.EncodeToPNG(tmpTex);//file = tmpTex.EncodeToJPG(); } if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameDatabase.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, textureFormat, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameDatabase.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, textureFormat, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } } } else { Debug.Log("Bad DDS header."); } }
// Loads a texture public static Texture2D LoadTexture(String path, Boolean compress, Boolean upload, Boolean unreadable) { Texture2D map = null; path = KSPUtil.ApplicationRootPath + "GameData/" + path; if (File.Exists(path)) { Boolean uncaught = true; try { if (path.ToLower().EndsWith(".dds")) { // Borrowed from stock KSP 1.0 DDS loader (hi Mike!) // Also borrowed the extra bits from Sarbian. BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)); uint num = binaryReader.ReadUInt32(); if (num == DDSHeaders.DDSValues.uintMagic) { DDSHeaders.DDSHeader dDSHeader = new DDSHeaders.DDSHeader(binaryReader); if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { new DDSHeaders.DDSHeaderDX10(binaryReader); } Boolean alpha = (dDSHeader.dwFlags & 0x00000002) != 0; Boolean fourcc = (dDSHeader.dwFlags & 0x00000004) != 0; Boolean rgb = (dDSHeader.dwFlags & 0x00000040) != 0; Boolean alphapixel = (dDSHeader.dwFlags & 0x00000001) != 0; Boolean luminance = (dDSHeader.dwFlags & 0x00020000) != 0; Boolean rgb888 = dDSHeader.ddspf.dwRBitMask == 0x000000ff && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x00ff0000; //Boolean bgr888 = dDSHeader.ddspf.dwRBitMask == 0x00ff0000 && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x000000ff; Boolean rgb565 = dDSHeader.ddspf.dwRBitMask == 0x0000F800 && dDSHeader.ddspf.dwGBitMask == 0x000007E0 && dDSHeader.ddspf.dwBBitMask == 0x0000001F; Boolean argb4444 = dDSHeader.ddspf.dwABitMask == 0x0000f000 && dDSHeader.ddspf.dwRBitMask == 0x00000f00 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x0000000f; Boolean rbga4444 = dDSHeader.ddspf.dwABitMask == 0x0000000f && dDSHeader.ddspf.dwRBitMask == 0x0000f000 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x00000f00; Boolean mipmap = (dDSHeader.dwCaps & DDSHeaders.DDSPixelFormatCaps.MIPMAP) != (DDSHeaders.DDSPixelFormatCaps) 0u; Boolean isNormalMap = ((dDSHeader.ddspf.dwFlags & 524288u) != 0u || (dDSHeader.ddspf.dwFlags & 2147483648u) != 0u); if (fourcc) { if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT1) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, TextureFormat.DXT1, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT3) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, (TextureFormat)11, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT5) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, TextureFormat.DXT5, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT2) { Debug.Log("[Kopernicus]: DXT2 not supported" + path); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT4) { Debug.Log("[Kopernicus]: DXT4 not supported: " + path); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { Debug.Log("[Kopernicus]: DX10 dds not supported: " + path); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintMagic) { Debug.Log("[Kopernicus]: Magic dds not supported: " + path); } else if (dDSHeader.ddspf.dwRGBBitCount == 4 || dDSHeader.ddspf.dwRGBBitCount == 8) { try { int bpp = (int)dDSHeader.ddspf.dwRGBBitCount; int colors = (int)Math.Pow(2, bpp); int width = (int)dDSHeader.dwWidth; int height = (int)dDSHeader.dwHeight; long length = new FileInfo(path).Length; int pixels = width * height * bpp / 8 + 4 * colors; if (length - 128 >= pixels) { byte[] data = binaryReader.ReadBytes(pixels); Color[] palette = new Color[colors]; Color[] image = new Color[width * height]; for (int i = 0; i < 4 * colors; i = i + 4) { palette[i / 4] = new Color32(data[i + 0], data[i + 1], data[i + 2], data[i + 3]); } for (int i = 4 * colors; i < data.Length; i++) { image[(i - 4 * colors) * 8 / bpp] = palette[data[i] * colors / 256]; if (bpp == 4) { image[(i - 64) * 2 + 1] = palette[data[i] % 16]; } } map = new Texture2D(width, height, TextureFormat.ARGB32, false); map.SetPixels(image); } else { fourcc = false; } } catch { fourcc = false; } } else { fourcc = false; } } if (!fourcc) { TextureFormat textureFormat = TextureFormat.ARGB32; Boolean ok = true; if (rgb && (rgb888 /*|| bgr888*/)) { // RGB or RGBA format textureFormat = alphapixel ? TextureFormat.RGBA32 : TextureFormat.RGB24; } else if (rgb && rgb565) { // Nvidia texconv B5G6R5_UNORM textureFormat = TextureFormat.RGB565; } else if (rgb && alphapixel && argb4444) { // Nvidia texconv B4G4R4A4_UNORM textureFormat = TextureFormat.ARGB4444; } else if (rgb && alphapixel && rbga4444) { textureFormat = TextureFormat.RGBA4444; } else if (!rgb && alpha != luminance) { // A8 format or Luminance 8 textureFormat = TextureFormat.Alpha8; } else { ok = false; Debug.Log( "[Kopernicus]: Only DXT1, DXT5, A8, RGB24, RGBA32, RGB565, ARGB4444, RGBA4444, 4bpp palette and 8bpp palette are supported"); } if (ok) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, textureFormat, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } } } else { Debug.Log("[Kopernicus]: Bad DDS header."); } } else { map = new Texture2D(2, 2); byte[] data = LoadWholeFile(path); if (data == null) { throw new Exception("LoadWholeFile failed"); } map.LoadImage(data); } } catch (Exception ex) { uncaught = false; Debug.Log("[Kopernicus]: failed to load " + path + " with exception " + ex.Message); } if (map == null && uncaught) { Debug.Log("[Kopernicus]: failed to load " + path); } else { map.name = path.Remove(0, (KSPUtil.ApplicationRootPath + "GameData/").Length); if (compress) { map.Compress(true); } if (upload) { map.Apply(false, unreadable); } } } else { Debug.Log("[Kopernicus]: texture does not exist! " + path); } return(map); }
// Loads a texture public static Texture2D LoadTexture(string path, bool compress, bool upload, bool unreadable) { Texture2D map = null; path = Directory.GetCurrentDirectory() + "/GameData/" + path; if (File.Exists(path)) { bool uncaught = true; try { if (path.ToLower().EndsWith(".dds")) { // Borrowed from stock KSP 1.0 DDS loader (hi Mike!) // Also borrowed the extra bits from Sarbian. BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)); uint num = binaryReader.ReadUInt32(); if (num == DDSHeaders.DDSValues.uintMagic) { DDSHeaders.DDSHeader dDSHeader = new DDSHeaders.DDSHeader(binaryReader); if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { new DDSHeaders.DDSHeaderDX10(binaryReader); } bool alpha = (dDSHeader.dwFlags & 0x00000002) != 0; bool fourcc = (dDSHeader.dwFlags & 0x00000004) != 0; bool rgb = (dDSHeader.dwFlags & 0x00000040) != 0; bool alphapixel = (dDSHeader.dwFlags & 0x00000001) != 0; bool luminance = (dDSHeader.dwFlags & 0x00020000) != 0; bool rgb888 = dDSHeader.ddspf.dwRBitMask == 0x000000ff && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x00ff0000; //bool bgr888 = dDSHeader.ddspf.dwRBitMask == 0x00ff0000 && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x000000ff; bool rgb565 = dDSHeader.ddspf.dwRBitMask == 0x0000F800 && dDSHeader.ddspf.dwGBitMask == 0x000007E0 && dDSHeader.ddspf.dwBBitMask == 0x0000001F; bool argb4444 = dDSHeader.ddspf.dwABitMask == 0x0000f000 && dDSHeader.ddspf.dwRBitMask == 0x00000f00 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x0000000f; bool rbga4444 = dDSHeader.ddspf.dwABitMask == 0x0000000f && dDSHeader.ddspf.dwRBitMask == 0x0000f000 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x00000f00; bool mipmap = (dDSHeader.dwCaps & DDSHeaders.DDSPixelFormatCaps.MIPMAP) != (DDSHeaders.DDSPixelFormatCaps)0u; bool isNormalMap = ((dDSHeader.ddspf.dwFlags & 524288u) != 0u || (dDSHeader.ddspf.dwFlags & 2147483648u) != 0u); if (fourcc) { if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT1) { map = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT1, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT3) { map = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, (TextureFormat)11, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT5) { map = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT5, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT2) { Debug.Log("[Kopernicus]: DXT2 not supported" + path); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT4) { Debug.Log("[Kopernicus]: DXT4 not supported: " + path); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { Debug.Log("[Kopernicus]: DX10 dds not supported: " + path); } else fourcc = false; } if (!fourcc) { TextureFormat textureFormat = TextureFormat.ARGB32; bool ok = true; if (rgb && (rgb888 /*|| bgr888*/)) { // RGB or RGBA format textureFormat = alphapixel ? TextureFormat.RGBA32 : TextureFormat.RGB24; } else if (rgb && rgb565) { // Nvidia texconv B5G6R5_UNORM textureFormat = TextureFormat.RGB565; } else if (rgb && alphapixel && argb4444) { // Nvidia texconv B4G4R4A4_UNORM textureFormat = TextureFormat.ARGB4444; } else if (rgb && alphapixel && rbga4444) { textureFormat = TextureFormat.RGBA4444; } else if (!rgb && alpha != luminance) { // A8 format or Luminance 8 textureFormat = TextureFormat.Alpha8; } else { ok = false; Debug.Log("[Kopernicus]: Only DXT1, DXT5, A8, RGB24, RGBA32, RGB565, ARGB4444 and RGBA4444 are supported"); } if (ok) { map = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, textureFormat, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } } if (map != null) if (upload) map.Apply(false, unreadable); } else Debug.Log("[Kopernicus]: Bad DDS header."); } else { map = new Texture2D(2, 2); byte[] data = LoadWholeFile(path); if (data == null) throw new Exception("LoadWholeFile failed"); map.LoadImage(data); if (compress) map.Compress(true); if (upload) map.Apply(false, unreadable); } } catch (Exception ex) { uncaught = false; Debug.Log("[Kopernicus]: failed to load " + path + " with exception " + ex.Message); } if (map == null && uncaught) { Debug.Log("[Kopernicus]: failed to load " + path); } map.name = path.Remove(0, (KSPUtil.ApplicationRootPath + "GameData/").Length); } else Debug.Log("[Kopernicus]: texture does not exist! " + path); return map; }
// Loads a texture public static Texture2D LoadTexture(String path, Boolean compress, Boolean upload, Boolean unreadable) { Texture2D map = null; path = KSPUtil.ApplicationRootPath + "GameData/" + path; if (File.Exists(path)) { Boolean uncaught = true; try { if (path.ToLower().EndsWith(".dds")) { // Borrowed from stock KSP 1.0 DDS loader (hi Mike!) // Also borrowed the extra bits from Sarbian. BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)); uint num = binaryReader.ReadUInt32(); if (num == DDSHeaders.DDSValues.uintMagic) { DDSHeaders.DDSHeader dDSHeader = new DDSHeaders.DDSHeader(binaryReader); if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { new DDSHeaders.DDSHeaderDX10(binaryReader); } Boolean alpha = (dDSHeader.dwFlags & 0x00000002) != 0; Boolean fourcc = (dDSHeader.dwFlags & 0x00000004) != 0; Boolean rgb = (dDSHeader.dwFlags & 0x00000040) != 0; Boolean alphapixel = (dDSHeader.dwFlags & 0x00000001) != 0; Boolean luminance = (dDSHeader.dwFlags & 0x00020000) != 0; Boolean rgb888 = dDSHeader.ddspf.dwRBitMask == 0x000000ff && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x00ff0000; //Boolean bgr888 = dDSHeader.ddspf.dwRBitMask == 0x00ff0000 && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x000000ff; Boolean rgb565 = dDSHeader.ddspf.dwRBitMask == 0x0000F800 && dDSHeader.ddspf.dwGBitMask == 0x000007E0 && dDSHeader.ddspf.dwBBitMask == 0x0000001F; Boolean argb4444 = dDSHeader.ddspf.dwABitMask == 0x0000f000 && dDSHeader.ddspf.dwRBitMask == 0x00000f00 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x0000000f; Boolean rbga4444 = dDSHeader.ddspf.dwABitMask == 0x0000000f && dDSHeader.ddspf.dwRBitMask == 0x0000f000 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x00000f00; Boolean mipmap = (dDSHeader.dwCaps & DDSHeaders.DDSPixelFormatCaps.MIPMAP) != (DDSHeaders.DDSPixelFormatCaps) 0u; Boolean isNormalMap = ((dDSHeader.ddspf.dwFlags & 524288u) != 0u || (dDSHeader.ddspf.dwFlags & 2147483648u) != 0u); if (fourcc) { if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT1) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, TextureFormat.DXT1, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT3) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, (TextureFormat)11, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT5) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, TextureFormat.DXT5, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT2) { Debug.Log("[Kopernicus]: DXT2 not supported" + path); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT4) { Debug.Log("[Kopernicus]: DXT4 not supported: " + path); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { Debug.Log("[Kopernicus]: DX10 dds not supported: " + path); } else { fourcc = false; } } if (!fourcc) { TextureFormat textureFormat = TextureFormat.ARGB32; Boolean ok = true; if (rgb && (rgb888 /*|| bgr888*/)) { // RGB or RGBA format textureFormat = alphapixel ? TextureFormat.RGBA32 : TextureFormat.RGB24; } else if (rgb && rgb565) { // Nvidia texconv B5G6R5_UNORM textureFormat = TextureFormat.RGB565; } else if (rgb && alphapixel && argb4444) { // Nvidia texconv B4G4R4A4_UNORM textureFormat = TextureFormat.ARGB4444; } else if (rgb && alphapixel && rbga4444) { textureFormat = TextureFormat.RGBA4444; } else if (!rgb && alpha != luminance) { // A8 format or Luminance 8 textureFormat = TextureFormat.Alpha8; } else { ok = false; Debug.Log("[Kopernicus]: Only DXT1, DXT5, A8, RGB24, RGBA32, RGB565, ARGB4444 and RGBA4444 are supported"); } if (ok) { map = new Texture2D((Int32)dDSHeader.dwWidth, (Int32)dDSHeader.dwHeight, textureFormat, mipmap); map.LoadRawTextureData(LoadRestOfReader(binaryReader)); } } if (map != null) { if (upload) { map.Apply(false, unreadable); } } } else { Debug.Log("[Kopernicus]: Bad DDS header."); } } else { map = new Texture2D(2, 2); byte[] data = LoadWholeFile(path); if (data == null) { throw new Exception("LoadWholeFile failed"); } map.LoadImage(data); if (compress) { map.Compress(true); } if (upload) { map.Apply(false, unreadable); } } } catch (Exception ex) { uncaught = false; Debug.Log("[Kopernicus]: failed to load " + path + " with exception " + ex.Message); } if (map == null && uncaught) { Debug.Log("[Kopernicus]: failed to load " + path); } map.name = path.Remove(0, (KSPUtil.ApplicationRootPath + "GameData/").Length); } else { Debug.Log("[Kopernicus]: texture does not exist! " + path); } return(map); }
public static void DDSToTexture(GameDatabase.TextureInfo texture, bool inPlace, Vector2 size, string cache = null, bool mipmaps = false) { /** * Kopernicus Planetary System Modifier * ==================================== * Created by: BryceSchroeder and Teknoman117 (aka. Nathaniel R. Lewis) * Maintained by: Thomas P., NathanKell and KillAshley * Additional Content by: Gravitasi, aftokino, KCreator, Padishar, Kragrathea, OvenProofMars, zengei, MrHappyFace * ------------------------------------------------------------- * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * This library is intended to be used as a plugin for Kerbal Space Program * which is copyright 2011-2015 Squad. Your usage of Kerbal Space Program * itself is governed by the terms of its EULA, not the license above. * * https://kerbalspaceprogram.com */ // Borrowed from stock KSP 1.0 DDS loader (hi Mike!) // Also borrowed the extra bits from Sarbian. byte[] buffer = System.IO.File.ReadAllBytes(texture.file.fullPath); System.IO.BinaryReader binaryReader = new System.IO.BinaryReader(new System.IO.MemoryStream(buffer)); uint num = binaryReader.ReadUInt32(); if (num == DDSHeaders.DDSValues.uintMagic) { DDSHeaders.DDSHeader dDSHeader = new DDSHeaders.DDSHeader(binaryReader); if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { new DDSHeaders.DDSHeaderDX10(binaryReader); } bool alpha = (dDSHeader.dwFlags & 0x00000002) != 0; bool fourcc = (dDSHeader.dwFlags & 0x00000004) != 0; bool rgb = (dDSHeader.dwFlags & 0x00000040) != 0; bool alphapixel = (dDSHeader.dwFlags & 0x00000001) != 0; bool luminance = (dDSHeader.dwFlags & 0x00020000) != 0; bool rgb888 = dDSHeader.ddspf.dwRBitMask == 0x000000ff && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x00ff0000; //bool bgr888 = dDSHeader.ddspf.dwRBitMask == 0x00ff0000 && dDSHeader.ddspf.dwGBitMask == 0x0000ff00 && dDSHeader.ddspf.dwBBitMask == 0x000000ff; bool rgb565 = dDSHeader.ddspf.dwRBitMask == 0x0000F800 && dDSHeader.ddspf.dwGBitMask == 0x000007E0 && dDSHeader.ddspf.dwBBitMask == 0x0000001F; bool argb4444 = dDSHeader.ddspf.dwABitMask == 0x0000f000 && dDSHeader.ddspf.dwRBitMask == 0x00000f00 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x0000000f; bool rbga4444 = dDSHeader.ddspf.dwABitMask == 0x0000000f && dDSHeader.ddspf.dwRBitMask == 0x0000f000 && dDSHeader.ddspf.dwGBitMask == 0x000000f0 && dDSHeader.ddspf.dwBBitMask == 0x00000f00; bool mipmap = (dDSHeader.dwCaps & DDSHeaders.DDSPixelFormatCaps.MIPMAP) != (DDSHeaders.DDSPixelFormatCaps)0u; bool isNormalMap = ((dDSHeader.ddspf.dwFlags & 524288u) != 0u || (dDSHeader.ddspf.dwFlags & 2147483648u) != 0u); Vector2 newSize = size == default(Vector2) ? new Vector2(dDSHeader.dwWidth, dDSHeader.dwHeight) : size; if (fourcc) { if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT1) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT1, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force DXT1 byte[] file = tmpTex.EncodeToPNG();//tmpTex.EncodeToJPG(); if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameObject.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.RGB24, mipmap); texture.texture.Compress(false); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameObject.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT1, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT3) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, (TextureFormat)11, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force DXT5 byte[] file = tmpTex.EncodeToPNG(); if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameObject.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, (TextureFormat)11, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameObject.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, (TextureFormat)11, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT5) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT5, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force DXT5 byte[] file = tmpTex.EncodeToPNG(); if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameObject.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.ARGB32, mipmap); texture.texture.Compress(false); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameObject.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, TextureFormat.DXT5, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT2) { Debug.Log("DXT2 not supported"); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDXT4) { Debug.Log("DXT4 not supported: "); } else if (dDSHeader.ddspf.dwFourCC == DDSHeaders.DDSValues.uintDX10) { Debug.Log("DX10 dds not supported: "); } else fourcc = false; } if (!fourcc) { TextureFormat textureFormat = TextureFormat.ARGB32; bool ok = true; if (rgb && (rgb888 /*|| bgr888*/)) { // RGB or RGBA format textureFormat = alphapixel ? TextureFormat.RGBA32 : TextureFormat.RGB24; } else if (rgb && rgb565) { // Nvidia texconv B5G6R5_UNORM textureFormat = TextureFormat.RGB565; } else if (rgb && alphapixel && argb4444) { // Nvidia texconv B4G4R4A4_UNORM textureFormat = TextureFormat.ARGB4444; } else if (rgb && alphapixel && rbga4444) { textureFormat = TextureFormat.RGBA4444; } else if (!rgb && alpha != luminance) { // A8 format or Luminance 8 textureFormat = TextureFormat.Alpha8; } else { ok = false; Debug.Log("Only DXT1, DXT5, A8, RGB24, RGBA32, RGB565, ARGB4444 and RGBA4444 are supported"); } if (ok) { if (inPlace && !texture.isReadable) { //This is a small hack to re-load the texture, even when it isn't readable. Unfortnately, //we can't control compression, mipmaps, or anything else really, as the texture is still //marked as unreadable. This will update the size and pixel data however. Texture2D tmpTex = new Texture2D((int)newSize.x, (int)newSize.y, TextureFormat.ARGB32, mipmap); Texture2D tmpTexSrc = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, textureFormat, mipmap); tmpTexSrc.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); Color32[] colors = tmpTexSrc.GetPixels32(); colors = ResizePixels(colors, tmpTexSrc.width, tmpTexSrc.height, (int)newSize.x, (int)newSize.y); tmpTex.SetPixels32(colors); tmpTex.Apply(false); //size using JPG to force alpha-less byte[] file; if (alphapixel) { file = tmpTex.EncodeToPNG(); } else { file = tmpTex.EncodeToPNG();//file = tmpTex.EncodeToJPG(); } if (cache != null) { Directory.GetParent(cache).Create(); System.IO.File.WriteAllBytes(cache, file); } texture.texture.LoadImage(file); GameDatabase.DestroyImmediate(tmpTex); GameDatabase.DestroyImmediate(tmpTexSrc); } else if (inPlace) { texture.texture.Resize((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, textureFormat, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } else { GameDatabase.DestroyImmediate(texture.texture); texture.texture = new Texture2D((int)dDSHeader.dwWidth, (int)dDSHeader.dwHeight, textureFormat, mipmap); texture.texture.LoadRawTextureData(binaryReader.ReadBytes((int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position))); texture.texture.Apply(false, !texture.isReadable); } } } } else Debug.Log("Bad DDS header."); }