Move GRF processing to OMI lib

This commit is contained in:
miku-666
2023-03-10 16:36:05 +01:00
parent c4914d7f64
commit 05b2dfc1e3
13 changed files with 135 additions and 1883 deletions

View File

@@ -1,283 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PckStudio.Classes.FileTypes
{
public class GRFFile
{
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 readonly GameRule Root = null;
public int Crc => _crc;
public bool IsWorld => _isWorld;
public CompressionType CompressionLevel => _compressionLevel;
private int _crc = 0;
private bool _isWorld = false;
private CompressionType _compressionLevel = CompressionType.None;
public enum CompressionType : byte
{
None = 0,
Compressed = 1,
CompressedRle = 2,
CompressedRleCrc = 3,
}
/// <summary>
/// Initializes a new GRFFile as a non-world grf file
/// </summary>
public GRFFile() : this(-1, false)
{}
public GRFFile(int crc, bool isWolrd) : this(crc, isWolrd, CompressionType.None)
{}
public GRFFile(int crc, bool isWolrd, CompressionType compressionLevel)
{
Root = new GameRule("__ROOT__", null);
_compressionLevel = compressionLevel;
_crc = crc;
_isWorld = isWolrd;
}
public class GameRule
{
/// <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 string Name { get; set; } = string.Empty;
public GameRule Parent { get; } = null;
public Dictionary<string, string> Parameters { get; } = new Dictionary<string, string>();
public List<GameRule> ChildRules { get; } = new List<GameRule>();
public GameRule(string name, GameRule parent)
{
Name = name;
Parent = parent;
}
public GameRule AddRule(string gameRuleName) => AddRule(gameRuleName, false);
/// <summary>Adds a new gamerule</summary>
/// <param name="gameRuleName">Game rule to add</param>
/// <param name="validate">Wether to check the given game rule</param>
/// <returns>The Added GRFTag</returns>
public GameRule AddRule(string gameRuleName, bool validate)
{
if (validate && !ValidGameRules.Contains(gameRuleName)) return null;
var tag = new GameRule(gameRuleName, this);
ChildRules.Add(tag);
return tag;
}
public GameRule AddRule(string gameRuleName, params KeyValuePair<string,string>[] parameters)
{
var tag = AddRule(gameRuleName); // should never return null unless its called with the validate bool set to true
foreach(var param in parameters)
{
tag.Parameters[param.Key] = param.Value;
}
return tag;
}
}
public void AddGameRules(IEnumerable<GameRule> gameRules) => Root.ChildRules.AddRange(gameRules);
public GameRule AddRule(string gameRuleName)
=> AddRule(gameRuleName, false);
public GameRule AddRule(string gameRuleName, bool validate)
=> Root.AddRule(gameRuleName, validate);
public GameRule AddRule(string gameRuleName, params KeyValuePair<string, string>[] parameters)
=> Root.AddRule(gameRuleName, parameters);
}
}

View File

@@ -1,140 +0,0 @@
using PckStudio.Classes.FileTypes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using PckStudio.Classes.Utils;
using System.Diagnostics;
namespace PckStudio.Classes.IO.GRF
{
internal class GRFFileReader : StreamDataReader<GRFFile>
{
private IList<string> StringLookUpTable;
private GRFFile _file;
public static GRFFile Read(Stream stream)
{
return new GRFFileReader().ReadFromStream(stream);
}
private GRFFileReader() : base(false)
{ }
protected override GRFFile ReadFromStream(Stream stream)
{
stream = ReadHeader(stream);
ReadBody(stream);
return _file;
}
private Stream ReadHeader(Stream stream)
{
int x = ReadShort(stream);
if (((x >> 31) | x) == 0)
{
// 14 bools ?...
ReadBytes(stream, 14);
return stream;
}
GRFFile.CompressionType compression_type = (GRFFile.CompressionType)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.CompressionType)byte4;
}
_file = new GRFFile(crc, byte4 > 0, compression_type);
if (compression_type == GRFFile.CompressionType.None && byte4 == 0)
return stream;
int buf_size = ReadInt(stream);
var new_stream = stream;
if (byte4 != 0)
{
new_stream = new MemoryStream(ReadBytes(stream, buf_size));
buf_size = ReadInt(new_stream);
}
else
{
ReadInt(stream); // ignored cuz rest of data is compressed
}
var decompressed_stream = DecompressZLX(new_stream);
new_stream.Dispose();
if (compression_type > GRFFile.CompressionType.Compressed)
{
byte[] data = ReadBytes(decompressed_stream, buf_size);
byte[] decoded_data = RLE<byte>.Decode(data).ToArray();
decompressed_stream.Dispose();
decompressed_stream = new MemoryStream(decoded_data);
}
if (byte4 != 0)
ReadBytes(decompressed_stream, 23);
return decompressed_stream;
}
private void ReadBody(Stream stream)
{
ReadStringLookUpTable(stream);
string Name = GetString(stream);
Debug.WriteLine(string.Format("Root Name: {0}", Name), category: nameof(GRFFile));
ReadGameRuleHierarchy(stream, _file.Root);
}
private Stream DecompressZLX(Stream compressedStream)
{
Stream outputstream = new MemoryStream();
using (var inputStream = new InflaterInputStream(compressedStream))
{
inputStream.IsStreamOwner = false;
inputStream.CopyTo(outputstream);
outputstream.Position = 0;
};
return outputstream;
}
private void ReadStringLookUpTable(Stream stream)
{
int tableSize = ReadInt(stream);
StringLookUpTable = new List<string>(tableSize);
for (int i = 0; i < tableSize; i++)
{
string s = ReadString(stream);
StringLookUpTable.Add(s);
}
}
private void ReadGameRuleHierarchy(Stream stream, GRFFile.GameRule parentRule)
{
_ = parentRule ?? throw new ArgumentNullException(nameof(parentRule));
int count = ReadInt(stream);
for (int i = 0; i < count; i++)
{
(string Name, int Count) parameter = (GetString(stream), ReadInt(stream));
var rule = parentRule.AddRule(parameter.Name);
for (int j = 0; j < parameter.Count; j++)
{
rule.Parameters.Add(GetString(stream), ReadString(stream));
}
ReadGameRuleHierarchy(stream, rule);
}
}
private string GetString(Stream stream) => StringLookUpTable[ReadInt(stream)];
private string ReadString(Stream stream)
{
short stringLength = ReadShort(stream);
return ReadString(stream, stringLength, Encoding.ASCII);
}
}
}

View File

