Overhaul BD+ to model/builder/wrapper

This commit is contained in:
Matt Nadareski
2023-01-12 14:45:04 -08:00
parent 371fbee7a4
commit 2d1e8e02aa
11 changed files with 386 additions and 72 deletions

View File

@@ -0,0 +1,95 @@
using System.IO;
using System.Text;
using BurnOutSharp.Models.BDPlus;
using BurnOutSharp.Utilities;
using static BurnOutSharp.Models.BDPlus.Constants;
namespace BurnOutSharp.Builders
{
public class BDPlus
{
#region Byte Data
/// <summary>
/// Parse a byte array into a BD+ SVM
/// </summary>
/// <param name="data">Byte array to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>Filled BD+ SVM on success, null on error</returns>
public static SVM ParseSVM(byte[] data, int offset)
{
// If the data is invalid
if (data == null)
return null;
// If the offset is out of bounds
if (offset < 0 || offset >= data.Length)
return null;
// Create a memory stream and parse that
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
return ParseSVM(dataStream);
}
#endregion
#region Stream Data
/// <summary>
/// Parse a Stream into an BD+ SVM
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled BD+ SVM on success, null on error</returns>
public static SVM ParseSVM(Stream data)
{
// If the data is invalid
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
return null;
// If the offset is out of bounds
if (data.Position < 0 || data.Position >= data.Length)
return null;
// Cache the current offset
int initialOffset = (int)data.Position;
// Try to parse the SVM
return ParseSVMData(data);
}
/// <summary>
/// Parse a Stream into an SVM
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled SVM on success, null on error</returns>
private static SVM ParseSVMData(Stream data)
{
// TODO: Use marshalling here instead of building
var svm = new SVM();
byte[] signature = data.ReadBytes(8);
svm.Signature = Encoding.ASCII.GetString(signature);
if (svm.Signature != SignatureString)
return null;
svm.Unknown1 = data.ReadBytes(5);
svm.Year = data.ReadUInt16BE();
svm.Month = data.ReadByteValue();
if (svm.Month < 1 || svm.Month > 12)
return null;
svm.Day = data.ReadByteValue();
if (svm.Day < 1 || svm.Day > 31)
return null;
svm.Unknown2 = data.ReadBytes(4);
svm.Length = data.ReadUInt32();
// if (svm.Length > 0)
// svm.Data = data.ReadBytes((int)svm.Length);
return svm;
}
#endregion
}
}

View File

@@ -0,0 +1,7 @@
namespace BurnOutSharp.Models.BDPlus
{
public static class Constants
{
public const string SignatureString = "BDSVM_CC";
}
}

View File

@@ -0,0 +1,46 @@
namespace BurnOutSharp.Models.BDPlus
{
/// <see href="https://github.com/mwgoldsmith/bdplus/blob/master/src/libbdplus/bdsvm/loader.c"/>
public sealed class SVM
{
/// <summary>
/// "BDSVM_CC"
/// </summary>
public string Signature;
/// <summary>
/// 5 bytes of unknown data
/// </summary>
public byte[] Unknown1;
/// <summary>
/// Version year
/// </summary>
public ushort Year;
/// <summary>
/// Version month
/// </summary>
public byte Month;
/// <summary>
/// Version day
/// </summary>
public byte Day;
/// <summary>
/// 4 bytes of unknown data
/// </summary>
public byte[] Unknown2;
/// <summary>
/// Length
/// </summary>
public uint Length;
/// <summary>
/// Length bytes of data
/// </summary>
public byte[] Data;
}
}

View File

