using Li.Utilities; using System; using System.Collections.Generic; using System.Diagnostics.Tracing; using System.IO; using System.Runtime.CompilerServices; // A Sfo Parser Written by Li // Because all the others are overly-complicated for no reason! // MIT Licensed. namespace Param { public class Sfo { private struct SfoEntry { internal string keyName; internal byte type; internal UInt32 valueSize; internal UInt32 totalSize; internal byte align; internal object value; } const int SFO_MAGIC = 0x46535000; const byte PSF_TYPE_BIN = 0; const byte PSF_TYPE_STR = 2; const byte PSF_TYPE_VAL = 4; private Dictionary sfoEntries; public Object this[string index] { get { if (sfoEntries.ContainsKey(index)) return sfoEntries[index].value; else return null; } set { if (sfoEntries.ContainsKey(index)) { SfoEntry sfoEnt = sfoEntries[index]; sfoEnt.value = value; // update sz sfoEnt.valueSize = getObjectSz(sfoEnt.value); if (sfoEnt.valueSize > sfoEnt.totalSize) sfoEnt.totalSize = Convert.ToUInt32(MathUtil.CalculatePaddingAmount(Convert.ToInt32(sfoEnt.valueSize), sfoEnt.align)); // update type sfoEnt.type = getPsfType(sfoEnt.value); sfoEntries[index] = sfoEnt; } else { UInt32 sz = getObjectSz(value); int alg = MathUtil.CalculatePaddingAmount(Convert.ToInt32(sz), 4); AddKey(index, value, Convert.ToUInt32(sz + alg), 4); } } } public void AddKey(string keyName, object value, UInt32 totalSize, byte align = 4) { SfoEntry ent = new SfoEntry(); ent.keyName = keyName; ent.type = getPsfType(value); ent.valueSize = getObjectSz(value); ent.totalSize = Convert.ToUInt32(totalSize + MathUtil.CalculatePaddingAmount(Convert.ToInt32(totalSize), align)); ent.align = align; ent.value = value; sfoEntries[ent.keyName] = ent; } public Sfo() { sfoEntries = new Dictionary(); } private static UInt32 getObjectSz(Object obj) { if (obj is Int32) return 4; if (obj is UInt32) return 4; if (obj is String) return Convert.ToUInt32((obj as String).Length + 1); if (obj is Byte[]) return Convert.ToUInt32((obj as Byte[]).Length); throw new Exception("Object is of unsupported type: " + obj.GetType()); } private static byte getPsfType(Object obj) { if (obj is Int32 || obj is UInt32) return PSF_TYPE_VAL; if (obj is String) return PSF_TYPE_STR; if (obj is Byte[]) return PSF_TYPE_BIN; throw new Exception("Object is of unsupported type: " + obj.GetType()); } public byte[] WriteSfo(UInt32 version = 0x101, Byte align = 0x4) { using (MemoryStream sfoStream = new MemoryStream()) { WriteSfo(sfoStream, version, align); byte[] sfoBytes = sfoStream.ToArray(); return sfoBytes; } } public void WriteSfo(Stream SfoStream, UInt32 version = 0x101, Byte align = 0x4) { using (MemoryStream sfoStream = new MemoryStream()) { StreamUtil sfoUtil = new StreamUtil(sfoStream); sfoUtil.WriteUInt32(SFO_MAGIC); sfoUtil.WriteUInt32(version); sfoUtil.WriteUInt32(0xFFFFFFFF); // key offset sfoUtil.WriteUInt32(0xFFFFFFFF); // value offset // (will fill these in after the file is created) sfoUtil.WriteInt32(sfoEntries.Count); using (MemoryStream keyTable = new MemoryStream()) { StreamUtil keyUtils = new StreamUtil(keyTable); using (MemoryStream valueTable = new MemoryStream()) { StreamUtil valueUtils = new StreamUtil(valueTable); foreach (SfoEntry entry in sfoEntries.Values) { // write name sfoUtil.WriteUInt16(Convert.ToUInt16(keyTable.Position)); keyUtils.WriteCStr(entry.keyName); // write entry sfoUtil.WriteByte(align); // align sfoUtil.WriteByte(entry.type); // type sfoUtil.WriteUInt32(entry.valueSize); // valueSize sfoUtil.WriteUInt32(entry.totalSize); // totalSize // write data sfoUtil.WriteUInt32(Convert.ToUInt32(valueTable.Position)); // dataOffset switch (entry.type) { case PSF_TYPE_VAL: valueUtils.WriteUInt32(Convert.ToUInt32(entry.value)); valueUtils.WritePadding(0x00, Convert.ToInt32(entry.totalSize - entry.valueSize)); break; case PSF_TYPE_STR: valueUtils.WriteStrWithPadding(entry.value as String, 0x00, Convert.ToInt32(entry.totalSize)); break; case PSF_TYPE_BIN: valueUtils.WriteBytesWithPadding(entry.value as Byte[], 0x00, Convert.ToInt32(entry.totalSize)); break; } } keyUtils.AlignTo(0x00, align); UInt32 keyOffset = Convert.ToUInt32(sfoStream.Position); keyTable.Seek(0x00, SeekOrigin.Begin); keyTable.CopyTo(sfoStream); UInt32 valueOffset = Convert.ToUInt32(sfoStream.Position); valueTable.Seek(0x00, SeekOrigin.Begin); valueTable.CopyTo(sfoStream); sfoStream.Seek(0x8, SeekOrigin.Begin); sfoUtil.WriteUInt32(keyOffset); // key offset sfoUtil.WriteUInt32(valueOffset); // value offset } } sfoStream.Seek(0x0, SeekOrigin.Begin); sfoStream.CopyTo(SfoStream); } } public static Sfo ReadSfo(Stream SfoStream) { Sfo sfoFile = new Sfo(); StreamUtil DataUtils = new StreamUtil(SfoStream); // Read Sfo Header UInt32 magic = DataUtils.ReadUInt32(); UInt32 version = DataUtils.ReadUInt32(); UInt32 keyOffset = DataUtils.ReadUInt32(); UInt32 valueOffset = DataUtils.ReadUInt32(); UInt32 count = DataUtils.ReadUInt32(); if (magic == SFO_MAGIC) //\x00PSF { for (int i = 0; i < count; i++) { SfoEntry entry = new SfoEntry(); UInt16 nameOffset = DataUtils.ReadUInt16(); entry.align = DataUtils.ReadByte(); entry.type = DataUtils.ReadByte(); entry.valueSize = DataUtils.ReadUInt32(); entry.totalSize = DataUtils.ReadUInt32(); UInt32 dataOffset = DataUtils.ReadUInt32(); int keyLocation = Convert.ToInt32(keyOffset + nameOffset); entry.keyName = DataUtils.ReadStringAt(keyLocation); int valueLocation = Convert.ToInt32(valueOffset + dataOffset); switch (entry.type) { case PSF_TYPE_STR: entry.value = DataUtils.ReadStringAt(valueLocation); break; case PSF_TYPE_VAL: entry.value = DataUtils.ReadUInt32At(valueLocation); break; case PSF_TYPE_BIN: entry.value = DataUtils.ReadBytesAt(valueLocation, Convert.ToInt32(entry.valueSize)); break; } sfoFile.sfoEntries[entry.keyName] = entry; } } else { throw new InvalidDataException("Sfo Magic is Invalid."); } return sfoFile; } public static Sfo ReadSfo(byte[] Sfo) { using (MemoryStream SfoStream = new MemoryStream(Sfo)) { return ReadSfo(SfoStream); } } } }