@@ -1,159 +0,0 @@
using PckStudio.Classes.FileTypes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using PckStudio.Classes.Utils.grf;
using PckStudio.Classes.Utils;
namespace PckStudio.Classes.IO.GRF
{
internal class GRFFileWriter : StreamDataWriter
{
private readonly GRFFile _grfFile;
private List<string> StringLookUpTable;
private GRFFile.CompressionType _compressionType;
public static void Write(in Stream stream, GRFFile grfFile, GRFFile.CompressionType compressionType)
{
new GRFFileWriter(grfFile, compressionType).WriteToStream(stream);
}
private GRFFileWriter(GRFFile grfFile, GRFFile.CompressionType compressionType) : base(false)
{
_compressionType = compressionType;
if (grfFile.IsWorld)
throw new NotImplementedException("World grf saving is currently unsupported");
_grfFile = grfFile;
StringLookUpTable = new List<string>();
PrepareLookUpTable(_grfFile.Root, StringLookUpTable);
}
private void PrepareLookUpTable(GRFFile.GameRule rule, List<string> LUT)
{
if (!LUT.Contains(rule.Name)) LUT.Add(rule.Name);
rule.ChildRules.ForEach(subRule => PrepareLookUpTable(subRule, LUT));
foreach (var param in rule.Parameters)
if (!LUT.Contains(param.Key)) LUT.Add(param.Key);
}
protected override void WriteToStream(Stream stream)
{
WriteHeader(stream);
using (var uncompressed_stream = new MemoryStream())
{
WriteBody(uncompressed_stream);
HandleCompression(stream, uncompressed_stream);
}
}
private void HandleCompression(Stream destinationStream, MemoryStream sourceStream)
{
byte[] _buffer = sourceStream.ToArray();
int _original_length = _buffer.Length;
if (_compressionType >= GRFFile.CompressionType.CompressedRle)
_buffer = CompressRle(_buffer);
if (_compressionType >= GRFFile.CompressionType.Compressed)
{
_buffer = CompressZib(_buffer);
WriteInt(destinationStream, _original_length);
WriteInt(destinationStream, _buffer.Length);
}
if (_compressionType >= GRFFile.CompressionType.CompressedRleCrc)
MakeAndWriteCrc(destinationStream, _buffer);
WriteBytes(destinationStream, _buffer);
return;
}
private byte[] CompressZib(byte[] buffer)
{
byte[] result;
var outputStream = new MemoryStream(); // Stream gets Disposed in DeflaterOutputStream
using (var deflateStream = new DeflaterOutputStream(outputStream))
{
WriteBytes(deflateStream, buffer);
deflateStream.Flush();
deflateStream.Finish();
outputStream.Position = 0;
result = outputStream.ToArray();
}
return result;
}
private byte[] CompressRle(byte[] buffer) => RLE<byte>.Encode(buffer).ToArray();
private void MakeAndWriteCrc(Stream stream, byte[] data)
{
uint crc = CRC32.CRC(data);
if (crc != _grfFile.Crc) // no writting needed if there is no change
{
stream.Position = 3;
WriteUInt(stream, crc);
stream.Seek(0, SeekOrigin.End); // reset to the end of the stream
}
}
private void WriteHeader(Stream stream)
{
WriteShort(stream, 1);
if (_compressionType < GRFFile.CompressionType.None ||
_compressionType > GRFFile.CompressionType.CompressedRleCrc)
throw new ArgumentException(nameof(_compressionType));
stream.WriteByte((byte)_compressionType);
WriteInt(stream, _grfFile.Crc);
stream.WriteByte(0);
stream.WriteByte(0);
stream.WriteByte(0);
stream.WriteByte(0); // <- used in world grf
}
private void WriteBody(Stream stream)
{
WriteTagLookUpTable(stream);
SetString(stream, _grfFile.Root.Name);
WriteInt(stream, _grfFile.Root.ChildRules.Count);
WriteGameRuleHierarchy(stream, _grfFile.Root);
}
private void WriteTagLookUpTable(Stream stream)
{
WriteInt(stream, StringLookUpTable.Count);
StringLookUpTable.ForEach( s => WriteString(stream, s) );
}
private void WriteGameRuleHierarchy(Stream stream, GRFFile.GameRule rule)
{
foreach (var subRule in rule.ChildRules)
{
SetString(stream, subRule.Name);
WriteInt(stream, subRule.Parameters.Count);
foreach (var param in subRule.Parameters) WriteParameter(stream, param);
WriteInt(stream, subRule.ChildRules.Count);
WriteGameRuleHierarchy(stream, subRule);
}
}
private void WriteParameter(Stream stream, KeyValuePair<string, string> param)
{
SetString(stream, param.Key);
WriteString(stream, param.Value);
}
private void SetString(Stream stream, string s)
{
int i = StringLookUpTable.IndexOf(s);
if (i == -1) throw new Exception(nameof(s));
WriteInt(stream, i);
}
private void WriteString(Stream stream, string s)
{
WriteShort(stream, (short)s.Length);
WriteString(stream, s, Encoding.ASCII);
}
}
}

View File

@@ -1,212 +0,0 @@
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++)
{
yield return value.Equals(rleMarker) ? rleMarker : value;
}
}
else
{
//compressed run
yield return rleMarker;
yield return (T)(dynamic)(length-1);
yield return value;
}
}
private static void GetMaxValues()
{
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");
}
}
}

View File

@@ -1,69 +0,0 @@
namespace PckStudio.Classes.Utils.grf
{
public class CRC32
{
static readonly private byte[] offsets =
{
0x05, 0x06, 0x07, 0x03,
0x04, 0x05, 0x06, 0x07,
0x01, 0x02, 0x06, 0x01,
0x01, 0x02, 0x03, 0x04,
0x05, 0x05, 0x06, 0x07,
0x03, 0x04, 0x05, 0x06,
0x07, 0x01, 0x02, 0x06,
0x01, 0x01, 0x02, 0x03,
0x04, 0x05, 0x05, 0x06,
0x07, 0x03, 0x04, 0x05,
0x06, 0x07, 0x07, 0x05,
0x04, 0x07, 0x05, 0x04,
0x07, 0x05, 0x04, 0x07,
0x06, 0x05, 0x04, 0x03,
};
static private uint[] CRCTable;
static private bool hasCRCTable = false;
static void MakeCRCTable()
{
const uint val = 0xedb88320;
CRCTable = new uint[256];
for (int i = 0; i < 256; i++)
{
uint temp = (uint)i;
for (int j = 0; j < 8; j++)
{
if ((temp & 1) == 1)
{
temp >>= 1;
temp ^= val;
}
else { temp >>= 1; }
}
CRCTable[i] = temp;
}
hasCRCTable = true;
}
public static uint UpdateCRC(uint _crc, byte[] data)
{
uint crc = _crc;
if (!hasCRCTable)
MakeCRCTable();
long pos = 0;
long offset = 0;
do
{
var value = data[pos];
pos += offsets[offset];
crc = CRCTable[(crc ^ value) & 0xff] ^ crc >> 8;
offset = offset + 1U + ((offset + 1U >> 3) / 7) * -0x38;
} while (pos < data.Length);
return crc;
}
public static uint CRC(byte[] data)
{
return ~UpdateCRC(0xffffffff, data);
}
}
}

View File

