From a75d2190269e1d092e6f0552e8fc162e503b262d Mon Sep 17 00:00:00 2001 From: Horst Beham Date: Sun, 11 Apr 2021 12:08:47 +0200 Subject: [PATCH] added support for loading and saving ChannelMap_30 format including favorites --- source/ChanSort.Api/ChanSort.Api.csproj | 4 + source/ChanSort.Api/Model/ChannelInfo.cs | 42 ++- .../BinarySerializer.cs | 313 ++++++++++++++++-- source/ChanSort.Loader.Philips/Channel.cs | 6 + .../ChanSort.Loader.Philips/PhilipsPlugin.cs | 15 +- 5 files changed, 323 insertions(+), 57 deletions(-) diff --git a/source/ChanSort.Api/ChanSort.Api.csproj b/source/ChanSort.Api/ChanSort.Api.csproj index 3131f22..3d315a1 100644 --- a/source/ChanSort.Api/ChanSort.Api.csproj +++ b/source/ChanSort.Api/ChanSort.Api.csproj @@ -24,6 +24,7 @@ prompt 4 false + latest pdbonly @@ -33,6 +34,7 @@ prompt 4 false + latest true @@ -43,6 +45,7 @@ prompt false false + latest ..\Release\ @@ -52,6 +55,7 @@ x86 prompt false + latest diff --git a/source/ChanSort.Api/Model/ChannelInfo.cs b/source/ChanSort.Api/Model/ChannelInfo.cs index 867f6bd..4cbeeac 100644 --- a/source/ChanSort.Api/Model/ChannelInfo.cs +++ b/source/ChanSort.Api/Model/ChannelInfo.cs @@ -146,6 +146,24 @@ namespace ChanSort.Api #endregion #region Uid + + public static string GetUid(SignalSource signalSource, decimal freqInMhz, int onid, int tsid, int sid, string channelOrTransponder) + { + if ((signalSource & SignalSource.Analog) != 0) + return "A-0-" + (int)(freqInMhz * 20) + "-0"; + if ((signalSource & SignalSource.MaskAntennaCableSat) == SignalSource.Sat) + return "S" + /*this.SatPosition + */ "-" + onid + "-" + tsid + "-" + sid; + if ((signalSource & SignalSource.MaskAntennaCableSat) == SignalSource.Antenna || (signalSource & SignalSource.MaskAntennaCableSat) == SignalSource.Cable) + { + // ChannelOrTransponder is needed for DVB-T where the same ONID+TSID+SID can be received from 2 different radio transmitters, but on different frequencies/channels + if (string.IsNullOrEmpty(channelOrTransponder)) + channelOrTransponder = ((signalSource & SignalSource.Antenna) != 0 ? LookupData.Instance.GetDvbtTransponder(freqInMhz) : LookupData.Instance.GetDvbcTransponder(freqInMhz)).ToString(); + return "C-" + onid + "-" + tsid + "-" + sid + "-" + channelOrTransponder; + } + + return onid + "-" + tsid + "-" + sid; + } + /// /// The Uid is the preferred way of matching channels between the current channel list and a reference list. /// The basic format of this string was taken from a command line tool "TllSort" for LG TVs but then expanded beyond that @@ -153,28 +171,8 @@ namespace ChanSort.Api /// public string Uid { - get - { - if (this.uid == null) - { - if ((this.SignalSource & SignalSource.Analog) != 0) - this.uid = "A-0-" + (int) (this.FreqInMhz*20) + "-0"; - else - { - if ((this.SignalSource & SignalSource.MaskAntennaCableSat) == SignalSource.Sat) - this.uid = "S" + /*this.SatPosition + */ "-" + this.OriginalNetworkId + "-" + this.TransportStreamId + "-" + this.ServiceId; - else if ((this.SignalSource & SignalSource.MaskAntennaCableSat) == SignalSource.Antenna || (this.SignalSource & SignalSource.MaskAntennaCableSat) == SignalSource.Cable) - { - // ChannelOrTransponder is needed for DVB-T where the same ONID+TSID+SID can be received from 2 different radio transmitters, but on different frequencies/channels - this.uid = "C-" + this.OriginalNetworkId + "-" + this.TransportStreamId + "-" + this.ServiceId + "-" + this.ChannelOrTransponder; - } - else - this.uid = this.OriginalNetworkId + "-" + this.TransportStreamId + "-" + this.ServiceId; - } - } - return this.uid; - } - set { this.uid = value; } + get => this.uid ??= GetUid(this.SignalSource, this.FreqInMhz, this.OriginalNetworkId, this.TransportStreamId, this.ServiceId, this.ChannelOrTransponder); + set => this.uid = value; } #endregion diff --git a/source/ChanSort.Loader.Philips/BinarySerializer.cs b/source/ChanSort.Loader.Philips/BinarySerializer.cs index e5c93e2..93fd26d 100644 --- a/source/ChanSort.Loader.Philips/BinarySerializer.cs +++ b/source/ChanSort.Loader.Philips/BinarySerializer.cs @@ -12,7 +12,9 @@ namespace ChanSort.Loader.Philips { /* - This loader handles the file format versions 1.x (*Table and *.dat files) and version 25.x-45.x (*Db.bin files + tv.db and list.db) + This loader handles the file format versions 1.x (*Table and *.dat files), version 25.x-45.x (*Db.bin files + tv.db and list.db) + Version 30.x and 45.x were tested, version 25 is untested due to the lack of any sample files. Based on what I read online, the files are pretty much the + same as with Format 45. channellib\CableDigSrvTable: =========================== @@ -40,6 +42,10 @@ namespace ChanSort.Loader.Philips 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. + channellib\CableChannelMaps.db + ============================== + Used in format version 30 (not 45) as a 3rd file containing program numbers. SQLite database containing tables "AnalogTable" and "DigSrvTable". + */ class BinarySerializer : SerializerBase { @@ -60,6 +66,9 @@ namespace ChanSort.Loader.Philips private const int FavListCount = 8; private bool mustFixFavListIds; + private readonly Dictionary channelsById = new(); + + #region ctor() public BinarySerializer(string inputFile) : base(inputFile) { @@ -80,7 +89,9 @@ namespace ChanSort.Loader.Philips } #endregion - #region Load() + // loading + + #region Load public override void Load() { this.chanLstBin = new ChanLstBin(); @@ -116,33 +127,46 @@ namespace ChanSort.Loader.Philips else if (chanLstBin.VersionMajor >= 25 && chanLstBin.VersionMajor <= 45) { // version 25-45 - this.favChannels.IsMixedSourceFavoritesList = true; this.Features.CanHaveGaps = true; this.DataRoot.AddChannelList(this.antChannels); this.DataRoot.AddChannelList(this.cabChannels); this.DataRoot.AddChannelList(this.satChannels); - this.DataRoot.AddChannelList(this.favChannels); + + // version 45 supports mixed source favorites, version 30 (and probably 25) have separate fav lists per input source + if (chanLstBin.VersionMajor == 45) + { + this.favChannels.IsMixedSourceFavoritesList = true; + this.DataRoot.AddChannelList(this.favChannels); + } LoadDvbCT(antChannels, Path.Combine(channellib, "TerrestrialDb.bin"), "Map45_CableDb.bin_entry"); LoadDvbCT(cabChannels, Path.Combine(channellib, "CableDb.bin"), "Map45_CableDb.bin_entry"); LoadDvbS(satChannels, Path.Combine(s2channellib, "SatelliteDb.bin"), "Map45_SatelliteDb.bin_entry"); - + var tvDbFile = Path.Combine(dir, "tv.db"); if (File.Exists(tvDbFile)) { this.dataFilePaths.Add(tvDbFile); - this.LoadMap45Channels(tvDbFile); + this.LoadTvDb(tvDbFile); } + LoadMap30ChannelMapsDb(antChannels, Path.Combine(channellib, "TerrestrialChannelMaps.db")); + LoadMap30ChannelMapsDb(cabChannels, Path.Combine(channellib, "CableChannelMaps.db")); + LoadMap30ChannelMapsDb(satChannels, Path.Combine(s2channellib, "SatelliteChannelMaps.db")); + + // favorites in "list.db" have different schema depending on version var listDbFile = Path.Combine(dir, "list.db"); if (File.Exists(listDbFile)) { + if (chanLstBin.VersionMajor == 30) + this.LoadMap30Favorites(listDbFile); + else if (chanLstBin.VersionMajor == 45) + this.LoadMap45Favorites(listDbFile); this.dataFilePaths.Add(listDbFile); - this.LoadMap45Favorites(listDbFile); } - foreach(var list in this.DataRoot.ChannelLists) + foreach (var list in this.DataRoot.ChannelLists) list.VisibleColumnFieldNames.Add(nameof(ChannelInfo.Encrypted)); satChannels.VisibleColumnFieldNames.Add(nameof(ChannelInfo.Polarity)); } @@ -181,8 +205,7 @@ namespace ChanSort.Loader.Philips } #endregion - - + #region LoadDvbCT private void LoadDvbCT(ChannelList list, string path, string mappingName) { @@ -326,7 +349,7 @@ namespace ChanSort.Loader.Philips #endregion - #region LoadDvbsSatellites() + #region LoadDvbsSatellites private void LoadDvbsSatellites(string path) { if (!File.Exists(path)) @@ -568,13 +591,75 @@ namespace ChanSort.Loader.Philips } #endregion - #region LoadMap45Channels - private void LoadMap45Channels(string tvDb) + #region LoadMap30ChannelMapsDb + private void LoadMap30ChannelMapsDb(ChannelList list, string dbPath) { - // the only purpose of this method is to validate if numbers in the SatelliteDb.bin file are the same as in the list.db file - // differences are written to the log which can be viewed under File / File information + // map30 format keeps channel numbers in 3 redundant locations: tv.db, a .bin file and a *ChannelMaps.db file + // here we read the ChannelMaps.db file, compare the data with the .bin file and keep a reference to the records for later update + if (!File.Exists(dbPath)) + return; + this.dataFilePaths.Add(dbPath); + + using var conn = new SQLiteConnection($"Data Source={dbPath}"); + conn.Open(); + using var cmd = conn.CreateCommand(); + + var queries = new[] + { + new Tuple(SignalSource.Analog | (list.SignalSource & SignalSource.MaskAntennaCableSat), "AnalogTable", + "select Dbindex, Frequency, 0, 0, 0, ChannelName, PresetNumber from AnalogTable order by Dbindex"), + new Tuple(SignalSource.Digital | (list.SignalSource & SignalSource.MaskAntennaCableSat), "DigSrvTable", + "select Dbindex, Frequency, OriginalNetworkId, Tsid, ServiceId, ChannelName, PresetNumber from DigSrvTable order by Dbindex") + }; + + foreach (var entry in queries) + { + var source = entry.Item1; + var table = entry.Item2; + var query = entry.Item3; + + // not all files contain an AnalogTable table + cmd.CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'"; + if ((long) cmd.ExecuteScalar() == 0) + continue; + + cmd.CommandText = query; + using var r = cmd.ExecuteReader(); + while (r.Read()) + { + var idx = r.GetInt32(0); + var freq = (decimal) r.GetInt32(1) / 1000; + var onid = r.GetInt32(2); + var tsid = r.GetInt32(3); + var sid = r.GetInt32(4); + var name = r.GetString(5); + var prnr = r.GetInt32(6); + + var uid = ChannelInfo.GetUid(source, freq, onid, tsid, sid, null); + var chans = list.GetChannelByUid(uid); + if (chans == null || chans.Count == 0) + this.logMessages.AppendLine($"{dbPath}: {table} entry with Dbindex={idx} has no corresponding channel in *.bin files"); + else + { + var ch = (Channel) chans[0]; + ch.Map30ChannelMapsDbindex = idx; + if (ch.Name != name) + this.logMessages.AppendLine($"{dbPath}: {table} entry with Dbindex={idx} has name {name}, in .bin file it is {ch.Name}"); + if (ch.OldProgramNr != prnr) + this.logMessages.AppendLine($"{dbPath}: {table} entry with Dbindex={idx} has PresetNumber {prnr}, in .bin file it is {ch.OldProgramNr}"); + } + } + } + } + #endregion + + #region LoadTvDb + private void LoadTvDb(string tvDb) + { + // the only purpose of this method is to validate if numbers in the the tv.db file are the same as in CableDb.bin/Terrestrial.bin/SatelliteDb.bin + // differences are written to the log which can be viewed under File / File information + // The tv.db file exists in formats 30 and 45 (and unconfirmed in 25 too) - var channelsById = new Dictionary(); foreach (var chanList in this.DataRoot.ChannelLists) { if (chanList.IsMixedSourceFavoritesList) @@ -634,6 +719,58 @@ namespace ChanSort.Loader.Philips } #endregion + #region LoadMap30Favorites + private void LoadMap30Favorites(string listDb) + { + // The "list.db" file in format 30 contains tables TList1-4, CList1-4 and SList1-4 for 4 favorite favorite lists for cable, satellite and terrestrial + // It also contains a table "List" with 12 entries holding names for the 3*4 favorite lists and SatFrequency and TCFrequency for satellite/terrestrial/cable frequencies + // The list.db:xListN.channel_id field references the tv.db:channels._id field + + if (!File.Exists(listDb) || this.channelsById.Count == 0) + return; + + this.Features.FavoritesMode = FavoritesMode.OrderedPerSource; + this.Features.MaxFavoriteLists = 4; + + using var conn = new SQLiteConnection($"Data Source={listDb}"); + conn.Open(); + using var cmd = conn.CreateCommand(); + + // read favorite list names - disabled because currently ChanSort does not support different names for Fav1-4 based on input source + //cmd.CommandText = "select list_id, list_name from List order by list_id"; + //using (var r = cmd.ExecuteReader()) + //{ + // while (r.Read()) + // { + // var id = r.GetInt32(0); + // var name = r.GetString(1); + // this.DataRoot.SetFavListCaption(id - 1, name); + // } + //} + + // read favorite channels + for (int listIdx=0; listIdx<12; listIdx++) + { + var table = "TCS"[listIdx / 4] + "List" + (listIdx % 4 + 1); // TList1-4, CList1-4, SList1-4 + cmd.CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'"; + if ((long) cmd.ExecuteScalar() == 0) + continue; + + cmd.CommandText = $"select _id, channel_id from {table} order by rank"; + using var r = cmd.ExecuteReader(); + int order = 0; + while (r.Read()) + { + int channelId = r.GetInt32(1); + if (this.channelsById.TryGetValue(channelId, out var ch)) + ch.SetOldPosition(1 + listIdx%4, ++order); + else + this.logMessages.AppendLine($"list.db: {table} _id {r.GetInt32(0)} references non-existing channel with channel_id {channelId}"); + } + } + } + #endregion + #region LoadMap45Favorites private void LoadMap45Favorites(string listDb) { @@ -689,7 +826,7 @@ namespace ChanSort.Loader.Philips using var r = cmd.ExecuteReader(); int seq = 0; while (r.Read()) - { + { var channelId = r.GetInt32(0); var chan = this.favChannels.Channels.FirstOrDefault(c => ((Channel)c).Id == channelId); if (chan == null) @@ -704,15 +841,7 @@ namespace ChanSort.Loader.Philips } #endregion - - #region GetDataFilePaths - - /// - /// List of files for backup/restore - /// - public override IEnumerable GetDataFilePaths() => this.dataFilePaths; - #endregion - + // saving #region Save() public override void Save(string tvOutputFile) @@ -738,8 +867,18 @@ namespace ChanSort.Loader.Philips SaveDvbCTChannels(this.cabChannels, Path.Combine(channellib, "CableDb.bin")); SaveDvbsChannels(this.satChannels, Path.Combine(s2channellib, "SatelliteDb.bin")); - UpdateChannelMap45TvDb(); - UpdateChannelMap45ListDb(); + SaveMap30ChannelMapsDb(this.antChannels, Path.Combine(channellib, "TerrestrialChannelMaps.db")); + SaveMap30ChannelMapsDb(this.cabChannels, Path.Combine(channellib, "CableChannelMaps.db")); + SaveMap30ChannelMapsDb(this.satChannels, Path.Combine(s2channellib, "SatelliteChannelMaps.db")); + + SaveTvDb(); + + // favorite lists have different DB schema depending on version + var listDb = Path.Combine(dir, "list.db"); + if (chanLstBin.VersionMajor == 30) + SaveMap30Favorites(listDb); + else if (chanLstBin.VersionMajor == 45) + SaveMap45Favorites(listDb); } this.chanLstBin.Save(this.FileName); @@ -950,8 +1089,48 @@ namespace ChanSort.Loader.Philips } #endregion - #region UpdateChannelMap45TvDb() - private void UpdateChannelMap45TvDb() + #region SaveMap30ChannelMapsDb + private void SaveMap30ChannelMapsDb(ChannelList list, string dbPath) + { + // map30 format keeps channel numbers in 3 redundant locations: tv.db, a .bin file and a *ChannelMaps.db file + // here we save the ChannelMaps.db file + if (!File.Exists(dbPath)) + return; + + using var conn = new SQLiteConnection($"Data Source={dbPath}"); + conn.Open(); + using var trans = conn.BeginTransaction(); + using var cmd = conn.CreateCommand(); + + var tables = new[] {"AnalogTable", "DigSrvTable"}; + foreach (var table in tables) + { + // not all files contain an AnalogTable table + cmd.CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'"; + if ((long)cmd.ExecuteScalar() == 0) + continue; + + cmd.CommandText = $"update {table} set PresetNumber = @prNum where Dbindex = @dbindex"; + cmd.Parameters.Add("@prNum", DbType.String); + cmd.Parameters.Add("@dbindex", DbType.Int32); + foreach(var channel in list.Channels) + { + if (!(channel is Channel ch) || ch.Map30ChannelMapsDbindex < 0) + continue; + cmd.Parameters["@dbindex"].Value = ch.Map30ChannelMapsDbindex; + cmd.Parameters["@prNum"].Value = ch.NewProgramNr.ToString(); + cmd.ExecuteNonQuery(); + } + } + trans.Commit(); + } + #endregion + + #region SaveTvDb + /// + /// The "tv.db" file was reported to exist as early as in ChannelMap_25 format and has been seen in formats 30 and 45 too + /// + private void SaveTvDb() { var tvDb = Path.Combine(Path.GetDirectoryName(this.FileName) ?? "", "tv.db"); if (!File.Exists(tvDb)) @@ -991,10 +1170,62 @@ namespace ChanSort.Loader.Philips } #endregion - #region UpdateChannelMap45ListDb() - private void UpdateChannelMap45ListDb() + #region SaveMap30Favorites + + private void SaveMap30Favorites(string listDb) + { + if (!File.Exists(listDb) || this.channelsById.Count == 0) + return; + + using var conn = new SQLiteConnection($"Data Source={listDb}"); + conn.Open(); + using var cmd = conn.CreateCommand(); + using var trans = conn.BeginTransaction(); + + // TODO change data model so we can support different fav list names in each list and not require same name for Fav1-4 in each input source; then save the names in the "List" table + + // save favorite channels + for (int listIdx = 0; listIdx < 12; listIdx++) + { + var table = "TCS"[listIdx / 4] + "List" + (listIdx % 4 + 1); // TList1-4, CList1-4, SList1-4 + cmd.CommandText = $"select count(1) from sqlite_master where type='table' and name='{table}'"; + if ((long)cmd.ExecuteScalar() == 0) + continue; + + cmd.CommandText = $"delete from {table}"; + cmd.ExecuteNonQuery(); + + cmd.CommandText = $"insert into {table} (_id, channel_id, rank) values (@id, @channelId, @rank)"; + cmd.Parameters.Add("@id", DbType.Int32); + cmd.Parameters.Add("@channelId", DbType.Int32); + cmd.Parameters.Add("@rank", DbType.Int32); + + int order = 0; + var list = listIdx < 4 ? this.antChannels : listIdx < 8 ? this.cabChannels : this.satChannels; + foreach (var channel in list.Channels) + { + if (!(channel is Channel ch)) + continue; + + var favPos = ch.GetPosition(1 + listIdx % 4); + if (favPos < 0) + continue; + + ++order; + cmd.Parameters["@id"].Value = order; + cmd.Parameters["@channelId"].Value = ch.Id; + cmd.Parameters["@rank"].Value = order - 1; + cmd.ExecuteNonQuery(); + } + } + + trans.Commit(); + } + #endregion + + #region SaveMap45Favorites + private void SaveMap45Favorites(string listDb) { - var listDb = Path.Combine(Path.GetDirectoryName(this.FileName) ?? "", "list.db"); if (!File.Exists(listDb)) return; @@ -1063,8 +1294,12 @@ namespace ChanSort.Loader.Philips } #endregion + // common #region FaultyCrc32 + /// + /// Philips uses a broken CRC32 implementation, so we can't use the ChanSort.Api.Utils.Crc32 code + /// public static uint FaultyCrc32(byte[] bytes, int start, int count) { var crc = 0xFFFFFFFF; @@ -1088,9 +1323,21 @@ namespace ChanSort.Loader.Philips #endregion + // framework support methods + + #region GetDataFilePaths + + /// + /// List of files for backup/restore + /// + public override IEnumerable GetDataFilePaths() => this.dataFilePaths; + #endregion + + #region GetFileInformation public override string GetFileInformation() { return base.GetFileInformation() + this.logMessages.Replace("\n", "\r\n"); } + #endregion } } diff --git a/source/ChanSort.Loader.Philips/Channel.cs b/source/ChanSort.Loader.Philips/Channel.cs index c2d9ac5..3af3835 100644 --- a/source/ChanSort.Loader.Philips/Channel.cs +++ b/source/ChanSort.Loader.Philips/Channel.cs @@ -23,11 +23,17 @@ namespace ChanSort.Loader.Philips /// public int PresetTableIndex { get; set; } = -1; + public int Map30ChannelMapsDbindex { get; set; } = -1; + // fields relevant for ChannelMap_100 and later (XML nodes) public readonly XmlNode SetupNode; public string RawName; public string RawSatellite; public int Format; + + /// + /// _id in tv.db:channels; referenced by ChannelMap30 list.db:xListN.channel_id and ChannelMap45 *DB.bin records + /// public int Id; // links entries in the ChannelMap45/*Db.bin files with the entries in the tv.db channels table } diff --git a/source/ChanSort.Loader.Philips/PhilipsPlugin.cs b/source/ChanSort.Loader.Philips/PhilipsPlugin.cs index b791100..97ecdf7 100644 --- a/source/ChanSort.Loader.Philips/PhilipsPlugin.cs +++ b/source/ChanSort.Loader.Philips/PhilipsPlugin.cs @@ -39,9 +39,20 @@ namespace ChanSort.Loader.Philips * PhilipsChannelMaps\ChannelMap_11\ChannelList\s2channellib\Satellite*Table (new here) * e.g. 55PFL8008S/12 * + * version 30.1 + * PhilipsChannelMaps\ChannelMap_30\ChannelList\chanLst.bin + * PhilipsChannelMaps\ChannelMap_30\ChannelList\list.db (for each of the input sources Sat/Cable/Terr 4 separate fav lists) + * PhilipsChannelMaps\ChannelMap_30\ChannelList\tv.db (contains channels from all sources) + * PhilipsChannelMaps\ChannelMap_30\ChannelList\channellib\*ChannelMaps.db (separate files for Cable and Terrestrial) + * PhilipsChannelMaps\ChannelMap_30\ChannelList\channellib\*Db.bin (separate files for Cable and Terrestrial) + * PhilipsChannelMaps\ChannelMap_30\ChannelList\s2channellib\SatelliteChannelMaps.db + * PhilipsChannelMaps\ChannelMap_30\ChannelList\s2channellib\SatelliteDb.bin + * e.g. 40PUK6400/12 + * * version 45.1 * PhilipsChannelMaps\ChannelMap_45\ChannelList\chanLst.bin - * PhilipsChannelMaps\ChannelMap_45\ChannelList\list.db (SQLite database including all channels - maybe just for EPG?) + * PhilipsChannelMaps\ChannelMap_45\ChannelList\list.db (favorite lists for all sources) + * PhilipsChannelMaps\ChannelMap_45\ChannelList\tv.db (SQLite database including all channels - maybe just for EPG?) * PhilipsChannelMaps\ChannelMap_45\ChannelList\channelLib\*Db.bin * PhilipsChannelMaps\ChannelMap_45\ChannelList\s2channellib\*Db.bin * e.g. 65PUS7601/12, 55PUS6581/12, 43PUS6401/12, 55PUS8601/12 @@ -112,7 +123,7 @@ namespace ChanSort.Loader.Philips if (majorVersion == 0 || majorVersion >= 100 && majorVersion <= 110) return new XmlSerializer(inputFile); - if (majorVersion == 1 || majorVersion == 45) // || majorVersion == 11 // format version 11 is similar to 1.x, but not (yet) supported + if (majorVersion == 1 || majorVersion == 30 || majorVersion == 45) // || majorVersion == 11 // format version 11 is similar to 1.x, but not (yet) supported return new BinarySerializer(inputFile); throw new FileLoadException($"Philips ChannelMap format version {majorVersion} is not supported.");