diff --git a/botw-toolset/IO/BinaryReaderBig.cs b/botw-toolset/IO/BinaryReaderBig.cs new file mode 100644 index 0000000..310ec22 --- /dev/null +++ b/botw-toolset/IO/BinaryReaderBig.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace BOTWToolset.IO +{ + /// + /// Big-endian variant of BinaryReader + /// + public class BinaryReaderBig : BinaryReader + { + public BinaryReaderBig(Stream stream) : base(stream) { } + public BinaryReaderBig(Stream stream, Encoding encoding) : base(stream, encoding) { } + public BinaryReaderBig(Stream stream, Encoding encoding, bool leaveOpen) : base(stream, encoding, leaveOpen) { } + + /// + /// Advances the stream by the specified number of bytes. + /// + /// The number of bytes to advance by. + public void Advance(long bytes) + { + BaseStream.Seek(bytes, SeekOrigin.Current); + } + + public override short ReadInt16() + { + return BitConverter.ToInt16(base.ReadBytes(2).Reverse().ToArray(), 0); + } + + public override ushort ReadUInt16() + { + return BitConverter.ToUInt16(base.ReadBytes(2).Reverse().ToArray(), 0); + } + + public override int ReadInt32() + { + return BitConverter.ToInt32(base.ReadBytes(4).Reverse().ToArray(), 0); + } + + public override uint ReadUInt32() + { + return BitConverter.ToUInt32(base.ReadBytes(4).Reverse().ToArray(), 0); + } + + public override long ReadInt64() + { + return BitConverter.ToInt64(base.ReadBytes(8).Reverse().ToArray(), 0); + } + public override ulong ReadUInt64() + { + return BitConverter.ToUInt64(base.ReadBytes(8).Reverse().ToArray(), 0); + } + + public override float ReadSingle() + { + return BitConverter.ToSingle(base.ReadBytes(4).Reverse().ToArray(), 0); + } + + public override double ReadDouble() + { + return BitConverter.ToDouble(base.ReadBytes(8).Reverse().ToArray(), 0); + } + } +} diff --git a/botw-toolset/IO/BinaryWriterBig.cs b/botw-toolset/IO/BinaryWriterBig.cs new file mode 100644 index 0000000..3b014ab --- /dev/null +++ b/botw-toolset/IO/BinaryWriterBig.cs @@ -0,0 +1,57 @@ +using System.IO; +using System.Text; + +//TODO: reverse bytes on each function + +namespace BOTWToolset.IO +{ + /// + /// Big-endian variant of BinaryWriter + /// + public class BinaryWriterBig : BinaryWriter + { + public BinaryWriterBig(Stream output) : base(output) { } + public BinaryWriterBig(Stream output, Encoding encoding) : base(output, encoding) { } + public BinaryWriterBig(Stream output, Encoding encoding, bool leaveOpen) : base(output, encoding, leaveOpen) { } + + public override void Write(short value) + { + base.Write(value); + } + + public override void Write(ushort value) + { + base.Write(value); + } + + public override void Write(int value) + { + base.Write(value); + } + + public override void Write(uint value) + { + base.Write(value); + } + + public override void Write(long value) + { + base.Write(value); + } + + public override void Write(ulong value) + { + base.Write(value); + } + + public override void Write(float value) + { + base.Write(value); + } + + public override void Write(double value) + { + base.Write(value); + } + } +} diff --git a/botw-toolset/IO/EXTM/Grass.cs b/botw-toolset/IO/EXTM/Grass.cs index e62eb9a..663192a 100644 --- a/botw-toolset/IO/EXTM/Grass.cs +++ b/botw-toolset/IO/EXTM/Grass.cs @@ -1,4 +1,6 @@ -namespace BOTWToolset.IO.EXTM +using System.IO; + +namespace BOTWToolset.IO.EXTM { /// /// Stores info on grass data in an .extm file @@ -16,5 +18,28 @@ public byte B { get => _b; set => _b = value; } private byte _b; + + public static Grass[] FromBytes(byte[] bytes) + { + using (var r = new BinaryReaderBig(new MemoryStream(bytes))) + { + Grass[] grasses = new Grass[r.BaseStream.Length / 4]; + + for (int i = 0; i < grasses.Length; i++) + { + Grass g = new Grass + { + Height = r.ReadByte(), + R = r.ReadByte(), + G = r.ReadByte(), + B = r.ReadByte() + }; + + grasses[i] = g; + } + + return grasses; + } + } } } diff --git a/botw-toolset/IO/EXTM/GrassColor.cs b/botw-toolset/IO/EXTM/GrassColor.cs deleted file mode 100644 index 678c159..0000000 --- a/botw-toolset/IO/EXTM/GrassColor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace BOTWToolset.IO.EXTM -{ - class GrassColor - { - public static readonly Color[] Colors = new Color[] - { - new Color(95, 142, 74), // Green grass - new Color(170, 193, 75), // Yellow grass - new Color(206, 122, 66), // Wood chips 00 - new Color(181, 107, 57), // Wood chips 01 - new Color(160, 95, 51), // Wood chips 02 - new Color(137, 81, 44), // Wood chips 03 - new Color(137, 106, 72), // Wood chips 04 reeds - new Color(79, 112, 68) // Wood chips 05 leaves - }; - } -} diff --git a/botw-toolset/IO/EXTM/Water.cs b/botw-toolset/IO/EXTM/Water.cs index 814f7dc..f2c308a 100644 --- a/botw-toolset/IO/EXTM/Water.cs +++ b/botw-toolset/IO/EXTM/Water.cs @@ -1,4 +1,6 @@ -namespace BOTWToolset.IO.EXTM +using System.IO; + +namespace BOTWToolset.IO.EXTM { /// /// Stores info on water data in an .extm file @@ -26,5 +28,32 @@ return _matIndex; } } + + public static Water[] FromBytes(byte[] bytes) + { + using (var r = new BinaryReaderBig(new MemoryStream(bytes))) + { + Water[] waters = new Water[r.BaseStream.Length / 8]; + + for (int i = 0; i < waters.Length; i++) + { + Water w = new Water + { + Height = r.ReadUInt16(), + XAxisFlowRate = r.ReadUInt16(), + ZAxisFlowRate = r.ReadUInt16() + }; + + // Skip material index checksum + r.BaseStream.Seek(1, SeekOrigin.Current); + + w.MaterialIndex = r.ReadByte(); + + waters[i] = w; + } + + return waters; + } + } } } diff --git a/botw-toolset/IO/EXTM/WaterColor.cs b/botw-toolset/IO/EXTM/WaterColor.cs deleted file mode 100644 index 4320a14..0000000 --- a/botw-toolset/IO/EXTM/WaterColor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace BOTWToolset.IO.EXTM -{ - class WaterColor - { - public static readonly Color[] Colors = new Color[] - { - new Color(73, 137, 255), // Water - new Color(114, 255, 210), // Hot water - new Color(173, 73, 255), // Poison water - new Color(255, 97, 0), // Lava - new Color(142, 211, 255), // Cold water - new Color(86, 44, 0), // Bog (mud) - new Color(102, 158, 255), // Clear water 01 - new Color(52, 99, 181) // Ocean - }; - } -} diff --git a/botw-toolset/IO/GridColors.cs b/botw-toolset/IO/GridColors.cs new file mode 100644 index 0000000..299d8e2 --- /dev/null +++ b/botw-toolset/IO/GridColors.cs @@ -0,0 +1,128 @@ +using System.Drawing; + +namespace BOTWToolset.IO +{ + /// + /// Contains colors used by the TSCB grid. + /// + public class GridColors + { + public static readonly Color[] GrassColors = new Color[] + { + Color.FromArgb(255, 95, 142, 74), // Green grass + Color.FromArgb(255, 255, 193, 75), // Yellow grass + Color.FromArgb(255, 206, 122, 66), // Wood chips 00 + Color.FromArgb(255, 181, 107, 57), // Wood chips 01 + Color.FromArgb(255, 160, 95, 51), // Wood chips 02 + Color.FromArgb(255, 137, 81, 44), // Wood chips 03 + Color.FromArgb(255, 137, 106, 72), // Wood chips 04 reeds + Color.FromArgb(255, 79, 112, 68) // Wood chips 05 leaves + }; + + public static readonly Color[] WaterColors = new Color[] + { + Color.FromArgb(255, 73, 137, 255), // Water + Color.FromArgb(255, 114, 255, 210), // Hot water + Color.FromArgb(255, 173, 73, 255), // Poison water + Color.FromArgb(255, 255, 97, 0), // Lava + Color.FromArgb(255, 142, 211, 255), // Cold water + Color.FromArgb(255, 86, 44, 0), // Bog (mud) + Color.FromArgb(255, 102, 158, 255), // Clear water 01 + Color.FromArgb(255, 52, 99, 181) // Ocean + }; + + public static readonly Color[] MaterialColors = new Color[] + { + Color.FromArgb(255, 45, 153, 0), // 00 - Grass (basic) + Color.FromArgb(255, 183, 183, 183), // 01 - Light stone - Rough rock A + Color.FromArgb(255, 170, 68, 66), // 02 - Red cube rock + Color.FromArgb(255, 163, 163, 163), // 03 - Rough rock + Color.FromArgb(255, 183, 183, 183), // 04 - White rock + Color.FromArgb(255, 32, 86, 32), // 05 - Brown Soil and Grass (consolidated green) + Color.FromArgb(255, 253, 251, 163), // 06 - sand beige + Color.FromArgb(255, 252, 255, 112), // 07 - sandy beach (for coast) + Color.FromArgb(255, 250, 250, 250), // 08 - snow (flat) + Color.FromArgb(255, 255, 255, 255), // 09 - rock gravel + Color.FromArgb(255, 255, 255, 255), // 10 - earth and rock - hard soil + Color.FromArgb(255, 255, 255, 255), // 11 - lawn & earth + Color.FromArgb(255, 255, 255, 255), // 12 - futago rock + Color.FromArgb(255, 255, 255, 255), // 13 - cobblestones & lawn + Color.FromArgb(255, 255, 255, 255), // 14 - fallen leaves A + Color.FromArgb(255, 255, 255, 255), // 15 - thin rock and grass + Color.FromArgb(255, 255, 255, 255), // 16 - cliff (white) A & grass + Color.FromArgb(255, 255, 255, 255), // 17 - cliff (white) A + Color.FromArgb(255, 255, 255, 255), // 18 - cliff (white) B + Color.FromArgb(255, 255, 255, 255), // 19 - rock (red) A + Color.FromArgb(255, 255, 255, 255), // 20 - maruwa & sand + Color.FromArgb(255, 255, 255, 255), // 21 - rectangular rock - orange + Color.FromArgb(255, 255, 255, 255), // 22 - rough rock B + Color.FromArgb(255, 255, 255, 255), // 23 - soil (hardened red + Color.FromArgb(255, 255, 255, 255), // 24 - sat - farm (for fields) + Color.FromArgb(255, 255, 255, 255), // 25 - soil (compacted pebble) + Color.FromArgb(255, 255, 255, 255), // 26 - green grass and stone + Color.FromArgb(255, 255, 255, 255), // 27 - stormy rocks + Color.FromArgb(255, 255, 255, 255), // 28 - earth and sand A + Color.FromArgb(255, 255, 255, 255), // 29 - grass (dead) dried grass field + Color.FromArgb(255, 255, 255, 255), // 30 - world end - the end of the world + Color.FromArgb(255, 255, 255, 255), // 31 - death mountain rock (volcano) B + Color.FromArgb(255, 255, 255, 255), // 32 - sat (mass / volcano) + Color.FromArgb(255, 255, 255, 255), // 33 - snow bumpy + Color.FromArgb(255, 255, 255, 255), // 34 - sat (brown) + Color.FromArgb(255, 255, 255, 255), // 35 - earth (mass) + Color.FromArgb(255, 255, 255, 255), // 36 - fallen leaves B + Color.FromArgb(255, 255, 255, 255), // 37 - fallen leaves C + Color.FromArgb(255, 255, 255, 255), // 38 - moss field A + Color.FromArgb(255, 255, 255, 255), // 39 - moss field B + Color.FromArgb(255, 255, 255, 255), // 40 - cracked soil + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255), + Color.FromArgb(255, 255, 255, 255) + }; + } +} diff --git a/botw-toolset/IO/HGHT.cs b/botw-toolset/IO/HGHT.cs new file mode 100644 index 0000000..8f0e270 --- /dev/null +++ b/botw-toolset/IO/HGHT.cs @@ -0,0 +1,30 @@ +using System.IO; + +namespace BOTWToolset.IO +{ + /// + /// Contains data for .hght files. + /// + public class HGHT + { + public ushort[] Heights; + + public static HGHT FromBytes(byte[] bytes) + { + using (var r = new BinaryReader(new MemoryStream(bytes))) + { + HGHT h = new HGHT + { + Heights = new ushort[r.BaseStream.Length / 2] + }; + + for (int i = 0; i < h.Heights.Length; i++) + { + h.Heights[i] = r.ReadUInt16(); + } + + return h; + } + } + } +} diff --git a/botw-toolset/IO/MATE.cs b/botw-toolset/IO/MATE.cs new file mode 100644 index 0000000..4ff0a1d --- /dev/null +++ b/botw-toolset/IO/MATE.cs @@ -0,0 +1,41 @@ +using System.IO; + +namespace BOTWToolset.IO +{ + public class MATE + { + public byte Material0 { get => _material0; set => _material0 = value; } + private byte _material0; + + public byte Material1 { get => _material1; set => _material1 = value; } + private byte _material1; + + public byte BlendWeight { get => _blendWeight; set => _blendWeight = value; } + private byte _blendWeight; + + public static MATE[] FromBytes(byte[] bytes) + { + using (var r = new BinaryReader(new MemoryStream(bytes))) + { + MATE[] mats = new MATE[r.BaseStream.Length / 4]; + + for (int i = 0; i < mats.Length; i++) + { + MATE m = new MATE + { + Material0 = r.ReadByte(), + Material1 = r.ReadByte(), + BlendWeight = r.ReadByte() + }; + + // Skip unknown byte + r.BaseStream.Seek(1, SeekOrigin.Current); + + mats[i] = m; + } + + return mats; + } + } + } +} diff --git a/botw-toolset/IO/SARC/SARC.cs b/botw-toolset/IO/SARC/SARC.cs new file mode 100644 index 0000000..83c2b4a --- /dev/null +++ b/botw-toolset/IO/SARC/SARC.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BOTWToolset.IO.SARC +{ + public class SARC + { + //SARC header + public string Magic { get => _magic; set => _magic = value; } + private string _magic; + + public ushort HeaderLength { get => _headerLength; set => _headerLength = value; } + private ushort _headerLength; + + public bool IsBigEndian { get => _isBigEndian; set => _isBigEndian = value; } + private bool _isBigEndian; + + public uint FileSize { get => _fileSize; set => _fileSize = value; } + private uint _fileSize; + + public uint DataOffset { get => _dataOffset; set => _dataOffset = value; } + private uint _dataOffset; + + public ushort Version { get => _version; set => _version = value; } + private ushort _version; + + public SFAT SFAT; + + public SFNT SFNT; + + public byte[][] Files; + + public static SARC FromBytes(Stream stream) + { + SARC s = new SARC(); + + using (var r = new BinaryReaderBig(stream)) + { + //SARC header + s.Magic = new string(r.ReadChars(4)); + if (s.Magic != "SARC") // TODO: raise an exception here instead of returning null + return null; + + s.HeaderLength = r.ReadUInt16(); + s.IsBigEndian = r.ReadUInt16() == 0xFEFF; + s.FileSize = r.ReadUInt32(); + s.DataOffset = r.ReadUInt32(); + s.Version = r.ReadUInt16(); + + r.Advance(2); // Skip 2 reserved bytes + + //SFAT header + string sfat_magic = new string(r.ReadChars(4)); + if (sfat_magic != "SFAT") // TODO: raise an exception here instead of returning null + return null; + + ushort sfat_headerlen = r.ReadUInt16(); + ushort sfat_nodecount = r.ReadUInt16(); + uint sfat_hashkey = r.ReadUInt32(); + + //SFAT nodes + SFATNode[] sfat_nodes = new SFATNode[sfat_nodecount]; + + for (int i = 0; i < sfat_nodecount; i++) + { + SFATNode sfan = new SFATNode( + r.ReadUInt32(), //hash + r.ReadUInt32(), //attributes + r.ReadUInt32(), //node file beginning + r.ReadUInt32() //node file end + ); + + sfat_nodes[i] = sfan; + } + + s.SFAT = new SFAT + { + Magic = sfat_magic, + HeaderLength = sfat_headerlen, + NodeCount = sfat_nodecount, + HashKey = sfat_hashkey, + Nodes = sfat_nodes + }; + + //SFNT header + var sfnt_magic = new string(r.ReadChars(4)); + if (sfnt_magic != "SFNT") // TODO: raise an exception here instead of returning null + return null; + var sfnt_headerlen = r.ReadUInt16(); + + r.Advance(2); // Skip 2 reserved bytes + + // Read every filename (null-terminated strings) + List file_names = new List(); + List cur_string = new List(); + + int bytes_to_iterate = (int)((s.DataOffset - r.BaseStream.Position) / 4); + + for (int i = 0; i < bytes_to_iterate; i++) + { + byte[] four_bytes = r.ReadBytes(4); + + if (Array.Exists(four_bytes, e => e == 0)) // If this four-byte array contains zeroes + { + // Remove the zeroes + byte[] bytes_filter = four_bytes.Where(x => x != 0).ToArray(); + + //Add the valid characters + foreach (var byte_s in bytes_filter) + { + cur_string.Add(byte_s); + } + + file_names.Add(System.Text.Encoding.UTF8.GetString(cur_string.ToArray())); + cur_string.Clear(); + } + else + { + cur_string.Add(four_bytes[0]); + cur_string.Add(four_bytes[1]); + cur_string.Add(four_bytes[2]); + cur_string.Add(four_bytes[3]); + } + } + + s.SFNT = new SFNT + { + Magic = sfnt_magic, + HeaderLength = sfnt_headerlen, + FileNames = file_names.ToArray() + }; + + s.Files = new byte[s.SFAT.Nodes.Length][]; // An array with byte[] arrays representing every file + + // Use the offsets of every file to read the file + for (int i = 0; i < s.SFAT.Nodes.Length; i++) + { + SFATNode f = s.SFAT.Nodes[i]; + + // Seek to the beginning of the node file + r.BaseStream.Seek(s.DataOffset + f.NodeFileDataBegin, SeekOrigin.Begin); + + // Read the bytes from beginning to end + s.Files[i] = r.ReadBytes((int)(f.NodeFileDataEnd - f.NodeFileDataBegin)); + } + } + + return s; + } + + public static SARC ReadFile(string file) + { + if (File.Exists(file)) + { + return FromBytes(File.Open(file, FileMode.Open)); + } + else + { + throw new FileNotFoundException("Cannot find SARC file to read."); + } + } + + public static byte[] GetBytes(SARC sarc) + { + // TODO: Make this an actual function + List bytes = new List(); + + return bytes.ToArray(); + } + + public static void WriteFiles(SARC sarc, string folder) + { + //TODO: Finish this function + byte[][] files = (byte[][])sarc.Files.Clone(); + + for (int i = 0; i < files.Length; i++) + { + var file_name = sarc.SFNT.FileNames[i]; + + // Get combined path (folder + file name) + var file_path = Path.Combine(folder, file_name); + + using (var r = new BinaryWriterBig(File.OpenWrite(file_path), System.Text.Encoding.UTF8)) + { + + } + } + } + } +} diff --git a/botw-toolset/IO/SARC/SFAT.cs b/botw-toolset/IO/SARC/SFAT.cs new file mode 100644 index 0000000..77bc37d --- /dev/null +++ b/botw-toolset/IO/SARC/SFAT.cs @@ -0,0 +1,19 @@ +namespace BOTWToolset.IO.SARC +{ + public struct SFAT + { + public string Magic { get => _magic; set => _magic = value; } + private string _magic; + + public ushort HeaderLength { get => _headerLength; set => _headerLength = value; } + private ushort _headerLength; + + public ushort NodeCount { get => _nodeCount; set => _nodeCount = value; } + private ushort _nodeCount; + + public uint HashKey { get => _hashKey; set => _hashKey = value; } + private uint _hashKey; + + public SFATNode[] Nodes; + } +} diff --git a/botw-toolset/IO/SARC/SFATNode.cs b/botw-toolset/IO/SARC/SFATNode.cs new file mode 100644 index 0000000..8959bea --- /dev/null +++ b/botw-toolset/IO/SARC/SFATNode.cs @@ -0,0 +1,25 @@ +namespace BOTWToolset.IO.SARC +{ + public struct SFATNode + { + public uint FileNameHash { get => _fileNameHash; set => _fileNameHash = value; } + private uint _fileNameHash; + + public uint FileAttributes { get => _fileAttributes; set => _fileAttributes = value; } + private uint _fileAttributes; + + public uint NodeFileDataBegin { get => _nodeFileDataBegin; set => _nodeFileDataBegin = value; } + private uint _nodeFileDataBegin; + + public uint NodeFileDataEnd { get => _nodeFileDataEnd; set => _nodeFileDataEnd = value; } + private uint _nodeFileDataEnd; + + public SFATNode(uint file_name_hash, uint file_attrs, uint node_file_begin, uint node_file_end) + { + _fileNameHash = file_name_hash; + _fileAttributes = file_attrs; + _nodeFileDataBegin = node_file_begin; + _nodeFileDataEnd = node_file_end; + } + } +} diff --git a/botw-toolset/IO/SARC/SFNT.cs b/botw-toolset/IO/SARC/SFNT.cs new file mode 100644 index 0000000..6b2681a --- /dev/null +++ b/botw-toolset/IO/SARC/SFNT.cs @@ -0,0 +1,13 @@ +namespace BOTWToolset.IO.SARC +{ + public struct SFNT + { + public string Magic { get => _magic; set => _magic = value; } + private string _magic; + + public ushort HeaderLength { get => _headerLength; set => _headerLength = value; } + private ushort _headerLength; + + public string[] FileNames; + } +} diff --git a/botw-toolset/IO/TSCB/AreaInfo.cs b/botw-toolset/IO/TSCB/AreaInfo.cs index 6c1e0db..8027356 100644 --- a/botw-toolset/IO/TSCB/AreaInfo.cs +++ b/botw-toolset/IO/TSCB/AreaInfo.cs @@ -47,23 +47,12 @@ public bool HasGrass { get => _hasGrass; set => _hasGrass = value; } private bool _hasGrass = false; - public uint Offset; + public uint Offset { get => _offset; set => _offset = value; } + private uint _offset; - public AreaInfo(float x_pos, float z_pos, float area_size, float min_terrain_height, float max_terrain_height, - float min_water_height, float max_water_height, uint unknown_1, uint file_base, uint unknown_2, uint unknown_3, uint ref_extra) - { - PositionX = x_pos; - PositionZ = z_pos; - AreaSize = area_size; - MinTerrainHeight = min_terrain_height; - MaxTerrainHeight = max_terrain_height; - MinWaterHeight = min_water_height; - MaxWaterHeight = max_water_height; - Unknown1 = unknown_1; - FileBase = file_base; - Unknown2 = unknown_2; - Unknown3 = unknown_3; - ReferenceExtra = ref_extra; - } + public uint ExtraInfoLength { get => _extraInfoLength; set => _extraInfoLength = value; } + private uint _extraInfoLength; + + public AreaInfo() { } } } diff --git a/botw-toolset/IO/TSCB/GridConverter.cs b/botw-toolset/IO/TSCB/GridConverter.cs new file mode 100644 index 0000000..2765452 --- /dev/null +++ b/botw-toolset/IO/TSCB/GridConverter.cs @@ -0,0 +1,28 @@ +namespace BOTWToolset.IO.TSCB +{ + /// + /// Manages the TSCB tab's pixel map. + /// + static class GridConverter + { + /// + /// Converts a Z-Curve position to (X, Y) coordinates. + /// + /// Z-Curve index + /// int array with X and Y coordinates. + public static int[] ZCurveToXY(int index) + { + int x = 0; + int y = 0; + + // Shift bits to the right to get untangled X and Y bytes + for (int i = 0; i < 16; i++) + { + x = ((index & (1 << (i * 2))) >> i) | x; + y = ((index & (2 << (i * 2))) >> i + 1) | y; + } + + return new int[] { x, y }; + } + } +} diff --git a/botw-toolset/IO/TSCB/TSCB.cs b/botw-toolset/IO/TSCB/TSCB.cs index 0fb6475..dfbf140 100644 --- a/botw-toolset/IO/TSCB/TSCB.cs +++ b/botw-toolset/IO/TSCB/TSCB.cs @@ -1,5 +1,6 @@ using BOTWToolset.Debugging; using System; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,21 +9,83 @@ namespace BOTWToolset.IO.TSCB /// /// Interacts with .tcsb files. /// - static class TSCB + public class TSCB { - public static TSCBInfo ReadFile(string file) + public const byte HeaderLength = 48; // Length of the header, in bytes + + public string Signature { get => _signature; set => _signature = value; } + private string _signature; + + public uint Version { get => _version; set => _version = value; } + private uint _version; + + public uint FileBaseOffset { get => _fileBaseOffset; set => _fileBaseOffset = value; } + private uint _fileBaseOffset; + + public float WorldScale { get => _worldScale; set => _worldScale = value.Clamp(0f, 800.0f); } + private float _worldScale; + + public float TerrainMaxHeight { get => _terrainMaxHeight; set => _terrainMaxHeight = value.Clamp(0f, 800.0f); } + private float _terrainMaxHeight; + + public byte[] MaterialInfoOffsets; + + public uint MaterialInfoLength { get => _materialInfoLength; set => _materialInfoLength = value; } + private uint _materialInfoLength; + + public byte[] AreaArrayOffsets; + + public uint AreaArrayLength { get => _areaArrayLength; set => _areaArrayLength = value; } + private uint _areaArrayLength; + + public MaterialInfo[] MaterialInfo; + + public AreaInfo[] AreaInfo; + + public float TileSize; + + public string[] FileNames; + + /// + /// Reads a .tscb file and returns a TSCBInfo containing its data. + /// + /// The .tscb file. + /// + public static TSCB ReadFile(string file) { if (File.Exists(file)) { - TSCBInfo t = new TSCBInfo(); + TSCB t = new TSCB(); - using (var r = new BinaryReader(File.Open(file, FileMode.Open))) + // Use big-endian + using (var r = new BinaryReaderBig(File.Open(file, FileMode.Open))) { // Set header info from file on the new TSCBInfo - t.SetHeaderInfo(r); + t.Signature = new string(r.ReadChars(4)); + t.Version = r.ReadByte(); - // Skip over mat info offsets - r.BaseStream.Seek((t.MaterialInfoLength * 4) + 4, SeekOrigin.Current); + // Skip the 3 extra version bytes + r.BaseStream.Seek(3, SeekOrigin.Current); + + // Skip 4 bytes of "00 00 00 01" + r.BaseStream.Seek(4, SeekOrigin.Current); + + t.FileBaseOffset = r.ReadUInt32(); + t.WorldScale = r.ReadSingle(); + t.TerrainMaxHeight = r.ReadSingle(); + t.MaterialInfoLength = r.ReadUInt32(); + t.AreaArrayLength = r.ReadUInt32(); + + // Skip 8 bytes of padding + r.Advance(8); + + t.TileSize = r.ReadSingle(); + + // Skip 4 bytes of "00 00 00 08" + r.Advance(4); + + // Read mat info offsets + t.MaterialInfoOffsets = r.ReadBytes((int)((t.MaterialInfoLength * 4) + 4)); BOTWConsole.Log($"Offset before material iteration: {r.BaseStream.Position}"); @@ -32,11 +95,11 @@ namespace BOTWToolset.IO.TSCB // Initialize every mat info, then add to the array for (int i = 0; i < t.MaterialInfoLength; i++) { - uint index = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); // Reverse byte order - use big endian - float tex_u = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float tex_v = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float unk_1 = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float unk_2 = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); + uint index = r.ReadUInt32(); + float tex_u = r.ReadSingle(); + float tex_v = r.ReadSingle(); + float unk_1 = r.ReadSingle(); + float unk_2 = r.ReadSingle(); MaterialInfo matInfo = new MaterialInfo(index, tex_u, tex_v, unk_1, unk_2); @@ -45,64 +108,73 @@ namespace BOTWToolset.IO.TSCB BOTWConsole.Log($"Offset before area offset iteration: {r.BaseStream.Position}"); - // Skip over area offsets (current position plus area array bytes) - r.BaseStream.Seek(t.AreaArrayLength * 4, SeekOrigin.Current); + // Read area offsets + t.AreaArrayOffsets = r.ReadBytes((int)(t.AreaArrayLength * 4)); BOTWConsole.Log($"Offset before area iteration: {r.BaseStream.Position}"); t.AreaInfo = new AreaInfo[t.AreaArrayLength]; + // Read every area info entry for (int i = 0; i < t.AreaArrayLength; i++) { uint offset = (uint)r.BaseStream.Position; - float xpos = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); // Reverse byte order - use big endian - float zpos = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float area_size = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float min_terrain_height = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float max_terrain_height = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float min_water_height = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - float max_water_height = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - uint unk_1 = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); + float xpos = r.ReadSingle(); + float zpos = r.ReadSingle(); + float area_size = r.ReadSingle(); + float min_terrain_height = r.ReadSingle(); + float max_terrain_height = r.ReadSingle(); + float min_water_height = r.ReadSingle(); + float max_water_height = r.ReadSingle(); + uint unk_1 = r.ReadUInt32(); if (unk_1 == 0) - { // If this unknown isn't equal to 2, skip the extra byte coming after it - uint next_val = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); + { // If this unknown is equal to 0, skip the extra byte coming after it + uint next_val = r.ReadUInt32(); - if (next_val == 1) //if the next value is extra unneeded info + if (next_val != 1) // If the next value isn't extra unneeded info { - - } - else //else, if the value is valid - { - r.BaseStream.Seek(-4, SeekOrigin.Current); + r.Advance(-4); } } - uint file_base = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); - uint unk_2 = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); - uint unk_3 = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); - uint ref_extra = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); + uint file_base = r.ReadUInt32(); + uint unk_2 = r.ReadUInt32(); + uint unk_3 = r.ReadUInt32(); + uint ref_extra = r.ReadUInt32(); - AreaInfo areaInfo = new AreaInfo(xpos, zpos, area_size, min_terrain_height, max_terrain_height, min_water_height, - max_water_height, unk_1, file_base, unk_2, unk_3, ref_extra); + AreaInfo areaInfo = new AreaInfo + { + PositionX = xpos, + PositionZ = zpos, + AreaSize = area_size, + MinTerrainHeight = min_terrain_height, + MaxTerrainHeight = max_terrain_height, + MinWaterHeight = min_water_height, + MaxWaterHeight = max_water_height, + Unknown1 = unk_1, + FileBase = file_base, + Unknown2 = unk_2, + Unknown3 = unk_3, + ReferenceExtra = ref_extra, + Offset = offset + }; - areaInfo.Offset = offset; - - uint extra_info_len = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); //Usually 0, 4, or 8 + areaInfo.ExtraInfoLength = r.ReadUInt32(); //Usually 0, 4, or 8 if (ref_extra == 4) { - if (extra_info_len == 8) + if (areaInfo.ExtraInfoLength == 8) { //Skip the extra "20" after the 8, as well as the extra info areaInfo.HasGrass = true; areaInfo.HasWater = true; - r.BaseStream.Seek(36, SeekOrigin.Current); + r.Advance(36); } else //If the length is 4 { - var bytes = r.ReadBytes(16).Reverse().ToArray(); - if (bytes[1] == 0) //If the 2nd byte is 0 + var bytes = r.ReadBytes(16).ToArray(); + if (bytes[7] == 0) //If byte 7 equals 0 areaInfo.HasGrass = true; else //Else if the 2nd byte should be anything else (should always be 1) areaInfo.HasWater = true; @@ -110,7 +182,7 @@ namespace BOTWToolset.IO.TSCB } else //If the extra info flags aren't set, go back 4 { - r.BaseStream.Seek(-4, SeekOrigin.Current); + r.Advance(-4); } t.AreaInfo[i] = areaInfo; @@ -142,5 +214,106 @@ namespace BOTWToolset.IO.TSCB throw new FileNotFoundException("Cannot find .tscb file to read."); } } + + /// + /// Writes TSCB data to a byte array. + /// + /// TSCBInfo that contains data to write. + /// Byte array containing the TSCB data. + public static byte[] GetBytes(TSCB tscb) + { + List b = new List(); + + //TSCB header + b.AddRange(new byte[] { + 0x54, 0x53, 0x43, 0x42, + 0x0A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01 + }); + + b.AddRange(BitConverter.GetBytes(tscb.FileBaseOffset).Reverse()); + b.AddRange(BitConverter.GetBytes(tscb.WorldScale).Reverse()); + b.AddRange(BitConverter.GetBytes(tscb.TerrainMaxHeight).Reverse()); + b.AddRange(BitConverter.GetBytes(tscb.MaterialInfoLength).Reverse()); + b.AddRange(BitConverter.GetBytes(tscb.AreaArrayLength).Reverse()); + + // Padding + b.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + + b.AddRange(BitConverter.GetBytes(tscb.TileSize).Reverse()); + + b.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x08 }); + + // Add material info offsets + b.AddRange(tscb.MaterialInfoOffsets); + + // Write material infos + foreach (var mat in tscb.MaterialInfo) + { + b.AddRange(BitConverter.GetBytes(mat.MaterialIndex).Reverse()); + b.AddRange(BitConverter.GetBytes(mat.TextureU).Reverse()); + b.AddRange(BitConverter.GetBytes(mat.TextureV).Reverse()); + b.AddRange(BitConverter.GetBytes(mat.Unknown1).Reverse()); + b.AddRange(BitConverter.GetBytes(mat.Unknown2).Reverse()); + } + + b.AddRange(tscb.AreaArrayOffsets); + + foreach (var area in tscb.AreaInfo) + { + // Add area bytes + b.AddRange(BitConverter.GetBytes(area.PositionX).Reverse()); + b.AddRange(BitConverter.GetBytes(area.PositionZ).Reverse()); + + b.AddRange(BitConverter.GetBytes(area.AreaSize).Reverse()); + b.AddRange(BitConverter.GetBytes(area.MinTerrainHeight).Reverse()); + b.AddRange(BitConverter.GetBytes(area.MaxTerrainHeight).Reverse()); + b.AddRange(BitConverter.GetBytes(area.MinWaterHeight).Reverse()); + b.AddRange(BitConverter.GetBytes(area.MaxWaterHeight).Reverse()); + b.AddRange(BitConverter.GetBytes(area.Unknown1).Reverse()); + b.AddRange(BitConverter.GetBytes(area.FileBase).Reverse()); + b.AddRange(BitConverter.GetBytes(area.Unknown2).Reverse()); + b.AddRange(BitConverter.GetBytes(area.Unknown3).Reverse()); + b.AddRange(BitConverter.GetBytes(area.ReferenceExtra).Reverse()); + + if (area.ReferenceExtra == 4) + { + b.AddRange(BitConverter.GetBytes(area.ExtraInfoLength).Reverse()); + + if (area.ExtraInfoLength == 8) + { + b.AddRange(new byte[] { + 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00 + }); + } + else + { + byte[] grass = new byte[] { 0x00, 0x00, 0x00, 0x00 }; + byte[] water = new byte[] { 0x00, 0x00, 0x00, 0x01 }; + + b.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x03 }); + b.AddRange(area.HasGrass == true ? grass : water); + b.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x01 }); + b.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 }); + } + } + } + + foreach (string filename in tscb.FileNames) + { + b.AddRange(System.Text.Encoding.ASCII.GetBytes(filename)); + } + + // TODO: Make this an actual function + return b.ToArray(); + } } } diff --git a/botw-toolset/IO/TSCB/TSCBInfo.cs b/botw-toolset/IO/TSCB/TSCBInfo.cs deleted file mode 100644 index ce38364..0000000 --- a/botw-toolset/IO/TSCB/TSCBInfo.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.IO; -using System.Linq; - -namespace BOTWToolset.IO.TSCB -{ - /// - /// Contains data for a TCSB file. - /// - public class TSCBInfo - { - public const byte HeaderLength = 48; // Length of the header, in bytes - - public string Signature { get => _signature; set => _signature = value; } - private string _signature; - - public uint Version { get => _version; set => _version = value; } - private uint _version; - - public uint FileBaseOffset { get => _fileBaseOffset; set => _fileBaseOffset = value; } - private uint _fileBaseOffset; - - public float WorldScale { get => _worldScale; set => _worldScale = value.Clamp(0f, 800.0f); } - private float _worldScale; - - public float TerrainMaxHeight { get => _terrainMaxHeight; set => _terrainMaxHeight = value.Clamp(0f, 800.0f); } - private float _terrainMaxHeight; - - public uint MaterialInfoLength { get => _materialInfoLength; set => _materialInfoLength = value; } - private uint _materialInfoLength; - - public MaterialInfo[] MaterialInfo; - - public uint AreaArrayLength { get => _areaArrayLength; set => _areaArrayLength = value; } - private uint _areaArrayLength; - - public AreaInfo[] AreaInfo; - - public float TileSize; - - public string[] FileNames; - - public void SetHeaderInfo(BinaryReader r) - { - Signature = new string(r.ReadChars(4)); - Version = r.ReadUInt32(); - - r.BaseStream.Seek(4, SeekOrigin.Current); // Skip 4 bytes of "00 00 00 01" - - FileBaseOffset = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); // Reverse byte order - use big endian - WorldScale = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - TerrainMaxHeight = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - MaterialInfoLength = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); - AreaArrayLength = BitConverter.ToUInt32(r.ReadBytes(4).Reverse().ToArray(), 0); - - r.BaseStream.Seek(8, SeekOrigin.Current);// Skip 8 bytes of padding - - TileSize = BitConverter.ToSingle(r.ReadBytes(4).Reverse().ToArray(), 0); - - r.BaseStream.Seek(4, SeekOrigin.Current); // Skip 4 bytes of "00 00 00 08" - } - } -} diff --git a/botw-toolset/IO/Yaz0/Yaz0.cs b/botw-toolset/IO/Yaz0/Yaz0.cs new file mode 100644 index 0000000..7c0a5ce --- /dev/null +++ b/botw-toolset/IO/Yaz0/Yaz0.cs @@ -0,0 +1,294 @@ +using BOTWToolset.Exceptions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BOTWToolset.IO.Yaz0 +{ + public class Yaz0 + { + public string Magic { get => _magic; set => _magic = value; } + private string _magic; + + public uint UncompressedDataSize { get => _uncompressedDataSize; set => _uncompressedDataSize = value; } + private uint _uncompressedDataSize; + + public uint DataAlignment { get => _dataAlignment; set => _dataAlignment = value; } + private uint _dataAlignment; + + public byte[] Bytes; + + private static int s_num_bytes1, s_match_pos; + private static bool s_prev_flag = false; + + public static Yaz0 ReadFile(string file) + { + if (File.Exists(file)) + { + Yaz0 y = new Yaz0(); + + // Use big-endian + using (var r = new BinaryReaderBig(File.Open(file, FileMode.Open))) + { + y.Magic = new string(r.ReadChars(4)); + if (y.Magic != "Yaz0") + throw new InvalidMagicException("This file is not Yaz0-encoded."); + + y.UncompressedDataSize = r.ReadUInt32(); + y.DataAlignment = r.ReadUInt32(); + + // Seek back to beginning of file to capture all bytes + r.BaseStream.Seek(0, SeekOrigin.Begin); + + // Capture all bytes + y.Bytes = r.ReadBytes((int)r.BaseStream.Length); + } + + return y; + } + else + { + throw new FileNotFoundException("Cannot find Yaz0 file to read."); + } + } + + /// + /// Decompresses a Yaz0-encoded array of bytes. + /// + /// The array of bytes to decompress. + /// byte[] containing the decompressed data. + public static byte[] Decompress(byte[] bytes) + { + List de_bytes = new List(); + + using (var r = new BinaryReaderBig(new MemoryStream(bytes))) + { + string magic = new string(r.ReadChars(4)); + if (magic != "Yaz0") + throw new InvalidMagicException("This file is not Yaz0-encoded."); + + uint de_size = r.ReadUInt32(); + /*var data_alignment = r.ReadUInt32();*/ + r.Advance(4); + + // Skip 4 empty bytes + r.Advance(4); + + while (de_bytes.Count < de_size) + { + byte bits = r.ReadByte(); + + BitArray ba = new BitArray(new byte[] { bits }); + + for (int i = 7; i > -1 && (de_bytes.Count < de_size); i--) + { + if (ba[i]) + { + de_bytes.Add(r.ReadByte()); + } + else + { + byte byte1 = r.ReadByte(); + byte byte2 = r.ReadByte(); + + byte byte1_upper = (byte)(byte1 & 0x0F); + byte byte1_lower = (byte)(byte1 & 0xF0); + byte1_lower = (byte)(byte1_lower >> 4); + + int final_offs = ((byte1_upper << 8) | byte2) + 1; + int final_len = (byte1_lower == 0) ? r.ReadByte() + 0x12 : byte1_lower + 2; + + for (int j = 0; j < final_len; j++) + { + de_bytes.Add(de_bytes[de_bytes.Count - final_offs]); + } + } + } + + } + } + + return de_bytes.ToArray(); + } + + /// + /// Compresses a byte array using Yaz0-encoding. + /// + /// The bytes to encode. + /// Yaz0-encoded byte array. + public static byte[] Compress(byte[] bytes) + { + if (System.Text.Encoding.ASCII.GetString(new byte[] { bytes[0], bytes[1], bytes[2], bytes[3] }) == "Yaz0") + throw new InvalidMagicException("This file is already Yaz0-encoded."); + + List de_bytes = new List(); + + uint uncompressed_size = (uint)bytes.Length; + + de_bytes.AddRange(new byte[] { 0x59, 0x61, 0x7A, 0x30 }); // Yaz0 magic + de_bytes.AddRange(BitConverter.GetBytes(uncompressed_size).Reverse()); // Uncompressed size - reverse for endianness + de_bytes.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 }); // Data alignment + de_bytes.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00 }); // End of header + de_bytes.AddRange(Encode(bytes)); // Compressed data + + return de_bytes.ToArray(); + } + + private static byte[] Encode(byte[] bytes) + { + int src_pos = 0; + + byte[] dest = new byte[24]; // 8, 16, 24 + int dest_pos = 0; + + int valid_bit_cnt = 0; + byte cur_code_byte = 0; + + List output = new List(); // Output bytes + + while (src_pos < bytes.Length) + { + CompressInner(bytes, src_pos, out int num_bytes, out int match_pos); + + if (num_bytes < 3) + { + // Straight copy + dest[dest_pos] = bytes[src_pos]; + src_pos++; + dest_pos++; + + // Set flag for straight copy + cur_code_byte |= (byte)(0x80 >> valid_bit_cnt); + } + else + { + // RLE + uint dist = (uint)(src_pos - match_pos - 1); + byte byte1, byte2, byte3; + + // Requires 3 byte encoding + if (num_bytes >= 0x12) + { + byte1 = (byte)(0 | (dist >> 8)); + byte2 = (byte)(dist & 0xFF); + + dest[dest_pos++] = byte1; + dest[dest_pos++] = byte2; + + if (num_bytes > 0xFF + 0x12) + num_bytes = 0xFF + 0x12; + byte3 = (byte)(num_bytes - 0x12); + dest[dest_pos++] = byte3; + } + else // 2 byte encoding + { + byte1 = (byte)((uint)((num_bytes - 2) << 4) | (dist >> 8)); + byte2 = (byte)(dist & 0xFF); + dest[dest_pos++] = byte1; + dest[dest_pos++] = byte2; + } + src_pos += num_bytes; + } + + valid_bit_cnt++; + + // If the block is filled + if (valid_bit_cnt == 8) + { + // Write the code byte + output.Add(cur_code_byte); + + // Write any bytes in the dest buffer + for (int i = 0; i < dest_pos; i++) + { + output.Add(dest[i]); + } + + cur_code_byte = 0; + valid_bit_cnt = 0; + dest_pos = 0; + } + } + + // If it didn't finish on a whole byte, add the last code byte. + if (valid_bit_cnt > 0) + { + // Write the code byte + output.Add(cur_code_byte); + + // Write any bytes in the dest buffer + for (int i = 0; i < dest_pos; i++) + { + output.Add(dest[i]); + } + } + + return output.ToArray(); + } + + private static void CompressInner(byte[] src, int src_pos, out int out_num_bytes, out int out_match_pos) + { + if (s_prev_flag) + { + out_match_pos = s_match_pos; + s_prev_flag = false; + out_num_bytes = s_num_bytes1; + return; + } + + s_prev_flag = false; + SimpleRLEEncode(src, src_pos, out int num_bytes, out s_match_pos); + out_match_pos = s_match_pos; + + if (num_bytes >= 3) + { + SimpleRLEEncode(src, src_pos + 1, out s_num_bytes1, out s_match_pos); + + if (s_num_bytes1 >= num_bytes + 2) + { + num_bytes = 1; + s_prev_flag = true; + } + } + + out_num_bytes = num_bytes; + } + + private static void SimpleRLEEncode(byte[] src, int src_pos, out int out_num_bytes, out int out_match_pos) + { + int start_pos = src_pos - 0x400; + int num_bytes = 1; + int match_pos = 0; + + if (start_pos < 0) + start_pos = 0; + + // Search backwards for an already-encoded bit + for (int i = start_pos; i < src_pos; i++) + { + int j; + + for (j = 0; j < src.Length - src_pos; j++) + { + if (src[i + j] != src[j + src_pos]) + break; + } + + if (j > num_bytes) + { + num_bytes = j; + match_pos = i; + } + } + + out_match_pos = match_pos; + + if (num_bytes == 2) + num_bytes = 1; + + out_num_bytes = num_bytes; + } + } +}