@@ -1,785 +0,0 @@
#region HEADER
/* This file was derived from libmspack
* (C) 2003-2004 Stuart Caie.
* (C) 2011 Ali Scissons.
*
* The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted
* by Microsoft Corporation.
*
* This source file is Dual licensed; meaning the end-user of this source file
* may redistribute/modify it under the LGPL 2.1 or MS-PL licenses.
*/
#region LGPL License
/* GNU LESSER GENERAL PUBLIC LICENSE version 2.1
* LzxDecoder is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License (LGPL) version 2.1
*/
#endregion
#region MS-PL License
/*
* MICROSOFT PUBLIC LICENSE
* This source code is subject to the terms of the Microsoft Public License (Ms-PL).
*
* Redistribution and use in source and binary forms, with or without modification,
* is permitted provided that redistributions of the source code retain the above
* copyright notices and this file header.
*
* Additional copyright notices should be appended to the list above.
*
* For details, see <http://www.opensource.org/licenses/ms-pl.html>.
*/
#endregion
/*
* This derived work is recognized by Stuart Caie and is authorized to adapt
* any changes made to lzxd.c in his libmspack library and will still retain
* this dual licensing scheme. Big thanks to Stuart Caie!
*
* DETAILS
* This file is a pure C# port of the lzxd.c file from libmspack, with minor
* changes towards the decompression of XNB files. The original decompression
* software of LZX encoded data was written by Suart Caie in his
* libmspack/cabextract projects, which can be located at
* http://http://www.cabextract.org.uk/
*/
#endregion
using System;
using System.Diagnostics;
namespace Microsoft.Xna.Framework.Content
{
using System.IO;
class LzxDecoder
{
public static uint[] position_base = null;
public static byte[] extra_bits = null;
private LzxState m_state;
public LzxDecoder(int window)
{
uint wndsize = (uint)(1 << window);
int posn_slots;
// setup proper exception
if (window < 15 || window > 21) throw new UnsupportedWindowSizeRange();
// let's initialise our state
m_state = new LzxState();
m_state.actual_size = 0;
m_state.window = new byte[wndsize];
for (int i = 0; i < wndsize; i++) m_state.window[i] = 0xDC;
m_state.actual_size = wndsize;
m_state.window_size = wndsize;
m_state.window_posn = 0;
/* initialize static tables */
if (extra_bits == null)
{
extra_bits = new byte[52];
for (int i = 0, j = 0; i <= 50; i += 2)
{
extra_bits[i] = extra_bits[i + 1] = (byte)j;
if ((i != 0) && (j < 17)) j++;
}
}
if (position_base == null)
{
position_base = new uint[51];
for (int i = 0, j = 0; i <= 50; i++)
{
position_base[i] = (uint)j;
j += 1 << extra_bits[i];
}
}
/* calculate required position slots */
if (window == 20) posn_slots = 42;
else if (window == 21) posn_slots = 50;
else posn_slots = window << 1;
m_state.R0 = m_state.R1 = m_state.R2 = 1;
m_state.main_elements = (ushort)(LzxConstants.NUM_CHARS + (posn_slots << 3));
m_state.header_read = 0;
m_state.frames_read = 0;
m_state.block_remaining = 0;
m_state.block_type = LzxConstants.BLOCKTYPE.INVALID;
m_state.intel_curpos = 0;
m_state.intel_started = 0;
// yo dawg i herd u liek arrays so we put arrays in ur arrays so u can array while u array
m_state.PRETREE_table = new ushort[(1 << LzxConstants.PRETREE_TABLEBITS) + (LzxConstants.PRETREE_MAXSYMBOLS << 1)];
m_state.PRETREE_len = new byte[LzxConstants.PRETREE_MAXSYMBOLS + LzxConstants.LENTABLE_SAFETY];
m_state.MAINTREE_table = new ushort[(1 << LzxConstants.MAINTREE_TABLEBITS) + (LzxConstants.MAINTREE_MAXSYMBOLS << 1)];
m_state.MAINTREE_len = new byte[LzxConstants.MAINTREE_MAXSYMBOLS + LzxConstants.LENTABLE_SAFETY];
m_state.LENGTH_table = new ushort[(1 << LzxConstants.LENGTH_TABLEBITS) + (LzxConstants.LENGTH_MAXSYMBOLS << 1)];
m_state.LENGTH_len = new byte[LzxConstants.LENGTH_MAXSYMBOLS + LzxConstants.LENTABLE_SAFETY];
m_state.ALIGNED_table = new ushort[(1 << LzxConstants.ALIGNED_TABLEBITS) + (LzxConstants.ALIGNED_MAXSYMBOLS << 1)];
m_state.ALIGNED_len = new byte[LzxConstants.ALIGNED_MAXSYMBOLS + LzxConstants.LENTABLE_SAFETY];
/* initialise tables to 0 (because deltas will be applied to them) */
for (int i = 0; i < LzxConstants.MAINTREE_MAXSYMBOLS; i++) m_state.MAINTREE_len[i] = 0;
for (int i = 0; i < LzxConstants.LENGTH_MAXSYMBOLS; i++) m_state.LENGTH_len[i] = 0;
}
public int Decompress(Stream inData, int inLen, Stream outData, int outLen)
{
BitBuffer bitbuf = new BitBuffer(inData);
long startpos = inData.Position;
long endpos = inData.Position + inLen;
byte[] window = m_state.window;
uint window_posn = m_state.window_posn;
uint window_size = m_state.window_size;
uint R0 = m_state.R0;
uint R1 = m_state.R1;
uint R2 = m_state.R2;
uint i, j;
int togo = outLen, this_run, main_element, match_length, match_offset, length_footer, extra, verbatim_bits;
int rundest, runsrc, copy_length, aligned_bits;
bitbuf.InitBitStream();
/* read header if necessary */
if (m_state.header_read == 0)
{
uint intel = bitbuf.ReadBits(1);
if (intel != 0)
{
// read the filesize
i = bitbuf.ReadBits(16); j = bitbuf.ReadBits(16);
m_state.intel_filesize = (int)((i << 16) | j);
}
m_state.header_read = 1;
}
/* main decoding loop */
while (togo > 0)
{
/* last block finished, new block expected */
if (m_state.block_remaining == 0)
{
// TODO may screw something up here
if (m_state.block_type == LzxConstants.BLOCKTYPE.UNCOMPRESSED)
{
if ((m_state.block_length & 1) == 1) inData.ReadByte(); /* realign bitstream to word */
bitbuf.InitBitStream();
}
m_state.block_type = (LzxConstants.BLOCKTYPE)bitbuf.ReadBits(3); ;
i = bitbuf.ReadBits(16);
j = bitbuf.ReadBits(8);
m_state.block_remaining = m_state.block_length = (uint)((i << 8) | j);
switch (m_state.block_type)
{
case LzxConstants.BLOCKTYPE.ALIGNED:
for (i = 0, j = 0; i < 8; i++) { j = bitbuf.ReadBits(3); m_state.ALIGNED_len[i] = (byte)j; }
MakeDecodeTable(LzxConstants.ALIGNED_MAXSYMBOLS, LzxConstants.ALIGNED_TABLEBITS,
m_state.ALIGNED_len, m_state.ALIGNED_table);
/* rest of aligned header is same as verbatim */
goto case LzxConstants.BLOCKTYPE.VERBATIM;
case LzxConstants.BLOCKTYPE.VERBATIM:
ReadLengths(m_state.MAINTREE_len, 0, 256, bitbuf);
ReadLengths(m_state.MAINTREE_len, 256, m_state.main_elements, bitbuf);
MakeDecodeTable(LzxConstants.MAINTREE_MAXSYMBOLS, LzxConstants.MAINTREE_TABLEBITS,
m_state.MAINTREE_len, m_state.MAINTREE_table);
if (m_state.MAINTREE_len[0xE8] != 0) m_state.intel_started = 1;
ReadLengths(m_state.LENGTH_len, 0, LzxConstants.NUM_SECONDARY_LENGTHS, bitbuf);
MakeDecodeTable(LzxConstants.LENGTH_MAXSYMBOLS, LzxConstants.LENGTH_TABLEBITS,
m_state.LENGTH_len, m_state.LENGTH_table);
break;
case LzxConstants.BLOCKTYPE.UNCOMPRESSED:
m_state.intel_started = 1; /* because we can't assume otherwise */
bitbuf.EnsureBits(16); /* get up to 16 pad bits into the buffer */
if (bitbuf.GetBitsLeft() > 16) inData.Seek(-2, SeekOrigin.Current); /* and align the bitstream! */
byte hi, mh, ml, lo;
lo = (byte)inData.ReadByte(); ml = (byte)inData.ReadByte(); mh = (byte)inData.ReadByte(); hi = (byte)inData.ReadByte();
R0 = (uint)(lo | ml << 8 | mh << 16 | hi << 24);
lo = (byte)inData.ReadByte(); ml = (byte)inData.ReadByte(); mh = (byte)inData.ReadByte(); hi = (byte)inData.ReadByte();
R1 = (uint)(lo | ml << 8 | mh << 16 | hi << 24);
lo = (byte)inData.ReadByte(); ml = (byte)inData.ReadByte(); mh = (byte)inData.ReadByte(); hi = (byte)inData.ReadByte();
R2 = (uint)(lo | ml << 8 | mh << 16 | hi << 24);
break;
default:
return -1; // TODO throw proper exception
}
}
/* buffer exhaustion check */
if (inData.Position > (startpos + inLen))
{
/* it's possible to have a file where the next run is less than
* 16 bits in size. In this case, the READ_HUFFSYM() macro used
* in building the tables will exhaust the buffer, so we should
* allow for this, but not allow those accidentally read bits to
* be used (so we check that there are at least 16 bits
* remaining - in this boundary case they aren't really part of
* the compressed data)
*/
//Debug.WriteLine("WTF");
if (inData.Position > (startpos + inLen + 2) || bitbuf.GetBitsLeft() < 16) return -1; //TODO throw proper exception
}
while ((this_run = (int)m_state.block_remaining) > 0 && togo > 0)
{
if (this_run > togo) this_run = togo;
togo -= this_run;
m_state.block_remaining -= (uint)this_run;
/* apply 2^x-1 mask */
window_posn &= window_size - 1;
/* runs can't straddle the window wraparound */
if ((window_posn + this_run) > window_size)
return -1; //TODO throw proper exception
switch (m_state.block_type)
{
case LzxConstants.BLOCKTYPE.VERBATIM:
while (this_run > 0)
{
main_element = (int)ReadHuffSym(m_state.MAINTREE_table, m_state.MAINTREE_len,
LzxConstants.MAINTREE_MAXSYMBOLS, LzxConstants.MAINTREE_TABLEBITS,
bitbuf);
if (main_element < LzxConstants.NUM_CHARS)
{
/* literal: 0 to NUM_CHARS-1 */
window[window_posn++] = (byte)main_element;
this_run--;
}
else
{
/* match: NUM_CHARS + ((slot<<3) | length_header (3 bits)) */
main_element -= LzxConstants.NUM_CHARS;
match_length = main_element & LzxConstants.NUM_PRIMARY_LENGTHS;
if (match_length == LzxConstants.NUM_PRIMARY_LENGTHS)
{
length_footer = (int)ReadHuffSym(m_state.LENGTH_table, m_state.LENGTH_len,
LzxConstants.LENGTH_MAXSYMBOLS, LzxConstants.LENGTH_TABLEBITS,
bitbuf);
match_length += length_footer;
}
match_length += LzxConstants.MIN_MATCH;
match_offset = main_element >> 3;
if (match_offset > 2)
{
/* not repeated offset */
if (match_offset != 3)
{
extra = extra_bits[match_offset];
verbatim_bits = (int)bitbuf.ReadBits((byte)extra);
match_offset = (int)position_base[match_offset] - 2 + verbatim_bits;
}
else
{
match_offset = 1;
}
/* update repeated offset LRU queue */
R2 = R1; R1 = R0; R0 = (uint)match_offset;
}
else if (match_offset == 0)
{
match_offset = (int)R0;
}
else if (match_offset == 1)
{
match_offset = (int)R1;
R1 = R0; R0 = (uint)match_offset;
}
else /* match_offset == 2 */
{
match_offset = (int)R2;
R2 = R0; R0 = (uint)match_offset;
}
rundest = (int)window_posn;
this_run -= match_length;
/* copy any wrapped around source data */
if (window_posn >= match_offset)
{
/* no wrap */
runsrc = rundest - match_offset;
}
else
{
runsrc = rundest + ((int)window_size - match_offset);
copy_length = match_offset - (int)window_posn;
if (copy_length < match_length)
{
match_length -= copy_length;
window_posn += (uint)copy_length;
while (copy_length-- > 0) window[rundest++] = window[runsrc++];
runsrc = 0;
}
}
window_posn += (uint)match_length;
/* copy match data - no worries about destination wraps */
while (match_length-- > 0) window[rundest++] = window[runsrc++];
}
}
break;
case LzxConstants.BLOCKTYPE.ALIGNED:
while (this_run > 0)
{
main_element = (int)ReadHuffSym(m_state.MAINTREE_table, m_state.MAINTREE_len,
LzxConstants.MAINTREE_MAXSYMBOLS, LzxConstants.MAINTREE_TABLEBITS,
bitbuf);
if (main_element < LzxConstants.NUM_CHARS)
{
/* literal 0 to NUM_CHARS-1 */
window[window_posn++] = (byte)main_element;
this_run--;
}
else
{
/* match: NUM_CHARS + ((slot<<3) | length_header (3 bits)) */
main_element -= LzxConstants.NUM_CHARS;
match_length = main_element & LzxConstants.NUM_PRIMARY_LENGTHS;
if (match_length == LzxConstants.NUM_PRIMARY_LENGTHS)
{
length_footer = (int)ReadHuffSym(m_state.LENGTH_table, m_state.LENGTH_len,
LzxConstants.LENGTH_MAXSYMBOLS, LzxConstants.LENGTH_TABLEBITS,
bitbuf);
match_length += length_footer;
}
match_length += LzxConstants.MIN_MATCH;
match_offset = main_element >> 3;
if (match_offset > 2)
{
/* not repeated offset */
extra = extra_bits[match_offset];
match_offset = (int)position_base[match_offset] - 2;
if (extra > 3)
{
/* verbatim and aligned bits */
extra -= 3;
verbatim_bits = (int)bitbuf.ReadBits((byte)extra);
match_offset += (verbatim_bits << 3);
aligned_bits = (int)ReadHuffSym(m_state.ALIGNED_table, m_state.ALIGNED_len,
LzxConstants.ALIGNED_MAXSYMBOLS, LzxConstants.ALIGNED_TABLEBITS,
bitbuf);
match_offset += aligned_bits;
}
else if (extra == 3)
{
/* aligned bits only */
aligned_bits = (int)ReadHuffSym(m_state.ALIGNED_table, m_state.ALIGNED_len,
LzxConstants.ALIGNED_MAXSYMBOLS, LzxConstants.ALIGNED_TABLEBITS,
bitbuf);
match_offset += aligned_bits;
}
else if (extra > 0) /* extra==1, extra==2 */
{
/* verbatim bits only */
verbatim_bits = (int)bitbuf.ReadBits((byte)extra);
match_offset += verbatim_bits;
}
else /* extra == 0 */
{
/* ??? */
match_offset = 1;
}
/* update repeated offset LRU queue */
R2 = R1; R1 = R0; R0 = (uint)match_offset;
}
else if (match_offset == 0)
{
match_offset = (int)R0;
}
else if (match_offset == 1)
{
match_offset = (int)R1;
R1 = R0; R0 = (uint)match_offset;
}
else /* match_offset == 2 */
{
match_offset = (int)R2;
R2 = R0; R0 = (uint)match_offset;
}
rundest = (int)window_posn;
this_run -= match_length;
/* copy any wrapped around source data */
if (window_posn >= match_offset)
{
/* no wrap */
runsrc = rundest - match_offset;
}
else
{
runsrc = rundest + ((int)window_size - match_offset);
copy_length = match_offset - (int)window_posn;
if (copy_length < match_length)
{
match_length -= copy_length;
window_posn += (uint)copy_length;
while (copy_length-- > 0) window[rundest++] = window[runsrc++];
runsrc = 0;
}
}
window_posn += (uint)match_length;
/* copy match data - no worries about destination wraps */
while (match_length-- > 0) window[rundest++] = window[runsrc++];
}
}
break;
case LzxConstants.BLOCKTYPE.UNCOMPRESSED:
if ((inData.Position + this_run) > endpos) return -1; //TODO throw proper exception
byte[] temp_buffer = new byte[this_run];
inData.Read(temp_buffer, 0, this_run);
temp_buffer.CopyTo(window, (int)window_posn);
window_posn += (uint)this_run;
break;
default:
return -1; //TODO throw proper exception
}
}
}
if (togo != 0) return -1; //TODO throw proper exception
int start_window_pos = (int)window_posn;
if (start_window_pos == 0) start_window_pos = (int)window_size;
start_window_pos -= outLen;
outData.Write(window, start_window_pos, outLen);
m_state.window_posn = window_posn;
m_state.R0 = R0;
m_state.R1 = R1;
m_state.R2 = R2;
// TODO finish intel E8 decoding
/* intel E8 decoding */
if ((m_state.frames_read++ < 32768) && m_state.intel_filesize != 0)
{
if (outLen <= 6 || m_state.intel_started == 0)
{
m_state.intel_curpos += outLen;
}
else
{
int dataend = outLen - 10;
uint curpos = (uint)m_state.intel_curpos;
m_state.intel_curpos = (int)curpos + outLen;
while (outData.Position < dataend)
{
if (outData.ReadByte() != 0xE8) { curpos++; continue; }
}
}
return -1;
}
return 0;
}
// READ_LENGTHS(table, first, last)
// if(lzx_read_lens(LENTABLE(table), first, last, bitsleft))
// return ERROR (ILLEGAL_DATA)
//
// TODO make returns throw exceptions
private int MakeDecodeTable(uint nsyms, uint nbits, byte[] length, ushort[] table)
{
ushort sym;
uint leaf;
byte bit_num = 1;
uint fill;
uint pos = 0; /* the current position in the decode table */
uint table_mask = (uint)(1 << (int)nbits);
uint bit_mask = table_mask >> 1; /* don't do 0 length codes */
uint next_symbol = bit_mask; /* base of allocation for long codes */
/* fill entries for codes short enough for a direct mapping */
while (bit_num <= nbits)
{
for (sym = 0; sym < nsyms; sym++)
{
if (length[sym] == bit_num)
{
leaf = pos;
if ((pos += bit_mask) > table_mask) return 1; /* table overrun */
/* fill all possible lookups of this symbol with the symbol itself */
fill = bit_mask;
while (fill-- > 0) table[leaf++] = sym;
}
}
bit_mask >>= 1;
bit_num++;
}
/* if there are any codes longer than nbits */
if (pos != table_mask)
{
/* clear the remainder of the table */
for (sym = (ushort)pos; sym < table_mask; sym++) table[sym] = 0;
/* give ourselves room for codes to grow by up to 16 more bits */
pos <<= 16;
table_mask <<= 16;
bit_mask = 1 << 15;
while (bit_num <= 16)
{
for (sym = 0; sym < nsyms; sym++)
{
if (length[sym] == bit_num)
{
leaf = pos >> 16;
for (fill = 0; fill < bit_num - nbits; fill++)
{
/* if this path hasn't been taken yet, 'allocate' two entries */
if (table[leaf] == 0)
{
table[(next_symbol << 1)] = 0;
table[(next_symbol << 1) + 1] = 0;
table[leaf] = (ushort)(next_symbol++);
}
/* follow the path and select either left or right for next bit */
leaf = (uint)(table[leaf] << 1);
if (((pos >> (int)(15 - fill)) & 1) == 1) leaf++;
}
table[leaf] = sym;
if ((pos += bit_mask) > table_mask) return 1;
}
}
bit_mask >>= 1;
bit_num++;
}
}
/* full talbe? */
if (pos == table_mask) return 0;
/* either erroneous table, or all elements are 0 - let's find out. */
for (sym = 0; sym < nsyms; sym++) if (length[sym] != 0) return 1;
return 0;
}
// TODO throw exceptions instead of returns
private void ReadLengths(byte[] lens, uint first, uint last, BitBuffer bitbuf)
{
uint x, y;
int z;
// hufftbl pointer here?
for (x = 0; x < 20; x++)
{
y = bitbuf.ReadBits(4);
m_state.PRETREE_len[x] = (byte)y;
}
MakeDecodeTable(LzxConstants.PRETREE_MAXSYMBOLS, LzxConstants.PRETREE_TABLEBITS,
m_state.PRETREE_len, m_state.PRETREE_table);
for (x = first; x < last;)
{
z = (int)ReadHuffSym(m_state.PRETREE_table, m_state.PRETREE_len,
LzxConstants.PRETREE_MAXSYMBOLS, LzxConstants.PRETREE_TABLEBITS, bitbuf);
if (z == 17)
{
y = bitbuf.ReadBits(4); y += 4;
while (y-- != 0) lens[x++] = 0;
}
else if (z == 18)
{
y = bitbuf.ReadBits(5); y += 20;
while (y-- != 0) lens[x++] = 0;
}
else if (z == 19)
{
y = bitbuf.ReadBits(1); y += 4;
z = (int)ReadHuffSym(m_state.PRETREE_table, m_state.PRETREE_len,
LzxConstants.PRETREE_MAXSYMBOLS, LzxConstants.PRETREE_TABLEBITS, bitbuf);
z = lens[x] - z; if (z < 0) z += 17;
while (y-- != 0) lens[x++] = (byte)z;
}
else
{
z = lens[x] - z; if (z < 0) z += 17;
lens[x++] = (byte)z;
}
}
}
private uint ReadHuffSym(ushort[] table, byte[] lengths, uint nsyms, uint nbits, BitBuffer bitbuf)
{
uint i, j;
bitbuf.EnsureBits(16);
if ((i = table[bitbuf.PeekBits((byte)nbits)]) >= nsyms)
{
j = (uint)(1 << (int)((sizeof(uint) * 8) - nbits));
do
{
j >>= 1; i <<= 1; i |= (bitbuf.GetBuffer() & j) != 0 ? (uint)1 : 0;
if (j == 0) return 0; // TODO throw proper exception
} while ((i = table[i]) >= nsyms);
}
j = lengths[i];
bitbuf.RemoveBits((byte)j);
return i;
}
#region Our BitBuffer Class
private class BitBuffer
{
uint buffer;
byte bitsleft;
Stream byteStream;
public BitBuffer(Stream stream)
{
byteStream = stream;
InitBitStream();
}
public void InitBitStream()
{
buffer = 0;
bitsleft = 0;
}
public void EnsureBits(byte bits)
{
while (bitsleft < bits)
{
int lo = (byte)byteStream.ReadByte();
int hi = (byte)byteStream.ReadByte();
//int amount2shift = sizeof(uint)*8 - 16 - bitsleft;
buffer |= (uint)(((hi << 8) | lo) << (sizeof(uint) * 8 - 16 - bitsleft));
bitsleft += 16;
}
}
public uint PeekBits(byte bits)
{
return (buffer >> ((sizeof(uint) * 8) - bits));
}
public void RemoveBits(byte bits)
{
buffer <<= bits;
bitsleft -= bits;
}
public uint ReadBits(byte bits)
{
uint ret = 0;
if (bits > 0)
{
EnsureBits(bits);
ret = PeekBits(bits);
RemoveBits(bits);
}
return ret;
}
public uint GetBuffer()
{
return buffer;
}
public byte GetBitsLeft()
{
return bitsleft;
}
}
#endregion
struct LzxState
{
public uint R0, R1, R2; /* for the LRU offset system */
public ushort main_elements; /* number of main tree elements */
public int header_read; /* have we started decoding at all yet? */
public LzxConstants.BLOCKTYPE block_type; /* type of this block */
public uint block_length; /* uncompressed length of this block */
public uint block_remaining; /* uncompressed bytes still left to decode */
public uint frames_read; /* the number of CFDATA blocks processed */
public int intel_filesize; /* magic header value used for transform */
public int intel_curpos; /* current offset in transform space */
public int intel_started; /* have we seen any translateable data yet? */
public ushort[] PRETREE_table;
public byte[] PRETREE_len;
public ushort[] MAINTREE_table;
public byte[] MAINTREE_len;
public ushort[] LENGTH_table;
public byte[] LENGTH_len;
public ushort[] ALIGNED_table;
public byte[] ALIGNED_len;
// NEEDED MEMBERS
// CAB actualsize
// CAB window
// CAB window_size
// CAB window_posn
public uint actual_size;
public byte[] window;
public uint window_size;
public uint window_posn;
}
}
/* CONSTANTS */
struct LzxConstants
{
public const ushort MIN_MATCH = 2;
public const ushort MAX_MATCH = 257;
public const ushort NUM_CHARS = 256;
public enum BLOCKTYPE
{
INVALID = 0,
VERBATIM = 1,
ALIGNED = 2,
UNCOMPRESSED = 3
}
public const ushort PRETREE_NUM_ELEMENTS = 20;
public const ushort ALIGNED_NUM_ELEMENTS = 8;
public const ushort NUM_PRIMARY_LENGTHS = 7;
public const ushort NUM_SECONDARY_LENGTHS = 249;
public const ushort PRETREE_MAXSYMBOLS = PRETREE_NUM_ELEMENTS;
public const ushort PRETREE_TABLEBITS = 6;
public const ushort MAINTREE_MAXSYMBOLS = NUM_CHARS + 50 * 8;
public const ushort MAINTREE_TABLEBITS = 12;
public const ushort LENGTH_MAXSYMBOLS = NUM_SECONDARY_LENGTHS + 1;
public const ushort LENGTH_TABLEBITS = 12;
public const ushort ALIGNED_MAXSYMBOLS = ALIGNED_NUM_ELEMENTS;
public const ushort ALIGNED_TABLEBITS = 7;
public const ushort LENTABLE_SAFETY = 64;
}
/* EXCEPTIONS */
class UnsupportedWindowSizeRange : Exception
{
}
}

