mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-05 22:01:33 +00:00
180 lines
5.8 KiB
C#
180 lines
5.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using SabreTools.Data.Models.TAR;
|
|
using SabreTools.IO.Extensions;
|
|
using SabreTools.Numerics.Extensions;
|
|
|
|
#pragma warning disable IDE0017 // Simplify object initialization
|
|
namespace SabreTools.Serialization.Readers
|
|
{
|
|
public class TapeArchive : BaseBinaryReader<Archive>
|
|
{
|
|
/// <inheritdoc/>
|
|
public override Archive? Deserialize(Stream? data)
|
|
{
|
|
// If the data is invalid
|
|
if (data is null || !data.CanRead)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Cache the current offset
|
|
long initialOffset = data.Position;
|
|
|
|
var archive = new Archive();
|
|
|
|
// Read all entries sequentially
|
|
List<Entry> entries = [];
|
|
while (data.Position < data.Length)
|
|
{
|
|
var entry = ParseEntry(data);
|
|
if (entry is null)
|
|
break;
|
|
|
|
entries.Add(entry);
|
|
}
|
|
|
|
// No entires indicates invalid
|
|
if (entries.Count == 0)
|
|
return null;
|
|
|
|
// Assign the entries
|
|
archive.Entries = [.. entries];
|
|
|
|
return archive;
|
|
}
|
|
catch
|
|
{
|
|
// Ignore the actual error
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into an Entry
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled Entry on success, null on error</returns>
|
|
public static Entry? ParseEntry(Stream data)
|
|
{
|
|
var obj = new Entry();
|
|
|
|
#region Header
|
|
|
|
var header = ParseHeader(data);
|
|
if (header is null)
|
|
return null;
|
|
|
|
obj.Header = header;
|
|
|
|
// Align to the block size
|
|
data.AlignToBoundary(512);
|
|
|
|
#endregion
|
|
|
|
#region Blocks
|
|
|
|
// Exit if the size is invalid
|
|
string sizeOctalString = header.Size!.TrimEnd('\0');
|
|
if (sizeOctalString.Length == 0)
|
|
return obj;
|
|
|
|
// Get the block count from the size
|
|
int octalSize = Convert.ToInt32(sizeOctalString, 8);
|
|
int blockCount = (int)Math.Ceiling((decimal)octalSize / 512);
|
|
|
|
// Read all blocks sequentially
|
|
var blocks = new Block[blockCount];
|
|
for (int i = 0; i < blocks.Length; i++)
|
|
{
|
|
var block = ParseBlock(data);
|
|
if (block is null)
|
|
break;
|
|
|
|
blocks[i] = block;
|
|
}
|
|
|
|
obj.Blocks = blocks;
|
|
|
|
#endregion
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a Header
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled Header on success, null on error</returns>
|
|
public static Header? ParseHeader(Stream data)
|
|
{
|
|
var obj = new Header();
|
|
|
|
byte[] filenameBytes = data.ReadBytes(100);
|
|
obj.FileName = Encoding.ASCII.GetString(filenameBytes);
|
|
byte[] modeBytes = data.ReadBytes(8);
|
|
obj.Mode = Encoding.ASCII.GetString(modeBytes);
|
|
byte[] uidBytes = data.ReadBytes(8);
|
|
obj.UID = Encoding.ASCII.GetString(uidBytes);
|
|
byte[] gidBytes = data.ReadBytes(8);
|
|
obj.GID = Encoding.ASCII.GetString(gidBytes);
|
|
byte[] sizeBytes = data.ReadBytes(12);
|
|
obj.Size = Encoding.ASCII.GetString(sizeBytes);
|
|
byte[] modifiedBytes = data.ReadBytes(12);
|
|
obj.ModifiedTime = Encoding.ASCII.GetString(modifiedBytes);
|
|
byte[] checksumBytes = data.ReadBytes(8);
|
|
obj.Checksum = Encoding.ASCII.GetString(checksumBytes);
|
|
obj.TypeFlag = (TypeFlag)data.ReadByteValue();
|
|
byte[] linkNameBytes = data.ReadBytes(100);
|
|
obj.LinkName = Encoding.ASCII.GetString(linkNameBytes);
|
|
|
|
// If end of stream has been reached
|
|
if (data.Position >= data.Length)
|
|
return obj;
|
|
|
|
// Peek at the next 5 bytes
|
|
byte[] temp = data.PeekBytes(5);
|
|
string tempString = Encoding.ASCII.GetString(temp);
|
|
if (tempString != "ustar")
|
|
return obj;
|
|
|
|
byte[] magicBytes = data.ReadBytes(5);
|
|
obj.Magic = Encoding.ASCII.GetString(magicBytes);
|
|
byte[] versionBytes = data.ReadBytes(3);
|
|
obj.Version = Encoding.ASCII.GetString(versionBytes);
|
|
byte[] userNameBytes = data.ReadBytes(32);
|
|
obj.UserName = Encoding.ASCII.GetString(userNameBytes);
|
|
byte[] groupNameBytes = data.ReadBytes(32);
|
|
obj.GroupName = Encoding.ASCII.GetString(groupNameBytes);
|
|
byte[] devMajorBytes = data.ReadBytes(8);
|
|
obj.DevMajor = Encoding.ASCII.GetString(devMajorBytes);
|
|
byte[] devMinorBytes = data.ReadBytes(8);
|
|
obj.DevMinor = Encoding.ASCII.GetString(devMinorBytes);
|
|
byte[] prefixBytes = data.ReadBytes(155);
|
|
obj.Prefix = Encoding.ASCII.GetString(prefixBytes);
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a Stream into a Block
|
|
/// </summary>
|
|
/// <param name="data">Stream to parse</param>
|
|
/// <returns>Filled Block on success, null on error</returns>
|
|
public static Block? ParseBlock(Stream data)
|
|
{
|
|
// Handle incomplete blocks
|
|
if (data.Position + 512 >= data.Length)
|
|
return null;
|
|
|
|
var obj = new Block();
|
|
|
|
obj.Data = data.ReadBytes(512);
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
}
|