mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-08 18:06:41 +00:00
ZArchive support (#75)
* ZArchive support * Fix offset record format * Simplfiy Extensions * Delete unused writers and test data * Rework reader * Fix build
This commit is contained in:
@@ -90,6 +90,7 @@ Below is a list of all existing namespaces with the `SabreTools.Data.Models` pre
|
||||
| `XDVDFS` | Xbox DVD Filesystem (XISO) |
|
||||
| `XZ` | xz archive |
|
||||
| `XZP` | XBox Package File |
|
||||
| `ZArchive` | ZArchive (ZAR) |
|
||||
| `ZSTD` | ZSTD archive |
|
||||
|
||||
## Notable Information Sources
|
||||
|
||||
50
SabreTools.Data.Models/ZArchive/Archive.cs
Normal file
50
SabreTools.Data.Models/ZArchive/Archive.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single ZAR archive
|
||||
/// Most fields are Big Endian
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public class Archive
|
||||
{
|
||||
/// <summary>
|
||||
/// Zstd compressed file data, from 65536-byte blocks of the original files
|
||||
/// Blocks are stored uncompressed if ZStd does not decrease the size
|
||||
/// Due to the file size, this field is not usually filled in but remains here for completeness
|
||||
/// </summary>
|
||||
public byte[]? CompressedData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Padding bytes to be added after compressed blocks to ensure 8-byte alignment
|
||||
/// Padding bytes are all NULL (0x00)
|
||||
/// </summary>
|
||||
public byte[]? Padding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Records containing the offsets and block sizes of each group of blocks
|
||||
/// This allows the reader to jump to any 65536-byte boundary in the uncompressed stream.
|
||||
/// </summary>
|
||||
public OffsetRecord[] OffsetRecords { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// UTF-8 strings, prepended by string lengths
|
||||
/// </summary>
|
||||
public NameTable NameTable { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Serialized file tree structure using a queue of nodes
|
||||
/// </summary>
|
||||
public FileDirectoryEntry[] FileTree { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Section for custom key-value pairs and properties
|
||||
/// </summary>
|
||||
public Metadata? Metadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Archive footer containing the offsets and sizes of all other sections
|
||||
/// Ends with a SHA256 hash/size of the entire archive, and magic bytes
|
||||
/// </summary>
|
||||
public Footer Footer { get; set; } = new();
|
||||
}
|
||||
}
|
||||
68
SabreTools.Data.Models/ZArchive/Constants.cs
Normal file
68
SabreTools.Data.Models/ZArchive/Constants.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of compressed blocks referred to by a record
|
||||
/// </summary>
|
||||
public const int BlockSize = 64 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Number of compressed blocks referred to by a record
|
||||
/// </summary>
|
||||
public const int BlocksPerOffsetRecord = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes stored in an offset record
|
||||
/// </summary>
|
||||
public const int OffsetRecordSize = sizeof(ulong) + sizeof(ushort) * BlocksPerOffsetRecord;
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes stored in a file/directory entry
|
||||
/// </summary>
|
||||
public const int FileDirectoryEntrySize = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes stored in the footer
|
||||
/// 6 OffsetInfo fields,
|
||||
/// </summary>
|
||||
public const int FooterSize = 144;
|
||||
|
||||
/// <summary>
|
||||
/// NameOffsetAndTypeFlag value for the root node in the FileTree
|
||||
/// </summary>
|
||||
public const uint RootNode = 0x7FFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// Mask for the NameOffsetAndTypeFlag value when checking if it is a file
|
||||
/// </summary>
|
||||
public const uint FileFlag = 0x80000000;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum size of the Offset Records section
|
||||
/// </summary>
|
||||
public const ulong MaxOffsetRecordsSize = 0xFFFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum size of the Offset Records section
|
||||
/// </summary>
|
||||
public const ulong MaxNameTableSize = 0x7FFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum size of the File Tree section
|
||||
/// </summary>
|
||||
public const ulong MaxFileTreeSize = 0x7FFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// ZArchive magic bytes at end of file
|
||||
/// </summary>
|
||||
public static readonly byte[] MagicBytes = [0x16, 0x9F, 0x52, 0xD6];
|
||||
|
||||
/// <summary>
|
||||
/// ZArchive version field that acts as an extended magic immediately before final 4 magic bytes
|
||||
/// Currently only version 1 is implemented, any future version bytes are not suppported yet
|
||||
/// </summary>
|
||||
public static readonly byte[] Version1Bytes = [0x61, 0xBF, 0x3A, 0x01];
|
||||
}
|
||||
}
|
||||
26
SabreTools.Data.Models/ZArchive/DirectoryEntry.cs
Normal file
26
SabreTools.Data.Models/ZArchive/DirectoryEntry.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Node in the FileTree representing a directory
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public sealed class DirectoryEntry : FileDirectoryEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Starting index of the directory node
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public uint NodeStartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public uint Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved field
|
||||
/// </summary>
|
||||
public uint Reserved { get; set; }
|
||||
}
|
||||
}
|
||||
17
SabreTools.Data.Models/ZArchive/FileDirectoryEntry.cs
Normal file
17
SabreTools.Data.Models/ZArchive/FileDirectoryEntry.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Node in the FileTree
|
||||
/// Represents either a file or a directory
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public abstract class FileDirectoryEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// MSB is the type flag, 0 is Directory, 1 is File
|
||||
/// Remaining 31 bits are the offset in the NameTable
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public uint NameOffsetAndTypeFlag { get; set; }
|
||||
}
|
||||
}
|
||||
33
SabreTools.Data.Models/ZArchive/FileEntry.cs
Normal file
33
SabreTools.Data.Models/ZArchive/FileEntry.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Node in the FileTree representing a file
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public sealed class FileEntry : FileDirectoryEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Lowest 8 bits of the file's offset
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public uint FileOffsetLow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lowest 8 bits of the file's size
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public uint FileSizeLow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Highest 4 bits of the file's size
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public ushort FileSizeHigh { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Highest 4 bits of the file's offset
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public ushort FileOffsetHigh { get; set; }
|
||||
}
|
||||
}
|
||||
60
SabreTools.Data.Models/ZArchive/Footer.cs
Normal file
60
SabreTools.Data.Models/ZArchive/Footer.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Footer data stored at the end of a ZArchive file
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public class Footer
|
||||
{
|
||||
/// <summary>
|
||||
/// Size and offset values for the CompressedData section
|
||||
/// </summary>
|
||||
public OffsetInfo SectionCompressedData { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Size and offset values for the OffsetRecords section
|
||||
/// </summary>
|
||||
public OffsetInfo SectionOffsetRecords { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Size and offset values for the NameTable section
|
||||
/// </summary>
|
||||
public OffsetInfo SectionNameTable { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Size and offset values for the FileFree section
|
||||
/// </summary>
|
||||
public OffsetInfo SectionFileTree { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Size and offset values for the MetaDirectory section
|
||||
/// </summary>
|
||||
public OffsetInfo SectionMetaDirectory { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Size and offset values for the MetaData section
|
||||
/// </summary>
|
||||
public OffsetInfo SectionMetaData { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 hash of the ZArchive file prior the footer
|
||||
/// </summary>
|
||||
public byte[] IntegrityHash { get; set; } = new byte[32];
|
||||
|
||||
/// <summary>
|
||||
/// Size of the entire ZArchive file
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public ulong Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version indicator, also acts as extended magic
|
||||
/// </summary>
|
||||
public byte[] Version { get; set; } = new byte[4];
|
||||
|
||||
/// <summary>
|
||||
/// Magic bytes to indicate ZArchive file
|
||||
/// </summary>
|
||||
public byte[] Magic { get; set; } = new byte[4];
|
||||
}
|
||||
}
|
||||
11
SabreTools.Data.Models/ZArchive/Metadata.cs
Normal file
11
SabreTools.Data.Models/ZArchive/Metadata.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// ZARchive section for Meta Data and Meta Directories
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public class Metadata
|
||||
{
|
||||
// Not yet implemented in the ZArchive standard, should be empty
|
||||
}
|
||||
}
|
||||
27
SabreTools.Data.Models/ZArchive/NameEntry.cs
Normal file
27
SabreTools.Data.Models/ZArchive/NameEntry.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Filename entry in the NameTable
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public class NameEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Filename length, with MSB set to 0 for filenames less than 127 long
|
||||
/// NodeLengthShort and NodeLengthLong fields are exclusive, and one must be present
|
||||
/// </summary>
|
||||
public byte? NodeLengthShort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filename length, with prefix byte's MSB set to 1 for filenames greater than 127 long
|
||||
/// NodeLengthShort and NodeLengthLong fields are exclusive, and one must be present
|
||||
/// </summary>
|
||||
public ushort? NodeLengthLong { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// UTF-8 encoded file name
|
||||
/// </summary>
|
||||
/// <remarks>Maximum length of 2^15 - 1 bytes</remarks>
|
||||
public byte[] NodeName { get; set; } = [];
|
||||
}
|
||||
}
|
||||
20
SabreTools.Data.Models/ZArchive/NameTable.cs
Normal file
20
SabreTools.Data.Models/ZArchive/NameTable.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// UTF-8 strings, prepended by string lengths
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public class NameTable
|
||||
{
|
||||
/// <summary>
|
||||
/// List of filename entries
|
||||
/// </summary>
|
||||
public NameEntry[] NameEntries { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Virtual field, to cache the offsets of each name entry in the name table
|
||||
/// Used for referencing the name entry from an offset into the name table
|
||||
/// </summary>
|
||||
public uint[] NameTableOffsets { get; set; } = [];
|
||||
}
|
||||
}
|
||||
19
SabreTools.Data.Models/ZArchive/OffsetInfo.cs
Normal file
19
SabreTools.Data.Models/ZArchive/OffsetInfo.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset and size values of a ZArchive section, stored in the Footer
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public class OffsetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Base offset value for the section in bytes
|
||||
/// </summary>
|
||||
public ulong Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total size of the section in bytes
|
||||
/// </summary>
|
||||
public ulong Size { get; set; }
|
||||
}
|
||||
}
|
||||
19
SabreTools.Data.Models/ZArchive/OffsetRecord.cs
Normal file
19
SabreTools.Data.Models/ZArchive/OffsetRecord.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace SabreTools.Data.Models.ZArchive
|
||||
{
|
||||
/// <summary>
|
||||
/// Location and size properties of compressed blocks of the file data
|
||||
/// </summary>
|
||||
/// <see href="https://github.com/Exzap/ZArchive/"/>
|
||||
public class OffsetRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Base offset of compressed blocks
|
||||
/// </summary>
|
||||
public ulong Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sizes of each compressed block in this record
|
||||
/// </summary>
|
||||
public ushort[] Size { get; set; } = new ushort[Constants.BlocksPerOffsetRecord];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user