292 lines
17 KiB
C#
292 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
// image importing methods will be left up to this thing, as i may have cluttered the fuck out of Form1.cs
|
|
|
|
namespace VTFXUtil
|
|
{ /// <summary>
|
|
/// Handles methods for general texture import/export/manipulation uses.
|
|
/// </summary>
|
|
class ImageImporter
|
|
{
|
|
|
|
/// <summary>
|
|
/// Decompresses LZMA compressed files. Primarily used on Xbox 360 textures.
|
|
/// </summary>
|
|
/// <param name="inData"></param>
|
|
/// <param name="inPath"></param>
|
|
/// <param name="outPath"></param>
|
|
/// <returns></returns>
|
|
public static byte[] LZMADecompress(byte[] inData, string inPath, string outPath)
|
|
{
|
|
System.IO.FileStream TempLZMAIn = new System.IO.FileStream(inPath, System.IO.FileMode.Create);
|
|
TempLZMAIn.Write(inData, 0, inData.Length);
|
|
TempLZMAIn.Close();
|
|
TempLZMAIn = new System.IO.FileStream(inPath, System.IO.FileMode.Open);
|
|
System.IO.FileStream TempLZMAOut = new System.IO.FileStream(outPath, System.IO.FileMode.Create);
|
|
byte[] HeaderBytes = new byte[4];
|
|
byte[] UncompressedSizeBytes = new byte[4];
|
|
byte[] CompressedSizeBytes = new byte[4];
|
|
byte[] DecoderProperties = new byte[5];
|
|
TempLZMAIn.Read(HeaderBytes, 0, 4);
|
|
TempLZMAIn.Read(UncompressedSizeBytes, 0, 4);
|
|
TempLZMAIn.Read(CompressedSizeBytes, 0, 4);
|
|
TempLZMAIn.Read(DecoderProperties, 0, 5);
|
|
int UncompressedSize = System.BitConverter.ToInt32(UncompressedSizeBytes, 0);
|
|
int CompressedSize = System.BitConverter.ToInt32(CompressedSizeBytes, 0);
|
|
SevenZip.Compression.LZMA.Decoder LZMADec = new SevenZip.Compression.LZMA.Decoder();
|
|
LZMADec.SetDecoderProperties(DecoderProperties);
|
|
LZMADec.Code(TempLZMAIn, TempLZMAOut, CompressedSize, UncompressedSize, null);
|
|
TempLZMAIn.Flush();
|
|
TempLZMAOut.Flush();
|
|
TempLZMAOut.Close();
|
|
TempLZMAOut = new System.IO.FileStream(outPath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
|
|
byte[] Texture = new byte[UncompressedSize];
|
|
using (TempLZMAOut)
|
|
{
|
|
TempLZMAOut.Read(Texture, 0, UncompressedSize);
|
|
}
|
|
TempLZMAOut.Close();
|
|
TempLZMAIn.Close();
|
|
return Texture;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an LZMA compressed version of the input data as a MemoryStream.
|
|
/// </summary>
|
|
/// <param name="InData"></param>
|
|
/// <returns></returns>
|
|
public static System.IO.MemoryStream LZMACompress(byte[] InData)
|
|
{
|
|
SevenZip.CoderPropID[] Properties = { SevenZip.CoderPropID.DictionarySize, SevenZip.CoderPropID.PosStateBits, SevenZip.CoderPropID.LitContextBits, SevenZip.CoderPropID.LitPosBits, SevenZip.CoderPropID.Algorithm, SevenZip.CoderPropID.NumFastBytes, SevenZip.CoderPropID.MatchFinder, SevenZip.CoderPropID.EndMarker };
|
|
object[] Props2 = { (1 << 23), 2, 3, 0, 2, 128, "bt4", false }; // default properties
|
|
SevenZip.Compression.LZMA.Encoder LZMAEnc = new SevenZip.Compression.LZMA.Encoder();
|
|
LZMAEnc.SetCoderProperties(Properties, Props2);
|
|
System.IO.MemoryStream TempInStream = new System.IO.MemoryStream();
|
|
TempInStream.Write(InData, 0, InData.Length);
|
|
TempInStream.Position = 0;
|
|
System.IO.MemoryStream TempOutStream = new System.IO.MemoryStream();
|
|
System.IO.MemoryStream CompressedTexture = new System.IO.MemoryStream();
|
|
LZMAEnc.Code(TempInStream, CompressedTexture, -1, -1, null);
|
|
long CompressedSize = CompressedTexture.Length;
|
|
byte[] LZMAIdent = { 0x4c, 0x5a, 0x4d, 0x41 }; // "LZMA", had to make this a byte array, as you can't copy string values to a stream
|
|
TempOutStream.Write(LZMAIdent, 0, 4);
|
|
TempOutStream.Write(System.BitConverter.GetBytes(InData.Length), 0, 4);
|
|
TempOutStream.Write(System.BitConverter.GetBytes(CompressedSize), 0, 4);
|
|
LZMAEnc.WriteCoderProperties(TempOutStream);
|
|
TempOutStream.Write(CompressedTexture.ToArray(), 0, CompressedTexture.ToArray().Length); // had to manually append the contents of the texture stream onto the other stream, as apparently resetting the stream screws up with the LZMA coder, which then refuses to properly write the texture
|
|
CompressedTexture.Dispose(); // free resources
|
|
return TempOutStream;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Just a simple endian swap. Nothing more, nothing less.
|
|
/// </summary>
|
|
/// <param name="Data"></param>
|
|
/// <param name="BufferSize"></param>
|
|
/// <returns></returns>
|
|
public static byte[] EndianSwap(byte[] Data, int BufferSize)
|
|
{
|
|
byte[] ByteBuffer = new byte[BufferSize]; //create bytebuffer for endianswapping
|
|
byte[] TextureTemp = new byte[Data.Length];
|
|
for (int i = 0; i < Data.Length; i += ByteBuffer.Length)
|
|
{
|
|
Array.Copy(Data, i, ByteBuffer, 0, ByteBuffer.Length);
|
|
Array.Reverse(ByteBuffer);
|
|
Array.Copy(ByteBuffer, 0, TextureTemp, i, ByteBuffer.Length);
|
|
}
|
|
return TextureTemp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Arranges any Block Compressed texture formats from a format usable by the Xbox 360 into one that a PC can comprehend
|
|
/// </summary>
|
|
/// <param name="TexData"></param>
|
|
/// <param name="Format"></param>
|
|
/// <returns></returns>
|
|
public static byte[] RearrangeCompressedTexture(byte[] TexData, int Format) //compressed textures on the 360 need to be endianswapped, yet the PS3 counterparts don't
|
|
{
|
|
byte[] NewTexData = new byte[TexData.Length];
|
|
int ColorValLen = 0;
|
|
int ColorBlendLen = 0;
|
|
int AlphaValLen = 0;
|
|
if (Format == (int)VTFGeneral.TEXTURE_FORMATS.IMAGE_FORMAT_DXT1)
|
|
{
|
|
ColorValLen = 2; //will be doubled!!!
|
|
ColorBlendLen = 4;
|
|
AlphaValLen = 0;
|
|
}
|
|
else if (Format == (int)VTFGeneral.TEXTURE_FORMATS.IMAGE_FORMAT_DXT3 || Format == (int)VTFGeneral.TEXTURE_FORMATS.IMAGE_FORMAT_DXT5)
|
|
{
|
|
AlphaValLen = 8;
|
|
ColorValLen = 2; //will be doubled!!
|
|
ColorBlendLen = 4;
|
|
}
|
|
else
|
|
{
|
|
return TexData;
|
|
}
|
|
int BlockLength = (ColorValLen * 2) + ColorBlendLen + AlphaValLen;
|
|
byte[] EditingBlock = new byte[BlockLength];
|
|
for (int i = 0; i < TexData.Length; i += BlockLength)
|
|
{
|
|
byte[] Color0 = new byte[ColorValLen];
|
|
byte[] Color1 = new byte[ColorValLen];
|
|
byte[] ColorBlendInfo = new byte[ColorBlendLen];
|
|
if (Format == (int)VTFGeneral.TEXTURE_FORMATS.IMAGE_FORMAT_DXT1)
|
|
{
|
|
Array.Copy(TexData, i, Color0, 0, ColorValLen);
|
|
Array.Copy(TexData, i + ColorValLen, Color1, 0, ColorValLen);
|
|
Array.Copy(TexData, i + BlockLength - AlphaValLen - ColorBlendLen, ColorBlendInfo, 0, ColorBlendLen);
|
|
Array.Reverse(Color0);
|
|
Array.Reverse(Color1); // reverse color data, as our endianness is not the same as the Xenon
|
|
byte[] DuplicateOfColorLayout = new byte[4];
|
|
Array.Copy(ColorBlendInfo, DuplicateOfColorLayout, 4);
|
|
ColorBlendInfo[0] = DuplicateOfColorLayout[1];
|
|
ColorBlendInfo[1] = DuplicateOfColorLayout[0];
|
|
ColorBlendInfo[2] = DuplicateOfColorLayout[3];
|
|
ColorBlendInfo[3] = DuplicateOfColorLayout[2]; // color layout inside of the 4x4 blocks is arranged strangely, as [row 2, row 1, row 3, row 4]
|
|
Array.Copy(Color0, 0, EditingBlock, 0, ColorValLen);
|
|
Array.Copy(Color1, 0, EditingBlock, ColorValLen, ColorValLen);
|
|
Array.Copy(ColorBlendInfo, 0, EditingBlock, ColorValLen * 2, ColorBlendLen); //copy new values into the newly created block
|
|
}
|
|
else //for formats with alpha
|
|
{
|
|
byte[] AlphaInfo = new byte[AlphaValLen];
|
|
Array.Copy(TexData, i, AlphaInfo, 0, AlphaValLen);
|
|
byte[] NewAlphaInfo = new byte[AlphaValLen]; // make this for reversible alphas
|
|
//Array.Reverse(AlphaInfo);
|
|
Array.Copy(TexData, i + AlphaValLen, Color0, 0, ColorValLen);
|
|
Array.Copy(TexData, i + AlphaValLen + ColorValLen, Color1, 0, ColorValLen);
|
|
Array.Copy(TexData, i + AlphaValLen + BlockLength - AlphaValLen - ColorBlendLen, ColorBlendInfo, 0, ColorBlendLen);
|
|
if (Format == (int)VTFGeneral.TEXTURE_FORMATS.IMAGE_FORMAT_DXT5) // the 360's DXT5 type files are reversed in an odd kind of way, i personally haven't encountered many DXT3 files, and will have to be added later if i find one
|
|
{
|
|
for (int k = 1; k < AlphaValLen; k += 2)
|
|
{
|
|
NewAlphaInfo[k - 1] = AlphaInfo[k];
|
|
NewAlphaInfo[k] = AlphaInfo[k - 1];
|
|
}
|
|
AlphaInfo = NewAlphaInfo;
|
|
}
|
|
Array.Reverse(Color0);
|
|
Array.Reverse(Color1);
|
|
byte[] DuplicateOfColorLayout = new byte[4];
|
|
Array.Copy(ColorBlendInfo, DuplicateOfColorLayout, 4);
|
|
ColorBlendInfo[0] = DuplicateOfColorLayout[1];
|
|
ColorBlendInfo[1] = DuplicateOfColorLayout[0];
|
|
ColorBlendInfo[2] = DuplicateOfColorLayout[3];
|
|
ColorBlendInfo[3] = DuplicateOfColorLayout[2]; // cloned from dxt1
|
|
Array.Copy(AlphaInfo, 0, EditingBlock, 0, AlphaValLen);
|
|
Array.Copy(Color0, 0, EditingBlock, AlphaValLen, ColorValLen);
|
|
Array.Copy(Color1, 0, EditingBlock, AlphaValLen + ColorValLen, ColorValLen);
|
|
Array.Copy(ColorBlendInfo, 0, EditingBlock, AlphaValLen + ColorValLen * 2, ColorBlendLen);
|
|
}
|
|
Array.Copy(EditingBlock, 0, NewTexData, i, BlockLength); //reconstruct texture data with new stuff
|
|
}
|
|
return NewTexData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Outputs a MemoryStream with the specified texture data, and reverses the mipmap order, for use in VTF/VTFX or other smallest-mip-first formats.
|
|
/// </summary>
|
|
/// <param name="TexData"></param>
|
|
/// <param name="Width"></param>
|
|
/// <param name="Height"></param>
|
|
/// <param name="Format"></param>
|
|
/// <param name="MipList"></param>
|
|
/// <returns></returns>
|
|
public static System.IO.MemoryStream ReverseMipsIntoMemoryStream(byte[] TexData, int Width, int Height, int Format, MipMaker.MipMap[] MipList)
|
|
{
|
|
System.IO.MemoryStream CurrentTexture = new System.IO.MemoryStream();
|
|
List<byte[]> Mips = new List<byte[]>();
|
|
CurrentTexture.Write(TexData, 0, TexData.Length);
|
|
CurrentTexture.Position = 0; // reset back to beginning of the stream, we will need this for what comes next
|
|
for (int i = 0; i < MipList.Length; i++)
|
|
{
|
|
byte[] CurrentMip = new byte[MipList[i].TotalBytes];
|
|
CurrentTexture.Read(CurrentMip, 0, MipList[i].TotalBytes);
|
|
Mips.Add(CurrentMip); // take the texture data from the stream, and shove it into the list
|
|
}
|
|
CurrentTexture.Flush();
|
|
CurrentTexture.Dispose();
|
|
Mips.Reverse();
|
|
CurrentTexture = new System.IO.MemoryStream();
|
|
foreach (byte[] Mip in Mips)
|
|
{
|
|
CurrentTexture.Write(Mip, 0, Mip.Length);
|
|
}
|
|
return CurrentTexture;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts and outputs texture data as a TIF file in a MemoryStream.
|
|
/// </summary>
|
|
/// <param name="Texture"></param>
|
|
/// <param name="Mipcount"></param>
|
|
/// <param name="Format"></param>
|
|
/// <param name="Imported"></param>
|
|
/// <returns></returns>
|
|
public static System.IO.MemoryStream GenerateImage(byte[] Texture, int Mipcount, int Format, VTFXHandler.VTFXFile Imported)
|
|
{
|
|
System.IO.MemoryStream TextureStream = new System.IO.MemoryStream();
|
|
TextureStream.Write(Texture, 0, Texture.Length);
|
|
CSharpImageLibrary.ImageEngineFormat ParsedEnum = (CSharpImageLibrary.ImageEngineFormat)Enum.Parse(typeof(VTFGeneral.TEXTURE_FORMATS_INTEROP), Enum.GetName(typeof(VTFGeneral.TEXTURE_FORMATS), Format));
|
|
Console.WriteLine(ParsedEnum);
|
|
|
|
CSharpImageLibrary.Headers.DDS_Header newDDSHeader = new CSharpImageLibrary.Headers.DDS_Header(Mipcount, Imported.ImageHeight, Imported.ImageWidth, ParsedEnum, CSharpImageLibrary.Headers.DDS_Header.DXGI_FORMAT.DXGI_FORMAT_UNKNOWN);
|
|
byte[] DDSHeader = new byte[newDDSHeader.dwSize + 4];
|
|
newDDSHeader.WriteToArray(DDSHeader, 0);
|
|
byte[] RealTexture = new byte[DDSHeader.Length + Texture.Length];
|
|
Array.Copy(DDSHeader, 0, RealTexture, 0, DDSHeader.Length);
|
|
Array.Copy(Texture, 0, RealTexture, DDSHeader.Length, Texture.Length);
|
|
TextureStream.Flush();
|
|
TextureStream.Write(RealTexture, 0, RealTexture.Length);
|
|
CSharpImageLibrary.ImageEngineImage CSILImage = new CSharpImageLibrary.ImageEngineImage(RealTexture);
|
|
//Console.WriteLine(CSILImage.ToString());
|
|
CSharpImageLibrary.ImageFormats.ImageEngineFormatDetails DestinationDetails = new CSharpImageLibrary.ImageFormats.ImageEngineFormatDetails(CSharpImageLibrary.ImageEngineFormat.TIF);
|
|
bool AlphaRemovalFlag = !(VTFGeneral.HasAlpha(Format));
|
|
byte[] ConvertedToPNG = CSILImage.Save(DestinationDetails, CSharpImageLibrary.MipHandling.KeepTopOnly, 8192, 0, false); //show only the first mip, as this is just a preview
|
|
System.IO.MemoryStream PNGStream = new System.IO.MemoryStream(); //import our file into ram
|
|
PNGStream.Write(ConvertedToPNG, 0, ConvertedToPNG.Length);
|
|
return PNGStream;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts and outputs texture data as a TIF file in a MemoryStream.
|
|
/// </summary>
|
|
/// <param name="Texture"></param>
|
|
/// <param name="Mipcount"></param>
|
|
/// <param name="Format"></param>
|
|
/// <param name="Imported"></param>
|
|
/// <returns></returns>
|
|
public static System.IO.MemoryStream GenerateImage(byte[] Texture, int Mipcount, int Format, Formats.VTFFile Imported)
|
|
{
|
|
System.IO.MemoryStream TextureStream = new System.IO.MemoryStream();
|
|
TextureStream.Write(Texture, 0, Texture.Length);
|
|
CSharpImageLibrary.ImageEngineFormat ParsedEnum = (CSharpImageLibrary.ImageEngineFormat)Enum.Parse(typeof(VTFGeneral.TEXTURE_FORMATS_INTEROP), Enum.GetName(typeof(VTFGeneral.TEXTURE_FORMATS), Format));
|
|
Console.WriteLine(ParsedEnum);
|
|
|
|
CSharpImageLibrary.Headers.DDS_Header newDDSHeader = new CSharpImageLibrary.Headers.DDS_Header(Mipcount, Imported.Height, Imported.Width, ParsedEnum, CSharpImageLibrary.Headers.DDS_Header.DXGI_FORMAT.DXGI_FORMAT_UNKNOWN);
|
|
byte[] DDSHeader = new byte[newDDSHeader.dwSize + 4];
|
|
newDDSHeader.WriteToArray(DDSHeader, 0);
|
|
byte[] RealTexture = new byte[DDSHeader.Length + Texture.Length];
|
|
Array.Copy(DDSHeader, 0, RealTexture, 0, DDSHeader.Length);
|
|
Array.Copy(Texture, 0, RealTexture, DDSHeader.Length, Texture.Length);
|
|
TextureStream.Flush();
|
|
TextureStream.Write(RealTexture, 0, RealTexture.Length);
|
|
CSharpImageLibrary.ImageEngineImage CSILImage = new CSharpImageLibrary.ImageEngineImage(RealTexture);
|
|
//Console.WriteLine(CSILImage.ToString());
|
|
CSharpImageLibrary.ImageFormats.ImageEngineFormatDetails DestinationDetails = new CSharpImageLibrary.ImageFormats.ImageEngineFormatDetails(CSharpImageLibrary.ImageEngineFormat.TIF);
|
|
bool AlphaRemovalFlag = !(VTFGeneral.HasAlpha(Format));
|
|
byte[] ConvertedToPNG = CSILImage.Save(DestinationDetails, CSharpImageLibrary.MipHandling.KeepTopOnly, 8192, 0, false); //show only the first mip, as this is just a preview
|
|
System.IO.MemoryStream PNGStream = new System.IO.MemoryStream(); //import our file into ram
|
|
PNGStream.Write(ConvertedToPNG, 0, ConvertedToPNG.Length);
|
|
return PNGStream;
|
|
}
|
|
|
|
|
|
}
|
|
}
|