mirror of
https://github.com/chev2/botw-toolset.git
synced 2026-01-07 15:52:34 +00:00
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:
parent
6f030d3de3
commit
4043969f15
18 changed files with 1171 additions and 160 deletions
65
botw-toolset/IO/BinaryReaderBig.cs
Normal file
65
botw-toolset/IO/BinaryReaderBig.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
botw-toolset/IO/BinaryWriterBig.cs
Normal file
57
botw-toolset/IO/BinaryWriterBig.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
128
botw-toolset/IO/GridColors.cs
Normal file
128
botw-toolset/IO/GridColors.cs
Normal 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
30
botw-toolset/IO/HGHT.cs
Normal 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
41
botw-toolset/IO/MATE.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
botw-toolset/IO/SARC/SARC.cs
Normal file
192
botw-toolset/IO/SARC/SARC.cs
Normal 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))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
botw-toolset/IO/SARC/SFAT.cs
Normal file
19
botw-toolset/IO/SARC/SFAT.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
25
botw-toolset/IO/SARC/SFATNode.cs
Normal file
25
botw-toolset/IO/SARC/SFATNode.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
botw-toolset/IO/SARC/SFNT.cs
Normal file
13
botw-toolset/IO/SARC/SFNT.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
botw-toolset/IO/TSCB/GridConverter.cs
Normal file
28
botw-toolset/IO/TSCB/GridConverter.cs
Normal 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
294
botw-toolset/IO/Yaz0/Yaz0.cs
Normal file
294
botw-toolset/IO/Yaz0/Yaz0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue