IO additions, changes

Put grass & water colors in a single claass (grid colors), WIP material colors. Added Yaz0 & SARC classes. Added big-endian binary reader.
This commit is contained in:
Chev 2021-01-10 20:04:31 -08:00
parent 6f030d3de3
commit 4043969f15
18 changed files with 1171 additions and 160 deletions

View file

@ -0,0 +1,65 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
namespace BOTWToolset.IO
{
/// <summary>
/// Big-endian variant of BinaryReader
/// </summary>
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) { }
/// <summary>
/// Advances the stream by the specified number of bytes.
/// </summary>
/// <param name="bytes">The number of bytes to advance by.</param>
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);
}
}
}

View file

@ -0,0 +1,57 @@
using System.IO;
using System.Text;
//TODO: reverse bytes on each function
namespace BOTWToolset.IO
{
/// <summary>
/// Big-endian variant of BinaryWriter
/// </summary>
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);
}
}
}

View file

@ -1,4 +1,6 @@
namespace BOTWToolset.IO.EXTM
using System.IO;
namespace BOTWToolset.IO.EXTM
{
/// <summary>
/// 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;
}
}
}
}

View file

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

View file

@ -1,4 +1,6 @@
namespace BOTWToolset.IO.EXTM
using System.IO;
namespace BOTWToolset.IO.EXTM
{
/// <summary>
/// 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;
}
}
}
}

View file

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

View file

@ -0,0 +1,128 @@
using System.Drawing;
namespace BOTWToolset.IO
{
/// <summary>
/// Contains colors used by the TSCB grid.
/// </summary>
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)
};
}
}

30
botw-toolset/IO/HGHT.cs Normal file
View file

@ -0,0 +1,30 @@
using System.IO;
namespace BOTWToolset.IO
{
/// <summary>
/// Contains data for .hght files.
/// </summary>
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;
}
}
}
}

41
botw-toolset/IO/MATE.cs Normal file
View file

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

View file

@ -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<string> file_names = new List<string>();
List<byte> cur_string = new List<byte>();
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<byte> bytes = new List<byte>();
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))
{
}
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -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() { }
}
}

View file

@ -0,0 +1,28 @@
namespace BOTWToolset.IO.TSCB
{
/// <summary>
/// Manages the TSCB tab's pixel map.
/// </summary>
static class GridConverter
{
/// <summary>
/// Converts a Z-Curve position to (X, Y) coordinates.
/// </summary>
/// <param name="index">Z-Curve index</param>
/// <returns>int array with X and Y coordinates.</returns>
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 };
}
}
}

View file

@ -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
/// <summary>
/// Interacts with .tcsb files.
/// </summary>
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;
/// <summary>
/// Reads a .tscb file and returns a TSCBInfo containing its data.
/// </summary>
/// <param name="file">The .tscb file.</param>
/// <returns></returns>
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.");
}
}
/// <summary>
/// Writes TSCB data to a byte array.
/// </summary>
/// <param name="tscb">TSCBInfo that contains data to write.</param>
/// <returns>Byte array containing the TSCB data.</returns>
public static byte[] GetBytes(TSCB tscb)
{
List<byte> b = new List<byte>();
//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();
}
}
}

View file

@ -1,63 +0,0 @@
using System;
using System.IO;
using System.Linq;
namespace BOTWToolset.IO.TSCB
{
/// <summary>
/// Contains data for a TCSB file.
/// </summary>
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"
}
}
}

View file

@ -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.");
}
}
/// <summary>
/// Decompresses a Yaz0-encoded array of bytes.
/// </summary>
/// <param name="bytes">The array of bytes to decompress.</param>
/// <returns>byte[] containing the decompressed data.</returns>
public static byte[] Decompress(byte[] bytes)
{
List<byte> de_bytes = new List<byte>();
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();
}
/// <summary>
/// Compresses a byte array using Yaz0-encoding.
/// </summary>
/// <param name="bytes">The bytes to encode.</param>
/// <returns>Yaz0-encoded byte array.</returns>
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<byte> de_bytes = new List<byte>();
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<byte> output = new List<byte>(); // 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;
}
}
}