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;
+ }
+ }
+}