mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-08 18:06:41 +00:00
Support optional header in STFS, fix endianness in Descriptor (#80)
* Fix endianness in STFS Descriptor * Support optional header for installer packages * Fix field types * Fix syntax * Fix build
This commit is contained in:
@@ -34,8 +34,33 @@ namespace SabreTools.Data.Models.STFS
|
||||
public const string MagicStringCON = "CON ";
|
||||
|
||||
/// <summary>
|
||||
/// Standard length of an STFS header
|
||||
/// Standard length of all fixed STFS header fields
|
||||
/// </summary>
|
||||
public const uint StandardHeaderSize = 0xB000;
|
||||
public const uint MinimumHeaderSize = 0x971A;
|
||||
|
||||
/// <summary>
|
||||
/// System Update installer type magic string
|
||||
/// </summary>
|
||||
public const string InstallerTypeSystemUpdate = "SUPD";
|
||||
|
||||
/// <summary>
|
||||
/// Title Update installer type magic string
|
||||
/// </summary>
|
||||
public const string InstallerTypeTitleUpdate = "TUPD";
|
||||
|
||||
/// <summary>
|
||||
/// System Update Cache installer type magic string
|
||||
/// </summary>
|
||||
public const string InstallerTypeSystemUpdateCache = "P$SU";
|
||||
|
||||
/// <summary>
|
||||
/// Title Update Cache installer type magic string
|
||||
/// </summary>
|
||||
public const string InstallerTypeTitleUpdateCache = "P$TU";
|
||||
|
||||
/// <summary>
|
||||
/// Title Content Cache installer type magic string
|
||||
/// </summary>
|
||||
public const string InstallerTypeTitleContentCache = "P$TC";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,4 +58,17 @@ namespace SabreTools.Data.Models.XenonExecutable
|
||||
DEVICE_ID_TRANSFER = 0x00000040,
|
||||
PROFILE_ID_TRANSFER = 0x00000080,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installer cache package resume state
|
||||
/// </summary>
|
||||
public enum ResumeState : uint
|
||||
{
|
||||
FILE_HEADERS_NOT_READY = 0x46494C48,
|
||||
NEW_FOLDER = 0x666F6C64,
|
||||
NEW_FOLDER_RESUME_ATTEMPT_1 = 0x666F6C31,
|
||||
NEW_FOLDER_RESUME_ATTEMPT_2 = 0x666F6C32,
|
||||
NEW_FOLDER_RESUME_ATTEMPT_UNKNOWN = 0x666F6C3F,
|
||||
NEW_FOLDER_RESUME_ATTEMPT_SPECIFIC = 0x666F6C40,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace SabreTools.Data.Models.STFS
|
||||
public byte[] HeaderHash { get; set; } = new byte[20];
|
||||
|
||||
/// <summary>
|
||||
/// Size of the header, in bytes (from ??? to ???)
|
||||
/// The actual end of header is padded and zeroed up until next multiple of 4096 bytes
|
||||
/// Size of the header, in bytes (from start of file)
|
||||
/// The actual end of header is padded and zeroed up until next block (multiple of 4096 bytes)
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public uint HeaderSize { get; set; }
|
||||
@@ -274,5 +274,10 @@ namespace SabreTools.Data.Models.STFS
|
||||
/// </summary>
|
||||
/// <remarks>If present, 768 bytes, UTF-8 string</remarks>
|
||||
public byte[]? AdditionalDisplayDescriptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional field present on installer update/cache packages
|
||||
/// </summary>
|
||||
public InstallerHeader? InstallerHeader { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
41
SabreTools.Data.Models/STFS/InstallerCacheHeader.cs
Normal file
41
SabreTools.Data.Models/STFS/InstallerCacheHeader.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using SabreTools.Numerics;
|
||||
|
||||
namespace SabreTools.Data.Models.STFS
|
||||
{
|
||||
/// <summary>
|
||||
/// STFS Volume Descriptor, for System or Title Cache Installer STFS packages
|
||||
/// </summary>
|
||||
public class InstallerCacheHeader : InstallerHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Resume state enum
|
||||
/// See Enums.ResumeState
|
||||
/// </summary>
|
||||
/// <remarks>If present, 4 bytes</remarks>
|
||||
public uint ResumeState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current file index
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public ulong CurrentFileIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes processed
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
public ulong BytesProcessed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Datetime for last modified
|
||||
/// </summary>
|
||||
/// <remarks>Microsoft FILETIME, Big-endian, 8 bytes</remarks>
|
||||
public long LastModifiedDateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cache resume data
|
||||
/// </summary>
|
||||
/// <remarks>5584 bytes</remarks>
|
||||
public byte[] ResumeData { get; set; } = new byte[5584];
|
||||
}
|
||||
}
|
||||
16
SabreTools.Data.Models/STFS/InstallerHeader.cs
Normal file
16
SabreTools.Data.Models/STFS/InstallerHeader.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace SabreTools.Data.Models.STFS
|
||||
{
|
||||
/// <summary>
|
||||
/// STFS Optional header present in STFS packages for installers
|
||||
/// Original research, field not mentioned on free60 wiki
|
||||
/// </summary>
|
||||
public class InstallerHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// String indicating type of installer
|
||||
/// See Constants.InstallerType*
|
||||
/// </summary>
|
||||
/// <remarks>4 bytes, ASCII</remarks>
|
||||
public byte[] InstallerType { get; set; } = new byte[4];
|
||||
}
|
||||
}
|
||||
22
SabreTools.Data.Models/STFS/InstallerUpdateHeader.cs
Normal file
22
SabreTools.Data.Models/STFS/InstallerUpdateHeader.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using SabreTools.Numerics;
|
||||
|
||||
namespace SabreTools.Data.Models.STFS
|
||||
{
|
||||
/// <summary>
|
||||
/// STFS Volume Descriptor, for System or Title Update Installer STFS packages
|
||||
/// </summary>
|
||||
public class InstallerUpdateHeader : InstallerHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Field for base version number, major.minor.build.revision
|
||||
/// </summary>
|
||||
/// <remarks>4 bytes</remarks>
|
||||
public uint InstallerBaseVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Field for version number number, major.minor.build.revision
|
||||
/// </summary>
|
||||
/// <remarks>4 bytes</remarks>
|
||||
public uint InstallerVersion { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,13 @@ namespace SabreTools.Data.Models.STFS
|
||||
/// <summary>
|
||||
/// File Table Block Count
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian</remarks>
|
||||
/// <remarks>Little-endian</remarks>
|
||||
public short FileTableBlockCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File Table Block Number
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian, 3-byte int24</remarks>
|
||||
/// <remarks>Little-endian, 3-byte int24</remarks>
|
||||
public Int24 FileTableBlockNumber { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,13 +37,13 @@ namespace SabreTools.Data.Models.STFS
|
||||
/// <summary>
|
||||
/// Data Block Count
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian, 3-byte uint24</remarks>
|
||||
/// <remarks>Little-endian, 3-byte uint24</remarks>
|
||||
public UInt24 DataBlockCount { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Data Block Offset
|
||||
/// </summary>
|
||||
/// <remarks>Big-endian, 3-byte uint24</remarks>
|
||||
/// <remarks>Little-endian, 3-byte uint24</remarks>
|
||||
public UInt24 DataBlockOffset { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Models.STFS;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
@@ -15,7 +16,7 @@ namespace SabreTools.Serialization.Readers
|
||||
return null;
|
||||
|
||||
// Simple check for a valid stream length
|
||||
if (Constants.StandardHeaderSize > data.Length - data.Position)
|
||||
if (Constants.MinimumHeaderSize > data.Length - data.Position)
|
||||
return null;
|
||||
|
||||
try
|
||||
@@ -127,6 +128,38 @@ namespace SabreTools.Serialization.Readers
|
||||
obj.TitleThumbnailImage = data.ReadBytes(0x4000);
|
||||
}
|
||||
|
||||
// Parse optional header if header size (rounded up to nearest block) is sufficiently large
|
||||
if (((obj.HeaderSize + 0xFFF) & 0xFFFFF000) - Constants.MinimumHeaderSize >= 0x15F4)
|
||||
{
|
||||
byte[] installerType = data.ReadBytes(4);
|
||||
string type = Encoding.UTF8.GetString(installerType);
|
||||
if (type.Equals(Constants.InstallerTypeSystemUpdate) || type.Equals(Constants.InstallerTypeTitleUpdate))
|
||||
{
|
||||
var updateHeader = new InstallerUpdateHeader();
|
||||
updateHeader.InstallerType = installerType;
|
||||
updateHeader.InstallerBaseVersion = data.ReadUInt32BigEndian();
|
||||
updateHeader.InstallerVersion = data.ReadUInt32BigEndian();
|
||||
obj.InstallerHeader = updateHeader;
|
||||
}
|
||||
else if (type.Equals(Constants.InstallerTypeSystemUpdateCache) || type.Equals(Constants.InstallerTypeTitleUpdateCache) || type.Equals(Constants.InstallerTypeTitleContentCache))
|
||||
{
|
||||
var cacheHeader = new InstallerCacheHeader();
|
||||
cacheHeader.InstallerType = installerType;
|
||||
cacheHeader.ResumeState = data.ReadUInt32BigEndian();
|
||||
cacheHeader.CurrentFileIndex = data.ReadUInt64BigEndian();
|
||||
cacheHeader.BytesProcessed = data.ReadUInt64BigEndian();
|
||||
cacheHeader.LastModifiedDateTime = data.ReadInt64BigEndian();
|
||||
cacheHeader.ResumeData = data.ReadBytes(5584);
|
||||
obj.InstallerHeader = cacheHeader;
|
||||
}
|
||||
else
|
||||
{
|
||||
var installerHeader = new InstallerHeader();
|
||||
installerHeader.InstallerType = installerType;
|
||||
obj.InstallerHeader = installerHeader;
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -200,8 +233,8 @@ namespace SabreTools.Serialization.Readers
|
||||
obj.WorkerThreadProcessor = data.ReadByteValue();
|
||||
obj.WorkerThreadPriority = data.ReadByteValue();
|
||||
obj.Hash = data.ReadBytes(20);
|
||||
obj.DataBlockCount = data.ReadUInt24BigEndian();
|
||||
obj.DataBlockOffset = data.ReadUInt24BigEndian();
|
||||
obj.DataBlockCount = data.ReadUInt24LittleEndian();
|
||||
obj.DataBlockOffset = data.ReadUInt24LittleEndian();
|
||||
obj.Hash = data.ReadBytes(5);
|
||||
|
||||
return obj;
|
||||
@@ -213,8 +246,8 @@ namespace SabreTools.Serialization.Readers
|
||||
obj.VolumeDescriptorSize = data.ReadByteValue();
|
||||
obj.Reserved = data.ReadByteValue();
|
||||
obj.BlockSeparation = data.ReadByteValue();
|
||||
obj.FileTableBlockCount = data.ReadInt16BigEndian();
|
||||
obj.FileTableBlockNumber = data.ReadInt24BigEndian();
|
||||
obj.FileTableBlockCount = data.ReadInt16LittleEndian();
|
||||
obj.FileTableBlockNumber = data.ReadInt24LittleEndian();
|
||||
obj.TopHashTableHash = data.ReadBytes(20);
|
||||
obj.TotalAllocatedBlockCount = data.ReadInt32BigEndian();
|
||||
obj.TotalUnallocatedBlockCount = data.ReadInt32BigEndian();
|
||||
|
||||
@@ -163,6 +163,9 @@ namespace SabreTools.Wrappers
|
||||
}
|
||||
}
|
||||
|
||||
if (header.InstallerHeader is not null)
|
||||
Print(builder, header.InstallerHeader);
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
@@ -267,5 +270,47 @@ namespace SabreTools.Wrappers
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
protected static void Print(StringBuilder builder, InstallerHeader installerHeader)
|
||||
{
|
||||
builder.AppendLine(" Installer Information");
|
||||
builder.AppendLine(" -------------------------");
|
||||
|
||||
builder.AppendLine(installerHeader.InstallerType, " Installer Type");
|
||||
builder.AppendLine(Encoding.UTF8.GetString(installerHeader.InstallerType), " Installer Type (Parsed)");
|
||||
|
||||
if (installerHeader is InstallerUpdateHeader updateHeader)
|
||||
{
|
||||
builder.AppendLine(updateHeader.InstallerBaseVersion, " Installer Base Version");
|
||||
|
||||
uint bvMajor = updateHeader.InstallerBaseVersion >> 28; // Top 4 bits
|
||||
uint bvMinor = (updateHeader.InstallerBaseVersion >> 24) & 0xF; // Next top 4 bits
|
||||
uint bvBuild = (updateHeader.InstallerBaseVersion >> 8) & 0xFFFF; // Next 16 bits
|
||||
uint bvRevision = updateHeader.InstallerBaseVersion & 0xFF; // Lowest 8 bits
|
||||
builder.AppendLine($"{bvMajor}.{bvMinor}.{bvBuild}.{bvRevision}", " Installer Base Version (Parsed)");
|
||||
|
||||
builder.AppendLine(updateHeader.InstallerVersion, " Installer Version");
|
||||
|
||||
uint vMajor = updateHeader.InstallerVersion >> 28; // Top 4 bits
|
||||
uint vMinor = (updateHeader.InstallerVersion >> 24) & 0xF; // Next top 4 bits
|
||||
uint vBuild = (updateHeader.InstallerVersion >> 8) & 0xFFFF; // Next 8 bits
|
||||
uint vRevision = updateHeader.InstallerVersion & 0xFF; // Lowest 8 bits
|
||||
builder.AppendLine($"{vMajor}.{vMinor}.{vBuild}.{vRevision}", " Installer Version (Parsed)");
|
||||
}
|
||||
else if (installerHeader is InstallerCacheHeader cacheHeader)
|
||||
{
|
||||
builder.AppendLine(cacheHeader.ResumeState, " Resume State"); // See Enums.ResumeState
|
||||
builder.AppendLine(cacheHeader.CurrentFileIndex, " Current File Index");
|
||||
builder.AppendLine(cacheHeader.BytesProcessed, " Bytes Processed");
|
||||
builder.AppendLine(cacheHeader.LastModifiedDateTime, " Last Modified Date Time");
|
||||
|
||||
DateTime datetime = DateTime.FromFileTime(cacheHeader.LastModifiedDateTime);
|
||||
builder.AppendLine(datetime.ToString("yyyy-MM-dd HH:mm:ss"), " Last Modified Date Time (Parsed)");
|
||||
|
||||
builder.AppendLine(cacheHeader.ResumeData, " Resume Data");
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user