Skip to content

Commit

Permalink
Add GP4 validator with several validation checks for #59
Browse files Browse the repository at this point in the history
  • Loading branch information
maxton committed Jul 23, 2020
1 parent 30b0780 commit a197b89
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 3 deletions.
1 change: 1 addition & 0 deletions LibOrbisPkg.Core/LibOrbisPkg.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<ItemGroup>
<Compile Include="..\LibOrbisPkg\GP4\Gp4Creator.cs" Link="GP4\Gp4Creator.cs" />
<Compile Include="..\LibOrbisPkg\GP4\Gp4Project.cs" Link="GP4\Gp4Project.cs" />
<Compile Include="..\LibOrbisPkg\GP4\Gp4Validator.cs" Link="GP4\Gp4Validator.cs" />
<Compile Include="..\LibOrbisPkg\PFS\FlatPathTable.cs" Link="PFS\FlatPathTable.cs" />
<Compile Include="..\LibOrbisPkg\PFS\FSTree.cs" Link="PFS\FSTree.cs" />
<Compile Include="..\LibOrbisPkg\PFS\PFSBuilder.cs" Link="PFS\PFSBuilder.cs" />
Expand Down
209 changes: 209 additions & 0 deletions LibOrbisPkg/GP4/Gp4Validator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using LibOrbisPkg.SFO;

