chovy-trans/CHOVY-TRANSFER/param.cs

569 lines
20 KiB
C#

/* 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<Table> 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);
}
/// <summary>
/// This is the SaveSFO Function for PS3/PS4/PSVita/And PSP no longer needed for Sony's CMD
/// </summary>
/// <param name="psfo">SFO That has been opened</param>
/// <param name="filename">Save Location</param>
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 <<Header Info >>
//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;
}
/// <summary>
/// Start Reading the Parameter file
/// </summary>
/// <param name="input">Input Stream</param>
private void Init(Stream input)
{
using (var br = new BinaryReader(input))
{
Header.Read(br);
var tables = new List<index_table>();
for (int i = 0; i < Header.IndexTableEntries; i++)
{
var t = new index_table();
t.Read(br);
tables.Add(t);
}
var xtables = new List<Table>();
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 >>
}
}