Add GRFFile class and GRFFileReader

This commit is contained in:
miku-666
2022-06-30 17:53:40 +02:00
parent 5aead0d339
commit e7ee1f2027
4 changed files with 721 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PckStudio.Classes.FileTypes
{
public class GRFFile
{
public GRFTag RootTag = null;
public int Crc => _crc;
public eCompressionType CompressionType => _compressionType;
private int _crc = 0;
private eCompressionType _compressionType = eCompressionType.None;
[Flags]
public enum eCompressionType : byte
{
None = 0,
Zlib = 1,
ZlibRle = 2,
/// <summary>
/// custom enum value
/// </summary>
IsWolrdGrf = 0x80,
}
public class GRFTag
{
private GRFTag _parent = null;
private SortedDictionary<string, string> _parameters = new SortedDictionary<string, string>();
/// <summary>
/// Contains all valid Parameter names
/// </summary>
public static readonly string[] ValidParameters = new string[]
{
"plus_x",
"minus_x",
"plus_z",
"minus_z",
"omni_plus_x",
"omni_minus_x",
"omni_plus_z",
"omni_minus_z",
"none",
"plus_y",
"minus_y",
"plus_x",
"minus_x",
"plus_z",
"minus_z",
"descriptionName",
"promptName",
"dataTag",
"enchantmentId",
"enchantmentLevel",
"itemId",
"quantity",
"auxValue",
"slot",
"name",
"food",
"health",
"blockId",
"useCoords",
"seed",
"flatworld",
"filename",
"rot",
"data",
"block",
"entity",
"facing",
"edgeBlock",
"fillBlock",
"skipAir",
"x",
"x0",
"x1",
"y",
"y0",
"y1",
"z",
"z0",
"z1",
"chunkX",
"chunkZ",
"yRot",
"xRot",
"spawnX",
"spawnY",
"spawnZ",
"orientation",
"dimension",
"topblockId",
"biomeId",
"feature",
"minCount",
"maxCount",
"weight",
"id",
"probability",
"method",
"hasBeenInCreative",
"cloudHeight",
"fogDistance",
"dayTime",
"target",
"speed",
"dir",
"type",
"pass",
"for",
"random",
"blockAux",
"size",
"scale",
"freq",
"func",
"upper",
"lower",
"dY",
"thickness",
"points",
"holeSize",
"variant",
"startHeight",
"pattern",
"colour",
"primary",
"laps",
"liftForceModifier",
"staticLift",
"targetHeight",
"speedBoost",
"boostDirection",
"condition_type",
"condition_value_0",
"condition_value_1",
"beam_length",
};
public static readonly string[] ValidGameRules = new string[]
{
"MapOptions",
"ApplySchematic",
"GenerateStructure",
"GenerateBox",
"PlaceBlock",
"PlaceContainer",
"PlaceSpawner",
"BiomeOverride",
"StartFeature",
"AddItem",
"AddEnchantment",
"WeighedTreasureItem",
"RandomItemSet",
"DistributeItems",
"WorldPosition",
"LevelRules",
"NamedArea",
"ActiveChunkArea",
"TargetArea",
"ScoreRing",
"ThermalArea",
"PlayerBoundsVolume",
"Killbox",
"BlockLayer",
"UseBlock",
"CollectItem",
"CompleteAll",
"UpdatePlayer",
"OnGameStartSpawnPositions",
"OnInitialiseWorld",
"SpawnPositionSet",
"PopulateContainer",
"DegradationSequence",
"RandomDissolveDegrade",
"DirectionalDegrade",
"GrantPermissions",
"AllowIn",
"LayerGeneration",
"LayerAsset",
"AnyCombinationOf",
"CombinationDefinition",
"Variations",
"BlockDef",
"LayerSize",
"UniformSize",
"RandomizeSize",
"LinearBlendSize",
"LayerShape",
"BasicShape",
"StarShape",
"PatchyShape",
"RingShape",
"SpiralShape",
"LayerFill",
"BasicLayerFill",
"CurvedLayerFill",
"WarpedLayerFill",
"LayerTheme",
"NullTheme",
"FilterTheme",
"ShaftsTheme",
"BasicPatchesTheme",
"BlockStackTheme",
"RainbowTheme",
"TerracottaTheme",
"FunctionPatchesTheme",
"SimplePatchesTheme",
"CarpetTrapTheme",
"MushroomBlockTheme",
"TextureTheme",
"SchematicTheme",
"BlockCollisionException",
"Powerup",
"Checkpoint",
"CustomBeacon",
"ActiveViewArea",
};
public string Name { get; set; } = string.Empty;
public GRFTag Parent => _parent;
public SortedDictionary<string, string> Parameters => _parameters;
public List<GRFTag> Tags { get; set; } = new List<GRFTag>();
public GRFTag(string name, GRFTag parent)
{
Name = name;
_parent = parent;
}
}
public GRFFile(eCompressionType compressionType, int crc)
{
_compressionType = compressionType;
_crc = crc;
}
}
}

