using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace IgniteEngine.IO
{
///
/// Class to load and access SHN files.
///
public class SHNFile : IDisposable
{
///
/// Objects representing the contents of loaded SHN files.
///
private static readonly Dictionary FileObjects = new Dictionary();
///
/// Tries to get the objects for a file.
///
/// The type of objects.
/// The name of the file.
/// The objects.
/// The file's objects.
public static bool TryGetObjects(string name, out ObjectCollection objects)
{
objects = new ObjectCollection();
if (!FileObjects.ContainsKey(name))
{
return false;
}
objects = (ObjectCollection) FileObjects[name];
return true;
}
///
/// The path of the file.
///
private readonly string fileName;
///
/// The underlying DataTable, which holds all of the file's
/// data.
///
private readonly DataTable table;
///
/// Creates a new instance of the class.
///
private SHNFile(string path)
{
fileName = path;
table = new DataTable(Path.GetFileName(path));
PopulateTable();
}
///
/// Loads all, or specified SHN files from a folder, if they are defined.
///
/// The folder to load the files from.
/// Specific files to load. If set to null, all files in the folder will be loaded.
public static void LoadFromFolder(string directory, params string[] files)
{
if (!Directory.Exists(directory))
{
Debug.LogAssert($"The directory '{directory}' could not be found.");
return;
}
// Load file definitions first.
var fileDefinitions = System.Reflection.Assembly.GetExecutingAssembly().GetTypes().Where(type => type.GetCustomAttributes(typeof(Definition), true).Length > 0).ToArray();
if (fileDefinitions.Length <= 0)
{
Debug.LogAssert("No file definitions were found.");
return;
}
// Converts all file names in the array to a full file path,
// if the length of the array is greater than 0.
for (var i = 0; i < files.Length; i++)
{
files[i] = Path.Combine(directory, files[i]);
}
var fileNames = files.Length > 0 ? files : Directory.GetFiles(directory, "*.shn");
Parallel.ForEach(fileNames, fileName =>
{
var shortName = Path.GetFileNameWithoutExtension(fileName);
var definition = fileDefinitions.FirstOrDefault(def => def.Name == shortName);
if (definition != null)
{
var genericClass = typeof(ObjectCollection<>);
var collection = genericClass.MakeGenericType(definition);
var created = (dynamic) Activator.CreateInstance(collection);
FileObjects.Remove(shortName);
FileObjects.Add(shortName, created);
using (var file = new SHNFile(fileName))
using (var reader = new DataTableReader(file.table))
{
while (reader.Read())
{
created.Add(reader);
}
}
}
});
}
///
/// Decrypts an array of bytes.
///
/// The byte array being decrypted.
/// The length of the byte array.
/// The result of the decryption.
private static void DecryptBuffer(byte[] input, int length, out byte[] output)
{
output = input;
var xor = (byte)length;
for (var i = length - 1; i >= 0; i--)
{
output[i] ^= xor;
var nextXor = (byte)i;
nextXor &= 0xF;
nextXor += 0x55;
nextXor ^= (byte)(i * 0xB);
nextXor ^= xor;
nextXor ^= 0xAA;
xor = nextXor;
}
}
///
/// Gets the data type for a column.
///
/// The byte value in the file.
/// The column's data type.
private static Type GetColumnDataType(uint type)
{
switch (type)
{
case 1:
case 12:
case 16:
return typeof(byte);
case 2:
return typeof(ushort);
case 3:
case 11:
case 18:
case 27:
return typeof(uint);
case 5:
return typeof(float);
case 13:
case 21:
return typeof(short);
case 20:
return typeof(sbyte);
case 22:
return typeof(int);
case 9:
case 24:
case 26:
return typeof(string);
default:
return typeof(object);
}
}
///
/// Disposes the file handle.
///
public void Dispose()
{
table.Dispose();
}
///
/// Populates the SHNFile's table with the file's data.
///
private void PopulateTable()
{
if (!File.Exists(fileName))
{
Debug.LogAssert($"The file '{fileName}' could not be found.");
return;
}
byte[] buffer;
using (var file = File.OpenRead(fileName))
using (var reader = new BinaryReader(file))
{
reader.ReadBytes(32); // Crypt header, unused.
var length = reader.ReadInt32();
var bytes = reader.ReadBytes(length - 36);
DecryptBuffer(bytes, bytes.Length, out buffer);
}
using (var stream = new MemoryStream(buffer))
using (var reader = new BinaryReader(stream))
{
reader.ReadBytes(4); // Header, unused.
var rowCount = reader.ReadUInt32();
var defaultRowLength = reader.ReadUInt32(); // Default row length, unused.
var columnCount = reader.ReadUInt32();
var columnTypes = new uint[columnCount];
var columnLengths = new int[columnCount];
var unkColumnCount = 0;
for (var i = 0; i < columnCount; i++)
{
var name = reader.ReadString(48);
var type = reader.ReadUInt32();
var length = reader.ReadInt32();
var column = new DataColumn(name, GetColumnDataType(type));
if (name.Trim().Length < 2)
{
column.ColumnName = $"UnkCol{unkColumnCount++}";
}
columnTypes[i] = type;
columnLengths[i] = length;
table.Columns.Add(column);
}
var row = new object[columnCount];
for (var i = 0; i < rowCount; i++)
{
var rowLength = reader.ReadUInt16();
for (var j = 0; j < columnCount; j++)
{
switch (columnTypes[j])
{
case 1:
case 12:
case 16:
row[j] = reader.ReadByte();
break;
case 2:
row[j] = reader.ReadUInt16();
break;
case 3:
case 11:
case 18:
case 27:
row[j] = reader.ReadUInt32();
break;
case 5:
row[j] = reader.ReadSingle();
break;
case 9:
case 24:
row[j] = reader.ReadString(columnLengths[j]);
break;
case 13:
case 21:
row[j] = reader.ReadInt16();
break;
case 20:
row[j] = reader.ReadSByte();
break;
case 22:
row[j] = reader.ReadInt32();
break;
case 26:
row[j] = reader.ReadString(rowLength - (int) defaultRowLength + 1);
break;
case 29:
var bytes = reader.ReadBytes(columnLengths[j]);
var val1 = BitConverter.ToUInt32(bytes, 0);
var val2 = BitConverter.ToUInt32(bytes, 4);
row[j] = new Tuple(val1, val2);
break;
}
}
table.Rows.Add(row);
}
}
}
}
}