#pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; namespace DvdLib.Ifo { public class Dvd { private readonly ushort _titleSetCount; public readonly List Titles; private ushort _titleCount; public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>(); public Dvd(string path) { Titles = new List<Title>(); var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories); 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('_', 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, IReadOnlyList<FileInfo> allFiles) { var filename = string.Format(CultureInfo.InvariantCulture, "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); } } } } } } }