|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using MediaBrowser.Model.IO;
|
|
|
|
|
|
|
|
namespace DvdLib.Ifo
|
|
|
|
{
|
|
|
|
public class Dvd
|
|
|
|
{
|
|
|
|
private readonly ushort _titleSetCount;
|
|
|
|
public readonly List<Title> Titles;
|
|
|
|
|
|
|
|
private ushort _titleCount;
|
|
|
|
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
|
|
|
private readonly IFileSystem _fileSystem;
|
|
|
|
|
|
|
|
public Dvd(string path, IFileSystem fileSystem)
|
|
|
|
{
|
|
|
|
_fileSystem = fileSystem;
|
|
|
|
Titles = new List<Title>();
|
|
|
|
var allFiles = _fileSystem.GetFiles(path, true).ToList();
|
|
|
|
|
|
|
|
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
|
|
|
|
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
|
|
if (vmgPath == null)
|
|
|
|
{
|
|
|
|
foreach (var ifo in allFiles)
|
|
|
|
{
|
|
|
|
if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
|
|
|
{
|
|
|
|
ReadVTS(ifoNumber, ifo.FullName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
|
|
|
{
|
|
|
|
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
|
|
|
|
{
|
|
|
|
vmgFs.Seek(0x3E, SeekOrigin.Begin);
|
|
|
|
_titleSetCount = vmgRead.ReadUInt16();
|
|
|
|
|
|
|
|
// read address of TT_SRPT
|
|
|
|
vmgFs.Seek(0xC4, SeekOrigin.Begin);
|
|
|
|
uint ttSectorPtr = vmgRead.ReadUInt32();
|
|
|
|
vmgFs.Seek(ttSectorPtr * 2048, SeekOrigin.Begin);
|
|
|
|
ReadTT_SRPT(vmgRead);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ushort titleSetNum = 1; titleSetNum <= _titleSetCount; titleSetNum++)
|
|
|
|
{
|
|
|
|
ReadVTS(titleSetNum, allFiles);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ReadTT_SRPT(BinaryReader read)
|
|
|
|
{
|
|
|
|
_titleCount = read.ReadUInt16();
|
|
|
|
read.BaseStream.Seek(6, SeekOrigin.Current);
|
|
|
|
for (uint titleNum = 1; titleNum <= _titleCount; titleNum++)
|
|
|
|
{
|
|
|
|
var t = new Title(titleNum);
|
|
|
|
t.ParseTT_SRPT(read);
|
|
|
|
Titles.Add(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
|
|
|
{
|
|
|
|
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
|
|
|
|
|
|
|
var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ??
|
|
|
|
allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
|
|
if (vtsPath == null)
|
|
|
|
{
|
|
|
|
throw new FileNotFoundException("Unable to find VTS IFO file");
|
|
|
|
}
|
|
|
|
|
|
|
|
ReadVTS(vtsNum, vtsPath.FullName);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ReadVTS(ushort vtsNum, string vtsPath)
|
|
|
|
{
|
|
|
|
VTSPaths[vtsNum] = vtsPath;
|
|
|
|
|
|
|
|
using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
|
|
|
{
|
|
|
|
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
|
|
|
|
{
|
|
|
|
// Read VTS_PTT_SRPT
|
|
|
|
vtsFs.Seek(0xC8, SeekOrigin.Begin);
|
|
|
|
uint vtsPttSrptSecPtr = vtsRead.ReadUInt32();
|
|
|
|
uint baseAddr = (vtsPttSrptSecPtr * 2048);
|
|
|
|
vtsFs.Seek(baseAddr, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
ushort numTitles = vtsRead.ReadUInt16();
|
|
|
|
vtsRead.ReadUInt16();
|
|
|
|
uint endaddr = vtsRead.ReadUInt32();
|
|
|
|
uint[] offsets = new uint[numTitles];
|
|
|
|
for (ushort titleNum = 0; titleNum < numTitles; titleNum++)
|
|
|
|
{
|
|
|
|
offsets[titleNum] = vtsRead.ReadUInt32();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint titleNum = 0; titleNum < numTitles; titleNum++)
|
|
|
|
{
|
|
|
|
uint chapNum = 1;
|
|
|
|
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
|
|
|
|
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
|
|
|
|
if (t == null) continue;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
|
|
|
|
if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break;
|
|
|
|
chapNum++;
|
|
|
|
}
|
|
|
|
while (vtsFs.Position < (baseAddr + endaddr));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read VTS_PGCI
|
|
|
|
vtsFs.Seek(0xCC, SeekOrigin.Begin);
|
|
|
|
uint vtsPgciSecPtr = vtsRead.ReadUInt32();
|
|
|
|
vtsFs.Seek(vtsPgciSecPtr * 2048, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
long startByte = vtsFs.Position;
|
|
|
|
|
|
|
|
ushort numPgcs = vtsRead.ReadUInt16();
|
|
|
|
vtsFs.Seek(6, SeekOrigin.Current);
|
|
|
|
for (ushort pgcNum = 1; pgcNum <= numPgcs; pgcNum++)
|
|
|
|
{
|
|
|
|
byte pgcCat = vtsRead.ReadByte();
|
|
|
|
bool entryPgc = (pgcCat & 0x80) != 0;
|
|
|
|
uint titleNum = (uint)(pgcCat & 0x7F);
|
|
|
|
|
|
|
|
vtsFs.Seek(3, SeekOrigin.Current);
|
|
|
|
uint vtsPgcOffset = vtsRead.ReadUInt32();
|
|
|
|
|
|
|
|
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
|
|
|
|
if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|