namespace LibOrbisPkg.GP4
{
public class ValidateResult
{
public enum ResultType
{
Warning,
Fatal,
}
public readonly ResultType Type;
public readonly string Message;
internal ValidateResult(ResultType t, string m)
{
Type = t;
Message = m;
}
public static ValidateResult Fatal(string message)
{
return new ValidateResult(ResultType.Fatal, message);
}
public static ValidateResult Warning(string messsage)
{
return new ValidateResult(ResultType.Warning, messsage);
}
}
public class Gp4Validator
{
private static ValidateResult checkPasscode(Gp4Project proj, string dir)
{
if (proj.volume.Package.Passcode.Length != 32)
{
return ValidateResult.Fatal("Passcode must be 32 characters long.");
}
return null;
}
private static ValidateResult checkAllFilesExist (Gp4Project proj, string dir)
{
var missingFiles = proj.files.Items
.Where(f => Path.Combine(dir, f.OrigPath) is string filePath && !File.Exists(filePath))
.Aggregate((string)null, (s, f) => s == null ? f.OrigPath : (s + ", " + f.OrigPath));
if (missingFiles != null)
{
return ValidateResult.Fatal("Could not find source file(s): " + missingFiles);
}
return null;
}
private static ValidateResult checkDuplicateFilenames(Gp4Project proj, string dir)
{
var dupeFiles = proj.files.Items
.GroupBy(f => f.TargetPath)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.Aggregate((string)null, (s, f) => s == null ? f : (s + ", " + f));
if (dupeFiles != null)
{
return ValidateResult.Fatal("PKG has duplicate filename(s): " + dupeFiles);
}
return null;
}
private static ValidateResult checkContentIdFormat(Gp4Project proj, string dir)
{
var pkgContentId = proj.volume.Package.ContentId;
Regex contentIdReg = new Regex("^[A-Z]{2}[0-9]{4}-[A-Z]{4}[0-9]{5}_00-[A-Z0-9]{16}$");
if (contentIdReg.IsMatch(pkgContentId))
{
return null;
}
return ValidateResult.Warning(
"PKG Content ID is the wrong format. " +
"Format should be XXYYYY-XXXXYYYYY_00-ZZZZZZZZZZZZZZZZ, where X is a letter, Y is a number, and Z is either.");
}
private static ValidateResult checkContentIdLength(Gp4Project proj, string dir)
{
if (proj.volume.Package.ContentId.Length != 36)
{
return ValidateResult.Fatal("PKG Content ID must be 36 characters long.");
}
return null;
}
private static ValidateResult checkPkgVolumeType(Gp4Project proj, string dir)
{
var pkgType = proj.volume.Type;
switch (pkgType)
{
case VolumeType.pkg_ps4_app:
break;
case VolumeType.pkg_ps4_ac_data:
break;
case VolumeType.pkg_ps4_ac_nodata:
break;
default:
return ValidateResult.Fatal(
"Unsupported PKG volume type: " + pkgType);
}
return null;
}
private static List<Func<Gp4Project, string, ValidateResult>> commonChecks = new List<Func<Gp4Project, string, ValidateResult>>()
{
checkPasscode,
checkAllFilesExist,
checkDuplicateFilenames,
checkContentIdLength,
checkContentIdFormat,
checkPkgVolumeType,
};
private static ValidateResult checkContentIdMatchesSfo(Gp4Project proj, string dir, ParamSfo sfo)
{
var pkgContentId = proj.volume.Package.ContentId;
var sfoContentId = sfo["CONTENT_ID"].ToString();
if (pkgContentId != sfoContentId)
{
return ValidateResult.Warning(
$"PKG Content ID {pkgContentId} does not match CONTENT_ID {sfoContentId} in param.sfo.");
}
return null;
}
private static ValidateResult checkContentIdMatchesTitleIdSfo(Gp4Project proj, string dir, ParamSfo sfo)
{
var sfoContentId = sfo["CONTENT_ID"].ToString();
var sfoTitleId = sfo["TITLE_ID"].ToString();
if (sfoContentId.Substring(7).StartsWith(sfoTitleId))
{
return null;
}
return ValidateResult.Warning(
$"SFO TITLE_ID {sfoTitleId} does not match the CONTENT_ID {sfoContentId}.");
}
private static ValidateResult checkCategoryMatchesPkgType(Gp4Project proj, string dir, ParamSfo sfo)
{
var sfoCategory = sfo["CATEGORY"].ToString();
var pkgType = proj.volume.Type;
bool ok = true;
switch (pkgType)
{
case VolumeType.pkg_ps4_app:
if (!sfoCategory.StartsWith("g"))
ok = false;
break;
case VolumeType.pkg_ps4_ac_data:
case VolumeType.pkg_ps4_ac_nodata:
case VolumeType.pkg_ps4_theme:
case VolumeType.pkg_ps4_sf_theme:
if (sfoCategory != "ac")
ok = false;
break;
}
if (!ok)
{
return ValidateResult.Warning(
$"SFO CATEGORY {sfoCategory} is not valid for PKG volume type {pkgType}.");
}
return null;
}
private static List<Func<Gp4Project, string, ParamSfo, ValidateResult>> sfoChecks = new List<Func<Gp4Project, string, ParamSfo, ValidateResult>>()
{
checkContentIdMatchesSfo,
checkContentIdMatchesTitleIdSfo,
checkCategoryMatchesPkgType
};

public static List<ValidateResult> ValidateProject(Gp4Project proj, string projDir)
{
var ret = new List<ValidateResult>();

foreach(var check in commonChecks)
{
var result = check(proj, projDir);
if (result != null) ret.Add(result);
}
// Checks with project and SFO file
if (proj.files.Items.Where(f => f.TargetPath == "sce_sys/param.sfo").FirstOrDefault() is Gp4File sfoFile
&& Path.Combine(projDir, sfoFile.OrigPath) is string sfoPath
&& File.Exists(sfoPath))
{
ParamSfo sfoObject = null;
try
{
using (var f = File.OpenRead(sfoPath))
{
sfoObject = ParamSfo.FromStream(f);
}
}
catch (Exception e)
{
ret.Add(ValidateResult.Fatal("Could not load param.sfo file: " + e.Message));
}
if (sfoObject != null) foreach (var check in sfoChecks)
{
var result = check(proj, projDir, sfoObject);
if (result != null) ret.Add(result);
}
}
else
{
ret.Add(ValidateResult.Fatal("Required file sce_sys/param.sfo is missing."));
}
return ret;
}
}
}
1 change: 1 addition & 0 deletions LibOrbisPkg/LibOrbisPkg.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<ItemGroup>
<Compile Include="GP4\Gp4Creator.cs" />
<Compile Include="GP4\Gp4Project.cs" />
<Compile Include="GP4\Gp4Validator.cs" />
<Compile Include="PFS\PFSCReader.cs" />
<Compile Include="PFS\PFSCWriter.cs" />
<Compile Include="PFS\PfsReader.cs" />
Expand Down
3 changes: 3 additions & 0 deletions PkgEditor.Core/PkgEditor.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<Compile Include="..\PkgEditor\PasscodeEntry.Designer.cs" Link="PasscodeEntry.Designer.cs" />
<Compile Include="..\PkgEditor\Program.cs" Link="Program.cs" />
<Compile Include="..\PkgEditor\Properties\AssemblyInfo.cs" Link="Properties\AssemblyInfo.cs" />
<Compile Include="..\PkgEditor\ValidationDialog.cs" Link="ValidationDialog.cs" />
<Compile Include="..\PkgEditor\ValidationDialog.Designer.cs" Link="ValidationDialog.Designer.cs" />
<Compile Include="..\PkgEditor\Views\CryptoDebug.cs" Link="Views\CryptoDebug.cs" />
<Compile Include="..\PkgEditor\Views\CryptoDebug.Designer.cs" Link="Views\CryptoDebug.Designer.cs" />
<Compile Include="..\PkgEditor\Views\FileView.cs" Link="Views\FileView.cs" />
Expand All @@ -44,6 +46,7 @@
<EmbeddedResource Include="..\PkgEditor\LogWindow.resx" Link="LogWindow.resx" />
<EmbeddedResource Include="..\PkgEditor\MainWin.resx" Link="MainWin.resx" />
<EmbeddedResource Include="..\PkgEditor\PasscodeEntry.resx" Link="PasscodeEntry.resx" />
<EmbeddedResource Include="..\PkgEditor\ValidationDialog.resx" Link="ValidationDialog.resx" />
<EmbeddedResource Include="..\PkgEditor\Views\CryptoDebug.resx" Link="Views\CryptoDebug.resx" />
<EmbeddedResource Include="..\PkgEditor\Views\FileView.resx" Link="Views\FileView.resx" />
<EmbeddedResource Include="..\PkgEditor\Views\GP4View.resx" Link="Views\GP4View.resx" />
Expand Down
9 changes: 9 additions & 0 deletions PkgEditor/PkgEditor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
<Compile Include="KeyDB.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ValidationDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="ValidationDialog.Designer.cs">
<DependentUpon>ValidationDialog.cs</DependentUpon>
</Compile>
<Compile Include="Views\CryptoDebug.cs">
<SubType>UserControl</SubType>
</Compile>
Expand Down Expand Up @@ -136,6 +142,9 @@
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<EmbeddedResource Include="ValidationDialog.resx">
<DependentUpon>ValidationDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Views\CryptoDebug.resx">
<DependentUpon>CryptoDebug.cs</DependentUpon>
</EmbeddedResource>
Expand Down
122 changes: 122 additions & 0 deletions PkgEditor/ValidationDialog.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a197b89

Please sign in to comment.