691 lines
27 KiB
C#
691 lines
27 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
public class AAssit
|
|
{
|
|
public static T ReadFromBinaryOf<T>(Stream stream, ref long readBytes, long fileOffset = -1)
|
|
{
|
|
// If fileOffset >= 0, seek to that position in the file
|
|
if (fileOffset >= 0)
|
|
{
|
|
stream.Seek(fileOffset, SeekOrigin.Begin);
|
|
}
|
|
|
|
int size = Marshal.SizeOf(typeof(T));
|
|
if (typeof(T) == typeof(bool)) size = 1; // bool is stored as 1 byte
|
|
|
|
byte[] buffer = new byte[size];
|
|
|
|
// Read `size` bytes into `buffer[0..size]`
|
|
int bytesRead = stream.Read(buffer, 0, size);
|
|
if (bytesRead < size)
|
|
{
|
|
Console.WriteLine($"ERROR::Not enough data in stream to read {typeof(T).Name}. Expected {size} bytes, but only read {bytesRead}.");
|
|
return default(T);
|
|
}
|
|
|
|
// Accumulate the number of bytes read
|
|
readBytes += bytesRead;
|
|
|
|
// Pin and marshal
|
|
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
|
try
|
|
{
|
|
return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
|
|
}
|
|
finally
|
|
{
|
|
handle.Free();
|
|
}
|
|
}
|
|
|
|
public static T[] ReadArrayFromBinary<T>(Stream stream, int arraySize, ref long readBytes, long fileOffset = -1)
|
|
{
|
|
T[] array = new T[arraySize];
|
|
for (int i = 0; i < arraySize; i++)
|
|
{
|
|
array[i] = ReadFromBinaryOf<T>(stream, ref readBytes, fileOffset);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads an array from a binary stream where the data layout is:
|
|
/// [int32 count] followed by <c>count</c> serialized elements of <typeparamref name="T"/>.
|
|
/// If <paramref name="fileOffset"/> >= 0, seeks to that absolute position before
|
|
/// reading the count and re-seeks before reading each element. Updates
|
|
/// <paramref name="readBytes"/> with the total number of bytes consumed. Returns
|
|
/// null when the read <c>count</c> is less than or equal to zero.
|
|
/// </summary>
|
|
/// <param name="stream">Open readable <see cref="FileStream"/> to read from.</param>
|
|
/// <param name="readBytes">Reference accumulator updated with bytes consumed by this method.</param>
|
|
/// <param name="fileOffset">Optional absolute file position to seek prior to reads; if >= 0 the stream is
|
|
/// re-seeked before reading the count and each element.</param>
|
|
/// <typeparam name="T">Element type to deserialize. Must be blittable/marshallable via <see cref="Marshal.PtrToStructure(System.IntPtr,System.Type)"/>.</typeparam>
|
|
/// <returns>The populated array when <c>count</c> > 0; otherwise null.</returns>
|
|
public static T[] ReadArrayPointerFromBinary<T>(FileStream stream, ref long readBytes, long fileOffset = -1)
|
|
{
|
|
// seek to the fileOffset if it's >= 0
|
|
if (fileOffset >= 0)
|
|
{
|
|
stream.Seek(fileOffset, SeekOrigin.Begin);
|
|
}
|
|
|
|
// Read the first 4 bytes to get the array size
|
|
int arraySize = GetIntFromFileStream(stream, ref readBytes);
|
|
if (arraySize <= 0) return null;
|
|
|
|
T[] array = new T[arraySize];
|
|
for (int i = 0; i < arraySize; i++)
|
|
{
|
|
array[i] = ReadFromBinaryOf<T>(stream, ref readBytes, fileOffset);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
public static byte[] ReadByArray(Stream stream, ref long readBytes, int size, long fileOffset = -1)
|
|
{
|
|
// If fileOffset >= 0, seek to that position in the file
|
|
if (fileOffset >= 0)
|
|
{
|
|
stream.Seek(fileOffset, SeekOrigin.Begin);
|
|
}
|
|
|
|
byte[] buffer = new byte[size];
|
|
|
|
// Read `size` bytes into `buffer[0..size]`
|
|
int bytesRead = stream.Read(buffer, 0, size);
|
|
if (bytesRead < size)
|
|
{
|
|
Console.WriteLine($"ERROR::Not enough data in stream to read byte array. Expected {size} bytes, but only read {bytesRead}.");
|
|
return null;
|
|
}
|
|
|
|
// Accumulate the number of bytes read
|
|
readBytes += bytesRead;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
public static bool GetBoolFromFileStream(FileStream fs, ref long readBytes)
|
|
{
|
|
byte[] buffer = new byte[1];
|
|
int bytesRead = fs.Read(buffer, 0, 1);
|
|
if (bytesRead < 1)
|
|
throw new EndOfStreamException("Not enough data in stream to read bool.");
|
|
|
|
readBytes += bytesRead;
|
|
return buffer[0] != 0;
|
|
}
|
|
|
|
public static int GetIntFromFileStream(FileStream fs, ref long readBytes)
|
|
{
|
|
byte[] buffer = new byte[4];
|
|
int bytesRead = fs.Read(buffer, 0, 4);
|
|
if (bytesRead < 4)
|
|
throw new EndOfStreamException("Not enough data in stream to read int.");
|
|
|
|
readBytes += bytesRead;
|
|
return BitConverter.ToInt32(buffer, 0);
|
|
}
|
|
|
|
public static uint GetUIntFromFileStream(FileStream fs, ref long readBytes)
|
|
{
|
|
byte[] buffer = new byte[4];
|
|
int bytesRead = fs.Read(buffer, 0, 4);
|
|
if (bytesRead < 4)
|
|
throw new EndOfStreamException("Not enough data in stream to read uint.");
|
|
|
|
readBytes += bytesRead;
|
|
return BitConverter.ToUInt32(buffer, 0);
|
|
}
|
|
|
|
public static long GetLongFromFileStream(FileStream fs, ref long readBytes)
|
|
{
|
|
byte[] buffer = new byte[8];
|
|
int bytesRead = fs.Read(buffer, 0, 8);
|
|
if (bytesRead < 8)
|
|
throw new EndOfStreamException("Not enough data in stream to read long.");
|
|
|
|
readBytes += bytesRead;
|
|
return BitConverter.ToInt64(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the substring after <paramref name="tag"/> if <paramref name="buffer"/> starts with <paramref name="tag"/>.
|
|
/// </summary>
|
|
/// <param name="buffer">The full string to check.</param>
|
|
/// <param name="tag">The tag string to look for at the beginning of <paramref name="buffer"/>.</param>
|
|
/// <param name="result">
|
|
/// Outputs the substring of <paramref name="buffer"/> that appears immediately after <paramref name="tag"/>,
|
|
/// if <paramref name="buffer"/> does indeed start with <paramref name="tag"/>.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if <paramref name="buffer"/> starts with <paramref name="tag"/>; false otherwise.
|
|
/// </returns>
|
|
public static bool GetStringFromCharsAfter(char[] buffer, string tag, out string result)
|
|
{
|
|
// from buffer to string
|
|
string source = new string(buffer);
|
|
|
|
return GetStringAfter(source, tag, out result);
|
|
}
|
|
|
|
public static bool GetStringAfter(string source, string tag, out string result)
|
|
{
|
|
// Initialize the output in case we return false
|
|
result = string.Empty;
|
|
|
|
// Check if buffer starts with the specified tag
|
|
if (!source.StartsWith(tag))
|
|
return false;
|
|
|
|
// If it starts with tag, skip tag.Length characters
|
|
// and copy the rest to result
|
|
result = source.Substring(tag.Length);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a string from a binary stream at the position indicated by <paramref name="readBytes"/>.
|
|
/// 1) Seeks to <paramref name="readBytes"/> from the start of the stream (SeekOrigin.Begin).
|
|
/// 2) Reads 4 bytes (an int) that specify the string length (in bytes).
|
|
/// 3) If length is 0, returns "".
|
|
/// 4) Otherwise, reads the specified number of bytes and decodes to a string.
|
|
/// 5) Updates <paramref name="readBytes"/> to the new position after reading.
|
|
/// </summary>
|
|
/// <param name="stream">The stream from which to read.</param>
|
|
/// <param name="readBytes">
|
|
/// On entry, this indicates where to seek in the stream (from the beginning).
|
|
/// On exit, this will be updated to the new position after reading.
|
|
/// </param>
|
|
/// <param name="result">Outputs the decoded string.</param>
|
|
/// <returns>True if successful; otherwise false (e.g., not enough data).</returns>
|
|
public static bool ReadString(Stream stream, ref long readBytes, out string result)
|
|
{
|
|
result = ReadString(stream, ref readBytes);
|
|
return result != null;
|
|
}
|
|
|
|
public static string ReadString(Stream stream, ref long readBytes)
|
|
{
|
|
var result = string.Empty;
|
|
|
|
try
|
|
{
|
|
int length = 0;
|
|
byte[] buffer = new byte[4];
|
|
|
|
int byteRead = stream.Read(buffer, 0, 4);
|
|
if (byteRead < 4)
|
|
return null;
|
|
|
|
readBytes += byteRead;
|
|
|
|
length = BitConverter.ToInt32(buffer, 0);
|
|
|
|
if (length == 0)
|
|
return null;
|
|
|
|
buffer = new byte[length];
|
|
byteRead = stream.Read(buffer, 0, length);
|
|
if (byteRead < length)
|
|
return null;
|
|
|
|
readBytes += byteRead;
|
|
// Encoding encoding =
|
|
result = Encoding.GetEncoding(936).GetString(buffer);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Could be EndOfStreamException, IOException, etc.
|
|
// Handle or log the exception as needed
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static void ReadLine(Stream stream, ref long bytesRead, out string result)
|
|
{
|
|
result = Fgets(stream, 1024, Encoding.GetEncoding(936));
|
|
bytesRead += result.Length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a null-terminated string from <paramref name="stream"/>, starting at <paramref name="fileOffset"/>.
|
|
/// - Seeks to <paramref name="fileOffset"/> in the file.
|
|
/// - Reads one byte at a time until encountering a null (0x00) or reaching <paramref name="bufferLength"/> - 1.
|
|
/// - Decodes the collected bytes using the specified <paramref name="encoding"/>.
|
|
/// - Appends how many bytes it actually read to <paramref name="alreadyReadBytes"/>.
|
|
///
|
|
/// Returns true on success (string in <paramref name="result"/>), or false if an error occurs
|
|
/// (EOF, buffer overflow, etc.).
|
|
/// </summary>
|
|
/// <param name="stream">Open, readable <see cref="Stream"/>.</param>
|
|
/// <param name="fileOffset">Absolute offset in the file to seek before reading.</param>
|
|
/// <param name="bufferLength">Maximum number of characters to collect (like the C++ dwBufferLength).</param>
|
|
/// <param name="alreadyReadBytes">Accumulates how many bytes we've read so far across calls.</param>
|
|
/// <param name="encoding">Encoding used to interpret the raw bytes (e.g. <see cref="Encoding.ASCII"/>).</param>
|
|
/// <param name="result">Outputs the decoded string (excluding the null terminator).</param>
|
|
/// <returns>True if a string was successfully read, false otherwise.</returns>
|
|
public static bool ReadString(Stream stream,long fileOffset,int bufferLength,ref long alreadyReadBytes, Encoding encoding,out string result)
|
|
{
|
|
result = null;
|
|
|
|
// Basic argument checks
|
|
if (stream == null)
|
|
throw new ArgumentNullException(nameof(stream));
|
|
if (!stream.CanRead)
|
|
throw new ArgumentException("Stream must be readable.", nameof(stream));
|
|
if (bufferLength < 2)
|
|
throw new ArgumentOutOfRangeException(nameof(bufferLength), "Buffer length must be >= 2.");
|
|
if (encoding == null)
|
|
throw new ArgumentNullException(nameof(encoding));
|
|
|
|
// 1) Seek to the specified offset from the beginning of the file
|
|
stream.Seek(fileOffset, SeekOrigin.Begin);
|
|
|
|
// 2) We will collect bytes into this temporary array (exclude the null terminator)
|
|
byte[] buffer = new byte[bufferLength - 1];
|
|
int index = 0;
|
|
|
|
// Try reading first byte
|
|
int firstByte = stream.ReadByte();
|
|
if (firstByte == -1)
|
|
{
|
|
// EOF immediately
|
|
return false;
|
|
}
|
|
|
|
// We have read 1 byte so far in this call
|
|
long localRead = 1;
|
|
byte b = (byte)firstByte;
|
|
|
|
// 3) Read until we hit a zero byte (null terminator) or fill the buffer
|
|
while (b != 0)
|
|
{
|
|
buffer[index++] = b;
|
|
|
|
// Check for overflow
|
|
if (index >= bufferLength - 1)
|
|
{
|
|
// We would overflow if we read more data
|
|
return false;
|
|
}
|
|
|
|
int nextByte = stream.ReadByte();
|
|
if (nextByte == -1)
|
|
{
|
|
// EOF before we found a null terminator
|
|
return false;
|
|
}
|
|
|
|
localRead++;
|
|
b = (byte)nextByte;
|
|
|
|
// Loop continues until b == 0
|
|
}
|
|
|
|
// If we get here, b == 0 => null terminator found.
|
|
// localRead already includes the byte we used for the null terminator
|
|
|
|
// 4) Update total bytes read (across calls)
|
|
alreadyReadBytes += localRead;
|
|
|
|
// 5) Convert the collected bytes to a string
|
|
result = encoding.GetString(buffer, 0, index);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads up to <paramref name="maxLength"/> - 1 characters from <paramref name="stream"/>,
|
|
/// stopping at newline or EOF. Returns the line (including the newline) or null if no bytes were read.
|
|
/// </summary>
|
|
/// <param name="stream">An open readable Stream.</param>
|
|
/// <param name="maxLength">
|
|
/// The maximum number of characters to read (like the buffer size in fgets).
|
|
/// The actual string can have up to maxLength - 1 characters plus a null terminator in C terms.
|
|
/// </param>
|
|
/// <param name="encoding">The character encoding (ASCII, UTF8, etc.).</param>
|
|
/// <returns>A string containing the line, or null if no data was read.</returns>
|
|
public static string Fgets(Stream stream, int maxLength, Encoding encoding)
|
|
{
|
|
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
|
if (!stream.CanRead) throw new ArgumentException("Stream is not readable.", nameof(stream));
|
|
if (maxLength <= 1) throw new ArgumentOutOfRangeException(nameof(maxLength), "Must be at least 2.");
|
|
|
|
// We'll store the raw bytes in a temporary buffer.
|
|
// maxLength - 1 because, in the C analogy, one char is reserved for '\0'.
|
|
// But in C#, we'll just use that as a ceiling.
|
|
byte[] buffer = new byte[maxLength - 1];
|
|
int totalRead = 0;
|
|
|
|
while (true)
|
|
{
|
|
int b = stream.ReadByte();
|
|
if (b == -1)
|
|
{
|
|
// EOF reached, stop reading
|
|
break;
|
|
}
|
|
|
|
// Put this byte into our buffer
|
|
buffer[totalRead++] = (byte)b;
|
|
|
|
// If we hit a newline, stop (mimicking fgets which stops at '\n')
|
|
if (b == '\n')
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If we've filled up the buffer (maxLength - 1 bytes), stop
|
|
if (totalRead >= buffer.Length)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we didn't read anything at all, return null (like fgets returning NULL on EOF)
|
|
if (totalRead == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Convert the bytes we read into a string
|
|
return encoding.GetString(buffer, 0, totalRead);
|
|
}
|
|
|
|
private static readonly uint[] l_aCRC32Table = new uint[]
|
|
{
|
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
|
|
0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
|
|
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
|
|
0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
|
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
|
|
0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
|
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
|
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
|
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
|
|
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
|
|
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
|
|
0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
|
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
|
|
0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
|
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
|
|
0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
|
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
|
|
0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
|
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
|
|
0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
|
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
|
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
|
|
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
|
|
0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
|
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
|
|
0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
|
|
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
|
|
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
|
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
|
|
0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
|
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
|
|
0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
|
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
|
|
0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
|
|
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
|
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
|
|
0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
|
|
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
|
|
0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
|
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
|
|
0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
|
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
|
|
0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
|
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
|
|
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
|
|
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
|
|
0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
|
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
|
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
|
|
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
|
|
0x2d02ef8d
|
|
};
|
|
|
|
public static uint MakeIDFromString(string str)
|
|
{
|
|
if (str == null)
|
|
throw new ArgumentNullException(nameof(str));
|
|
|
|
uint c = 0xffffffff; // shift register contents
|
|
byte[] bytes = Encoding.ASCII.GetBytes(str);
|
|
|
|
foreach (byte b in bytes)
|
|
{
|
|
c = l_aCRC32Table[(c ^ b) & 0xff] ^ (c >> 8);
|
|
}
|
|
|
|
return c ^ 0xffffffff;
|
|
}
|
|
|
|
// load the DXT texture from the byte array.
|
|
// The byte array is loaded after loading the A3DSkin
|
|
public static Texture2D LoadTextureDXT(byte[] ddsBytes)
|
|
{
|
|
// from 72 to 76 is the texture format
|
|
// get the texture format from the ddsBytes
|
|
byte[] formatBytes = new byte[4];
|
|
Array.Copy(ddsBytes, 84, formatBytes, 0, 4);
|
|
|
|
// Convert the byte array to a string (assume it's ASCII-encoded)
|
|
string textureFormat = Encoding.UTF8.GetString(formatBytes);
|
|
|
|
TextureFormat.TryParse(textureFormat, out TextureFormat textureFormatEnum);
|
|
if (textureFormatEnum != TextureFormat.DXT1 && textureFormatEnum != TextureFormat.DXT5)
|
|
{
|
|
Console.WriteLine($"ERROR::Expected DXT1 or DXT5 texture format, but got {textureFormat}. Using DXT5 instead.");
|
|
textureFormatEnum = TextureFormat.DXT5;
|
|
}
|
|
|
|
byte ddsSizeCheck = ddsBytes[4];
|
|
if (ddsSizeCheck != 124)
|
|
{
|
|
Console.WriteLine("ERROR::Invalid DDS DXTn texture. Unable to read"); //this header byte should be 124 for DDS image files
|
|
return Texture2D.whiteTexture;
|
|
}
|
|
|
|
int height = ddsBytes[13] * 256 + ddsBytes[12];
|
|
int width = ddsBytes[17] * 256 + ddsBytes[16];
|
|
|
|
int DDS_HEADER_SIZE = 128;
|
|
byte[] dxtBytes = new byte[ddsBytes.Length - DDS_HEADER_SIZE];
|
|
Buffer.BlockCopy(ddsBytes, DDS_HEADER_SIZE, dxtBytes, 0, ddsBytes.Length - DDS_HEADER_SIZE);
|
|
|
|
Texture2D texture = new Texture2D(width, height, textureFormatEnum, false);
|
|
texture.LoadRawTextureData(dxtBytes);
|
|
texture.Apply();
|
|
|
|
return (texture);
|
|
}
|
|
|
|
public static Texture2D LoadTextureTGA(string filePath)
|
|
{
|
|
if (!File.Exists(filePath)) return Texture2D.whiteTexture;
|
|
|
|
using (var tgaStream = File.OpenRead(filePath))
|
|
using (BinaryReader r = new BinaryReader(tgaStream))
|
|
{
|
|
// Skip some header info we don't care about.
|
|
// Even if we did care, we have to move the stream seek point to the beginning,
|
|
// as the previous method in the workflow left it at the end.
|
|
r.BaseStream.Seek(12, SeekOrigin.Begin);
|
|
|
|
short width = r.ReadInt16();
|
|
short height = r.ReadInt16();
|
|
int bitDepth = r.ReadByte();
|
|
|
|
// Skip a byte of header information we don't care about.
|
|
r.BaseStream.Seek(1, SeekOrigin.Current);
|
|
|
|
Texture2D tex = new Texture2D(width, height);
|
|
Color32[] pulledColors = new Color32[width * height];
|
|
|
|
if (bitDepth == 32)
|
|
{
|
|
for (int i = 0; i < width * height; i++)
|
|
{
|
|
byte red = r.ReadByte();
|
|
byte green = r.ReadByte();
|
|
byte blue = r.ReadByte();
|
|
byte alpha = r.ReadByte();
|
|
|
|
pulledColors[i] = new Color32(blue, green, red, alpha);
|
|
}
|
|
}
|
|
else if (bitDepth == 24)
|
|
{
|
|
for (int i = 0; i < width * height; i++)
|
|
{
|
|
byte red = r.ReadByte();
|
|
byte green = r.ReadByte();
|
|
byte blue = r.ReadByte();
|
|
|
|
pulledColors[i] = new Color32(blue, green, red, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("TGA texture had non 32/24 bit depth.");
|
|
}
|
|
|
|
tex.SetPixels32(pulledColors);
|
|
tex.Apply();
|
|
return tex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves a Texture2D to a PNG file at the specified absolute path.
|
|
/// If a file already exists at the path, returns true without overwriting.
|
|
/// If the file doesn't exist, creates a new PNG file and saves the texture.
|
|
/// </summary>
|
|
/// <param name="texture">The Texture2D to save as PNG</param>
|
|
/// <param name="absolutePath">The absolute file path where to save the PNG</param>
|
|
/// <returns>True if file already exists or was successfully saved, false on error</returns>
|
|
public static bool SaveTexture2DToPNG(Texture2D texture, string absolutePath, bool compressedTexture = false)
|
|
{
|
|
// Check if texture parameter is valid
|
|
if (texture == null)
|
|
{
|
|
Debug.LogError("SaveTexture2DToPNG: Texture is null");
|
|
return false;
|
|
}
|
|
|
|
// Check if the absolute path is valid
|
|
if (string.IsNullOrEmpty(absolutePath))
|
|
{
|
|
Debug.LogError("SaveTexture2DToPNG: Absolute path is null or empty");
|
|
return false;
|
|
}
|
|
|
|
// Check if file already exists
|
|
if (File.Exists(absolutePath))
|
|
{
|
|
return true; // File already exists, return true
|
|
}
|
|
|
|
try
|
|
{
|
|
// Ensure the directory exists
|
|
string directory = Path.GetDirectoryName(absolutePath);
|
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
if (compressedTexture)
|
|
{
|
|
// Create a temporary RenderTexture with same dimensions as source
|
|
RenderTexture tempRT = RenderTexture.GetTemporary(
|
|
texture.width,
|
|
texture.height,
|
|
0,
|
|
RenderTextureFormat.ARGB32
|
|
);
|
|
|
|
// Copy compressed texture to RT, decompressing on GPU
|
|
Graphics.Blit(texture, tempRT);
|
|
|
|
// Create new uncompressed texture
|
|
Texture2D uncompressedTex = new Texture2D(
|
|
texture.width,
|
|
texture.height,
|
|
TextureFormat.ARGB32,
|
|
false
|
|
);
|
|
|
|
// Store active RT and switch to temp
|
|
RenderTexture prevRT = RenderTexture.active;
|
|
RenderTexture.active = tempRT;
|
|
|
|
// Read pixels from RT into uncompressed texture
|
|
uncompressedTex.ReadPixels(new Rect(0, 0, tempRT.width, tempRT.height), 0, 0);
|
|
uncompressedTex.Apply();
|
|
|
|
// Restore previous RT and release temp
|
|
RenderTexture.active = prevRT;
|
|
RenderTexture.ReleaseTemporary(tempRT);
|
|
|
|
// Use uncompressed texture instead of original
|
|
texture = uncompressedTex;
|
|
}
|
|
|
|
// Encode texture to PNG
|
|
byte[] pngBytes = texture.EncodeToPNG();
|
|
|
|
if (pngBytes == null || pngBytes.Length == 0)
|
|
{
|
|
Debug.LogError("SaveTexture2DToPNG: Failed to encode texture to PNG");
|
|
return false;
|
|
}
|
|
|
|
// Write PNG bytes to file
|
|
File.WriteAllBytes(absolutePath, pngBytes);
|
|
|
|
Debug.Log($"SaveTexture2DToPNG: Successfully saved texture to {absolutePath}");
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"SaveTexture2DToPNG: Error saving texture to {absolutePath}: {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is for when we load a byte buffer for string. \
|
|
/// We have to remove the empty byte at the end so the string can be decoded correctly.
|
|
/// </summary>
|
|
/// <param name="buffer"></param>
|
|
public static void RemoveEmptyByte(ref byte[] buffer)
|
|
{
|
|
int firstEmptyByte = Array.IndexOf(buffer, (byte)0);
|
|
if (firstEmptyByte != -1)
|
|
{
|
|
Array.Resize(ref buffer, firstEmptyByte);
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct StringStruct
|
|
{
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2048)]
|
|
public char[] value;
|
|
} |