using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.Json; 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; } /// /// A field within the PS3_DISC.SFB file /// /// string Key, string Value public Dictionary Field { get; private set; } /// /// 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 file /// 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(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(sfoStream); // Check file signature is correct string magic = Encoding.ASCII.GetString(br.ReadBytes(4)); if (magic != ParamSFO.Magic) throw new FileLoadException("Unexpected PARAM.SFO file"); // Parse header Version = br.ReadUInt32(); uint keyTableStart = br.ReadUInt32(); uint dataTableStart = br.ReadUInt32(); uint paramCount = br.ReadUInt32(); // Parse parameter metadata ushort[] keyOffset = new ushort[paramCount]; uint[] dataFormat = new uint[paramCount]; uint[] dataLength = new uint[paramCount]; uint[] dataTotal = new uint[paramCount]; uint[] dataOffset = new uint[paramCount]; for (int i = 0; i < paramCount; i++) { keyOffset[i] = br.ReadUInt16(); dataFormat[i] = br.ReadUInt16(); dataLength[i] = br.ReadUInt32(); dataTotal[i] = br.ReadUInt32(); dataOffset[i] = br.ReadUInt32(); } // Parse parameters Field = []; for (int i = 0; i < paramCount; i++) { // Move stream to ith key sfoStream.Position = keyTableStart + keyOffset[i]; // Determine ith key length uint keyLen = ((i == paramCount - 1) ? dataTableStart - keyTableStart : keyOffset[i + 1]) - keyOffset[i]; // Read ith key name string key = Encoding.ASCII.GetString(br.ReadBytes((int)keyLen)).TrimEnd('\0'); // Move stream to ith data sfoStream.Position = dataTableStart + dataOffset[i]; // Read ith data, based on data format Field[key] = dataFormat[i] switch { // Non-null-terminated UTF-8 String 0x0004 => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])), // Null-terminated UTF-8 String 0x0204 => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])).TrimEnd('\0'), // Integer 0x0404 => br.ReadInt32().ToString(), // Unknown data format, assume null-terminated string _ => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])).TrimEnd('\0'), }; } } /// /// Prints formatted parameters extracted from PARAM.SFO to console /// /// Optionally print to text file public void Print(string printPath = null, string isoName = null) { // Build string from parameters StringBuilder printText = new(); if (isoName != null) printText.AppendLine($"PARAM.SFO Contents: {isoName}"); else printText.AppendLine("PARAM.SFO Contents:"); printText.AppendLine("==================="); // Loop through all parameters in PARAM.SFO foreach (KeyValuePair field in Field) printText.AppendLine(field.Key + ": " + field.Value); // Blank line printText.Append(Environment.NewLine); // If no path given, print to console if (printPath == null) { // Ensure UTF-8 will display properly in console Console.OutputEncoding = Encoding.UTF8; // Print formatted string to console Console.Write(printText); } else { // Write data to file File.AppendAllText(printPath, printText.ToString()); } } /// /// Define JSON options once /// private readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true }; /// /// Prints parameters extracted from PARAM.SFO to a json object /// /// Optionally print to json file public void PrintJson(string jsonPath = null) { // Serialise PS3_Disc.SFB data to a JSON object string json = JsonSerializer.Serialize(Field, JsonOpts); // If no path given, output to console if (jsonPath == null) { // Ensure UTF-8 will display properly in console Console.OutputEncoding = Encoding.UTF8; // Print formatted string to console Console.Write(json); } else { // Write to path File.AppendAllText(jsonPath, json); } } } }