View File

@@ -1,155 +0,0 @@
// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.IO;
using Microsoft.Xna.Framework.Content;
namespace MonoGame.Framework.Utilities
{
internal class LzxDecoderStream : Stream
{
LzxDecoder dec;
MemoryStream decompressedStream;
public LzxDecoderStream(Stream input, int decompressedSize, int compressedSize)
{
dec = new LzxDecoder(16);
// TODO: Rewrite using block decompression like Lz4DecoderStream
Decompress(input, decompressedSize, compressedSize);
}
// Decompress into MemoryStream
private void Decompress(Stream stream, int decompressedSize, int compressedSize)
{
//thanks to ShinAli (https://bitbucket.org/alisci01/xnbdecompressor)
// default window size for XNB encoded files is 64Kb (need 16 bits to represent it)
decompressedStream = new MemoryStream(decompressedSize);
long startPos = stream.Position;
long pos = startPos;
while (pos - startPos < compressedSize)
{
// the compressed stream is seperated into blocks that will decompress
// into 32Kb or some other size if specified.
// normal, 32Kb output blocks will have a short indicating the size
// of the block before the block starts
// blocks that have a defined output will be preceded by a byte of value
// 0xFF (255), then a short indicating the output size and another
// for the block size
// all shorts for these cases are encoded in big endian order
int hi = stream.ReadByte();
int lo = stream.ReadByte();
int block_size = (hi << 8) | lo;
int frame_size = 0x8000; // frame size is 32Kb by default
// does this block define a frame size?
if (hi == 0xFF)
{
hi = lo;
lo = (byte)stream.ReadByte();
frame_size = (hi << 8) | lo;
hi = (byte)stream.ReadByte();
lo = (byte)stream.ReadByte();
block_size = (hi << 8) | lo;
pos += 5;
}
else
pos += 2;
// either says there is nothing to decode
if (block_size == 0 || frame_size == 0)
break;
dec.Decompress(stream, block_size, decompressedStream, frame_size);
pos += block_size;
// reset the position of the input just incase the bit buffer
// read in some unused bytes
stream.Seek(pos, SeekOrigin.Begin);
}
Console.WriteLine(decompressedStream.Position);
Console.WriteLine(decompressedSize);
//if (decompressedStream.Position != decompressedSize)
//{
// throw new Exception("Decompression failed.");
//}
decompressedStream.Seek(0, SeekOrigin.Begin);
using (var fs = File.OpenWrite("xmem_test.grf"))
{
decompressedStream.CopyTo(fs);
}
decompressedStream.Seek(0, SeekOrigin.Begin);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
decompressedStream.Dispose();
}
dec = null;
decompressedStream = null;
}
#region Stream internals
public override int Read(byte[] buffer, int offset, int count)
{
return decompressedStream.Read(buffer, offset, count);
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override void Flush()
{
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -1,6 +1,6 @@
namespace PckStudio.Forms.Editor
{
partial class GRFEditor
partial class GameRuleFileEditor
{
/// <summary>
/// Required designer variable.
@@ -29,7 +29,7 @@
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GRFEditor));
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GameRuleFileEditor));
this.GrfTreeView = new System.Windows.Forms.TreeView();
this.MessageContextMenu = new MetroFramework.Controls.MetroContextMenu(this.components);
this.addGameRuleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -44,9 +44,11 @@
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.metroPanel1 = new MetroFramework.Controls.MetroPanel();
this.compressionLvlToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripComboBox1 = new System.Windows.Forms.ToolStripComboBox();
this.compressionTypeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.compressionTypeComboBox = new System.Windows.Forms.ToolStripComboBox();
this.metroPanel1 = new MetroFramework.Controls.MetroPanel();
this.MessageContextMenu.SuspendLayout();
this.DetailContextMenu.SuspendLayout();
this.menuStrip1.SuspendLayout();
@@ -153,7 +155,8 @@
this.menuStrip1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileToolStripMenuItem,
this.compressionLvlToolStripMenuItem});
this.compressionLvlToolStripMenuItem,
this.compressionTypeToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(25, 60);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(450, 24);
@@ -172,10 +175,10 @@
//
// openToolStripMenuItem
//
this.openToolStripMenuItem.Enabled = false;
this.openToolStripMenuItem.Name = "openToolStripMenuItem";
this.openToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.openToolStripMenuItem.Text = "Open";
this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click);
//
// saveToolStripMenuItem
//
@@ -185,6 +188,43 @@
this.saveToolStripMenuItem.Text = "Save";
this.saveToolStripMenuItem.Click += new System.EventHandler(this.saveToolStripMenuItem_Click);
//
// compressionLvlToolStripMenuItem
//
this.compressionLvlToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripComboBox1});
this.compressionLvlToolStripMenuItem.ForeColor = System.Drawing.SystemColors.Menu;
this.compressionLvlToolStripMenuItem.Name = "compressionLvlToolStripMenuItem";
this.compressionLvlToolStripMenuItem.Size = new System.Drawing.Size(106, 20);
this.compressionLvlToolStripMenuItem.Text = "Compression Lvl";
//
// toolStripComboBox1
//
this.toolStripComboBox1.Items.AddRange(new object[] {
"None",
"Compressed",
"Compressed + RLE",
"Compressed + RLE + CRC"});
this.toolStripComboBox1.Name = "toolStripComboBox1";
this.toolStripComboBox1.Size = new System.Drawing.Size(121, 23);
this.toolStripComboBox1.Text = "None";
//
// compressionTypeToolStripMenuItem
//
this.compressionTypeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.compressionTypeComboBox});
this.compressionTypeToolStripMenuItem.Name = "compressionTypeToolStripMenuItem";
this.compressionTypeToolStripMenuItem.Size = new System.Drawing.Size(116, 20);
this.compressionTypeToolStripMenuItem.Text = "Compression Type";
//
// compressionTypeComboBox
//
this.compressionTypeComboBox.Items.AddRange(new object[] {
"Zlib",
"Deflate",
"XMem"});
this.compressionTypeComboBox.Name = "compressionTypeComboBox";
this.compressionTypeComboBox.Size = new System.Drawing.Size(121, 23);
//
// metroPanel1
//
this.metroPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
@@ -205,26 +245,6 @@
this.metroPanel1.VerticalScrollbarSize = 10;
this.metroPanel1.Resize += new System.EventHandler(this.metroPanel1_Resize);
//
// compressionLvlToolStripMenuItem
//
this.compressionLvlToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripComboBox1});
this.compressionLvlToolStripMenuItem.ForeColor = System.Drawing.SystemColors.Menu;
this.compressionLvlToolStripMenuItem.Name = "compressionLvlToolStripMenuItem";
this.compressionLvlToolStripMenuItem.Size = new System.Drawing.Size(106, 20);
this.compressionLvlToolStripMenuItem.Text = "Compression Lvl";
//
// toolStripComboBox1
//
this.toolStripComboBox1.Items.AddRange(new object[] {
"None",
"Compressed",
"Compressed + RLE",
"Compressed + RLE + CRC"});
this.toolStripComboBox1.Name = "toolStripComboBox1";
this.toolStripComboBox1.Size = new System.Drawing.Size(121, 23);
this.toolStripComboBox1.Text = "Zlib";
//
// GRFEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -274,5 +294,7 @@
private MetroFramework.Controls.MetroPanel metroPanel1;
private System.Windows.Forms.ToolStripMenuItem compressionLvlToolStripMenuItem;
private System.Windows.Forms.ToolStripComboBox toolStripComboBox1;
private System.Windows.Forms.ToolStripMenuItem compressionTypeToolStripMenuItem;
private System.Windows.Forms.ToolStripComboBox compressionTypeComboBox;
}
}

