diff --git a/CHANGELIST.md b/CHANGELIST.md
index b905e255..562768eb 100644
--- a/CHANGELIST.md
+++ b/CHANGELIST.md
@@ -36,6 +36,7 @@
- Support single digit subs
- Fix info tool hash finding
- Fix missing size for ISO data
+- Attempt to more accurately parse layerbreaks
### 2.5 (2023-03-12)
diff --git a/MPF.Core/Data/PICDiscInformationUnit.cs b/MPF.Core/Data/PICDiscInformationUnit.cs
index f2479885..392c28a0 100644
--- a/MPF.Core/Data/PICDiscInformationUnit.cs
+++ b/MPF.Core/Data/PICDiscInformationUnit.cs
@@ -27,7 +27,7 @@
///
/// Should be 0x00
///
- public byte Reserved2 { get; set; }
+ public byte Reserved0 { get; set; }
///
/// DI unit Sequence Number
@@ -42,7 +42,7 @@
///
/// Should be 0x00
///
- public byte Reserved3 { get; set; }
+ public byte Reserved1 { get; set; }
#endregion
diff --git a/MPF.Modules/BaseParameters.cs b/MPF.Modules/BaseParameters.cs
index 421102ca..7b7b9602 100644
--- a/MPF.Modules/BaseParameters.cs
+++ b/MPF.Modules/BaseParameters.cs
@@ -1160,6 +1160,126 @@ namespace MPF.Modules
}
}
+ ///
+ /// Gets disc information from a PIC file
+ ///
+ /// Path to a PIC.bin file
+ /// Filled PICDiscInformation on success, null on error
+ /// This omits the emergency brake information, if it exists
+ protected static PICDiscInformation GetDiscInformation(string pic)
+ {
+ try
+ {
+ using (BinaryReader br = new BinaryReader(File.OpenRead(pic)))
+ {
+ var di = new PICDiscInformation();
+
+ // Read the initial disc information
+ di.DataStructureLength = br.ReadUInt16BigEndian();
+ di.Reserved0 = br.ReadByte();
+ di.Reserved1 = br.ReadByte();
+
+ // Create a list for the units
+ var diUnits = new List();
+
+ // Loop and read all available units
+ for (int i = 0; i < 32; i++)
+ {
+ var unit = new PICDiscInformationUnit();
+
+ // We only accept Disc Information units, not Emergency Brake or other
+ unit.DiscInformationIdentifier = Encoding.ASCII.GetString(br.ReadBytes(2));
+ if (unit.DiscInformationIdentifier != "DI")
+ break;
+
+ unit.DiscInformationFormat = br.ReadByte();
+ unit.NumberOfUnitsInBlock = br.ReadByte();
+ unit.Reserved0 = br.ReadByte();
+ unit.SequenceNumber = br.ReadByte();
+ unit.BytesInUse = br.ReadByte();
+ unit.Reserved1 = br.ReadByte();
+
+ unit.DiscTypeIdentifier = Encoding.ASCII.GetString(br.ReadBytes(3));
+ unit.DiscSizeClassVersion = br.ReadByte();
+ switch (unit.DiscTypeIdentifier)
+ {
+ case PICDiscInformationUnit.DiscTypeIdentifierROM:
+ unit.FormatDependentContents = br.ReadBytes(52);
+ break;
+ case PICDiscInformationUnit.DiscTypeIdentifierReWritable:
+ case PICDiscInformationUnit.DiscTypeIdentifierRecordable:
+ unit.FormatDependentContents = br.ReadBytes(100);
+ unit.DiscManufacturerID = br.ReadBytes(6);
+ unit.MediaTypeID = br.ReadBytes(3);
+ unit.TimeStamp = br.ReadUInt16();
+ unit.ProductRevisionNumber = br.ReadByte();
+ break;
+ }
+
+ diUnits.Add(unit);
+ }
+
+ // Assign the units and return
+ di.Units = diUnits.ToArray();
+ return di;
+ }
+ }
+ catch
+ {
+ // We don't care what the error was
+ return null;
+ }
+ }
+
+ ///
+ /// Get the layerbreak info associated from the disc information
+ ///
+ /// Disc information containing unformatted data
+ /// True if layerbreak info was set, false otherwise
+ protected static bool GetLayerbreaks(PICDiscInformation di, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3)
+ {
+ // Set the default values
+ layerbreak1 = null; layerbreak2 = null; layerbreak3 = null;
+
+ // If we don't have valid disc information, we can't do anything
+ if (di?.Units == null || di.Units.Length <= 1)
+ return false;
+
+ int ReadFromArrayBigEndian(byte[] bytes, int offset)
+ {
+ var span = new ReadOnlySpan(bytes, offset, 0x04);
+ byte[] rev = span.ToArray();
+ Array.Reverse(rev);
+ return BitConverter.ToInt32(rev, 0);
+ }
+
+ // Layerbreak 1 (2+ layers)
+ if (di.Units.Length >= 2)
+ {
+ long offset = ReadFromArrayBigEndian(di.Units[0].FormatDependentContents, 0x0C);
+ long value = ReadFromArrayBigEndian(di.Units[0].FormatDependentContents, 0x10);
+ layerbreak1 = value - offset + 2;
+ }
+
+ // Layerbreak 2 (3+ layers)
+ if (di.Units.Length >= 3)
+ {
+ long offset = ReadFromArrayBigEndian(di.Units[1].FormatDependentContents, 0x0C);
+ long value = ReadFromArrayBigEndian(di.Units[1].FormatDependentContents, 0x10);
+ layerbreak2 = layerbreak1 + value - offset + 2;
+ }
+
+ // Layerbreak 3 (4 layers)
+ if (di.Units.Length >= 4)
+ {
+ long offset = ReadFromArrayBigEndian(di.Units[2].FormatDependentContents, 0x0C);
+ long value = ReadFromArrayBigEndian(di.Units[2].FormatDependentContents, 0x10);
+ layerbreak3 = layerbreak2 + value - offset + 2;
+ }
+
+ return true;
+ }
+
///
/// Get hashes from an input file path
///
diff --git a/MPF.Modules/DiscImageCreator/Parameters.cs b/MPF.Modules/DiscImageCreator/Parameters.cs
index 541b317c..9857f60c 100644
--- a/MPF.Modules/DiscImageCreator/Parameters.cs
+++ b/MPF.Modules/DiscImageCreator/Parameters.cs
@@ -461,7 +461,8 @@ namespace MPF.Modules.DiscImageCreator
}
else if (this.Type == MediaType.BluRay)
{
- if (GetLayerbreak($"{basePath}_PIC.bin", out long? layerbreak1, out long? layerbreak2, out long? layerbreak3))
+ var di = GetDiscInformation($"{basePath}_PIC.bin");
+ if (GetLayerbreaks(di, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3))
{
if (layerbreak1 != null && layerbreak1 * 2048 < info.SizeAndChecksums.Size)
info.SizeAndChecksums.Layerbreak = layerbreak1.Value;
@@ -2978,79 +2979,6 @@ namespace MPF.Modules.DiscImageCreator
}
}
- ///
- /// Get the layerbreak info associated with the dump
- ///
- /// Path to the PIC.bin file associated with the dump
- /// True if layerbreak info was set, false otherwise
- /// https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters
- private static bool GetLayerbreak(string picPath, out long? layerbreak1, out long? layerbreak2, out long? layerbreak3)
- {
- // Set the default values
- layerbreak1 = null; layerbreak2 = null; layerbreak3 = null;
-
- // If the file doesn't exist, we can't get the info
- if (!File.Exists(picPath))
- return false;
-
- try
- {
- using (BinaryReader br = new BinaryReader(File.OpenRead(picPath)))
- {
- // Disc type
- int infoLength;
- br.BaseStream.Seek(0x0C, SeekOrigin.Begin);
- string identifier = Encoding.ASCII.GetString(br.ReadBytes(3));
- switch (identifier)
- {
- case "BDO":
- infoLength = 0x40;
- break;
- case "BDW":
- case "BDR":
- infoLength = 0x74;
- break;
- default:
- return false;
- }
-
- // Layerbreak 1
- br.BaseStream.Seek((0 * infoLength) + 0x1C, SeekOrigin.Begin);
- long layerbreak1Offset = br.ReadInt32BigEndian();
- long layerbreak1Value = br.ReadInt32BigEndian();
- if (layerbreak1Value == 0)
- return false;
-
- layerbreak1 = layerbreak1Value - layerbreak1Offset + 2;
-
- // Layerbreak 2
- br.BaseStream.Seek((1 * infoLength) + 0x1C, SeekOrigin.Begin);
- long layerbreak2Offset = br.ReadInt32BigEndian();
- long layerbreak2Value = br.ReadInt32BigEndian();
- if (layerbreak2Value == 0)
- return true;
-
- layerbreak2 = layerbreak1 + layerbreak2Value - layerbreak2Offset + 2;
-
- // Layerbreak 3
- br.BaseStream.Seek((2 * infoLength) + 0x1C, SeekOrigin.Begin);
- long layerbreak3Offset = br.ReadInt32BigEndian();
- long layerbreak3Value = br.ReadInt32BigEndian();
- if (layerbreak3Value == 0)
- return true;
-
- layerbreak3 = layerbreak2 + layerbreak3Value - layerbreak3Offset + 2;
-
- return true;
- }
- }
- catch
- {
- // We don't care what the error was
- return false;
- }
- }
-
///
/// Get multisession information from the input file, if possible
///