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);
}
}
}
}