From 292ff60634d919ff6d1fae64336d0855aafbeec4 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sat, 30 Nov 2019 04:31:15 -0500 Subject: [PATCH] preliminary save + load implementation of Sony PSF (PARAM.SFO) database format --- .../Sony/Database/PSFDataFormat.uexml | 28 ++ ...lEditor.Content.PlatformIndependent.csproj | 1 + .../DataFormats/Database/PSFColumnDataType.cs | 39 +++ .../DataFormats/Database/PSFColumnInfo.cs | 41 +++ .../DataFormats/Database/PSFDataFormat.cs | 243 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 46 ++++ .../UniversalEditor.Plugins.Sony.csproj | 52 ++++ CSharp/UniversalEditor.sln | 7 + 8 files changed, 457 insertions(+) create mode 100644 CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Extensions/Sony/Database/PSFDataFormat.uexml create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnDataType.cs create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnInfo.cs create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFDataFormat.cs create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Sony/Properties/AssemblyInfo.cs create mode 100644 CSharp/Plugins/UniversalEditor.Plugins.Sony/UniversalEditor.Plugins.Sony.csproj diff --git a/CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Extensions/Sony/Database/PSFDataFormat.uexml b/CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Extensions/Sony/Database/PSFDataFormat.uexml new file mode 100644 index 00000000..b88d507a --- /dev/null +++ b/CSharp/Content/UniversalEditor.Content.PlatformIndependent/Extensions/GameDeveloper/Extensions/Sony/Database/PSFDataFormat.uexml @@ -0,0 +1,28 @@ + + + + + + + + *.sfo + + + + 00 + + + PSF + + + + + + + + + + + + + \ No newline at end of file diff --git a/CSharp/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj b/CSharp/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj index 6a3e0bab..066a2985 100644 --- a/CSharp/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj +++ b/CSharp/Content/UniversalEditor.Content.PlatformIndependent/UniversalEditor.Content.PlatformIndependent.csproj @@ -654,6 +654,7 @@ + diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnDataType.cs b/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnDataType.cs new file mode 100644 index 00000000..68286499 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnDataType.cs @@ -0,0 +1,39 @@ +// +// PSFColumnDataType.cs +// +// Author: +// Mike Becker +// +// Copyright (c) 2019 Mike Becker +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +namespace UniversalEditor.Plugins.Sony.DataFormats.Database +{ + public enum PSFColumnDataType : ushort + { + /// + /// UTF-8 Special mode, NOT -terminated + /// + UTF8S = 4, + /// + /// UTF-8 character string, -terminated + /// + UTF8Z = 516, + /// + /// 32-bit integer, unsigned + /// + UInt32 = 1028 + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnInfo.cs b/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnInfo.cs new file mode 100644 index 00000000..c80480d2 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFColumnInfo.cs @@ -0,0 +1,41 @@ +// +// PSFColumnInfo.cs +// +// Author: +// Mike Becker +// +// Copyright (c) 2019 Mike Becker +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +namespace UniversalEditor.Plugins.Sony.DataFormats.Database +{ + public struct PSFColumnInfo + { + public ushort columnNameOffset; + public PSFColumnDataType columnDataType; + public uint dataLength; + public uint dataMaxLength; + public uint valueOffset; + + public PSFColumnInfo(ushort columnNameOffset, PSFColumnDataType columnDataType, uint dataLength, uint dataMaxLength, uint valueOffset) : this() + { + this.columnNameOffset = columnNameOffset; + this.columnDataType = columnDataType; + this.dataLength = dataLength; + this.dataMaxLength = dataMaxLength; + this.valueOffset = valueOffset; + } + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFDataFormat.cs b/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFDataFormat.cs new file mode 100644 index 00000000..d5f11139 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Sony/DataFormats/Database/PSFDataFormat.cs @@ -0,0 +1,243 @@ +// +// PSFDataFormat.cs +// +// Author: +// Mike Becker +// +// Copyright (c) 2019 Mike Becker +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using System.Collections.Generic; +using UniversalEditor.Accessors; +using UniversalEditor.IO; +using UniversalEditor.ObjectModels.Database; + +namespace UniversalEditor.Plugins.Sony.DataFormats.Database +{ + public class PSFDataFormat : DataFormat + { + private static DataFormatReference _dfr = null; + protected override DataFormatReference MakeReferenceInternal() + { + if (_dfr == null) + { + _dfr = base.MakeReferenceInternal(); + _dfr.Capabilities.Add(typeof(DatabaseObjectModel), DataFormatCapabilities.All); + } + return _dfr; + } + + protected override void LoadInternal(ref ObjectModel objectModel) + { + DatabaseObjectModel db = (objectModel as DatabaseObjectModel); + if (db == null) + throw new ObjectModelNotSupportedException(); + + Reader reader = base.Accessor.Reader; + string signature = reader.ReadFixedLengthString(4); + if (signature != "\0PSF") + throw new InvalidDataFormatException("file does not begin with '\\0PSF'"); + + uint unknown1 = reader.ReadUInt32(); + uint stringTableOffset = reader.ReadUInt32(); + uint dataTableOffset = reader.ReadUInt32(); // to "01.00" + uint columnCount = reader.ReadUInt32(); + + PSFColumnInfo[] columnInfos = new PSFColumnInfo[columnCount]; + for (uint i = 0; i < columnCount; i++) + { + // each entry is 16 bytes (four x UInt32) + ushort columnNameOffset = reader.ReadUInt16(); // offset into string table + PSFColumnDataType columnDataType = (PSFColumnDataType) reader.ReadUInt16(); + uint dataLength = reader.ReadUInt32(); + uint dataMaxLength = reader.ReadUInt32(); + uint valueOffset = reader.ReadUInt32(); + columnInfos[i] = new PSFColumnInfo(columnNameOffset, columnDataType, dataLength, dataMaxLength, valueOffset); + } + + // now we should be at the string table + uint stringTableDataLength = dataTableOffset - stringTableOffset; + byte[] stringTableData = reader.ReadBytes(stringTableDataLength); + + MemoryAccessor maStringTable = new MemoryAccessor(stringTableData); + + DatabaseTable dt = new DatabaseTable(); + DatabaseRecord rec = new DatabaseRecord(); + + for (uint i = 0; i < columnCount; i++) + { + maStringTable.Seek(columnInfos[i].columnNameOffset, SeekOrigin.Begin); + string columnName = maStringTable.Reader.ReadNullTerminatedString(); + object value = null; + + reader.Seek(dataTableOffset + columnInfos[i].valueOffset, SeekOrigin.Begin); + switch (columnInfos[i].columnDataType) + { + case PSFColumnDataType.UInt32: + { + value = reader.ReadUInt32(); + break; + } + case PSFColumnDataType.UTF8S: + { + value = reader.ReadFixedLengthString(columnInfos[i].dataLength, Encoding.UTF8); + break; + } + case PSFColumnDataType.UTF8Z: + { + value = reader.ReadNullTerminatedString(Encoding.UTF8); + break; + } + } + + dt.Fields.Add(columnName, value); + rec.Fields.Add(columnName, value); + } + + dt.Records.Add(rec); + db.Tables.Add(dt); + } + + protected override void SaveInternal(ObjectModel objectModel) + { + DatabaseObjectModel db = (objectModel as DatabaseObjectModel); + if (db == null) + throw new ObjectModelNotSupportedException(); + if (db.Tables.Count != 1) + throw new ObjectModelNotSupportedException("must have exactly one table to write PSF"); + + Writer writer = base.Accessor.Writer; + writer.WriteFixedLengthString("\0PSF"); + + DatabaseTable dt = db.Tables[0]; + + writer.WriteBytes(new byte[] { 01, 01, 00, 00 }); // version + + uint stringTableOffset = (uint)(20 + (16 * dt.Fields.Count)); + stringTableOffset = stringTableOffset.RoundUp(4); + writer.WriteUInt32(stringTableOffset); + + uint dataTableOffset = stringTableOffset; + for (int i = 0; i < dt.Fields.Count; i++) + { + dataTableOffset += (uint)dt.Fields[i].Name.Length + 1; + } + dataTableOffset = dataTableOffset.RoundUp(4); + writer.WriteUInt32(dataTableOffset); + + writer.WriteUInt32((uint)dt.Fields.Count); + + ushort columnNameOffset = 0; + uint valueOffset = 0; + PSFColumnDataType[] columnDataTypes = new PSFColumnDataType[dt.Fields.Count]; + for (int i = 0; i < dt.Fields.Count; i++) + { + // each entry is 16 bytes (four x UInt32) + writer.WriteUInt16(columnNameOffset); // offset into string table + + if (dt.Fields[i].Value is string) + { + columnDataTypes[i] = PSFColumnDataType.UTF8Z; + } + else if (dt.Fields[i].Value is uint) + { + columnDataTypes[i] = PSFColumnDataType.UInt32; + } + writer.WriteUInt16((ushort)columnDataTypes[i]); + + uint dataLength = 4; + int dataMaxLength = GetMaxLengthForField(dt.Fields[i].Name); + if (dt.Fields[i].Value is string) + { + dataLength = (uint)(System.Text.Encoding.UTF8.GetByteCount((string)dt.Fields[i].Value) + 1); + // dataLength = (uint)(((string)dt.Fields[i].Value).Length + 1); + } + else if (dt.Fields[i].Value is uint) + { + dataMaxLength = 4; + } + writer.WriteUInt32(dataLength); + writer.WriteInt32(dataMaxLength); + writer.WriteUInt32(valueOffset); + if (dt.Fields[i].Value is string) + { + valueOffset += (uint) dataMaxLength; + } + else if (dt.Fields[i].Value is uint) + { + valueOffset += 4; + } + + columnNameOffset += (ushort)(dt.Fields[i].Name.Length + 1); + } + + // now we should be at the string table + for (int i = 0; i < dt.Fields.Count; i++) + { + writer.WriteNullTerminatedString(dt.Fields[i].Name); + } + + // now we should be at the data table + writer.Align(4); + + for (int i = 0; i < dt.Fields.Count; i++) + { + switch (columnDataTypes[i]) + { + case PSFColumnDataType.UInt32: + { + writer.WriteUInt32((uint)(dt.Fields[i].Value)); + break; + } + case PSFColumnDataType.UTF8S: + { + writer.WriteFixedLengthString((string)dt.Fields[i].Value, Encoding.UTF8, GetMaxLengthForField(dt.Fields[i].Name)); + break; + } + case PSFColumnDataType.UTF8Z: + { + writer.WriteFixedLengthString((string)dt.Fields[i].Value, Encoding.UTF8, GetMaxLengthForField(dt.Fields[i].Name)); + break; + } + } + writer.Align(4); + } + } + + static PSFDataFormat() + { + maxLengthsForField.Add("APP_VER", 8); + maxLengthsForField.Add("ATTRIBUTE", 4); + maxLengthsForField.Add("BOOTABLE", 4); + maxLengthsForField.Add("CATEGORY", 4); + maxLengthsForField.Add("LICENSE", 512); + maxLengthsForField.Add("PARENTAL_LEVEL", 4); + maxLengthsForField.Add("PS3_SYSTEM_VER", 8); + maxLengthsForField.Add("RESOLUTION", 4); + maxLengthsForField.Add("SOUND_FORMAT", 4); + maxLengthsForField.Add("TITLE", 128); + maxLengthsForField.Add("TITLE_ID", 16); + maxLengthsForField.Add("VERSION", 8); + } + + private static Dictionary maxLengthsForField = new Dictionary(); + private static int GetMaxLengthForField(string name) + { + if (maxLengthsForField.ContainsKey(name)) + return maxLengthsForField[name]; + return 16; + } + } +} diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Sony/Properties/AssemblyInfo.cs b/CSharp/Plugins/UniversalEditor.Plugins.Sony/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..6679ad10 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Sony/Properties/AssemblyInfo.cs @@ -0,0 +1,46 @@ +// +// AssemblyInfo.cs +// +// Author: +// Mike Becker +// +// Copyright (c) 2019 Mike Becker +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("UniversalEditor.Plugins.Sony")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Mike Becker")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/CSharp/Plugins/UniversalEditor.Plugins.Sony/UniversalEditor.Plugins.Sony.csproj b/CSharp/Plugins/UniversalEditor.Plugins.Sony/UniversalEditor.Plugins.Sony.csproj new file mode 100644 index 00000000..a0306da1 --- /dev/null +++ b/CSharp/Plugins/UniversalEditor.Plugins.Sony/UniversalEditor.Plugins.Sony.csproj @@ -0,0 +1,52 @@ + + + + Debug + AnyCPU + {F9854149-5685-4F35-A698-C68F30AAF1B0} + Library + UniversalEditor.Plugins.Sony + UniversalEditor.Plugins.Sony + + + true + full + false + ..\..\Output\Debug\Plugins + DEBUG; + prompt + 4 + false + + + true + ..\..\Output\Release\Plugins + prompt + 4 + false + + + + + + + + + + + + + + + + + {2D4737E6-6D95-408A-90DB-8DFF38147E85} + UniversalEditor.Core + + + {30467E5C-05BC-4856-AADC-13906EF4CADD} + UniversalEditor.Essential + + + + \ No newline at end of file diff --git a/CSharp/UniversalEditor.sln b/CSharp/UniversalEditor.sln index df6c726f..367dbef5 100644 --- a/CSharp/UniversalEditor.sln +++ b/CSharp/UniversalEditor.sln @@ -149,6 +149,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.CRI EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.CRI.UserInterface", "Plugins.UserInterface\UniversalEditor.Plugins.CRI.UserInterface\UniversalEditor.Plugins.CRI.UserInterface.csproj", "{83709A63-8C43-4C67-80F6-00022986A086}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalEditor.Plugins.Sony", "Plugins\UniversalEditor.Plugins.Sony\UniversalEditor.Plugins.Sony.csproj", "{F9854149-5685-4F35-A698-C68F30AAF1B0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -429,6 +431,10 @@ Global {83709A63-8C43-4C67-80F6-00022986A086}.Debug|Any CPU.Build.0 = Debug|Any CPU {83709A63-8C43-4C67-80F6-00022986A086}.Release|Any CPU.ActiveCfg = Release|Any CPU {83709A63-8C43-4C67-80F6-00022986A086}.Release|Any CPU.Build.0 = Release|Any CPU + {F9854149-5685-4F35-A698-C68F30AAF1B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9854149-5685-4F35-A698-C68F30AAF1B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9854149-5685-4F35-A698-C68F30AAF1B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9854149-5685-4F35-A698-C68F30AAF1B0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6F0AB1AF-E1A1-4D19-B19C-05BBB15C94B2} = {05D15661-E684-4EC9-8FBD-C014BA433CC5} @@ -498,6 +504,7 @@ Global {0EEC3646-9749-48AF-848E-0F699247E76F} = {2ED32D16-6C06-4450-909A-40D32DA67FB4} {DBA93D1B-01BC-4218-8309-85FA0D5402FC} = {2ED32D16-6C06-4450-909A-40D32DA67FB4} {83709A63-8C43-4C67-80F6-00022986A086} = {7B535D74-5496-4802-B809-89ED88274A91} + {F9854149-5685-4F35-A698-C68F30AAF1B0} = {2ED32D16-6C06-4450-909A-40D32DA67FB4} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0