mirror of
				https://github.com/chev2/botw-toolset.git
				synced 2025-10-30 08:12:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			302 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using BOTWToolset.Debugging;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.IO;
 | |
| using System.Linq;
 | |
| 
 | |
| namespace BOTWToolset.IO.TSCB
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Interacts with .tcsb (terrain scene binary) files.
 | |
|     /// More info found on the <see href="https://zeldamods.org/wiki/TSCB">ZeldaMods wiki</see>.
 | |
|     /// </summary>
 | |
|     public class TSCB
 | |
|     {
 | |
|         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 = Math.Clamp(value, 0f, 800.0f); }
 | |
|         private float _worldScale;
 | |
| 
 | |
|         public float TerrainMaxHeight { get => _terrainMaxHeight; set => _terrainMaxHeight = Math.Clamp(value, 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 FromBytes(byte[] bytes)
 | |
|         {
 | |
|             TSCB t = new TSCB();
 | |
| 
 | |
|             // Use big-endian
 | |
|             using (var r = new BinaryReaderBig(new MemoryStream(bytes)))
 | |
|             {
 | |
|                 // Set header info from file on the new TSCBInfo
 | |
|                 t.Signature = new string(r.ReadChars(4));
 | |
|                 t.Version = r.ReadByte();
 | |
| 
 | |
|                 // 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));
 | |
| 
 | |
|                 // Initialize mat info array with provided length
 | |
|                 t.MaterialInfo = new MaterialInfo[t.MaterialInfoLength];
 | |
| 
 | |
|                 // Initialize every mat info, then add to the array
 | |
|                 for (int i = 0; i < t.MaterialInfoLength; i++)
 | |
|                 {
 | |
|                     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);
 | |
| 
 | |
|                     t.MaterialInfo[i] = matInfo;
 | |
|                 }
 | |
| 
 | |
|                 // Read area offsets
 | |
|                 t.AreaArrayOffsets = r.ReadBytes((int)(t.AreaArrayLength * 4));
 | |
| 
 | |
|                 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 = 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 is equal to 0, skip the extra byte coming after it
 | |
|                         uint next_val = r.ReadUInt32();
 | |
| 
 | |
|                         if (next_val != 1) // If the next value isn't extra unneeded info
 | |
|                         {
 | |
|                             r.Advance(-4);
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     uint file_base = r.ReadUInt32();
 | |
|                     uint unk_2 = r.ReadUInt32();
 | |
|                     uint unk_3 = r.ReadUInt32();
 | |
|                     uint ref_extra = r.ReadUInt32();
 | |
| 
 | |
|                     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.ExtraInfoLength = r.ReadUInt32(); //Usually 0, 4, or 8
 | |
| 
 | |
|                     if (ref_extra == 4)
 | |
|                     {
 | |
|                         if (areaInfo.ExtraInfoLength == 8)
 | |
|                         { //Skip the extra "20" after the 8, as well as the extra info
 | |
|                             areaInfo.HasGrass = true;
 | |
|                             areaInfo.HasWater = true;
 | |
|                             r.Advance(36);
 | |
|                         }
 | |
|                         else //If the length is 4
 | |
|                         {
 | |
|                             var areabytes = r.ReadBytes(16).ToArray();
 | |
|                             if (areabytes[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;
 | |
|                         }
 | |
|                     }
 | |
|                     else //If the extra info flags aren't set, go back 4
 | |
|                     {
 | |
|                         r.Advance(-4);
 | |
|                     }
 | |
| 
 | |
|                     t.AreaInfo[i] = areaInfo;
 | |
|                 }
 | |
| 
 | |
|                 //Get the number of filenames by getting how many bytes they take up out of the entire file size
 | |
|                 var filenames_count = (r.BaseStream.Length - (t.FileBaseOffset + 16)) / 12;
 | |
| 
 | |
|                 t.FileNames = new string[filenames_count];
 | |
| 
 | |
|                 r.BaseStream.Seek(t.FileBaseOffset + 16, SeekOrigin.Begin); // TODO: change this to 'current' later, or maybe even remove
 | |
| 
 | |
|                 for (int i = 0; i < filenames_count; i++)
 | |
|                 {
 | |
|                     string filename = new string(r.ReadChars(12));
 | |
| 
 | |
|                     t.FileNames[i] = filename;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return t;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes TSCB data to a byte array.
 | |
|         /// </summary>
 | |
|         /// <param name="tscb"><see cref="TSCB"/> that contains data to write.</param>
 | |
|         /// <returns>Byte array containing the TSCB data.</returns>
 | |
|         public static byte[] ToBytes(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));
 | |
|             }
 | |
| 
 | |
|             return b.ToArray();
 | |
|         }
 | |
|     }
 | |
| }
 | 