@@ -0,0 +1,131 @@
using System;
using System.IO;
namespace BurnOutSharp.Wrappers
{
public class BDPlusSVM : WrapperBase
{
#region Pass-Through Properties
/// <inheritdoc cref="Models.BDPlus.SVM.Signature"/>
public string Signature => _svm.Signature;
/// <inheritdoc cref="Models.BDPlus.SVM.Unknown1"/>
public byte[] Unknown1 => _svm.Unknown1;
/// <inheritdoc cref="Models.BDPlus.SVM.Year"/>
public ushort Year => _svm.Year;
/// <inheritdoc cref="Models.BDPlus.SVM.Month"/>
public byte Month => _svm.Month;
/// <inheritdoc cref="Models.BDPlus.SVM.Day"/>
public byte Day => _svm.Day;
/// <inheritdoc cref="Models.BDPlus.SVM.Unknown2"/>
public byte[] Unknown2 => _svm.Unknown2;
/// <inheritdoc cref="Models.BDPlus.SVM.Length"/>
public uint Length => _svm.Length;
/// <inheritdoc cref="Models.BDPlus.SVM.Data"/>
public byte[] Data => _svm.Data;
#endregion
#region Instance Variables
/// <summary>
/// Internal representation of the SVM
/// </summary>
private Models.BDPlus.SVM _svm;
#endregion
#region Constructors
/// <summary>
/// Private constructor
/// </summary>
private BDPlusSVM() { }
/// <summary>
/// Create a BD+ SVM from a byte array and offset
/// </summary>
/// <param name="data">Byte array representing the archive</param>
/// <param name="offset">Offset within the array to parse</param>
/// <returns>A BD+ SVM wrapper on success, null on failure</returns>
public static BDPlusSVM Create(byte[] data, int offset)
{
// If the data is invalid
if (data == null)
return null;
// If the offset is out of bounds
if (offset < 0 || offset >= data.Length)
return null;
// Create a memory stream and use that
MemoryStream dataStream = new MemoryStream(data, offset, data.Length - offset);
return Create(dataStream);
}
/// <summary>
/// Create a BD+ SVM from a Stream
/// </summary>
/// <param name="data">Stream representing the archive</param>
/// <returns>A BD+ SVM wrapper on success, null on failure</returns>
public static BDPlusSVM Create(Stream data)
{
// If the data is invalid
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
return null;
var svm = Builders.BDPlus.ParseSVM(data);
if (svm == null)
return null;
var wrapper = new BDPlusSVM
{
_svm = svm,
_dataSource = DataSource.Stream,
_streamData = data,
};
return wrapper;
}
#endregion
#region Printing
/// <inheritdoc/>
public override void Print()
{
Console.WriteLine("BD+ Information:");
Console.WriteLine("-------------------------");
Console.WriteLine();
PrintSVM();
}
/// <summary>
/// Print SVM information
/// </summary>
private void PrintSVM()
{
Console.WriteLine(" SVM Information:");
Console.WriteLine(" -------------------------");
Console.WriteLine($" Signature: {Signature}");
Console.WriteLine($" Unknown 1: {BitConverter.ToString(Unknown1).Replace('-', ' ')}");
Console.WriteLine($" Year: {Year} (0x{Year:X})");
Console.WriteLine($" Month: {Month} (0x{Month:X})");
Console.WriteLine($" Day: {Day} (0x{Day:X})");
Console.WriteLine($" Unknown 2: {BitConverter.ToString(Unknown2).Replace('-', ' ')}");
Console.WriteLine($" Length: {Length} (0x{Length:X})");
//Console.WriteLine($" Data: {BitConverter.ToString(Data ?? new byte[0]).Replace('-', ' ')}");
Console.WriteLine();
}
#endregion
}
}

View File

@@ -15,6 +15,11 @@
/// </summary>
AACSMediaKeyBlock,
/// <summary>
/// BD+ SVM
/// </summary>
BDPlusSVM,
/// <summary>
/// BFPK custom archive
/// </summary>

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using BurnOutSharp.Interfaces;
namespace BurnOutSharp.FileType
{
/// <summary>
/// BD+ SVM
/// </summary>
public class BDPlusSVM : IScannable
{
/// <inheritdoc/>
public ConcurrentDictionary<string, ConcurrentQueue<string>> Scan(Scanner scanner, string file)
{
if (!File.Exists(file))
return null;
using (var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return Scan(scanner, fs, file);
}
}
/// <inheritdoc/>
public ConcurrentDictionary<string, ConcurrentQueue<string>> Scan(Scanner scanner, Stream stream, string file)
{
// If the MKB file itself fails
try
{
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempPath);
// Create the wrapper
Wrappers.BDPlusSVM svm = Wrappers.BDPlusSVM.Create(stream);
if (svm == null)
return null;
// Setup the output
var protections = new ConcurrentDictionary<string, ConcurrentQueue<string>>();
protections[file] = new ConcurrentQueue<string>();
// Format the date
string date = $"{svm.Year:0000}/{svm.Month:00}/{svm.Day:00}";
// Add and return the protection
protections[file].Enqueue($"BD+ {date}");
return protections;
}
catch (Exception ex)
{
if (scanner.IncludeDebug) Console.WriteLine(ex);
}
return null;
}
}
}

View File

@@ -1,70 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BurnOutSharp.Interfaces;
using BurnOutSharp.Matching;
namespace BurnOutSharp.ProtectionType
{
public class BDPlus : IPathCheck
{
/// <inheritdoc/>
public ConcurrentQueue<string> CheckDirectoryPath(string path, IEnumerable<string> files)
{
var matchers = new List<PathMatchSet>
{
new PathMatchSet(new List<string>
{
Path.Combine("BDSVM", "00000.svm").Replace("\\", "/"),
Path.Combine("BDSVM", "BACKUP", "00000.svm").Replace("\\", "/"),
}, GetVersion, "BD+"),
};
return MatchUtil.GetAllMatches(files, matchers, any: true);
}
/// <inheritdoc/>
public string CheckFilePath(string path)
{
var matchers = new List<PathMatchSet>
{
new PathMatchSet(new PathMatch("00000.svm", useEndsWith: true), GetVersion, "BD+"),
};
return MatchUtil.GetFirstMatch(path, matchers, any: true);
}
/// <remarks>
/// Version detection logic from libbdplus was used to implement this
/// </remarks>
public static string GetVersion(string firstMatchedString, IEnumerable<string> files)
{
if (!File.Exists(firstMatchedString))
return "(Unknown Version)";
try
{
using (var fs = File.OpenRead(firstMatchedString))
{
fs.Seek(0x0D, SeekOrigin.Begin);
byte[] date = new byte[4];
fs.Read(date, 0, 4); //yymd
short year = BitConverter.ToInt16(date.Reverse().ToArray(), 2);
// Do some rudimentary date checking
if (year < 2006 || year > 2100 || date[2] < 1 || date[2] > 12 || date[3] < 1 || date[3] > 31)
return "(Invalid Version)";
return $"{year:0000}/{date[2]:00}/{date[3]:00}";
}
}
catch
{
return "(Unknown Version)";
}
}
}
}

