using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Diagnostics.CodeAnalysis; using System.IO; namespace OpenVIII { /// /// TEX file handler class. TEX files are packages of textures and Palettes. /// /// /// I borrowed this from my Rinoa's toolset, but modified to aim for buffer rather than file-work /// /// /// public sealed class TEX : Texture_Base { #region Fields /// /// Raw data of TEX file /// private byte[] _buffer; private Texture _texture; #endregion Fields #region Constructors public TEX(byte[] buffer) => Load(buffer); public TEX() { } #endregion Constructors ///// ///// Contains header info and Palette data of TEX file. ///// //public Texture TextureData => texture; //added to get texture data outside of class. #region Properties public bool CLP => _texture.PaletteFlag != 0; public override byte GetBytesPerPixel => _texture.BytesPerPixel; public override int GetClutCount => _texture.NumOfCluts; public override int GetClutSize => (int)_texture.PaletteSize; public override int GetColorsCountPerPalette => _texture.NumOfColors; public override int GetHeight => _texture.Height; public override int GetOrigX => 0; public override int GetOrigY => 0; public override int GetWidth => _texture.Width; /// /// size of header section /// private int HeaderSize => _texture.Version <= 1 ? 0xEC : 0xF0; /// /// size of palette section /// private int PaletteSectionSize => (int)(_texture.PaletteSize * 4); /// /// start of texture section /// private int TextureLocator => HeaderSize + PaletteSectionSize; #endregion Properties #region Methods public override void ForceSetClutColors(ushort newNumOfColors) => _texture.NumOfColors = newNumOfColors; public override void ForceSetClutCount(ushort newClut) => _texture.NumOfCluts = (byte)newClut; public override Color[] GetClutColors(ushort clut) { if (!CLP || _texture.NumOfCluts == 0) return null; if (clut >= _texture.NumOfCluts) throw new Exception($"Desired palette is incorrect use -1 for default or use a smaller number: {clut} > {_texture.NumOfCluts}"); var colors = new Color[_texture.NumOfColors]; var k = 0; for (var i = clut * _texture.NumOfColors * 4; i < _texture.PaletteData.Length && k < colors.Length; i += 4) { colors[k].B = _texture.PaletteData[i]; colors[k].G = _texture.PaletteData[i + 1]; colors[k].R = _texture.PaletteData[i + 2]; colors[k].A = _texture.PaletteData[i + 3]; k++; } return colors; } /// /// Get Texture2D converted to 32bit color /// /// Desired Palette, see texture.NumOfPalettes. -1 is default. /// Override colors of palette; Array size must match texture.NumOfColorsPerPalette /// 32bit Texture2D /// /// Some palettes are 256 but the game only uses 16 colors might need to make the restriction /// more lax and allow any size array and only throw errors if the color key is greater than /// size of array. Or we could treat any of those bad matches as transparent. /// public override Texture2D GetTexture(Color[] colors) { if (Memory.Graphics.GraphicsDevice == null) return null; if (_texture.PaletteFlag != 0) { if (colors == null) throw new ArgumentNullException(nameof(colors)); //if (colors != null && colors.Length != texture.NumOfColors) //{ // //if (colors.Length > texture.NumOfColors) //truncate colors to the correct amount. in some // // colors = colors.Take(texture.NumOfColors).ToArray(); // //else // might need to expand the array the other way if we get more mismatches. // //Array.Resize(ref colors,texture.NumOfColors); // //throw new Exception($" custom colors parameter set but array size to match palette size: {texture.NumOfColors}"); //} MemoryStream ms; using (var br = new BinaryReader(ms = new MemoryStream(_buffer))) { ms.Seek(TextureLocator, SeekOrigin.Begin); var convertBuffer = new TextureBuffer(_texture.Width, _texture.Height); for (var i = 0; i < convertBuffer.Length && ms.Position < ms.Length; i++) { var colorKey = br.ReadByte(); if (colorKey > colors.Length) continue; convertBuffer[i] = colors[colorKey]; } return convertBuffer.GetTexture(); } } if (_texture.BytesPerPixel == 2) { MemoryStream ms; using (var br = new BinaryReader(ms = new MemoryStream(_buffer))) { ms.Seek(TextureLocator, SeekOrigin.Begin); var convertBuffer = new TextureBuffer(_texture.Width, _texture.Height); for (var i = 0; ms.Position + 2 < ms.Length; i++) { convertBuffer[i] = ABGR1555toRGBA32bit(br.ReadUInt16()); } return convertBuffer.GetTexture(); } } if (_texture.BytesPerPixel != 3) return null; { // not tested but vincent tim had support for it so i guess it's possible RGB or BGR MemoryStream ms; using (var br = new BinaryReader(ms = new MemoryStream(_buffer))) { ms.Seek(TextureLocator, SeekOrigin.Begin); var convertBuffer = new TextureBuffer(_texture.Width, _texture.Height); var color = new Color { A = 0xFF }; for (var i = 0; ms.Position + 3 < ms.Length; i++) { //RGB or BGR so might need to reorder things to RGB color.B = br.ReadByte(); color.G = br.ReadByte(); color.R = br.ReadByte(); convertBuffer[i] = color; } return convertBuffer.GetTexture(); } } } public override Texture2D GetTexture(ushort clut) => GetTexture(GetClutColors(clut)); public override void Load(byte[] buffer, uint offset = 0) { _texture = new Texture(); this._buffer = buffer; ReadParameters(); } /// /// Writes the Tim file to the hard drive. /// /// Path where you want file to be saved. public override void Save(string path) { using (var bw = new BinaryWriter(File.Create(path))) { bw.Write(_buffer); } } public override void SaveCLUT(string path) { using (var clut = new Texture2D(Memory.Graphics.GraphicsDevice, _texture.NumOfColors, _texture.NumOfCluts)) { for (ushort i = 0; i < _texture.NumOfCluts; i++) { clut.SetData(0, new Rectangle(0, i, _texture.NumOfColors, 1), GetClutColors(i), 0, _texture.NumOfColors); } using (var fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) clut.SaveAsPng(fs, _texture.NumOfColors, _texture.NumOfCluts); } } /// /// Read header data from TEX file. /// /// /// private void ReadParameters() { _texture.Version = BitConverter.ToUInt32(_buffer, 0x00); _texture.Width = (int)BitConverter.ToUInt32(_buffer, 0x3C); //nothing will be uint size big. _texture.Height = (int)BitConverter.ToUInt32(_buffer, 0x40); _texture.BytesPerPixel = _buffer[0x68]; _texture.NumOfCluts = _buffer[0x30]; _texture.NumOfColors = BitConverter.ToInt32(_buffer, 0x34); _texture.BitDepth = BitConverter.ToUInt32(_buffer, 0x38); _texture.PaletteFlag = _buffer[0x4C]; _texture.PaletteSize = BitConverter.ToUInt32(_buffer, 0x58); if (_texture.PaletteFlag == 0) return; _texture.PaletteData = new byte[PaletteSectionSize]; Buffer.BlockCopy(_buffer, 0xF0, _texture.PaletteData, 0, PaletteSectionSize); } #endregion Methods #region Structs /// /// Contains Header info and Palette data of TEX file. /// /// /// private struct Texture { #region Fields /// /// 0x38 /// [SuppressMessage("ReSharper", "NotAccessedField.Local")] public uint BitDepth; /// /// 0x68 /// public byte BytesPerPixel; /// /// 0x40 /// public int Height; /// /// 0x30 /// public byte NumOfCluts; /// /// 0x34 /// public int NumOfColors; /// /// 0xF0 for ff8;0xEC for ff7; size = PaletteSize * 4; /// public byte[] PaletteData; /// /// 0x4C /// public byte PaletteFlag; /// /// 0x58 /// public uint PaletteSize; /// /// 0x00; 1=FF7 | 2=FF8 /// public uint Version; /// /// 0x3C /// public int Width; #endregion Fields } #endregion Structs //public struct Color //{ // #region Fields // public byte Alpha; public byte Blue; public byte Green; public byte Red; // #endregion Fields //} } }