From dd9945f3a04327c1a8b47b904ed2ad6771f5da8e Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Sat, 28 Oct 2023 23:57:38 +1300 Subject: [PATCH] PARAM.SFO parsing --- BuildIRD/BuildIRD.cs | 4 +- LibIRD.sln | 9 +- LibIRD/ParamSFO.cs | 232 +++++++++++++++++++++++++++++++++ PrintParams/PrintParams.cs | 25 ++++ PrintParams/PrintParams.csproj | 14 ++ 5 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 LibIRD/ParamSFO.cs create mode 100644 PrintParams/PrintParams.cs create mode 100644 PrintParams/PrintParams.csproj diff --git a/BuildIRD/BuildIRD.cs b/BuildIRD/BuildIRD.cs index 0693e8f..839dc5f 100644 --- a/BuildIRD/BuildIRD.cs +++ b/BuildIRD/BuildIRD.cs @@ -3,7 +3,7 @@ using System; namespace BuildIRD { - internal class BuildIRD + internal class Program { 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[] 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.Write("./ ird.ird"); + ird.Write("./test.ird"); Console.WriteLine(ird.HeaderLength); } } diff --git a/LibIRD.sln b/LibIRD.sln index 5f62227..9a91fb2 100644 --- a/LibIRD.sln +++ b/LibIRD.sln @@ -6,9 +6,8 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibIRD", "LibIRD\LibIRD.csproj", "{4A80C34B-D4C5-4536-BEAE-46218BC09980}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildIRD", "BuildIRD\BuildIRD.csproj", "{A2FC2D93-1C3E-46E2-9D69-11F9DDA8CFC7}" - ProjectSection(ProjectDependencies) = postProject - {4A80C34B-D4C5-4536-BEAE-46218BC09980} = {4A80C34B-D4C5-4536-BEAE-46218BC09980} - EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintParams", "PrintParams\PrintParams.csproj", "{B5D9C0AD-EA8D-486E-A1D7-ED2C3C7163BC}" EndProject Global 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}.Release|Any CPU.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LibIRD/ParamSFO.cs b/LibIRD/ParamSFO.cs new file mode 100644 index 0000000..af4bed5 --- /dev/null +++ b/LibIRD/ParamSFO.cs @@ -0,0 +1,232 @@ +using System; +using System.IO; +using System.Text; + +namespace LibIRD +{ + /// + /// PARAM.SFO file parsing + /// + public class ParamSFO + { + /// + /// PARAM.SFO file signature + /// + /// { 0x00, 0x50, 0x53, 0x46 } + public static readonly string Magic = "\0PSF"; + + /// + /// PARAM.SFO file version + /// + /// Typically { 0x01 0x01 0x00 0x00 } (v1.1) + public uint Version { get; private set; } + + /// + /// The location of the first byte of the Key Table + /// + public uint KeyTableStart { get; private set; } + + /// + /// The location of the first byte of the Data Table + /// + public uint DataTableStart { get; private set; } + + /// + /// The number of parameters in the table + /// + public uint ParamCount { get; private set; } + + /// + /// Parameter, a single entry in parameter table + /// + public class Param + { + /// + /// Offset of key, relative to KeyTableStart + /// + public ushort KeyOffset { get; internal set; } + + /// + /// Format of parameter + /// + /// 0x0400 is string, 0x0404 is uint + public ushort DataFormat { get; internal set; } + + /// + /// Number of bytes used for parameter + /// + public uint DataLength { get; internal set; } + + /// + /// Total number of bytes for parameter + /// + /// DataTotal - DataLength is padding of 0x00 + public uint DataTotal { get; internal set; } + + /// + /// offset of parameter, relative to DataTableStart + /// + public uint DataOffset { get; internal set; } + + /// + /// The name of the parameter + /// + public string Name { get; internal set; } + + /// + /// The value of the parameter, if it is a string + /// + public string StringValue { get; internal set; } = null; + + /// + /// The value of the parameter, if it is a UInt32 + /// + public int IntValue { get; internal set; } = 0; + } + + /// + /// The parameters in the table + /// + /// Params in the table + public Param[] Params { get; private set; } + + /// + /// String index overloading, gets string value of given key + /// + /// Parameter to be retreived + /// The string value of the given key + public string this[string key] + { + get + { + int index = Array.FindIndex(Params, param => param.Name == key); + return Params[index].StringValue; + } + } + + /// + /// Constructor using a PARAM.SFO file stream + /// + /// SFO file stream + public ParamSFO(Stream sfoStream) + { + // Parse file stream + Parse(sfoStream); + } + + /// + /// Constructor using a PARAM.SFO file path + /// + /// Full file path to the PARAM.SFO + /// + 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); + } + + /// + /// Parse parameters and PARAM.SFO metadata from stream + /// + /// SFO file stream + /// + 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; + } + } + } + } + + /// + /// Prints formatted parameters extracted from PARAM.SFO to console + /// + 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); + } + } +} diff --git a/PrintParams/PrintParams.cs b/PrintParams/PrintParams.cs new file mode 100644 index 0000000..64b4f78 --- /dev/null +++ b/PrintParams/PrintParams.cs @@ -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"); + } + } + } +} diff --git a/PrintParams/PrintParams.csproj b/PrintParams/PrintParams.csproj new file mode 100644 index 0000000..2e50058 --- /dev/null +++ b/PrintParams/PrintParams.csproj @@ -0,0 +1,14 @@ + + + + Exe + net48;net6.0;net7.0 + win-x86;win-x64;linux-x64;osx-x64 + 0.1 + + + + + + +