From df5d8770be1fc017e52b89c62b4ff04d7cfbb2de Mon Sep 17 00:00:00 2001 From: Horst Beham Date: Mon, 10 Aug 2020 11:27:05 +0200 Subject: [PATCH] finished DVB-C and DVB-S2 implementation of Philips BIN format with Repair / channellib/*Table and s2channellib/*.dat format --- .../ChanSort.Loader.PhilipsBin.csproj | 1 + .../ChanSort.Loader.PhilipsBin.ini | 25 ++ source/ChanSort.Loader.PhilipsBin/Channel.cs | 16 + .../ChanSort.Loader.PhilipsBin/Serializer.cs | 275 +++++++++++++----- .../philips_dat.h | 118 +++++++- .../PhilipsChannellibTest.cs | 37 +++ .../Test.Loader.PhilipsBin.csproj | 109 +++---- .../TestFiles1/Repair/ChannelList/chanLst.bin | Bin 663 -> 663 bytes .../ChannelList/channellib/CableDigSrvTable | Bin 74484 -> 74484 bytes 9 files changed, 444 insertions(+), 137 deletions(-) create mode 100644 source/ChanSort.Loader.PhilipsBin/Channel.cs create mode 100644 source/Test.Loader.PhilipsBin/PhilipsChannellibTest.cs diff --git a/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.csproj b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.csproj index 2c67c2d..0269083 100644 --- a/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.csproj +++ b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.csproj @@ -64,6 +64,7 @@ + diff --git a/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.ini b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.ini index 4a7a94d..e3d907b 100644 --- a/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.ini +++ b/source/ChanSort.Loader.PhilipsBin/ChanSort.Loader.PhilipsBin.ini @@ -17,3 +17,28 @@ lenName=32 offProvider=60 lenProvider=32 +[CableDigSrvTable_entry] +offChecksum=0 +offSymbolRate=24 +offFreqTimes16=48 +offOnid=50 +offSid=52 +offTsid=54 +offProgNr=122 +offLocked=146 +offIsFav=147 +offName=216 +lenName=64 + +[CablePresetTable_entry] +offChecksum=0 +offFreqTimes16=4 +offProgNr=8 +offOnid=14 +offTsid=16 +offSid=18 + +[CableFrqMapTable_entry] +offChecksum=0 +offSymbolRate=8 +offFreq=18 \ No newline at end of file diff --git a/source/ChanSort.Loader.PhilipsBin/Channel.cs b/source/ChanSort.Loader.PhilipsBin/Channel.cs new file mode 100644 index 0000000..d424642 --- /dev/null +++ b/source/ChanSort.Loader.PhilipsBin/Channel.cs @@ -0,0 +1,16 @@ +using ChanSort.Api; + +namespace ChanSort.Loader.PhilipsBin +{ + class Channel : ChannelInfo + { + public Channel(SignalSource source, long index, int oldProgNr, string name) : base(source, index, oldProgNr, name) + { + } + + /// + /// index of the record in the AntennaPresetTable / CablePresetTable file for the channel, matched by (onid + tsid + sid) + /// + public int PresetTableIndex { get; set; } = -1; + } +} diff --git a/source/ChanSort.Loader.PhilipsBin/Serializer.cs b/source/ChanSort.Loader.PhilipsBin/Serializer.cs index 8a21833..4d169ba 100644 --- a/source/ChanSort.Loader.PhilipsBin/Serializer.cs +++ b/source/ChanSort.Loader.PhilipsBin/Serializer.cs @@ -3,16 +3,19 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.Text; -using System.Threading; -using System.Xml; -using System.Xml.Schema; using ChanSort.Api; namespace ChanSort.Loader.PhilipsBin { /* + channellib\CableDigSrvTable: + =========================== + Channels in this file are not phyiscally ordered by the program number and there is no linked list with prev/next indexes. + When editing a channel with the Philips Channel Editor, it only updates the progNr field (and overwrites all trailing bytes of the channel name with 0x00). + There is also the CablePresetTable file which is probably used for LCN. The Philips tool also updates the progNr in that file and uses it as is primary source + for the progNr. I don't know if there is a direct reference from the channel to the preset, hence this code uses the combination of ONID+TSID+SID to link the two. + s2channellib\service.dat: ======================== All observed files have a perfectly linear next/prev table. The Philips Channel Editor also keeps that list linear and physically reorders the channel records. @@ -27,17 +30,20 @@ namespace ChanSort.Loader.PhilipsBin When swapping satellite channels 1 and 2 with the Philips Channel Editor 6.62, it only updates a few fields and leaves the rest stale. updated: SID, transponderIndex, channelName, providerName - This code here copyies the whole record before updating the fields + This code here copies the whole record before updating the fields. + + The favorite.dat file stores favorites as linked list which may support independent ordering from the main channel list. + The Philips editor even saves non-linear lists, but not in any particular order. + */ class Serializer : SerializerBase { private readonly IniFile ini; private readonly List dataFilePaths = new List(); - private readonly ChannelList dvbtChannels = new ChannelList(SignalSource.DvbCT, "DVB-T"); - private readonly ChannelList dvbcChannels = new ChannelList(SignalSource.DvbCT, "DVB-C"); + private readonly ChannelList dvbtChannels = new ChannelList(SignalSource.DvbT, "DVB-T"); + private readonly ChannelList dvbcChannels = new ChannelList(SignalSource.DvbC, "DVB-C"); private readonly ChannelList satChannels = new ChannelList(SignalSource.DvbS, "DVB-S"); - #region ctor() public Serializer(string inputFile) : base(inputFile) { @@ -49,7 +55,7 @@ namespace ChanSort.Loader.PhilipsBin this.Features.CanSaveAs = false; this.Features.CanHaveGaps = false; this.Features.SupportedFavorites = Favorites.A; - this.Features.SortedFavorites = true; + this.Features.SortedFavorites = false; // satellite favorites are stored in a separate file that may support independent sorting, but DVB C/T only have a flag this.Features.AllowGapsInFavNumbers = false; this.Features.CanEditFavListNames = false; @@ -61,17 +67,20 @@ namespace ChanSort.Loader.PhilipsBin { list.VisibleColumnFieldNames.Remove("Skip"); list.VisibleColumnFieldNames.Remove("ShortName"); + list.VisibleColumnFieldNames.Remove("ServiceTypeName"); + list.VisibleColumnFieldNames.Remove("Hidden"); + list.VisibleColumnFieldNames.Remove("AudioPid"); + list.VisibleColumnFieldNames.Remove("Encrypted"); } - var supportedColumns = new[] {"OldPosition", "Position", "Name", "Lock"}; - //this.satChannels.VisibleColumnFieldNames.Clear(); - //foreach(var supportedColumn in supportedColumns) - // this.satChannels.VisibleColumnFieldNames.Add(supportedColumn); - - this.satChannels.VisibleColumnFieldNames.Remove("AudioPid"); - this.satChannels.VisibleColumnFieldNames.Remove("ServiceTypeName"); - this.satChannels.VisibleColumnFieldNames.Remove("Encrypted"); - this.satChannels.VisibleColumnFieldNames.Remove("Hidden"); + foreach (var list in new[] {dvbcChannels, dvbtChannels}) + { + list.VisibleColumnFieldNames.Remove("PcrPid"); + list.VisibleColumnFieldNames.Remove("VideoPid"); + list.VisibleColumnFieldNames.Remove("AudioPid"); + list.VisibleColumnFieldNames.Remove("ChannelOrTransponder"); + list.VisibleColumnFieldNames.Remove("Provider"); + } string iniFile = Assembly.GetExecutingAssembly().Location.Replace(".dll", ".ini"); this.ini = new IniFile(iniFile); @@ -87,32 +96,43 @@ namespace ChanSort.Loader.PhilipsBin + "ChannelList\\channellib\\CableDigSrvTable\n" + "ChannelList\\s2channellib\\service.dat"); - var dir = Path.GetDirectoryName(this.FileName); - LoadDvbCT(dvbtChannels, Path.Combine(dir, "channellib", "AntennaDigSrvTable")); - LoadDvbCT(dvbcChannels, Path.Combine(dir, "channellib", "CableDigSrvTable")); + var dir = Path.GetDirectoryName(this.FileName) ?? ""; + var channellib = Path.Combine(dir, "channellib"); + var s2channellib = Path.Combine(dir, "s2channellib"); - LoadDvbsSatellites(Path.Combine(dir, "s2channellib", "satellite.dat")); - LoadDvbsTransponders(Path.Combine(dir, "s2channellib", "tuneinfo.dat")); - LoadDvbS(satChannels, Path.Combine(dir, "s2channellib", "service.dat")); - LoadDvbsFavorites(Path.Combine(dir, "s2channellib", "favorite.dat")); - var db_file_info = Path.Combine(dir, "s2channellib", "db_file_info.dat"); + // channellib files for DVB-C/T + LoadDvbCT(dvbtChannels, Path.Combine(channellib, "AntennaDigSrvTable")); + LoadDvbCTPresets(dvbtChannels, Path.Combine(channellib, "AntennaPresetTable")); + LoadDvbCT(dvbcChannels, Path.Combine(channellib, "CableDigSrvTable")); + LoadDvbCTPresets(dvbcChannels, Path.Combine(channellib, "CablePresetTable")); + + // s2channellib files for DVB-S + LoadDvbsSatellites(Path.Combine(s2channellib, "satellite.dat")); + LoadDvbsTransponders(Path.Combine(s2channellib, "tuneinfo.dat")); + LoadDvbS(satChannels, Path.Combine(s2channellib, "service.dat")); + LoadDvbsFavorites(Path.Combine(s2channellib, "favorite.dat")); + var db_file_info = Path.Combine(s2channellib, "db_file_info.dat"); if (File.Exists(db_file_info)) this.dataFilePaths.Add(db_file_info); // for a proper ChanSort backup/restore with .bak files, the Philips _backup.dat files must also be included - foreach(var file in this.dataFilePaths.ToList()) - this.dataFilePaths.Add(file.Replace(".dat", "_backup.dat")); + foreach (var file in this.dataFilePaths.ToList()) + { + if (file.Contains(".dat")) + this.dataFilePaths.Add(file.Replace(".dat", "_backup.dat")); + } } + #endregion #region SetFileNameToChanLstBin() private bool SetFileNameToChanLstBin() { - var dir = Path.GetDirectoryName(this.FileName); + var dir = Path.GetDirectoryName(this.FileName) ?? ""; var dirName = Path.GetFileName(dir); if (StringComparer.InvariantCultureIgnoreCase.Compare(dirName, "channellib") == 0 || StringComparer.InvariantCultureIgnoreCase.Compare(dirName, "s2channellib") == 0) { - dir = Path.GetDirectoryName(dir); + dir = Path.GetDirectoryName(dir) ?? ""; dirName = Path.GetFileName(dir); } @@ -134,52 +154,110 @@ namespace ChanSort.Loader.PhilipsBin #endregion #region LoadDvbCT - private byte[] LoadDvbCT(ChannelList list, string path) + private void LoadDvbCT(ChannelList list, string path) { + if (!ReadAndValidateChannellibFile(path, out var data, out var recordSize, out var recordCount)) + return; + + var mapping = new DataMapping(this.ini.GetSection("CableDigSrvTable_entry")); + mapping.SetDataPtr(data, 20); + + for (int i = 0; i < recordCount; i++, mapping.BaseOffset += recordSize) + { + var progNr = mapping.GetWord("offProgNr"); + + var offChannelName = mapping.BaseOffset + mapping.GetConst("offName", 0); + var lenName = mapping.GetConst("lenName", 0); + for (int j = 0; j < lenName; j += 2) + { + if (data[offChannelName + j] == 0) + { + lenName = j; + break; + } + } + string channelName = Encoding.Unicode.GetString(data, offChannelName, lenName); + + var checksum = mapping.GetDword("offChecksum"); + mapping.SetDword("offChecksum", 0); + var crc = FaultyCrc32(data, mapping.BaseOffset + mapping.GetConst("offChecksum", 0), recordSize); + if (crc != checksum) + throw new FileLoadException($"Invalid CRC in record {i} in {path}"); + + var ch = new Channel(list.SignalSource, i, progNr, channelName); + ch.FreqInMhz = (decimal) mapping.GetWord("offFreqTimes16") / 16; + ch.OriginalNetworkId = mapping.GetWord("offOnid"); + ch.TransportStreamId = mapping.GetWord("offTsid"); + ch.ServiceId = mapping.GetWord("offSid"); + ch.SymbolRate = (int)mapping.GetDword("offSymbolRate") / 1000; + ch.Lock = mapping.GetByte("offLocked") != 0; + ch.Favorites = mapping.GetByte("offIsFav") != 0 ? Favorites.A : 0; + if (ch.Favorites != 0) + ch.OldFavIndex[0] = ch.OldProgramNr; + this.DataRoot.AddChannel(list, ch); + } + } + #endregion + + #region LoadDvbCTPresets + private void LoadDvbCTPresets(ChannelList list, string path) + { + if (!ReadAndValidateChannellibFile(path, out var data, out var recordSize, out var recordCount)) + return; + + // build a mapping of (onid,tsid,sid) => channel + var channelById = new Dictionary(); + foreach(var chan in list.Channels) + { + var ch = (Channel)chan; + var id = ((ulong)ch.OriginalNetworkId << 32) | ((ulong)ch.TransportStreamId << 16) | (uint)ch.ServiceId; + channelById[id] = ch; + } + + // apply preset progNr (LCN?) to the channel and remember the preset index for it + var mapping = new DataMapping(this.ini.GetSection("CablePresetTable_entry")); + mapping.SetDataPtr(data, 20); + for (int i = 0; i < recordCount; i++, mapping.BaseOffset += recordSize) + { + var onid = mapping.GetWord("offOnid"); + var tsid = mapping.GetWord("offTsid"); + var sid = mapping.GetWord("offSid"); + var id = ((ulong)onid << 32) | ((ulong)tsid << 16) | sid; + if (!channelById.TryGetValue(id, out var ch)) + continue; + + ch.PresetTableIndex = i; + var progNr = mapping.GetWord("offProgNr"); + if (progNr != 0 && progNr != 0xFFFF) + ch.OldProgramNr = progNr; + } + } + #endregion + + #region ReadAndValidateChannellibFile + private bool ReadAndValidateChannellibFile(string path, out byte[] data, out int recordSize, out int recordCount) + { + data = null; + recordSize = 0; + recordCount = 0; + if (!File.Exists(path)) - return null; + return false; - var data = File.ReadAllBytes(path); + data = File.ReadAllBytes(path); if (data.Length < 20) - return null; + return false; - var recordSize = BitConverter.ToInt32(data, 8); - var recordCount = BitConverter.ToInt32(data, 12); + recordSize = BitConverter.ToInt32(data, 8); + recordCount = BitConverter.ToInt32(data, 12); if (data.Length != 20 + recordCount * recordSize) throw new FileLoadException("Unsupported file content: " + path); this.dataFilePaths.Add(path); - - int baseOffset = 20; - for (int i = 0; i < recordCount; i++, baseOffset += recordSize) - { - uint checksum = BitConverter.ToUInt32(data, baseOffset + 0); - ushort progNr = BitConverter.ToUInt16(data, baseOffset + 122); - byte locked = data[baseOffset + 140]; - int nameLen; - for (nameLen=0; nameLen<64; nameLen+=2) - if (data[baseOffset + 216 + nameLen] == 0) - break; - string channelName = Encoding.Unicode.GetString(data, baseOffset + 216, nameLen); - - data[baseOffset + 0] = 0; - data[baseOffset + 1] = 0; - data[baseOffset + 2] = 0; - data[baseOffset + 3] = 0; - var crc = FaultyCrc32(data, baseOffset, recordSize); - - if (crc != checksum) - throw new FileLoadException($"Invalid CRC in record {i} in {path}"); - - var ch = new ChannelInfo(list.SignalSource, i, progNr, channelName); - ch.Lock = locked != 0; - this.DataRoot.AddChannel(list, ch); - } - - return data; + return true; } - #endregion + #endregion #region LoadDvbsSatellites() private void LoadDvbsSatellites(string path) @@ -397,13 +475,70 @@ namespace ChanSort.Loader.PhilipsBin #region Save() public override void Save(string tvOutputFile) { - var dir = Path.GetDirectoryName(this.FileName); + var dir = Path.GetDirectoryName(this.FileName) ?? ""; + var channellib = Path.Combine(dir, "channellib"); + var s2channellib = Path.Combine(dir, "s2channellib"); - // TODO: save cable and antenna channels + SaveDvbCTChannels(this.dvbtChannels, Path.Combine(channellib, "AntennaDigSrvTable")); + SaveDvbCTPresets(this.dvbtChannels, Path.Combine(channellib, "AntennaPresetTable")); + SaveDvbCTChannels(this.dvbcChannels, Path.Combine(channellib, "CableDigSrvTable")); + SaveDvbCTPresets(this.dvbcChannels, Path.Combine(channellib, "CablePresetTable")); - SaveDvbsChannels(Path.Combine(dir, "s2channellib", "service.dat")); - SaveDvbsFavorites(Path.Combine(dir, "s2channellib", "favorite.dat")); - SaveDvbsDbFileInfo(Path.Combine(dir, "s2channellib", "db_file_info.dat")); + SaveDvbsChannels(Path.Combine(s2channellib, "service.dat")); + SaveDvbsFavorites(Path.Combine(s2channellib, "favorite.dat")); + SaveDvbsDbFileInfo(Path.Combine(s2channellib, "db_file_info.dat")); + } + + #endregion + + #region SaveDvbCTChannels + private void SaveDvbCTChannels(ChannelList list, string path) + { + if (!ReadAndValidateChannellibFile(path, out var data, out var recordSize, out _)) + return; + + var mapping = new DataMapping(this.ini.GetSection("CableDigSrvTable_entry")); + mapping.SetDataPtr(data, 20); + foreach (var ch in list.Channels) + { + mapping.BaseOffset = 20 + (int)ch.RecordIndex * recordSize; + mapping.SetWord("offProgNr", ch.NewProgramNr); + mapping.SetByte("offLocked", ch.Lock ? 1 : 0); + mapping.SetByte("offIsFav", ch.Favorites == 0 ? 0 : 1); + + mapping.SetDword("offChecksum", 0); + var crc = FaultyCrc32(data, mapping.BaseOffset, recordSize); + mapping.SetDword("offChecksum", crc); + } + + File.WriteAllBytes(path, data); + } + #endregion + + #region SaveDvbCTPresets + private void SaveDvbCTPresets(ChannelList list, string path) + { + if (!ReadAndValidateChannellibFile(path, out var data, out var recordSize, out _)) + return; + + var mapping = new DataMapping(this.ini.GetSection("CablePresetTable_entry")); + mapping.SetDataPtr(data, 20); + + + // update the preset records with new channel numbers + foreach (var chan in list.Channels) + { + if (!(chan is Channel ch) || ch.PresetTableIndex < 0) + continue; + mapping.BaseOffset = 20 + ch.PresetTableIndex * recordSize; + mapping.SetWord("offProgNr", ch.NewProgramNr); + + mapping.SetDword("offChecksum", 0); + var crc = FaultyCrc32(data, mapping.BaseOffset, recordSize); + mapping.SetDword("offChecksum", crc); + } + + File.WriteAllBytes(path, data); } #endregion diff --git a/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h index 443fdc1..1a8d638 100644 --- a/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h +++ b/source/Information/FileStructures_for_HHD_Hex_Editor_Neo/philips_dat.h @@ -1,5 +1,9 @@ #include "chansort.h" +/*********************************************************** + * s2channellib / DVB-S files + ***********************************************************/ + struct Ph_NextPrevTableEntry { word next; @@ -134,43 +138,131 @@ public struct Ph_FavoriteDat dword crc32; }; +/*********************************************************** + * channellib / antenna and cable files + ***********************************************************/ public struct Ph_CableDigSrvTable { - byte unk1[8]; + dword unk1; + dword unk2; dword chRecordSize; dword channelCount; - byte unk2[4]; + dword versionCode; struct Ph_CableChannel { var off0 = current_offset; dword checksum; - byte unk1[110]; + byte unk1[20]; + dword symbolRate; + byte unk2[20]; + struct + { + word subFreq : 4; + word mhz : 12; + } freqTimes16; + word onid; + word sid; + word tsid; + byte unk3b[58]; + word progNrMostly; + byte unk4[6]; word progNr; - byte unk2[6]; - word progNr2; - byte unk2b[16]; + byte unk5[22]; byte locked; - byte unk3[75]; + byte isFav; + byte unk6[68]; wchar_t channelName[32]; byte unk4[chRecordSize - (current_offset - off0)]; } Channels[channelCount]; }; -public struct Ph_CablePresetTable +public struct Ph_CableDigTSTable { - byte unk1[8]; + dword unk1; + dword unk2; dword recordSize; dword recordCount; - byte unk2[4]; + dword versionCode; + struct Ph_CableChannel + { + var off0 = current_offset; + dword checksum; + dword unk1; + dword unk2; + struct + { + dword subFreq :4; + dword mhz : 28; + } freqInMhzTimes16a; + dword unk3; + dword unk4; + dword unk5; + dword symbolRate; + dword unk6; + dword unk7; + struct + { + dword subFreq :4; + dword mhz : 28; + } freqInMhzTimes16b; + word onid; + word nid; + word tsid; + word unk10; + byte unk4[recordSize - (current_offset - off0)]; + } Transponders[recordCount]; +}; + +public struct Ph_CableFrqMapTable +{ + dword unk1; + dword unk2; + dword recordSize; + dword recordCount; + dword versionCode; + struct + { + var off0 = current_offset; + dword checksum; + struct + { + dword subFreq :4; + dword mhz : 28; + } freqInMhzTimes16; + dword symbolRate; + dword unk2; + word onid; + word tsid; + word unk5; + word unk6; + dword unk7; + byte unk[recordSize - (current_offset - off0)]; + } Transponders[recordCount]; +}; + +public struct Ph_CablePresetTable +{ + dword unk1; + dword unk2; + dword recordSize; + dword recordCount; + dword versionCode; struct { var off0 = current_offset; - byte unk1[12]; - word unk2; + dword checksum; + struct + { + dword subFreq :4; + dword mhz : 28; + } freqInMhzTimes16; + word progNr; word unk3; word unk4; - word unk5; + word onid; + word tsid; + word sid; byte unk[recordSize - (current_offset - off0)]; } ChanPreset[recordCount]; }; diff --git a/source/Test.Loader.PhilipsBin/PhilipsChannellibTest.cs b/source/Test.Loader.PhilipsBin/PhilipsChannellibTest.cs new file mode 100644 index 0000000..c612517 --- /dev/null +++ b/source/Test.Loader.PhilipsBin/PhilipsChannellibTest.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using System.Linq; +using ChanSort.Api; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Test.Loader.PhilipsBin +{ + [TestClass] + public class PhilipsChannellibTest + { + [TestMethod] + public void TestFiles1() + { + var baseDir = Path.GetDirectoryName(this.GetType().Assembly.Location); + var baseFile = Path.Combine(baseDir, "TestFiles1\\Repair\\ChannelList\\chanLst.bin"); + var plugin = new ChanSort.Loader.PhilipsBin.SerializerPlugin(); + var loader = plugin.CreateSerializer(baseFile); + loader.Load(); + + var list = loader.DataRoot.GetChannelList(SignalSource.DvbC); + Assert.AreEqual(179, list.Channels.Count); + Assert.AreEqual(179, list.Channels.Count(ch => !ch.IsDeleted)); + + var ch0 = list.Channels.FirstOrDefault(ch => ch.RecordIndex == 0); + Assert.AreEqual(41, ch0.OldProgramNr); + Assert.AreEqual("Passion HD", ch0.Name); + Assert.IsFalse(ch0.Lock); + Assert.AreEqual((Favorites)0, ch0.Favorites); + Assert.AreEqual(810, ch0.FreqInMhz); + Assert.AreEqual(9999, ch0.OriginalNetworkId); + Assert.AreEqual(461, ch0.TransportStreamId); + Assert.AreEqual(46102, ch0.ServiceId); + Assert.AreEqual(6900, ch0.SymbolRate); + } + } +} diff --git a/source/Test.Loader.PhilipsBin/Test.Loader.PhilipsBin.csproj b/source/Test.Loader.PhilipsBin/Test.Loader.PhilipsBin.csproj index e0b81b1..ee48b8e 100644 --- a/source/Test.Loader.PhilipsBin/Test.Loader.PhilipsBin.csproj +++ b/source/Test.Loader.PhilipsBin/Test.Loader.PhilipsBin.csproj @@ -69,172 +69,173 @@ + - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always - PreserveNewest + Always diff --git a/source/Test.Loader.PhilipsBin/TestFiles1/Repair/ChannelList/chanLst.bin b/source/Test.Loader.PhilipsBin/TestFiles1/Repair/ChannelList/chanLst.bin index 71f4578e69bfd307e86b61cbc4cde41cdd63055a..556829567341f590938a1072daa1fb679db20c8b 100644 GIT binary patch delta 13 UcmbQvI-PYwBNOM{jm;uV03gQ%d;kCd delta 13 UcmbQvI-PYwBa_I2jm;uV03X-{N&o-= diff --git a/source/Test.Loader.PhilipsBin/TestFiles1/Repair/ChannelList/channellib/CableDigSrvTable b/source/Test.Loader.PhilipsBin/TestFiles1/Repair/ChannelList/channellib/CableDigSrvTable index 48c48a153a7f04c1cb885b979eb2369eb6cab3da..9f1f508573870a137cbb6e56ea15911e34c1332b 100644 GIT binary patch delta 3482 zcmYk8dsI~Q7RNg=#Gpb5ibq5gw3>)?Do{u>GR4VjJQQWjH_c*=a1qptvGj7y_kiIr zZd)_y%|J|tdhEQN2m4HDZGXgrtz7Uvcb>^fHXrH&s9hQU`E8;_!c zn|(sTufwsnU%)g<4o03%a#*sw7ooxn zoeY}+_r1KCjZnC_Z$I9^K5LnOh4)hN6t>+*7NeDB^iuZ8>4RA?!N-k#mSg@oNtbSe zVJypk3`Mf!Wrr}>CHm$a^@Abx4LPX&sNC15&HfdkbbPVcn7+hYwfU)UNIC8m*c1wq zzH!<;C{nyt%G~*5P{+?o_9L{)sK+NVD$aw_|DjGb7Dc6zJ6}bSETDv03KKJdbBG7S zyZ1#`^NmOQQKg%h1BX)Jmx#8r``dX`9mrP1Xb^V;He$Vl`vE*%r~B)N;*vp zn*XnlvpLDzzlWRz{*w+iG=g+!BY%JamdbP*t*`d-{h*xj%pP@X)8rHs$-+pKA3UWE zp?MY^BCb5b9fCi*E1s(C@8&(IlKz8Ko(|)dBQ(zWL#J}blg+&tI2ZVEI)0xb;v1nz z5yzRkU{p=G_>l~uCaRE~L8zSN{J99Q&85kXoz{rq&iz8RJNmJE3haLWP?ANle{| zU|LD0flFqDqe_!d51T`GOMGi4{)#}Bcvc*Sys4Aw!LaIvSnl-3#ag_$>SS>Qi&NHA zILLlc zVdKzHv6+h1r02R3TDK6aK6I};N z`l=;6(Ny{k1yH`V^;1aAY-F|m5OaOaXLS&oDzr53wdWSOAhc%DN%dQXG=p+C)x$FO zgN>+??IF{fJ)K`buwD-4Z(r(glBtEkdt%jGCELSMCH;=JWKnXf4WV%_CET~E;2`k- zrKsPfc{HQS`aPLWiMrGV${(n1F}CJT2tLRw>IbED97Skc-^J}QG{&ZER8ivgAQO2LR05nG_-?a_a z!wgaz%6lpr2YwgKC+Mm2=-XBgFw%QmZO!B#?BPChf_9;%Gxr20>On+rlkCBgdGXAWShBGYTz;!dMVQ$wn?(QD2il$i-e z22I}xh6`k>DIYZrESFhsmq=AP;!p#FRI}NCb$Fr5SX-lu6Q1N|sdgPQMIDWN*3M5> zg6TJ!jWhE?w}bK~+aABuo<8f_&!egA&l>v+pQI@-0mJ`jAT!m4Z75P(?9I&o<2rjA z-S;WhLu!NgZxGp6K2AuaiB{XhTsGSS=d#`qt)p);>zZG>qc9;^Gxd=lv!Ba1+!lXS z{KG4yC{o|4)2TdhkmlT~@_{pGE$$NR6*@7j-IcEbsF{?>s-uOE}J6@4Don z7(j1zlC$}29zDJdL&`WWP5~H6WRS)5)_g|F< z^yn=v98F$X?D6YDey?*U+1{Uo3BI@dpC&y&l%rmsYVSHSP-4B+oulqJR{YqZH;v)s zdd)CSeBs)&LN4fS!#S~OcSeUc4FI{`Jc6USm3rmz+i%Z$G8Ccyayqwe~Q_ pDL~K}y(voa@@w%zY=sYJw$I1RtsRX6lro<(`AXr79DT(W{2xRL=$`-p literal 74484 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 hA2a(?$$TnXgioUUfDq;!`#$MBj5