diff --git a/BurnOutSharp.Builders/BDPlus.cs b/BurnOutSharp.Builders/BDPlus.cs
new file mode 100644
index 00000000..b137ac60
--- /dev/null
+++ b/BurnOutSharp.Builders/BDPlus.cs
@@ -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
+
+ ///
+ /// Parse a byte array into a BD+ SVM
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Filled BD+ SVM on success, null on error
+ 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
+
+ ///
+ /// Parse a Stream into an BD+ SVM
+ ///
+ /// Stream to parse
+ /// Filled BD+ SVM on success, null on error
+ 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);
+ }
+
+ ///
+ /// Parse a Stream into an SVM
+ ///
+ /// Stream to parse
+ /// Filled SVM on success, null on error
+ 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
+ }
+}
diff --git a/BurnOutSharp.Models/BDPlus/Constants.cs b/BurnOutSharp.Models/BDPlus/Constants.cs
new file mode 100644
index 00000000..112b64b9
--- /dev/null
+++ b/BurnOutSharp.Models/BDPlus/Constants.cs
@@ -0,0 +1,7 @@
+namespace BurnOutSharp.Models.BDPlus
+{
+ public static class Constants
+ {
+ public const string SignatureString = "BDSVM_CC";
+ }
+}
\ No newline at end of file
diff --git a/BurnOutSharp.Models/BDPlus/SVM.cs b/BurnOutSharp.Models/BDPlus/SVM.cs
new file mode 100644
index 00000000..96392b39
--- /dev/null
+++ b/BurnOutSharp.Models/BDPlus/SVM.cs
@@ -0,0 +1,46 @@
+namespace BurnOutSharp.Models.BDPlus
+{
+ ///
+ public sealed class SVM
+ {
+ ///
+ /// "BDSVM_CC"
+ ///
+ public string Signature;
+
+ ///
+ /// 5 bytes of unknown data
+ ///
+ public byte[] Unknown1;
+
+ ///
+ /// Version year
+ ///
+ public ushort Year;
+
+ ///
+ /// Version month
+ ///
+ public byte Month;
+
+ ///
+ /// Version day
+ ///
+ public byte Day;
+
+ ///
+ /// 4 bytes of unknown data
+ ///
+ public byte[] Unknown2;
+
+ ///
+ /// Length
+ ///
+ public uint Length;
+
+ ///
+ /// Length bytes of data
+ ///
+ public byte[] Data;
+ }
+}
\ No newline at end of file
diff --git a/BurnOutSharp.Wrappers/BDPlusSVM.cs b/BurnOutSharp.Wrappers/BDPlusSVM.cs
new file mode 100644
index 00000000..e16a1a1b
--- /dev/null
+++ b/BurnOutSharp.Wrappers/BDPlusSVM.cs
@@ -0,0 +1,131 @@
+using System;
+using System.IO;
+
+namespace BurnOutSharp.Wrappers
+{
+ public class BDPlusSVM : WrapperBase
+ {
+ #region Pass-Through Properties
+
+ ///
+ public string Signature => _svm.Signature;
+
+ ///
+ public byte[] Unknown1 => _svm.Unknown1;
+
+ ///
+ public ushort Year => _svm.Year;
+
+ ///
+ public byte Month => _svm.Month;
+
+ ///
+ public byte Day => _svm.Day;
+
+ ///
+ public byte[] Unknown2 => _svm.Unknown2;
+
+ ///
+ public uint Length => _svm.Length;
+
+ ///
+ public byte[] Data => _svm.Data;
+
+ #endregion
+
+ #region Instance Variables
+
+ ///
+ /// Internal representation of the SVM
+ ///
+ private Models.BDPlus.SVM _svm;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Private constructor
+ ///
+ private BDPlusSVM() { }
+
+ ///
+ /// Create a BD+ SVM from a byte array and offset
+ ///
+ /// Byte array representing the archive
+ /// Offset within the array to parse
+ /// A BD+ SVM wrapper on success, null on failure
+ 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);
+ }
+
+ ///
+ /// Create a BD+ SVM from a Stream
+ ///
+ /// Stream representing the archive
+ /// A BD+ SVM wrapper on success, null on failure
+ 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
+
+ ///
+ public override void Print()
+ {
+ Console.WriteLine("BD+ Information:");
+ Console.WriteLine("-------------------------");
+ Console.WriteLine();
+
+ PrintSVM();
+ }
+
+ ///
+ /// Print SVM information
+ ///
+ 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
+ }
+}
\ No newline at end of file
diff --git a/BurnOutSharp/Enums.cs b/BurnOutSharp/Enums.cs
index fca0e028..118dd818 100644
--- a/BurnOutSharp/Enums.cs
+++ b/BurnOutSharp/Enums.cs
@@ -15,6 +15,11 @@
///
AACSMediaKeyBlock,
+ ///
+ /// BD+ SVM
+ ///
+ BDPlusSVM,
+
///
/// BFPK custom archive
///
diff --git a/BurnOutSharp/FileType/BDPlusSVM.cs b/BurnOutSharp/FileType/BDPlusSVM.cs
new file mode 100644
index 00000000..b16bac4a
--- /dev/null
+++ b/BurnOutSharp/FileType/BDPlusSVM.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using BurnOutSharp.Interfaces;
+
+namespace BurnOutSharp.FileType
+{
+ ///
+ /// BD+ SVM
+ ///
+ public class BDPlusSVM : IScannable
+ {
+ ///
+ public ConcurrentDictionary> 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);
+ }
+ }
+
+ ///
+ public ConcurrentDictionary> 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>();
+ protections[file] = new ConcurrentQueue();
+
+ // 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;
+ }
+ }
+}
diff --git a/BurnOutSharp/ProtectionType/BDPlus.cs b/BurnOutSharp/ProtectionType/BDPlus.cs
deleted file mode 100644
index 69345993..00000000
--- a/BurnOutSharp/ProtectionType/BDPlus.cs
+++ /dev/null
@@ -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
- {
- ///
- public ConcurrentQueue CheckDirectoryPath(string path, IEnumerable files)
- {
- var matchers = new List
- {
- new PathMatchSet(new List
- {
- Path.Combine("BDSVM", "00000.svm").Replace("\\", "/"),
- Path.Combine("BDSVM", "BACKUP", "00000.svm").Replace("\\", "/"),
- }, GetVersion, "BD+"),
- };
-
- return MatchUtil.GetAllMatches(files, matchers, any: true);
- }
-
- ///
- public string CheckFilePath(string path)
- {
- var matchers = new List
- {
- new PathMatchSet(new PathMatch("00000.svm", useEndsWith: true), GetVersion, "BD+"),
- };
-
- return MatchUtil.GetFirstMatch(path, matchers, any: true);
- }
-
- ///
- /// Version detection logic from libbdplus was used to implement this
- ///
- public static string GetVersion(string firstMatchedString, IEnumerable 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)";
- }
- }
- }
-}
-
diff --git a/BurnOutSharp/Scanner.cs b/BurnOutSharp/Scanner.cs
index c4d20b9e..1f9f7a9d 100644
--- a/BurnOutSharp/Scanner.cs
+++ b/BurnOutSharp/Scanner.cs
@@ -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)
{
diff --git a/BurnOutSharp/Tools/Utilities.cs b/BurnOutSharp/Tools/Utilities.cs
index 2f1fd174..4ec16983 100644
--- a/BurnOutSharp/Tools/Utilities.cs
+++ b/BurnOutSharp/Tools/Utilities.cs
@@ -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();
diff --git a/README.md b/README.md
index 56c04e99..57af7fb7 100644
--- a/README.md
+++ b/README.md
@@ -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 |
diff --git a/Test/Printer.cs b/Test/Printer.cs
index ace3f0a5..2f713253 100644
--- a/Test/Printer.cs
+++ b/Test/Printer.cs
@@ -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)
{