diff --git a/MinecraftUSkinEditor/Classes/FileTypes/GRFFile.cs b/MinecraftUSkinEditor/Classes/FileTypes/GRFFile.cs
new file mode 100644
index 00000000..7034aadb
--- /dev/null
+++ b/MinecraftUSkinEditor/Classes/FileTypes/GRFFile.cs
@@ -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,
+ ///
+ /// custom enum value
+ ///
+ IsWolrdGrf = 0x80,
+ }
+
+ public class GRFTag
+ {
+ private GRFTag _parent = null;
+ private SortedDictionary _parameters = new SortedDictionary();
+
+ ///
+ /// Contains all valid Parameter names
+ ///
+ 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 Parameters => _parameters;
+ public List Tags { get; set; } = new List();
+
+ public GRFTag(string name, GRFTag parent)
+ {
+ Name = name;
+ _parent = parent;
+ }
+ }
+
+ public GRFFile(eCompressionType compressionType, int crc)
+ {
+ _compressionType = compressionType;
+ _crc = crc;
+ }
+
+ }
+}
diff --git a/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileReader.cs b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileReader.cs
new file mode 100644
index 00000000..3283254c
--- /dev/null
+++ b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileReader.cs
@@ -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 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.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(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 ReadItemList(Stream stream, int count, GRFFile.GRFTag parent)
+ {
+ List tags = new List();
+ 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(GetTagName(stream), ReadInt(stream));
+ }
+
+ internal (string, string) GetTagNameAndValue(Stream stream)
+ {
+ return new ValueTuple(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);
+ }
+
+ }
+}
diff --git a/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileWriter.cs b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileWriter.cs
new file mode 100644
index 00000000..99708f17
--- /dev/null
+++ b/MinecraftUSkinEditor/Classes/IO/GRF/GRFFileWriter.cs
@@ -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 tagNames = new List();
+ 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 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);
+ }
+
+
+ }
+}
diff --git a/MinecraftUSkinEditor/Classes/Utils/RLE.cs b/MinecraftUSkinEditor/Classes/Utils/RLE.cs
new file mode 100644
index 00000000..fd890bc9
--- /dev/null
+++ b/MinecraftUSkinEditor/Classes/Utils/RLE.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+namespace PckStudio.Classes.Utils;
+
+///! Credits
+///!
+
+///
+/// Provides the RLE codec for any integer data type.
+///
+/// The data's type. Must be an integer type or an ArgumentException will be thrown
+static class RLE where T : struct, IConvertible
+{
+ ///
+ /// This is the marker that identifies a compressed run
+ ///
+ private static T rleMarker;
+
+ ///
+ /// A run can be at most as long as the marker - 1
+ ///
+ private static ulong maxLength;
+
+ static RLE()
+ {
+ GetMaxValues();
+ }
+
+ ///
+ /// RLE-Encodes a data set.
+ ///
+ /// The data to encode
+ /// Encoded data
+ public static IEnumerable Encode(IEnumerable 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;
+ }
+
+ ///
+ /// Decodes RLE-encoded data
+ ///
+ /// RLE-encoded data
+ /// The original data
+ public static IEnumerable Decode(IEnumerable 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 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");
+ }
+ }
+}
\ No newline at end of file