View File

@@ -5,81 +5,101 @@ using System.IO;
using System.Linq;
using System.Windows.Forms;
using PckStudio.Classes.FileTypes;
using PckStudio.Classes.IO.GRF;
using PckStudio.Forms.Additional_Popups.Grf;
using PckStudio.Classes.Misc;
using OMI.Formats.GameRule;
using OMI.Workers.GameRule;
using System.Diagnostics;
using PckStudio.Forms.Additional_Popups.Audio;
namespace PckStudio.Forms.Editor
{
public partial class GRFEditor : MetroFramework.Forms.MetroForm
public partial class GameRuleFileEditor : MetroFramework.Forms.MetroForm
{
private PCKFile.FileData _pckfile;
private GRFFile _file;
private GameRuleFile _file;
private GRFEditor()
public GameRuleFileEditor()
{
InitializeComponent();
PromptForCompressionType();
}
public GRFEditor(PCKFile.FileData file) : this()
private void PromptForCompressionType()
{
addCategory dialog = new addCategory(compressionTypeComboBox.Items.Cast<string>().ToArray());
dialog.label2.Text = "Type";
dialog.button1.Text = "Ok";
if (dialog.ShowDialog() == DialogResult.OK)
compressionTypeComboBox.SelectedIndex = compressionTypeComboBox.Items.IndexOf(dialog.Category);
}
public GameRuleFileEditor(PCKFile.FileData file) : this()
{
_pckfile = file;
using(var stream = new MemoryStream(file.Data))
using (var stream = new MemoryStream(file.Data))
{
try
{
_file = GRFFileReader.Read(stream);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
MessageBox.Show("Faild to open .grf/.grh file");
}
_file = OpenGameRuleFile(stream, (GameRuleFile.CompressionType)compressionTypeComboBox.SelectedIndex);
}
}
public GRFEditor(Stream stream) : this()
public GameRuleFileEditor(Stream stream) : this()
{
_file = OpenGameRuleFile(stream, (GameRuleFile.CompressionType)compressionTypeComboBox.SelectedIndex);
}
private GameRuleFile OpenGameRuleFile(Stream stream, GameRuleFile.CompressionType compressionType)
{
try
{
_file = GRFFileReader.Read(stream);
var reader = new GameRuleFileReader(compressionType);
return reader.FromStream(stream);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Debug.WriteLine(ex.Message);
MessageBox.Show("Faild to open .grf/.grh file");
}
return default!;
}
private void OnLoad(object sender, EventArgs e)
{
RPC.SetPresence("GRF Editor", "Editing a GRF File");
toolStripComboBox1.SelectedIndex = (int)_file.CompressionLevel;
loadGRFTreeView(GrfTreeView.Nodes, _file.Root);
ReloadGameRuleTree();
}
private void loadGRFTreeView(TreeNodeCollection root, GRFFile.GameRule parentRule)
private void LoadGameRuleTree(TreeNodeCollection root, GameRuleFile.GameRule parentRule)
{
foreach (var rule in parentRule.ChildRules)
{
TreeNode node = new TreeNode(rule.Name);
node.Tag = rule;
root.Add(node);
loadGRFTreeView(node.Nodes, rule);
LoadGameRuleTree(node.Nodes, rule);
}
}
private void ReloadGameRuleTree()
{
GrfTreeView.Nodes.Clear();
if (_file is not null)
{
toolStripComboBox1.SelectedIndex = (int)_file.FileHeader.CompressionLevel;
LoadGameRuleTree(GrfTreeView.Nodes, _file.Root);
}
}
private void GrfTreeView_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node is TreeNode t && t.Tag is GRFFile.GameRule)
if (e.Node is TreeNode t && t.Tag is GameRuleFile.GameRule)
ReloadParameterTreeView();
}
private void ReloadParameterTreeView()
{
GrfParametersTreeView.Nodes.Clear();
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule rule)
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GameRuleFile.GameRule rule)
foreach (var param in rule.Parameters)
{
GrfParametersTreeView.Nodes.Add(new TreeNode($"{param.Key}: {param.Value}") { Tag = param});
@@ -88,8 +108,8 @@ namespace PckStudio.Forms.Editor
private void addDetailContextMenuItem_Click(object sender, EventArgs e)
{
if (GrfTreeView.SelectedNode == null || !(GrfTreeView.SelectedNode.Tag is GRFFile.GameRule)) return;
var grfTag = GrfTreeView.SelectedNode.Tag as GRFFile.GameRule;
if (GrfTreeView.SelectedNode == null || !(GrfTreeView.SelectedNode.Tag is GameRuleFile.GameRule)) return;
var grfTag = GrfTreeView.SelectedNode.Tag as GameRuleFile.GameRule;
AddParameter prompt = new AddParameter();
if (prompt.ShowDialog() == DialogResult.OK)
{
@@ -105,7 +125,7 @@ namespace PckStudio.Forms.Editor
private void removeToolStripMenuItem_Click(object sender, EventArgs e)
{
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule rule &&
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GameRuleFile.GameRule rule &&
GrfParametersTreeView.SelectedNode is TreeNode paramNode && paramNode.Tag is KeyValuePair<string, string> pair &&
rule.Parameters.ContainsKey(pair.Key) && rule.Parameters.Remove(pair.Key))
{
@@ -123,7 +143,7 @@ namespace PckStudio.Forms.Editor
private void GrfDetailsTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule rule &&
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GameRuleFile.GameRule rule &&
GrfParametersTreeView.SelectedNode is TreeNode paramNode && paramNode.Tag is KeyValuePair<string, string> param)
{
AddParameter prompt = new AddParameter(param.Key, param.Value, false);
@@ -137,9 +157,9 @@ namespace PckStudio.Forms.Editor
private void addGameRuleToolStripMenuItem_Click(object sender, EventArgs e)
{
bool isValidNode = GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule;
GRFFile.GameRule parentRule = isValidNode
? GrfTreeView.SelectedNode.Tag as GRFFile.GameRule
bool isValidNode = GrfTreeView.SelectedNode is TreeNode t && t.Tag is GameRuleFile.GameRule;
var parentRule = isValidNode
? GrfTreeView.SelectedNode.Tag as GameRuleFile.GameRule
: _file.Root;
TreeNodeCollection root = isValidNode
@@ -164,11 +184,11 @@ namespace PckStudio.Forms.Editor
private void removeGameRuleToolStripMenuItem_Click(object sender, EventArgs e)
{
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GRFFile.GameRule tag && removeTag(tag))
if (GrfTreeView.SelectedNode is TreeNode t && t.Tag is GameRuleFile.GameRule tag && removeTag(tag))
t.Remove();
}
private bool removeTag(GRFFile.GameRule rule)
private bool removeTag(GameRuleFile.GameRule rule)
{
_ = rule.Parent ?? throw new ArgumentNullException(nameof(rule.Parent));
foreach (var subTag in rule.ChildRules.ToList())
@@ -184,7 +204,7 @@ namespace PckStudio.Forms.Editor
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_file.IsWorld)
if (_file.FileHeader.unknownData[3] != 0)
{
MessageBox.Show("World grf saving is currently unsupported");
return;
@@ -193,7 +213,11 @@ namespace PckStudio.Forms.Editor
{
try
{
GRFFileWriter.Write(stream, _file, (GRFFile.CompressionType)toolStripComboBox1.SelectedIndex/*GRFFile.eCompressionType.ZlibRleCrc*/);
var writer = new GameRuleFileWriter(
_file,
(GameRuleFile.CompressionLevel)toolStripComboBox1.SelectedIndex,
(GameRuleFile.CompressionType)compressionTypeComboBox.SelectedIndex);
writer.WriteToStream(stream);
_pckfile?.SetData(stream.ToArray());
MessageBox.Show("Saved!");
}
@@ -213,5 +237,20 @@ namespace PckStudio.Forms.Editor
// good enough
metroLabel2.Location = new Point(metroPanel1.Size.Width / 2 + 25, metroLabel2.Location.Y);
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Game Rule File|*.grf";
PromptForCompressionType();
if (dialog.ShowDialog(this) == DialogResult.OK)
{
using (var fs = File.OpenRead(dialog.FileName))
{
_file = OpenGameRuleFile(fs, (GameRuleFile.CompressionType)compressionTypeComboBox.SelectedIndex);
ReloadGameRuleTree();
}
}
}
}
}

View File

@@ -8,11 +8,12 @@ using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Drawing.Imaging;
using OMI.Formats.GameRule;
using OMI.Workers.GameRule;
using PckStudio.Properties;
using PckStudio.Classes.FileTypes;
using PckStudio.Classes.IO.LOC;
using PckStudio.Classes.IO.PCK;
using PckStudio.Classes.IO.GRF;
using PckStudio.Classes.Utils;
using PckStudio.Classes.Utils.ARC;
using PckStudio.Classes._3ds.Utils;
@@ -372,7 +373,7 @@ namespace PckStudio
private void HandleGameRuleFile(PCKFile.FileData file)
{
using GRFEditor grfEditor = new GRFEditor(file);
using GameRuleFileEditor grfEditor = new GameRuleFileEditor(file);
wasModified = grfEditor.ShowDialog(this) == DialogResult.OK;
UpdateRPC();
}
@@ -1256,7 +1257,7 @@ namespace PckStudio
{
var newPck = InitializeTexturePack(packId, packVersion, packName, res, true);
var gameRuleFile = newPck.CreateNew("GameRules.grf", PCKFile.FileData.FileType.GameRulesFile);
var grfFile = new GRFFile();
var grfFile = new GameRuleFile();
grfFile.AddRule("MapOptions",
new KeyValuePair<string, string>("seed", "0"),
new KeyValuePair<string, string>("baseSaveName", string.Empty),
@@ -1272,8 +1273,9 @@ namespace PckStudio
new KeyValuePair<string, string>("spawnZ", "0")
);
using (var stream = new MemoryStream())
{
GRFFileWriter.Write(stream, grfFile, GRFFile.CompressionType.None);
{
var writer = new GameRuleFileWriter(grfFile);
writer.WriteToStream(stream);
gameRuleFile.SetData(stream.ToArray());
}
return newPck;

View File

@@ -176,7 +176,6 @@
<Compile Include="Classes\FileTypes\PCKAudioFile.cs" />
<Compile Include="Classes\FileTypes\Binka.cs" />
<Compile Include="Classes\FileTypes\CSM.cs" />
<Compile Include="Classes\FileTypes\GRFFile.cs" />
<Compile Include="Classes\IO\CSMB\CSMBFileReader.cs" />
<Compile Include="Classes\IO\CSMB\CSMBFileWriter.cs" />
<Compile Include="Classes\IO\Materials\MaterialsReader.cs" />
@@ -195,8 +194,6 @@
<Compile Include="Classes\Utils\SkinANIM.cs" />
<Compile Include="Classes\FileTypes\PCKProperties.cs" />
<Compile Include="Classes\FileTypes\PCKFile.cs" />
<Compile Include="Classes\IO\GRF\GRFFileReader.cs" />
<Compile Include="Classes\IO\GRF\GRFFileWriter.cs" />
<Compile Include="Classes\IO\LOC\LOCFileReader.cs" />
<Compile Include="Classes\IO\LOC\LOCFileWriter.cs" />
<Compile Include="Classes\IO\PCK\PCKFileReader.cs" />
@@ -232,8 +229,6 @@
<Compile Include="Classes\Networking\Network.cs" />
<Compile Include="Classes\Misc\RichPresenceClient.cs" />
<Compile Include="Classes\Networking\Update.cs" />
<Compile Include="Classes\Utils\GRF\CRC32.cs" />
<Compile Include="Classes\Utils\RLE.cs" />
<Compile Include="Forms\Additional-Popups\EntityForms\AddEntry.cs">
<SubType>Form</SubType>
</Compile>
@@ -342,11 +337,11 @@
<Compile Include="Forms\Skins-And-Textures\AdvancedOptions.Designer.cs">
<DependentUpon>AdvancedOptions.cs</DependentUpon>
</Compile>
<Compile Include="Forms\Editor\GRFEditor.cs">
<Compile Include="Forms\Editor\GameRuleFileEditor.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\Editor\GRFEditor.Designer.cs">
<DependentUpon>GRFEditor.cs</DependentUpon>
<Compile Include="Forms\Editor\GameRuleFileEditor.Designer.cs">
<DependentUpon>GameRuleFileEditor.cs</DependentUpon>
</Compile>
<Compile Include="Forms\Additional-Popups\Grf\AddParameter.cs">
<SubType>Form</SubType>
@@ -561,8 +556,8 @@
<DependentUpon>AdvancedOptions.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Forms\Editor\GRFEditor.resx">
<DependentUpon>GRFEditor.cs</DependentUpon>
<EmbeddedResource Include="Forms\Editor\GameRuleFileEditor.resx">
<DependentUpon>GameRuleFileEditor.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\Additional-Popups\Grf\AddParameter.resx">
<DependentUpon>AddParameter.cs</DependentUpon>
@@ -772,9 +767,6 @@
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.2</Version>
</PackageReference>
<PackageReference Include="SharpZipLib">
<Version>1.4.1</Version>
</PackageReference>
<PackageReference Include="System.ValueTuple">
<Version>4.5.0</Version>
</PackageReference>

2
Vendor/OMI-Lib vendored