View File

@@ -0,0 +1,168 @@
using PckStudio.Classes.FileTypes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using PckStudio.Classes.Utils;
namespace PckStudio.Classes.IO.GRF
{
internal class GRFFileReader
{
internal List<string> TagNames;
internal GRFFile _file;
public static GRFFile Read(Stream stream)
{
return new GRFFileReader().read(stream);
}
private GRFFile read(Stream stream)
{
stream = ReadHeader(stream);
ReadTagNames(stream);
ReadGRFTags(stream);
return _file;
}
private Stream ReadHeader(Stream stream)
{
int x = ReadShort(stream);
if (((x >> 31) | x) == 0)
{
ReadBytes(stream, 14); // 14 bools ?...
return stream;
}
GRFFile.eCompressionType compression_type = (GRFFile.eCompressionType)stream.ReadByte();
int crc = ReadInt(stream);
int byte1 = stream.ReadByte();
int byte2 = stream.ReadByte();
int byte3 = stream.ReadByte();
int byte4 = stream.ReadByte();
if (byte4 > 0)
{
compression_type = (GRFFile.eCompressionType)byte4;
// Custom enum
compression_type &= GRFFile.eCompressionType.IsWolrdGrf;
}
_file = new GRFFile(compression_type, crc);
if (compression_type == GRFFile.eCompressionType.None && byte4 == 0)
return stream;
int buf_size = ReadInt(stream);
if (byte4 != 0)
{
stream = new MemoryStream(ReadBytes(stream, buf_size));
buf_size = ReadInt(stream);
}
else
{
ReadInt(stream); // ignored cuz rest of data is compressed
}
stream = DecompressZLX(stream);
if (compression_type > GRFFile.eCompressionType.Zlib)
{
byte[] data = RLE<byte>.Decode(ReadBytes(stream, buf_size)).ToArray();
stream = new MemoryStream(data);
}
if (byte4 != 0)
ReadBytes(stream, 23);
return stream;
}
private Stream DecompressZLX(Stream compressedStream)
{
Stream outputstream = new MemoryStream();
using (var inputStream = new InflaterInputStream(compressedStream))
{
inputStream.CopyTo(outputstream);
outputstream.Position = 0;
};
return outputstream;
}
private void ReadTagNames(Stream stream)
{
int name_count = ReadInt(stream);
TagNames = new List<string>(name_count);
for (int i = 0; i < name_count; i++)
TagNames.Add(ReadString(stream));
}
private void ReadGRFTags(Stream stream)
{
var NameAndCount = GetTagNameAndDetailCount(stream);
_file.RootTag = new GRFFile.GRFTag(NameAndCount.Item1, null);
_file.RootTag.Tags = ReadItemList(stream, NameAndCount.Item2, _file.RootTag);
}
internal List<GRFFile.GRFTag> ReadItemList(Stream stream, int count, GRFFile.GRFTag parent)
{
List<GRFFile.GRFTag> tags = new List<GRFFile.GRFTag>();
for (int i = 0; i < count; i++)
{
var valuePair = GetTagNameAndDetailCount(stream);
var tag = new GRFFile.GRFTag(valuePair.Item1, parent);
for (var j = 0; j < valuePair.Item2; j++)
{
var tuple = GetTagNameAndValue(stream);
tag.Parameters.Add(tuple.Item1, tuple.Item2);
}
tag.Tags = ReadItemList(stream, ReadInt(stream), tag);
tags.Add(tag);
}
return tags;
}
internal string GetTagName(Stream stream) => TagNames[ReadInt(stream)];
internal (string, int) GetTagNameAndDetailCount(Stream stream)
{
return new ValueTuple<string, int>(GetTagName(stream), ReadInt(stream));
}
internal (string, string) GetTagNameAndValue(Stream stream)
{
return new ValueTuple<string, string>(GetTagName(stream), ReadString(stream));
}
internal byte[] ReadBytes(Stream stream, int count)
{
byte[] buffer = new byte[count];
stream.Read(buffer, 0, count);
return buffer;
}
internal int ReadInt(Stream stream)
{
byte[] buffer = ReadBytes(stream, 4);
if (BitConverter.IsLittleEndian)
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
internal short ReadShort(Stream stream)
{
byte[] buffer = ReadBytes(stream, 2);
if (BitConverter.IsLittleEndian)
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
internal string ReadString(Stream stream)
{
short stringLength = ReadShort(stream);
byte[] buffer = ReadBytes(stream, stringLength);
return Encoding.UTF8.GetString(buffer);
}
}
}

View File

@@ -0,0 +1,92 @@
using PckStudio.Classes.FileTypes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PckStudio.Classes.IO.GRF
{
public class GRFFileWriter
{
internal GRFFile _grfFile;
public static void Write(Stream stream, GRFFile grfFile)
{
new GRFFileWriter(grfFile).write(stream);
}
private GRFFileWriter(GRFFile grfFile)
{
_grfFile = grfFile;
}
private void write(Stream stream)
{
BuildHeader(stream);
WriteTagNames(stream);
}
private void BuildHeader(Stream stream)
{
WriteShort(stream, 1); // (x >> 31 | x) == 1
stream.WriteByte((byte)_grfFile.CompressionType);
WriteInt(stream, _grfFile.Crc);
stream.WriteByte(0);
stream.WriteByte(0);
stream.WriteByte(0);
stream.WriteByte(0);
}
private void WriteTagNames(Stream stream)
{
List<string> tagNames = new List<string>();
GatherTagNames(_grfFile.RootTag, tagNames);
WriteInt(stream, tagNames.Count);
foreach (var s in tagNames)
{
WriteString(stream, s);
Console.WriteLine(s);
}
}
private void GatherTagNames(GRFFile.GRFTag tag, List<string> l)
{
if (!l.Contains(tag.Name)) l.Add(tag.Name);
foreach (var subTag in tag.Tags)
GatherTagNames(subTag, l);
foreach (var subTag in tag.Parameters)
if (!l.Contains(subTag.Key)) l.Add(subTag.Key);
}
internal void WriteInt(Stream stream, int value)
{
byte[] bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
WriteBytes(stream, bytes);
}
internal void WriteShort(Stream stream, short value)
{
byte[] bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
WriteBytes(stream, bytes);
}
internal void WriteString(Stream stream, string s)
{
WriteShort(stream, (short)s.Length);
WriteBytes(stream, Encoding.UTF8.GetBytes(s));
}
internal void WriteBytes(Stream stream, byte[] bytes)
{
stream.Write(bytes, 0, bytes.Length);
}
}
}

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace PckStudio.Classes.Utils;
///! Credits
///! <see cref="https://nicoschertler.wordpress.com/2014/06/04/generic-run-length-encoding-rle-for-c/"/>
/// <summary>
/// Provides the RLE codec for any integer data type.
/// </summary>
/// <typeparam name="T">The data's type. Must be an integer type or an ArgumentException will be thrown</typeparam>
static class RLE<T> where T : struct, IConvertible
{
/// <summary>
/// This is the marker that identifies a compressed run
/// </summary>
private static T rleMarker;
/// <summary>
/// A run can be at most as long as the marker - 1
/// </summary>
private static ulong maxLength;
static RLE()
{
GetMaxValues();
}
/// <summary>
/// RLE-Encodes a data set.
/// </summary>
/// <param name="data">The data to encode</param>
/// <returns>Encoded data</returns>
public static IEnumerable<T> Encode(IEnumerable<T> data)
{
var enumerator = data.GetEnumerator();
if (!enumerator.MoveNext())
yield break;
var firstRunValue = enumerator.Current;
ulong runLength = 1;
while (enumerator.MoveNext())
{
var currentValue = enumerator.Current;
// if the current value is the value of the current run, don't yield anything,
// just extend the run
if (currentValue.Equals(firstRunValue))
runLength++;
else
{
// the current value is different from the current run
// yield what we have so far
foreach (var item in MakeRun(firstRunValue, runLength))
yield return item;
// and reset the run
firstRunValue = currentValue;
runLength = 1;
}
// if there are very many identical values, don't exceed the max length
if (runLength > maxLength)
{
foreach (var item in MakeRun(firstRunValue, maxLength))
yield return item;
runLength -= maxLength;
}
}
//yield everything that has been buffered
foreach (var item in MakeRun(firstRunValue, runLength))
yield return item;
}
/// <summary>
/// Decodes RLE-encoded data
/// </summary>
/// <param name="data">RLE-encoded data</param>
/// <returns>The original data</returns>
public static IEnumerable<T> Decode(IEnumerable<T> data)
{
var enumerator = data.GetEnumerator();
if (!enumerator.MoveNext())
yield break;
do
{
var value = enumerator.Current;
if (!value.Equals(rleMarker))
{
//an ordinary value
yield return value;
}
else
{
//might be flag or escape
//examine the next value
if (!enumerator.MoveNext())
throw new ArgumentException("The provided data is not properly encoded.");
if (enumerator.Current.Equals(rleMarker))
{
//escaped value
yield return value;
}
else
{
//rle marker
var length = enumerator.Current.ToInt64(CultureInfo.InvariantCulture);
if (!enumerator.MoveNext())
throw new ArgumentException("The provided data is not properly encoded.");
var val = enumerator.Current;
for (var j = 0; j < length+1; ++j)
yield return val;
}
}
}
while (enumerator.MoveNext());
}
private static IEnumerable<T> MakeRun(T value, ulong length)
{
if ((length <= 3 && !value.Equals(rleMarker)) || length <= 1)
{
//don't compress this run, it is just too small
for (ulong i = 0; i < length; ++i)
{
if (value.Equals(rleMarker))
yield return rleMarker;
yield return value;
}
}
else
{
//compressed run
yield return rleMarker;
yield return (T)(dynamic)length;
yield return value;
}
}
private static void GetMaxValues()
{
object maxValue = default(T);
TypeCode typeCode = Type.GetTypeCode(typeof(T));
switch (typeCode)
{
case TypeCode.Byte:
{
var limit = byte.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.Char:
{
var limit = char.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.Int16:
{
var limit = short.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.Int32:
{
var limit = int.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.Int64:
{
var limit = long.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.SByte:
{
var limit = sbyte.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.UInt16:
{
var limit = ushort.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.UInt32:
{
var limit = uint.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
case TypeCode.UInt64:
{
var limit = ulong.MaxValue;
rleMarker = __refvalue(__makeref(limit), T);
maxLength = (ulong)(limit - 1);
break;
}
default:
throw new ArgumentException("The provided type parameter is not an integer type");
}
}
}