View File

@@ -359,6 +359,13 @@ namespace BurnOutSharp
AppendToDictionary(protections, subProtections);
}
// BD+ SVM
if (scannable is BDPlusSVM)
{
var subProtections = scannable.Scan(this, stream, fileName);
AppendToDictionary(protections, subProtections);
}
// Executable
if (scannable is Executable)
{

View File

@@ -34,6 +34,13 @@ namespace BurnOutSharp.Tools
#endregion
#region BDPlusSVM
if (magic.StartsWith(new byte?[] { 0x42, 0x44, 0x53, 0x56, 0x4D, 0x5F, 0x43, 0x43 }))
return SupportedFileType.BDPlusSVM;
#endregion
#region BFPK
if (magic.StartsWith(new byte?[] { 0x42, 0x46, 0x50, 0x4b }))
@@ -369,6 +376,13 @@ namespace BurnOutSharp.Tools
#endregion
#region BDPlusSVM
if (extension.Equals(value: "svm", StringComparison.OrdinalIgnoreCase))
return SupportedFileType.BDPlusSVM;
#endregion
#region BFPK
// No extensions registered for BFPK
@@ -757,6 +771,7 @@ namespace BurnOutSharp.Tools
switch (fileType)
{
case SupportedFileType.AACSMediaKeyBlock: return new FileType.AACSMediaKeyBlock();
case SupportedFileType.BDPlusSVM: return new FileType.BDPlusSVM();
case SupportedFileType.BFPK: return new FileType.BFPK();
case SupportedFileType.BSP: return new FileType.BSP();
case SupportedFileType.BZip2: return new FileType.BZip2();

View File

@@ -18,6 +18,7 @@ The following projects have influenced this library:
- [BurnOut](http://burnout.sourceforge.net/) - Project that this library was initially based on. The only thing left from that original port is in the name of the library. This project is fully unaffiliated with the original BurnOut and its authors.
- [HLLibSharp](https://github.com/mnadareski/HLLibSharp) - Documentation around Valve package handling, including extraction.
- [libbdplus](https://www.videolan.org/developers/libbdplus.html) - Documentation around the BD+ SVM files.
- [libmspack](https://github.com/kyz/libmspack) - Documentation around the MS-CAB format and associated compression methods.
- [NDecrypt](https://github.com/SabreTools/NDecrypt) - NDS (Nitro) and 3DS cart image file layouts and documentation, though not encrypt/decrypt.
@@ -31,12 +32,10 @@ Below is a list of protections detected by BurnOutSharp. The two columns explain
| --------------- | ------------- | ---------- | ----- |
| 3PLock | True | False | |
| 321Studios Online Activation | True | False | |
| AACS | False | True | BluRay and HD-DVD variants detected |
| ActiveMARK | True | False | Version 5 unconfirmed²; version finding incomplete |
| AegiSoft License Manager | True | True | |
| Alpha-DVD | False | True | Unconfirmed¹ |
| Alpha-ROM | True | False | |
| BD+ | False | True | |
| Bitpool | False | True | |
| ByteShield | True | True | |
| C-Dilla License Management Solution / CD-Secure / CD-Compress | True | True | |
@@ -154,6 +153,8 @@ Below is a list of container formats that are supported in some way:
| Format Name | Information Printing | Detection | Extraction | Notes |
| --- | --- | --- | --- | --- |
| 7-zip archive | No | Yes | Yes | Via `SharpCompress` |
| AACS Media Key Block | Yes | Yes | N/A | BluRay and HD-DVD variants detected |
| BD+ SVM | Yes | Yes | N/A | |
| BFPK custom archive format | Yes | Yes | Yes | |
| bzip2 archive | No | Yes | Yes | Via `SharpCompress` |
| Compound File Binary (CFB) | Yes* | Yes | Yes | Via `OpenMcdf`, only CFB common pieces printable |

View File

@@ -164,6 +164,25 @@ namespace Test
mkb.Print();
}
// BD+ SVM
else if (ft == SupportedFileType.BDPlusSVM)
{
// Build the BD+ SVM information
Console.WriteLine("Creating BD+ SVM deserializer");
Console.WriteLine();
var svm = BDPlusSVM.Create(stream);
if (svm == null)
{
Console.WriteLine("Something went wrong parsing BD+ SVM");
Console.WriteLine();
return;
}
// Print the BD+ SVM info to screen
svm.Print();
}
// BFPK archive
else if (ft == SupportedFileType.BFPK)
{