PARAM.SFO parsing

This commit is contained in:
Deterous
2023-10-28 23:57:38 +13:00
parent 026e2c1d85
commit dd9945f3a0
5 changed files with 279 additions and 5 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ using System;
namespace BuildIRD namespace BuildIRD
{ {
internal class BuildIRD internal class Program
{ {
static void Main() static void Main()
{ {
@@ -11,7 +11,7 @@ namespace BuildIRD
byte[] discID = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte[] discID = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte[] discPIC = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte[] discPIC = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
IRD ird = new IRD("Z:", discKey, discID, discPIC); IRD ird = new IRD("Z:", discKey, discID, discPIC);
ird.Write("./ ird.ird"); ird.Write("./test.ird");
Console.WriteLine(ird.HeaderLength); Console.WriteLine(ird.HeaderLength);
} }
} }
+6 -3
View File
@@ -6,9 +6,8 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibIRD", "LibIRD\LibIRD.csproj", "{4A80C34B-D4C5-4536-BEAE-46218BC09980}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibIRD", "LibIRD\LibIRD.csproj", "{4A80C34B-D4C5-4536-BEAE-46218BC09980}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildIRD", "BuildIRD\BuildIRD.csproj", "{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildIRD", "BuildIRD\BuildIRD.csproj", "{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}"
ProjectSection(ProjectDependencies) = postProject EndProject
{4A80C34B-D4C5-4536-BEAE-46218BC09980} = {4A80C34B-D4C5-4536-BEAE-46218BC09980} Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintParams", "PrintParams\PrintParams.csproj", "{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}"
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -24,6 +23,10 @@ Global
{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Debug|Any CPU.Build.0 = Debug|Any CPU {A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Release|Any CPU.ActiveCfg = Release|Any CPU {A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Release|Any CPU.Build.0 = Release|Any CPU {A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}.Release|Any CPU.Build.0 = Release|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+232
View File
@@ -0,0 +1,232 @@
using System;
using System.IO;
using System.Text;
namespace LibIRD
{
/// <summary>
/// PARAM.SFO file parsing
/// </summary>
public class ParamSFO
{
/// <summary>
/// PARAM.SFO file signature
/// </summary>
/// <remarks>{ 0x00, 0x50, 0x53, 0x46 }</remarks>
public static readonly string Magic = "\0PSF";
/// <summary>
/// PARAM.SFO file version
/// </summary>
/// <remarks>Typically { 0x01 0x01 0x00 0x00 } (v1.1)</remarks>
public uint Version { get; private set; }
/// <summary>
/// The location of the first byte of the Key Table
/// </summary>
public uint KeyTableStart { get; private set; }
/// <summary>
/// The location of the first byte of the Data Table
/// </summary>
public uint DataTableStart { get; private set; }
/// <summary>
/// The number of parameters in the table
/// </summary>
public uint ParamCount { get; private set; }
/// <summary>
/// Parameter, a single entry in parameter table
/// </summary>
public class Param
{
/// <summary>
/// Offset of key, relative to KeyTableStart
/// </summary>
public ushort KeyOffset { get; internal set; }
/// <summary>
/// Format of parameter
/// </summary>
/// <remarks>0x0400 is string, 0x0404 is uint</remarks>
public ushort DataFormat { get; internal set; }
/// <summary>
/// Number of bytes used for parameter
/// </summary>
public uint DataLength { get; internal set; }
/// <summary>
/// Total number of bytes for parameter
/// </summary>
/// <remarks>DataTotal - DataLength is padding of 0x00</remarks>
public uint DataTotal { get; internal set; }
/// <summary>
/// offset of parameter, relative to DataTableStart
/// </summary>
public uint DataOffset { get; internal set; }
/// <summary>
/// The name of the parameter
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// The value of the parameter, if it is a string
/// </summary>
public string StringValue { get; internal set; } = null;
/// <summary>
/// The value of the parameter, if it is a UInt32
/// </summary>
public int IntValue { get; internal set; } = 0;
}
/// <summary>
/// The parameters in the table
/// </summary>
/// <remarks><see cref="ParamCount"/> Params in the table</remarks>
public Param[] Params { get; private set; }
/// <summary>
/// String index overloading, gets string value of given key
/// </summary>
/// <param name="key">Parameter to be retreived</param>
/// <returns>The string value of the given key</returns>
public string this[string key]
{
get
{
int index = Array.FindIndex(Params, param => param.Name == key);
return Params[index].StringValue;
}
}
/// <summary>
/// Constructor using a PARAM.SFO file stream
/// </summary>
/// <param name="sfoStream">SFO file stream</param>
public ParamSFO(Stream sfoStream)
{
// Parse file stream
Parse(sfoStream);
}
/// <summary>
/// Constructor using a PARAM.SFO file path
/// </summary>
/// <param name="sfoPath">Full file path to the PARAM.SFO</param>
/// <exception cref="ArgumentNullException"></exception>
public ParamSFO(string sfoPath)
{
// Validate file path
if (sfoPath == null || sfoPath.Length <= 0)
throw new ArgumentNullException(nameof(sfoPath));
// Read file as a stream, and parse file
using (FileStream fs = new FileStream(sfoPath, FileMode.Open, FileAccess.Read))
Parse(fs);
}
/// <summary>
/// Parse parameters and PARAM.SFO metadata from stream
/// </summary>
/// <param name="sfoStream">SFO file stream</param>
/// <exception cref="FileLoadException"></exception>
private void Parse(Stream sfoStream)
{
// Read binary stream
using (BinaryReader br = new BinaryReader(sfoStream))
{
// Check file signature is correct
string magic = Encoding.ASCII.GetString(br.ReadBytes(4));
if (magic != ParamSFO.Magic)
throw new FileLoadException("Not a valid PARAM.SFO file");
// Parse header
Version = br.ReadUInt32();
KeyTableStart = br.ReadUInt32();
DataTableStart = br.ReadUInt32();
ParamCount = br.ReadUInt32();
// Parse parameter metadata
Params = new Param[ParamCount];
for (int i = 0; i < ParamCount; i++)
{
Params[i] = new Param
{
KeyOffset = br.ReadUInt16(),
DataFormat = br.ReadUInt16(),
DataLength = br.ReadUInt32(),
DataTotal = br.ReadUInt32(),
DataOffset = br.ReadUInt32()
};
}
// Parse parameters
for (int i = 0; i < ParamCount; i++)
{
// Move stream to ith key
sfoStream.Position = KeyTableStart + Params[i].KeyOffset;
// Determine ith key length
uint keyLen = ((i == ParamCount - 1) ? DataTableStart - KeyTableStart : Params[i + 1].KeyOffset)
- Params[i].KeyOffset;
// Read ith key name
Params[i].Name = Encoding.ASCII.GetString(br.ReadBytes((int) keyLen)).TrimEnd('\0');
// Move stream to ith data
sfoStream.Position = DataTableStart + Params[i].DataOffset;
// Read ith data, based on data format
switch (Params[i].DataFormat)
{
case 0x0400: // Non-null-terminated UTF-8 String
Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength));
break;
case 0x0402: // Null-terminated UTF-8 String
Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength)).TrimEnd('\0');
break;
case 0x0404: // Integer
//if (Params[i].DataLength != 4)
//throw new ArgumentException("Integer parameter not 4 bytes?");
Params[i].IntValue = br.ReadInt32();
break;
default: // Unknown data format, assume null-terminated string
Params[i].StringValue = Encoding.UTF8.GetString(br.ReadBytes((int)Params[i].DataLength)).TrimEnd('\0');
break;
}
}
}
}
/// <summary>
/// Prints formatted parameters extracted from PARAM.SFO to console
/// </summary>
public void Print()
{
StringBuilder print = new StringBuilder("PARAM.SFO Contents:\n====================\n");
for (int i = 0; i < ParamCount; i++)
{
print.Append(Params[i].Name);
print.Append(' ');
for (int j = Params[i].Name.Length; j < 20; j++)
print.Append(' ');
switch (Params[i].DataFormat)
{
case 0x0404:
print.Append(Params[i].IntValue);
break;
default:
print.Append(Params[i].StringValue);
break;
}
print.Append('\n');
}
Console.Write(print);
}
}
}
+25
View File
@@ -0,0 +1,25 @@
using LibIRD;
using System;
using System.IO;
namespace PrintParams
{
internal class Program
{
static void Main()
{
string filename = "./PARAM.SFO";
if (File.Exists(filename))
{
ParamSFO paramSFO = new ParamSFO("./PARAM.SFO");
Console.WriteLine(paramSFO["TITLE_ID"]);
paramSFO.Print();
}
else
{
Console.WriteLine(filename + " not found");
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net48;net6.0;net7.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Version>0.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LibIRD\LibIRD.csproj" />
</ItemGroup>
</Project>