/* Copyright (c) 2015 - 2018 TheDarkporgramer * * This was originally done by Jappi88 (Jappi88 at Gmail dot com) https://github.com/Jappi88 * All modifications have been TheDarkporgramer (save sfo ext ext ) https://github.com/xXxTheDarkprogramerxXx * * This(software Is provided) 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications*, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledge in the product documentation is required. * * 2. Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * * *Contact must be made to discuses permission and terms. */ using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Linq; namespace Param_SFO { public class PARAM_SFO { #region << Enums >> public enum DataTypes : uint { PSN_Game = 18248, GameData = 0x4744, SaveData = 0x5344, AppPhoto = 0x4150, AppMusic = 0x414D, AppVideo = 0x4156, BroadCastVideo = 0x4256, AppleTV = 4154, WebTV = 5754, CellBE = 0x4342, Home = 0x484D, StoreFronted = 0x5346, HDDGame = 0x4847, DiscGame = 0x4447, AutoInstallRoot = 0x4152, DiscPackage = 0x4450, ExtraRoot = 0x5852, VideoRoot = 0x5652, ThemeRoot = 0x5452, DiscMovie = 0x444D, Game_Digital_Application = 0x4081AC0,//GD PS4_Game_Application_Patch = 28775, Additional_Content = 25441,//PSvita PS4 GameContent = 25447,//PSVITA Blu_Ray_Disc = 25698,//PS4 None } public enum FMT : ushort { UTF_8 = 0x0004, ASCII = 0x0402, Utf8Null = 0x0204, UINT32 = 0x0404, } #endregion << Enums >> #region << Vars>> public List Tables { get; set; } #endregion << Vars>> #region << Example Of Calling Functions >> //ypu can use this as SFO.Atribute public string Attribute { get { if (Tables == null) return ""; foreach (Table t in Tables) { if (t.Name == "ATTRIBUTE") return t.Value; } return ""; } } public DataTypes DataType { get { if (Tables == null) return DataTypes.None; foreach (Table t in Tables) if (t.Name == "CATEGORY") return ((DataTypes)BitConverter.ToUInt16(Encoding.UTF8.GetBytes(t.Value), 0)); return DataTypes.None; } } public string APP_VER { get { if (Tables == null) return ""; foreach (Table t in Tables) { if (t.Name == "APP_VER") return t.Value; } return ""; } } public string Detail { get { if (Tables == null) return ""; foreach (Table t in Tables) if (t.Name == "DETAIL") return t.Value; return ""; } } public string ContentID { get { if (Tables == null) return ""; foreach (Table t in Tables) if (t.Name == "CONTENT_ID") return t.Value; return ""; } } public string GetValue(string tagName) { foreach (Table t in Tables) if (t.Name == tagName) return t.Value; return ""; } public string TITLEID { get { if (Tables == null) return ""; foreach (Table t in Tables) if (t.Name == "TITLE_ID") return t.Value; return ""; } } public string TitleID { get { string name = TITLEID; if (name == "") return ""; return name.Split('-')[0]; } } public string Title { get { if (Tables == null) return ""; foreach (Table t in Tables) if (t.Name == "TITLE") return t.Value; return ""; } } public string Category { get { if (Tables == null) return ""; foreach (Table t in Tables) if (t.Name == "CATEGORY") return t.Value; return ""; } } public enum Playstation { ps3 = 0, psvita = 1, ps4 = 2, psp = 3, unknown = 4,//there will be a time i no longer support the scene this will be for ps5+ most probabbly } public Playstation PlaystationVersion { get { if (Tables == null) return Playstation.unknown; foreach (Table t in Tables) { if (t.Name == "PS3_SYSTEM_VER") return Playstation.ps3;//this is the unique offset for ps3 if (t.Name == "PSP2_SYSTEM_VER") { return Playstation.psvita;//this is the only flag that tells us its a psvita } if (t.Name == "PSP_SYSTEM_VER") { return Playstation.psp;//this is how we know its a psp } if (t.Name == "SYSTEM_VER")//i believe this to be only ps4 { return Playstation.ps4; } } return Playstation.unknown; } } #endregion << Example Of Calling Functions >> #region Param.SFO Struct public struct Header { public static byte[] Magic = { 0, 0x50, 0x53, 0x46 }; public static byte[] version = { 01, 01, 0, 0 }; public static uint KeyTableStart = 0; public static uint DataTableStart = 0; public static uint IndexTableEntries = 0; private static byte[] Buffer { get { var header = new byte[20]; Array.Copy(Magic, 0, header, 0, 4); Array.Copy(version, 0, header, 4, 4); Array.Copy(BitConverter.GetBytes(KeyTableStart), 0, header, 8, 4); Array.Copy(BitConverter.GetBytes(DataTableStart), 0, header, 12, 4); Array.Copy(BitConverter.GetBytes(IndexTableEntries), 0, header, 16, 4); return header; } } public static void Read(BinaryReader input) { input.BaseStream.Seek(0, SeekOrigin.Begin); input.Read(Magic, 0, 4); input.Read(version, 0, 4); KeyTableStart = input.ReadUInt32(); DataTableStart = input.ReadUInt32(); IndexTableEntries = input.ReadUInt32(); } } [Serializable] public struct Table : IComparable { public index_table Indextable; public string Name; public string Value; public int index; public byte[] NameBuffer { get { var buffer = new byte[Name.Length + 1]; Array.Copy(Encoding.UTF8.GetBytes(Name), 0, buffer, 0, Encoding.UTF8.GetBytes(Name).Length); return buffer; } } public byte[] ValueBuffer { get { byte[] buffer; switch (Indextable.param_data_fmt) { case FMT.ASCII: buffer = new byte[Indextable.param_data_max_len]; Array.Copy(Encoding.ASCII.GetBytes(Value), 0, buffer, 0, Encoding.UTF8.GetBytes(Value).Length); return buffer; case FMT.UINT32: return BitConverter.GetBytes(uint.Parse(Value)); case FMT.UTF_8: buffer = new byte[Indextable.param_data_max_len]; Array.Copy(Encoding.UTF8.GetBytes(Value), 0, buffer, 0, Encoding.UTF8.GetBytes(Value).Length); return buffer; case FMT.Utf8Null: buffer = new byte[Indextable.param_data_max_len]; Array.Copy(Encoding.UTF8.GetBytes(Value), 0, buffer, 0, Encoding.UTF8.GetBytes(Value).Length);/*write the length of the array*/ return buffer; default: return null; } } } public int CompareTo(object obj) { throw new NotImplementedException(); } } [Serializable] public struct index_table { public FMT param_data_fmt; /* param_data data type */ public uint param_data_len; /* param_data used bytes */ public uint param_data_max_len; /* param_data total reserved bytes */ public uint param_data_offset; /* param_data offset (relative to start offset of data_table) */ public ushort param_key_offset; /* param_key offset (relative to start offset of key_table) */ private byte[] Buffer { get { var data = new byte[16]; Array.Copy(BitConverter.GetBytes(param_key_offset), 0, data, 0, 2); Array.Copy(BitConverter.GetBytes(((ushort)param_data_fmt)), 0, data, 2, 2); Array.Copy(BitConverter.GetBytes(param_data_len), 0, data, 4, 4); Array.Copy(BitConverter.GetBytes(param_data_max_len), 0, data, 8, 4); Array.Copy(BitConverter.GetBytes(param_data_offset), 0, data, 12, 4); return data; } } public void Read(BinaryReader input) { param_key_offset = input.ReadUInt16(); param_data_fmt = (FMT)input.ReadUInt16(); param_data_len = input.ReadUInt32(); param_data_max_len = input.ReadUInt32(); param_data_offset = input.ReadUInt32(); } } [Serializable] private enum DATA_TYPE : byte { BinaryData = 0, Utf8Text = 2, Si32Integer = 4 } #endregion Param.SFO Struct #region << Methods >> public PARAM_SFO() { } public PARAM_SFO(string filepath) { Init(new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read)); } public PARAM_SFO(byte[] inputdata) { Init(new MemoryStream(inputdata)); } public PARAM_SFO(Stream input) { Init(input); } /// /// This is the SaveSFO Function for PS3/PS4/PSVita/And PSP no longer needed for Sony's CMD /// /// SFO That has been opened /// Save Location public void SaveSFO(PARAM_SFO psfo, string filename) { //we start by opening the stream to the file using (var stream = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.Read)) { if (!stream.CanSeek) throw new ArgumentException("Stream must be seekable");//throw this error we cant seek the stream var utf8 = new UTF8Encoding(false);//encoding using (var writer = new BinaryWriter(stream, utf8))//start binary reader { #region << Header Info (DevWiki) >> /* Header * 0x00 0x04 magic PSF 0x04 0x04 version 01 01 00 00 1.01 0x08 0x04 key_table_start 24 00 00 00 Absolute start offset of key_table = 0x24 0x0C 0x04 data_table_start 30 00 00 00 Absolute start offset of data_table = 0x30 0x10 0x04 tables_entries 01 00 00 00 Number of entries in index_table, key_table, and data_table = 1 */ #endregion <
> //so lets start writing the info writer.Write(Header.Magic);//write magic "\0PSF" writer.Write(Header.version);//write version info this is mayjor and minor (01 01 00 00 1.01) Header.KeyTableStart = 0x14 + Header.IndexTableEntries * 0x10;/*we can write all this lovely info from the tables back*/ writer.Write(Header.KeyTableStart); Header.DataTableStart = Convert.ToUInt32(Header.KeyTableStart + Tables.Sum(i => i.Name.Length + 1));//needs to be Uint if (Header.DataTableStart % 4 != 0) Header.DataTableStart = (Header.DataTableStart / 4 + 1) * 4; writer.Write(Header.DataTableStart); Header.IndexTableEntries = Convert.ToUInt32(Tables.Count); writer.Write(Header.IndexTableEntries); int lastKeyOffset = Convert.ToInt32(Header.KeyTableStart); int lastValueOffset = Convert.ToInt32(Header.DataTableStart); for (var i = 0; i < Tables.Count; i++) { var entry = Tables[i]; writer.BaseStream.Seek(0x14 + i * 0x10, SeekOrigin.Begin); writer.Write((ushort)(lastKeyOffset - Header.KeyTableStart)); writer.Write((ushort)entry.Indextable.param_data_fmt); writer.Write(entry.Indextable.param_data_len); writer.Write(entry.Indextable.param_data_max_len); writer.Write(lastValueOffset - Header.DataTableStart); writer.BaseStream.Seek(lastKeyOffset, SeekOrigin.Begin); writer.Write(utf8.GetBytes(entry.Name)); writer.Write((byte)0); lastKeyOffset = (int)writer.BaseStream.Position; writer.BaseStream.Seek(lastValueOffset, SeekOrigin.Begin); writer.Write(entry.ValueBuffer); lastValueOffset = (int)writer.BaseStream.Position; } //I'm doing this to just rewrite the first item (Some Cleanup will be needed) //Or maybe not as when I checked this gives a 1 - 1 match with how the Sony tool works //we need to rewrite that first item (PS4/PS3/PSV should be APP-VER) lastKeyOffset = Convert.ToInt32(Header.KeyTableStart); lastValueOffset = Convert.ToInt32(Header.DataTableStart); var tableentry = Tables[0]; writer.BaseStream.Seek(lastKeyOffset, SeekOrigin.Begin); writer.Write(utf8.GetBytes(tableentry.Name)); writer.Write((byte)0); lastKeyOffset = (int)writer.BaseStream.Position; } } } private string ReadValue(BinaryReader br, index_table table) { br.BaseStream.Position = ((Header.DataTableStart) + table.param_data_offset); switch (table.param_data_fmt) { case FMT.ASCII: //return Encoding.GetEncoding(1252).GetString(br.ReadBytes((int) table.param_data_max_len)).Replace("\0", ""); return Encoding.UTF8.GetString(br.ReadBytes((int)table.param_data_max_len)).Replace("\0", ""); case FMT.UINT32: return br.ReadUInt32().ToString(); case FMT.UTF_8: return Encoding.UTF8.GetString(br.ReadBytes((int)table.param_data_max_len)).Replace("\0", ""); case FMT.Utf8Null: return Encoding.UTF8.GetString(br.ReadBytes((int)table.param_data_max_len)).Replace("\0", ""); default: return null; } } private string ReadValueSpecialChars(BinaryReader br, index_table table) { br.BaseStream.Position = ((Header.DataTableStart) + table.param_data_offset); switch (table.param_data_fmt) { case FMT.ASCII: return Encoding.UTF8.GetString(br.ReadBytes((int)table.param_data_max_len)).Replace("\0", ""); case FMT.UINT32: return br.ReadUInt32().ToString(); case FMT.UTF_8: return Encoding.UTF8.GetString(br.ReadBytes((int)table.param_data_max_len)).Replace("\0", ""); default: return null; } } private string ReadName(BinaryReader br, index_table table) { br.BaseStream.Position = (Header.KeyTableStart + table.param_key_offset); string name = ""; while (((byte)br.PeekChar()) != 0) name += br.ReadChar(); br.BaseStream.Position++; return name; } /// /// Start Reading the Parameter file /// /// Input Stream private void Init(Stream input) { using (var br = new BinaryReader(input)) { Header.Read(br); var tables = new List(); for (int i = 0; i < Header.IndexTableEntries; i++) { var t = new index_table(); t.Read(br); tables.Add(t); } var xtables = new List
(); int count = 0; foreach (index_table t in tables) { var x = new Table(); x.index = count; x.Indextable = t; x.Name = ReadName(br, t); x.Value = ReadValue(br, t); count++; xtables.Add(x); } Tables = xtables; br.Close(); } } #endregion << Methods >> } }